@@ -229,6 +229,10 @@ class Cell(Agent):
229229 """
230230 Cells are containers of raster attributes, and are building blocks of `RasterLayer`.
231231
232+ Band data is stored in the parent RasterLayer's ``_data`` arrays.
233+ Cell proxies attribute access into those arrays via ``__getattr__``
234+ and ``__setattr__``.
235+
232236 Deprecated:
233237 `Cell.indices` is deprecated. Use `Cell.rowcol` instead.
234238 """
@@ -245,6 +249,7 @@ def __init__(
245249 * ,
246250 rowcol = None ,
247251 xy = None ,
252+ _layer = None ,
248253 ):
249254 """
250255 Initialize a cell.
@@ -256,13 +261,40 @@ def __init__(
256261 :param rowcol: Indices of the cell in (row, col) format.
257262 Origin is at upper left corner of the grid
258263 :param xy: Geographic/projected (x, y) coordinates of the cell center in the CRS.
264+ :param _layer: The parent RasterLayer for band proxy access (internal use).
259265 """
266+ self ._layer = _layer
260267
261268 super ().__init__ (model )
262269 self ._pos = pos
263270 self ._rowcol = indices if rowcol is None else rowcol
264271 self ._xy = xy
265272
273+ def __getattr__ (self , name : str ):
274+ try :
275+ layer = object .__getattribute__ (self , "_layer" )
276+ except AttributeError :
277+ raise AttributeError (f"No attribute '{ name } '" ) from None
278+ if layer is not None and name in layer ._data :
279+ row , col = object .__getattribute__ (self , "_rowcol" )
280+ return layer ._data [name ][row , col ]
281+ raise AttributeError (f"No band '{ name } ' on this layer" )
282+
283+ def __setattr__ (self , name : str , value ):
284+ if name .startswith ("_" ):
285+ object .__setattr__ (self , name , value )
286+ return
287+ try :
288+ layer = object .__getattribute__ (self , "_layer" )
289+ except AttributeError :
290+ object .__setattr__ (self , name , value )
291+ return
292+ if layer is not None and name in layer ._data :
293+ row , col = object .__getattribute__ (self , "_rowcol" )
294+ layer ._data [name ][row , col ] = value
295+ return
296+ object .__setattr__ (self , name , value )
297+
266298 @property
267299 def pos (self ) -> Coordinate | None :
268300 """
@@ -275,17 +307,13 @@ def pos(self, pos: Coordinate | None) -> None:
275307 """
276308 Deprecated setter for `pos`.
277309 """
278- # mesa Agent set pos to None by default
279- # avoid raising a warning when pos is set to None by the Agent constructor
280310 if pos is not None :
281311 warnings .warn (
282312 "Cell.pos setter is deprecated and will be read-only in a future release." ,
283313 DeprecationWarning ,
284314 stacklevel = 2 ,
285315 )
286316
287- # set the pos for backward compatibility
288- # in the future, this will be removed because pos is read-only
289317 self ._pos = pos
290318
291319 @property
@@ -334,6 +362,18 @@ def xy(self) -> FloatCoordinate | None:
334362 def step (self ):
335363 pass
336364
365+ @property
366+ def grid_pos (self ) -> Coordinate :
367+ """
368+ Grid position in (grid_x, grid_y) format with origin at lower left.
369+ Preferred replacement for the deprecated ``pos`` property.
370+ """
371+ row , col = self ._rowcol
372+ grid_x = col
373+ grid_y = self ._layer .height - row - 1
374+ return grid_x , grid_y
375+
376+
337377
338378class RasterLayer (RasterBase ):
339379 """
@@ -407,25 +447,22 @@ def _initialize_cells(self) -> None:
407447 if supports_legacy_pos_indices :
408448
409449 def make_cell (grid_x : int , grid_y : int , row_idx : int , col_idx : int , xy ):
410- # Backward-compatible path for legacy signature:
411- # __init__(self, model, pos=None, indices=None, ...)
412450 cell = self .cell_cls (
413451 self .model ,
414452 pos = (grid_x , grid_y ),
415453 indices = (row_idx , col_idx ),
454+ _layer = self ,
416455 )
417- # Legacy constructor path does not accept xy; set it manually.
418456 cell ._xy = xy
419457 return cell
420458 else :
421- # New constructor path: __init__(self, model, pos=None, rowcol=None, xy=None, ...)
422- # or: __init__(self, model, **kwargs)
423459 def make_cell (grid_x : int , grid_y : int , row_idx : int , col_idx : int , xy ):
424460 return self .cell_cls (
425461 self .model ,
426462 pos = (grid_x , grid_y ),
427463 rowcol = (row_idx , col_idx ),
428464 xy = xy ,
465+ _layer = self ,
429466 )
430467
431468 self .cells = []
@@ -539,13 +576,6 @@ def set_band(self, name: str, data: np.ndarray | float = 0.0) -> None:
539576 else :
540577 self ._data [name ] = np .full ((self .height , self .width ), data )
541578 self ._attributes .add (name )
542- for grid_x in range (self .width ):
543- for grid_y in range (self .height ):
544- setattr (
545- self .cells [grid_x ][grid_y ],
546- name ,
547- self ._data [name ][self .height - grid_y - 1 , grid_x ],
548- )
549579
550580 def get_band (self , name : str ) -> np .ndarray :
551581 """
@@ -560,13 +590,7 @@ def get_band(self, name: str) -> np.ndarray:
560590 raise ValueError (
561591 f"Band '{ name } ' does not exist. Choose from { self ._attributes } ."
562592 )
563- data = np .empty ((self .height , self .width ))
564- for grid_x in range (self .width ):
565- for grid_y in range (self .height ):
566- data [self .height - grid_y - 1 , grid_x ] = getattr (
567- self .cells [grid_x ][grid_y ], name
568- )
569- return data
593+ return self ._data [name ].copy ()
570594
571595 def remove_band (self , name : str ) -> None :
572596 """
@@ -579,9 +603,6 @@ def remove_band(self, name: str) -> None:
579603 raise ValueError (f"Band '{ name } ' does not exist." )
580604 del self ._data [name ]
581605 self ._attributes .discard (name )
582- for column in self .cells :
583- for cell in column :
584- delattr (cell , name )
585606
586607 def apply_raster (
587608 self , data : np .ndarray , attr_name : str | Sequence [str ] | None = None
@@ -642,13 +663,6 @@ def _default_attr_name() -> str:
642663 attr = _default_attr_name () if name is None else name
643664 self ._attributes .add (attr )
644665 self ._data [attr ] = data [band_idx ].copy ()
645- for grid_x in range (self .width ):
646- for grid_y in range (self .height ):
647- setattr (
648- self .cells [grid_x ][grid_y ],
649- attr ,
650- data [band_idx , self .height - grid_y - 1 , grid_x ],
651- )
652666
653667 def get_raster (self , attr_name : str | Sequence [str ] | None = None ) -> np .ndarray :
654668 """
@@ -684,11 +698,7 @@ def get_raster(self, attr_name: str | Sequence[str] | None = None) -> np.ndarray
684698 attr_names = [attr_name ]
685699 data = np .empty ((num_bands , self .height , self .width ))
686700 for ind , name in enumerate (attr_names ):
687- for grid_x in range (self .width ):
688- for grid_y in range (self .height ):
689- data [ind , self .height - grid_y - 1 , grid_x ] = getattr (
690- self .cells [grid_x ][grid_y ], name
691- )
701+ data [ind ] = self ._data [name ]
692702 return data
693703
694704 def get_random_xy (
0 commit comments