package-points
package-points produces one label point per admin unit per detected
level, combined into a single output file, for web-map label placement.
It uses the pole of inaccessibility rather than the centroid, since a
centroid can fall outside a concave or multi-part polygon (e.g. a
crescent-shaped or archipelago admin unit), which would place a label off
its own territory.
Usage¶
OUTPUT_FILE (positional, optional) defaults to INPUT_FILE with a
_points suffix.
Run topo-tools package-points --help for the full, always-current option
list.
Pipeline¶
_01_inputs: loads and reprojects the input, same aspackage-polygons._02_points: detects every level (explicit schema or structural auto-detection, same aspackage-polygons), then per level: dissolves viacore.dissolve's_02_dissolve.main()(grouped by that level's code column, every other level's own identity columns excluded outright so no numbered ancestor column ever survives), checks the dissolved row count against the input's own distinct code-column count, renames that level's own identity columns to one name shared across every level (group_families_by_level()'s naming-anchor stripping for auto-detection, or a fixedcode/namefor an explicit schema), excludes any remaining column that can't generalize to a coarser level (an attribute already dropped from a coarser level's own dissolve), computes each dissolved unit'sST_MaximumInscribedCircle(geom).centeras its label point, stamps the depth column, and checks every point isST_Covers-ed by its own source polygon. Every level's points table is combined viaUNION ALL BY NAME, not sequential inserts into a fixed-schema table: a column excluded at a given level is simply absent from that level's own table rather than unioned in as an always-NULLcolumn._03_outputs: exports the combined table directly; no topology gate applies, since this is a derived cartographic layer, not a coverage layer.
Detecting a constant root level (e.g. a single-country file's adm0_*)¶
With auto-detection, detect_root_level() (core.schema_map) looks for an
unassigned column family sharing the detected levels' own naming style
(the same (prefix, anchor, suffix) split _level_anchors() derives for
a real level, so it covers a digit anchor like adm0_* beside adm1_*/
adm2_* just as well as a word anchor like country_* beside state_*/
county_*), constant across the whole table; it's None if no such
unassigned family exists or more than one does, rather than guess. When
found, it's treated as its own level, dissolved with an empty group_by
(the whole table collapses to one row), excluded from every other level's
own dissolve like any other level, and renamed via
group_families_by_level()'s naming-anchor injection into the same
pcode/name columns every other level's own row uses, rather than
surviving under a separate adm0_pcode/adm0_name (or country_code/
country_name) name. The root's own iteration is also excluded from the
coarsest-level generalizable set used to decide what a finer level may
keep, since it has nothing coarser to compare against and would otherwise
wrongly veto attributes real
levels can keep.
Why ST_MaximumInscribedCircle, not the centroid¶
ST_Centroid is a purely geometric average of a polygon's area and can
land outside the polygon itself for a concave shape, or on a body of water
between two islands for a multi-part one. ST_MaximumInscribedCircle's
center is always interior to the polygon (it's the center of the largest
circle that fits inside it), the standard technique web-map label
placement uses to keep a label anchored on its own territory.
Portolan-scale profiling¶
The real target scale for this tool, --debug off, Apple Silicon/10 logical
cores:
| Run | Points out (all levels combined) | Wall time | RSS peak | Result |
|---|---|---|---|---|
Global admin4 (portolan/global/admin4/admin4.parquet, 217,223 rows, 5 detected levels, --depth-column pkg_lvl) |
297,503 (111 + 1,889 + 19,842 + 58,438 + 217,223) | 483s | 8.01 GB | Every point ST_Covers-ed by its own source polygon |
Real-data gotcha: depth_column collision. The portolan catalog's own
global admin files already carry an adm_lvl column of their own (the
source's real-depth stamp); package-points' default depth_column
("adm_lvl") collides with it. _02_points.py raises ValueError on this
collision (depth_column {value!r} already exists on {table!r}) rather
than silently producing a renamed duplicate column the way DuckDB's own
CREATE TABLE AS SELECT does for a repeated column name; pass a
non-colliding --depth-column (e.g. pkg_lvl, as above) against such a
file.