Skip to content

Commit

Permalink
Add simple features source
Browse files Browse the repository at this point in the history
  • Loading branch information
Stefan Kuethe committed Jul 20, 2024
1 parent 103b500 commit b1b5f97
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 22 deletions.
24 changes: 24 additions & 0 deletions _experimental/add_multiple_layers_at_once.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from maplibre import Layer, LayerType, Map, MapOptions
from maplibre.sources import SimpleFeatures

path = "https://d2ad6b4ur7yvpq.cloudfront.net/naturalearth-3.3.0/ne_110m_admin_1_states_provinces_shp.geojson"

simple_features = SimpleFeatures(path, source_id="states")
# sources = {"states": simple_features.to_source()}
sources = simple_features.to_sources_dict()
layers = [
Layer(
type=LayerType.FILL,
paint={"fill-color": "green"},
source=simple_features.source_id,
),
Layer(
type=LayerType.LINE,
paint={"line-color": "blue"},
source=simple_features.source_id,
),
]

m = Map(MapOptions(bounds=simple_features.bounds))
m.add_layers(layers, sources)
m.save("/tmp/py-maplibre-express.html")
10 changes: 8 additions & 2 deletions maplibre/map.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
from __future__ import annotations

import json
import os.path
import webbrowser
from typing import Union

from jinja2 import Template
from pydantic import ConfigDict, Field, field_validator

from ._templates import html_template, js_template
from ._utils import BaseModel, get_output_dir, get_temp_filename, read_internal_file
from ._utils import BaseModel, get_temp_filename, read_internal_file
from .basemaps import Carto, construct_carto_basemap_url
from .controls import Control, ControlPosition, Marker
from .layer import Layer
Expand Down Expand Up @@ -160,6 +159,13 @@ def add_layer(self, layer: [Layer | dict], before_id: str = None) -> None:

self.add_call("addLayer", layer, before_id)

def add_layers(self, layers: list, sources: dict = None):
for source_id, source in sources.items():
self.add_source(source_id, source)

for layer in layers:
self.add_layer(layer)

def add_marker(self, marker: Marker) -> None:
"""Add a marker to the map
Expand Down
84 changes: 64 additions & 20 deletions maplibre/sources.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,19 @@

from enum import Enum
from typing import Optional, Union
from uuid import uuid4

from pydantic import ConfigDict, Field, computed_field
from pydantic import Field, computed_field, field_validator

from ._utils import BaseModel
from .utils import geopandas_to_geojson

try:
from geopandas import GeoDataFrame, read_file
except ImportError:
GeoDataFrame, read_file = None, None

CRS = "EPSG:4326"


class SourceType(Enum):
Expand All @@ -21,12 +30,6 @@ class SourceType(Enum):

class Source(BaseModel):
pass
# model_config = ConfigDict(validate_assignment=True, extra="forbid")

"""
def model_dump(self):
return super().model_dump(exclude_none=True, by_alias=True)
"""


class GeoJSONSource(Source):
Expand All @@ -40,19 +43,33 @@ class GeoJSONSource(Source):
"""

data: Union[str, dict]
attribution: str = None
buffer: int = None
cluster: bool = None
cluster_max_zoom: int = Field(None, serialization_alias="clusterMaxZoom")
cluster_min_points: int = Field(None, serialization_alias="clusterMinPoints")
cluster_properties: dict = Field(None, serialization_alias="clusterProperties")
cluster_radius: int = Field(None, serialization_alias="clusterRadius")
filter: list = None
generate_id: bool = Field(None, serialization_alias="generateId")
line_metrics: bool = Field(None, serialization_alias="lineMetrics")
maxzoom: int = None
promote_id: Union[str, dict] = Field(None, serialization_alias="promoteId")
tolerance: float = None
attribution: Optional[str] = None
buffer: Optional[int] = None
cluster: Optional[bool] = None
cluster_max_zoom: Optional[int] = Field(None, serialization_alias="clusterMaxZoom")
cluster_min_points: Optional[int] = Field(
None, serialization_alias="clusterMinPoints"
)
cluster_properties: Optional[dict] = Field(
None, serialization_alias="clusterProperties"
)
cluster_radius: Optional[int] = Field(None, serialization_alias="clusterRadius")
filter: Optional[list] = None
generate_id: Optional[bool] = Field(None, serialization_alias="generateId")
line_metrics: Optional[bool] = Field(None, serialization_alias="lineMetrics")
min_zoom: Optional[int] = Field(None, serialization_alias="minzoom")
max_zoom: Optional[int] = Field(None, serialization_alias="maxzoom")
promote_id: Union[str, dict, None] = Field(None, serialization_alias="promoteId")
tolerance: Optional[float] = None

"""
@field_validator("data")
def validate_data(cls, v):
if isinstance(v, GeoDataFrame):
return geopandas_to_geojson(v)
return v
"""

@computed_field
@property
Expand Down Expand Up @@ -125,3 +142,30 @@ class VectorTileSource(Source):
@property
def type(self) -> str:
return SourceType.VECTOR.value


class SimpleFeatures(object):
def __init__(self, data: GeoDataFrame | str, source_id: str = None):
if isinstance(data, str):
data = read_file(data)

if str(data.crs) != CRS:
data = data.to_crs(CRS)

self._data = data
self._source_id = source_id or str(uuid4())

@property
def bounds(self) -> tuple:
return self._data.total_bounds

@property
def source_id(self) -> str:
return self._source_id

def to_source(self, **kwargs) -> GeoJSONSource:
kwargs["data"] = geopandas_to_geojson(self._data)
return GeoJSONSource(**kwargs)

def to_sources_dict(self, **kwargs) -> dict:
return {self.source_id: self.to_source(**kwargs)}

0 comments on commit b1b5f97

Please sign in to comment.