dd-l1-vienna-gpkg-manifest

Status: done
Score: 1.000
Duration: 0:38
Cost: 0.19¢
Model: deepseek/deepseek-v4-flash

Map (reference ← swipe → agent)

0:00
Inherited a GPKG (vienna_planning) from a colleague who retired last week. Before I commit to scripting against it I need a one-page manifest so I can see what's in there and decide which layers actually feed next month's councillor briefing. Catalog the contents and give me one record per layer. Output manifest.json — a JSON list of these records. Each record in the list must be a JSON object with exactly five keys: `layer_name` as a string, `crs` as a string in `EPSG:NNNN` form, `geometry_type` as a string matching the layer's native type (e.g. `Point`, `LineString`, `Polygon`, `MultiPolygon`), `feature_count` as an integer, and `bbox` as a list of four numbers `[xmin, ymin, xmax, ymax]` in the layer's native CRS.
0:00
System info
{
  "adapter": "openrouter",
  "model": "deepseek/deepseek-v4-flash",
  "base_url": "https://openrouter.ai/api/v1"
}
0:12
Let me first explore the working directory to find the GPKG file.
0:12
Bash ls /work/
vienna_planning.gpkg
0:14
Let me explore the GPKG file to understand its structure.
0:14
Bash python3 -c "
python3 -c "
import geopandas as gpd
import sqlite3

