diff --git a/common/celestial_data_service.py b/common/celestial_data_service.py new file mode 100644 index 0000000000000000000000000000000000000000..0db9a052c44927a96ccf051b7d70b791926e07e2 --- /dev/null +++ b/common/celestial_data_service.py @@ -0,0 +1,82 @@ +# -*- coding:utf-8 -*- +# title :天体数据服务类型 +# description :天体数据服务类型 +# author :Python超人 +# date :2023-08-05 +# link :https://gitcode.net/pythoncr/ +# python_version :3.8 +# ============================================================================== + + +def get_body_posvel(body, time=None): + """ + 获取太阳系天体指定时间的位置和矢量速度 + @param body: 天体(天体名称) + @param time: 时间 + @return: + """ + from astropy.coordinates import get_body_barycentric_posvel + from astropy.time import Time + if time is None: + time = Time.now() + if not isinstance(body, str): + body = body.__class__.__name__ + + posvel = get_body_barycentric_posvel(body, time) + + return posvel + + +def get_bodies_posvels(planet_names="sun,mercury,venus,earth,moon,mars,jupiter,saturn,uranus,neptune", time=None): + from astropy.coordinates import get_body_barycentric_posvel + from astropy.time import Time + if time is None: + time = Time.now() + planets = planet_names.split(",") + posvels = {} + for planet in planets: + try: + position, velocity = get_body_barycentric_posvel(planet, time) + posvels[planet] = position, velocity + # print(planet, position) + except Exception as e: + print(planet, str(e)) + return posvels + + +def recalc_moon_position(moon_posvel, earth_pos): + """ + 重新计算月球的位置(由于月球和地球的距离在整个太阳系尺度下非常近,为了较好的显示效果,需要放大月球和地球的距离,但不要改变月球相对地球的位置) + @param moon_posvel: 月球的三维坐标位置和三维矢量速度 + @param earth_pos: 地球的三维坐标位置 + @return: + """ + moon_pos, moon_vel = moon_posvel[0], moon_posvel[1] + moon_pos_to_earth = moon_pos - earth_pos + moon_pos_to_earth = moon_pos_to_earth * 50 + + return moon_pos_to_earth + earth_pos, moon_vel + + +def get_celestial_body_data(body_name): + import ephem + # 创建一个Observer对象,用于指定观测者的位置 + observer = ephem.Observer() + observer.lat = '0' # 观测者的纬度,这里使用0度作为示例 + observer.lon = '0' # 观测者的经度,这里使用0度作为示例 + # 创建一个Date对象,表示当前时间 + current_time = ephem.now() + # 根据天体名称创建一个CelestialBody对象 + body = getattr(ephem, body_name)() + # 计算天体的位置和速度 + body.compute(observer) + # 获取天体的实时位置和速度信息 + position = (body.ra, body.dec) # 天体的赤经和赤纬 + velocity = (body.ra_velocity, body.dec_velocity) # 天体的赤经速度和赤纬速度 + return position, velocity + + # # 示例用法 + # body_name = 'Mars' # 天体名称,这里以火星为例 + # position, velocity = get_celestial_body_data(body_name) + # print(f"The current position of {body_name} is: {position}") + # print(f"The current velocity of {body_name} is: {velocity}") diff --git a/sim_scenes/solar_system/solar_system_reality.py b/sim_scenes/solar_system/solar_system_reality.py index 470bf4f688b6a24af2adaf80eeabd971cbce5154..28b501e94c10044d02eee80c9c286d092539fb03 100644 --- a/sim_scenes/solar_system/solar_system_reality.py +++ b/sim_scenes/solar_system/solar_system_reality.py @@ -7,15 +7,12 @@ # python_version :3.8 # ============================================================================== # pip install -i http://pypi.douban.com/simple/ --trusted-host=pypi.douban.com de423 -# solar_system_ephemeris.bodies -# ('earth', 'sun', 'moon', 'mercury', 'venus', 'earth-moon-barycenter', 'mars', 'jupiter', 'saturn', 'uranus', 'neptune') import numpy as np -from astropy.coordinates import get_body_barycentric_posvel -from astropy.time import Time from bodies import Sun, Mercury, Venus, Earth, Mars, Asteroids, Jupiter, Saturn, Uranus, Neptune, Moon +from common.celestial_data_service import get_body_posvel, recalc_moon_position from common.consts import SECONDS_PER_WEEK, SECONDS_PER_DAY, AU from sim_scenes.func import ursina_run from simulators.ursina.entities.body_timer import TimeData @@ -25,249 +22,191 @@ from simulators.ursina.ursina_event import UrsinaEvent from ursina import camera, application -def get_bodies_posvels(planet_names="sun,mercury,venus,earth,moon,mars,jupiter,saturn,uranus,neptune", time=None): - if time is None: - time = Time.now() - planets = planet_names.split(",") - posvels = {} - for planet in planets: - try: - position, velocity = get_body_barycentric_posvel(planet, time) - posvels[planet] = position, velocity - # print(planet, position) - except Exception as e: - print(planet, str(e)) - return posvels - - -def recalc_moon_position(moon_posvel, earth_pos): - moon_pos, moon_vel = moon_posvel[0], moon_posvel[1] - moon_pos_to_earth = moon_pos - earth_pos - moon_pos_to_earth = moon_pos_to_earth * 50 - - return moon_pos_to_earth + earth_pos, moon_vel - - -def get_bodies_names(bodies): - names = "" - for body in bodies: - names += body.__class__.__name__ + "," - return names[0:-1] - - -def are_planets_in_line(positions, line_width): - # 检查行星的数量是否足够判断是否在一条线上 - if len(positions) < 3: - return False - # 获取第一个行星的坐标 - x1, y1, z1 = positions[0] - # 计算行星之间的向量差 - dx = positions[1][0] - x1 - dy = positions[1][1] - y1 - dz = positions[1][2] - z1 - # 计算线的宽度的平方 - line_width_squared = line_width ** 2 - # 遍历剩余的行星 - for i in range(2, len(positions)): - # 获取当前行星的坐标 - x, y, z = positions[i] - # 计算当前行星与第一个行星之间的向量差 - current_dx = x - x1 - current_dy = y - y1 - current_dz = z - z1 - # 计算当前行星与线之间的距离的平方 - distance_squared = (current_dy * dz - current_dz * dy) ** 2 + (current_dz * dx - current_dx * dz) ** 2 + ( - current_dx * dy - current_dy * dx) ** 2 - # 如果距离的平方大于线的宽度的平方,则行星不在一条线上 - if distance_squared > line_width_squared: - return False - # 所有行星都在一条线上 - return True - - -def are_planets_in_line(planets, line_width): - if len(planets) < 2: - return False - - x_coords = [planet[0] for planet in planets] - y_coords = [planet[1] for planet in planets] - z_coords = [planet[2] for planet in planets] - - x_diff = [abs(x2 - x1) for (x1, x2) in zip(x_coords, x_coords[1:])] - y_diff = [abs(y2 - y1) for (y1, y2) in zip(y_coords, y_coords[1:])] - z_diff = [abs(z2 - z1) for (z1, z2) in zip(z_coords, z_coords[1:])] - - widths = [max(d1, d2, line_width) for (d1, d2) in zip(x_diff, y_diff)] + [line_width] + [max(d1, d2, line_width) for - (d1, d2) in - zip(x_diff[::-1], - y_diff[::-1])] - - return all(w == widths[0] for w in widths) - - -def are_planets_in_line(planets, line_width): - if len(planets) < 2: - return False - - x_coords = [planet[0] for planet in planets] - y_coords = [planet[1] for planet in planets] - z_coords = [planet[2] for planet in planets] - - x_diff = [abs(x2 - x1) for (x1, x2) in zip(x_coords, x_coords[1:])] - y_diff = [abs(y2 - y1) for (y1, y2) in zip(y_coords, y_coords[1:])] - z_diff = [abs(z2 - z1) for (z1, z2) in zip(z_coords, z_coords[1:])] - - widths = [max(d1, d2, line_width ** 2) for (d1, d2) in zip(x_diff, y_diff)] + [line_width ** 2] + [ - max(d1, d2, line_width ** 2) for (d1, d2) in zip(x_diff[::-1], y_diff[::-1])] - - return all(w == widths[0] for w in widths) - - -current_time = Time.now() - -# in_line_datetimes = [] +class SolarSystemRealitySim: + def __init__(self): + """ + + @param debug_mode: 是否为调试模式 + """ + self.show_asteroids = False + self.clock_position_center = False + self.show_earth_clouds = False + self.debug_mode = False + + def create_bodies(self): + """ + 创建太阳系的天体 + @return: + """ + self.sun_size_scale = 0.04e2 if self.debug_mode else 0.4e2 + self.earth_size_scale = 10e3 if self.debug_mode else 1e3 + + self.sun = Sun(name="太阳", size_scale=self.sun_size_scale) # 太阳 + self.mercury = Mercury(name="水星", size_scale=1.5e3) # 水星 + self.venus = Venus(name="金星", size_scale=1e3) # 金星 + self.earth = Earth(name="地球", texture="earth_hd.jpg", size_scale=self.earth_size_scale) # 地球 + self.earth_clouds = Earth(name="地球云层", texture="transparent_clouds.png", + size_scale=self.earth_size_scale * 1.01) # 地球云层 + self.moon = Moon(name="月球", size_scale=2e3) # 月球 + self.mars = Mars(name="火星", size_scale=1.2e3) # 火星 + self.asteroids = Asteroids(size_scale=1e2, parent=self.sun, rotate_angle=-20) # 模拟的小行星带 + self.jupiter = Jupiter(name="木星", size_scale=4e2) # 木星 + self.saturn = Saturn(name="土星", size_scale=4e2) # 土星 + self.uranus = Uranus(name="天王星", size_scale=10e2) # 天王星 + self.neptune = Neptune(name="海王星", size_scale=10e2) # 海王星 + # 行星 + self.planets = [self.mercury, self.venus, self.earth, self.mars, + self.jupiter, self.saturn, self.uranus, self.neptune] + # 所有天体 + self.bodies = [self.sun] + self.planets + [self.moon] + + if self.show_earth_clouds: + self.bodies += [self.earth_clouds] + + if self.show_asteroids: + self.bodies += [self.asteroids] + + def init_earth(self): + """ + 初始化地球 + @return: + """ + # 让地球显示自转轴线 + self.earth.rotate_axis_color = (255, 255, 50) + # 如果为调试模式,则太阳光对地球无效,方便查看 + if self.debug_mode: + self.earth.set_light_disable(True) + + def on_ready(self): + # 运行前触发 + camera.rotation_z = -20 + if self.debug_mode: + camera.fov = 20 # 调试时,拉近摄像机距离 -if __name__ == '__main__': - # 以下展示的效果为太阳系真实的时间和位置 - # 由于宇宙空间尺度非常大,如果按照实际的天体大小,则无法看到天体,因此需要对天体的尺寸进行放大 - sun = Sun(name="太阳", size_scale=0.4e2) # 太阳放大 40 倍,TODO:调试时的大小,size_scale=0.04e2 - bodies = [ - sun, - Mercury(name="水星", size_scale=1.5e3), # 水星 - Venus(name="金星", size_scale=1e3), # 金星 - Earth(name="地球", - texture="earth_hd.jpg", - size_scale=1e3), # 地球 TODO:调试时的大小,size_scale=10e3 - # Earth(name="地球云层", - # texture="transparent_clouds.png", - # size_scale=1.01e3), # 地球云层 TODO:调试时的大小,size_scale=10.1e3 - Moon(name="月球", size_scale=2e3), # 月球 - Mars(name="火星", size_scale=1.2e3), # 火星 - # Asteroids(size_scale=1e2, parent=sun, rotate_angle=-20), - Jupiter(name="木星", size_scale=4e2), # 木星 - Saturn(name="土星", size_scale=4e2), # 土星 - Uranus(name="天王星", size_scale=10e2), # 天王星 - Neptune(name="海王星", size_scale=10e2), # 海王星 - ] - - earth = bodies[3] - # 显示自转轴线 - earth.rotate_axis_color = (255, 255, 50) - # earth.set_light_disable(True) # TODO:调试时,取消注释 - - names = get_bodies_names(bodies) - names = names.replace("Asteroids,", "") - - - def get_body_posvel(body, posvels=None, time=None): - if posvels is None: - posvel = get_body_barycentric_posvel(body.__class__.__name__, time) - else: - posvel = posvels.get(body.__class__.__name__, None) - return posvel + # 需要按照时间和日期来控制地球的自转,所以删除控制地球自转的属性 + delattr(self.earth.planet, "rotation_speed") + delattr(self.earth.planet, "rotspeed") + # 设置后,可以调整鼠标键盘的控制速度 + application.time_scale = 2 + + def show_timer_text(self, time_data): + dt = time_data.get_datetime(str(self.start_time)) - def on_ready(): - # 运行前触发 - camera.rotation_z = -20 - # camera.fov = 20 # TODO:调试时,取消注释 # 需要按照时间和日期控制地球的自转,不能随意转动 - delattr(earth.planet, "rotation_speed") - delattr(earth.planet, "rotspeed") - application.time_scale = 2 + # 日期是当年的第几天 + timetuple = dt.timetuple() + + # 计算出:日期当天的偏转角度 + day_of_year = timetuple.tm_yday + angle_of_day = day_of_year * (360 / 365) + total_hours = timetuple.tm_hour + timetuple.tm_min / 60 + timetuple.tm_sec / 60 / 60 + self.earth.planet.rotation_y = -total_hours * 15 - angle_of_day + # print(time_data.get_datetime(str(self.start_time))) + if self.clock_position_center: + position, origin = (0, .25), (0, 0), + else: + position, origin = (0.60, -0.465), (-0.5, 0.5), + + ControlUI.current_ui.show_message(dt.strftime('%Y-%m-%d %H:%M:%S'), + position=position, + origin=origin, + font="verdana.ttf", + close_time=-1) + def on_timer_changed(self, time_data: TimeData): + """ + 时时刻刻运行 + @param time_data: + @return: + """ + t = self.start_time + time_data.total_days - def on_timer_changed(time_data: TimeData): - t = current_time + time_data.total_days - # posvels = get_bodies_posvels(names, t) - # earth_loc = None earth_pos = None sun_pos = None - positions = [] - for body in bodies: - if isinstance(body, Asteroids): + for body in self.bodies: + if isinstance(body, Asteroids): # 小行星带是模拟,不是正常的天体 posvel = None else: - posvel = get_body_posvel(body, None, t) + # 获取天体的三维位置和矢量速度 + posvel = get_body_posvel(body, t) - if isinstance(body, Moon): + if isinstance(body, Moon): # 如果是月球,为了好的展示效果,需要对月球的位置重新计算 posvel = recalc_moon_position(posvel, earth_pos) if posvel is None: + # posvel 为空,则使用太阳的坐标 position, velocity = [sun_pos.x.value * AU, sun_pos.z.value * AU, sun_pos.y.value * AU], [0, 0, 0] else: - S_OF_D = 24 * 60 * 60 # 坐标单位:千米 速度单位:千米/秒 position, velocity = [posvel[0].x.value * AU, posvel[0].z.value * AU, posvel[0].y.value * AU], \ - [posvel[1].x.value * AU / S_OF_D, posvel[1].z.value * AU / S_OF_D, - posvel[1].y.value * AU / S_OF_D] - - if isinstance(body, Asteroids) or isinstance(body, Moon) or isinstance(body, Sun): - pass - else: - positions.append(position) + [posvel[1].x.value * AU / SECONDS_PER_DAY, + posvel[1].z.value * AU / SECONDS_PER_DAY, + posvel[1].y.value * AU / SECONDS_PER_DAY] + # 实时调整天体的位置和速度 body.position = np.array(position) body.velocity = np.array(velocity) + if isinstance(body, Earth): - # earth_loc = EarthLocation(x=posvel[0].x, y=posvel[0].y, z=posvel[0].z) + # 记录地球的位置 earth_pos = posvel[0] elif isinstance(body, Sun): + # 记录太阳的位置 sun_pos = posvel[0] - dt = time_data.get_datetime(str(current_time)) - - # # 日期当天的偏转角度+误差 - # angle_of_day = day_of_year * (360 / 365) + 75 - # # 控制地球的自转 - # earth.planet.rotation_y = -(time_data.total_hours) * 15 + angle_of_day - - # 需要按照时间和日期控制地球的自转,不能随意转动 - # 日期是当年的第几天 - timetuple = dt.timetuple() - - # 计算出:日期当天的偏转角度 - 贴图的误差 - # angle_of_day = day_of_year * (360 / 365) - 93.5 # 2023.7.25 - # angle_of_day = day_of_year * (360 / 365) - 60 # 2023.7.27 - # 控制地球的自转速度和方向,保障白天,中国面对太阳(会存在一点点的误差,可以通过上面“贴图的误差”进行调整)。 - # earth.planet.rotation_y = -(time_data.total_hours) * 15 - angle_of_day - - # 计算出:日期当天的偏转角度 - day_of_year = timetuple.tm_yday - angle_of_day = day_of_year * (360 / 365) - total_hours = timetuple.tm_hour + timetuple.tm_min / 60 + timetuple.tm_sec / 60 / 60 - earth.planet.rotation_y = -total_hours * 15 - angle_of_day + self.show_timer_text(time_data) + + def bind_events(self): + # 运行中,每时每刻都会触发 on_timer_changed + UrsinaEvent.on_timer_changed_subscription(self.on_timer_changed) + # 运行前会触发 on_ready + UrsinaEvent.on_ready_subscription(self.on_ready) + + def run(self, + debug_mode=False, + start_time=None, + show_asteroids=False, + show_earth_clouds=False, + clock_position_center=False): + """ + 模拟运行 + @return: + """ + self.debug_mode = debug_mode + self.clock_position_center = clock_position_center + self.show_asteroids = show_asteroids + self.show_earth_clouds = show_earth_clouds + self.create_bodies() # 创建太阳系天体 + self.init_earth() # 初始化地球 + self.bind_events() # 绑定事件 + + if start_time is None: + from astropy.time import Time + self.start_time = Time.now() # 获取默认开始时间为当前时间 + + dt = SECONDS_PER_DAY # 1秒=1天 + dt = 1 # 1秒=1秒 + # 使用 ursina 查看的运行效果 + # 常用快捷键: P:运行和暂停 O:重新开始 I:显示天体轨迹 + # position = 左-右+、上+下-、前+后- + ursina_run(self.bodies, dt, + position=(0, 0.2 * AU, -3 * AU), + gravity_works=False, # 关闭万有引力的计算 + show_grid=False, + show_camera_info=False, + timer_enabled=True) - # if len(in_line_datetimes) == 0: - # in_line = are_planets_in_line(positions, 5*AU) - # if in_line: - # in_line_datetimes.append(dt.strftime('%Y-%m-%d %H:%M:%S')) - # print(in_line_datetimes) - # UrsinaConfig.seconds_per = 1 - - # print(time_data.get_datetime(str(current_time))) - ControlUI.current_ui.show_message(dt.strftime('%Y-%m-%d %H:%M:%S'), - position=(0.60, -0.465), - origin=(-0.5, 0.5), - font="verdana.ttf", - close_time=-1) +if __name__ == '__main__': + # 以下展示的效果为太阳系真实的时间和位置 + # 由于宇宙空间尺度非常大,如果按照实际的天体大小,则无法看到天体,因此需要对天体的尺寸进行放大 - # 运行中,每时每刻都会触发 on_timer_changed - UrsinaEvent.on_timer_changed_subscription(on_timer_changed) - # 运行前会触发 on_ready - UrsinaEvent.on_ready_subscription(on_ready) - dt = SECONDS_PER_DAY # 1秒=1天 - dt = 1 # 1秒=1秒 - # 使用 ursina 查看的运行效果 - # 常用快捷键: P:运行和暂停 O:重新开始 I:显示天体轨迹 - # position = 左-右+、上+下-、前+后- - ursina_run(bodies, dt, - position=(0, 0.2 * AU, -3 * AU), - gravity_works=False, # 关闭万有引力的计算 - show_grid=False, - show_camera_info=False, - timer_enabled=True) + sim = SolarSystemRealitySim() + sim.run( + debug_mode=True, + clock_position_center=True + )