updated v

This commit is contained in:
2025-07-09 16:47:17 +03:00
parent 35d3bb8442
commit 4fa7ed2a48
3 changed files with 440 additions and 1 deletions

View File

@@ -529,7 +529,9 @@ class Advanced3DGenerator:
frame_path = self.create_pydeck_frame(df, current_index, frame_num) frame_path = self.create_pydeck_frame(df, current_index, frame_num)
elif style == 'plotly': elif style == 'plotly':
frame_path = self.create_plotly_frame(df, current_index, frame_num) 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) frame_path = self.create_advanced_plotly_frame(df, current_index, frame_num)
if frame_path: if frame_path:
@@ -654,6 +656,237 @@ class Advanced3DGenerator:
progress_callback(-1, f"Error: {e}") progress_callback(-1, f"Error: {e}")
return False 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", def generate_advanced_3d_video(positions_file, output_folder, filename_prefix="advanced_3d",
style='advanced', progress_callback=None): style='advanced', progress_callback=None):
""" """

View File

@@ -331,6 +331,39 @@ class CreateAnimationScreen(Screen):
) )
layout.add_widget(advanced_btn) layout.add_widget(advanced_btn)
# Google Earth Flythrough Mode
google_earth_layout = BoxLayout(orientation='vertical', spacing=5)
google_earth_title = Label(
text="🌍 Google Earth Flythrough",
font_size=16,
size_hint_y=None,
height=30,
color=(0.1, 0.8, 0.1, 1)
)
google_earth_desc = Label(
text="• Realistic 3D terrain with mountains\n• Cinematic camera following at 1000-2000m\n• Google Earth-style flythrough\n• Professional geographic animation",
font_size=11,
size_hint_y=None,
height=70,
color=(0.9, 0.9, 0.9, 1),
halign="left",
valign="middle"
)
google_earth_desc.text_size = (None, None)
google_earth_layout.add_widget(google_earth_title)
google_earth_layout.add_widget(google_earth_desc)
layout.add_widget(google_earth_layout)
# Google Earth button
google_earth_btn = Button(
text="Generate Google Earth Flythrough",
background_color=(0.1, 0.8, 0.1, 1),
size_hint_y=None,
height=45,
font_size=13
)
layout.add_widget(google_earth_btn)
# Blender Cinema Mode # Blender Cinema Mode
blender_layout = BoxLayout(orientation='vertical', spacing=5) blender_layout = BoxLayout(orientation='vertical', spacing=5)
blender_title = Label( blender_title = Label(
@@ -393,6 +426,10 @@ class CreateAnimationScreen(Screen):
popup.dismiss() popup.dismiss()
self.generate_advanced_3d_animation() self.generate_advanced_3d_animation()
def start_google_earth(instance):
popup.dismiss()
self.generate_google_earth_animation()
def start_blender_animation(instance): def start_blender_animation(instance):
popup.dismiss() popup.dismiss()
self.generate_blender_animation() self.generate_blender_animation()
@@ -400,6 +437,7 @@ class CreateAnimationScreen(Screen):
classic_test_btn.bind(on_press=start_classic_test) classic_test_btn.bind(on_press=start_classic_test)
classic_prod_btn.bind(on_press=start_classic_production) classic_prod_btn.bind(on_press=start_classic_production)
advanced_btn.bind(on_press=start_advanced_3d) advanced_btn.bind(on_press=start_advanced_3d)
google_earth_btn.bind(on_press=start_google_earth)
blender_btn.bind(on_press=start_blender_animation) blender_btn.bind(on_press=start_blender_animation)
cancel_btn.bind(on_press=lambda x: popup.dismiss()) cancel_btn.bind(on_press=lambda x: popup.dismiss())
@@ -573,6 +611,91 @@ class CreateAnimationScreen(Screen):
# Schedule the animation generation # Schedule the animation generation
Clock.schedule_once(lambda dt: run_blender_animation(), 0.5) Clock.schedule_once(lambda dt: run_blender_animation(), 0.5)
def generate_google_earth_animation(self):
"""Generate Google Earth-style flythrough animation with terrain"""
# Show processing popup
layout = BoxLayout(orientation='vertical', spacing=10, padding=10)
label = Label(text="Initializing Google Earth flythrough...")
progress = ProgressBar(max=100, value=0)
layout.add_widget(label)
layout.add_widget(progress)
popup = Popup(
title="Generating Google Earth Flythrough",
content=layout,
size_hint=(0.9, None),
size=(0, 200),
auto_dismiss=False
)
popup.open()
def run_google_earth_animation():
try:
# Update status
def update_status(progress_val, status_text):
def _update(dt):
progress.value = progress_val
label.text = status_text
Clock.schedule_once(_update, 0)
project_folder = os.path.join(RESOURCES_FOLDER, "projects", self.project_name)
positions_path = os.path.join(project_folder, "positions.json")
if not os.path.exists(positions_path):
update_status(0, "Error: No GPS data found")
Clock.schedule_once(lambda dt: popup.dismiss(), 2)
return
update_status(10, "Checking dependencies...")
# Check dependencies first
generator = Advanced3DGenerator(project_folder)
generator.check_dependencies()
update_status(20, "Loading GPS data...")
df = generator.load_gps_data(positions_path)
update_status(30, "Generating terrain and camera flythrough...")
output_video_path = os.path.join(project_folder, f"{self.project_name}_google_earth_{datetime.now().strftime('%Y%m%d_%H%M%S')}.mp4")
# Progress callback for the generator
def generator_progress(progress, message):
update_status(30 + (progress * 0.5), message) # Map 0-100% to 30-80%
update_status(80, "Creating flythrough video...")
success = generator.generate_3d_animation(
positions_path,
output_video_path,
style='google_earth',
progress_callback=generator_progress
)
if success:
update_status(100, "Google Earth flythrough complete!")
output_path = output_video_path
else:
raise Exception("Failed to generate flythrough video")
def show_success(dt):
popup.dismiss()
self.show_success_popup(
"Google Earth Flythrough Complete!",
f"Your cinematic flythrough has been created:\n{output_path}",
output_path
)
Clock.schedule_once(show_success, 1)
except Exception as e:
error_message = str(e)
def show_error(dt):
popup.dismiss()
self.show_error_popup("Google Earth Animation Error", error_message)
Clock.schedule_once(show_error, 0)
# Schedule the animation generation
Clock.schedule_once(lambda dt: run_google_earth_animation(), 0.5)
def show_success_popup(self, title, message, file_path=None): def show_success_popup(self, title, message, file_path=None):
"""Show success popup with option to open file location""" """Show success popup with option to open file location"""
layout = BoxLayout(orientation='vertical', spacing=10, padding=10) layout = BoxLayout(orientation='vertical', spacing=10, padding=10)

83
test_google_earth.py Normal file
View File

@@ -0,0 +1,83 @@
#!/usr/bin/env python3
"""
Test script for Google Earth-style flythrough animation
"""
import os
import sys
sys.path.append('/home/pi/Desktop/traccar_animation')
from py_scripts.advanced_3d_generator import Advanced3DGenerator
from datetime import datetime
def test_google_earth_animation():
"""Test the new Google Earth flythrough animation"""
# Find a project with GPS data
projects_folder = "/home/pi/Desktop/traccar_animation/resources/projects"
if not os.path.exists(projects_folder):
print("Projects folder not found!")
return
# Look for projects
projects = [d for d in os.listdir(projects_folder) if os.path.isdir(os.path.join(projects_folder, d))]
if not projects:
print("No projects found!")
return
# Use the first project found
project_name = projects[0]
project_folder = os.path.join(projects_folder, project_name)
positions_file = os.path.join(project_folder, "positions.json")
if not os.path.exists(positions_file):
print(f"No positions.json found in project {project_name}")
return
print(f"Testing Google Earth animation with project: {project_name}")
# Create generator
generator = Advanced3DGenerator(project_folder)
# Check dependencies
try:
generator.check_dependencies()
print("✅ All dependencies available")
except Exception as e:
print(f"❌ Dependency error: {e}")
return
# Generate Google Earth-style animation
output_video = os.path.join(project_folder, f"{project_name}_google_earth_test_{datetime.now().strftime('%Y%m%d_%H%M%S')}.mp4")
def progress_callback(progress, message):
print(f"Progress: {progress:.1f}% - {message}")
try:
print("Starting Google Earth flythrough generation...")
success = generator.generate_3d_animation(
positions_file,
output_video,
style='google_earth',
progress_callback=progress_callback
)
if success and os.path.exists(output_video):
print(f"✅ SUCCESS! Google Earth flythrough created: {output_video}")
# Get file size
file_size = os.path.getsize(output_video) / (1024 * 1024) # MB
print(f"📹 Video size: {file_size:.1f} MB")
else:
print("❌ Failed to create video")
except Exception as e:
print(f"❌ Error during generation: {e}")
import traceback
traceback.print_exc()
if __name__ == "__main__":
test_google_earth_animation()