updates preview and pauses

This commit is contained in:
2025-06-06 21:29:07 +03:00
parent 6240042901
commit 0ebdbc6b74
20 changed files with 16411 additions and 96046 deletions

Binary file not shown.

46
proba
View File

@@ -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()

View File

@@ -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

View File

@@ -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

View 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
}
}
]

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.

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

View File

@@ -4,7 +4,7 @@ from kivy.uix.screenmanager import ScreenManager, Screen
import os import os
import json import json
from kivy.clock import Clock from kivy.clock import Clock
from kivy.properties import StringProperty, ListProperty, AliasProperty from kivy.properties import StringProperty, NumericProperty, AliasProperty
from utils import ( from utils import (
generate_key, load_key, encrypt_data, decrypt_data, generate_key, load_key, encrypt_data, decrypt_data,
check_server_settings, save_server_settings, check_server_settings, save_server_settings,
@@ -33,11 +33,13 @@ from selenium.webdriver.chrome.options import Options
from PIL import Image from PIL import Image
import time import time
import os import os
import math
class CreateAnimationScreen(Screen): class CreateAnimationScreen(Screen):
project_name = StringProperty("") project_name = StringProperty("")
preview_html_path = StringProperty("") # Path to the HTML file for preview preview_html_path = StringProperty("") # Path to the HTML file for preview
preview_image_path = StringProperty("") # Add this line preview_image_path = StringProperty("") # Add this line
preview_image_version = NumericProperty(0) # Add this line
def get_preview_image_source(self): def get_preview_image_source(self):
project_folder = os.path.join(RESOURCES_FOLDER, "projects", self.project_name) project_folder = os.path.join(RESOURCES_FOLDER, "projects", self.project_name)
@@ -46,7 +48,9 @@ class CreateAnimationScreen(Screen):
return img_path return img_path
return "resources/images/track.png" 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): def on_pre_enter(self):
# Update the route entries label with the actual number of entries # Update the route entries label with the actual number of entries
@@ -130,8 +134,10 @@ class CreateAnimationScreen(Screen):
popup.open() popup.open()
def process_entries(dt): def process_entries(dt):
import datetime
project_folder = os.path.join(RESOURCES_FOLDER, "projects", self.project_name) project_folder = os.path.join(RESOURCES_FOLDER, "projects", self.project_name)
positions_path = os.path.join(project_folder, "positions.json") 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): if not os.path.exists(positions_path):
label.text = "positions.json not found!" label.text = "positions.json not found!"
progress.value = 100 progress.value = 100
@@ -164,10 +170,98 @@ class CreateAnimationScreen(Screen):
if end_remove > 0: if end_remove > 0:
end_remove -= 1 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 progress.value = 100
label.text = ( label.text = (
f"Entries removable at start: {start_remove}\n" 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)) 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) layout.add_widget(btn_box)
def save_optimized(instance): 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: with open(positions_path, "w") as f:
json.dump(new_positions, f, indent=2) 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_save.disabled = True
btn_cancel.disabled = True btn_cancel.disabled = True
def close_and_refresh(dt): def close_and_refresh(dt):
@@ -212,6 +307,7 @@ class CreateAnimationScreen(Screen):
def set_preview_image_path(path): def set_preview_image_path(path):
self.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.property('preview_image_source').dispatch(self)
self.ids.preview_image.reload() self.ids.preview_image.reload()
# Schedule the processing function # Schedule the processing function
@@ -229,3 +325,13 @@ class CreateAnimationScreen(Screen):
0.5 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))

View File

@@ -683,10 +683,10 @@
Image: Image:
id: preview_image id: preview_image
source: root.preview_image_source source: root.preview_image_source
allow_stretch: True allow_stretch: False
keep_ratio: False keep_ratio: True
size_hint_y: None size_hint_y: None
height: 220 height: 202
size_hint_x: 1 size_hint_x: 1

View File

@@ -167,17 +167,7 @@ def fetch_positions_for_selected_day(settings, device_mapping, device_name, star
return [], error return [], error
return positions, None return positions, None
def html_to_image(html_path, img_path, width=800, height=600, delay=2, driver_path='/usr/bin/chromedriver'): def html_to_image(html_path, img_path, width=1280, height=720, 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.
"""
from selenium import webdriver from selenium import webdriver
from selenium.webdriver.chrome.service import Service from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options 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 time
import os import os
selenium_height = int(height * 1.2) # 10% taller for compensation
chrome_options = Options() chrome_options = Options()
chrome_options.add_argument("--headless") 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("--no-sandbox")
chrome_options.add_argument("--disable-dev-shm-usage") 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) driver = webdriver.Chrome(service=service, options=chrome_options)
try: try:
driver.set_window_size(width, selenium_height)
driver.get("file://" + os.path.abspath(html_path)) 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" tmp_img = img_path + ".tmp.png"
driver.save_screenshot(tmp_img) driver.save_screenshot(tmp_img)
driver.quit() driver.quit()
img = Image.open(tmp_img) 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) img.save(img_path)
os.remove(tmp_img) 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: except Exception as e:
print(f"Error converting HTML to image: {e}") print(f"Error converting HTML to image: {e}")
driver.quit() driver.quit()
@@ -247,15 +240,53 @@ def process_preview_util(
return return
coords = [(pos['latitude'], pos['longitude']) for pos in positions] coords = [(pos['latitude'], pos['longitude']) for pos in positions]
width, height = 1280, 720 # 16:9 HD
m = folium.Map( m = folium.Map(
location=coords[0], location=coords[0],
width=width, width=width,
height=height height=height,
control_scale=True
) )
folium.PolyLine(coords, color="blue", weight=4.5, opacity=1).add_to(m) 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[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) 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) m.save(html_path)
html_to_image(html_path, img_path, width=width, height=height) html_to_image(html_path, img_path, width=width, height=height)