Skill Tracer
Skills › Scientific Agent Skills › geomaster › big-data.md

big-data.md

165 lines · skills/geomaster/references/big-data.md at 5f2bc03

Open on GitHub
1# Big data and cloud geospatial computing
2
3Core local array/storage recipes are tested. Remote clusters, cloud uploads and GPU
4examples below are illustrative and require their own runtime/access. See [review.md](review.md).
5

Big data and cloud geospatial computing

Core local array/storage recipes are tested. Remote clusters, cloud uploads and GPU examples below are illustrative and require their own runtime/access. See review.md.

Raw lines 1 to 5
6## Dask-GeoPandas
7
8```python
9import dask_geopandas
11points = dask_geopandas.read_file('points.gpkg', npartitions=5)
12zones = dask_geopandas.read_file('zones.gpkg', npartitions=3)
13if points.crs is None or zones.crs is None:
14 raise ValueError('CRS must be known')
15points = points.to_crs(zones.crs)
16points = points.spatial_shuffle()
17zones = zones.spatial_shuffle()
18joined = points.sjoin(zones, how='inner', predicate='within')
19result = joined.compute()
20```
22`spatial_shuffle` computes spatial partitioning; `set_index(calculate_spatial_partitions=True)`
23is not that API. The current distributed spatial join supports inner joins. Its
24result can multiply rows for overlapping zones. Perform area/buffer calculations only
25after projecting to a justified metric CRS. Partition count alone is not a memory cap.

Dask-GeoPandas

import dask_geopandas

points = dask_geopandas.read_file('points.gpkg', npartitions=5)
zones = dask_geopandas.read_file('zones.gpkg', npartitions=3)
if points.crs is None or zones.crs is None:
    raise ValueError('CRS must be known')
points = points.to_crs(zones.crs)
points = points.spatial_shuffle()
zones = zones.spatial_shuffle()
joined = points.sjoin(zones, how='inner', predicate='within')
result = joined.compute()

spatial_shuffle computes spatial partitioning; set_index(calculate_spatial_partitions=True) is not that API. The current distributed spatial join supports inner joins. Its result can multiply rows for overlapping zones. Perform area/buffer calculations only after projecting to a justified metric CRS. Partition count alone is not a memory cap.

Raw lines 6 to 26
27## Lazy rasters
29Dask has no `array.from_rasterio`. Rioxarray constructs chunked arrays and manages file
30access; do not close a Rasterio handle that deferred tasks still depend on.
32```python
33import rioxarray
34import numpy as np
36cube = rioxarray.open_rasterio('stack.tif', masked=True,
37 chunks={'band': 1, 'x': 1024, 'y': 1024})
38# Caller has verified a common-grid four-band B02/B03/B04/B08 reflectance stack.
39red, nir = cube.isel(band=2), cube.isel(band=3)
40denominator = nir + red
41ndvi = ((nir - red) / denominator).where(denominator != 0)
42# Global reduction, not a different normalization for each chunk.
43low, high = ndvi.min(skipna=True), ndvi.max(skipna=True)
44normalized = ((ndvi - low) / (high - low)).where(high > low)
45normalized.rio.to_raster('normalized.tif', dtype='float32')
46cube.close()
47```
49Per-chunk min/max creates seams and changes the statistic with chunk layout. Focal
50filters need a halo via `map_overlap` or equivalent. Cloud/nodata masks must be applied
51before reductions. Clip early; `compute()` on the whole cube can exceed RAM.
53Use `with Client(...)`/`with LocalCluster(...)` to close cluster resources. In scripts,
54put process-based cluster creation under `if __name__ == '__main__':`. Do not serialize
55open HDF5/GDAL handles into process workers; each task should open its own input.

Lazy rasters

Dask has no array.from_rasterio. Rioxarray constructs chunked arrays and manages file access; do not close a Rasterio handle that deferred tasks still depend on.

import rioxarray
import numpy as np

cube = rioxarray.open_rasterio('stack.tif', masked=True,
                             chunks={'band': 1, 'x': 1024, 'y': 1024})
# Caller has verified a common-grid four-band B02/B03/B04/B08 reflectance stack.
red, nir = cube.isel(band=2), cube.isel(band=3)
denominator = nir + red
ndvi = ((nir - red) / denominator).where(denominator != 0)
# Global reduction, not a different normalization for each chunk.
low, high = ndvi.min(skipna=True), ndvi.max(skipna=True)
normalized = ((ndvi - low) / (high - low)).where(high > low)
normalized.rio.to_raster('normalized.tif', dtype='float32')
cube.close()

Per-chunk min/max creates seams and changes the statistic with chunk layout. Focal filters need a halo via map_overlap or equivalent. Cloud/nodata masks must be applied before reductions. Clip early; compute() on the whole cube can exceed RAM.

Use with Client(...)/with LocalCluster(...) to close cluster resources. In scripts, put process-based cluster creation under if __name__ == '__main__':. Do not serialize open HDF5/GDAL handles into process workers; each task should open its own input.

Raw lines 27 to 56
57## Cloud platforms

Cloud platforms

