提交 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 @@
# 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
# 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)
self.show_timer_text(time_data)
def bind_events(self):
# 运行中,每时每刻都会触发 on_timer_changed
UrsinaEvent.on_timer_changed_subscription(on_timer_changed)
UrsinaEvent.on_timer_changed_subscription(self.on_timer_changed)
# 运行前会触发 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 = 1 # 1秒=1秒
# 使用 ursina 查看的运行效果
# 常用快捷键: P:运行和暂停 O:重新开始 I:显示天体轨迹
# position = 左-右+、上+下-、前+后-
ursina_run(bodies, dt,
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 __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.
先完成此消息的编辑!
想要评论请 注册