Skill Tracer
Skills › Scientific Agent Skills › geomaster › core-libraries.md

core-libraries.md

277 lines · skills/geomaster/references/core-libraries.md at 5f2bc03

Open on GitHub
1# Core Geospatial Libraries
2
3Local API reference. File-dependent fragments are illustrative; see [review.md](review.md)
4for tested versions. Raster masks, band meaning and grid alignment are prerequisites.
5Metric geometry operations require a suitable projected CRS with metre units.
6

Core Geospatial Libraries

Local API reference. File-dependent fragments are illustrative; see review.md for tested versions. Raster masks, band meaning and grid alignment are prerequisites. Metric geometry operations require a suitable projected CRS with metre units.

Raw lines 1 to 6
7## GDAL (Geospatial Data Abstraction Library)
8
9GDAL is the foundation for geospatial I/O in Python.
11```python
12from osgeo import gdal
13gdal.UseExceptions()
15# Open a raster file
16ds = gdal.Open('raster.tif')
17band = ds.GetRasterBand(1)
18data = band.ReadAsArray()
20# Get geotransform
21geotransform = ds.GetGeoTransform()
22origin_x = geotransform[0]
23pixel_width = geotransform[1]
25# Get projection
26proj = ds.GetProjection()
27```

GDAL (Geospatial Data Abstraction Library)

GDAL is the foundation for geospatial I/O in Python.

from osgeo import gdal
gdal.UseExceptions()

# Open a raster file
ds = gdal.Open('raster.tif')
band = ds.GetRasterBand(1)
data = band.ReadAsArray()

# Get geotransform
geotransform = ds.GetGeoTransform()
origin_x = geotransform[0]
pixel_width = geotransform[1]

# Get projection
proj = ds.GetProjection()
Raw lines 7 to 28
29## Rasterio
31Rasterio provides a cleaner interface to GDAL.
33```python
34import rasterio
35from rasterio.mask import mask
36import numpy as np
38# Basic reading
39with rasterio.open('raster.tif') as src:
40 data = src.read(masked=True) # All bands: (bands, rows, columns)
41 band1 = src.read(1) # Single band
42 profile = src.profile # Metadata
44# Windowed reading (memory efficient)
45with rasterio.open('large.tif') as src:
46 window = ((0, 100), (0, 100))
47 subset = src.read(1, window=window)
49# Writing
50with rasterio.open('output.tif', 'w',
51 driver='GTiff',
52 height=data.shape[1],
53 width=data.shape[2],
54 count=data.shape[0],
55 dtype=data.dtype,
56 crs=profile['crs'],
57 transform=profile['transform']) as dst:
58 dst.write(data, masked=True)
60# Masking
61with rasterio.open('raster.tif') as src:
62 masked_data, cropped_transform = mask(src, shapes=[polygon], crop=True, filled=False)
63```

Rasterio

Rasterio provides a cleaner interface to GDAL.

import rasterio
from rasterio.mask import mask
import numpy as np

# Basic reading
with rasterio.open('raster.tif') as src:
    data = src.read(masked=True) # All bands: (bands, rows, columns)
    band1 = src.read(1)         # Single band
    profile = src.profile       # Metadata

# Windowed reading (memory efficient)
with rasterio.open('large.tif') as src:
    window = ((0, 100), (0, 100))
    subset = src.read(1, window=window)

# Writing
with rasterio.open('output.tif', 'w',
                   driver='GTiff',
                   height=data.shape[1],
                   width=data.shape[2],
                   count=data.shape[0],
                   dtype=data.dtype,
                   crs=profile['crs'],
                   transform=profile['transform']) as dst:
    dst.write(data, masked=True)

# Masking
with rasterio.open('raster.tif') as src:
    masked_data, cropped_transform = mask(src, shapes=[polygon], crop=True, filled=False)
Raw lines 29 to 64
65## Fiona
67Fiona handles vector data I/O.
69```python
70import fiona
72# Read features
73with fiona.open('data.geojson') as src:
74 for feature in src:
75 geom = feature['geometry']
76 props = feature['properties']
78# Get schema and CRS
79with fiona.open('data.shp') as src:
80 schema = src.schema
81 crs = src.crs
83# Write data
84schema = {'geometry': 'Point', 'properties': {'name': 'str'}}
85with fiona.open('output.geojson', 'w', driver='GeoJSON',
86 schema=schema, crs='EPSG:4326') as dst:
87 dst.write({
88 'geometry': {'type': 'Point', 'coordinates': [0, 0]},
89 'properties': {'name': 'Origin'}
90 })
91```

