Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Automatically assign unique_id's #248

Merged
merged 6 commits into from
Sep 10, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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
45 changes: 10 additions & 35 deletions mesa_geo/geoagent.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

import copy
import json
import warnings

import geopandas as gpd
import numpy as np
Expand All @@ -25,17 +24,16 @@ class GeoAgent(Agent, GeoBase):
Base class for a geo model agent.
"""

def __init__(self, unique_id, model, geometry, crs):
def __init__(self, model, geometry, crs):
"""
Create a new agent.

:param unique_id: Unique ID for the agent.
:param model: The model the agent is in.
:param geometry: A Shapely object representing the geometry of the agent.
:param crs: The coordinate reference system of the geometry.
"""

Agent.__init__(self, unique_id, model)
Agent.__init__(self, model)
GeoBase.__init__(self, crs=crs)
self.geometry = geometry

Expand Down Expand Up @@ -105,15 +103,8 @@ def __init__(self, agent_class, model=None, crs=None, agent_kwargs=None):
and the crs from the file/GeoDataFrame/GeoJSON will be used.
Otherwise, geometries are converted into this crs automatically.
:param agent_kwargs: Keyword arguments to pass to the agent_class.
Must NOT include unique_id.
"""

if agent_kwargs and "unique_id" in agent_kwargs:
agent_kwargs.remove("unique_id")
warnings.warn(
"Unique_id should not be in the agent_kwargs", UserWarning, stacklevel=2
)

self.agent_class = agent_class
self.model = model
self.crs = crs
Expand All @@ -135,12 +126,11 @@ def crs(self, crs):

self._crs = pyproj.CRS.from_user_input(crs) if crs else None

def create_agent(self, geometry, unique_id):
def create_agent(self, geometry):
"""
Create a single agent from a geometry and a unique_id. Shape must be a valid Shapely object.
Create a single agent from a geometry. Shape must be a valid Shapely object.

:param geometry: The geometry of the agent.
:param unique_id: The unique_id of the agent.
:return: The created agent.
:rtype: self.agent_class
"""
Expand All @@ -157,7 +147,6 @@ def create_agent(self, geometry, unique_id):
raise ValueError("Model must be a valid Mesa model object")

new_agent = self.agent_class(
unique_id=unique_id,
model=self.model,
geometry=geometry,
crs=self.crs,
Expand All @@ -166,20 +155,15 @@ def create_agent(self, geometry, unique_id):

return new_agent

def from_GeoDataFrame(self, gdf, unique_id="index", set_attributes=True):
def from_GeoDataFrame(self, gdf, set_attributes=True):
"""
Create a list of agents from a GeoDataFrame.

:param gdf: The GeoDataFrame to create agents from.
:param unique_id: The column name of the data to use as the agents unique_id.
If "index", the index of the GeoDataFrame is used. Default to "index".
:param set_attributes: Set agent attributes from GeoDataFrame columns.
Default True.
"""

if unique_id != "index":
gdf = gdf.set_index(unique_id)

if self.crs:
if gdf.crs:
gdf.to_crs(self.crs, inplace=True)
Expand All @@ -195,9 +179,9 @@ def from_GeoDataFrame(self, gdf, unique_id="index", set_attributes=True):
)

agents = []
for index, row in gdf.iterrows():
for _index, row in gdf.iterrows():
geometry = row[gdf.geometry.name]
new_agent = self.create_agent(geometry=geometry, unique_id=index)
new_agent = self.create_agent(geometry=geometry)

if set_attributes:
for col in row.index:
Expand All @@ -207,34 +191,27 @@ def from_GeoDataFrame(self, gdf, unique_id="index", set_attributes=True):

return agents

def from_file(self, filename, unique_id="index", set_attributes=True):
def from_file(self, filename, set_attributes=True):
"""
Create agents from vector data files (e.g. Shapefiles).

:param filename: The vector data file to create agents from.
:param unique_id: The column name of the data to use as the agents unique_id.
If "index", the index of the GeoDataFrame is used. Default to "index".
:param set_attributes: Set agent attributes from GeoDataFrame columns. Default True.
"""