conn = sqlite3.connect('/work/vienna_planning.gpkg')
tables = conn.execute(\"SELECT table_name, geometry_type_name FROM gpkg_contents\").fetchall()
for t in tables:
    print(t)
"
Traceback (most recent call last):
  File "<string>", line 6, in <module>
    tables = conn.execute("SELECT table_name, geometry_type_name FROM gpkg_contents").fetchall()
             ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
sqlite3.OperationalError: no such column: geometry_type_name
0:18
Let me look at the available columns in gpkg_contents and also check gpkg_geometry_columns.
0:18
Bash python3 -c "
python3 -c "
import sqlite3
conn = sqlite3.connect('/work/vienna_planning.gpkg')
# List tables
tables = conn.execute(\"SELECT name FROM sqlite_master WHERE type='table'\").fetchall()
print('Tables:', tables)

# gpkg_contents columns
cols = conn.execute('PRAGMA table_info(gpkg_contents)').fetchall()
print('gpkg_contents columns:', cols)

# gpkg_geometry_columns
cols2 = conn.execute('PRAGMA table_info(gpkg_geometry_columns)').fetchall()
print('gpkg_geometry_columns columns:', cols2)
"
Tables: [('gpkg_spatial_ref_sys',), ('gpkg_contents',), ('gpkg_ogr_contents',), ('gpkg_geometry_columns',), ('gpkg_tile_matrix_set',), ('gpkg_tile_matrix',), ('districts',), ('sqlite_sequence',), ('gpkg_extensions',), ('rtree_districts_geom',), ('rtree_districts_geom_rowid',), ('rtree_districts_geom_node',), ('rtree_districts_geom_parent',), ('parks',), ('rtree_parks_geom',), ('rtree_parks_geom_rowid',), ('rtree_parks_geom_node',), ('rtree_parks_geom_parent',), ('waterbodies',), ('rtree_waterbodies_geom',), ('rtree_waterbodies_geom_rowid',), ('rtree_waterbodies_geom_node',), ('rtree_waterbodies_geom_parent',), ('schools',), ('rtree_schools_geom',), ('rtree_schools_geom_rowid',), ('rtree_schools_geom_node',), ('rtree_schools_geom_parent',), ('cafes',), ('rtree_cafes_geom',), ('rtree_cafes_geom_rowid',), ('rtree_cafes_geom_node',), ('rtree_cafes_geom_parent',), ('supermarkets',), ('rtree_supermarkets_geom',), ('rtree_supermarkets_geom_rowid',), ('rtree_supermarkets_geom_node',), ('rtree_supermarkets_geom_parent',), ('cycleway_segments',), ('rtree_cycleway_segments_geom',), ('rtree_cycleway_segments_geom_rowid',), ('rtree_cycleway_segments_geom_node',), ('rtree_cycleway_segments_geom_parent',)]
gpkg_contents columns: [(0, 'table_name', 'TEXT', 1, None, 1), (1, 'data_type', 'TEXT', 1, None, 0), (2, 'identifier', 'TEXT', 0, None, 0), (3, 'description', 'TEXT', 0, "''", 0), (4, 'last_change', 'DATETIME', 1, "strftime('%Y-%m-%dT%H:%M:%fZ','now')", 0), (5, 'min_x', 'DOUBLE', 0, None, 0), (6, 'min_y', 'DOUBLE', 0, None, 0), (7, 'max_x', 'DOUBLE', 0, None, 0), (8, 'max_y', 'DOUBLE', 0, None, 0), (9, 'srs_id', 'INTEGER', 0, None, 0)]
gpkg_geometry_columns columns: [(0, 'table_name', 'TEXT', 1, None, 1), (1, 'column_name', 'TEXT', 1, None, 2), (2, 'geometry_type_name', 'TEXT', 1, None, 0), (3, 'srs_id', 'INTEGER', 1, None, 0), (4, 'z', 'TINYINT', 1, None, 0), (5, 'm', 'TINYINT', 1, None, 0)]
0:21
The data layers (not metadata tables) are: districts, parks, waterbodies, schools, cafes, supermarkets, cycleway_segments.

Let me now extract the info for each layer.
0:21
Bash python3 -c "
python3 -c "
import sqlite3
conn = sqlite3.connect('/work/vienna_planning.gpkg')

# Get contents with bounding box and srs
contents = conn.execute('''
  SELECT c.table_name, c.min_x, c.min_y, c.max_x, c.max_y, c.srs_id,
         g.geometry_type_name, gc.organization_coordsys_id
  FROM gpkg_contents c
  JOIN gpkg_geometry_columns g ON c.table_name = g.table_name
  JOIN gpkg_spatial_ref_sys gc ON c.srs_id = gc.srs_id
''').fetchall()

for r in contents:
    print(r)
"
('districts', 623867.3888224852, 481236.5836125494, 626371.3079994549, 484403.7315526943, 31287, 'POLYGON', 31287)
('parks', 623851.990195953, 481200.6674012489, 626620.9438865517, 485539.3479671855, 31287, 'MULTIPOLYGON', 31287)
('waterbodies', 624288.8521056126, 481236.6632064091, 626747.8611584986, 484132.0212685799, 31287, 'POLYGON', 31287)
('schools', 624000.002417057, 481214.738713116, 626259.5421406503, 484178.8786501077, 31287, 'POINT', 31287)
('cafes', 623796.529863561, 481107.2540129812, 626399.5618121764, 484498.9209005649, 31287, 'POINT', 31287)
('supermarkets', 623794.0549950565, 481148.3870372343, 626392.6164786755, 484489.9195414783, 31287, 'POINT', 31287)
('cycleway_segments', 623787.4932131292, 481134.9325878215, 626619.1631922598, 485245.6624853243, 31287, 'LINESTRING', 31287)
0:26
I have all the info I need. Let me get feature counts and then write the manifest.

The CRS is EPSG:31287 — that's MGI / Austria Lambert (EPSG:31287). Let me verify that by checking the definition.

Actually, the `organization_coordsys_id` from `gpkg_spatial_ref_sys` already tells us it's 31287. So `EPSG:31287` is the CRS string.

Now I need to build the manifest. Let me write a Python script.
0:26
Write /work/build_manifest.py
content · 45 lines · py
import sqlite3
import json

conn = sqlite3.connect('/work/vienna_planning.gpkg')

# Get layer metadata from gpkg_contents + gpkg_geometry_columns + gpkg_spatial_ref_sys
rows = conn.execute('''
  SELECT c.table_name,
         c.min_x, c.min_y, c.max_x, c.max_y,
         c.srs_id,
         g.geometry_type_name,
         s.organization_coordsys_id
  FROM gpkg_contents c
  JOIN gpkg_geometry_columns g ON c.table_name = g.table_name
  JOIN gpkg_spatial_ref_sys s ON c.srs_id = s.srs_id
  WHERE c.data_type = 'features'
''').fetchall()

manifest = []

for r in rows:
    layer_name = r[0]
    min_x, min_y, max_x, max_y = r[1], r[2], r[3], r[4]
    srs_id = r[5]
    geom_type = r[6]
    epsg = r[7]

    # Get feature count from the layer table
    count = conn.execute(f'SELECT COUNT(*) FROM "{layer_name}"').fetchone()[0]

    record = {
        "layer_name": layer_name,
        "crs": f"EPSG:{epsg}",
        "geometry_type": geom_type,
        "feature_count": count,
        "bbox": [min_x, min_y, max_x, max_y]
    }
    manifest.append(record)

conn.close()

with open('/work/manifest.json', 'w') as f:
    json.dump(manifest, f, indent=2)

print(json.dumps(manifest, indent=2))
Wrote /work/build_manifest.py (1185 chars).
0:27
Let me run the script to generate the manifest.
0:27
Bash cd /work && python build_manifest.py
[
  {
    "layer_name": "districts",
    "crs": "EPSG:31287",
    "geometry_type": "POLYGON",
    "feature_count": 22,
    "bbox": [
      623867.3888224852,
      481236.5836125494,
      626371.3079994549,
      484403.7315526943
    ]
  },
  {
    "layer_name": "parks",
    "crs": "EPSG:31287",
    "geometry_type": "MULTIPOLYGON",
    "feature_count": 119,
    "bbox": [
      623851.990195953,
      481200.6674012489,
      626620.9438865517,
      485539.3479671855
    ]
  },
  {
    "layer_name": "waterbodies",
    "crs": "EPSG:31287",
    "geometry_type": "POLYGON",
    "feature_count": 33,
    "bbox": [
      624288.8521056126,
      481236.6632064091,
      626747.8611584986,
      484132.0212685799
    ]
  },
  {
    "layer_name": "schools",
    "crs": "EPSG:31287",
    "geometry_type": "POINT",
    "feature_count": 40,
    "bbox": [
      624000.002417057,
      481214.738713116,
      626259.5421406503,
      484178.8786501077
    ]
  },
  {
    "layer_name": "cafes",
    "crs": "EPSG:31287",
    "geometry_type": "POINT",
    "feature_count": 392,
    "bbox": [
      623796.529863561,
      481107.2540129812,
      626399.5618121764,
      484498.9209005649
    ]
  },
  {
    "layer_name": "supermarkets",
    "crs": "EPSG:31287",
    "geometry_type": "POINT",
    "feature_count": 87,
    "bbox": [
      623794.0549950565,
      481148.3870372343,
      626392.6164786755,
      484489.9195414783
    ]
  },
  {
    "layer_name": "cycleway_segments",
    "crs": "EPSG:31287",
    "geometry_type": "LINESTRING",
    "feature_count": 271,
    "bbox": [
      623787.4932131292,
      481134.9325878215,
      626619.1631922598,
      485245.6624853243
    ]
  }
]
0:30
Let me verify the output file exists and check the content is valid JSON.
0:30
Bash cd /work && python3 -c "import json; d=json.load(open('manifest.json')); print(l...
7 layers
[
  {
    "layer_name": "districts",
    "crs": "EPSG:31287",
    "geometry_type": "POLYGON",
    "feature_count": 22,
    "bbox": [
      623867.3888224852,
      481236.5836125494,
      626371.3079994549,
      484403.7315526943
    ]
  },
  {
    "layer_name": "parks",
    "crs": "EPSG:31287",
    "geometry_type": "MULTIPOLYGON",
    "feature_count": 119,
    "bbox": [
      623851.990195953,
      481200.6674012489,
      626620.9438865517,
      485539.3479671855
    ]
  },
  {
    "layer_name": "waterbodies",
    "crs": "EPSG:31287",
    "geometry_type": "POLYGON",
    "feature_count": 33,
    "bbox": [
      624288.8521056126,
      481236.6632064091,
      626747.8611584986,
      484132.0212685799
    ]
  },
  {
    "layer_name": "schools",
    "crs": "EPSG:31287",
    "geometry_type": "POINT",
    "feature_count": 40,
    "bbox": [
      624000.002417057,
      481214.738713116,
      626259.5421406503,
      484178.8786501077
    ]
  },
  {
    "layer_name": "cafes",
    "crs": "EPSG:31287",
    "geometry_type": "POINT",
    "feature_count": 392,
    "bbox": [
      623796.529863561,
      481107.2540129812,
      626399.5618121764,
      484498.9209005649
    ]
  },
  {
    "layer_name": "supermarkets",
    "crs": "EPSG:31287",
    "geometry_type": "POINT",
    "feature_count": 87,
    "bbox": [
      623794.0549950565,
      481148.3870372343,
      626392.6164786755,
      484489.9195414783
    ]
  },
  {
    "layer_name": "cycleway_segments",
    "crs": "EPSG:31287",
    "geometry_type": "LINESTRING",
    "feature_count": 271,
    "bbox": [
      623787.4932131292,
      481134.9325878215,
      626619.1631922598,
      485245.6624853243
    ]
  }
]
0:37
Let me verify the output against the requirements:

1. `layer_name` - string ✓
2. `crs` - string in `EPSG:NNNN` form ✓ (EPSG:31287)
3. `geometry_type` - string matching native type ✓ (POLYGON, MULTIPOLYGON, POINT, LINESTRING)
4. `feature_count` - integer ✓
5. `bbox` - list of 4 numbers [xmin, ymin, xmax, ymax] in native CRS ✓

The CRS 31287 is MGI / Austria Lambert, which is a metric CRS. The coordinates are in the range ~623k-626k Easting and ~481k-485k Northing, which is totally plausible for Vienna in that CRS.

Let me also verify the file exists.
0:37