Fiona

Fiona handles vector data I/O.

import fiona

# Read features
with fiona.open('data.geojson') as src:
    for feature in src:
        geom = feature['geometry']
        props = feature['properties']

# Get schema and CRS
with fiona.open('data.shp') as src:
    schema = src.schema
    crs = src.crs

# Write data
schema = {'geometry': 'Point', 'properties': {'name': 'str'}}
with fiona.open('output.geojson', 'w', driver='GeoJSON',
                schema=schema, crs='EPSG:4326') as dst:
    dst.write({
        'geometry': {'type': 'Point', 'coordinates': [0, 0]},
        'properties': {'name': 'Origin'}
    })
Raw lines 65 to 92
93## Shapely
95Shapely provides geometric operations.
97```python
98from shapely.geometry import Point, LineString, Polygon
99from shapely.ops import unary_union
101# Create geometries
102point = Point(0, 0)
103line = LineString([(0, 0), (1, 1)])
104poly = Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
106# Geometric operations
107buffered = point.buffer(1) # Buffer
108simplified = poly.simplify(0.01) # Simplify
109centroid = poly.centroid # Centroid
110intersection = poly1.intersection(poly2) # Intersection
112# Spatial relationships
113point.within(poly) # True if point inside polygon
114poly1.intersects(poly2) # True if geometries intersect
115poly1.contains(poly2) # True if poly2 inside poly1
117# Unary union
118combined = unary_union([poly1, poly2, poly3])
120# Buffer with different joins
121buffer_round = point.buffer(1, quad_segs=16)
122buffer_mitre = point.buffer(1, mitre_limit=1, join_style=2)
123```

Shapely

Shapely provides geometric operations.

from shapely.geometry import Point, LineString, Polygon
from shapely.ops import unary_union

# Create geometries
point = Point(0, 0)
line = LineString([(0, 0), (1, 1)])
poly = Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])

# Geometric operations
buffered = point.buffer(1)              # Buffer
simplified = poly.simplify(0.01)        # Simplify
centroid = poly.centroid                 # Centroid
intersection = poly1.intersection(poly2) # Intersection

# Spatial relationships
point.within(poly)      # True if point inside polygon
poly1.intersects(poly2) # True if geometries intersect
poly1.contains(poly2)   # True if poly2 inside poly1

# Unary union
combined = unary_union([poly1, poly2, poly3])

# Buffer with different joins
buffer_round = point.buffer(1, quad_segs=16)
buffer_mitre = point.buffer(1, mitre_limit=1, join_style=2)
Raw lines 93 to 124
125## PyProj
127PyProj handles coordinate transformations.
129```python
130from pyproj import Transformer, CRS
132# Consistent longitude/latitude order; these points are in UTM zone 10N.
133transformer = Transformer.from_crs('EPSG:4326', 'EPSG:32610', always_xy=True)
134x, y = transformer.transform(-122.4, 37.7, errcheck=True)
135lon, lat = transformer.transform(x, y, direction='INVERSE', errcheck=True)
136x_array, y_array = transformer.transform([-122.4, -122.3], [37.7, 37.8])
137# Height is a third transform argument, not an always_z option. A horizontal-only
138# transform can leave Z unchanged; vertical conversion needs a suitable compound CRS/grid.
140# Get CRS info
141crs = CRS.from_epsg(4326)
142print(crs.name) # WGS 84
143print(crs.axis_info) # Axis info
145# Custom transformation
146transformer = Transformer.from_pipeline(
147 'proj=pipeline step inv proj=utm zone=32 ellps=WGS84 step proj=unitconvert xy_in=rad xy_out=deg'
149```

PyProj

PyProj handles coordinate transformations.

from pyproj import Transformer, CRS

# Consistent longitude/latitude order; these points are in UTM zone 10N.
transformer = Transformer.from_crs('EPSG:4326', 'EPSG:32610', always_xy=True)
x, y = transformer.transform(-122.4, 37.7, errcheck=True)
lon, lat = transformer.transform(x, y, direction='INVERSE', errcheck=True)
x_array, y_array = transformer.transform([-122.4, -122.3], [37.7, 37.8])
# Height is a third transform argument, not an always_z option. A horizontal-only
# transform can leave Z unchanged; vertical conversion needs a suitable compound CRS/grid.

