diff --git a/folium/features.py b/folium/features.py index 65b6da9475..a11316791f 100644 --- a/folium/features.py +++ b/folium/features.py @@ -1047,19 +1047,22 @@ def __init__( def style_data(self) -> None: """Applies self.style_function to each feature of self.data.""" + for feature in self._get_geometries(): + feature.setdefault("properties", {}).setdefault("style", {}).update( + self.style_function(feature) + ) # noqa + + def _get_geometries(self) -> list: + """Return the selected TopoJSON object as a list of geometries.""" + def recursive_get(data, keys): if len(keys): return recursive_get(data.get(keys[0]), keys[1:]) else: return data - geometries = recursive_get(self.data, self.object_path.split("."))[ - "geometries" - ] # noqa - for feature in geometries: - feature.setdefault("properties", {}).setdefault("style", {}).update( - self.style_function(feature) - ) # noqa + geometry = recursive_get(self.data, self.object_path.split(".")) + return geometry["geometries"] if "geometries" in geometry else [geometry] def render(self, **kwargs): """Renders the HTML representation of the element.""" @@ -1200,12 +1203,7 @@ def render(self, **kwargs): ) self.warn_for_geometry_collections() elif isinstance(self._parent, TopoJson): - obj_name = self._parent.object_path.split(".")[-1] - keys = tuple( - self._parent.data["objects"][obj_name]["geometries"][0][ - "properties" - ].keys() - ) + keys = tuple(self._parent._get_geometries()[0]["properties"].keys()) else: raise TypeError( f"You cannot add a {self._name} to anything other than a " diff --git a/tests/test_features.py b/tests/test_features.py index ecb5398f81..10a5ec6494 100644 --- a/tests/test_features.py +++ b/tests/test_features.py @@ -17,6 +17,75 @@ from folium.utilities import JsCode +@pytest.mark.parametrize("geometry_type", ["Polygon", "MultiPolygon"]) +def test_topojson_non_collection_geometry(geometry_type): + """TopoJson supports objects that are not GeometryCollections.""" + style = {"color": "red"} + topology = { + "type": "Topology", + "objects": { + "shape": { + "type": geometry_type, + "arcs": [[0]] if geometry_type == "Polygon" else [[[0]]], + } + }, + "arcs": [[[0, 0], [1, 0], [0, 1], [-1, -1]]], + "transform": {"scale": [1, 1], "translate": [0, 0]}, + } + + map_ = folium.Map() + folium.TopoJson( + topology, "objects.shape", style_function=lambda feature: style + ).add_to(map_) + + map_.get_root().render() + + assert topology["objects"]["shape"]["properties"]["style"] == style + + +def test_topojson_non_collection_geometry_without_type(): + """TopoJson does not require a type key to apply styles.""" + topology = { + "type": "Topology", + "objects": {"shape": {"arcs": [[0]]}}, + "arcs": [[[0, 0], [1, 0], [0, 1], [-1, -1]]], + "transform": {"scale": [1, 1], "translate": [0, 0]}, + } + + topojson = folium.TopoJson( + topology, + "objects.shape", + style_function=lambda feature: {"color": "red"}, + ) + + topojson.style_data() + + assert topology["objects"]["shape"]["properties"]["style"] == {"color": "red"} + + +@pytest.mark.parametrize("detail_type", [folium.GeoJsonPopup, folium.GeoJsonTooltip]) +def test_topojson_non_collection_geometry_detail(detail_type): + """TopoJson popup and tooltip fields support non-collection geometries.""" + topology = { + "type": "Topology", + "objects": { + "shape": { + "type": "Polygon", + "arcs": [[0]], + "properties": {"name": "A"}, + } + }, + "arcs": [[[0, 0], [1, 0], [0, 1], [-1, -1]]], + "transform": {"scale": [1, 1], "translate": [0, 0]}, + } + + map_ = folium.Map() + topojson = folium.TopoJson(topology, "objects.shape").add_to(map_) + detail_type(fields=["name"]).add_to(topojson) + + assert "name" in map_.get_root().render() + + @pytest.fixture def tmpl(): yield ("""