python-template-project 0.1.19__tar.gz → 0.1.21__tar.gz

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.
Files changed (80) hide show
  1. {python_template_project-0.1.19 → python_template_project-0.1.21}/.github/workflows/build-macos.yml +1 -2
  2. {python_template_project-0.1.19 → python_template_project-0.1.21}/.github/workflows/rename_project.yml +1 -1
  3. {python_template_project-0.1.19 → python_template_project-0.1.21}/HISTORY.md +28 -0
  4. {python_template_project-0.1.19 → python_template_project-0.1.21}/Makefile +2 -2
  5. {python_template_project-0.1.19/src/python_template_project.egg-info → python_template_project-0.1.21}/PKG-INFO +3 -1
  6. python_template_project-0.1.21/config.yaml +22 -0
  7. {python_template_project-0.1.19 → python_template_project-0.1.21}/mkdocs.yml +1 -1
  8. {python_template_project-0.1.19 → python_template_project-0.1.21}/pyproject.toml +2 -0
  9. {python_template_project-0.1.19 → python_template_project-0.1.21}/src/python_template_project/_version.py +2 -2
  10. {python_template_project-0.1.19 → python_template_project-0.1.21}/src/python_template_project/core/base.py +182 -71
  11. {python_template_project-0.1.19 → python_template_project-0.1.21}/src/python_template_project/gui/gui.py +226 -47
  12. {python_template_project-0.1.19 → python_template_project-0.1.21/src/python_template_project.egg-info}/PKG-INFO +3 -1
  13. {python_template_project-0.1.19 → python_template_project-0.1.21}/src/python_template_project.egg-info/requires.txt +2 -0
  14. {python_template_project-0.1.19 → python_template_project-0.1.21}/tests/test_gpx.py +6 -6
  15. {python_template_project-0.1.19 → python_template_project-0.1.21}/uv.lock +148 -1
  16. python_template_project-0.1.19/config.yaml +0 -96
  17. {python_template_project-0.1.19 → python_template_project-0.1.21}/.github/FUNDING.yml +0 -0
  18. {python_template_project-0.1.19 → python_template_project-0.1.21}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  19. {python_template_project-0.1.19 → python_template_project-0.1.21}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  20. {python_template_project-0.1.19 → python_template_project-0.1.21}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  21. {python_template_project-0.1.19 → python_template_project-0.1.21}/.github/actions/setup-environment/action.yml +0 -0
  22. {python_template_project-0.1.19 → python_template_project-0.1.21}/.github/dependabot.yml +0 -0
  23. {python_template_project-0.1.19 → python_template_project-0.1.21}/.github/init.sh +0 -0
  24. {python_template_project-0.1.19 → python_template_project-0.1.21}/.github/release_message.sh +0 -0
  25. {python_template_project-0.1.19 → python_template_project-0.1.21}/.github/rename_project.sh +0 -0
  26. {python_template_project-0.1.19 → python_template_project-0.1.21}/.github/template.yml +0 -0
  27. {python_template_project-0.1.19 → python_template_project-0.1.21}/.github/workflows/build.yml +0 -0
  28. {python_template_project-0.1.19 → python_template_project-0.1.21}/.github/workflows/main.yml +0 -0
  29. {python_template_project-0.1.19 → python_template_project-0.1.21}/.github/workflows/release.yml +0 -0
  30. {python_template_project-0.1.19 → python_template_project-0.1.21}/.github/workflows/update_readme.yml +0 -0
  31. {python_template_project-0.1.19 → python_template_project-0.1.21}/.gitignore +0 -0
  32. {python_template_project-0.1.19 → python_template_project-0.1.21}/.idea/runConfigurations/config_generate.xml +0 -0
  33. {python_template_project-0.1.19 → python_template_project-0.1.21}/.idea/runConfigurations/module_cli.xml +0 -0
  34. {python_template_project-0.1.19 → python_template_project-0.1.21}/.idea/runConfigurations/module_gui.xml +0 -0
  35. {python_template_project-0.1.19 → python_template_project-0.1.21}/.pre-commit-config.yaml +0 -0
  36. {python_template_project-0.1.19 → python_template_project-0.1.21}/.readthedocs.yaml +0 -0
  37. {python_template_project-0.1.19 → python_template_project-0.1.21}/LICENSE +0 -0
  38. {python_template_project-0.1.19 → python_template_project-0.1.21}/README.md +0 -0
  39. {python_template_project-0.1.19 → python_template_project-0.1.21}/docs/.nav.yml +0 -0
  40. {python_template_project-0.1.19 → python_template_project-0.1.21}/docs/_static/img/favicon.png +0 -0
  41. {python_template_project-0.1.19 → python_template_project-0.1.21}/docs/_static/img/logo.png +0 -0
  42. {python_template_project-0.1.19 → python_template_project-0.1.21}/docs/css/custom.css +0 -0
  43. {python_template_project-0.1.19 → python_template_project-0.1.21}/docs/develop/01_getting_started_dev.md +0 -0
  44. {python_template_project-0.1.19 → python_template_project-0.1.21}/docs/develop/contributing.md +0 -0
  45. {python_template_project-0.1.19 → python_template_project-0.1.21}/docs/develop/make_windows.md +0 -0
  46. {python_template_project-0.1.19 → python_template_project-0.1.21}/docs/develop/naming_convention.md +0 -0
  47. {python_template_project-0.1.19 → python_template_project-0.1.21}/docs/develop/pypi_release.md +0 -0
  48. {python_template_project-0.1.19 → python_template_project-0.1.21}/docs/getting-started/install.md +0 -0
  49. {python_template_project-0.1.19 → python_template_project-0.1.21}/docs/getting-started/virtual-environment.md +0 -0
  50. {python_template_project-0.1.19 → python_template_project-0.1.21}/docs/index.md +0 -0
  51. {python_template_project-0.1.19 → python_template_project-0.1.21}/docs/usage/cli.md +0 -0
  52. {python_template_project-0.1.19 → python_template_project-0.1.21}/docs/usage/config.md +0 -0
  53. {python_template_project-0.1.19 → python_template_project-0.1.21}/examples/kuhkopfsteig.gpx +0 -0
  54. {python_template_project-0.1.19 → python_template_project-0.1.21}/examples/rother_lilienstein.gpx +0 -0
  55. {python_template_project-0.1.19 → python_template_project-0.1.21}/examples/teneriffa.gpx +0 -0
  56. {python_template_project-0.1.19 → python_template_project-0.1.21}/scripts/show_filelist.ps1 +0 -0
  57. {python_template_project-0.1.19 → python_template_project-0.1.21}/scripts/show_tree.ps1 +0 -0
  58. {python_template_project-0.1.19 → python_template_project-0.1.21}/scripts/update_readme.py +0 -0
  59. {python_template_project-0.1.19 → python_template_project-0.1.21}/setup.cfg +0 -0
  60. {python_template_project-0.1.19 → python_template_project-0.1.21}/src/__init__.py +0 -0
  61. {python_template_project-0.1.19 → python_template_project-0.1.21}/src/firewall_handler.py +0 -0
  62. {python_template_project-0.1.19 → python_template_project-0.1.21}/src/main.py +0 -0
  63. {python_template_project-0.1.19 → python_template_project-0.1.21}/src/python_template_project/__init__.py +0 -0
  64. {python_template_project-0.1.19 → python_template_project-0.1.21}/src/python_template_project/__main__.py +0 -0
  65. {python_template_project-0.1.19 → python_template_project-0.1.21}/src/python_template_project/cli/__init__.py +0 -0
  66. {python_template_project-0.1.19 → python_template_project-0.1.21}/src/python_template_project/cli/__main__.py +0 -0
  67. {python_template_project-0.1.19 → python_template_project-0.1.21}/src/python_template_project/cli/cli.py +0 -0
  68. {python_template_project-0.1.19 → python_template_project-0.1.21}/src/python_template_project/config/__init__.py +0 -0
  69. {python_template_project-0.1.19 → python_template_project-0.1.21}/src/python_template_project/config/config.py +0 -0
  70. {python_template_project-0.1.19 → python_template_project-0.1.21}/src/python_template_project/core/__init__.py +0 -0
  71. {python_template_project-0.1.19 → python_template_project-0.1.21}/src/python_template_project/core/logging.py +0 -0
  72. {python_template_project-0.1.19 → python_template_project-0.1.21}/src/python_template_project/gui/__init__.py +0 -0
  73. {python_template_project-0.1.19 → python_template_project-0.1.21}/src/python_template_project/gui/__main__.py +0 -0
  74. {python_template_project-0.1.19 → python_template_project-0.1.21}/src/python_template_project.egg-info/SOURCES.txt +0 -0
  75. {python_template_project-0.1.19 → python_template_project-0.1.21}/src/python_template_project.egg-info/dependency_links.txt +0 -0
  76. {python_template_project-0.1.19 → python_template_project-0.1.21}/src/python_template_project.egg-info/entry_points.txt +0 -0
  77. {python_template_project-0.1.19 → python_template_project-0.1.21}/src/python_template_project.egg-info/top_level.txt +0 -0
  78. {python_template_project-0.1.19 → python_template_project-0.1.21}/template.yml.url +0 -0
  79. {python_template_project-0.1.19 → python_template_project-0.1.21}/tests/__init__.py +0 -0
  80. {python_template_project-0.1.19 → python_template_project-0.1.21}/tests/test_generic_cli.py +0 -0
