python-template-project 0.1.20__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.
- {python_template_project-0.1.20 → python_template_project-0.1.21}/HISTORY.md +8 -0
- {python_template_project-0.1.20 → python_template_project-0.1.21}/Makefile +2 -2
- {python_template_project-0.1.20/src/python_template_project.egg-info → python_template_project-0.1.21}/PKG-INFO +3 -1
- python_template_project-0.1.21/config.yaml +22 -0
- {python_template_project-0.1.20 → python_template_project-0.1.21}/pyproject.toml +2 -0
- {python_template_project-0.1.20 → python_template_project-0.1.21}/src/python_template_project/_version.py +2 -2
- {python_template_project-0.1.20 → python_template_project-0.1.21}/src/python_template_project/core/base.py +182 -71
- {python_template_project-0.1.20 → python_template_project-0.1.21}/src/python_template_project/gui/gui.py +226 -47
- {python_template_project-0.1.20 → python_template_project-0.1.21/src/python_template_project.egg-info}/PKG-INFO +3 -1
- {python_template_project-0.1.20 → python_template_project-0.1.21}/src/python_template_project.egg-info/requires.txt +2 -0
- {python_template_project-0.1.20 → python_template_project-0.1.21}/tests/test_gpx.py +6 -6
- {python_template_project-0.1.20 → python_template_project-0.1.21}/uv.lock +148 -1
- python_template_project-0.1.20/config.yaml +0 -96
- {python_template_project-0.1.20 → python_template_project-0.1.21}/.github/FUNDING.yml +0 -0
- {python_template_project-0.1.20 → python_template_project-0.1.21}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {python_template_project-0.1.20 → python_template_project-0.1.21}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {python_template_project-0.1.20 → python_template_project-0.1.21}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {python_template_project-0.1.20 → python_template_project-0.1.21}/.github/actions/setup-environment/action.yml +0 -0
- {python_template_project-0.1.20 → python_template_project-0.1.21}/.github/dependabot.yml +0 -0
- {python_template_project-0.1.20 → python_template_project-0.1.21}/.github/init.sh +0 -0
- {python_template_project-0.1.20 → python_template_project-0.1.21}/.github/release_message.sh +0 -0
- {python_template_project-0.1.20 → python_template_project-0.1.21}/.github/rename_project.sh +0 -0
- {python_template_project-0.1.20 → python_template_project-0.1.21}/.github/template.yml +0 -0
- {python_template_project-0.1.20 → python_template_project-0.1.21}/.github/workflows/build-macos.yml +0 -0
- {python_template_project-0.1.20 → python_template_project-0.1.21}/.github/workflows/build.yml +0 -0
- {python_template_project-0.1.20 → python_template_project-0.1.21}/.github/workflows/main.yml +0 -0
- {python_template_project-0.1.20 → python_template_project-0.1.21}/.github/workflows/release.yml +0 -0
- {python_template_project-0.1.20 → python_template_project-0.1.21}/.github/workflows/rename_project.yml +0 -0
- {python_template_project-0.1.20 → python_template_project-0.1.21}/.github/workflows/update_readme.yml +0 -0
- {python_template_project-0.1.20 → python_template_project-0.1.21}/.gitignore +0 -0
- {python_template_project-0.1.20 → python_template_project-0.1.21}/.idea/runConfigurations/config_generate.xml +0 -0
- {python_template_project-0.1.20 → python_template_project-0.1.21}/.idea/runConfigurations/module_cli.xml +0 -0
- {python_template_project-0.1.20 → python_template_project-0.1.21}/.idea/runConfigurations/module_gui.xml +0 -0
- {python_template_project-0.1.20 → python_template_project-0.1.21}/.pre-commit-config.yaml +0 -0
- {python_template_project-0.1.20 → python_template_project-0.1.21}/.readthedocs.yaml +0 -0
- {python_template_project-0.1.20 → python_template_project-0.1.21}/LICENSE +0 -0
- {python_template_project-0.1.20 → python_template_project-0.1.21}/README.md +0 -0
- {python_template_project-0.1.20 → python_template_project-0.1.21}/docs/.nav.yml +0 -0
- {python_template_project-0.1.20 → python_template_project-0.1.21}/docs/_static/img/favicon.png +0 -0
- {python_template_project-0.1.20 → python_template_project-0.1.21}/docs/_static/img/logo.png +0 -0
- {python_template_project-0.1.20 → python_template_project-0.1.21}/docs/css/custom.css +0 -0
- {python_template_project-0.1.20 → python_template_project-0.1.21}/docs/develop/01_getting_started_dev.md +0 -0
- {python_template_project-0.1.20 → python_template_project-0.1.21}/docs/develop/contributing.md +0 -0
- {python_template_project-0.1.20 → python_template_project-0.1.21}/docs/develop/make_windows.md +0 -0
- {python_template_project-0.1.20 → python_template_project-0.1.21}/docs/develop/naming_convention.md +0 -0
- {python_template_project-0.1.20 → python_template_project-0.1.21}/docs/develop/pypi_release.md +0 -0
- {python_template_project-0.1.20 → python_template_project-0.1.21}/docs/getting-started/install.md +0 -0
- {python_template_project-0.1.20 → python_template_project-0.1.21}/docs/getting-started/virtual-environment.md +0 -0
- {python_template_project-0.1.20 → python_template_project-0.1.21}/docs/index.md +0 -0
- {python_template_project-0.1.20 → python_template_project-0.1.21}/docs/usage/cli.md +0 -0
- {python_template_project-0.1.20 → python_template_project-0.1.21}/docs/usage/config.md +0 -0
- {python_template_project-0.1.20 → python_template_project-0.1.21}/examples/kuhkopfsteig.gpx +0 -0
- {python_template_project-0.1.20 → python_template_project-0.1.21}/examples/rother_lilienstein.gpx +0 -0
- {python_template_project-0.1.20 → python_template_project-0.1.21}/examples/teneriffa.gpx +0 -0
- {python_template_project-0.1.20 → python_template_project-0.1.21}/mkdocs.yml +0 -0
- {python_template_project-0.1.20 → python_template_project-0.1.21}/scripts/show_filelist.ps1 +0 -0
- {python_template_project-0.1.20 → python_template_project-0.1.21}/scripts/show_tree.ps1 +0 -0
- {python_template_project-0.1.20 → python_template_project-0.1.21}/scripts/update_readme.py +0 -0
- {python_template_project-0.1.20 → python_template_project-0.1.21}/setup.cfg +0 -0
- {python_template_project-0.1.20 → python_template_project-0.1.21}/src/__init__.py +0 -0
- {python_template_project-0.1.20 → python_template_project-0.1.21}/src/firewall_handler.py +0 -0
- {python_template_project-0.1.20 → python_template_project-0.1.21}/src/main.py +0 -0
- {python_template_project-0.1.20 → python_template_project-0.1.21}/src/python_template_project/__init__.py +0 -0
- {python_template_project-0.1.20 → python_template_project-0.1.21}/src/python_template_project/__main__.py +0 -0
- {python_template_project-0.1.20 → python_template_project-0.1.21}/src/python_template_project/cli/__init__.py +0 -0
- {python_template_project-0.1.20 → python_template_project-0.1.21}/src/python_template_project/cli/__main__.py +0 -0
- {python_template_project-0.1.20 → python_template_project-0.1.21}/src/python_template_project/cli/cli.py +0 -0
- {python_template_project-0.1.20 → python_template_project-0.1.21}/src/python_template_project/config/__init__.py +0 -0
- {python_template_project-0.1.20 → python_template_project-0.1.21}/src/python_template_project/config/config.py +0 -0
- {python_template_project-0.1.20 → python_template_project-0.1.21}/src/python_template_project/core/__init__.py +0 -0
- {python_template_project-0.1.20 → python_template_project-0.1.21}/src/python_template_project/core/logging.py +0 -0
- {python_template_project-0.1.20 → python_template_project-0.1.21}/src/python_template_project/gui/__init__.py +0 -0
- {python_template_project-0.1.20 → python_template_project-0.1.21}/src/python_template_project/gui/__main__.py +0 -0
- {python_template_project-0.1.20 → python_template_project-0.1.21}/src/python_template_project.egg-info/SOURCES.txt +0 -0
- {python_template_project-0.1.20 → python_template_project-0.1.21}/src/python_template_project.egg-info/dependency_links.txt +0 -0
- {python_template_project-0.1.20 → python_template_project-0.1.21}/src/python_template_project.egg-info/entry_points.txt +0 -0
- {python_template_project-0.1.20 → python_template_project-0.1.21}/src/python_template_project.egg-info/top_level.txt +0 -0
- {python_template_project-0.1.20 → python_template_project-0.1.21}/template.yml.url +0 -0
- {python_template_project-0.1.20 → python_template_project-0.1.21}/tests/__init__.py +0 -0
- {python_template_project-0.1.20 → python_template_project-0.1.21}/tests/test_generic_cli.py +0 -0
|
@@ -4,6 +4,14 @@ 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
|
+
-------------------
|
|
7
15
|
- Feat #31: move project specific namings into makefile so that it can
|
|
8
16
|
be renamed. [Paul Magister]
|
|
9
17
|
- Feat #31: try this renaming automation. [Paul Magister]
|
|
@@ -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/
|
|
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/
|
|
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.
|
|
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
|
|
@@ -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(
|
|
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
|
|
232
|
-
"""Get all GPX files from input (file, folder, or zip)."""
|
|
233
|
-
|
|
234
|
-
for
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
272
|
+
return input_files
|
|
258
273
|
|
|
259
|
-
def
|
|
260
|
-
"""Extract GPX files from ZIP archive to temporary location."""
|
|
261
|
-
|
|
262
|
-
temp_dir = Path.cwd() / "
|
|
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
|
-
|
|
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
|
|
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
|
|
294
|
-
"""
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
393
|
+
input_files = self._get_input_files()
|
|
315
394
|
output_folder = self._get_output_folder()
|
|
316
395
|
|
|
317
|
-
self.logger.info(f"Processing {len(
|
|
396
|
+
self.logger.info(f"Processing {len(input_files)} GPX/KML files for compression...")
|
|
318
397
|
|
|
319
|
-
for
|
|
398
|
+
for input_file in input_files:
|
|
320
399
|
try:
|
|
321
|
-
gpx =
|
|
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_{
|
|
344
|
-
self._save_gpx_file(gpx, output_path,
|
|
345
|
-
|
|
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 {
|
|
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
|
-
|
|
477
|
+
input_files = self._get_input_files()
|
|
390
478
|
output_folder = self._get_output_folder()
|
|
391
479
|
|
|
392
|
-
if not
|
|
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(
|
|
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(
|
|
489
|
+
merged_gpx.description = f"Merged from {len(input_files)} GPX/KML files."
|
|
402
490
|
|
|
403
491
|
track_counter = 1
|
|
404
492
|
|
|
405
|
-
for
|
|
493
|
+
for input_file in input_files:
|
|
406
494
|
try:
|
|
407
|
-
gpx =
|
|
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 =
|
|
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 =
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
563
|
+
input_files = self._get_input_files()
|
|
463
564
|
output_folder = self._get_output_folder()
|
|
464
565
|
|
|
465
|
-
if not
|
|
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(
|
|
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(
|
|
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
|
|
579
|
+
for input_file in input_files:
|
|
479
580
|
try:
|
|
480
|
-
gpx =
|
|
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 =
|
|
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 {
|
|
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 {
|
|
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
|
-
|
|
536
|
-
|
|
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() / "
|
|
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}")
|