[ s ] ai-skill-library hub.krojer.net
Authentik: LocalAdmin
← Zurück zur Übersicht

duckdb-spatial-ops

own v1.0.0
Pfad: skills/own/duckdb-spatial-ops  •  Clients: codex claude-code gemini-cli
🛡️ Sicherheitsaudit: Freigegeben
Gate: PASSED | Scanner: skillspector 2.12.0 | Netzwerk: none | LLM: False
Geprüft & Freigegeben
Vollständige Anweisungen (SKILL.md)
---
name: duckdb-spatial-ops
description: Fast geospatial data processing, GeoParquet conversion, and analytical queries using DuckDB Spatial.
---

# DuckDB Spatial Operations

Fast in-process spatial SQL engine for local GIS data processing, GeoParquet conversion, analytical queries, and PostGIS data pipelines.

## When to Use

- High-speed local processing of large vector GIS datasets (GeoJSON, Shapefile, FlatGeobuf, GeoParquet) without spinning up a heavy server database.
- Converting multi-gigabyte spatial files into cloud-optimized GeoParquet.
- Executing spatial analytics (joins, distance, buffers, bounding box clipping) on millions of features in seconds.
- Staging and preprocessing raw GIS data before importing into a production PostGIS instance.
- Direct spatial queries across remote files (S3, HTTP, Parquet) with predicate and projection pushdown.

---

## 1. Quick Start & Setup

### In Python

```bash
uv pip install duckdb geopandas pyarrow
```

```python
import duckdb

con = duckdb.connect()
# Install and load spatial extension
con.execute("INSTALL spatial;")
con.execute("LOAD spatial;")
```

### In DuckDB CLI

```sql
INSTALL spatial;
LOAD spatial;
```

---

## 2. Reading Vector Formats

DuckDB Spatial uses `ST_Read` (wrapping GDAL drivers) or native Parquet readers:

```sql
-- Read GeoJSON
CREATE TABLE buildings AS
SELECT * FROM ST_Read('data/buildings.geojson');

-- Read Shapefile
CREATE TABLE parcels AS
SELECT * FROM ST_Read('data/parcels.shp');

-- Read FlatGeobuf (extremely fast binary spatial format)
CREATE TABLE hiking_trails AS
SELECT * FROM ST_Read('data/trails.fgb');

-- Read GeoParquet (fastest columnar format, native multithreaded)
CREATE TABLE austria_parcels AS
SELECT * FROM read_parquet('data/parcels.parquet');
```

---

## 3. High-Performance GeoParquet Conversion

Convert huge, slow GeoJSON or Shapefiles into modern, compressed GeoParquet:

```sql
COPY (
    SELECT 
        id,
        name,
        category,
        ST_AsWKB(geom) AS geometry
    FROM ST_Read('large_dataset.geojson')
) TO 'output.parquet' (FORMAT PARQUET, COMPRESSION ZSTD);
```

Or using native geometry:
```sql
COPY (
    SELECT * FROM ST_Read('raw_data.shp')
) TO 'optimized.parquet' (FORMAT PARQUET);
```

---

## 4. Key Spatial Analytics Patterns

### Spatial Join (Point in Polygon)
```sql
SELECT 
    p.poi_name,
    d.district_name
FROM ST_Read('points_of_interest.geojson') p
JOIN ST_Read('districts.geojson') d
  ON ST_Contains(d.geom, p.geom);
```

### Bounding Box Clipping & Filtering
```sql
SELECT *
FROM read_parquet('austria_buildings.parquet')
WHERE ST_Intersects(
    geom, 
    ST_MakeEnvelope(16.30, 48.18, 16.45, 48.25) -- Vienna bounding box
);
```

### Distance & Buffer Calculations
```sql
SELECT 
    t.trail_name,
    h.hut_name,
    ST_Distance(t.geom, h.geom) AS distance_meters
FROM trails t, huts h
WHERE ST_DWithin(t.geom, h.geom, 500.0);
```

---

## 5. PostGIS <-> DuckDB Pipeline Pattern

DuckDB is **not a replacement** for PostGIS, but its ultimate companion:

```mermaid
flowchart LR
    A["Raw Data (GeoJSON, PBF, Shapefile)"] --> B["DuckDB Spatial (Local ETL & Filtering)"]
    B --> C["Clean GeoParquet / Filtered Tables"]
    C --> D["PostGIS Database (Production Web API & QGIS)"]
```

### Exporting directly from DuckDB to PostGIS (via Python):
```python
import duckdb
import geopandas as gpd
from sqlalchemy import create_engine

con = duckdb.connect()
con.execute("INSTALL spatial; LOAD spatial;")

# Perform fast local aggregation in DuckDB
df = con.execute("""
    SELECT 
        district,
        COUNT(*) as building_count,
        ST_AsText(ST_Union_Agg(geom)) as merged_geom
    FROM read_parquet('large_buildings.parquet')
    GROUP BY district
""").df()

# Convert WKT to geometry and push to PostGIS
df['geom'] = gpd.GeoSeries.from_wkt(df['merged_geom'], crs="EPSG:4326")
gdf = gpd.GeoDataFrame(df.drop(columns=['merged_geom']), geometry='geom')

engine = create_engine("postgresql://postgres:password@192.168.1.183:5432/gisdb")
gdf.to_postgis("district_building_summary", engine, if_exists="replace")
```

---

## 6. Best Practices

- **Memory Limit:** Always specify memory limit on large ETL jobs:
  `SET max_memory = '8GB';`
- **Threads:** DuckDB defaults to all CPU cores:
  `SET threads = 8;`
- **SRID Transformation:** Transform coordinates with `ST_Transform(geom, 'EPSG:4326', 'EPSG:31287')`.
- **Use GeoParquet for Storage:** 10x smaller file sizes and 50x faster read speeds compared to GeoJSON or Shapefiles.