dd-l1-vienna-gpkg-manifest
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:08
Let me start by exploring the GPKG file to understand its contents.
0:08
Bash
ls -la /work/vienna_planning.gpkg
-rw-r--r-- 1 agent agent 524288 Jun 16 22:51 /work/vienna_planning.gpkg
0:19
Let me first explore the GPKG file to see what layers are in it.
0:19
Bash
cd /work && python3 -c "
cd /work && python3 -c "
import sqlite3
conn = sqlite3.connect('vienna_planning.gpkg')
cur = conn.cursor()
cur.execute(\"SELECT table_name, geometry_type_name FROM gpkg_contents LEFT JOIN gpkg_geometry_columns USING (table_name)\")
rows = cur.fetchall()
for r in rows:
print(r)
conn.close()
"
('cafes', 'POINT')
('cycleway_segments', 'LINESTRING')
('districts', 'POLYGON')
('parks', 'MULTIPOLYGON')
('schools', 'POINT')
('supermarkets', 'POINT')
('waterbodies', 'POLYGON')
0:29
I have the layers. Now I need to get the CRS, feature count, and bounding box for each layer. Let me use geopandas to read each layer.
0:29
Bash
cd /work && python3 << 'EOF'
cd /work && python3 << 'EOF'
import geopandas as gpd
import json
gpkg = 'vienna_planning.gpkg'
layers = ['cafes', 'cycleway_segments', 'districts', 'parks', 'schools', 'supermarkets', 'waterbodies']
records = []
for layer in layers:
gdf = gpd.read_file(gpkg, layer=layer)
crs = gdf.crs.to_epsg()
if crs is None:
crs = gdf.crs.srs.split(':')[-1]
crs_str = f"EPSG:{crs}"
else:
crs_str = f"EPSG:{crs}"
geom_type = gdf.geometry.geom_type.iloc[0]
if 'Multi' not in geom_type:
# Some single types might actually be mixed; use iloc[0] as native
pass
feat_count = len(gdf)
bbox = list(gdf.total_bounds) # [xmin, ymin, xmax, ymax]
records.append({
"layer_name": layer,
"crs": crs_str,
"geometry_type": geom_type,
"feature_count": feat_count,
"bbox": bbox
})
with open('/work/manifest.json', 'w') as f:
json.dump(records, f, indent=2)
print(json.dumps(records, indent=2))
EOF
[
{
"layer_name": "cafes",
"crs": "EPSG:31287",
"geometry_type": "Point",
"feature_count": 392,
"bbox": [
623796.529863561,
481107.2540129812,
626399.5618121764,
484498.9209005649
]
},
{
"layer_name": "cycleway_segments",
"crs": "EPSG:31287",
"geometry_type": "LineString",
"feature_count": 271,
"bbox": [
623787.4932131292,
481134.9325878215,
626619.1631922598,
485245.6624853243
]
},
{
"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": "schools",
"crs": "EPSG:31287",
"geometry_type": "Point",
"feature_count": 40,
"bbox": [
624000.002417057,
481214.73871311604,
626259.5421406503,
484178.87865010765
]
},
{
"layer_name": "supermarkets",
"crs": "EPSG:31287",
"geometry_type": "Point",
"feature_count": 87,
"bbox": [
623794.0549950565,
481148.38703723426,
626392.6164786755,
484489.91954147833
]
},
{
"layer_name": "waterbodies",
"crs": "EPSG:31287",
"geometry_type": "Polygon",
"feature_count": 33,
"bbox": [
624288.8521056126,
481236.66320640914,
626747.8611584986,
484132.0212685799
]
}
]
0:34
The script ran successfully and produced the manifest. Let me verify the output file.
0:34
Read
/work/manifest.json
1 [
2 {
3 "layer_name": "cafes",
4 "crs": "EPSG:31287",
5 "geometry_type": "Point",
6 "feature_count": 392,
7 "bbox": [
8 623796.529863561,
9 481107.2540129812,
10 626399.5618121764,
11 484498.9209005649
12 ]
13 },
14 {
15 "layer_name": "cycleway_segments",
16 "crs": "EPSG:31287",
17 "geometry_type": "LineString",
18 "feature_count": 271,
19 "bbox": [
20 623787.4932131292,
21 481134.9325878215,
22 626619.1631922598,
23 485245.6624853243
24 ]
25 },
26 {
27 "layer_name": "districts",
28 "crs": "EPSG:31287",
29 "geometry_type": "Polygon",
30 "feature_count": 22,
31 "bbox": [
32 623867.3888224852,
33 481236.5836125494,
34 626371.3079994549,
35 484403.7315526943
36 ]
37 },
38 {
39 "layer_name": "parks",
40 "crs": "EPSG:31287",
41 "geometry_type": "MultiPolygon",
42 "feature_count": 119,
43 "bbox": [
44 623851.990195953,
45 481200.6674012489,
46 626620.9438865517,
47 485539.3479671855
48 ]
49 },
50 {
51 "layer_name": "schools",
52 "crs": "EPSG:31287",
53 "geometry_type": "Point",
54 "feature_count": 40,
55 "bbox": [
56 624000.002417057,
57 481214.73871311604,
58 626259.5421406503,
59 484178.87865010765
60 ]
61 },
62 {
63 "layer_name": "supermarkets",
64 "crs": "EPSG:31287",
65 "geometry_type": "Point",
66 "feature_count": 87,
67 "bbox": [
68 623794.0549950565,
69 481148.38703723426,
70 626392.6164786755,
71 484489.91954147833
72 ]
73 },
74 {
75 "layer_name": "waterbodies",
76 "crs": "EPSG:31287",
77 "geometry_type": "Polygon",
78 "feature_count": 33,
79 "bbox": [
80 624288.8521056126,
81 481236.66320640914,
82 626747.8611584986,
83 484132.0212685799
84 ]
85 }
86 ]
0:39
The manifest.json is complete and well-formed. Let me verify the requirements: 1. ✅ JSON list of objects — yes 2. ✅ `layer_name` as a string — yes 3. ✅ `crs` as a string in `EPSG:NNNN` form — yes (EPSG:31287) 4. ✅ `geometry_type` matching native type — Point, LineString, Polygon, MultiPolygon 5. ✅ `feature_count` as integer — yes 6. ✅ `bbox` as list `[xmin, ymin, xmax, ymax]` in layer's native CRS — yes All good!
0:39