Greece places: settlements + islands (the archipelago requirement) — #44/#171 #190

Merged
art merged 3 commits from gr-places into main 2026-08-20 16:04:06 +00:00
Owner

Reference: the places row of the Greece catalogue (docs/country-gr.md, "Places"), under the #44/#171 pilot. No dedicated issue — this is the catalogue row's "worth it" verdict delivered, plus the user requirement that Greece's ISLANDS be labeled with high priority (no previous country has an archipelago; Κρήτη must be among the last labels standing at z4–z6).

What lands

Greece's partition of the shared places contract (stop_z 4, maxz 14, filter_only, attrs geom/name/importance/rank, rank mvt_excluded): 13 547 settlements + 30 island labels, one total rank ladder, per-zoom label budget, 7 acceptance tests on the layer. The style needs no change — importance is the style's own ladder.

Sources (both official, non-OSM, already in this config's pipeline)

  • Settlements: ΥΠΕΝ oikismoi (ELSTAT census settlement set, 13 548 points, kalcode/oikismos/top_koin) joined to ELSTAT's census populations on the 10-digit Kallikratis code.
  • Islands: the Kallikratis administrative division — perifereiakes_enotites (75 regional units) + kallikratikoi_dimoi (325 municipalities) + perifereies (13 regions), all from the same ΥΠΕΝ WFS workspace.

The 2011-vs-2021 population decision

The join uses ELSTAT's 2011-codification file ("Απογραφές πληθυσμού 1991,2001,2011" — the catalogue's SAM03 row), 100% join coverage (13 547/13 547). The 2021 file (MON_PLI_DHM_OIKISN_2021.xlsx) uses the post-2019 municipal codification: it joins only 89.2% of the codes and would silently demote real towns (Αίγινα 6 867, Σιάτιστα 5 490, Σπέτσαι 4 001) to importance 1 — rejected. The 2011 file is binary .xls; the obvious Go library (extrame/xls) misparses its shared-string table (measured: every string cell after the first ~8 kB comes back empty), so the importer gains a minimal BIFF8 reader (importer/xls.go, on extrame/ole2 + x/text's cp1253), tested against a checked-in xlwt fixture and the real file (20 991 rows, 13 554 level-8 rows, Greek strings intact). The staging machinery gains a tabular (no-geometry) table path.

The island mechanism

Geometric, over the admin division: clip every regional unit to the national land outline, then a unit whose land shares no boundary ≥ 1 km with any other unit's land is an island unit (the band is empty: 25 units at 0 m, the smallest real land border is Άγιο Όρος's 8.9 km). The land outline must be the GeoJSON vintage of perifereies — the GML one the mask uses shares no linework with the GeoJSON units (measured). Εύβοια is added explicitly (only land connection: the ~40 m Chalkida bridge; its unit shares the gulf-midline sea boundary with Βοιωτία, which the source does not distinguish from land). The two group-named units (Σποράδες, Κέας-Κύθνου) label their island municipalities (Σκιάθος — the user's required below-unit island — Σκόπελος, Αλόννησος, Κέα, Κύθνος). Κρήτη comes from the region-level test (sea-separated region whose largest landmass holds ≥ 90% of its land — only Περιφέρεια Κρήτης qualifies at 99.5%). Result: 30 island labels at the centroid of each unit's largest landmass, names via a documented genitive→nominative mapping of the ALL-CAPS administrative forms (ΡΟΔΟΥ → Ρόδος; Θήρα is the official name of Σαντορίνη, Κεφαλονιά the common form of Κεφαλληνία). Islands below the unit level (Σκύρος, Παξοί, Σαλαμίνα, Ίος…) are out of scope — their towns are labeled by the settlements feed. The acceptance tests pin the exact 30-name set and the 15 required islands' ranks.

The rank ladder (the deliberate choice)

One total order: ROW_NUMBER() OVER (ORDER BY importance DESC, size DESC, name); settlements' size = 2011 population, islands' size = area km² × 70 (a round constant just below the ~82 residents/km² average density, chosen so no island outranks the capital — at the true density Κρήτη would beat Αθήναι and break R85). The ladder reads Αθήναι (1), Κρήτη (2), Θεσσαλονίκη (3), Εύβοια (4), then cities and islands interleaved by size. Importance: 8 cities ≥ 100k / 136 towns ≥ 10k / villages; the requirement's 15 islands at 3, the rest at 2.

The budget choice

X = 65% instead of the 60% default, argued in the config comment: at 60% the z5 cap floors to 1 label over Greece's 132 029 km² (the R34 land reference) and the country view would show only the capital; 65% is the smallest round value whose caps admit Κρήτη at z5 (N = 2) and both big islands at z6 (N = 8). Caps: z4 ≤ 1, z5 ≤ 2, z6 ≤ 8, z7 ≤ 33, z8 ≤ 132, z9 ≤ 531, z10 ≤ 2127, z11 ≤ 8510, z12 ≤ 34040 — all recomputed from the formula, no literal overrides (R33/R84). z4 shows Αθήναι alone (the capital is the last name standing, R85); z5 shows Αθήναι + Κρήτη; z6 adds Θεσσαλονίκη, Εύβοια, Πάτραι, Πειραιεύς, Λάρισα, Ηράκλειον; z7 admits all 15 required islands (Σκιάθος is rank 23).

Verification (live DB)

  • importer acceptance --acceptance all conf/gr: 45/45 passed (7 new layer tests incl. the island requirements + the shared R85/R83 cross-country tests with 5 countries); conf/fi acceptance 49/49 (no regressions).
  • importer tiles --check conf/gr: 32/32 PASS (places tiles carry exactly name, importance, country — rank mvt-excluded).
  • Per-zoom: z4=1, z5=2, z6=8, z7=33, z8=132, z9=531, z10=2127, z11=8510, z12=13577 (whole register), z13=13577, z14=13577; 0 NULL names.
  • Real-browser renders (scripts/cdp-map-check.mjs, queryRenderedFeatures on place_names): z5 whole Greece = Αθήναι + Κρήτη; z6 = Αθήναι, Κρήτη, Θεσσαλονίκη, Εύβοια, Πάτραι, Λάρισα (+Πειραιεύς/Ηράκλειον in the tile, MapLibre-collided at this zoom); Aegean view z7 = Μύκονος, Νάξος, Θήρα, Ρόδος, Σάμος, Χίος + the big two; Crete view z7 = Κρήτη, z9 = Κρήτη + Ηράκλειον + towns. Screenshots: /tmp/gr_z5.png, /tmp/gr_aegean_z7.png, /tmp/gr_crete_z7.png, /tmp/gr_crete_z9.png.

Stacked on the unmerged sibling PR #189 (gr-roads-rail-waterways) — do not merge independently.

Reference: the `places` row of the Greece catalogue (docs/country-gr.md, "Places"), under the #44/#171 pilot. No dedicated issue — this is the catalogue row's "worth it" verdict delivered, plus the user requirement that Greece's ISLANDS be labeled with high priority (no previous country has an archipelago; Κρήτη must be among the last labels standing at z4–z6). ## What lands Greece's partition of the shared `places` contract (stop_z 4, maxz 14, filter_only, attrs geom/name/importance/rank, rank mvt_excluded): **13 547 settlements + 30 island labels**, one total rank ladder, per-zoom label budget, 7 acceptance tests on the layer. The style needs no change — importance is the style's own ladder. ## Sources (both official, non-OSM, already in this config's pipeline) - **Settlements**: ΥΠΕΝ `oikismoi` (ELSTAT census settlement set, 13 548 points, `kalcode`/`oikismos`/`top_koin`) joined to ELSTAT's census populations on the 10-digit Kallikratis code. - **Islands**: the Kallikratis administrative division — `perifereiakes_enotites` (75 regional units) + `kallikratikoi_dimoi` (325 municipalities) + `perifereies` (13 regions), all from the same ΥΠΕΝ WFS workspace. ## The 2011-vs-2021 population decision The join uses ELSTAT's **2011-codification** file ("Απογραφές πληθυσμού 1991,2001,2011" — the catalogue's SAM03 row), **100% join coverage (13 547/13 547)**. The 2021 file (MON_PLI_DHM_OIKISN_2021.xlsx) uses the post-2019 municipal codification: it joins only 89.2% of the codes and would silently demote real towns (Αίγινα 6 867, Σιάτιστα 5 490, Σπέτσαι 4 001) to importance 1 — rejected. The 2011 file is binary .xls; the obvious Go library (extrame/xls) misparses its shared-string table (measured: every string cell after the first ~8 kB comes back empty), so the importer gains a minimal BIFF8 reader (importer/xls.go, on extrame/ole2 + x/text's cp1253), tested against a checked-in xlwt fixture and the real file (20 991 rows, 13 554 level-8 rows, Greek strings intact). The staging machinery gains a tabular (no-geometry) table path. ## The island mechanism Geometric, over the admin division: clip every regional unit to the national land outline, then a unit whose land shares no boundary ≥ 1 km with any other unit's land is an island unit (the band is empty: 25 units at 0 m, the smallest real land border is Άγιο Όρος's 8.9 km). The land outline must be the **GeoJSON** vintage of `perifereies` — the GML one the mask uses shares no linework with the GeoJSON units (measured). Εύβοια is added explicitly (only land connection: the ~40 m Chalkida bridge; its unit shares the gulf-midline sea boundary with Βοιωτία, which the source does not distinguish from land). The two group-named units (Σποράδες, Κέας-Κύθνου) label their island municipalities (Σκιάθος — the user's required below-unit island — Σκόπελος, Αλόννησος, Κέα, Κύθνος). Κρήτη comes from the region-level test (sea-separated region whose largest landmass holds ≥ 90% of its land — only Περιφέρεια Κρήτης qualifies at 99.5%). Result: **30 island labels** at the centroid of each unit's largest landmass, names via a documented genitive→nominative mapping of the ALL-CAPS administrative forms (ΡΟΔΟΥ → Ρόδος; Θήρα is the official name of Σαντορίνη, Κεφαλονιά the common form of Κεφαλληνία). Islands below the unit level (Σκύρος, Παξοί, Σαλαμίνα, Ίος…) are out of scope — their towns are labeled by the settlements feed. The acceptance tests pin the exact 30-name set and the 15 required islands' ranks. ## The rank ladder (the deliberate choice) One total order: `ROW_NUMBER() OVER (ORDER BY importance DESC, size DESC, name)`; settlements' size = 2011 population, islands' size = area km² × 70 (a round constant just below the ~82 residents/km² average density, chosen so no island outranks the capital — at the true density Κρήτη would beat Αθήναι and break R85). The ladder reads **Αθήναι (1), Κρήτη (2), Θεσσαλονίκη (3), Εύβοια (4)**, then cities and islands interleaved by size. Importance: 8 cities ≥ 100k / 136 towns ≥ 10k / villages; the requirement's 15 islands at 3, the rest at 2. ## The budget choice **X = 65%** instead of the 60% default, argued in the config comment: at 60% the z5 cap floors to 1 label over Greece's 132 029 km² (the R34 land reference) and the country view would show only the capital; 65% is the smallest round value whose caps admit Κρήτη at z5 (N = 2) and both big islands at z6 (N = 8). Caps: z4 ≤ 1, z5 ≤ 2, z6 ≤ 8, z7 ≤ 33, z8 ≤ 132, z9 ≤ 531, z10 ≤ 2127, z11 ≤ 8510, z12 ≤ 34040 — all recomputed from the formula, no literal overrides (R33/R84). z4 shows Αθήναι alone (the capital is the last name standing, R85); z5 shows Αθήναι + Κρήτη; z6 adds Θεσσαλονίκη, Εύβοια, Πάτραι, Πειραιεύς, Λάρισα, Ηράκλειον; z7 admits all 15 required islands (Σκιάθος is rank 23). ## Verification (live DB) - `importer acceptance --acceptance all conf/gr`: **45/45 passed** (7 new layer tests incl. the island requirements + the shared R85/R83 cross-country tests with 5 countries); `conf/fi` acceptance 49/49 (no regressions). - `importer tiles --check conf/gr`: **32/32 PASS** (places tiles carry exactly `name`, `importance`, `country` — `rank` mvt-excluded). - Per-zoom: z4=1, z5=2, z6=8, z7=33, z8=132, z9=531, z10=2127, z11=8510, z12=13577 (whole register), z13=13577, z14=13577; 0 NULL names. - Real-browser renders (scripts/cdp-map-check.mjs, queryRenderedFeatures on place_names): z5 whole Greece = **Αθήναι + Κρήτη**; z6 = Αθήναι, Κρήτη, Θεσσαλονίκη, Εύβοια, Πάτραι, Λάρισα (+Πειραιεύς/Ηράκλειον in the tile, MapLibre-collided at this zoom); Aegean view z7 = Μύκονος, Νάξος, Θήρα, Ρόδος, Σάμος, Χίος + the big two; Crete view z7 = Κρήτη, z9 = Κρήτη + Ηράκλειον + towns. Screenshots: /tmp/gr_z5.png, /tmp/gr_aegean_z7.png, /tmp/gr_crete_z7.png, /tmp/gr_crete_z9.png. Stacked on the unmerged sibling PR #189 (gr-roads-rail-waterways) — do not merge independently.
Three fetch/load gaps that Greece's transport and water layers hit, filled as
one importer change so the conf/gr batch can land on top:

1. GeoJSON staging loader (importer/geojson.go): FeatureCollection → staging
   table, all six geometry types, properties as text columns. The ΥΠΕΝ floods
   WFS serves GeoJSON in EPSG:2100 with no crs member, so source_srid is a
   config decision like GML's; GAIAOSE's ArcGIS f=geojson is WGS84.

2. PolyLine support in the shapefile loader: the loader read Point and Polygon
   only, and TEN-T roads ship as a PolyLine shapefile. One part → LINESTRING,
   several → MULTILINESTRING (the roads layer ST_Dumps down to the contract's
   LineString); PolyLineZ/M share the Parts/Points layout.

3. The `arcgis:` provider (importer/arcgis.go): a paged ArcGIS REST
   FeatureServer query, the fetch shape the downloads: mechanism cannot
   express (one URL → one file) — GAIAOSE's Railnet is maxRecordCount 2000, so
   a full layer is read by resultOffset pages and concatenated into one
   GeoJSON file per order. Anonymous like downloads:, so no secrets block; an
   existing file is reused and --refetch forces a re-query (no freshness
   header exists to compare). Wired into the config merge, the import fetch
   phase, and the fetch/list subcommands + their usage docs.

Verification: `go build`, `go test` (importer), `make test`; the Greece import
exercised all three paths live — 8 865 rail + 443 station features via arcgis,
16 GeoJSON WFS files, and the TEN-T shapefile. The old reader's "lines not
read" comment is gone because it is no longer true.
Greece's transport and water layers, landing the three backlog issues that
corrected the #171 catalogue's ABSENT verdicts:

- waterways (03-waterways.yaml, #182): floods_rev1 publishes the dense unnamed
  hydro network (el##_hntw) in 10 of 14 river basin districts and only the
  named main-river network (el##_hntz) in EL06/07/11/14 — the #182 check found
  the hntz layers in the same workspace, and they coincide with hntw within
  ~100 m where both exist, so the four districts are filled with the named
  tier instead of being left empty (the "looks like an import bug" hole #182
  exists to prevent). Strahler over the union, rooted at the coast (gr_coast,
  derived from the land outline with the MakeValid-then-Union the mask query
  uses — the plain union fails with a GEOS side-location conflict, the #14
  landmine), water_table gr_lwb. Names: hntz carries name_el, hntw joins the
  nearest river water body (gr_rwb, wb_name_el). The district-set acceptance
  test enumerates all 14 per-district staging tables and the R46/R47 histogram
  is Horton (1:6373 > 2:2543 > 3:1195 > 4:380 > 5:90).

- roads (04-roads.yaml, #180): the TEN-T tier (Egnatia Odos, CC BY 4.0, 4 853
  km national incl. Crete). The quick check of options 2/3 found no national
  ΔΜΕΟ file on data.europa.eu (only municipal/regional clips) and no uniform
  region-geoserver coverage, so option 1 is the layer; options 2/3 stay the
  documented upgrade path. Core → motorway, Core-to-be-upgraded → trunk,
  Comprehensive (± to be upgraded) → primary, New Construction filtered; no
  names/refs (dissolved delivery), which the shared R25 test now exempts with
  a written note (the exemption expires with the source).

- railways + stations (05-/06-railways|stations.yaml, #179): GAIAOSE Railnet
  via the new arcgis: provider. rail_class from the source's own M/B taxonomy
  (main/branch; no siding in the source — Finland's mirror case), in_use = 0
  (disused) → siding so abandoned track draws only from z11, electrified NULL
  (no such attribute; a 0/1 would lie about a partly-electrified network).
  Stations: 436 named, importance cut from GAIAOSE's own 0/1/3/4 code onto the
  shared ladder (0 → 3: the three unranked termini; 3/4 → 2; 1 → 1), names as
  delivered (ALL CAPS Greek — initcap would strip the tonos accents).

- The R2 licence question (#179's open item) is unchanged: GAIAOSE asserts no
  licence; the layers are built and verified, the go-live call waits on a
  written answer, the CC-BY-3.0 geodata.gov.gr copy is the documented fallback.

- Greece is now selectable in the map UI (html/index.html MAPS row +
  conf/mapsrv.yaml maps: entry — the documented "adding a country" recipe).

Verification (all against the live database): importer config-check; the full
gr import built every layer; 38/38 gr acceptance tests pass (make acceptance
and --acceptance all), including the new R11/R182 district set, R46/R47
histogram, R137 class-vocabulary tests and the R75 station-ladder test;
make acceptance-all is green except the pre-existing PL buildings R120
marginal-timeout (open issue); importer tiles --check passes decoded tiles for
all four layers; real-browser CDP renders (scripts/cdp-map-check.mjs) show
roads/railways/waterways/stations at z6–z11 with legible Greek station labels;
row counts per zoom, NULLs and partitions verified per layer (R119 tests).
Greece's partition of the shared `places` contract: the ELSTAT census
settlement set joined to the census populations, plus — the requirement
this layer exists for — the islands, labeled with high priority. No
previous country has an archipelago; this is the first.

SETTLEMENTS. ΥΠΕΝ `oikismoi` (13 548 points, `kalcode` + `oikismos` +
`top_koin`) joined to ELSTAT's 2011-codification census populations on the
10-digit Kallikratis code: 13 547/13 547 codes joined (100%). The 2011
file beats the 2021 one (MON_PLI_DHM_OIKISN_2021.xlsx), which uses the
post-2019 municipal codification, joins only 89.2% of the codes and would
demote real towns (Αίγινα 6 867, Σιάτιστα 5 490, Σπέτσαι 4 001) to
importance 1. Names keep the source's formal census spellings (Αθήναι,
Ηράκλειον); the article suffix is stripped (Αετός,ο → Αετός).

THE XLS LOADER. The 2011 file is legacy binary .xls, and the obvious Go
library (extrame/xls) misparses it: its SST/CONTINUE handling reads a
continued string's option byte as a length, so every string cell after the
first ~8 kB of the shared-string table comes back EMPTY (measured: level
and population numbers read, codes and names did not). The OLE2 layer by
the same author (extrame/ole2) is sound, so importer/xls.go reimplements
only the BIFF8 record layer, for the shapes the file actually uses (SST
with split strings, LABELSST, RK/MULRK, NUMBER/LABEL/RSTRING, cp1253 +
UTF-16LE strings). Tested against the checked-in xlwt fixture AND the real
file (20 991 rows, 13 554 level-8 rows, Greek strings intact). The staging
machinery gains a tabular (no-geometry) path — the population table has no
geom column — and the previous agent's unused .xlsx loader is dropped.

ISLANDS. The mechanism is geometric over the Kallikratis administrative
division (the same ΥΠΕΝ WFS workspace): clip every regional unit to the
national land outline (the MakeValid-then-Union of `perifereies` — the
#14 landmine applies), then a unit whose land shares no boundary ≥ 1 km
with any other unit's land is an island unit. The threshold band is empty
(25 units at 0 m; the smallest real land border is Άγιο Όρος's 8.9 km).
The land outline must be the GeoJSON vintage of `perifereies`
(gr_perifereies_land), not the GML one the mask uses: the two deliveries
are different digitizations and only the GeoJSON one shares linework with
the GeoJSON units (measured: with the GML mask every island unit reports
50+ km of "shared" coastline, with the GeoJSON mask 0 m). Εύβοια is the
one island the test cannot see (its only land connection is the ~40 m
Chalkida bridge, but its unit shares the gulf-midline SEA boundary with
Βοιωτία, which the source does not distinguish from land) and is added
explicitly; the two group-named units (Σποράδες, Κέας-Κύθνου) label their
island municipalities (Σκιάθος, Σκόπελος, Αλόννησος, Κέα, Κύθνος — the
user's required Σκιάθος lives here; Σκύρος is administratively in ΠΕ
Εύβοιας and is below the unit-level scope); Κρήτη is four land-adjacent
units, so it comes from the region-level test (no land boundary AND a
single landmass ≥ 90% of the region's land — only Περιφέρεια Κρήτης
qualifies at 99.5%, the next is 42%). 30 island labels, names from a
documented genitive→nominative mapping of the ALL-CAPS administrative
forms (ΡΟΔΟΥ → Ρόδος; Θήρα is the official name of Σαντορίνη), each at
the centroid of the unit's largest landmass, area in EPSG:2100 (R88).

RANK — the deliberate decision. One total order over both feeds:
ROW_NUMBER() OVER (ORDER BY importance DESC, size DESC, name), where a
settlement's size is its 2011 population and an island's size is its area
in km² × 70 — a round constant just below the country's ~82 residents/km²
average density, chosen so no island outranks the capital (at the true
density Κρήτη would score 680k and beat Αθήναι's 664k, breaking R85).
The ladder reads: Αθήναι (1), Κρήτη (2), Θεσσαλονίκη (3), Εύβοια (4),
then the remaining cities and islands interleaved by size. Importance:
settlements ≥ 100 000 (8 cities) / ≥ 10 000 (136 towns) / below; islands
at the requirement's list of 15 (3) and the remaining 15 (2).

BUDGET. X = 65% instead of the 60% default, argued: at 60% the z5 cap
floors to 1 label (land 132 029 km² — the R34 reference) and the country
view would show only the capital; 65% is the smallest round value whose
caps admit Κρήτη at z5 (N = 2) and both big islands at z6 (N = 8):
z4 ≤ 1, z5 ≤ 2, z6 ≤ 8, z7 ≤ 33, z8 ≤ 132, z9 ≤ 531, z10 ≤ 2127,
z11 ≤ 8510, z12 ≤ 34040. z4 shows Αθήναι alone (the capital is the last
name standing, R85); z5 shows Αθήναι + Κρήτη; z6 adds Θεσσαλονίκη,
Εύβοια, Πάτραι, Πειραιεύς, Λάρισα, Ηράκλειον; z7 admits all 15 required
islands (Σκιάθος is rank 23).

VERIFICATION (live DB, 2026-08-21):
- gr acceptance: 45/45 with --acceptance all (incl. shared places R85/R83
  across all 5 countries); fi acceptance 49/49; tiles --check 32/32.
- per-zoom: z4=1, z5=2, z6=8, z7=33, z8=132, z9=531, z10=2127, z11=8510,
  z12=13577 (the whole register), z13=13577, z14=13577; 0 NULL names.
- real-browser renders (cdp-map-check): z5 whole Greece shows Αθήναι +
  Κρήτη; z6 shows both big islands; the Aegean view at z7 shows Μύκονος,
  Νάξος, Θήρα, Ρόδος, Σάμος, Χίος; the Crete view shows Κρήτη at z7 and
  Κρήτη + Ηράκλειον + the towns at z9. No style change needed (importance
  is the style's own ladder).
Review fixes to !190. Each one is a case where the comment claimed something
the code did not do, or a claim that no test would have caught.

THREE ISLAND LABELS WERE IN THE SEA. The anchor is the centroid of the
island's largest landmass — and a centroid is not inside a landmass wrapped
around a gulf. Λέσβος's fell 2 287 m out into Κόλπος Καλλονής, Μήλος's
1 059 m into its horseshoe bay, Ιθάκη's 192 m into the isthmus bay, and the
label was drawn on open water (seen at z9 over Lesbos in a real browser,
scripts/cdp-map-check.mjs). Those three fall back to ST_PointOnSurface,
which is inside the polygon by construction; the other 27 keep the centroid,
which is the better-centred point where it is on land. The new acceptance
test asserts no island label is in the sea — and it is islands-only on
purpose: 11 census settlements are surveyed on islets too small to appear in
the 1:50k regions outline (Χονδρονήσι 20 m out, Μάκαρες 8.9 km), which is
source coverage, not a misplaced label.

BIFF8 8-BIT STRINGS ARE LATIN-1, NOT A CODEPAGE. The loader decoded them as
cp1253 and its comment said that was "the workbook's declared codepage". The
ELSTAT file's CODEPAGE record actually says 1200 — Unicode — and per MS-XLS
a compressed string is one byte per character holding the LOW half of each
UTF-16 code unit, which is Latin-1; Greek cannot be stored that way at all.
Measured on the real file: 15 908 UTF-16 strings, every Greek name among
them, and 20 977 compressed ones, all pure ASCII — so nothing this file
loads changes. What changes is the next .xls source: a compressed "Malmö"
came back "Malmφ". golang.org/x/text goes back to being an indirect
dependency.

THE ISLAND NAME MAPPINGS FAIL CLOSED. `ELSE lektiko` put the ALL-CAPS
administrative genitive ("ΠΕΡΙΦΕΡΕΙΑΚΗ ΕΝΟΤΗΤΑ ΡΟΔΟΥ") on the map as a label
for any name the mapping was not written against. The ELSE is gone, so such
a name lands NULL and fires the no-blank-names test, and a second new test
asks the SOURCE whether the 23 unit names, the three group/Εύβοια codes and
the five municipality codes are still its own vocabulary — the only place
that question can be answered. The dead ΠΕ ΕΥΒΟΙΑΣ arm is gone with it:
Εύβοια never reaches island_units, it comes from the `evia` branch.

R88 IN THE ISLAND TEST. The 1 km shared-boundary threshold was a raw 3857
length, ~31% long at Greek latitudes, so it was really testing ~760 m while
the areas beside it were correctly measured in EPSG:2100. It is transformed
now. Nothing moves: the band is still empty (25 units at 0 m, the smallest
real land border Άγιο Όρος's 8.9 km), which the same 30 island labels
confirm.

THE TABULAR FLAG IS THE LOADER'S STATEMENT, not an observation about rows.
staging.go inferred "no geometry" from "no row carried one", so a geometry
source that delivered zero rows would have silently built a table with no
geom column and no gist index, and a glob mixing the two kinds would have
dropped the geometry of the file that had it. readXLSTable sets
loadedTable.tabular; a mixed glob is now an error naming the odd file.

sortInts was an insertion sort over Go map-iteration order, with a comment
claiming the rows were "already ascending" — `for r := range byRow` is
randomised, so it ran ~1.1e8 operations on the 21 k-row ELSTAT sheet.
sort.Ints: the real-file test drops from 0.19 s to 0.08 s.

REQUIREMENTS. docs/country-requirements.md now records what this layer does
differently, in the shape R82 already used for Iceland: R82/R87 gain
Greece's population exception (oikismoi carries no print-size attribute at
all, and the census count is the source's own column joined on the
settlement's own code), R86 gains the island labels (a geometric test over
the administrative division, a pinned set of 30 names — not a toponym class
admitted wholesale), R85 the island interleave, R84 the per-country X and
why Greece's is 65%.

NITS. BOUNDSHEET is parsed as the BoundSheet8 it is (cch(1) + flags(1) +
name at 8, verified against the file's own bytes) instead of guessing
between two layouts with a one-byte read either way, and it now reads a
UTF-16 sheet name instead of failing the whole load. The SST reader treats a
record running out as the CONTINUE it is, in the one case it used to call an
error (a string header flush against the boundary), and says so plainly if a
UTF-16 character is split across records. MULRK's loop stops before the
trailing lastcol field. parseXLSCell loses its dead MULRK branch,
xlsxColName is xlsColName, and the comments no longer refer to an .xlsx
loader that never existed in this repo. xls_test.go is gofmt-clean and its
formatRK table no longer carries an arithmetic claim that was not true.
New unit tests cover the SST record layer directly — both encodings, a
split string, an encoding switch mid-string, the boundary header, rich-text
rejection — so the part extrame/xls gets wrong is covered where data/ is
absent too.

VERIFICATION (live DB, after `import --layer places conf/gr`):
- gr acceptance 47/47 with --acceptance all (45 + the two new tests), fi
  49/49, tiles --check 32/32, go vet and go test clean.
- Λέσβος/Μήλος/Ιθάκη are inside the land outline now; re-rendered z9 Lesbos
  and z11 Milos in Chrome and the labels read on land.
- Unchanged by all of this: the 30 island names, rank ladder (Αθήναι 1,
  Κρήτη 2, Θεσσαλονίκη 3, Εύβοια 4, Σκιάθος 23), importance tiers
  (23/151/13403) and per-zoom counts (z4=1 … z12–z14=13577).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UGAZTzU6FGJSQKj437rr3L
art merged commit f18ef00d10 into main 2026-08-20 16:04:06 +00:00
art deleted branch gr-places 2026-08-20 16:04:06 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
art/ismap!190
No description provided.