updates preview and pauses
This commit is contained in:
Binary file not shown.
46
proba
46
proba
@@ -1,46 +0,0 @@
|
||||
|
||||
class CreateAnimationScreen(Screen):
|
||||
project_name = StringProperty("")
|
||||
preview_html_path = StringProperty("") # Path to the HTML file for preview
|
||||
preview_image_path = StringProperty("") # Add this line
|
||||
|
||||
def on_pre_enter(self):
|
||||
# Update the route entries label with the actual number of entries
|
||||
project_folder = os.path.join(RESOURCES_FOLDER, "projects", self.project_name)
|
||||
positions_path = os.path.join(project_folder, "positions.json")
|
||||
count = 0
|
||||
if os.path.exists(positions_path):
|
||||
with open(positions_path, "r") as f:
|
||||
try:
|
||||
positions = json.load(f)
|
||||
count = len(positions)
|
||||
except Exception:
|
||||
count = 0
|
||||
self.ids.route_entries_label.text = f"Your route has {count} entries,"
|
||||
|
||||
def open_rename_popup(self):
|
||||
from kivy.uix.popup import Popup
|
||||
from kivy.uix.boxlayout import BoxLayout
|
||||
from kivy.uix.button import Button
|
||||
from kivy.uix.textinput import TextInput
|
||||
from kivy.uix.label import Label
|
||||
|
||||
layout = BoxLayout(orientation='vertical', spacing=10, padding=10)
|
||||
label = Label(text="Enter new project name:")
|
||||
input_field = TextInput(text=self.project_name, multiline=False)
|
||||
btn_save = Button(text="Save", background_color=(0.008, 0.525, 0.290, 1))
|
||||
btn_cancel = Button(text="Cancel")
|
||||
|
||||
layout.add_widget(label)
|
||||
layout.add_widget(input_field)
|
||||
layout.add_widget(btn_save)
|
||||
layout.add_widget(btn_cancel)
|
||||
|
||||
popup = Popup(title="Rename Project", content=layout, size_hint=(None, None), size=(350, 260), auto_dismiss=False)
|
||||
|
||||
def do_rename(instance):
|
||||
new_name = input_field.text.strip()
|
||||
if new_name and new_name != self.project_name:
|
||||
if self.rename_project_folder(self.project_name, new_name):
|
||||
self.project_name = new_name
|
||||
popup.dismiss()
|
||||
@@ -1 +1 @@
|
||||
gAAAAABoQu2h6f3qYomY4xLeBt21EA4y4D87mlBn31OUGEcEs3e3Pw0WXYnYHpRANYW291unV2egC2F1pGhuXelOi2N8xm-bEjlkhTMYZhxDLdZdPNNqNmk_HTom_JYZuqYEan3Oz7Xj
|
||||
gAAAAABoQzLdIdf3DPBo8ovchYeCI6p5KPYw_8GLixB4NpVQfJRQFWvDvi1b7BmxTqwx8UTuiOFabSPBEhR25LARU7YlTVuoYxFZjahHwvu7COTzRfi98qePYrhvb1AFoJSkDNWbbbfg
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
Binary file not shown.
|
Before Width: | Height: | Size: 264 KiB |
File diff suppressed because it is too large
Load Diff
@@ -1,169 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
|
||||
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
|
||||
<script src="https://cdn.jsdelivr.net/npm/leaflet@1.9.3/dist/leaflet.js"></script>
|
||||
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/Leaflet.awesome-markers/2.0.2/leaflet.awesome-markers.js"></script>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/leaflet@1.9.3/dist/leaflet.css"/>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css"/>
|
||||
<link rel="stylesheet" href="https://netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap-glyphicons.css"/>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.2.0/css/all.min.css"/>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Leaflet.awesome-markers/2.0.2/leaflet.awesome-markers.css"/>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/python-visualization/folium/folium/templates/leaflet.awesome.rotate.min.css"/>
|
||||
|
||||
<meta name="viewport" content="width=device-width,
|
||||
initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||
<style>
|
||||
#map_6173f842f324405630d110023c8c7322 {
|
||||
position: relative;
|
||||
width: 800.0px;
|
||||
height: 600.0px;
|
||||
left: 0.0%;
|
||||
top: 0.0%;
|
||||
}
|
||||
.leaflet-container { font-size: 1rem; }
|
||||
</style>
|
||||
|
||||
<style>html, body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>#map {
|
||||
position:absolute;
|
||||
top:0;
|
||||
bottom:0;
|
||||
right:0;
|
||||
left:0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
L_NO_TOUCH = false;
|
||||
L_DISABLE_3D = false;
|
||||
</script>
|
||||
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
||||
<div class="folium-map" id="map_6173f842f324405630d110023c8c7322" ></div>
|
||||
|
||||
</body>
|
||||
<script>
|
||||
|
||||
|
||||
var map_6173f842f324405630d110023c8c7322 = L.map(
|
||||
"map_6173f842f324405630d110023c8c7322",
|
||||
{
|
||||
center: [45.805146666666666, 24.126355555555556],
|
||||
crs: L.CRS.EPSG3857,
|
||||
...{
|
||||
"zoom": 14,
|
||||
"zoomControl": true,
|
||||
"preferCanvas": false,
|
||||
}
|
||||
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
var tile_layer_0dca03e0dfb8bb283db80e7711192936 = L.tileLayer(
|
||||
"https://tile.openstreetmap.org/{z}/{x}/{y}.png",
|
||||
{
|
||||
"minZoom": 0,
|
||||
"maxZoom": 19,
|
||||
"maxNativeZoom": 19,
|
||||
"noWrap": false,
|
||||
"attribution": "\u0026copy; \u003ca href=\"https://www.openstreetmap.org/copyright\"\u003eOpenStreetMap\u003c/a\u003e contributors",
|
||||
"subdomains": "abc",
|
||||
"detectRetina": false,
|
||||
"tms": false,
|
||||
"opacity": 1,
|
||||
}
|
||||
|
||||
);
|
||||
|
||||
|
||||
tile_layer_0dca03e0dfb8bb283db80e7711192936.addTo(map_6173f842f324405630d110023c8c7322);
|
||||
|
||||
|
||||
var poly_line_5185539b101b8eefa1da8150f47aa184 = L.polyline(
|
||||
[[45.805146666666666, 24.126355555555556], [45.80562444444445, 24.123990555555554], [45.805820555555556, 24.122884444444445], [45.806001111111115, 24.121864444444444], [45.80658944444445, 24.118647777777777], [45.80706166666667, 24.11584], [45.80744277777778, 24.113130555555554], [45.80744444444444, 24.111027777777778], [45.807554999999994, 24.10904111111111], [45.80765388888889, 24.10791777777778], [45.80775722222222, 24.106204444444444], [45.80775722222222, 24.106204444444444], [45.807792777777784, 24.10529888888889], [45.80769222222222, 24.105220555555558], [45.807494444444444, 24.10537666666667], [45.80721722222222, 24.10552888888889], [45.80721722222222, 24.10552888888889], [45.80452833333334, 24.106312222222222], [45.80452833333334, 24.106312222222222], [45.802245000000006, 24.106793888888888], [45.802245000000006, 24.106793888888888], [45.80039166666667, 24.107621666666667], [45.80039166666667, 24.107621666666667], [45.79863111111111, 24.10826], [45.79706388888889, 24.109215], [45.796372222222224, 24.109560000000002], [45.79611444444444, 24.109526666666667], [45.79596611111111, 24.109244999999998], [45.79575722222222, 24.107441666666666], [45.79575722222222, 24.107441666666666], [45.79544, 24.105129444444444], [45.79544, 24.105129444444444], [45.795164444444445, 24.103232777777777], [45.794825555555555, 24.100786111111113], [45.79484444444444, 24.10045277777778], [45.79482, 24.100100555555557], [45.79452388888888, 24.098648333333333], [45.794362222222226, 24.097596666666668], [45.794362222222226, 24.097596666666668], [45.794362222222226, 24.097596666666668], [45.79418555555556, 24.09649111111111], [45.79419388888889, 24.096272777777777], [45.79433111111111, 24.095743333333335], [45.795445, 24.094136111111112], [45.796870000000006, 24.09261777777778], [45.797534444444445, 24.091910555555554], [45.79878277777778, 24.090588888888888], [45.79978833333333, 24.089429444444445], [45.799776111111115, 24.089080555555554], [45.79944055555555, 24.086607777777775], [45.79913277777778, 24.086008333333332], [45.79909722222222, 24.08582277777778], [45.79911555555555, 24.085697222222223], [45.79911555555555, 24.085697222222223], [45.79919388888889, 24.08558888888889], [45.79919388888889, 24.08558888888889], [45.79919388888889, 24.08558888888889], [45.79919388888889, 24.08558888888889], [45.79919388888889, 24.08558888888889], [45.79919388888889, 24.08558888888889], [45.79919388888889, 24.08558888888889], [45.79919388888889, 24.08558888888889], [45.79919388888889, 24.08558888888889], [45.79919388888889, 24.08558888888889], [45.79919388888889, 24.08558888888889], [45.79919388888889, 24.08558888888889], [45.79919388888889, 24.08558888888889], [45.79919388888889, 24.08558888888889], [45.79919388888889, 24.08558888888889], [45.79919388888889, 24.08558888888889], [45.79919388888889, 24.08558888888889], [45.79919388888889, 24.08558888888889], [45.79919388888889, 24.08558888888889], [45.79919388888889, 24.08558888888889], [45.79919388888889, 24.08558888888889], [45.79919388888889, 24.08558888888889], [45.79919388888889, 24.08558888888889], [45.79921, 24.085612222222224]],
|
||||
{"bubblingMouseEvents": true, "color": "blue", "dashArray": null, "dashOffset": null, "fill": false, "fillColor": "blue", "fillOpacity": 0.2, "fillRule": "evenodd", "lineCap": "round", "lineJoin": "round", "noClip": false, "opacity": 1, "smoothFactor": 1.0, "stroke": true, "weight": 4.5}
|
||||
).addTo(map_6173f842f324405630d110023c8c7322);
|
||||
|
||||
|
||||
var marker_5c3a847fd31760304779791ec443f473 = L.marker(
|
||||
[45.805146666666666, 24.126355555555556],
|
||||
{
|
||||
}
|
||||
).addTo(map_6173f842f324405630d110023c8c7322);
|
||||
|
||||
|
||||
var icon_77c186d2e55fb8ae2e0070503fd0539c = L.AwesomeMarkers.icon(
|
||||
{
|
||||
"markerColor": "green",
|
||||
"iconColor": "white",
|
||||
"icon": "info-sign",
|
||||
"prefix": "glyphicon",
|
||||
"extraClasses": "fa-rotate-0",
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
marker_5c3a847fd31760304779791ec443f473.bindTooltip(
|
||||
`<div>
|
||||
Start
|
||||
</div>`,
|
||||
{
|
||||
"sticky": true,
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
marker_5c3a847fd31760304779791ec443f473.setIcon(icon_77c186d2e55fb8ae2e0070503fd0539c);
|
||||
|
||||
|
||||
var marker_0a30d487a2cda7756fd32b90acb9989b = L.marker(
|
||||
[45.79921, 24.085612222222224],
|
||||
{
|
||||
}
|
||||
).addTo(map_6173f842f324405630d110023c8c7322);
|
||||
|
||||
|
||||
var icon_cdb6bcef11f931e9bb82eb9c1a2e79bf = L.AwesomeMarkers.icon(
|
||||
{
|
||||
"markerColor": "red",
|
||||
"iconColor": "white",
|
||||
"icon": "info-sign",
|
||||
"prefix": "glyphicon",
|
||||
"extraClasses": "fa-rotate-0",
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
marker_0a30d487a2cda7756fd32b90acb9989b.bindTooltip(
|
||||
`<div>
|
||||
End
|
||||
</div>`,
|
||||
{
|
||||
"sticky": true,
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
marker_0a30d487a2cda7756fd32b90acb9989b.setIcon(icon_cdb6bcef11f931e9bb82eb9c1a2e79bf);
|
||||
|
||||
</script>
|
||||
</html>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 291 KiB |
11
resources/projects/azi/pauses.json
Normal file
11
resources/projects/azi/pauses.json
Normal file
@@ -0,0 +1,11 @@
|
||||
[
|
||||
{
|
||||
"start_time": "2025-06-06T04:45:45.000+00:00",
|
||||
"end_time": "2025-06-06T13:49:07.000+00:00",
|
||||
"duration_seconds": 32563,
|
||||
"location": {
|
||||
"latitude": 45.79917944444444,
|
||||
"longitude": 24.085654444444444
|
||||
}
|
||||
}
|
||||
]
|
||||
15997
resources/projects/azi/positions.json
Normal file
15997
resources/projects/azi/positions.json
Normal file
File diff suppressed because it is too large
Load Diff
240
resources/projects/azi/preview.html
Normal file
240
resources/projects/azi/preview.html
Normal file
File diff suppressed because one or more lines are too long
BIN
resources/projects/azi/preview.png
Normal file
BIN
resources/projects/azi/preview.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 877 KiB |
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
Binary file not shown.
|
Before Width: | Height: | Size: 335 KiB |
Binary file not shown.
@@ -4,7 +4,7 @@ from kivy.uix.screenmanager import ScreenManager, Screen
|
||||
import os
|
||||
import json
|
||||
from kivy.clock import Clock
|
||||
from kivy.properties import StringProperty, ListProperty, AliasProperty
|
||||
from kivy.properties import StringProperty, NumericProperty, AliasProperty
|
||||
from utils import (
|
||||
generate_key, load_key, encrypt_data, decrypt_data,
|
||||
check_server_settings, save_server_settings,
|
||||
@@ -33,11 +33,13 @@ from selenium.webdriver.chrome.options import Options
|
||||
from PIL import Image
|
||||
import time
|
||||
import os
|
||||
import math
|
||||
|
||||
class CreateAnimationScreen(Screen):
|
||||
project_name = StringProperty("")
|
||||
preview_html_path = StringProperty("") # Path to the HTML file for preview
|
||||
preview_image_path = StringProperty("") # Add this line
|
||||
preview_image_version = NumericProperty(0) # Add this line
|
||||
|
||||
def get_preview_image_source(self):
|
||||
project_folder = os.path.join(RESOURCES_FOLDER, "projects", self.project_name)
|
||||
@@ -46,7 +48,9 @@ class CreateAnimationScreen(Screen):
|
||||
return img_path
|
||||
return "resources/images/track.png"
|
||||
|
||||
preview_image_source = AliasProperty(get_preview_image_source, None, bind=['project_name'])
|
||||
preview_image_source = AliasProperty(
|
||||
get_preview_image_source, None, bind=['project_name', 'preview_image_version']
|
||||
)
|
||||
|
||||
def on_pre_enter(self):
|
||||
# Update the route entries label with the actual number of entries
|
||||
@@ -130,8 +134,10 @@ class CreateAnimationScreen(Screen):
|
||||
popup.open()
|
||||
|
||||
def process_entries(dt):
|
||||
import datetime
|
||||
project_folder = os.path.join(RESOURCES_FOLDER, "projects", self.project_name)
|
||||
positions_path = os.path.join(project_folder, "positions.json")
|
||||
pauses_path = os.path.join(project_folder, "pauses.json")
|
||||
if not os.path.exists(positions_path):
|
||||
label.text = "positions.json not found!"
|
||||
progress.value = 100
|
||||
@@ -164,10 +170,98 @@ class CreateAnimationScreen(Screen):
|
||||
if end_remove > 0:
|
||||
end_remove -= 1
|
||||
|
||||
# Shorten the positions list
|
||||
new_positions = positions[start_remove:len(positions)-end_remove if end_remove > 0 else None]
|
||||
|
||||
# --- PAUSE DETECTION ---
|
||||
pauses = []
|
||||
if new_positions:
|
||||
pause_start = None
|
||||
pause_end = None
|
||||
pause_location = None
|
||||
for i in range(1, len(new_positions)):
|
||||
prev = new_positions[i-1]
|
||||
curr = new_positions[i]
|
||||
# Check if stopped (same location)
|
||||
if curr['latitude'] == prev['latitude'] and curr['longitude'] == prev['longitude']:
|
||||
if pause_start is None:
|
||||
pause_start = prev['deviceTime']
|
||||
pause_location = (prev['latitude'], prev['longitude'])
|
||||
pause_end = curr['deviceTime']
|
||||
else:
|
||||
if pause_start and pause_end:
|
||||
# Calculate pause duration
|
||||
t1 = datetime.datetime.fromisoformat(pause_start.replace('Z', '+00:00'))
|
||||
t2 = datetime.datetime.fromisoformat(pause_end.replace('Z', '+00:00'))
|
||||
duration = (t2 - t1).total_seconds()
|
||||
if duration >= 180:
|
||||
pauses.append({
|
||||
"start_time": pause_start,
|
||||
"end_time": pause_end,
|
||||
"duration_seconds": int(duration),
|
||||
"location": {"latitude": pause_location[0], "longitude": pause_location[1]}
|
||||
})
|
||||
pause_start = None
|
||||
pause_end = None
|
||||
pause_location = None
|
||||
# Check for pause at the end
|
||||
if pause_start and pause_end:
|
||||
t1 = datetime.datetime.fromisoformat(pause_start.replace('Z', '+00:00'))
|
||||
t2 = datetime.datetime.fromisoformat(pause_end.replace('Z', '+00:00'))
|
||||
duration = (t2 - t1).total_seconds()
|
||||
if duration >= 120:
|
||||
pauses.append({
|
||||
"start_time": pause_start,
|
||||
"end_time": pause_end,
|
||||
"duration_seconds": int(duration),
|
||||
"location": {"latitude": pause_location[0], "longitude": pause_location[1]}
|
||||
})
|
||||
|
||||
# --- FILTER PAUSES ---
|
||||
# 1. Remove pauses near start/end
|
||||
start_lat, start_lon = new_positions[0]['latitude'], new_positions[0]['longitude']
|
||||
end_lat, end_lon = new_positions[-1]['latitude'], new_positions[-1]['longitude']
|
||||
filtered_pauses = []
|
||||
for pause in pauses:
|
||||
plat = pause["location"]["latitude"]
|
||||
plon = pause["location"]["longitude"]
|
||||
dist_start = haversine(start_lat, start_lon, plat, plon)
|
||||
dist_end = haversine(end_lat, end_lon, plat, plon)
|
||||
if dist_start < 50 or dist_end < 50:
|
||||
continue # Skip pauses near start or end
|
||||
filtered_pauses.append(pause)
|
||||
|
||||
# 2. Merge pauses close in time and space
|
||||
merged_pauses = []
|
||||
filtered_pauses.sort(key=lambda p: p["start_time"])
|
||||
for pause in filtered_pauses:
|
||||
if not merged_pauses:
|
||||
merged_pauses.append(pause)
|
||||
else:
|
||||
last = merged_pauses[-1]
|
||||
# Time difference in seconds
|
||||
t1 = datetime.datetime.fromisoformat(last["end_time"].replace('Z', '+00:00'))
|
||||
t2 = datetime.datetime.fromisoformat(pause["start_time"].replace('Z', '+00:00'))
|
||||
time_diff = (t2 - t1).total_seconds()
|
||||
# Distance in meters
|
||||
last_lat = last["location"]["latitude"]
|
||||
last_lon = last["location"]["longitude"]
|
||||
plat = pause["location"]["latitude"]
|
||||
plon = pause["location"]["longitude"]
|
||||
dist = haversine(last_lat, last_lon, plat, plon)
|
||||
if time_diff < 300 and dist < 50:
|
||||
# Merge: extend last pause's end_time and duration
|
||||
last["end_time"] = pause["end_time"]
|
||||
last["duration_seconds"] += pause["duration_seconds"]
|
||||
else:
|
||||
merged_pauses.append(pause)
|
||||
pauses = merged_pauses
|
||||
|
||||
progress.value = 100
|
||||
label.text = (
|
||||
f"Entries removable at start: {start_remove}\n"
|
||||
f"Entries removable at end: {end_remove}"
|
||||
f"Entries removable at end: {end_remove}\n"
|
||||
f"Detected pauses: {len(pauses)}"
|
||||
)
|
||||
|
||||
btn_save = Button(text="Save optimized file", background_color=(0.008, 0.525, 0.290, 1))
|
||||
@@ -178,10 +272,11 @@ class CreateAnimationScreen(Screen):
|
||||
layout.add_widget(btn_box)
|
||||
|
||||
def save_optimized(instance):
|
||||
new_positions = positions[start_remove:len(positions)-end_remove if end_remove > 0 else None]
|
||||
with open(positions_path, "w") as f:
|
||||
json.dump(new_positions, f, indent=2)
|
||||
label.text = "File optimized and saved!"
|
||||
with open(pauses_path, "w") as f:
|
||||
json.dump(pauses, f, indent=2)
|
||||
label.text = "File optimized and pauses saved!"
|
||||
btn_save.disabled = True
|
||||
btn_cancel.disabled = True
|
||||
def close_and_refresh(dt):
|
||||
@@ -212,6 +307,7 @@ class CreateAnimationScreen(Screen):
|
||||
|
||||
def set_preview_image_path(path):
|
||||
self.preview_image_path = path
|
||||
self.preview_image_version += 1 # Force AliasProperty to update
|
||||
self.property('preview_image_source').dispatch(self)
|
||||
self.ids.preview_image.reload()
|
||||
# Schedule the processing function
|
||||
@@ -229,3 +325,13 @@ class CreateAnimationScreen(Screen):
|
||||
0.5
|
||||
)
|
||||
|
||||
def haversine(lat1, lon1, lat2, lon2):
|
||||
# Returns distance in meters between two lat/lon points
|
||||
R = 6371000 # Earth radius in meters
|
||||
phi1 = math.radians(lat1)
|
||||
phi2 = math.radians(lat2)
|
||||
dphi = math.radians(lat2 - lat1)
|
||||
dlambda = math.radians(lon2 - lon1)
|
||||
a = math.sin(dphi/2)**2 + math.cos(phi1)*math.cos(phi2)*math.sin(dlambda/2)**2
|
||||
return 2 * R * math.asin(math.sqrt(a))
|
||||
|
||||
|
||||
@@ -683,10 +683,10 @@
|
||||
Image:
|
||||
id: preview_image
|
||||
source: root.preview_image_source
|
||||
allow_stretch: True
|
||||
keep_ratio: False
|
||||
allow_stretch: False
|
||||
keep_ratio: True
|
||||
size_hint_y: None
|
||||
height: 220
|
||||
height: 202
|
||||
size_hint_x: 1
|
||||
|
||||
|
||||
|
||||
65
utils.py
65
utils.py
@@ -167,17 +167,7 @@ def fetch_positions_for_selected_day(settings, device_mapping, device_name, star
|
||||
return [], error
|
||||
return positions, None
|
||||
|
||||
def html_to_image(html_path, img_path, width=800, height=600, delay=2, driver_path='/usr/bin/chromedriver'):
|
||||
"""
|
||||
Convert an HTML file to an image using Selenium and Pillow.
|
||||
Args:
|
||||
html_path (str): Path to the HTML file.
|
||||
img_path (str): Path to save the output image (PNG).
|
||||
width (int): Width of the browser window.
|
||||
height (int): Height of the browser window.
|
||||
delay (int): Seconds to wait for the page to render.
|
||||
driver_path (str): Path to chromedriver binary.
|
||||
"""
|
||||
def html_to_image(html_path, img_path, width=1280, height=720, delay=2, driver_path='/usr/bin/chromedriver'):
|
||||
from selenium import webdriver
|
||||
from selenium.webdriver.chrome.service import Service
|
||||
from selenium.webdriver.chrome.options import Options
|
||||
@@ -185,9 +175,11 @@ def html_to_image(html_path, img_path, width=800, height=600, delay=2, driver_pa
|
||||
import time
|
||||
import os
|
||||
|
||||
selenium_height = int(height * 1.2) # 10% taller for compensation
|
||||
|
||||
chrome_options = Options()
|
||||
chrome_options.add_argument("--headless")
|
||||
chrome_options.add_argument(f"--window-size={width},{height}")
|
||||
chrome_options.add_argument(f"--window-size={width},{selenium_height}")
|
||||
chrome_options.add_argument("--no-sandbox")
|
||||
chrome_options.add_argument("--disable-dev-shm-usage")
|
||||
|
||||
@@ -195,17 +187,18 @@ def html_to_image(html_path, img_path, width=800, height=600, delay=2, driver_pa
|
||||
driver = webdriver.Chrome(service=service, options=chrome_options)
|
||||
|
||||
try:
|
||||
driver.set_window_size(width, selenium_height)
|
||||
driver.get("file://" + os.path.abspath(html_path))
|
||||
time.sleep(delay) # Wait for the page to render
|
||||
time.sleep(delay)
|
||||
tmp_img = img_path + ".tmp.png"
|
||||
driver.save_screenshot(tmp_img)
|
||||
driver.quit()
|
||||
|
||||
img = Image.open(tmp_img)
|
||||
img = img.crop((0, 0, width, height))
|
||||
img = img.crop((0, 0, width, height)) # Crop to original map size
|
||||
img.save(img_path)
|
||||
os.remove(tmp_img)
|
||||
print(f"Image saved to: {img_path}")
|
||||
print(f"Image saved to: {img_path} ({width}x{height})")
|
||||
except Exception as e:
|
||||
print(f"Error converting HTML to image: {e}")
|
||||
driver.quit()
|
||||
@@ -247,15 +240,53 @@ def process_preview_util(
|
||||
return
|
||||
|
||||
coords = [(pos['latitude'], pos['longitude']) for pos in positions]
|
||||
width, height = 1280, 720 # 16:9 HD
|
||||
|
||||
m = folium.Map(
|
||||
location=coords[0],
|
||||
width=width,
|
||||
height=height
|
||||
height=height,
|
||||
control_scale=True
|
||||
)
|
||||
folium.PolyLine(coords, color="blue", weight=4.5, opacity=1).add_to(m)
|
||||
folium.Marker(coords[0], tooltip="Start", icon=folium.Icon(color="green")).add_to(m)
|
||||
folium.Marker(coords[-1], tooltip="End", icon=folium.Icon(color="red")).add_to(m)
|
||||
m.fit_bounds(coords)
|
||||
|
||||
# --- Add pause markers if pauses.json exists ---
|
||||
pauses_path = os.path.join(project_folder, "pauses.json")
|
||||
if os.path.exists(pauses_path):
|
||||
with open(pauses_path, "r") as pf:
|
||||
pauses = json.load(pf)
|
||||
for pause in pauses:
|
||||
lat = pause["location"]["latitude"]
|
||||
lon = pause["location"]["longitude"]
|
||||
duration = pause["duration_seconds"]
|
||||
start = pause["start_time"]
|
||||
end = pause["end_time"]
|
||||
folium.Marker(
|
||||
[lat, lon],
|
||||
tooltip=f"Pause: {duration//60} min {duration%60} sec",
|
||||
popup=f"Pause from {start} to {end} ({duration//60} min {duration%60} sec)",
|
||||
icon=folium.Icon(color="orange", icon="pause", prefix="fa")
|
||||
).add_to(m)
|
||||
|
||||
m.fit_bounds(coords, padding=(80, 80))
|
||||
m.get_root().html.add_child(folium.Element(f"""
|
||||
<style>
|
||||
html, body {{
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}}
|
||||
#{m.get_name()} {{
|
||||
position: absolute;
|
||||
top: 0; bottom: 0; left: 0; right: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}}
|
||||
</style>
|
||||
"""))
|
||||
m.save(html_path)
|
||||
|
||||
html_to_image(html_path, img_path, width=width, height=height)
|
||||
|
||||
Reference in New Issue
Block a user