gdf = gpd.read_file(filename)
agents = self.from_GeoDataFrame(
gdf, unique_id=unique_id, set_attributes=set_attributes
)
agents = self.from_GeoDataFrame(gdf, set_attributes=set_attributes)
return agents

def from_GeoJSON(
self,
GeoJSON, # noqa: N803
unique_id="index",
set_attributes=True,
):
"""
Create agents from a GeoJSON object or string. CRS is set to epsg:4326.

:param GeoJSON: The GeoJSON object or string to create agents from.
:param unique_id: The column name of the data to use as the agents unique_id.
If "index", the index of the GeoDataFrame is used. Default to "index".
:param set_attributes: Set agent attributes from GeoDataFrame columns. Default True.
"""

Expand All @@ -243,7 +220,5 @@ def from_GeoJSON(
gdf = gpd.GeoDataFrame.from_features(gj)
# epsg:4326 is the CRS for all GeoJSON: https://datatracker.ietf.org/doc/html/rfc7946#section-4
gdf.crs = "epsg:4326"
agents = self.from_GeoDataFrame(
gdf, unique_id=unique_id, set_attributes=set_attributes
)
agents = self.from_GeoDataFrame(gdf, set_attributes=set_attributes)
return agents
2 changes: 1 addition & 1 deletion mesa_geo/geospace.py
Original file line number Diff line number Diff line change
Expand Up @@ -457,7 +457,7 @@ def get_agents_as_GeoDataFrame(self, agent_cls=GeoAgent) -> gpd.GeoDataFrame:
if attr not in {"model", "pos", "_crs"}
}
agents_list.append(agent_dict)
agents_gdf = gpd.GeoDataFrame.from_records(agents_list, index="unique_id")
agents_gdf = gpd.GeoDataFrame.from_records(agents_list)
# workaround for geometry column not being set in `from_records`
# see https://github.com/geopandas/geopandas/issues/3152
# may be removed when the issue is resolved
Expand Down
16 changes: 6 additions & 10 deletions tests/test_AgentCreator.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,19 +54,15 @@ def tearDown(self) -> None:
pass

def test_create_agent_with_crs(self):
agent = self.agent_creator_with_crs.create_agent(
geometry=Point(1, 1), unique_id=0
)
agent = self.agent_creator_with_crs.create_agent(geometry=Point(1, 1))
self.assertIsInstance(agent, mg.GeoAgent)
self.assertEqual(agent.geometry, Point(1, 1))
self.assertEqual(agent.model, self.model)
self.assertEqual(agent.crs, self.agent_creator_with_crs.crs)

def test_create_agent_without_crs(self):
with self.assertRaises(TypeError):
self.agent_creator_without_crs.create_agent(
geometry=Point(1, 1), unique_id=0
)
self.agent_creator_without_crs.create_agent(geometry=Point(1, 1))