Raw lines 57 to 58
59### Earth Engine
61Use the SCL-mask template in [remote-sensing.md](remote-sensing.md). For a year,
62`filterDate(f'{year}-01-01', f'{year+1}-01-01')` includes all days because the end is
63exclusive. A valid-pixel mask goes to `updateMask(mask)`, not `updateMask(mask.Not())`.
64Median mosaics are not guaranteed cloud-free and need QA/support inspection.
65`ee.batch.Export.image.toDrive` requires image, region, scale/CRS and output settings;
66starting the task writes to an external destination and may consume service quota.

Earth Engine

Use the SCL-mask template in remote-sensing.md. For a year, filterDate(f'{year}-01-01', f'{year+1}-01-01') includes all days because the end is exclusive. A valid-pixel mask goes to updateMask(mask), not updateMask(mask.Not()). Median mosaics are not guaranteed cloud-free and need QA/support inspection. ee.batch.Export.image.toDrive requires image, region, scale/CRS and output settings; starting the task writes to an external destination and may consume service quota.

Raw lines 59 to 67
68### Planetary Computer / ODC
70Search with a small bbox and `max_items`; use `list(search.items())`. `odc.stac.load`
71accepts `chunks={'x': 1024, 'y': 1024}`, not `chunkx`/`chunky`. Supply the output bbox
72as well as resolution to avoid loading full intersecting scenes. Inspect NAIP band
73metadata rather than assuming a multi-band `image` asset becomes one scalar band.
74Use a common `geobox` or `like=` when independent loads must align exactly. Calibration
75and provider-specific asset names are described in the remote-sensing reference.

Planetary Computer / ODC

Search with a small bbox and max_items; use list(search.items()). odc.stac.load accepts chunks={'x': 1024, 'y': 1024}, not chunkx/chunky. Supply the output bbox as well as resolution to avoid loading full intersecting scenes. Inspect NAIP band metadata rather than assuming a multi-band image asset becomes one scalar band. Use a common geobox or like= when independent loads must align exactly. Calibration and provider-specific asset names are described in the remote-sensing reference.

Raw lines 68 to 76
77### S3 and Google Cloud Storage
79Rasterio authentication sessions belong to `rasterio.Env`, not an `open(..., session=...)`
80keyword. Prefer the environment's credential chain; do not embed keys.
82```python
83import rasterio
84from rasterio.session import AWSSession
86# Requires boto3 and existing authorized AWS credential configuration.
87with rasterio.Env(AWSSession()):
88 with rasterio.open('s3://your-bucket/path.tif') as src:
89 subset = src.read(1, window=((0, 128), (0, 128)), masked=True)
90```
92GCS uses a `GSSession` and configured Google credentials in the same Env pattern.
93`google.cloud.storage.Client().bucket(name).blob(key).upload_from_filename(path)`
94performs an external upload; validate bucket/key/overwrite intent first. COG range
95reads still incur requests/egress and may fail when SAS credentials expire mid-job.

S3 and Google Cloud Storage

Rasterio authentication sessions belong to rasterio.Env, not an open(..., session=...) keyword. Prefer the environment's credential chain; do not embed keys.

import rasterio
from rasterio.session import AWSSession

# Requires boto3 and existing authorized AWS credential configuration.
with rasterio.Env(AWSSession()):
    with rasterio.open('s3://your-bucket/path.tif') as src:
        subset = src.read(1, window=((0, 128), (0, 128)), masked=True)

GCS uses a GSSession and configured Google credentials in the same Env pattern. google.cloud.storage.Client().bucket(name).blob(key).upload_from_filename(path) performs an external upload; validate bucket/key/overwrite intent first. COG range reads still incur requests/egress and may fail when SAS credentials expire mid-job.

Raw lines 77 to 96
97## GPU paths (illustrative, not executed)
99CuPy needs compatible GPU/CUDA software; transferring arrays can dominate small work.
100Cast to floating point before subtraction and keep the same QA mask/denominator policy
101as CPU processing. Do not normalize integer arrays in place.
103cuSpatial's `point_in_polygon(points.geometry, polygons.geometry)` returns a boolean
104point-by-polygon table, not a joined GeoDataFrame. For larger data use its documented
105quadtree workflow. Both inputs need the same coordinate system; output indices require
106explicit reconciliation. `join_polygon_points` is not the current documented API.
108PyTorch datasets need `__len__`, `__getitem__`, aligned image/label grids and explicit
109nodata label handling. Training batches require `optimizer.zero_grad(set_to_none=True)`.
110Current AMP APIs are `torch.amp.GradScaler('cuda')` and
111`torch.autocast(device_type='cuda')`; the older `torch.cuda.amp` namespace is deprecated.
112Keep preprocessing, band order and normalization consistent across training/inference.
113See [machine-learning.md](machine-learning.md) for spatial holdouts and tensor shapes.

GPU paths (illustrative, not executed)

CuPy needs compatible GPU/CUDA software; transferring arrays can dominate small work. Cast to floating point before subtraction and keep the same QA mask/denominator policy as CPU processing. Do not normalize integer arrays in place.

