Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 13 additions & 4 deletions folium/features.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
from folium.elements import JSCSSMixin
from folium.folium import Map
from folium.map import Class, FeatureGroup, Icon, Layer, Marker, Popup, Tooltip
from folium.template import Template
from folium.template import Template, _to_escaped_json
from folium.utilities import (
JsCode,
TypeBoundsReturn,
Expand Down Expand Up @@ -829,7 +829,11 @@ def find_identifier(self) -> str:
if isinstance(feat["properties"].get(key, None), (str, int))
}
if len(unique_values) == len(feats):
return f"feature.properties.{key}"
if key.isidentifier():
return f"feature.properties.{key}"
# Keys like "Time (UTC)" or "@id" need bracket notation
# to be valid Javascript.
return f"feature.properties[{_to_escaped_json(key)}]"
# We add an 'id' field with a unique value to the data.
if self.embed:
for i, feature in enumerate(feats):
Expand Down Expand Up @@ -910,8 +914,13 @@ def _create_mapping(self, func: Callable, switch: str) -> TypeStyleMapping:

def get_feature_id(self, feature: dict) -> Union[str, int]:
"""Return a value identifying the feature."""
fields = self.feature_identifier.split(".")[1:]
value = functools.reduce(operator.getitem, fields, feature)
prefix = "feature.properties["
if self.feature_identifier.startswith(prefix):
key = json.loads(self.feature_identifier[len(prefix) : -1])
value = feature["properties"][key]
else:
fields = self.feature_identifier.split(".")[1:]
value = functools.reduce(operator.getitem, fields, feature)
assert isinstance(value, (str, int))
return value

Expand Down
41 changes: 39 additions & 2 deletions tests/test_features.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,20 +262,28 @@ def _assert_id_got_added(data):
assert geojson.find_identifier() == "feature.id"
assert geojson.data["features"][0]["id"] == "this-is-an-id"

data_with_unique_properties = _create(
{"property_key": "some-value"},
{"property_key": "another-value"},
)
geojson = GeoJson(data_with_unique_properties)
assert geojson.find_identifier() == "feature.properties.property_key"

# Keys that aren't valid Javascript identifiers need bracket notation.
data_with_unique_properties = _create(
{"property-key": "some-value"},
{"property-key": "another-value"},
)
geojson = GeoJson(data_with_unique_properties)
assert geojson.find_identifier() == "feature.properties.property-key"
assert geojson.find_identifier() == 'feature.properties["property-key"]'

data_with_unique_properties = _create(
{"property-key": 42},
{"property-key": 43},
{"property-key": "or a string"},
)
geojson = GeoJson(data_with_unique_properties)
assert geojson.find_identifier() == "feature.properties.property-key"
assert geojson.find_identifier() == 'feature.properties["property-key"]'

# The test cases below have no id field or unique property,
# so an id will be added to the data.
Expand Down Expand Up @@ -344,6 +352,35 @@ def _assert_id_got_added(data):
assert geojson.data["features"][0]["id"] == "0"


@pytest.mark.parametrize("key", ["Time (UTC)", "@id", "osm.id"])
def test_geojson_style_identifier_with_special_characters(key):
"""Assert #1782 is fixed"""
m = Map()
data = {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {key: time},
"geometry": {"type": "Point", "coordinates": [0, 0]},
}
for time in ["10:00", "11:00", "12:00"]
],
}
geojson = GeoJson(
data,
style_function=lambda x: {
"color": "red" if x["properties"][key] == "10:00" else "blue"
},
).add_to(m)
rendered = m.get_root().render()
assert f"switch(feature.properties[{json.dumps(key)}])" in rendered
assert geojson.style_map == {
'{"color": "red"}': ["10:00"],
"default": '{"color": "blue"}',
}


def test_geojson_empty_features_with_styling():
# test we don't fail style function validation when there are no features
m = Map()
Expand Down
Loading