Reading a geometry's Placemark, however cheaply, is work repeated for every geometry, and it makes the Placemark's metadata repeated work too: `extract_metadata` parses the CDATA table in a `<description>` into a hash once per geometry, so a Placemark holding several hundred parts parsed the same description several hundred times.
Instead of walking up from each geometry to its Placemark, `each_record` now starts at each Placemark and reads the geometry below it. The Placemark's metadata, name and image paths are read once and shared by its parts, and no geometry ever asks what it belongs to.
Measured on a 33.8 MB CAD-derived KML holding 76,190 geometries in 1,233 Placemarks, with the per-geometry database round trip stubbed out so the figures cover the traversal alone:
ancestors + metadata per geometry 902.02s (before this branch)
parent walk + metadata per geometry 10.76s (previous commit)
Placemark-first 2.86s
Eliminating the upward search is worth 891s of that and eliminating the repeated metadata 7.9s, so the second is small in absolute terms while still being most of what was left.
Geometry outside any Placemark still imports with no name and no metadata, which iterating Placemarks alone would silently drop. A second pass matches it with one XPath over the document rather than by asking each element for its ancestors. `kmz_file_features_without_placemarks.kmz` covers it, and fails when that pass is removed.
Each part takes its own copy of the Placemark's metadata, since a hash shared between features would be one object behind several records. Image paths are read once per Placemark rather than once per part, because `images_from_metadata` removes the key it reads, so calling it per part would leave every part after the first without images.
Iteration order changes. It was every Polygon in the document, then every LineString, then every Point; it is now each Placemark's geometry together in document order, then any geometry outside a Placemark. Nothing asserts on feature sequence, `features_hash` is an MD5 of the source bytes rather than of what the importer emits, and `mvt_sql` orders by id at query time. Feature ids do land in a different order.
Closes #66
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>