cuSpatial's point_in_polygon(points.geometry, polygons.geometry) returns a boolean point-by-polygon table, not a joined GeoDataFrame. For larger data use its documented quadtree workflow. Both inputs need the same coordinate system; output indices require explicit reconciliation. join_polygon_points is not the current documented API.

PyTorch datasets need __len__, __getitem__, aligned image/label grids and explicit nodata label handling. Training batches require optimizer.zero_grad(set_to_none=True). Current AMP APIs are torch.amp.GradScaler('cuda') and torch.autocast(device_type='cuda'); the older torch.cuda.amp namespace is deprecated. Keep preprocessing, band order and normalization consistent across training/inference. See machine-learning.md for spatial holdouts and tensor shapes.

Raw lines 97 to 114
115## Storage
117### COG
119```python
120from rasterio.shutil import copy as rio_copy
121from rio_cogeo.cogeo import cog_validate
122rio_copy('input.tif', 'output.tif', driver='COG', compress='DEFLATE',
123 overview_resampling='NEAREST') # categorical data; choose appropriately
124valid, errors, warnings = cog_validate('output.tif')
125assert valid, errors
126```
128The COG driver constructs the layout; tiled/compressed TIFF alone does not guarantee it.
129Use average/bilinear only for suitable continuous data, nearest/mode for classes.
130Create overviews as part of conversion. Updating the output afterward can break layout.

COG

from rasterio.shutil import copy as rio_copy
from rio_cogeo.cogeo import cog_validate
rio_copy('input.tif', 'output.tif', driver='COG', compress='DEFLATE',
         overview_resampling='NEAREST')  # categorical data; choose appropriately
valid, errors, warnings = cog_validate('output.tif')
assert valid, errors

The COG driver constructs the layout; tiled/compressed TIFF alone does not guarantee it. Use average/bilinear only for suitable continuous data, nearest/mode for classes. Create overviews as part of conversion. Updating the output afterward can break layout.

Raw lines 117 to 131
132### Zarr / Xarray
134```python
135# ds is an Xarray Dataset with explicit chunks and spatial/time metadata.
136ds.to_zarr('data.zarr', mode='w-', zarr_format=2, consolidated=True)
137import xarray as xr
138restored = xr.open_zarr('data.zarr', consolidated=True)
139```
141`w-` rejects an existing store; choose replacement explicitly. This selects v2 for
142interoperability rather than depending on default format. Zarr 3 uses
143`zarr.storage.LocalStore`, not the removed `DirectoryStore`. Verify dataset coordinates,
144CRS conventions, chunk boundaries and dtype after round trip.

Zarr / Xarray

# ds is an Xarray Dataset with explicit chunks and spatial/time metadata.
ds.to_zarr('data.zarr', mode='w-', zarr_format=2, consolidated=True)
import xarray as xr
restored = xr.open_zarr('data.zarr', consolidated=True)

w- rejects an existing store; choose replacement explicitly. This selects v2 for interoperability rather than depending on default format. Zarr 3 uses zarr.storage.LocalStore, not the removed DirectoryStore. Verify dataset coordinates, CRS conventions, chunk boundaries and dtype after round trip.

Raw lines 132 to 145
146### GeoParquet
148```python
149gdf.to_parquet('data.parquet', index=False, compression='snappy',
150 write_covering_bbox=True, schema_version='1.1.0')
151subset = gpd.read_parquet('data.parquet', bbox=(xmin, ymin, xmax, ymax))
152```
154The bbox is in the file's CRS. `index=True` stores the pandas index; it does not create
155a spatial index. Covering bbox enables spatial filtering; inspect metadata and client
156support. Test I/O through the receiving system, especially for multiple geometries.
158Sources: [Dask-GeoPandas spatial shuffle](https://dask-geopandas.readthedocs.io/en/stable/docs/reference/api/dask_geopandas.GeoDataFrame.spatial_shuffle.html),
159[Rioxarray](https://corteva.github.io/rioxarray/stable/rioxarray.html),
160[ODC](https://odc-stac.readthedocs.io/en/latest/_api/odc.stac.load.html),
161[Rasterio cloud credentials](https://rasterio.readthedocs.io/en/stable/topics/switch.html),
162[cuSpatial](https://docs.rapids.ai/api/cuspatial/stable/api_docs/spatial/),
163[PyTorch AMP](https://docs.pytorch.org/docs/stable/amp.html),
164[COG](https://gdal.org/en/stable/drivers/raster/cog.html),
165[Xarray Zarr](https://docs.xarray.dev/en/stable/generated/xarray.Dataset.to_zarr.html).

GeoParquet

gdf.to_parquet('data.parquet', index=False, compression='snappy',
               write_covering_bbox=True, schema_version='1.1.0')
subset = gpd.read_parquet('data.parquet', bbox=(xmin, ymin, xmax, ymax))

The bbox is in the file's CRS. index=True stores the pandas index; it does not create a spatial index. Covering bbox enables spatial filtering; inspect metadata and client support. Test I/O through the receiving system, especially for multiple geometries.

Sources: Dask-GeoPandas spatial shuffle, Rioxarray, ODC, Rasterio cloud credentials, cuSpatial, PyTorch AMP, COG, Xarray Zarr.

Raw lines 146 to 165