@@ -30,8 +30,7 @@ jobs:
30
30
  python-version: ${{ matrix.python-version }}
31
31
 
32
32
  - name: Build Unified Executable and App Bundle with PyInstaller
33
- run: |
34
- run: make build-win
33
+ run: make build-macos
35
34
 
36
35
  - name: Upload ZIP to GitHub Release
37
36
  uses: softprops/action-gh-release@v2
@@ -1,6 +1,6 @@
1
1
  name: Rename the project from template
2
2
 
3
- on: [push]
3
+ on: [workflow_dispatch]
4
4
 
5
5
  permissions: write-all
6
6
 
@@ -4,6 +4,34 @@ Changelog
4
4
 
5
5
  (unreleased)
6
6
  ------------
7
+ - Feat #31: move project specific namings into makefile so that it can
8
+ be renamed. [Paul Magister]
9
+ - Feat #31: move project specific namings into makefile so that it can
10
+ be renamed. [Paul Magister]
11
+
12
+
13
+ 0.1.19 (2025-06-20)
14
+ -------------------
15
+ - Feat #31: move project specific namings into makefile so that it can
16
+ be renamed. [Paul Magister]
17
+ - Feat #31: try this renaming automation. [Paul Magister]
18
+ - Feat #31: improve renaming automation: has to be triggered manually,
19
+ keep kebab-case if project is named like this. [Paul Magister]
20
+ - Feat #31: improve renaming automation: has to be triggered manually,
21
+ keep kebab-case if project is named like this. [Paul Magister]
22
+ - Feat #31: improve renaming automation: has to be triggered manually,
23
+ keep kebab-case if project is named like this. [Paul Magister]
24
+ - Feat #31: consistent naming. [Paul Magister]
25
+ - Feat #31: fix rename_project.yml: use correct git auto commit. [Paul
26
+ Magister]
27
+ - Feat #31: fix rename_project.yml: use correct git auto commit. [Paul
28
+ Magister]
29
+ - Feat #30: fix link issue by modifying link in README.md. [Paul
30
+ Magister]
31
+
32
+
33
+ 0.1.18 (2025-06-20)
34
+ -------------------
7
35
  - Feat #30: do not track _version.py. [Paul Magister]
