Skip to main content

geospatial-ds

Best-practice suggestions for geospatial data science in Python — vector data with GeoPandas/Shapely, raster data with rasterio/rioxarray/xarray, OpenStreetMap via OSMnx, geocoding, H3 spatial indexing, basemaps with contextily, and spatial statistics or regression with PySAL/esda/spreg. Use for shapefiles, GeoJSON, GeoTIFF, GeoPackage, GeoParquet, KML, OSM data, coordinate reference systems, projections, maps, geocoding, hex grids, spatial joins, distance/area measurement, spatial autocorrelation, hotspot detection, or spatial regression. Pair implementation reviews with code-review.

来源信息

仓库
balintdecsi/skills
最近来源活动
2026年10月19日 15:25
检测到的 SKILL.md 语言
英语
星标
0
Forks
0

安装方式

默认使用会先检查来源的 Prompt;你也可以切换为直接命令,或下载本地副本。

检查来源文件

决定是否安装前,请先阅读 SKILL.md,以及 SkillsMP 当前展示的配套文件。

文件资源管理器
9 个文件

正在显示 SKILL.md

SKILL.md
来源说明 · 只读预览
name
geospatial-ds
description
Best-practice suggestions for geospatial data science in Python — vector data with GeoPandas/Shapely, raster data with rasterio/rioxarray/xarray, OpenStreetMap via OSMnx, geocoding, H3 spatial indexing, basemaps with contextily, and spatial statistics or regression with PySAL/esda/spreg. Use for shapefiles, GeoJSON, GeoTIFF, GeoPackage, GeoParquet, KML, OSM data, coordinate reference systems, projections, maps, geocoding, hex grids, spatial joins, distance/area measurement, spatial autocorrelation, hotspot detection, or spatial regression. Pair implementation reviews with code-review.
# Geospatial Data Science Best Practices These are **suggestions**, not absolute rules — pragmatic patterns for geospatial work in Python. Adapt freely to context. The approach here was shaped by the CEU MSBA geospatial data science course taught by [Milán Janosov](https://www.thenewscienceofmaps.com/) ([CEU profile](https://people.ceu.edu/node/2831)) — in particular the vector → raster → OSM progression, projecting before measuring, and H3 for hex aggregation. The course notebooks were shared privately with enrolled students and are **not** redistributed here; all text and code in this skill are written from scratch. Credited as attribution, not endorsement. Topics covered: Shapely geometries → GeoPandas vector data → rasterio/rioxarray rasters → OSMnx + geocoding + H3 spatial indexing. ## Default Stack | Concern | Library | |---|---| | Geometry primitives | `shapely` | | Vector data tables | `geopandas` | | Raster (cell grids) | `rasterio` + `rioxarray` (xarray-aware wrapper) | | N-D scientific arrays | `xarray`, `numpy` | | OpenStreetMap downloads | `osmnx` | | Geocoding (address ↔ point) | `geopy` (Nominatim, etc.) | | Hex spatial index | `h3` (Uber's H3) | | Basemaps (web tiles) | `contextily` | | Big-point density rendering | `datashader` | | Spatial weights / Moran's I | `libpysal`, `esda` | | Spatial regression | `spreg` | | Sample datasets | `geodatasets` | | Output formats | GeoParquet (default), GeoJSON, Shapefile (legacy) | Use the project's existing package manager. For a greenfield project, declare dependencies in `pyproject.toml` and commit `uv.lock` for reproducibility. Get approval before adding packages. Add `geodatasets` with `uv add geodatasets` only after approval and only when the project uses the sample-data examples; production workflows do not need it. ## Core Principles 1. **Always know your CRS.** Every GeoDataFrame has one; mixing them silently is the most common bug. Reproject explicitly with `to_crs(epsg=...)` before any distance/area calculation. 2. **Use a *projected* CRS for distances/areas, a geographic CRS only for storage / lat-lon math.** Web Mercator (`EPSG:3857`) is fine for visual map overlays but distorts areas — use a local equal-area projection (e.g. ETRS89-LAEA `EPSG:3035` for Europe, an appropriate UTM zone, or country-specific) for measurements. 3. **Prefer GeoParquet over Shapefile** for new files. Shapefile is legacy: 10-character column names, no proper Unicode, multi-file payload. 4. **Cache OSM and geocoder results to disk.** Both Nominatim and Overpass throttle. `osmnx` already caches by default — leave it on. 5. **Validate geometries early.** `gdf.is_valid.all()` should be `True`. Fix invalid ones with `geometry.buffer(0)` or `make_valid` before joins. 6. **Spatial joins inflate rows** the same way relational joins do — check row counts before/after. 7. **For aggregating events to areas, prefer H3 hex grids** over arbitrary admin polygons when the question is "where is the density?". ## Coordinate Reference Systems — the must-knows | EPSG | Name | When | |---|---|---| | `4326` | WGS 84 lat/lon | Default for most input data, GeoJSON, GPS | | `3857` | Web Mercator | Web tiles / contextily basemaps, **not for areas** | | `3035` | ETRS89-LAEA Europe | Equal-area projection across Europe | | `32633`, `32634`, … | UTM zones | Local distance/area work in metres | | Country-specific | e.g. `23700` (HD72/EOV Hungary) | National grids when stakeholders expect them | ```python gdf = gdf.set_crs(4326) # only when CRS is missing and you KNOW it's lat/lon gdf = gdf.to_crs(epsg=3035) # reproject for area/distance in metres ``` If `gdf.crs is None`, do **not** call `.to_crs(...)` — call `set_crs(...)` first with the *known* source CRS. ## Vector Workflow Cheatsheet ```python import geopandas as gpd import shapely gdf = gpd.read_file("data/ne_110m_admin_0_countries") # shapefile, GeoJSON, GeoPackage gdf = gpd.read_parquet("data/cities.parquet") # GeoParquet — preferred gdf.crs # what CRS am I in? gdf = gdf.to_crs(epsg=3035) # reproject gdf["area_km2"] = gdf.geometry.area / 1e6 # only meaningful in projected CRS gdf["centroid"] = gdf.geometry.centroid # Spatial join: point-in-polygon points_with_country = gpd.sjoin(points, countries, how="left", predicate="within") # Nearest-neighbour join (GeoPandas ≥ 0.10): nearest = gpd.sjoin_nearest(points, lines, distance_col="dist_m") # Write gdf.to_parquet("output/cities.parquet") ``` For a fuller template see [snippets/vector_workflow.py](snippets/vector_workflow.py). ## Raster Workflow Cheatsheet `rioxarray` gives an xarray-flavoured API on top of `rasterio` — use it by default. ```python import rioxarray as rxr raster = rxr.open_rasterio("data/GHS_POP_E2030_GLOBE_R2023A_4326_30ss_V1_0.tif", masked=True) raster = raster.rio.reproject("EPSG:3035") # Clip to a polygon clipped = raster.rio.clip(admin_gdf.geometry, admin_gdf.crs) # Zonal stats: total population per polygon clipped.sum().item() # Resample to coarser resolution coarse = raster.rio.reproject(raster.rio.crs, resolution=1000) # 1km cells ``` For batch zonal-stats (many polygons against one raster), see [snippets/zonal_stats.py](snippets/zonal_stats.py). ## OpenStreetMap with OSMnx `osmnx` calls Overpass and Nominatim; both throttle, so let it cache. ```python import osmnx as ox # Boundary by place name budapest_1 = ox.geocode_to_gdf("Budapest 1st district, Hungary") # Points of interest inside that polygon (uses Map Features tags) cafes = ox.features_from_polygon( budapest_1.geometry.iloc[0], tags={"amenity": "cafe"}, ) # Drivable street network G = ox.graph_from_place("Budapest, Hungary", network_type="drive") ``` OSM tag reference: <https://wiki.openstreetmap.org/wiki/Map_features>. The `osmnx` user guide is the second most useful link: <https://osmnx.readthedocs.io>. ## H3 Hex Grids (when you need an aggregation grid) ```python import h3 from shapely.geometry import Polygon # Point → cell cell = h3.latlng_to_cell(lat, lng, res=8) # res 8 ≈ 0.7 km² hex # Cell → polygon (lat,lng order from H3 → swap to lng,lat for Shapely) poly = Polygon([(lng, lat) for lat, lng in h3.cell_to_boundary(cell)]) # Cover a polygon with hexes at a resolution hex_ids = h3.geo_to_cells(admin_geojson, res=8) ``` Resolution guide: 8–11 for urban analytics, 5–7 for regional, 3–4 for global. See <https://h3geo.org/docs/core-library/restable/>. ## Geocoding (address ↔ coordinates) Nominatim is free, unauthenticated, and **rate-limited to ~1 req/sec**. For >100 addresses, batch with rate limiting and cache aggressively. Addresses are sent to an external service: explain the transfer and get approval before geocoding private, unpublished, or personal address data with a new provider. ```python from geopy.geocoders import Nominatim from geopy.extra.rate_limiter import RateLimiter geocoder = Nominatim(user_agent="my-project-name") # set a real user agent geocode = RateLimiter(geocoder.geocode, min_delay_seconds=1.1) df["location"] = df["address"].map(geocode) df["lat"] = df["location"].map(lambda p: p.latitude if p else None) df["lng"] = df["location"].map(lambda p: p.longitude if p else None) ``` For high volume / commercial use, switch to a paid provider (Mapbox, Google, HERE) — Nominatim's terms forbid heavy use. ## Maps with Basemaps (contextily) ```python import contextily as ctx import matplotlib.pyplot as plt ax = gdf.to_crs(epsg=3857).plot(figsize=(8, 8), alpha=0.6, edgecolor="black") ctx.add_basemap(ax, source=ctx.providers.CartoDB.Positron) ax.set_axis_off() plt.tight_layout() ``` **Always reproject to `EPSG:3857` before adding a `contextily` basemap** — the tile providers are in Web Mercator. For 100k+ points, use `datashader` instead of matplotlib scatter. ## Spatial Statistics (PySAL family) ```python from libpysal.weights import Queen from esda.moran import Moran, Moran_Local w = Queen.from_dataframe(gdf, use_index=False) w.transform = "r" # row-standardise global_moran = Moran(gdf["my_var"].values, w) print(global_moran.I, global_moran.p_sim) # global spatial autocorrelation local = Moran_Local(gdf["my_var"].values, w) # cluster + outlier per polygon gdf["lm_q"] = local.q # 1=HH, 2=LH, 3=LL, 4=HL gdf["lm_sig"] = (local.p_sim < 0.05).astype(int) ``` For spatial regression (lag / error / SARMA models): `spreg.OLS`, `spreg.ML_Lag`, `spreg.ML_Error`. Residual spatial autocorrelation can invalidate naive uncertainty estimates or indicate omitted spatial structure; choose a spatial model from the data-generating assumptions rather than assuming ordinary OLS is always biased. ## Anti-Patterns to Flag - **Doing distance / area calculations in `EPSG:4326`** (geographic CRS, units are degrees!). - **Plotting GeoDataFrame in lat/lon then calling `ctx.add_basemap`** — basemap and data won't align. - **Spatial-joining without checking row counts** — silent inflation. - **Calling Nominatim in a tight loop** with no rate limiter or cache. - **Reading Shapefiles as the default** for new outputs (write GeoParquet instead). - **Setting CRS with `.set_crs(...)` to "fix" wrong coordinates** — `set_crs` only declares; it does not transform. To transform, use `.to_crs(...)`. - **Buffering in degrees** because the CRS is `4326` — buffer 0.01° is ~1 km at the equator and ~700 m at 45° latitude. Reproject to metres first. - **Using `geopandas.sjoin` with default `predicate="intersects"` when you mean `"within"`** — picks up touching polygons. - **Loading a 10 GB GeoTIFF fully into memory** — use `rioxarray.open_rasterio(..., chunks=True)` (Dask-backed) and clip first. ## Code Snippets In [snippets/](snippets/): - `vector_workflow.py` — load → reproject → spatial-join → write GeoParquet template. - `zonal_stats.py` — population/area/sum-of-raster per polygon, vectorised. - `osm_pois.py` — pull POIs by tag inside an admin boundary, save as GeoParquet. - `h3_aggregation.py` — bin point events to H3 hexes and produce a choropleth. - `geocode_with_cache.py` — rate-limited geocoding with a JSON-on-disk cache. - `moran_local.py` — global + local Moran's I with cluster-map plot. ## Data Sources (free and good for projects) Common open sources for geospatial projects: | Source | What | URL | |---|---|---| | **Natural Earth** | Country / admin / cultural / physical vector layers (110m, 50m, 10m) | <https://www.naturalearthdata.com/> | | **OpenStreetMap (via OSMnx)** | POIs, buildings, road graphs, admin boundaries by name | <https://www.openstreetmap.org/>, <https://osmnx.readthedocs.io/> | | **Nominatim (OSM)** | Free geocoding, ≤1 req/sec | <https://nominatim.openstreetmap.org/> | | **GHSL — Global Human Settlement Layer (JRC)** | Population grids (`GHS_POP_*`), built-up surface, settlement model (e.g. `GHS_POP_E2030_GLOBE_R2023A_4326_30ss_V1_0.tif`) | <https://ghsl.jrc.ec.europa.eu/datasets.php> | | **HydroSHEDS / HydroRIVERS** | Global rivers and watersheds | <https://www.hydrosheds.org/> | | **Vienna Open Data** | `vienna_districts.geojson` and many city-level layers | <https://www.data.gv.at/> | | **Geofabrik** | OSM extracts per country/region as `.osm.pbf` (much faster than Overpass for bulk) | <https://download.geofabrik.de/> | | **Copernicus / Sentinel Hub / Microsoft Planetary Computer** | Free satellite imagery (Sentinel-2 RGB / NDVI / land cover) | <https://www.copernicus.eu/>, <https://planetarycomputer.microsoft.com/> | | **NASA Earthdata** | DEMs (SRTM), MODIS, Landsat | <https://www.earthdata.nasa.gov/> | | **Eurostat GISCO** | NUTS administrative boundaries for EU at multiple resolutions | <https://ec.europa.eu/eurostat/web/gisco> | | **WorldPop** | Population grids per country at ~100 m | <https://www.worldpop.org/> | | **OpenAQ** | Global air-quality measurements (point data) | <https://openaq.org/> | | **geodatasets** | Versioned sample geospatial datasets for quick prototyping | `gpd.read_file(geodatasets.get_path("geoda.natregimes"))` | When pulling new data for a project, default to **GeoParquet** for storage and document the **CRS, source URL, download date, and licence** in a one-line comment next to the load call. ## Further Reference More data-source detail: [reference/data_sources.md](reference/data_sources.md). CRS guidance: [reference/crs_cookbook.md](reference/crs_cookbook.md). External documentation: - [GeoPandas user guide](https://geopandas.org/en/stable/docs.html) - [rioxarray docs](https://corteva.github.io/rioxarray/) - [OSMnx user guide](https://osmnx.readthedocs.io/) - [PySAL ecosystem](https://pysal.org/) - [H3 docs](https://h3geo.org/docs/) - [contextily docs](https://contextily.readthedocs.io/) - File-format comparison: <https://milanjanosov.substack.com/p/geospatial-file-formats> Companion skills: - **`analytics-project-setup`** — folder structure, settings, and AGENTS.md for projects that include geospatial components. - **`ml-modeling`** — for predictive modelling on geospatial features (e.g. nearest-neighbour-derived features, spatial aggregations as ML inputs).
在 GitHub 查看
这个 SKILL.md 很大,SkillsMP 这里只预览前一段内容。 在 GitHub 查看