# Get CRS info
crs = CRS.from_epsg(4326)
print(crs.name)  # WGS 84
print(crs.axis_info)  # Axis info

# Custom transformation
transformer = Transformer.from_pipeline(
    'proj=pipeline step inv proj=utm zone=32 ellps=WGS84 step proj=unitconvert xy_in=rad xy_out=deg'
)
Raw lines 125 to 150
151## GeoPandas
153GeoPandas combines pandas with geospatial capabilities.
155```python
156import geopandas as gpd
158# Reading data
159gdf = gpd.read_file('data.geojson')
160gdf = gpd.read_file('data.shp', encoding='utf-8')
161gdf = gpd.read_postgis('SELECT * FROM data', con=engine)
163# Writing data
164gdf.to_file('output.geojson', driver='GeoJSON')
165gdf.to_file('output.gpkg', layer='data', use_arrow=True)
167# CRS operations
168gdf.crs # Get CRS
169if gdf.crs is None:
170 raise ValueError('Confirm the source CRS from authoritative metadata before transforming')
171gdf = gdf.to_crs('EPSG:32633') # Example target: use only within zone 33N
172# set_crs labels existing coordinates without transforming them. Use it only when
173# the missing source CRS is independently known, before calling to_crs.
175# Geometric operations
176gdf['area'] = gdf.geometry.area
177gdf['length'] = gdf.geometry.length
178gdf['buffer'] = gdf.geometry.buffer(100)
179gdf['centroid'] = gdf.geometry.centroid
181# Spatial joins
182joined = gpd.sjoin(gdf1, gdf2, how='inner', predicate='intersects')
183joined = gpd.sjoin_nearest(gdf1, gdf2, max_distance=1000)
185# Overlay operations
186intersection = gpd.overlay(gdf1, gdf2, how='intersection')
187union = gpd.overlay(gdf1, gdf2, how='union')
188difference = gpd.overlay(gdf1, gdf2, how='difference')
190# Dissolve
191dissolved = gdf.dissolve(by='region', aggfunc='sum')
193# Clipping
194clipped = gpd.clip(gdf, mask_gdf)
196# Spatial indexing (for performance)
197idx = gdf.sindex
198possible_matches = idx.intersection(polygon.bounds)
199```

GeoPandas

GeoPandas combines pandas with geospatial capabilities.

import geopandas as gpd

# Reading data
gdf = gpd.read_file('data.geojson')
gdf = gpd.read_file('data.shp', encoding='utf-8')
gdf = gpd.read_postgis('SELECT * FROM data', con=engine)

# Writing data
gdf.to_file('output.geojson', driver='GeoJSON')
gdf.to_file('output.gpkg', layer='data', use_arrow=True)

# CRS operations
gdf.crs  # Get CRS
if gdf.crs is None:
    raise ValueError('Confirm the source CRS from authoritative metadata before transforming')
gdf = gdf.to_crs('EPSG:32633')  # Example target: use only within zone 33N
# set_crs labels existing coordinates without transforming them. Use it only when
# the missing source CRS is independently known, before calling to_crs.

# Geometric operations
gdf['area'] = gdf.geometry.area
gdf['length'] = gdf.geometry.length
gdf['buffer'] = gdf.geometry.buffer(100)
gdf['centroid'] = gdf.geometry.centroid

# Spatial joins
joined = gpd.sjoin(gdf1, gdf2, how='inner', predicate='intersects')
joined = gpd.sjoin_nearest(gdf1, gdf2, max_distance=1000)

# Overlay operations
intersection = gpd.overlay(gdf1, gdf2, how='intersection')
union = gpd.overlay(gdf1, gdf2, how='union')
difference = gpd.overlay(gdf1, gdf2, how='difference')

# Dissolve
dissolved = gdf.dissolve(by='region', aggfunc='sum')

# Clipping
clipped = gpd.clip(gdf, mask_gdf)

# Spatial indexing (for performance)
idx = gdf.sindex
possible_matches = idx.intersection(polygon.bounds)
Raw lines 151 to 200
201## Common Workflows

Common Workflows