8
36
  - Feat #30: fix version. [Paul Magister]
9
37
 
@@ -160,11 +160,11 @@ docs: ## Build and sync the documentation.
160
160
 
161
161
  .PHONY: list
162
162
  list: ## Show project file list (excluding ignored folders)
163
- @powershell -NoProfile -ExecutionPolicy Bypass -File ./scripts/show-filelist.ps1
163
+ @powershell -NoProfile -ExecutionPolicy Bypass -File ./scripts/show_filelist.ps1
164
164
 
165
165
  .PHONY: tree
166
166
  tree: ## Show project tree (excluding ignored folders)
167
- @powershell -NoProfile -ExecutionPolicy Bypass -File ./scripts/show-tree.ps1
167
+ @powershell -NoProfile -ExecutionPolicy Bypass -File ./scripts/show_tree.ps1
168
168
 
169
169
  .PHONY: init
170
170
  init: ## Initialize the project based on an application template.
@@ -1,14 +1,16 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-template-project
3
- Version: 0.1.19
3
+ Version: 0.1.21
4
4
  Summary: Feature-rich Python project template designed for robustness and ease of use.
5
5
  Author: pamagister
6
6
  Requires-Python: <3.12,>=3.10
7
7
  Description-Content-Type: text/markdown
8
8
  License-File: LICENSE
9
+ Requires-Dist: fastkml>=1.1.0
9
10
  Requires-Dist: gpxpy>=1.6.2
10
11
  Requires-Dist: pydantic>=2.11.7
11
12
  Requires-Dist: pyyaml>=6.0.2
13
+ Requires-Dist: shapely>=2.1.1
12
14
  Requires-Dist: srtm-py>=0.3.7
