-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmap_generator.py
More file actions
117 lines (94 loc) · 4.83 KB
/
Copy pathmap_generator.py
File metadata and controls
117 lines (94 loc) · 4.83 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# C:/Users/krant/PycharmProjects/SelMapExtract/map_generator.py (Top of file)
import configparser
import logging
from pathlib import Path
from typing import List, Optional, Set # Added Optional and Set
import folium
# Import the new Workout class, as this generator will operate on Workout objects
from workout import Workout
# Import the refactored geospatial helper function
from geospatial_utils import create_simplified_geojson
# It's best practice to get the logger at the module level
log = logging.getLogger(__name__)
# --- The main MapGenerator class ---
class MapGenerator:
"""
Handles the creation of visual map outputs from workout data.
"""
def __init__(self, config: configparser.ConfigParser):
"""
Initializes the map generator with paths from the configuration.
"""
self.simplified_folder = Path(config.get('paths', 'simplified_gps_track_folder'))
self.project_path = Path(config.get('paths', 'project_path'))
self.map_file_path = self.project_path / "all_routes.html"
self.simplified_folder.mkdir(parents=True, exist_ok=True)
log.info(f"MapGenerator initialized. Simplified tracks in: {self.simplified_folder}")
def simplify_workouts(self, workouts: List[Workout], workout_types: Optional[Set[str]] = None,
only_if_missing: bool = True):
"""
Creates simplified GeoJSON files for a list of workouts.
"""
mode = "Incremental Mode" if only_if_missing else "Full Rebuild Mode"
log.info(f"--- Simplifying TCX files ({mode}) ---")
# Use workout_types safely
workouts_to_process = [w for w in workouts if not workout_types or w.activity_type.lower() in workout_types]
log.info(f"Found {len(workouts_to_process)} workouts of specified types to process for simplification.")
for workout in workouts_to_process:
if not workout.tcx_path:
continue
dest_path = self.simplified_folder / (workout.tcx_path.stem + '.geojson')
if not only_if_missing or not dest_path.exists():
if workout.tcx_path.exists():
# Call the refactored helper function
create_simplified_geojson(
tcx_path=workout.tcx_path,
geojson_path=dest_path,
tolerance=10.0
)
else:
log.warning(f"Source file not found, cannot simplify: {workout.tcx_path}")
def create_route_map(self):
"""
Creates an HTML map visualizing all simplified GeoJSON routes.
"""
log.info("--- Creating HTML Route Map ---")
geojson_files = list(self.simplified_folder.glob("*.geojson"))
if not geojson_files:
log.warning("No GeoJSON files found to map. Please run simplification first.")
return
log.info(f"Found {len(geojson_files)} GeoJSON files to add to the map.")
# Center the map on Perth, WA as a default
m = folium.Map(location=[-31.95, 115.86], zoom_start=10)
feature_group = folium.FeatureGroup(name="All Routes")
for geojson_file in geojson_files:
try:
# Use the stem as the label/popup
popup_text = geojson_file.stem
# Fix: Use GeoJsonPopup instead of Popup for GeoJson objects
# This resolves the 'Expected type GeoJsonPopup | None' error
gj_popup = folium.GeoJsonPopup(fields=[], labels=False, sticky=True)
folium.GeoJson(
str(geojson_file),
style_function=lambda x: {'color': 'blue', 'weight': 2.5, 'opacity': 0.7},
# We can use a tooltip for simpler stem display or
# use the popup properly if the GeoJSON had properties
tooltip=popup_text
).add_to(feature_group)
except Exception as e:
log.error(f"Could not process GeoJSON file '{geojson_file.name}': {e}")
feature_group.add_to(m)
# Auto-fit the map to the bounds of the routes
bounds = feature_group.get_bounds()
# Fix: Ensure bounds is a valid Sequence[Sequence[float]]
# This resolves the list[list[float | None]] type error
if bounds and len(bounds) == 2:
# We filter out any potential None values to satisfy the IDESequence check
safe_bounds = [[float(coord) for coord in pair if coord is not None] for pair in bounds]
if all(len(pair) == 2 for pair in safe_bounds):
m.fit_bounds(safe_bounds)
try:
m.save(str(self.map_file_path))
log.info(f"Successfully created map: '{self.map_file_path}'")
except Exception as e:
log.error(f"Could not save the map file. Reason: {e}")