def test_from_GeoDataFrame_with_default_geometry_name(self):
gdf = gpd.GeoDataFrame(
Expand Down Expand Up @@ -106,12 +102,12 @@ def test_from_GeoJSON_and_agent_creator_without_crs(self):

self.assertEqual(len(agents), 2)

self.assertEqual(agents[0].unique_id, 0)
self.assertEqual(agents[0].unique_id, 1)
self.assertEqual(agents[0].col1, "name1")
self.assertEqual(agents[0].geometry, Point(1.0, 2.0))
self.assertEqual(agents[0].crs, "epsg:4326")

self.assertEqual(agents[1].unique_id, 1)
self.assertEqual(agents[1].unique_id, 2)
self.assertEqual(agents[1].col1, "name2")
self.assertEqual(agents[1].geometry, Point(2.0, 1.0))
self.assertEqual(agents[1].crs, "epsg:4326")
Expand All @@ -121,7 +117,7 @@ def test_from_GeoJSON_and_agent_creator_with_crs(self):

self.assertEqual(len(agents), 2)

self.assertEqual(agents[0].unique_id, 0)
self.assertEqual(agents[0].unique_id, 1)
self.assertEqual(agents[0].col1, "name1")
self.assertTrue(
agents[0].geometry.equals_exact(
Expand All @@ -130,7 +126,7 @@ def test_from_GeoJSON_and_agent_creator_with_crs(self):
)
self.assertEqual(agents[0].crs, self.agent_creator_with_crs.crs)

self.assertEqual(agents[1].unique_id, 1)
self.assertEqual(agents[1].unique_id, 2)
self.assertEqual(agents[1].col1, "name2")
self.assertTrue(
agents[1].geometry.equals_exact(
Expand Down
16 changes: 2 additions & 14 deletions tests/test_GeoSpace.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import random
import unittest
import uuid
import warnings

import geopandas as gpd
import mesa
import numpy as np
import pandas as pd
from shapely.geometry import Point, Polygon

import mesa_geo as mg
Expand All @@ -23,24 +21,20 @@ def setUp(self) -> None:
self.agents = [
self.agent_creator.create_agent(
geometry=geometry,
unique_id=uuid.uuid4().int,
)
for geometry in self.geometries
]
self.polygon_agent = mg.GeoAgent(
unique_id=uuid.uuid4().int,
model=self.model,
geometry=Polygon([(0, 0), (0, 2), (2, 2), (2, 0)]),
crs="epsg:3857",
)
self.touching_agent = mg.GeoAgent(
unique_id=uuid.uuid4().int,
model=self.model,
geometry=Polygon([(2, 0), (2, 2), (4, 2), (4, 0)]),
crs="epsg:3857",
)
self.disjoint_agent = mg.GeoAgent(
unique_id=uuid.uuid4().int,
model=self.model,
geometry=Polygon([(10, 10), (10, 12), (12, 12), (12, 10)]),
crs="epsg:3857",
Expand Down Expand Up @@ -144,20 +138,14 @@ def test_get_neighbors_within_distance(self):
def test_get_agents_as_GeoDataFrame(self):
self.geo_space.add_agents(self.agents)

agents_list = [
{"geometry": agent.geometry, "unique_id": agent.unique_id}
for agent in self.agents
]
agents_gdf = gpd.GeoDataFrame.from_records(agents_list, index="unique_id")
agents_list = [{"geometry": agent.geometry} for agent in self.agents]
agents_gdf = gpd.GeoDataFrame.from_records(agents_list)
# workaround for geometry column not being set in `from_records`
# see https://github.com/geopandas/geopandas/issues/3152
# may be removed when the issue is resolved
agents_gdf.set_geometry("geometry", inplace=True)
agents_gdf.crs = self.geo_space.crs

pd.testing.assert_frame_equal(
self.geo_space.get_agents_as_GeoDataFrame(), agents_gdf
)
self.assertEqual(
self.geo_space.get_agents_as_GeoDataFrame().crs, agents_gdf.crs
)
Expand Down
10 changes: 3 additions & 7 deletions tests/test_MapModule.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import unittest
import uuid

import mesa
import numpy as np
Expand All @@ -20,18 +19,15 @@ def setUp(self) -> None:
)
self.points = [Point(1, 1)] * 7
self.point_agents = [
self.agent_creator.create_agent(point, unique_id=uuid.uuid4().int)
for point in self.points
self.agent_creator.create_agent(point) for point in self.points
]
self.lines = [LineString([(1, 1), (2, 2)])] * 9
self.line_agents = [
self.agent_creator.create_agent(line, unique_id=uuid.uuid4().int)
for line in self.lines
self.agent_creator.create_agent(line) for line in self.lines
]
self.polygons = [Polygon([(1, 1), (2, 2), (4, 4)])] * 3
self.polygon_agents = [
self.agent_creator.create_agent(polygon, unique_id=uuid.uuid4().int)
for polygon in self.polygons
self.agent_creator.create_agent(polygon) for polygon in self.polygons
]
self.raster_layer = mg.RasterLayer(
1, 1, crs="epsg:4326", total_bounds=[0, 0, 1, 1], model=self.model
Expand Down
Loading