13
15
  Provides-Extra: dev
14
16
  Requires-Dist: pytest>=8.4.0; extra == "dev"
@@ -0,0 +1,22 @@
1
+ app:
2
+ date_format: '%Y-%m-%d'
3
+ enable_console_logging: true
4
+ enable_file_logging: true
5
+ log_backup_count: 5
6
+ log_file_max_size: 10
7
+ log_format: detailed
8
+ log_level: INFO
9
+ max_workers: 4
10
+ cli:
11
+ elevation: true
12
+ extract_waypoints: true
13
+ input: ''
14
+ min_dist: 100
15
+ output: ''
16
+ gui:
17
+ auto_scroll_log: true
18
+ log_window_height: 200
19
+ max_log_lines: 1000
20
+ theme: light
21
+ window_height: 600
22
+ window_width: 800
@@ -1,7 +1,7 @@
1
1
  site_name: python-template-project
2
2
  site_url: !ENV READTHEDOCS_CANONICAL_URL
3
3
  site_description: A feature-rich Python project template with with auto-generated CLI, GUI and parameterized configuration.
4
- repo_url: https://github.com/pamagister/python-template-projec
4
+ repo_url: https://github.com/pamagister/python-template-project
5
5
  repo_name: GitHub
6
6
 
7
7
  # See also:
