-
Notifications
You must be signed in to change notification settings - Fork 59
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
215 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
Uganda Model | ||
============== | ||
|
||
An implementation of the [Uganda Example](https://github.com/abmgis/abmgis/tree/master/Chapter05-GIS/Models/UgandaExample) in Python, using [Mesa](https://github.com/projectmesa/mesa) and [Mesa-Geo](https://github.com/projectmesa/mesa-geo). | ||
|
||
## How to run | ||
|
||
To run the model interactively, run `mesa runserver` in this directory. e.g. | ||
|
||
```bash | ||
mesa runserver | ||
``` | ||
|
||
Then open your browser to [http://127.0.0.1:8521/](http://127.0.0.1:8521/) and press `Start`. |
Binary file not shown.
Binary file not shown.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
from uganda.server import server | ||
|
||
server.launch() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
import math | ||
import random | ||
import uuid | ||
|
||
import mesa | ||
import numpy as np | ||
from shapely.geometry import Point | ||
|
||
from mesa_geo.geoagent import GeoAgent | ||
|
||
from .space import UgandaArea | ||
|
||
|
||
class Person(GeoAgent): | ||
MOBILITY_RANGE_X = 0.0 | ||
MOBILITY_RANGE_Y = 0.0 | ||
|
||
def __init__(self, unique_id, model, geometry, crs, img_coord): | ||
super().__init__(unique_id, model, geometry, crs) | ||
self.img_coord = img_coord | ||
|
||
def set_random_world_coord(self): | ||
world_coord_point = Point( | ||
self.model.space.population_layer.transform * self.img_coord | ||
) | ||
random_world_coord_x = world_coord_point.x + np.random.uniform( | ||
-self.MOBILITY_RANGE_X, self.MOBILITY_RANGE_X | ||
) | ||
random_world_coord_y = world_coord_point.y + np.random.uniform( | ||
-self.MOBILITY_RANGE_Y, self.MOBILITY_RANGE_Y | ||
) | ||
self.geometry = Point(random_world_coord_x, random_world_coord_y) | ||
|
||
def step(self): | ||
neighborhood = self.model.space.population_layer.get_neighborhood( | ||
self.img_coord, moore=True | ||
) | ||
found = False | ||
while neighborhood and not found: | ||
next_img_coord = random.choice(neighborhood) | ||
world_coord_point = Point( | ||
self.model.space.population_layer.transform * next_img_coord | ||
) | ||
if world_coord_point.within(self.model.space.lake): | ||
neighborhood.remove(next_img_coord) | ||
continue | ||
else: | ||
found = True | ||
self.img_coord = next_img_coord | ||
self.set_random_world_coord() | ||
|
||
|
||
class Uganda(mesa.Model): | ||
def __init__( | ||
self, | ||
population_gzip_file="data/popu.asc.gz", | ||
lake_zip_file="data/lake.zip", | ||
world_zip_file="data/clip.zip", | ||
): | ||
super().__init__() | ||
self.space = UgandaArea(crs="epsg:4326") | ||
self.space.load_data(population_gzip_file, lake_zip_file, world_zip_file) | ||
pixel_size_x, pixel_size_y = self.space.population_layer.resolution | ||
Person.MOBILITY_RANGE_X = pixel_size_x / 2.0 | ||
Person.MOBILITY_RANGE_Y = pixel_size_y / 2.0 | ||
|
||
self.schedule = mesa.time.RandomActivation(self) | ||
self._create_agents() | ||
|
||
def _create_agents(self): | ||
num_agents = 0 | ||
for cell in self.space.population_layer: | ||
popu_round = math.ceil(cell.population) | ||
if popu_round > 0: | ||
for _ in range(popu_round): | ||
num_agents += 1 | ||
point = Point(self.space.population_layer.transform * cell.indices) | ||
if not point.within(self.space.lake): | ||
person = Person( | ||
unique_id=uuid.uuid4().int, | ||
model=self, | ||
crs=self.space.crs, | ||
geometry=point, | ||
img_coord=cell.indices, | ||
) | ||
person.set_random_world_coord() | ||
self.space.add_agents(person) | ||
self.schedule.add(person) | ||
|
||
def step(self): | ||
self.schedule.step() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import mesa | ||
from shapely.geometry import Point, Polygon | ||
|
||
from mesa_geo.geoagent import GeoAgent | ||
from mesa_geo.visualization.ModularVisualization import ModularServer | ||
from mesa_geo.visualization.modules import MapModule | ||
|
||
from .model import Uganda | ||
from .space import UgandaCell | ||
|
||
|
||
class NumAgentsElement(mesa.visualization.TextElement): | ||
def __init__(self): | ||
super().__init__() | ||
|
||
def render(self, model): | ||
return f"Number of Agents: {len(model.space.agents)}" | ||
|
||
|
||
def agent_portrayal(agent): | ||
if isinstance(agent, GeoAgent): | ||
if isinstance(agent.geometry, Point): | ||
return { | ||
"stroke": False, | ||
"color": "Green", | ||
"radius": 2, | ||
"fillOpacity": 0.3, | ||
} | ||
elif isinstance(agent.geometry, Polygon): | ||
return { | ||
"fillColor": "Blue", | ||
"fillOpacity": 1.0, | ||
} | ||
elif isinstance(agent, UgandaCell): | ||
return (agent.population, agent.population, agent.population, 1) | ||
|
||
|
||
geospace_element = MapModule( | ||
agent_portrayal, | ||
[0.18357442221655898, 32.877823549000034], | ||
12.9, | ||
map_height=500, | ||
map_width=500, | ||
) | ||
num_agents_element = NumAgentsElement() | ||
|
||
server = ModularServer(Uganda, [geospace_element, num_agents_element], "Uganda Model") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
from __future__ import annotations | ||
|
||
import gzip | ||
import uuid | ||
|
||
import geopandas as gpd | ||
import mesa | ||
import rasterio as rio | ||
|
||
from mesa_geo.geoagent import GeoAgent | ||
from mesa_geo.geospace import GeoSpace | ||
from mesa_geo.raster_layers import Cell, RasterLayer | ||
|
||
|
||
class UgandaCell(Cell): | ||
population: float | None | ||
|
||
def __init__( | ||
self, | ||
pos: mesa.space.Coordinate | None = None, | ||
indices: mesa.space.Coordinate | None = None, | ||
): | ||
super().__init__(pos, indices) | ||
self.population = None | ||
|
||
def step(self): | ||
pass | ||
|
||
|
||
class Lake(GeoAgent): | ||
pass | ||
|
||
|
||
class UgandaArea(GeoSpace): | ||
def __init__(self, crs): | ||
super().__init__(crs=crs) | ||
|
||
def load_data(self, population_gzip_file, lake_zip_file, world_zip_file): | ||
world_size = gpd.GeoDataFrame.from_file(world_zip_file) | ||
with gzip.open(population_gzip_file, "rb") as population_file: | ||
with rio.open(population_file, "r") as population_data: | ||
values = population_data.read() | ||
_, height, width = values.shape | ||
self.add_layer( | ||
RasterLayer( | ||
width, | ||
height, | ||
world_size.crs, | ||
world_size.total_bounds, | ||
cell_cls=UgandaCell, | ||
) | ||
) | ||
self.population_layer.apply_raster(values, name="population") | ||
|
||
self.lake = gpd.GeoDataFrame.from_file(lake_zip_file).geometry[0] | ||
self.add_agents(GeoAgent(uuid.uuid4().int, None, self.lake, self.crs)) | ||
|
||
@property | ||
def population_layer(self): | ||
return self.layers[0] |