Raw lines 201 to 202
203### Batch Reprojection
205```python
206import geopandas as gpd
207from pathlib import Path
209input_dir = Path('input')
210output_dir = Path('output')
211output_dir.mkdir(parents=True, exist_ok=True)
213for shp in input_dir.glob('*.shp'):
214 gdf = gpd.read_file(shp)
215 gdf = gdf.to_crs('EPSG:32633')
216 gdf.to_file(output_dir / shp.name)
217```

Batch Reprojection

import geopandas as gpd
from pathlib import Path

input_dir = Path('input')
output_dir = Path('output')
output_dir.mkdir(parents=True, exist_ok=True)

for shp in input_dir.glob('*.shp'):
    gdf = gpd.read_file(shp)
    gdf = gdf.to_crs('EPSG:32633')
    gdf.to_file(output_dir / shp.name)
Raw lines 203 to 218
219### Raster to Vector Conversion
221```python
222import rasterio.features
223import geopandas as gpd
224from shapely.geometry import shape
226with rasterio.open('raster.tif') as src:
227 image = src.read(1)
228 valid = src.read_masks(1) > 0
229 crs = src.crs
230 results = (
231 {'properties': {'value': v}, 'geometry': s}
232 for s, v in rasterio.features.shapes(image, mask=valid, transform=src.transform)
233 )
235geoms = list(results)
236gdf = gpd.GeoDataFrame.from_features(geoms, crs=crs)
237```

Raster to Vector Conversion

import rasterio.features
import geopandas as gpd
from shapely.geometry import shape

with rasterio.open('raster.tif') as src:
    image = src.read(1)
    valid = src.read_masks(1) > 0
    crs = src.crs
    results = (
        {'properties': {'value': v}, 'geometry': s}
        for s, v in rasterio.features.shapes(image, mask=valid, transform=src.transform)
    )

geoms = list(results)
gdf = gpd.GeoDataFrame.from_features(geoms, crs=crs)
Raw lines 219 to 238
239### Vector to Raster Conversion
241```python
242from rasterio.features import rasterize
243import geopandas as gpd
245gdf = gpd.read_file('polygons.gpkg')
246shapes = ((geom, 1) for geom in gdf.geometry)
248raster = rasterize(
249 shapes,
250 out_shape=(height, width),
251 transform=transform,
252 fill=0,
253 dtype=np.uint8
255```

Vector to Raster Conversion

from rasterio.features import rasterize
import geopandas as gpd

gdf = gpd.read_file('polygons.gpkg')
shapes = ((geom, 1) for geom in gdf.geometry)

raster = rasterize(
    shapes,
    out_shape=(height, width),
    transform=transform,
    fill=0,
    dtype=np.uint8
)
Raw lines 239 to 256
257### Combining Multiple Rasters
259```python
260import rasterio.merge
261import rasterio as rio
262from contextlib import ExitStack
264files = ['tile1.tif', 'tile2.tif', 'tile3.tif']
265with ExitStack() as stack:
266 datasets = [stack.enter_context(rio.open(f)) for f in files]
267 # Verify matching CRS, band count/type and compatible grids before merging.
268 merged, transform = rasterio.merge.merge(datasets)
269 profile = datasets[0].profile.copy()
270 profile.update(transform=transform, height=merged.shape[1], width=merged.shape[2])
271 with rio.open('merged.tif', 'w', **profile) as dst:
272 dst.write(merged)
273```
275For more detailed examples, see [code-examples.md](code-examples.md).
277Sources: [Rasterio masks](https://rasterio.readthedocs.io/en/stable/topics/masks.html), [PyProj Transformer](https://pyproj4.github.io/pyproj/stable/api/transformer.html), [GeoPandas API](https://geopandas.org/en/stable/docs/reference.html).

Combining Multiple Rasters

import rasterio.merge
import rasterio as rio
from contextlib import ExitStack

files = ['tile1.tif', 'tile2.tif', 'tile3.tif']
with ExitStack() as stack:
    datasets = [stack.enter_context(rio.open(f)) for f in files]
    # Verify matching CRS, band count/type and compatible grids before merging.
    merged, transform = rasterio.merge.merge(datasets)
    profile = datasets[0].profile.copy()
    profile.update(transform=transform, height=merged.shape[1], width=merged.shape[2])
    with rio.open('merged.tif', 'w', **profile) as dst:
        dst.write(merged)

For more detailed examples, see code-examples.md.

Sources: Rasterio masks, PyProj Transformer, GeoPandas API.

Raw lines 257 to 277