@@ -8,9 +8,11 @@ authors = [
8
8
  readme = "docs/index.md"
9
9
  requires-python = ">=3.10,<3.12"
10
10
  dependencies = [
11
+ "fastkml>=1.1.0",
11
12
  "gpxpy>=1.6.2",
12
13
  "pydantic>=2.11.7",
13
14
  "pyyaml>=6.0.2",
15
+ "shapely>=2.1.1",
14
16
  "srtm-py>=0.3.7",
15
17
  ]
16
18
 
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '0.1.19'
21
- __version_tuple__ = version_tuple = (0, 1, 19)
20
+ __version__ = version = '0.1.21'
21
+ __version_tuple__ = version_tuple = (0, 1, 21)
@@ -16,6 +16,17 @@ except ImportError:
16
16
  SRTM_AVAILABLE = False
17
17
  srtm = None
18
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
+
19
30
  NAME = "python_template_project"
20
31
 
21
32
 
@@ -114,7 +125,9 @@ class BaseGPXProcessor:
114
125
  if self.output:
115
126
  output_path = Path(self.output)
116
127
  else:
117
- timestamp = datetime.now().strftime(f"{self.date_format}_%H%M")
128
+ timestamp = datetime.now().strftime(
129
+ f"{self.date_format}_%H%M%S"
130
+ ) # Added seconds for uniqueness
118
131
  output_path = Path.cwd() / f"gpx_processed_{timestamp}"
119
132
 
120
133
  output_path.mkdir(parents=True, exist_ok=True)
@@ -228,54 +241,56 @@ class BaseGPXProcessor:
228
241
  self.logger.debug(f"Full traceback:\n{traceback.format_exc()}")
229
242
  return track_points # Return original points if optimization fails
230
243
 
231
- def _get_gpx_files(self) -> list[Path]:
232
- """Get all GPX files from input (file, folder, or zip)."""
233
- gpx_files = []
234
- for input_path in self.input:
244
+ def _get_input_files(self) -> list[Path]:
245
+ """Get all GPX/KML files from input (file, folder, or zip)."""
246
+ input_files = []
247
+ for input_path_str in self.input:
235
248
  try:
236
- if not isinstance(input_path, Path):
237
- input_path = Path(input_path)
249
+ input_path = Path(input_path_str)
238
250
  self.logger.debug(f"Input path: {input_path.absolute()}")
239
251
 
240
252
  if input_path.is_file():
241
253
  if input_path.suffix.lower() == ".gpx":
242
- gpx_files.append(input_path)
254
+ input_files.append(input_path)
255
+ elif input_path.suffix.lower() == ".kml":
256
+ input_files.append(input_path)
243
257
  elif input_path.suffix.lower() == ".zip":
244
- gpx_files.extend(self._extract_gpx_from_zip(input_path))
258
+ input_files.extend(self._extract_gpx_kml_from_zip(input_path))
245
259
  elif input_path.is_dir():
246
- # Get all GPX files in directory
247
- gpx_files.extend(input_path.glob("*.gpx"))
260
+ # Get all GPX/KML files in directory
261
+ input_files.extend(input_path.glob("*.gpx"))
262
+ input_files.extend(input_path.glob("*.kml"))
248
263
 
249
- # Get GPX files from ZIP files in directory
264
+ # Get GPX/KML files from ZIP files in directory
250
265
  for zip_file in input_path.glob("*.zip"):
251
- gpx_files.extend(self._extract_gpx_from_zip(zip_file))
266
+ input_files.extend(self._extract_gpx_kml_from_zip(zip_file))
252
267
  except Exception as e:
253
268
  self.logger.error(f"Error processing input path {input_path}: {e}")
254
269
  self.logger.debug(f"Full traceback:\n{traceback.format_exc()}")
255
270
  continue
256
271
 
257
- return gpx_files
272
+ return input_files
258
273
 
259
- def _extract_gpx_from_zip(self, zip_path: Path) -> list[Path]:
260
- """Extract GPX files from ZIP archive to temporary location."""
261
- gpx_files = []
262
- temp_dir = Path.cwd() / "temp_gpx_extract"
274
+ def _extract_gpx_kml_from_zip(self, zip_path: Path) -> list[Path]:
275
+ """Extract GPX/KML files from ZIP archive to temporary location."""
276
+ extracted_files = []
277
+ temp_dir = Path.cwd() / "temp_extracted_files"
263
278
 
264
279
  try:
265
280
  temp_dir.mkdir(exist_ok=True)
266
281
 
267
282
  with zipfile.ZipFile(zip_path, "r") as zip_ref:
268
283
  for file_info in zip_ref.infolist():
269
- if file_info.filename.lower().endswith(".gpx"):
284
+ if file_info.filename.lower().endswith((".gpx", ".kml")):
270
285
  extracted_path = temp_dir / Path(file_info.filename).name
271
286
  with open(extracted_path, "wb") as f:
272
287
  f.write(zip_ref.read(file_info.filename))
273
- gpx_files.append(extracted_path)
288
+ extracted_files.append(extracted_path)
274
289
  except Exception as e:
275
290
  self.logger.error(f"Error extracting ZIP file {zip_path}: {e}")
276
291
  self.logger.debug(f"Full traceback:\n{traceback.format_exc()}")
277
292
 
278
- return gpx_files
293
+ return extracted_files
279
294
 
280
295
  def _load_gpx_file(self, gpx_path: Path) -> GPX | None:
281
296
  """Load and parse GPX file."""
@@ -290,35 +305,104 @@ class BaseGPXProcessor:
290
305
  self.logger.debug(f"Full traceback:\n{traceback.format_exc()}")
291
306
  return None
292
307
 
293
- def _save_gpx_file(self, gpx: GPX, output_path: Path, original_file: Path | None = None):
294
- """Save GPX object to file."""
308
+ def _load_kml_file(self, kml_path: Path) -> GPX | None:
309
+ """Load and parse KML file, converting it to a GPX object."""
310
+ if not KML_AVAILABLE:
311
+ self.logger.error("fastkml library is not available to process KML files.")
312
+ return None
313
+
314
+ try:
315
+ with open(kml_path, "r", encoding="utf-8") as f:
316
+ doc = f.read()
317
+
318
+ k = kml.KML()
319
+ k.from_string(doc)
320
+
321
+ gpx = gpxpy.gpx.GPX()
322
+
323
+ # Iterate through KML features and convert to GPX tracks/waypoints
324
+ for feature in k.features():
325
+ if isinstance(feature, Document) or isinstance(feature, Folder):
326
+ for sub_feature in feature.features():
327
+ self._process_kml_feature(sub_feature, gpx)
328
+ else:
329
+ self._process_kml_feature(feature, gpx)
330
+
331
+ self.logger.info(f"Successfully loaded and converted KML file {kml_path} to GPX.")
332
+ return gpx
333
+
334
+ except Exception as e:
335
+ self.logger.error(f"Error loading or converting KML file {kml_path}: {e}")
336
+ self.logger.debug(f"Full traceback:\n{traceback.format_exc()}")
337
+ return None
338
+
339
+ def _process_kml_feature(self, feature, gpx: GPX):
340
+ """Recursively process KML features to extract points and add to GPX."""
341
+ if isinstance(feature, Placemark):
342
+ if feature.geometry is not None:
343
+ if isinstance(feature.geometry, Point):
344
+ waypoint = gpxpy.gpx.GPXWaypoint(
345
+ latitude=feature.geometry.y,
346
+ longitude=feature.geometry.x,
347
+ elevation=feature.geometry.z if feature.geometry.has_z else None,
348
+ name=feature.name,
349
+ description=feature.description,
350
+ )
351
+ gpx.waypoints.append(waypoint)
352
+ elif isinstance(feature.geometry, LineString):
353
+ gpx_track = gpxpy.gpx.GPXTrack()
354
+ gpx_track.name = feature.name
355
+ gpx_segment = gpxpy.gpx.GPXTrackSegment()
356
+ for coord in feature.geometry.coords:
357
+ point = gpxpy.gpx.GPXTrackPoint(
358
+ latitude=coord[1],
359
+ longitude=coord[0],
360
+ elevation=coord[2] if len(coord) > 2 else None,
361
+ )
362
+ gpx_segment.points.append(point)
363
+ if gpx_segment.points:
364
+ gpx_track.segments.append(gpx_segment)
365
+ if gpx_track.segments:
366
+ gpx.tracks.append(gpx_track)
367
+ elif isinstance(feature, Document) or isinstance(feature, Folder):
368
+ for sub_feature in feature.features():
369
+ self._process_kml_feature(sub_feature, gpx)
370
+
371
+ def _save_gpx_file(
372
+ self, gpx: GPX, output_path: Path, original_file: Path | None = None
373
+ ) -> Path:
374
+ """Save GPX object to file and return the path."""
295
375
  try:
296
376
  with open(output_path, "w", encoding="utf-8") as f:
297
377
  f.write(gpx.to_xml())
298
378
  if original_file and original_file.exists():
299
379
  self.logger.info(
300
- f"Original gpx file size: "
301
- f"{Path(original_file).stat().st_size / 1024:.2f} KB"
380
+ f"Original file size: {Path(original_file).stat().st_size / 1024:.2f} KB"
302
381
  )
303
- self.logger.info(
304
- f"Processed gpx file size: {output_path.stat().st_size / 1024:.2f} KB"
305
- )
306
-
382
+ self.logger.info(f"Processed file size: {output_path.stat().st_size / 1024:.2f} KB")
383
+ return output_path
307
384
  except Exception as e:
308
385
  self.logger.error(f"Error saving GPX file {output_path}: {e}")
309
386
  self.logger.debug(f"Full traceback:\n{traceback.format_exc()}")
387
+ return None
310
388
 
311
- def compress_files(self):
312
- """Shrink the size of all given gpx files in self.input."""
389
+ def compress_files(self) -> list[str]:
390
+ """Shrink the size of all given gpx/kml files in self.input."""
391
+ generated_files = []
313
392
  try:
314
- gpx_files = self._get_gpx_files()
393
+ input_files = self._get_input_files()
315
394
  output_folder = self._get_output_folder()
316
395
 
317
- self.logger.info(f"Processing {len(gpx_files)} GPX files...")
396
+ self.logger.info(f"Processing {len(input_files)} GPX/KML files for compression...")
318
397
 
319
- for gpx_file in gpx_files:
398
+ for input_file in input_files:
320
399
  try:
321
- gpx = self._load_gpx_file(gpx_file)
400
+ gpx = None
401
+ if input_file.suffix.lower() == ".gpx":
402
+ gpx = self._load_gpx_file(input_file)
403
+ elif input_file.suffix.lower() == ".kml":
404
+ gpx = self._load_kml_file(input_file)
405
+
322
406
  if gpx is None:
323
407
  continue
324
408
 
@@ -340,14 +424,17 @@ class BaseGPXProcessor:
340
424
  gpx.extensions = None
341
425
 
342
426
  # Save compressed file
343
- output_path = output_folder / f"compressed_{gpx_file.name}"
344
- self._save_gpx_file(gpx, output_path, gpx_file)
345
- self.logger.info(f"Compressed: {gpx_file.name} -> {output_path}")
427
+ output_path = output_folder / f"compressed_{input_file.stem}.gpx"
428
+ saved_path = self._save_gpx_file(gpx, output_path, input_file)
429
+ if saved_path:
430
+ generated_files.append(str(saved_path))
431
+ self.logger.info(f"Compressed: {input_file.name} -> {output_path}")
346
432
 
347
433
  except Exception as e:
348
- self.logger.error(f"Error processing file {gpx_file}: {e}")
434
+ self.logger.error(f"Error processing file {input_file}: {e}")
349
435
  self.logger.debug(f"Full traceback:\n{traceback.format_exc()}")
350
436
  continue
437
+ return generated_files
351
438
 
352
439
  except Exception as e:
353
440
  self.logger.error(f"Error in compress_files: {e}")
@@ -383,41 +470,49 @@ class BaseGPXProcessor:
383
470
  self.logger.warning(f"Error optimizing waypoint: {e}")
384
471
  return waypoint
385
472
 
386
- def merge_files(self):
473
+ def merge_files(self) -> list[str]:
387
474
  """Merge all files of self.input into one gpx file with reduced resolution."""
475
+ generated_files = []
388
476
  try:
389
- gpx_files = self._get_gpx_files()
477
+ input_files = self._get_input_files()
390
478
  output_folder = self._get_output_folder()
391
479
 
392
- if not gpx_files:
393
- self.logger.error("No GPX files found to merge.")
394
- return
480
+ if not input_files:
481
+ self.logger.error("No GPX/KML files found to merge.")
482
+ return []
395
483
 
396
- self.logger.info(f"Merging {len(gpx_files)} GPX files...")
484
+ self.logger.info(f"Merging {len(input_files)} GPX/KML files...")
397
485
 
398
486
  # Create new GPX object
399
487
  merged_gpx = gpxpy.gpx.GPX()
400
488
  merged_gpx.name = "Merged GPX Tracks"
401
- merged_gpx.description = f"Merged from {len(gpx_files)} GPX files."
489
+ merged_gpx.description = f"Merged from {len(input_files)} GPX/KML files."
402
490
 
403
491
  track_counter = 1
404
492
 
405
- for gpx_file in gpx_files:
493
+ for input_file in input_files:
406
494
  try:
407
- gpx = self._load_gpx_file(gpx_file)
495
+ gpx = None
496
+ if input_file.suffix.lower() == ".gpx":
497
+ gpx = self._load_gpx_file(input_file)
498
+ elif input_file.suffix.lower() == ".kml":
499
+ gpx = self._load_kml_file(input_file)
500
+
408
501
  if gpx is None:
409
502
  continue
410
503
 
411
504
  # Add all waypoints from this file
412
505
  for waypoint in gpx.waypoints:
413
506
  waypoint = self._optimize_waypoint(waypoint)
414
- waypoint.name = f"{waypoint.name}_{track_counter}"
507
+ waypoint.name = f"{waypoint.name or 'Waypoint'}_{track_counter}"
415
508
  merged_gpx.waypoints.append(waypoint)
416
509
 
417
510
  # Add all tracks from this file
418
511
  for track in gpx.tracks:
419
512
  new_track = gpxpy.gpx.GPXTrack()
420
- new_track.name = f"{track.name or track_counter}_{gpx_file.stem}"
513
+ new_track.name = (
514
+ f"{track.name or 'Track'}_{input_file.stem}_{track_counter}"
515
+ )
421
516
 
422
517
  for segment in track.segments:
423
518
  optimized_points = self._optimize_track_points(segment.points)
@@ -433,7 +528,9 @@ class BaseGPXProcessor:
433
528
  # Add all routes from this file
434
529
  for route in gpx.routes:
435
530
  new_route = gpxpy.gpx.GPXRoute()
436
- new_route.name = f"{route.name or track_counter}_{gpx_file.stem}"
531
+ new_route.name = (
532
+ f"{route.name or 'Route'}_{input_file.stem}_{track_counter}"
533
+ )
437
534
  new_route.points = self._optimize_track_points(route.points)
438
535
 
439
536
  if new_route.points:
@@ -441,43 +538,52 @@ class BaseGPXProcessor:
441
538
  track_counter += 1
442
539
 
443
540
  except Exception as e:
444
- self.logger.error(f"Error processing file {gpx_file} during merge: {e}")
541
+ self.logger.error(f"Error processing file {input_file} during merge: {e}")
445
542
  self.logger.debug(f"Full traceback:\n{traceback.format_exc()}")
446
543
  continue
447
544
 
448
545
  # Save merged file
449
546
  output_path = output_folder / "merged_tracks.gpx"
450
- self._save_gpx_file(merged_gpx, output_path)
451
- self.logger.info(f"Merged file saved: {output_path}")
547
+ saved_path = self._save_gpx_file(merged_gpx, output_path)
548
+ if saved_path:
549
+ generated_files.append(str(saved_path))
550
+ self.logger.info(f"Merged file saved: {output_path}")
551
+ return generated_files
452
552
 
453
553
  except Exception as e:
454
554
  self.logger.error(f"Error in merge_files: {e}")
455
555
  self.logger.debug(f"Full traceback:\n{traceback.format_exc()}")
456
556
  raise
457
557
 
458
- def extract_pois(self):
558
+ def extract_pois(self) -> list[str]:
459
559
  """Merge every starting point of each track in all files
460
560
  into one gpx file with many pois."""
561
+ generated_files = []
461
562
  try:
462
- gpx_files = self._get_gpx_files()
563
+ input_files = self._get_input_files()
463
564
  output_folder = self._get_output_folder()
464
565
 
465
- if not gpx_files:
466
- self.logger.error("No GPX files found to extract POIs from.")
467
- return
566
+ if not input_files:
567
+ self.logger.error("No GPX/KML files found to extract POIs from.")
568
+ return []
468
569
 
469
- self.logger.info(f"Extracting POIs from {len(gpx_files)} GPX files...")
570
+ self.logger.info(f"Extracting POIs from {len(input_files)} GPX/KML files...")
470
571
 
471
572
  # Create new GPX object for waypoints
472
573
  poi_gpx = gpxpy.gpx.GPX()
473
574
  poi_gpx.name = "Extracted Track Starting Points"
474
- poi_gpx.description = f"Starting points extracted from {len(gpx_files)} GPX files"
575
+ poi_gpx.description = f"Starting points extracted from {len(input_files)} GPX/KML files"
475
576
 
476
577
  poi_counter = 1
477
578
 
478
- for gpx_file in gpx_files:
579
+ for input_file in input_files:
479
580
  try:
480
- gpx = self._load_gpx_file(gpx_file)
581
+ gpx = None
582
+ if input_file.suffix.lower() == ".gpx":
583
+ gpx = self._load_gpx_file(input_file)
584
+ elif input_file.suffix.lower() == ".kml":
585
+ gpx = self._load_kml_file(input_file)
586
+
481
587
  if gpx is None:
482
588
  continue
483
589
 
@@ -496,7 +602,9 @@ class BaseGPXProcessor:
496
602
 
497
603
  track_name = track.name or f"Track_{track_idx + 1}"
498
604
  waypoint.name = f"POI_{poi_counter:03d}"
499
- waypoint.description = f"Start of {track_name} from {gpx_file.name}"
605
+ waypoint.description = (
606
+ f"Start of {track_name} from {input_file.name}"
607
+ )
500
608
  waypoint.type = "Track Start"
501
609
 
502
610
  poi_gpx.waypoints.append(waypoint)
@@ -516,7 +624,7 @@ class BaseGPXProcessor:
516
624
 
517
625
  route_name = route.name or f"Route_{route_idx + 1}"
518
626
  waypoint.name = f"POI_{poi_counter:03d}"
519
- waypoint.description = f"Start of {route_name} from {gpx_file.name}"
627
+ waypoint.description = f"Start of {route_name} from {input_file.name}"
520
628
  waypoint.type = "Route Start"
521
629
 
522
630
  poi_gpx.waypoints.append(waypoint)
@@ -524,24 +632,27 @@ class BaseGPXProcessor:
524
632
 
525
633
  except Exception as e:
526
634
  self.logger.error(
527
- f"Error processing file {gpx_file} during POI extraction: {e}"
635
+ f"Error processing file {input_file} during POI extraction: {e}"
528
636
  )
529
637
  self.logger.debug(f"Full traceback:\n{traceback.format_exc()}")
530
638
  continue
531
639
 
532
640
  # Save POI file
533
641
  output_path = output_folder / "extracted_pois.gpx"
534
- self._save_gpx_file(poi_gpx, output_path)
535
- self.logger.info(
536
- f"POI file saved with {len(poi_gpx.waypoints)} waypoints: {output_path}"
537
- )
642
+ saved_path = self._save_gpx_file(poi_gpx, output_path)
643
+ if saved_path:
644
+ generated_files.append(str(saved_path))
645
+ self.logger.info(
646
+ f"POI file saved with {len(poi_gpx.waypoints)} waypoints: {output_path}"
647
+ )
538
648
 
539
649
  # Clean up temporary files
540
- temp_dir = Path.cwd() / "temp_gpx_extract"
650
+ temp_dir = Path.cwd() / "temp_extracted_files" # Changed folder name
541
651
  if temp_dir.exists():
542
652
  import shutil
543
653
 
544
654
  shutil.rmtree(temp_dir)
655
+ return generated_files
545
656
 
546
657
  except Exception as e:
547
658
  self.logger.error(f"Error in extract_pois: {e}")