updated v
This commit is contained in:
@@ -529,7 +529,9 @@ class Advanced3DGenerator:
|
||||
frame_path = self.create_pydeck_frame(df, current_index, frame_num)
|
||||
elif style == 'plotly':
|
||||
frame_path = self.create_plotly_frame(df, current_index, frame_num)
|
||||
else: # advanced
|
||||
elif style == 'google_earth':
|
||||
frame_path = self.create_google_earth_frame(df, current_index, frame_num)
|
||||
else: # advanced (default)
|
||||
frame_path = self.create_advanced_plotly_frame(df, current_index, frame_num)
|
||||
|
||||
if frame_path:
|
||||
@@ -654,6 +656,237 @@ class Advanced3DGenerator:
|
||||
progress_callback(-1, f"Error: {e}")
|
||||
return False
|
||||
|
||||
def create_google_earth_frame(self, df, current_index, frame_num):
|
||||
"""
|
||||
Create a Google Earth-style flythrough frame with realistic terrain and camera following
|
||||
"""
|
||||
if not PLOTLY_AVAILABLE:
|
||||
raise ImportError("Plotly is required for Google Earth-style frames")
|
||||
|
||||
# Get current position and context
|
||||
current_row = df.iloc[current_index]
|
||||
current_lat = current_row['latitude']
|
||||
current_lon = current_row['longitude']
|
||||
current_alt = current_row.get('elevation', 100)
|
||||
|
||||
# Get track up to current position (progressive reveal)
|
||||
track_so_far = df.iloc[:current_index + 1]
|
||||
|
||||
# Calculate terrain bounds around the track
|
||||
lat_margin = 0.02 # degrees
|
||||
lon_margin = 0.02 # degrees
|
||||
min_lat = track_so_far['latitude'].min() - lat_margin
|
||||
max_lat = track_so_far['latitude'].max() + lat_margin
|
||||
min_lon = track_so_far['longitude'].min() - lon_margin
|
||||
max_lon = track_so_far['longitude'].max() + lon_margin
|
||||
|
||||
# Generate terrain mesh
|
||||
terrain_resolution = 40
|
||||
lat_range = np.linspace(min_lat, max_lat, terrain_resolution)
|
||||
lon_range = np.linspace(min_lon, max_lon, terrain_resolution)
|
||||
lat_mesh, lon_mesh = np.meshgrid(lat_range, lon_range)
|
||||
|
||||
# Generate realistic terrain heights
|
||||
terrain_heights = self.generate_terrain_heights(lat_mesh, lon_mesh, current_lat, current_lon)
|
||||
|
||||
# Create the figure
|
||||
fig = go.Figure()
|
||||
|
||||
# Add terrain surface
|
||||
fig.add_trace(
|
||||
go.Surface(
|
||||
x=lon_mesh,
|
||||
y=lat_mesh,
|
||||
z=terrain_heights,
|
||||
colorscale=[
|
||||
[0.0, 'rgb(139,69,19)'], # Brown (low elevation)
|
||||
[0.2, 'rgb(160,82,45)'], # Saddle brown
|
||||
[0.4, 'rgb(107,142,35)'], # Olive drab (medium)
|
||||
[0.6, 'rgb(34,139,34)'], # Forest green
|
||||
[0.8, 'rgb(105,105,105)'], # Dim gray (high)
|
||||
[1.0, 'rgb(255,255,255)'] # White (peaks)
|
||||
],
|
||||
opacity=0.9,
|
||||
showscale=False,
|
||||
name='Terrain',
|
||||
lighting=dict(
|
||||
ambient=0.3,
|
||||
diffuse=0.8,
|
||||
specular=0.3,
|
||||
roughness=0.3
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
# Add GPS track so far (elevated above terrain)
|
||||
if len(track_so_far) > 1:
|
||||
track_elevation = track_so_far['elevation'].values + 80 # 80m above terrain
|
||||
|
||||
fig.add_trace(
|
||||
go.Scatter3d(
|
||||
x=track_so_far['longitude'],
|
||||
y=track_so_far['latitude'],
|
||||
z=track_elevation,
|
||||
mode='lines+markers',
|
||||
line=dict(
|
||||
color='red',
|
||||
width=10
|
||||
),
|
||||
marker=dict(
|
||||
size=4,
|
||||
color='orange',
|
||||
opacity=0.8
|
||||
),
|
||||
name='GPS Track'
|
||||
)
|
||||
)
|
||||
|
||||
# Add current vehicle position
|
||||
vehicle_height = current_alt + 120 # Above terrain
|
||||
fig.add_trace(
|
||||
go.Scatter3d(
|
||||
x=[current_lon],
|
||||
y=[current_lat],
|
||||
z=[vehicle_height],
|
||||
mode='markers',
|
||||
marker=dict(
|
||||
color='red',
|
||||
size=20,
|
||||
symbol='diamond',
|
||||
line=dict(color='yellow', width=3)
|
||||
),
|
||||
name='Vehicle'
|
||||
)
|
||||
)
|
||||
|
||||
# Calculate dynamic camera position for cinematic following
|
||||
# Camera follows behind and above the vehicle
|
||||
follow_distance = 0.005 # degrees behind
|
||||
camera_height_offset = 1000 # meters above vehicle
|
||||
|
||||
if current_index > 5:
|
||||
# Calculate movement direction from last few points
|
||||
recent_track = df.iloc[max(0, current_index-5):current_index+1]
|
||||
lat_direction = recent_track['latitude'].iloc[-1] - recent_track['latitude'].iloc[0]
|
||||
lon_direction = recent_track['longitude'].iloc[-1] - recent_track['longitude'].iloc[0]
|
||||
|
||||
# Normalize direction
|
||||
direction_length = np.sqrt(lat_direction**2 + lon_direction**2)
|
||||
if direction_length > 0:
|
||||
lat_direction /= direction_length
|
||||
lon_direction /= direction_length
|
||||
|
||||
# Position camera behind the vehicle
|
||||
camera_lat = current_lat - lat_direction * follow_distance
|
||||
camera_lon = current_lon - lon_direction * follow_distance
|
||||
else:
|
||||
camera_lat = current_lat - follow_distance
|
||||
camera_lon = current_lon - follow_distance
|
||||
|
||||
camera_z = (current_alt + camera_height_offset) / 1000 # Convert to relative scale
|
||||
|
||||
# Update layout for cinematic Google Earth-style view
|
||||
fig.update_layout(
|
||||
title=dict(
|
||||
text=f'GPS Flythrough - {current_row["timestamp"].strftime("%H:%M:%S")} - Frame {frame_num}',
|
||||
x=0.5,
|
||||
font=dict(size=24, color='white', family="Arial Black")
|
||||
),
|
||||
scene=dict(
|
||||
camera=dict(
|
||||
eye=dict(
|
||||
x=1.2, # Camera position relative to scene
|
||||
y=-1.5,
|
||||
z=0.8
|
||||
),
|
||||
center=dict(
|
||||
x=0,
|
||||
y=0,
|
||||
z=0.2
|
||||
),
|
||||
up=dict(x=0, y=0, z=1)
|
||||
),
|
||||
xaxis=dict(
|
||||
title='',
|
||||
showgrid=False,
|
||||
zeroline=False,
|
||||
showline=False,
|
||||
showticklabels=False,
|
||||
showbackground=False
|
||||
),
|
||||
yaxis=dict(
|
||||
title='',
|
||||
showgrid=False,
|
||||
zeroline=False,
|
||||
showline=False,
|
||||
showticklabels=False,
|
||||
showbackground=False
|
||||
),
|
||||
zaxis=dict(
|
||||
title='',
|
||||
showgrid=False,
|
||||
zeroline=False,
|
||||
showline=False,
|
||||
showticklabels=False,
|
||||
showbackground=False
|
||||
),
|
||||
aspectmode='cube',
|
||||
bgcolor='rgb(135,206,235)', # Sky blue background
|
||||
camera_projection_type='perspective'
|
||||
),
|
||||
paper_bgcolor='rgb(0,0,0)',
|
||||
plot_bgcolor='rgb(0,0,0)',
|
||||
showlegend=False,
|
||||
width=1920,
|
||||
height=1080,
|
||||
margin=dict(l=0, r=0, t=60, b=0),
|
||||
font=dict(color='white')
|
||||
)
|
||||
|
||||
# Save frame
|
||||
frame_path = os.path.join(self.frames_folder, f"GoogleEarth_Frame_{frame_num:04d}.png")
|
||||
try:
|
||||
fig.write_image(frame_path, engine="kaleido", width=1920, height=1080)
|
||||
return frame_path
|
||||
except Exception as e:
|
||||
print(f"Error saving frame {frame_num}: {e}")
|
||||
return None
|
||||
|
||||
def generate_terrain_heights(self, lat_mesh, lon_mesh, center_lat, center_lon):
|
||||
"""
|
||||
Generate realistic terrain heights using mathematical functions to simulate mountains/hills
|
||||
"""
|
||||
# Convert lat/lon to local coordinates (approximate)
|
||||
lat_m = (lat_mesh - center_lat) * 111000 # degrees to meters
|
||||
lon_m = (lon_mesh - center_lon) * 111000 * np.cos(np.radians(center_lat))
|
||||
|
||||
# Base terrain height
|
||||
base_height = 300
|
||||
|
||||
# Create mountain ridges using sine waves
|
||||
ridge1 = 400 * np.exp(-((lat_m - 1000)**2 + (lon_m + 500)**2) / (800**2))
|
||||
ridge2 = 500 * np.exp(-((lat_m + 800)**2 + (lon_m - 1200)**2) / (1000**2))
|
||||
ridge3 = 350 * np.exp(-((lat_m)**2 + (lon_m)**2) / (1500**2))
|
||||
|
||||
# Add rolling hills
|
||||
hills = 150 * np.sin(lat_m / 400) * np.cos(lon_m / 600)
|
||||
hills += 100 * np.sin(lat_m / 800) * np.sin(lon_m / 300)
|
||||
|
||||
# Add valleys
|
||||
valleys = -80 * np.exp(-((lat_m - 500)**2 + (lon_m + 800)**2) / (600**2))
|
||||
|
||||
# Combine all terrain features
|
||||
terrain = base_height + ridge1 + ridge2 + ridge3 + hills + valleys
|
||||
|
||||
# Add some noise for realism
|
||||
noise = 30 * np.sin(lat_m / 100) * np.cos(lon_m / 150)
|
||||
terrain += noise
|
||||
|
||||
# Ensure minimum elevation
|
||||
terrain = np.maximum(terrain, 50)
|
||||
|
||||
return terrain
|
||||
|
||||
def generate_advanced_3d_video(positions_file, output_folder, filename_prefix="advanced_3d",
|
||||
style='advanced', progress_callback=None):
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user