config-cli-gui 0.0.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,634 @@
1
+ import math
2
+ import traceback
3
+ import zipfile
4
+ from datetime import datetime
5
+ from pathlib import Path
6
+
7
+ import gpxpy
8
+ from gpxpy.gpx import GPX, GPXTrackPoint, GPXWaypoint, GPXXMLSyntaxException
9
+
10
+ # Optional SRTM import with fallback
11
+ try:
12
+ import srtm
13
+
14
+ SRTM_AVAILABLE = True
15
+ except ImportError:
16
+ SRTM_AVAILABLE = False
17
+ srtm = None
18
+
19
+ # Optional fastkml import for KML reading
20
+ try:
21
+ from fastkml import kml, styles
22
+ from fastkml.features import Document, Folder, Placemark
23
+ from shapely.geometry import LineString, Point
24
+
25
+ KML_AVAILABLE = True
26
+ except ImportError:
27
+ KML_AVAILABLE = False
28
+ kml = styles = Folder = Placemark = Document = Point = LineString = None
29
+
30
+ NAME = "config_cli_gui"
31
+
32
+
33
+ class BaseGPXProcessor:
34
+ def __init__(
35
+ self,
36
+ input_: str | Path | list[str],
37
+ output=None,
38
+ min_dist=10,
39
+ date_format="%Y-%m-%d",
40
+ elevation=True,
41
+ logger=None,
42
+ ):
43
+ # ensure that input is converted into a list[Path]
44
+ if isinstance(input_, str):
45
+ self.input = [Path(input_)]
46
+ elif isinstance(input_, Path):
47
+ self.input = [input_]
48
+ elif isinstance(input_, list):
49
+ self.input = [Path(p) for p in input_ if isinstance(p, str | Path)]
50
+ else:
51
+ raise ValueError("Input must be a string, Path, or list of strings/Paths.")
52
+
53
+ self.output = output
54
+ self.min_dist = min_dist
55
+ self.date_format = date_format
56
+ self.include_elevation = elevation
57
+ self.logger = logger
58
+
59
+ # Initialize SRTM elevation data only if elevation is requested and SRTM is available
60
+ self.elevation_data = None
61
+ self.srtm_available = False
62
+
63
+ if self.include_elevation:
64
+ self._initialize_elevation_data()
65
+
66
+ def _initialize_elevation_data(self):
67
+ """Initialize SRTM elevation data with proper error handling."""
68
+ if not SRTM_AVAILABLE:
69
+ self.logger.warning(
70
+ "SRTM library not available. "
71
+ "Elevation data will use original GPX values or default to 0."
72
+ )
73
+ return
74
+
75
+ try:
76
+ self.logger.info("Initializing SRTM elevation data...")
77
+ self.elevation_data = srtm.get_data()
78
+ self.srtm_available = True
79
+ self.logger.info("SRTM elevation data initialized successfully.")
80
+ except AssertionError as e:
81
+ self.logger.error(f"SRTM initialization failed with AssertionError: {e}")
82
+ self.logger.debug(f"Full traceback:\n{traceback.format_exc()}")
83
+ self._handle_srtm_failure("SRTM assertion failed - possibly network or firewall issue")
84
+ except Exception as e:
85
+ self.logger.error(f"SRTM initialization failed: {e}")
86
+ self.logger.debug(f"Full traceback:\n{traceback.format_exc()}")
87
+ self._handle_srtm_failure(f"SRTM initialization error: {str(e)}")
88
+
89
+ def _handle_srtm_failure(self, error_msg: str):
90
+ """Handle SRTM initialization failure with firewall check."""
91
+ self.srtm_available = False
92
+ self.elevation_data = None
93
+
94
+ self.logger.warning(f"SRTM elevation data unavailable: {error_msg}")
95
+ self.logger.info("Checking for network/firewall issues...")
96
+
97
+ def _get_output_folder(self) -> Path:
98
+ """Get the output folder path, create if not exists."""
99
+ if self.output:
100
+ output_path = Path(self.output)
101
+ else:
102
+ timestamp = datetime.now().strftime(
103
+ f"{self.date_format}_%H%M%S"
104
+ ) # Added seconds for uniqueness
105
+ output_path = Path.cwd() / f"gpx_processed_{timestamp}"
106
+
107
+ output_path.mkdir(parents=True, exist_ok=True)
108
+ return output_path
109
+
110
+ def _get_adjusted_elevation(self, point: GPXTrackPoint) -> int | float:
111
+ """Get adjusted elevation from SRTM data, fallback to original elevation."""
112
+ # If elevation is not requested, return None
113
+ if not self.include_elevation:
114
+ return None
115
+
116
+ # If SRTM is available and working, try to get elevation data
117
+ if self.srtm_available and self.elevation_data:
118
+ try:
119
+ srtm_elevation = self.elevation_data.get_elevation(point.latitude, point.longitude)
120
+ if srtm_elevation is not None:
121
+ return round(srtm_elevation, 1)
122
+ except Exception as e:
123
+ self.logger.info(
124
+ f"Error getting SRTM elevation "
125
+ f"for point ({point.latitude}, {point.longitude}): {e}"
126
+ )
127
+ # Don't disable SRTM entirely for single point failures
128
+ pass
129
+
130
+ # Fallback to original elevation or 0
131
+ original_elevation = (
132
+ point.elevation if hasattr(point, "elevation") and point.elevation is not None else 0
133
+ )
134
+ return round(original_elevation, 1)
135
+
136
+ @staticmethod
137
+ def _calculate_distance(point1: GPXTrackPoint, point2: GPXTrackPoint) -> float:
138
+ """Calculate distance between two GPX points in meters using Haversine formula."""
139
+ try:
140
+ lat1, lon1 = math.radians(point1.latitude), math.radians(point1.longitude)
141
+ lat2, lon2 = math.radians(point2.latitude), math.radians(point2.longitude)
142
+
143
+ dlat = lat2 - lat1
144
+ dlon = lon2 - lon1
145
+
146
+ a = math.sin(dlat / 2) ** 2 + math.cos(lat1) * math.cos(lat2) * math.sin(dlon / 2) ** 2
147
+ c = 2 * math.asin(math.sqrt(a))
148
+
149
+ # Earth's radius in meters
150
+ earth_radius = 6371000
151
+ return earth_radius * c
152
+ except (AttributeError, TypeError, ValueError):
153
+ # Handle cases where points might have invalid coordinates
154
+ return 0.0
155
+
156
+ def _optimize_track_points(
157
+ self, track_points: list[GPXTrackPoint] | list[GPXWaypoint]
158
+ ) -> list[GPXTrackPoint]:
159
+ """Optimize track points by removing close points and cleaning metadata."""
160
+ if not track_points:
161
+ return track_points
162
+
163
+ try:
164
+ optimized_points = [track_points[0]] # Always keep first point
165
+
166
+ for point in track_points[1:]:
167
+ # Check distance to last kept point
168
+ if self._calculate_distance(optimized_points[-1], point) >= self.min_dist:
169
+ optimized_points.append(point)
170
+
171
+ # Always keep last point if it's different from the last kept point
172
+ if len(track_points) > 1 and optimized_points[-1] != track_points[-1]:
173
+ optimized_points.append(track_points[-1])
174
+
175
+ # Clean and optimize each point
176
+ for point in optimized_points:
177
+ try:
178
+ # Remove time information
179
+ point.time = None
180
+
181
+ # Round coordinates to 5 decimal places
182
+ if hasattr(point, "latitude") and point.latitude is not None:
183
+ point.latitude = round(point.latitude, 5)
184
+ if hasattr(point, "longitude") and point.longitude is not None:
185
+ point.longitude = round(point.longitude, 5)
186
+
187
+ # Set optimized elevation
188
+ point.elevation = self._get_adjusted_elevation(point)
189
+
190
+ # Remove unnecessary extensions and metadata
191
+ point.extensions = None
192
+ if hasattr(point, "symbol"):
193
+ point.symbol = None
194
+ if hasattr(point, "type"):
195
+ point.type = None
196
+ point.comment = None
197
+ point.description = None
198
+ point.source = None
199
+ point.link = None
200
+ point.link_text = None
201
+ point.link_type = None
202
+ point.horizontal_dilution = None
203
+ point.vertical_dilution = None
204
+ point.position_dilution = None
205
+ point.age_of_dgps_data = None
206
+ point.dgps_id = None
207
+ except Exception as e:
208
+ self.logger.warning(f"Error optimizing point: {e}")
209
+ continue
210
+
211
+ return optimized_points
212
+
213
+ except Exception as e:
214
+ self.logger.error(f"Error optimizing track points: {e}")
215
+ self.logger.debug(f"Full traceback:\n{traceback.format_exc()}")
216
+ return track_points # Return original points if optimization fails
217
+
218
+ def _get_input_files(self) -> list[Path]:
219
+ """Get all GPX/KML files from input (file, folder, or zip)."""
220
+ input_files = []
221
+ for input_path_str in self.input:
222
+ try:
223
+ input_path = Path(input_path_str)
224
+ self.logger.debug(f"Input path: {input_path.absolute()}")
225
+
226
+ if input_path.is_file():
227
+ if input_path.suffix.lower() == ".gpx":
228
+ input_files.append(input_path)
229
+ elif input_path.suffix.lower() == ".kml":
230
+ input_files.append(input_path)
231
+ elif input_path.suffix.lower() == ".zip":
232
+ input_files.extend(self._extract_gpx_kml_from_zip(input_path))
233
+ elif input_path.is_dir():
234
+ # Get all GPX/KML files in directory
235
+ input_files.extend(input_path.glob("*.gpx"))
236
+ input_files.extend(input_path.glob("*.kml"))
237
+
238
+ # Get GPX/KML files from ZIP files in directory
239
+ for zip_file in input_path.glob("*.zip"):
240
+ input_files.extend(self._extract_gpx_kml_from_zip(zip_file))
241
+ except Exception as e:
242
+ self.logger.error(f"Error processing input path {input_path}: {e}")
243
+ self.logger.debug(f"Full traceback:\n{traceback.format_exc()}")
244
+ continue
245
+
246
+ return input_files
247
+
248
+ def _extract_gpx_kml_from_zip(self, zip_path: Path) -> list[Path]:
249
+ """Extract GPX/KML files from ZIP archive to temporary location."""
250
+ extracted_files = []
251
+ temp_dir = Path.cwd() / "temp_extracted_files"
252
+
253
+ try:
254
+ temp_dir.mkdir(exist_ok=True)
255
+
256
+ with zipfile.ZipFile(zip_path, "r") as zip_ref:
257
+ for file_info in zip_ref.infolist():
258
+ if file_info.filename.lower().endswith((".gpx", ".kml")):
259
+ extracted_path = temp_dir / Path(file_info.filename).name
260
+ with open(extracted_path, "wb") as f:
261
+ f.write(zip_ref.read(file_info.filename))
262
+ extracted_files.append(extracted_path)
263
+ except Exception as e:
264
+ self.logger.error(f"Error extracting ZIP file {zip_path}: {e}")
265
+ self.logger.debug(f"Full traceback:\n{traceback.format_exc()}")
266
+
267
+ return extracted_files
268
+
269
+ def _load_gpx_file(self, gpx_path: Path) -> GPX | None:
270
+ """Load and parse GPX file."""
271
+ try:
272
+ with open(gpx_path, "r", encoding="utf-8") as f:
273
+ return gpxpy.parse(f)
274
+ except GPXXMLSyntaxException as e:
275
+ self.logger.error(f"Error parsing GPX file {gpx_path}: {e}")
276
+ return None
277
+ except Exception as e:
278
+ self.logger.error(f"Error loading GPX file {gpx_path}: {e}")
279
+ self.logger.debug(f"Full traceback:\n{traceback.format_exc()}")
280
+ return None
281
+
282
+ def _load_kml_file(self, kml_path: Path) -> GPX | None:
283
+ """Load and parse KML file, converting it to a GPX object."""
284
+ if not KML_AVAILABLE:
285
+ self.logger.error("fastkml library is not available to process KML files.")
286
+ return None
287
+
288
+ try:
289
+ with open(kml_path, "r", encoding="utf-8") as f:
290
+ doc = f.read()
291
+
292
+ k = kml.KML()
293
+ k.from_string(doc)
294
+
295
+ gpx = gpxpy.gpx.GPX()
296
+
297
+ # Iterate through KML features and convert to GPX tracks/waypoints
298
+ for feature in k.features():
299
+ if isinstance(feature, Document) or isinstance(feature, Folder):
300
+ for sub_feature in feature.features():
301
+ self._process_kml_feature(sub_feature, gpx)
302
+ else:
303
+ self._process_kml_feature(feature, gpx)
304
+
305
+ self.logger.info(f"Successfully loaded and converted KML file {kml_path} to GPX.")
306
+ return gpx
307
+
308
+ except Exception as e:
309
+ self.logger.error(f"Error loading or converting KML file {kml_path}: {e}")
310
+ self.logger.debug(f"Full traceback:\n{traceback.format_exc()}")
311
+ return None
312
+
313
+ def _process_kml_feature(self, feature, gpx: GPX):
314
+ """Recursively process KML features to extract points and add to GPX."""
315
+ if isinstance(feature, Placemark):
316
+ if feature.geometry is not None:
317
+ if isinstance(feature.geometry, Point):
318
+ waypoint = gpxpy.gpx.GPXWaypoint(
319
+ latitude=feature.geometry.y,
320
+ longitude=feature.geometry.x,
321
+ elevation=feature.geometry.z if feature.geometry.has_z else None,
322
+ name=feature.name,
323
+ description=feature.description,
324
+ )
325
+ gpx.waypoints.append(waypoint)
326
+ elif isinstance(feature.geometry, LineString):
327
+ gpx_track = gpxpy.gpx.GPXTrack()
328
+ gpx_track.name = feature.name
329
+ gpx_segment = gpxpy.gpx.GPXTrackSegment()
330
+ for coord in feature.geometry.coords:
331
+ point = gpxpy.gpx.GPXTrackPoint(
332
+ latitude=coord[1],
333
+ longitude=coord[0],
334
+ elevation=coord[2] if len(coord) > 2 else None,
335
+ )
336
+ gpx_segment.points.append(point)
337
+ if gpx_segment.points:
338
+ gpx_track.segments.append(gpx_segment)
339
+ if gpx_track.segments:
340
+ gpx.tracks.append(gpx_track)
341
+ elif isinstance(feature, Document) or isinstance(feature, Folder):
342
+ for sub_feature in feature.features():
343
+ self._process_kml_feature(sub_feature, gpx)
344
+
345
+ def _save_gpx_file(
346
+ self, gpx: GPX, output_path: Path, original_file: Path | None = None
347
+ ) -> Path:
348
+ """Save GPX object to file and return the path."""
349
+ try:
350
+ with open(output_path, "w", encoding="utf-8") as f:
351
+ f.write(gpx.to_xml())
352
+ if original_file and original_file.exists():
353
+ self.logger.info(
354
+ f"Original file size: {Path(original_file).stat().st_size / 1024:.2f} KB"
355
+ )
356
+ self.logger.info(f"Processed file size: {output_path.stat().st_size / 1024:.2f} KB")
357
+ return output_path
358
+ except Exception as e:
359
+ self.logger.error(f"Error saving GPX file {output_path}: {e}")
360
+ self.logger.debug(f"Full traceback:\n{traceback.format_exc()}")
361
+ return None
362
+
363
+ def compress_files(self) -> list[str]:
364
+ """Shrink the size of all given gpx/kml files in self.input."""
365
+ generated_files = []
366
+ try:
367
+ input_files = self._get_input_files()
368
+ output_folder = self._get_output_folder()
369
+
370
+ self.logger.info(f"Processing {len(input_files)} GPX/KML files for compression...")
371
+
372
+ for input_file in input_files:
373
+ try:
374
+ gpx = None
375
+ if input_file.suffix.lower() == ".gpx":
376
+ gpx = self._load_gpx_file(input_file)
377
+ elif input_file.suffix.lower() == ".kml":
378
+ gpx = self._load_kml_file(input_file)
379
+
380
+ if gpx is None:
381
+ continue
382
+
383
+ # process and clean waypoints
384
+ for waypoint in gpx.waypoints:
385
+ self._optimize_waypoint(waypoint)
386
+
387
+ # Process all tracks
388
+ for track in gpx.tracks:
389
+ for segment in track.segments:
390
+ segment.points = self._optimize_track_points(segment.points)
391
+
392
+ # Process all routes
393
+ for route in gpx.routes:
394
+ route.points = self._optimize_track_points(route.points)
395
+
396
+ # Clean GPX metadata
397
+ gpx.time = None
398
+ gpx.extensions = None
399
+
400
+ # Save compressed file
401
+ output_path = output_folder / f"compressed_{input_file.stem}.gpx"
402
+ saved_path = self._save_gpx_file(gpx, output_path, input_file)
403
+ if saved_path:
404
+ generated_files.append(str(saved_path))
405
+ self.logger.info(f"Compressed: {input_file.name} -> {output_path}")
406
+
407
+ except Exception as e:
408
+ self.logger.error(f"Error processing file {input_file}: {e}")
409
+ self.logger.debug(f"Full traceback:\n{traceback.format_exc()}")
410
+ continue
411
+ return generated_files
412
+
413
+ except Exception as e:
414
+ self.logger.error(f"Error in compress_files: {e}")
415
+ self.logger.debug(f"Full traceback:\n{traceback.format_exc()}")
416
+ raise
417
+
418
+ def _optimize_waypoint(self, waypoint: GPXWaypoint) -> GPXWaypoint:
419
+ """Optimize waypoint with error handling."""
420
+ try:
421
+ # Round coordinates and elevation
422
+ if hasattr(waypoint, "latitude") and waypoint.latitude is not None:
423
+ waypoint.latitude = round(waypoint.latitude, 5)
424
+ if hasattr(waypoint, "longitude") and waypoint.longitude is not None:
425
+ waypoint.longitude = round(waypoint.longitude, 5)
426
+
427
+ waypoint.elevation = self._get_adjusted_elevation(waypoint)
428
+
429
+ # Clean metadata
430
+ waypoint.time = None
431
+ waypoint.extensions = None
432
+ if hasattr(waypoint, "symbol"):
433
+ waypoint.symbol = None
434
+ if hasattr(waypoint, "type"):
435
+ waypoint.type = None
436
+ waypoint.comment = None
437
+ waypoint.description = None
438
+ waypoint.source = None
439
+ waypoint.link = None
440
+ waypoint.link_text = None
441
+ waypoint.link_type = None
442
+ return waypoint
443
+ except Exception as e:
444
+ self.logger.warning(f"Error optimizing waypoint: {e}")
445
+ return waypoint
446
+
447
+ def merge_files(self) -> list[str]:
448
+ """Merge all files of self.input into one gpx file with reduced resolution."""
449
+ generated_files = []
450
+ try:
451
+ input_files = self._get_input_files()
452
+ output_folder = self._get_output_folder()
453
+
454
+ if not input_files:
455
+ self.logger.error("No GPX/KML files found to merge.")
456
+ return []
457
+
458
+ self.logger.info(f"Merging {len(input_files)} GPX/KML files...")
459
+
460
+ # Create new GPX object
461
+ merged_gpx = gpxpy.gpx.GPX()
462
+ merged_gpx.name = "Merged GPX Tracks"
463
+ merged_gpx.description = f"Merged from {len(input_files)} GPX/KML files."
464
+
465
+ track_counter = 1
466
+
467
+ for input_file in input_files:
468
+ try:
469
+ gpx = None
470
+ if input_file.suffix.lower() == ".gpx":
471
+ gpx = self._load_gpx_file(input_file)
472
+ elif input_file.suffix.lower() == ".kml":
473
+ gpx = self._load_kml_file(input_file)
474
+
475
+ if gpx is None:
476
+ continue
477
+
478
+ # Add all waypoints from this file
479
+ for waypoint in gpx.waypoints:
480
+ waypoint = self._optimize_waypoint(waypoint)
481
+ waypoint.name = f"{waypoint.name or 'Waypoint'}_{track_counter}"
482
+ merged_gpx.waypoints.append(waypoint)
483
+
484
+ # Add all tracks from this file
485
+ for track in gpx.tracks:
486
+ new_track = gpxpy.gpx.GPXTrack()
487
+ new_track.name = (
488
+ f"{track.name or 'Track'}_{input_file.stem}_{track_counter}"
489
+ )
490
+
491
+ for segment in track.segments:
492
+ optimized_points = self._optimize_track_points(segment.points)
493
+ if optimized_points:
494
+ new_segment = gpxpy.gpx.GPXTrackSegment()
495
+ new_segment.points = optimized_points
496
+ new_track.segments.append(new_segment)
497
+
498
+ if new_track.segments:
499
+ merged_gpx.tracks.append(new_track)
500
+ track_counter += 1
501
+
502
+ # Add all routes from this file
503
+ for route in gpx.routes:
504
+ new_route = gpxpy.gpx.GPXRoute()
505
+ new_route.name = (
506
+ f"{route.name or 'Route'}_{input_file.stem}_{track_counter}"
507
+ )
508
+ new_route.points = self._optimize_track_points(route.points)
509
+
510
+ if new_route.points:
511
+ merged_gpx.routes.append(new_route)
512
+ track_counter += 1
513
+
514
+ except Exception as e:
515
+ self.logger.error(f"Error processing file {input_file} during merge: {e}")
516
+ self.logger.debug(f"Full traceback:\n{traceback.format_exc()}")
517
+ continue
518
+
519
+ # Save merged file
520
+ output_path = output_folder / "merged_tracks.gpx"
521
+ saved_path = self._save_gpx_file(merged_gpx, output_path)
522
+ if saved_path:
523
+ generated_files.append(str(saved_path))
524
+ self.logger.info(f"Merged file saved: {output_path}")
525
+ return generated_files
526
+
527
+ except Exception as e:
528
+ self.logger.error(f"Error in merge_files: {e}")
529
+ self.logger.debug(f"Full traceback:\n{traceback.format_exc()}")
530
+ raise
531
+
532
+ def extract_pois(self) -> list[str]:
533
+ """Merge every starting point of each track in all files
534
+ into one gpx file with many pois."""
535
+ generated_files = []
536
+ try:
537
+ input_files = self._get_input_files()
538
+ output_folder = self._get_output_folder()
539
+
540
+ if not input_files:
541
+ self.logger.error("No GPX/KML files found to extract POIs from.")
542
+ return []
543
+
544
+ self.logger.info(f"Extracting POIs from {len(input_files)} GPX/KML files...")
545
+
546
+ # Create new GPX object for waypoints
547
+ poi_gpx = gpxpy.gpx.GPX()
548
+ poi_gpx.name = "Extracted Track Starting Points"
549
+ poi_gpx.description = f"Starting points extracted from {len(input_files)} GPX/KML files"
550
+
551
+ poi_counter = 1
552
+
553
+ for input_file in input_files:
554
+ try:
555
+ gpx = None
556
+ if input_file.suffix.lower() == ".gpx":
557
+ gpx = self._load_gpx_file(input_file)
558
+ elif input_file.suffix.lower() == ".kml":
559
+ gpx = self._load_kml_file(input_file)
560
+
561
+ if gpx is None:
562
+ continue
563
+
564
+ # Extract starting points from tracks
565
+ for track_idx, track in enumerate(gpx.tracks):
566
+ for _segment_idx, segment in enumerate(track.segments):
567
+ if segment.points:
568
+ start_point = segment.points[0]
569
+
570
+ # Create waypoint from starting point
571
+ waypoint = gpxpy.gpx.GPXWaypoint(
572
+ latitude=round(start_point.latitude, 5),
573
+ longitude=round(start_point.longitude, 5),
574
+ elevation=self._get_adjusted_elevation(start_point),
575
+ )
576
+
577
+ track_name = track.name or f"Track_{track_idx + 1}"
578
+ waypoint.name = f"POI_{poi_counter:03d}"
579
+ waypoint.description = (
580
+ f"Start of {track_name} from {input_file.name}"
581
+ )
582
+ waypoint.type = "Track Start"
583
+
584
+ poi_gpx.waypoints.append(waypoint)
585
+ poi_counter += 1
586
+
587
+ # Extract starting points from routes
588
+ for route_idx, route in enumerate(gpx.routes):
589
+ if route.points:
590
+ start_point = route.points[0]
591
+
592
+ # Create waypoint from starting point
593
+ waypoint = gpxpy.gpx.GPXWaypoint(
594
+ latitude=round(start_point.latitude, 5),
595
+ longitude=round(start_point.longitude, 5),
596
+ elevation=self._get_adjusted_elevation(start_point),
597
+ )
598
+
599
+ route_name = route.name or f"Route_{route_idx + 1}"
600
+ waypoint.name = f"POI_{poi_counter:03d}"
601
+ waypoint.description = f"Start of {route_name} from {input_file.name}"
602
+ waypoint.type = "Route Start"
603
+
604
+ poi_gpx.waypoints.append(waypoint)
605
+ poi_counter += 1
606
+
607
+ except Exception as e:
608
+ self.logger.error(
609
+ f"Error processing file {input_file} during POI extraction: {e}"
610
+ )
611
+ self.logger.debug(f"Full traceback:\n{traceback.format_exc()}")
612
+ continue
613
+
614
+ # Save POI file
615
+ output_path = output_folder / "extracted_pois.gpx"
616
+ saved_path = self._save_gpx_file(poi_gpx, output_path)
617
+ if saved_path:
618
+ generated_files.append(str(saved_path))
619
+ self.logger.info(
620
+ f"POI file saved with {len(poi_gpx.waypoints)} waypoints: {output_path}"
621
+ )
622
+
623
+ # Clean up temporary files
624
+ temp_dir = Path.cwd() / "temp_extracted_files" # Changed folder name
625
+ if temp_dir.exists():
626
+ import shutil
627
+
628
+ shutil.rmtree(temp_dir)
629
+ return generated_files
630
+
631
+ except Exception as e:
632
+ self.logger.error(f"Error in extract_pois: {e}")
633
+ self.logger.debug(f"Full traceback:\n{traceback.format_exc()}")
634
+ raise