提交 ace3b911 编写于 作者: 三月三net's avatar 三月三net

Python超人-宇宙模拟器

上级 4118345e
# -*- 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}")
...@@ -7,15 +7,12 @@ ...@@ -7,15 +7,12 @@
# python_version :3.8 # python_version :3.8
# ============================================================================== # ==============================================================================
# pip install -i http://pypi.douban.com/simple/ --trusted-host=pypi.douban.com de423 # 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 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 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 common.consts import SECONDS_PER_WEEK, SECONDS_PER_DAY, AU
from sim_scenes.func import ursina_run from sim_scenes.func import ursina_run
from simulators.ursina.entities.body_timer import TimeData from simulators.ursina.entities.body_timer import TimeData
...@@ -25,249 +22,191 @@ from simulators.ursina.ursina_event import UrsinaEvent ...@@ -25,249 +22,191 @@ from simulators.ursina.ursina_event import UrsinaEvent
from ursina import camera, application from ursina import camera, application
def get_bodies_posvels(planet_names="sun,mercury,venus,earth,moon,mars,jupiter,saturn,uranus,neptune", time=None): class SolarSystemRealitySim:
if time is None: def __init__(self):
time = Time.now() """
planets = planet_names.split(",")
posvels = {} @param debug_mode: 是否为调试模式
for planet in planets: """
try: self.show_asteroids = False
position, velocity = get_body_barycentric_posvel(planet, time) self.clock_position_center = False
posvels[planet] = position, velocity self.show_earth_clouds = False
# print(planet, position) self.debug_mode = False
except Exception as e:
print(planet, str(e)) def create_bodies(self):
return posvels """
创建太阳系的天体
@return:
def recalc_moon_position(moon_posvel, earth_pos): """
moon_pos, moon_vel = moon_posvel[0], moon_posvel[1] self.sun_size_scale = 0.04e2 if self.debug_mode else 0.4e2
moon_pos_to_earth = moon_pos - earth_pos self.earth_size_scale = 10e3 if self.debug_mode else 1e3
moon_pos_to_earth = moon_pos_to_earth * 50
self.sun = Sun(name="太阳", size_scale=self.sun_size_scale) # 太阳
return moon_pos_to_earth + earth_pos, moon_vel 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) # 地球
def get_bodies_names(bodies): self.earth_clouds = Earth(name="地球云层", texture="transparent_clouds.png",
names = "" size_scale=self.earth_size_scale * 1.01) # 地球云层
for body in bodies: self.moon = Moon(name="月球", size_scale=2e3) # 月球
names += body.__class__.__name__ + "," self.mars = Mars(name="火星", size_scale=1.2e3) # 火星
return names[0:-1] 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) # 土星
def are_planets_in_line(positions, line_width): self.uranus = Uranus(name="天王星", size_scale=10e2) # 天王星
# 检查行星的数量是否足够判断是否在一条线上 self.neptune = Neptune(name="海王星", size_scale=10e2) # 海王星
if len(positions) < 3: # 行星
return False self.planets = [self.mercury, self.venus, self.earth, self.mars,
# 获取第一个行星的坐标 self.jupiter, self.saturn, self.uranus, self.neptune]
x1, y1, z1 = positions[0] # 所有天体
# 计算行星之间的向量差 self.bodies = [self.sun] + self.planets + [self.moon]
dx = positions[1][0] - x1
dy = positions[1][1] - y1 if self.show_earth_clouds:
dz = positions[1][2] - z1 self.bodies += [self.earth_clouds]
# 计算线的宽度的平方
line_width_squared = line_width ** 2 if self.show_asteroids:
# 遍历剩余的行星 self.bodies += [self.asteroids]
for i in range(2, len(positions)):
# 获取当前行星的坐标 def init_earth(self):
x, y, z = positions[i] """
# 计算当前行星与第一个行星之间的向量差 初始化地球
current_dx = x - x1 @return:
current_dy = y - y1 """
current_dz = z - z1 # 让地球显示自转轴线
# 计算当前行星与线之间的距离的平方 self.earth.rotate_axis_color = (255, 255, 50)
distance_squared = (current_dy * dz - current_dz * dy) ** 2 + (current_dz * dx - current_dx * dz) ** 2 + ( # 如果为调试模式,则太阳光对地球无效,方便查看
current_dx * dy - current_dy * dx) ** 2 if self.debug_mode:
# 如果距离的平方大于线的宽度的平方,则行星不在一条线上 self.earth.set_light_disable(True)
if distance_squared > line_width_squared:
return False def on_ready(self):
# 所有行星都在一条线上 # 运行前触发
return True camera.rotation_z = -20
if self.debug_mode:
camera.fov = 20 # 调试时,拉近摄像机距离
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 = []
if __name__ == '__main__': # 需要按照时间和日期来控制地球的自转,所以删除控制地球自转的属性
# 以下展示的效果为太阳系真实的时间和位置 delattr(self.earth.planet, "rotation_speed")
# 由于宇宙空间尺度非常大,如果按照实际的天体大小,则无法看到天体,因此需要对天体的尺寸进行放大 delattr(self.earth.planet, "rotspeed")
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
# 设置后,可以调整鼠标键盘的控制速度
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") timetuple = dt.timetuple()
application.time_scale = 2
# 计算出:日期当天的偏转角度
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 earth_pos = None
sun_pos = None sun_pos = None
positions = [] for body in self.bodies:
for body in bodies: if isinstance(body, Asteroids): # 小行星带是模拟,不是正常的天体
if isinstance(body, Asteroids):
posvel = None posvel = None
else: 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) posvel = recalc_moon_position(posvel, earth_pos)
if posvel is None: if posvel is None:
# posvel 为空,则使用太阳的坐标
position, velocity = [sun_pos.x.value * AU, position, velocity = [sun_pos.x.value * AU,
sun_pos.z.value * AU, sun_pos.z.value * AU,
sun_pos.y.value * AU], [0, 0, 0] sun_pos.y.value * AU], [0, 0, 0]
else: else:
S_OF_D = 24 * 60 * 60
# 坐标单位:千米 速度单位:千米/秒 # 坐标单位:千米 速度单位:千米/秒
position, velocity = [posvel[0].x.value * AU, posvel[0].z.value * AU, posvel[0].y.value * AU], \ 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].x.value * AU / SECONDS_PER_DAY,
posvel[1].y.value * AU / S_OF_D] posvel[1].z.value * AU / SECONDS_PER_DAY,
posvel[1].y.value * AU / SECONDS_PER_DAY]
if isinstance(body, Asteroids) or isinstance(body, Moon) or isinstance(body, Sun):
pass
else:
positions.append(position)
# 实时调整天体的位置和速度
body.position = np.array(position) body.position = np.array(position)
body.velocity = np.array(velocity) body.velocity = np.array(velocity)
if isinstance(body, Earth): if isinstance(body, Earth):
# earth_loc = EarthLocation(x=posvel[0].x, y=posvel[0].y, z=posvel[0].z) # 记录地球的位置
earth_pos = posvel[0] earth_pos = posvel[0]
elif isinstance(body, Sun): elif isinstance(body, Sun):
# 记录太阳的位置
sun_pos = posvel[0] sun_pos = posvel[0]
dt = time_data.get_datetime(str(current_time)) self.show_timer_text(time_data)
# # 日期当天的偏转角度+误差
# 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
# 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)
def bind_events(self):
# 运行中,每时每刻都会触发 on_timer_changed # 运行中,每时每刻都会触发 on_timer_changed
UrsinaEvent.on_timer_changed_subscription(on_timer_changed) UrsinaEvent.on_timer_changed_subscription(self.on_timer_changed)
# 运行前会触发 on_ready # 运行前会触发 on_ready
UrsinaEvent.on_ready_subscription(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 = SECONDS_PER_DAY # 1秒=1天
dt = 1 # 1秒=1秒 dt = 1 # 1秒=1秒
# 使用 ursina 查看的运行效果 # 使用 ursina 查看的运行效果
# 常用快捷键: P:运行和暂停 O:重新开始 I:显示天体轨迹 # 常用快捷键: P:运行和暂停 O:重新开始 I:显示天体轨迹
# position = 左-右+、上+下-、前+后- # position = 左-右+、上+下-、前+后-
ursina_run(bodies, dt, ursina_run(self.bodies, dt,
position=(0, 0.2 * AU, -3 * AU), position=(0, 0.2 * AU, -3 * AU),
gravity_works=False, # 关闭万有引力的计算 gravity_works=False, # 关闭万有引力的计算
show_grid=False, show_grid=False,
show_camera_info=False, show_camera_info=False,
timer_enabled=True) timer_enabled=True)
if __name__ == '__main__':
# 以下展示的效果为太阳系真实的时间和位置
# 由于宇宙空间尺度非常大,如果按照实际的天体大小,则无法看到天体,因此需要对天体的尺寸进行放大
sim = SolarSystemRealitySim()
sim.run(
debug_mode=True,
clock_position_center=True
)
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册