Register an area (maintainers)¶
Mappers need nothing from this page: the iD and JOSM guides and a
report are their whole workflow. This is the other half, which needs a clone
of the repository and its data/ tree: drawing candidates, registering a mapped area,
declaring it complete, and publishing the boundaries the atlas loads.
Draw candidates¶
pixi run calib-candidates -- --aoi zambia --dry-run # report the draw, write nothing
pixi run calib-candidates -- --bronze # every Bronze atlas
pixi run calib-candidates -- --bronze --priority # + two start-here boxes each
pixi run calib-candidates -- --aoi yemen --farmland # farm boxes, stratified by cropland
pixi run calib-publish # put them on the atlases
--farmland draws 3 km farm areas for the
farm ground-PV detector, stratified by cropland rather than
buildings. Register a mapped one with --farm added to the command below: its name then
carries _farm_calib_. Both kinds of area feed both detectors (since 2026-10-25): a farm
area calibrates the farm detector and the rooftop classifier, a building area calibrates the
rooftop classifier and trains the farm detector. An area whose imagery is too poor to
certify completeness is listed corroborated, not rule1, and is left out of
calibration with explicit --quadrat flags.
scripts/propose_calibration_candidates.py cuts a country's density grid into eight bands
of equal building share, draws one cell per band at random, places a box on the buildings
so its own density matches its band, and drops any box whose Esri imagery is too old for
the country's composite. With --priority it instead places two 1 km start-here boxes
in the densest quarter of the country's buildings, where the roof area in buildings of
1,000 m2 or more is largest, through the same imagery gate. The details, and what
it does not do, are in
Automated quadrat selection.
On an atlas where either small-installation instrument is unmeasured, the rooftop
classifier or the farm ground-PV detector, calib-publish also ranks the candidates and
draws the first as a star. That is every Bronze atlas, and a Gold or Silver one whose page
says one of the two is not measured; it reads that from the page itself, as the page does.
Start-here boxes come first, then the rest from the densest down, farm boxes last; where only
farm ground PV is unmeasured (Pakistan), the farm boxes come first, by cropland share. The
star moves to the next one by itself once that area is registered. To choose the star by
hand, on any atlas, name the candidate in the country's config as start_here: <stem>
(configs/atlas/<aoi>.yaml); calib-publish then ranks that country's candidates with it
first, and fails on a name that is neither a candidate nor a registered area.
calib-publish writes the public per-area GeoJSON under
docs/assets/labels/calibration_areas/ that the atlas fetches at run time, so new
candidates need no atlas rebuild. Re-run it after anything below changes an area.
Register a mapped area¶
A mapper's report names the area. Run the command from its atlas popup (Register it),
which carries the right --aoi, the box and the candidate's imagery date. Check the dry run
first:
python scripts/new_calibration_quadrat.py --aoi zambia --dry-run \
--name chama_s1 --lat -11.539319 --lon 33.208160 --side-m 2000 \
--imagery-layer "Esri World Imagery (Vivid)" --imagery-date 2024-09-13
or, for a boundary a mapper drew in JOSM (File -> Save As -> GeoJSON of a layer holding only the boundary; see Draw your own area). It arrives pasted into the report's Boundary GeoJSON field: save that block to a file, check the dry run, and reply on the report before the mapper starts, so a rejected outline costs no mapping:
python scripts/new_calibration_quadrat.py --aoi zambia --dry-run \
--name chama_north --geojson ~/drawn/chama_north.geojson \
--imagery-layer "Esri World Imagery" --imagery-date 2025-03
--dry-run prints the geometry (area, bounding box, whether it fits one 2.24 km training
chip) and the overlap check against every registered area of that country, and writes
nothing. It refuses an overlap unless --allow-overlap is passed, and refuses a box with no
imagery date unless --imagery-unchecked is. Pakistan's areas live flat in data/labels/;
every other country's in data/labels/<aoi>/, so they never join a Pakistani refit.
Drop --dry-run to write the boundary and pull the OpenStreetMap solar inside it. This
writes <stem>_boundary.geojson (+ .parquet) and <stem>_overpass_solar.parquet, and
prints a ground-truth profile: size distribution against the 400 m2 floor,
rooftop/ground split and roofclf.packing_density. A registered box is not complete
because this ran: it holds whatever OpenStreetMap had at that moment.
A box reported not mappable is recorded instead, so nobody re-draws it:
python scripts/new_calibration_quadrat.py --aoi zambia --name chama_s1 \
--lat -11.539319 --lon 33.208160 --side-m 2000 \
--imagery-layer "Esri World Imagery" --imagery-date 2019-11 \
--reject "imagery predates the composite by six years"
Declare it complete¶
After the second pass, record next to the boundary: mapper names, completion date, the
imagery layer and its capture date, installation count, and how many the second pass added.
That is what turns a registered box into a Rule-1 area ready for the next roofclf
refit. roofclf.discover_quadrats picks up any boundary + mapped-solar pair automatically,
but nothing folds an area into a fit without a human completeness declaration first (see the
random-cell validation note for why this
matters as much on the deployment side). Then list it in configs/atlas/<aoi>.yaml
(calibration_boxes, with its status) and re-run calib-publish; the next atlas rebuild
draws it as a filled diamond.
Pakistan: the density gap¶
A quadrat's own building density decides which national cells it can calibrate. As of
2026-08-20, the sparsest quadrat actually feeding roofclf's coverage-ratio/area-recall
correction sits at 124 buildings/km2, but 13.5% of the buildings that correction
is applied to nationally sit sparser than that (median around 89 buildings/km2).
That gap used to be far worse: as measured 2026-08-16, the sparsest supporting quadrat was
872 buildings/km2 and 84% of buildings sat below it, pricing 54% of the whole
published Best estimate with a correction fit on much denser ground than it was applied to.
See Calibration density mismatch.
Pakistan's eight open candidates target exactly that gap. They were picked by hand before the sampler existed (Box 19 in the calibration boxes log): each is the centroid of a ~100 km2 grid cell whose average density falls in the gap, a coarse regional screen rather than a verified settlement. Box 18's quadrats moved from source-grid densities of 549/250/150 buildings/km2 to own-measured densities of 895/237/146 once mapped; expect something similar.
| candidate (boundary) | province | district | source-grid density (bldg/km2) |
|---|---|---|---|
rajanpur_gap_calib_2km |
Punjab | Rajanpur | 54.4 |
kasur_gap_calib_2km |
Punjab | Kasur | 124.4 |
khairpur_gap_calib_2km |
Sindh | Khairpur | 55.3 |
jacobabad_gap_calib_2km |
Sindh | Jacobabad | 107.9 |
deraismailkhan_gap_calib_2km |
Khyber Pakhtunkhwa | Dera Ismail Khan | 63.2 |
haripur_gap_calib_2km |
Khyber Pakhtunkhwa | Haripur | 102.1 |
pishin_gap_calib_2km |
Balochistan | Pishin | 53.8 |
mastung_gap_calib_2km |
Balochistan | Mastung | 118.8 |
Once a quadrat is Rule-1 and its density sits inside the gap (or genuinely below the
current calibrated domain floor, 48.5 buildings/km2: a different, harder thing to
move safely, because a quadrat only lowers that floor if its own measured density is below
it; see Calibration quadrats before
attempting it), it enters the next earthpv roof-classifier refit, is rescored nationally,
and is folded into the next evidence-atlas rebuild. The most recent example of that cycle,
three quadrats declared Rule-1 and moving the published headline number the same day, is
Box 18.