diff --git a/ideas.txt b/ideas.txt index 8bb5b80..e73fe2b 100644 --- a/ideas.txt +++ b/ideas.txt @@ -1,4 +1,3 @@ -Django backend, vue.js frontend Tagging support (tag roads, trails, etc) Sharing (share individual items or select items or tags to include) Organization by folder diff --git a/src/geo-backend/geo_lib/daemon/workers/workers_lib/importer/kml.py b/src/geo-backend/geo_lib/daemon/workers/workers_lib/importer/kml.py index 5281137..bdc840a 100644 --- a/src/geo-backend/geo_lib/daemon/workers/workers_lib/importer/kml.py +++ b/src/geo-backend/geo_lib/daemon/workers/workers_lib/importer/kml.py @@ -47,7 +47,7 @@ def process_feature(converted_kml) -> Tuple[list, ImportLog]: for i, timestamp_str in enumerate(feature['properties']['times']): timestamp = int(parse(timestamp_str).timestamp() * 1000) feature['geometry']['coordinates'][i].append(timestamp) - feature['properties'] = GeojsonRawProperty(**feature['properties']).dict() + feature['properties'] = GeojsonRawProperty(**feature['properties']).model_dump() features.append(feature) else: # Log the error @@ -78,5 +78,4 @@ def load_geojson_type(data: dict) -> dict: 'coordinates': item.pop('coordinates'), } item['type'] = 'Feature' - item['properties']['title'] = item['properties'].pop('name') return geojson_dict diff --git a/src/geo-backend/geo_lib/types/feature.py b/src/geo-backend/geo_lib/types/feature.py index 4a18e61..cc7eb3e 100644 --- a/src/geo-backend/geo_lib/types/feature.py +++ b/src/geo-backend/geo_lib/types/feature.py @@ -1,12 +1,11 @@ -from datetime import datetime +import json from enum import Enum -from typing import Optional, List, Union, Tuple +from typing import List, Tuple, Optional +from typing import Union -import pytz -from pydantic import Field, BaseModel +from pydantic import BaseModel, Field from geo_lib.daemon.workers.workers_lib.importer.logging import ImportLog -from geo_lib.geo_backend import SOFTWARE_NAME, SOFTWARE_VERSION class GeoFeatureType(str, Enum): @@ -15,42 +14,54 @@ class GeoFeatureType(str, Enum): POLYGON = 'Polygon' -class GeoFeatureProperties(BaseModel): - tags: List[str] = Field(default_factory=list) - created: datetime = datetime.utcnow().replace(tzinfo=pytz.utc) - software: str = Field(SOFTWARE_NAME, frozen=True) - software_version: str = Field(SOFTWARE_VERSION, frozen=True) +class Rendering(BaseModel): + stroke_width: int = Field(2, alias='strokeWidth') + stroke_color: Tuple[int, int, int, float] = Field((255, 0, 0, 1.0), alias='strokeColor') + fill_color: Optional[Tuple[int, int, int, float]] = Field((255, 0, 0, 0.2), alias='fillColor') -class GeoFeature(BaseModel): - """ - A thing that's shown on the map. - Can be a point, linestring, or polygon. - """ +class Properties(BaseModel): name: str - id: int # From the database - type: GeoFeatureType + id: Optional[int] = -1 description: Optional[str] = None - geometry: List - properties: GeoFeatureProperties = Field(default_factory=GeoFeatureProperties) + tags: Optional[List[str]] = Field(default_factory=list) + rendering: Optional[Rendering] = Field(default_factory=Rendering) -class GeoPoint(GeoFeature): +class PointFeatureGeometry(BaseModel): type: GeoFeatureType = GeoFeatureType.POINT - geometry: List[float] + coordinates: Union[Tuple[float, float], Tuple[float, float, float]] -class GeoLineString(GeoFeature): +class LineStringGeometry(BaseModel): type: GeoFeatureType = GeoFeatureType.LINESTRING - geometry: List[List[float]] + coordinates: List[Union[Tuple[float, float], Tuple[float, float, float], Tuple[float, float, float, int]]] -class GeoPolygon(GeoFeature): +class PolygonGeometry(BaseModel): type: GeoFeatureType = GeoFeatureType.POLYGON - geometry: List[List[List[float]]] + coordinates: List[List[Union[Tuple[float, float], Tuple[float, float, float]]]] -GeoFeatureSupported = Union[GeoPoint, GeoLineString, GeoPolygon] +class Feature(BaseModel): + type: str = 'Feature' + geometry: Union[PointFeatureGeometry, LineStringGeometry, PolygonGeometry] + properties: Properties + + +class PointFeature(Feature): + geometry: PointFeatureGeometry + + +class LineStringFeature(Feature): + geometry: LineStringGeometry + + +class PolygonFeature(Feature): + geometry: PolygonGeometry + + +GeoFeatureSupported = Union[PointFeature, LineStringFeature, PolygonFeature] def geojson_to_geofeature(geojson: dict) -> Tuple[List[GeoFeatureSupported], ImportLog]: @@ -59,20 +70,32 @@ def geojson_to_geofeature(geojson: dict) -> Tuple[List[GeoFeatureSupported], Imp for item in geojson['features']: match item['geometry']['type'].lower(): case 'point': - c = GeoPoint + c = PointFeature case 'linestring': - c = GeoLineString + c = LineStringFeature case 'polygon': - c = GeoPolygon + c = PolygonFeature case _: import_log.add(f'Feature named "{item["properties"]["title"]}" had unsupported type "{item["geometry"]["type"]}".') continue - result.append(c( - name=item['properties']['title'], - id=-1, # This will be updated after it's added to the main data store. - description=item['properties']['description'], - tags=item['properties']['feature_tags'], - geometry=item['geometry']['coordinates'] - )) + + f = c(**item) + if isinstance(f, (PointFeature, LineStringFeature)): + del f.properties.rendering.fill_color + + # TODO: do this shit + f.properties.id = -1 # This will be updated after it's added to the main data store. + + result.append(f) return result, import_log + + +def geofeature_to_geojson(feature: Union[GeoFeatureSupported, list]) -> str: + if isinstance(feature, list): + return json.dumps({ + 'type': 'FeatureCollection', + 'features': [json.loads(x.model_dump_json(by_alias=True)) for x in feature] + }) + else: + return feature.model_dump_json(by_alias=True) diff --git a/src/geo-backend/geo_lib/types/geojson.py b/src/geo-backend/geo_lib/types/geojson.py index eda3581..40b7dab 100644 --- a/src/geo-backend/geo_lib/types/geojson.py +++ b/src/geo-backend/geo_lib/types/geojson.py @@ -7,4 +7,4 @@ class GeojsonRawProperty(BaseModel): # A class to whitelist these properties. name: str description: Optional[str] = None - feature_tags: List[str] = Field(default_factory=list) + tags: List[str] = Field(default_factory=list, alias='feature_tags') # kml2geojson calls this field `feature_tags` diff --git a/src/geo-backend/tests/kml-to-geojson.py b/src/geo-backend/tests/kml-to-geojson.py new file mode 100644 index 0000000..f2dbe4a --- /dev/null +++ b/src/geo-backend/tests/kml-to-geojson.py @@ -0,0 +1,19 @@ +import argparse +import sys +from pathlib import Path + +sys.path.append(str(list(Path(__file__).parents)[1])) +from geo_lib.daemon.workers.workers_lib.importer.kml import kml_to_geojson +from geo_lib.types.feature import geojson_to_geofeature, geofeature_to_geojson + +parser = argparse.ArgumentParser() +parser.add_argument('kml_path') +args = parser.parse_args() + +raw_kml = Path(args.kml_path).expanduser().absolute().resolve().read_text() + +geojson_data, kml_conv_messages = kml_to_geojson(raw_kml) + +geofetures, typing_messages = geojson_to_geofeature(geojson_data) + +print(geofeature_to_geojson(geofetures)) diff --git a/src/geo-backend/todo.txt b/src/geo-backend/todo.txt index 7c068d7..4c515fd 100644 --- a/src/geo-backend/todo.txt +++ b/src/geo-backend/todo.txt @@ -1,7 +1 @@ -1. Style main import page -2. Fix created field reset on edit imported -3. Implement refresh on edit imported -4. Style messages/log on edit imported -5. Implement upload working animation on edit imported - -- For tracks, set the created date to the timestamp of the first point in the track \ No newline at end of file +- For tracks, set the created date to the timestamp of the first point in the track diff --git a/src/geo-frontend/src/components/import/geojson-openlayers.html b/src/geo-frontend/src/components/import/geojson-openlayers.html new file mode 100644 index 0000000..403d199 --- /dev/null +++ b/src/geo-frontend/src/components/import/geojson-openlayers.html @@ -0,0 +1,85 @@ + + + + OpenLayers LineString Example + + + + +
+ + + +