Digital Garden
The Changzhou Satellite-Imagery City: One Data Expedition and Three Delivery Keys
When both “photogrammetry” and “manual modeling” are closed off, how can a city still approach reality? The Changzhou answer: swap every data source for high-precision network data that can be obtained, and let the Agent run the fully automated pipeline. Six routes, walked in one day.
I. The fork in the road
The story begins with that constraint: “we can only go through network data or finding the corresponding data, no photography, no manual landmarks — so which route should we take? Only AI and agents.” The counselor first drew the ceiling: without photogrammetry you never get true facade textures; the reachable limit is “precise outlines + true heights + roofs with orthophoto textures + facades procedural to the point of passing as real at mid-far range” — the Google Earth 3D white-model-plus-imagery effect. Then six routes were ranked: Overture building heights, Esri/Tianditu satellite imagery, Beike community floor-count backfill, Amap POI auto-labeling, Mapillary street-level facades, and academic height datasets for cross-validation. One command from the user: start on routes 1 through 6, until all are done.
II. The setback of route 1 and the advance of route 2
Route 1 (heights) was blocked by the network environment, honestly recorded: the OpenSSL handshake of Overture DuckDB/libcurl was intercepted by the transparent proxy (curl schannel is fine, but 258 GB of parquet cannot be fully downloaded); Google Open Buildings could not locate the Changzhou file by S2 shard; the GHSL JRC server was unreachable. Building heights remain OSM tags plus heuristic estimation — this is the model’s biggest precision shortcoming, and the report did not whitewash it.
Route 2 (satellite imagery) was the highest-yield battle: Esri World Imagery tiles downloaded in batches — the original 18,303-tile scale would take 85 minutes at 3.6 tiles/second, so it was changed to core-area z15 plus outskirts z14 (total down to about 4,400) with concurrency raised to 32, speeding to 30 tiles/second. The transparent proxy’s trick of randomly dropping requests was also routed around: Python urllib’s SSL was broken by the proxy while curl was fine — all switched to curl subprocesses. Finally 10,381 tiles (z14 whole city 93% + z15 core 94%), mosaicked into 16 chunks of 4096², the terrain fully replaced with real satellite imagery, missing areas filled with neighborhood means.
Route 2c (roof color) was a surprise: all 36,375 buildings individually sampled from satellite imagery for roof color, 100% coverage, quantized and merged into about 200 materials — new residential white roofs, industrial blue roofs, old-town red tiles automatically distinguished, and a city’s economic geography became readable at a glance.
III. Routes 4/5/6: the facade revolution
Route 4 (programmatic facades): two per-vertex attributes were added to the building meshes — wall local height wz and wall horizontal coordinate wu — and the material shader generates window grids and floor slabs from them: residential 3.2 m floor height / 4 m bay, glass curtain 3.6 m / 3.2 m, industrial 4.5 m / 8 m sparse windows, with window roughness/metallic changing in step; flat roofs above 16 m get 1.1 m parapets. The street-level view shows complete window-grid facades — this is the leap from “white model” to “architecture.”
Route 5 (landmark refinement): the Tianning Pagoda was rebuilt — octagonal thirteen stories, upturned eaves (corners kicking up 1.3 m, the most distinctive feature of Chinese pagodas), the xianglun spire with a golden orb.
Route 6 (POI labels): 52 curated labels extracted from OSM (6 districts + dinosaur park / museum / theater / olympic / canal / taihu / railway station), filtering neighboring-province city remnants and village-level labels, in an independent 09_Labels collection rendered only in top-down view — a fixed font size cannot serve both 5 km and 55 km view heights; this is a trade-off on record.
Engineering optimizations also deserve mention: the **kwargs dict passing in MeshBuilder.v() was the source of a 50× slowdown; after optimization the build takes only 26 seconds; 633 materials were too many, so the roof-color quantization step was coarsened to 0.1.
IV. Three delivery keys
A 71 MB blend is unintuitive for non-Blender users, so three keys were made:
- Browser viewer: changzhou_web.glb (Draco plus meshopt compressed from 267 MB to 80 MB) + a three.js drag-and-drop viewer. The debugging process itself was a detective story: the page loaded the model but showed a blank screen — successively ruled out rAF background-tab throttling (manual rendering succeeded, 235 draw calls, 3 million triangles), the camera near=5/far=2,000,000 depth-buffer precision catastrophe (about 2 km per LSB at 22 km, fixed to near=50/far=500000), and the final culprit was a 500 km backdrop whose depth difference from the city was smaller than one depth-buffer precision unit, the city entirely covered by the backdrop — fixed by excluding the backdrop with use_selection on export, and the picture was perfect.
- Flythrough video: 22 seconds, 528 frames, 1280×720 — high-altitude dive → urban traversal → pagoda orbit → pull-up finale. Blender 5.2 pitfalls surfaced along the way: the Action API changed (fcurves moved to layers/channelbags), FFMPEG removed from image_settings (using media_type=‘VIDEO’ plus ffmpeg.format/codec).
- Blender roaming: viewport clip distance preset to 1000 km (the most common “zoom out and everything disappears” pitfall of large-scale models, saved into the file), fly mode WASD+QE, 10 preset camera positions, 09_Labels self-toggleable.
The final changzhou.blend: 71 MB, 3.08 million vertices, 1.92 million faces, about 200 materials, 10 camera positions. The script pipeline is fully preserved (terrain.py → extract.py → esri_download.py → build_esri_mosaic.py → blender_build.py); change any parameter and re-run in full.
Real coordinates
- Route 1 (Overture) blocked, honestly recorded: DuckDB/libcurl OpenSSL intercepted by the transparent proxy, 258 GB cannot be fully downloaded, Google Open Buildings S2 shards unlocatable, GHSL unreachable; heights remain OSM tags + heuristics
- Route 2: 10,381 Esri tiles (z14 whole city 93% + z15 core 94%) → 16 chunks of 4096²; Python urllib SSL broken by the proxy, all switched to curl subprocesses; missing areas filled with neighborhood means; Web Mercator not orthorectified (slight tall-building displacement, recorded)
- Route 2c: 36,375 buildings roof-color 100% sampled, quantized to about 200 materials (new residential white / industrial blue / old-town red automatically distinguished)
- Route 4: wz/wu per-vertex attributes driving window grids (residential 3.2 m/4 m, curtain 3.6 m/3.2 m, industrial 4.5 m/8 m) + 1.1 m parapets above 16 m; MeshBuilder kwargs 50× slowdown optimized (26 s build)
- Route 5: Tianning Pagoda octagonal thirteen stories + upturned eaves (1.3 m kick) + xianglun spire + golden orb; Route 6: 52 curated POIs (6 districts + landmarks), 09_Labels top-down only
- Three delivery keys: browser viewer (GLB 80 MB Draco; depth precision catastrophe near=5/far=2M→50/500k; 500 km backdrop occlusion culprit; use_selection exclusion), flythrough video 22 s/528 frames, Blender roaming (viewport clip 1000 km, 10 camera positions); Blender 5.2 APIs: Action layers/channelbags, FFMPEG media_type=‘VIDEO’, MixRGB named interfaces
- Final: changzhou.blend 71 MB / 3.08M vertices / 1.92M faces / about 200 materials / 10 camera positions; five-script pipeline fully re-runnable
Comments