Skip to content

MapSmith 0.9.0

Choose a tag to compare

@mapsmith-ai mapsmith-ai released this 08 Oct 20:56
· 73 commits to main since this release

Records still declare spec_version 1.0.0-draft.10, and no key is renamed.
Three keys of MapSmith's own are new, each declared with the reason it is not
a synonym of anything the specification has: x-mapsmith:areas_measured_on in
crs_decisions, from apportion_by_area, and x-mapsmith:ballpark_share and
x-mapsmith:outside_area_of_use inside crs_decisions.transformation. Three
check names are new, all from apportion_by_area:
x-mapsmith:extensive_total_preserved, x-mapsmith:every_source_value_placed
and x-mapsmith:no_zone_gives_more_than_it_holds. What
reproject_layer writes changes in two cases
, both described under Fixed:
coordinates outside every published operation's area are no longer moved by
an operation from elsewhere, and a geographic CRS counted from another
meridian whose operations are published only for its Greenwich twin
(EPSG:4806, Monte Mario from Rome) goes through that twin. Shapely 2.1 and
pyarrow 23.0.1 or later are required.

Added

  • apportion_by_area: move counts and rates from the zones they are reported
    in to zones that cross them.
    Cases by municipality to health districts,
    census-tract population to school catchments. Each column is declared
    extensive (a count, split by share of area) or intensive (a rate, averaged by
    area); a call declaring neither is refused, because splitting a rate or
    averaging a count gives a plausible wrong number. Each count comes with
    _min and _max that hold however it is distributed inside the source
    zones; rate bounds rest on the same uniformity the estimate assumes, and the
    record says which is which. Zones are cut in the source zones' own CRS,
    where a boundary both layers share is the same line, and a zone counts as
    wholly inside a target by a topological test, not by a ratio of areas.
    Shares are ratios of areas measured on an equal-area surface -- the source
    CRS when it is one, otherwise a plane centred on the zones on their own
    datum, named in the record -- so the pieces of each zone add up to it.
    Whether targets overlap is decided zone by zone, so a few square metres of
    overlapping boundaries are said in the record where they put a zone's value
    in two targets, and a check fails if a zone with no overlap gives out more
    than it holds. Invalid zones are repaired and the repair recorded; a zone
    the repair leaves with no area or with a coordinate that is not a number,
    zones spread over more than a hemisphere, layers writing longitude in two
    conventions, and a target that the source CRS would tear at one of its
    seams (the 180th meridian, a pole: caught by area and by position, whatever
    the units) are refused; targets keep their own CRS; and the counts written
    are checked against the pieces they came from.

Fixed

  • crs_decisions.transformation now describes what PROJ did to the data's
    own coordinates.
    Which operation PROJ selects depends on where a
    coordinate is, and the record asked at one point chosen from the source CRS
    alone -- for EPSG:4326, the middle of the world, in the Gulf of Guinea. It
    was wrong in both directions: WGS 84 data in Europe moved to EPSG:3035, or in
    England to EPSG:27700, was recorded as a ballpark (is_ballpark: true) where
    PROJ applied a published 1 m or 2 m shift; and NAD27 coordinates in Italy were
    recorded as a 7 m shift where PROJ applied none -- the error a ballpark record
    exists to report, hidden by it. Every operation that moves an input now hands
    over the moved layer, PROJ is asked what it did at a spread of the data's own
    points, inside and outside the areas of use of the operations it has -- outside
    them it applies no shift for some pairs (NAD27) and stretches a published
    operation for others (DHDN, the Swiss grid), so the answer is PROJ's and not
    inferred. A layer straddling the edge of a datum grid is recorded as a
    ballpark with x-mapsmith:ballpark_share saying how many of its vertices got
    no shift; x-mapsmith:outside_area_of_use says how many lie outside the area
    of every operation published for the pair, whatever PROJ then did with them;
    a move of no coordinates at all records that nothing was transformed; and a
    check with no data to hand asks where the two CRSs' areas of use overlap.
  • reproject_layer no longer applies an operation outside its area of use,
    and its output changes where it did.
    When PROJ's own choice at its probe
    point was a ballpark it substituted the first published operation for the
    pair, wherever the data was: WGS 84 data in Madrid moved into EPSG:3003 by
    the Italian operation came out 84.5 m from PROJ's own result, and WGS 84
    data in Italy moved into EPSG:27700 by the British one, 248 m. Now it
    substitutes only an operation whose area of use contains every vertex, and
    otherwise leaves each point where PROJ does. A geographic CRS counted from
    another meridian whose operations are published only for its Greenwich twin
    goes through that twin -- EPSG:4806 (Monte Mario, Rome) and EPSG:4819 (Nord
    Sahara 1959, Paris) among the 23 tried; NTF Paris, EPSG:4807, has an
    operation of its own and does not change. A station in Piedmont on EPSG:4806
    now lands on the published 4 m operation's answer, where it was 6.3 m off.
  • better_available_m names only an operation that covers the data and is
    not installed.
    It reported any published operation for the pair: EPSG:4806
    data in Piedmont was told a 44 m operation needed its grid installed, when
    that operation needs none and is valid in Sardinia only.
  • A record written on a failing path no longer claims an earlier run's
    output.
    With a dataset already at the output path, a run refused by its
    preconditions, or one that crashed before writing, recorded the digest of
    the earlier bytes as its own output -- and a lineage walk by digest then
    attributed them to the inputs of the run that failed -- and its record
    replaced theirs, leaving them with none. Such a record now omits output
    and says why, and where a record on disk still describes the bytes at the
    path it is left in place and the failing run's record is written beside it
    as <output>.failed.provenance.json, which the error names. Bytes the
    failing run did write keep their digest, including when a check of the
    output crashes after the write.

Changed

  • Shapely 2.1 or later is required (was 2.0): apportion_by_area uses its
    maximum inscribed circle to check that a reprojected target still holds its
    own interior.
  • pyarrow 23.0.1 or later is required (was 21), the release fixing
    CVE-2026-25087, a use-after-free reading Arrow IPC files; MapSmith reads none,
    and the floor keeps an install at the minimum from carrying it. pytest 9.0.3
    or later for the test extra.