Geo — Raster
Raster substrate classes and utilities backed by NumPy arrays.
dissmodel.geo.raster.backend
dissmodel/geo/raster/backend.py
Vectorized engine for cellular automata on raster grids (NumPy 2D arrays).
Responsibility
Provide generic spatial operations (shift, dilate, focal_sum, snapshot) with no domain knowledge — no land-use classes, no CRS, no I/O, no project-specific constants.
Domain models (FloodRasterModel, MangroveRasterModel, …) import
RasterBackend and operate on named arrays stored in self.arrays.
Minimal example
from dissmodel.geo.raster.backend import RasterBackend, DIRS_MOORE
b = RasterBackend(shape=(100, 100))
b.set("state", np.zeros((100, 100), dtype=np.int8))
state = b.get("state").copy() # equivalent to cell.past[attr]
contact = b.neighbor_contact(state == 1)
for dr, dc in DIRS_MOORE:
neighbour = RasterBackend.shift2d(state, dr, dc)
...
b.arrays["state"] = new_state
DIRS_MOORE = [(-1, -1), (-1, 0), (-1, 1), (0, -1), (0, 1), (1, -1), (1, 0), (1, 1)]
module-attribute
DIRS_VON_NEUMANN = [(-1, 0), (0, -1), (0, 1), (1, 0)]
module-attribute
RasterBackend
Storage and vectorized operations for 2D raster grids.
Replaces TerraME's forEachCell / forEachNeighbor with pure NumPy
operations. The backend is shared across multiple models running in the
same Environment — each model reads and writes named arrays every step.
Arrays
Stored in self.arrays as np.ndarray of shape (rows, cols).
No names are reserved — domain models define their own
("uso", "alt", "solo", "state", "temperature", …).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
shape
|
tuple[int, int]
|
Grid shape as |
required |
nodata_value
|
float | int | None
|
Sentinel value used to mark cells outside the study extent.
When provided, |
None
|
Examples:
>>> b = RasterBackend(shape=(10, 10))
>>> b.set("state", np.zeros((10, 10), dtype=np.int8))
>>> b.get("state").shape
(10, 10)
>>> b = RasterBackend(shape=(10, 10), nodata_value=-1)
>>> b.nodata_mask # True = valid cell, False = outside extent
Source code in dissmodel/geo/raster/backend.py
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 | |
nodata_mask
property
Boolean mask: True = valid cell, False = outside extent / nodata.
Derived in priority order:
1. arrays["mask"] — explicit mask band (dissluc / coastal convention:
non-zero = valid).
2. nodata_value — applied over the first available array.
3. None — no information; RasterMap skips auto-masking.
Used by RasterMap (auto_mask=True) to render out-of-extent pixels
as transparent without any per-project configuration.
focal_sum(name, neighborhood=DIRS_MOORE)
Focal sum: for each cell, sum the values of name across its neighbours.
The cell itself is not included.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
|
required |
neighborhood
|
list[tuple[int, int]]
|
Default: |
DIRS_MOORE
|
Returns:
| Type | Description |
|---|---|
ndarray
|
|
Source code in dissmodel/geo/raster/backend.py
209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 | |
focal_sum_mask(mask, neighborhood=DIRS_MOORE)
Count neighbours where mask is True.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mask
|
ndarray
|
|
required |
neighborhood
|
list[tuple[int, int]]
|
Default: |
DIRS_MOORE
|
Returns:
| Type | Description |
|---|---|
ndarray
|
Integer array with per-cell neighbour counts. |
Source code in dissmodel/geo/raster/backend.py
234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 | |
get(name)
Return a direct reference to the named array.
Use .copy() to obtain a snapshot equivalent to TerraME's .past.
Raises:
| Type | Description |
|---|---|
KeyError
|
If |
Source code in dissmodel/geo/raster/backend.py
123 124 125 126 127 128 129 130 131 132 133 134 | |
neighbor_contact(condition, neighborhood=None)
staticmethod
Return a boolean mask where each cell has at least one neighbour
satisfying condition.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
condition
|
ndarray
|
|
required |
neighborhood
|
list[tuple[int, int]] | None
|
|
None
|
Returns:
| Type | Description |
|---|---|
ndarray
|
Boolean array. |
Source code in dissmodel/geo/raster/backend.py
182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 | |
set(name, array)
Store a copy of array under name.
Source code in dissmodel/geo/raster/backend.py
119 120 121 | |
shift2d(arr, dr, dc)
staticmethod
Shift arr by (dr, dc) rows/columns without wrap-around.
Edges are filled with zero.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
arr
|
ndarray
|
|
required |
dr
|
int
|
Row offset (positive = down, negative = up). |
required |
dc
|
int
|
Column offset (positive = right, negative = left). |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
Shifted array of the same shape as |
Source code in dissmodel/geo/raster/backend.py
154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 | |
snapshot()
Return a deep copy of all arrays — equivalent to TerraME's .past mechanism.
Typical usage::
past = backend.snapshot()
uso_past = past["uso"] # state at the beginning of the step
Returns:
| Type | Description |
|---|---|
dict[str, ndarray]
|
Dictionary mapping array names to independent copies. |
Source code in dissmodel/geo/raster/backend.py
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 | |
dissmodel.geo.raster.raster_model
dissmodel/geo/raster/model.py
Base class for models backed by RasterBackend (NumPy 2D arrays).
Analogous to SpatialModel for the raster substrate — provides
infrastructure without imposing a transition rule contract.
Class hierarchy
Model (dissmodel.core)
├── SpatialModel GeoDataFrame + Queen/Rook neighbourhood (vector)
└── RasterModel RasterBackend + shift2d (raster) ← this file
├── FloodRasterModel
└── MangroveRasterModel
Usage
class MyRasterModel(RasterModel):
def setup(self, backend, my_param=1.0):
super().setup(backend)
self.my_param = my_param
def execute(self):
uso = self.backend.get("uso").copy()
...
self.backend.arrays["uso"] = new_uso
RasterModel
Bases: Model
Model backed by a RasterBackend.
Subclass of Model that adds raster infrastructure without imposing
a transition rule contract. Can be subclassed directly by any model
that operates on NumPy 2D arrays.
Parameters (setup)
backend : RasterBackend
Backend shared across all models in the same Environment.
Attributes available in subclasses
backend : RasterBackend
The shared array store.
shape : tuple[int, int]
Grid shape (rows, cols) — shortcut for self.backend.shape.
shift : callable
Shortcut for RasterBackend.shift2d (static method).
dirs : list[tuple[int, int]]
DIRS_MOORE — the 8 directions of the Moore neighbourhood.
Examples:
>>> class HeatDiffusion(RasterModel):
... def execute(self):
... temp = self.backend.get("temp").copy()
... for dr, dc in self.dirs:
... temp += 0.1 * self.shift(temp, dr, dc)
... self.backend.arrays["temp"] = temp
Source code in dissmodel/geo/raster/raster_model.py
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | |
dissmodel.geo.raster.cellular_automaton
dissmodel/geo/raster_cellular_automaton.py
Base class for cellular automata backed by RasterBackend (NumPy 2D arrays).
Analogous to CellularAutomaton (GeoDataFrame), but for the raster substrate.
Hierarchy
Model
├── SpatialModel
│ └── CellularAutomaton rule(idx) → value (vector, pull)
└── RasterModel
└── RasterCellularAutomaton rule(arrays) → arrays (raster, vectorized)
Why a different rule() contract
CellularAutomaton.rule(idx) returns a single value for one cell — it is called once per cell per step (O(n) Python calls). This is correct for the vector substrate where neighborhood lookup is the bottleneck.
For the raster substrate, the bottleneck is the Python loop itself. RasterCellularAutomaton.rule() receives the full snapshot of all arrays and returns a dict of updated arrays — one NumPy call covers the entire grid. This is the natural pattern for NumPy-based CA.
Comparison
# vector CA — rule called n times per step
class GameOfLife(CellularAutomaton):
def rule(self, idx):
alive = self.neighbor_values(idx, "state").sum()
...
return new_state
# raster CA — rule called once per step
class GameOfLife(RasterCellularAutomaton):
def rule(self, arrays):
state = arrays["state"]
alive = backend.focal_sum_mask(state == 1)
...
return {"state": new_state}
Usage
from dissmodel.geo.raster_cellular_automaton import RasterCellularAutomaton
from dissmodel.geo.raster.backend import RasterBackend
from dissmodel.core import Environment
import numpy as np
class GameOfLife(RasterCellularAutomaton):
def rule(self, arrays):
state = arrays["state"]
neighbors = self.backend.focal_sum_mask(state == 1)
born = (state == 0) & (neighbors == 3)
survive = (state == 1) & np.isin(neighbors, [2, 3])
return {"state": np.where(born | survive, 1, 0)}
b = RasterBackend(shape=(50, 50))
b.set("state", np.random.randint(0, 2, (50, 50)))
env = Environment(start_time=1, end_time=100)
GameOfLife(backend=b)
env.run()
RasterCellularAutomaton
Bases: RasterModel, ABC
Base class for NumPy-based cellular automata.
Extends :class:~dissmodel.geo.raster.model.RasterModel with a
vectorized transition rule — rule() receives all arrays as a
snapshot and returns a dict of updated arrays.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
backend
|
RasterBackend
|
Shared backend with the simulation arrays. |
required |
state_attr
|
str
|
Primary state array name, by default |
required |
**kwargs
|
Any
|
Extra keyword arguments forwarded to RasterModel. |
{}
|
Examples:
>>> class MyCA(RasterCellularAutomaton):
... def rule(self, arrays):
... state = arrays["state"]
... # ... NumPy operations over full grid ...
... return {"state": new_state}
Source code in dissmodel/geo/raster/cellular_automaton.py
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 | |
execute()
Execute one simulation step by calling rule() once over the full grid.
Takes a snapshot of all arrays (past state), passes it to rule(), and writes the returned arrays back to the backend.
Source code in dissmodel/geo/raster/cellular_automaton.py
145 146 147 148 149 150 151 152 153 154 155 | |
rule(arrays)
abstractmethod
Vectorized transition rule applied to the full grid.
Receives a snapshot of all arrays (equivalent to celula.past[] in TerraME) and returns a dict with the arrays to update.
Only the arrays present in the returned dict are written back — arrays not returned are left unchanged.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
arrays
|
dict[str, ndarray]
|
Snapshot of backend arrays at the start of the step. Modifying these arrays does NOT affect the backend — they are copies (equivalent to .past semantics). |
required |
Returns:
| Type | Description |
|---|---|
dict[str, ndarray]
|
Dict mapping array name → new array. Partial updates allowed. |
Examples:
>>> def rule(self, arrays):
... state = arrays["state"] # read from snapshot
... neighbors = self.backend.focal_sum_mask(state == 1)
... new_state = np.where(neighbors > 3, 0, state)
... return {"state": new_state} # write back
Source code in dissmodel/geo/raster/cellular_automaton.py
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 | |
dissmodel.geo.raster.raster_grid
dissmodel/geo/raster_grid.py
Utilitário para criar RasterBackend sintético.
Análogo a vector_grid() (GeoDataFrame), mas para o substrato NumPy.
Uso
from dissmodel.geo.raster_grid import raster_grid
import numpy as np
# grade vazia com arrays zerados
b = raster_grid(rows=50, cols=50, attrs={"state": 0})
# grade com array inicial customizado
b = raster_grid(
rows=50, cols=50,
attrs={"state": np.random.randint(0, 2, (50, 50))}
)
raster_grid(rows, cols, attrs=None, dtype=None)
Create a RasterBackend with optional pre-filled arrays.
Analogous to :func:~dissmodel.geo.vector_grid for the raster
substrate. Useful for tests, examples, and synthetic benchmarks.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
rows
|
int
|
Number of rows in the grid. |
required |
cols
|
int
|
Number of columns in the grid. |
required |
attrs
|
dict
|
Mapping of array name → initial value. - scalar (int or float): fills the entire grid with that value. - np.ndarray of shape (rows, cols): used directly (a copy is stored). If not provided, an empty backend is returned. |
None
|
dtype
|
numpy dtype
|
Default dtype for scalar-initialized arrays. If None, inferred from the scalar type (int → np.int32, float → np.float64). |
None
|
Returns:
| Type | Description |
|---|---|
RasterBackend
|
Backend with shape (rows, cols) and the requested arrays. |
Examples:
>>> b = raster_grid(10, 10, attrs={"state": 0})
>>> b.shape
(10, 10)
>>> b.get("state").shape
(10, 10)
>>> import numpy as np
>>> state = np.random.randint(0, 2, (10, 10))
>>> b = raster_grid(10, 10, attrs={"state": state})
Source code in dissmodel/geo/raster/raster_grid.py
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 | |