From 411b884323c3b23a8f85f2ac5ef1162565984472 Mon Sep 17 00:00:00 2001 From: Tom Pike Date: Tue, 9 Jul 2024 14:34:49 -0400 Subject: [PATCH] add agent point markers - add point markers options - updates test --- mesa_geo/tile_layers.py | 2 +- mesa_geo/visualization/leaflet_viz.py | 59 ++++++++++++++++++++++--- tests/test_MapModule.py | 63 +++++++++++++++++++-------- 3 files changed, 98 insertions(+), 26 deletions(-) diff --git a/mesa_geo/tile_layers.py b/mesa_geo/tile_layers.py index 6defe516..1ec4716a 100644 --- a/mesa_geo/tile_layers.py +++ b/mesa_geo/tile_layers.py @@ -27,7 +27,7 @@ class RasterWebTile: kind: str = "raster_web_tile" @classmethod - def from_xyzservices(cls, provider=xyzservices.TileProvider) -> RasterWebTile: + def from_xyzservices(cls, provider: xyzservices.TileProvider) -> RasterWebTile: """ Create a RasterWebTile from an xyzservices TileProvider. diff --git a/mesa_geo/visualization/leaflet_viz.py b/mesa_geo/visualization/leaflet_viz.py index c09cb534..d86aa3b1 100644 --- a/mesa_geo/visualization/leaflet_viz.py +++ b/mesa_geo/visualization/leaflet_viz.py @@ -32,7 +32,8 @@ def map(model, map_drawer, zoom, center_default): scroll_wheel_zoom=True, layers=[ ipyleaflet.TileLayer.element(url=base_map["url"]), - ipyleaflet.GeoJSON.element(data=layers["agents"]), + ipyleaflet.GeoJSON.element(data=layers["agents"][0]), + *layers["agents"][1], ], ) @@ -53,7 +54,8 @@ def map_jupyter(model, map_drawer, zoom, center_default): scroll_wheel_zoom=True, layers=[ ipyleaflet.TileLayer.element(url=base_map["url"]), - ipyleaflet.GeoJSON.element(data=layers["agents"]), + ipyleaflet.GeoJSON.element(data=layers["agents"][0]), + *layers["agents"][1], ], ) @@ -67,7 +69,6 @@ class LeafletViz: """ style: dict[str, LeafletOption] | None = None - pointToLayer: dict[str, LeafletOption] | None = None # noqa: N815 popupProperties: dict[str, LeafletOption] | None = None # noqa: N815 @@ -183,26 +184,72 @@ def _render_layers(self, model): ] return layers + def _get_marker(self, location, properties): + """ + takes point objects and transforms them to ipyleaflet marker objects + + allowed marker types are point marker types from ipyleaflet + https://ipyleaflet.readthedocs.io/en/latest/layers/index.html + + default is circle with radius 5 + + Parameters + ---------- + location: iterable + iterable of location in models geometry + + properties : dict + properties passed in through agent portrayal + + + Returns + ------- + ipyleaflet marker element + + """ + + if "marker_type" not in properties: # make circle default marker type + properties["marker_type"] = "Circle" + properties["radius"] = 5 + + marker = properties["marker_type"] + if marker == "Circle": + return ipyleaflet.Circle(location=location, **properties) + elif marker == "CircleMarker": + return ipyleaflet.CircleMarker(location=location, **properties) + elif marker == "Marker" or marker == "Icon" or marker == "AwesomeIcon": + return ipyleaflet.Marker(location=location, **properties) + else: + raise ValueError( + f"Unsupported marker type:{marker}", + ) + def _render_agents(self, model): feature_collection = {"type": "FeatureCollection", "features": []} + point_markers = [] + agent_portrayal = {} for agent in model.space.agents: transformed_geometry = agent.get_transformed_geometry( model.space.transformer ) - agent_portrayal = {} + if self.portrayal_method: properties = self.portrayal_method(agent) agent_portrayal = LeafletViz( popupProperties=properties.pop("description", None) ) if isinstance(agent.geometry, Point): - agent_portrayal.pointToLayer = properties + location = mapping(transformed_geometry) + # for some reason points are reversed + location = (location["coordinates"][1], location["coordinates"][0]) + point_markers.append(self._get_marker(location, properties)) else: agent_portrayal.style = properties agent_portrayal = dataclasses.asdict( agent_portrayal, dict_factory=lambda x: {k: v for (k, v) in x if v is not None}, ) + feature_collection["features"].append( { "type": "Feature", @@ -210,4 +257,4 @@ def _render_agents(self, model): "properties": agent_portrayal, } ) - return feature_collection + return [feature_collection, point_markers] diff --git a/tests/test_MapModule.py b/tests/test_MapModule.py index b487c61f..6700c35c 100644 --- a/tests/test_MapModule.py +++ b/tests/test_MapModule.py @@ -4,6 +4,7 @@ import mesa import numpy as np import xyzservices.providers as xyz +from ipyleaflet import Circle, CircleMarker, Marker from shapely.geometry import LineString, Point, Polygon import mesa_geo as mg @@ -41,28 +42,42 @@ def tearDown(self) -> None: pass def test_render_point_agents(self): + # test length point agents and Circle marker as default map_module = mgv.leaflet_viz.MapModule( - portrayal_method=lambda x: {"color": "Red", "radius": 7}, + portrayal_method=lambda x: {"color": "Green"}, view=None, zoom=3, tiles=xyz.OpenStreetMap.Mapnik, ) self.model.space.add_agents(self.point_agents) - self.assertDictEqual( - map_module.render(self.model).get("agents"), - { - "type": "FeatureCollection", - "features": [ - { - "type": "Feature", - "geometry": {"type": "Point", "coordinates": (1.0, 1.0)}, - "properties": {"pointToLayer": {"color": "Red", "radius": 7}}, - } - ] - * len(self.point_agents), + self.assertEqual(len(map_module.render(self.model).get("agents")[1]), 7) + self.assertIsInstance(map_module.render(self.model).get("agents")[1][3], Circle) + # test CircleMarker option + map_module = mgv.leaflet_viz.MapModule( + portrayal_method=lambda x: { + "marker_type": "CircleMarker", + "color": "Green", }, + view=None, + zoom=3, + tiles=xyz.OpenStreetMap.Mapnik, + ) + self.model.space.add_agents(self.point_agents) + self.assertIsInstance( + map_module.render(self.model).get("agents")[1][3], CircleMarker ) + # test Marker option + map_module = mgv.leaflet_viz.MapModule( + portrayal_method=lambda x: {"marker_type": "Icon", "color": "Green"}, + view=None, + zoom=3, + tiles=xyz.OpenStreetMap.Mapnik, + ) + self.model.space.add_agents(self.point_agents) + self.assertEqual(len(map_module.render(self.model).get("agents")[1]), 7) + self.assertIsInstance(map_module.render(self.model).get("agents")[1][3], Marker) + # test popupProperties for Point map_module = mgv.leaflet_viz.MapModule( portrayal_method=lambda x: { "color": "Red", @@ -75,7 +90,7 @@ def test_render_point_agents(self): ) self.model.space.add_agents(self.point_agents) self.assertDictEqual( - map_module.render(self.model).get("agents"), + map_module.render(self.model).get("agents")[0], { "type": "FeatureCollection", "features": [ @@ -83,7 +98,6 @@ def test_render_point_agents(self): "type": "Feature", "geometry": {"type": "Point", "coordinates": (1.0, 1.0)}, "properties": { - "pointToLayer": {"color": "Red", "radius": 7}, "popupProperties": "popupMsg", }, } @@ -92,6 +106,17 @@ def test_render_point_agents(self): }, ) + # test ValueError if not known markertype + map_module = mgv.leaflet_viz.MapModule( + portrayal_method=lambda x: {"marker_type": "Hexagon", "color": "Green"}, + view=None, + zoom=3, + tiles=xyz.OpenStreetMap.Mapnik, + ) + self.model.space.add_agents(self.point_agents) + with self.assertRaises(ValueError): + map_module.render(self.model) + def test_render_line_agents(self): map_module = mgv.leaflet_viz.MapModule( portrayal_method=lambda x: {"color": "#3388ff", "weight": 7}, @@ -101,7 +126,7 @@ def test_render_line_agents(self): ) self.model.space.add_agents(self.line_agents) self.assertDictEqual( - map_module.render(self.model).get("agents"), + map_module.render(self.model).get("agents")[0], { "type": "FeatureCollection", "features": [ @@ -130,7 +155,7 @@ def test_render_line_agents(self): ) self.model.space.add_agents(self.line_agents) self.assertDictEqual( - map_module.render(self.model).get("agents"), + map_module.render(self.model).get("agents")[0], { "type": "FeatureCollection", "features": [ @@ -161,7 +186,7 @@ def test_render_polygon_agents(self): ) self.model.space.add_agents(self.polygon_agents) self.assertDictEqual( - map_module.render(self.model).get("agents"), + map_module.render(self.model).get("agents")[0], { "type": "FeatureCollection", "features": [ @@ -194,7 +219,7 @@ def test_render_polygon_agents(self): ) self.model.space.add_agents(self.polygon_agents) self.assertDictEqual( - map_module.render(self.model).get("agents"), + map_module.render(self.model).get("agents")[0], { "type": "FeatureCollection", "features": [