argleton 0.2.0__tar.gz → 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {argleton-0.2.0 → argleton-0.4.0}/.github/workflows/ci.yml +21 -2
- argleton-0.4.0/.zenodo.json +19 -0
- {argleton-0.2.0 → argleton-0.4.0}/CITATION.cff +2 -2
- {argleton-0.2.0 → argleton-0.4.0}/PKG-INFO +66 -33
- {argleton-0.2.0 → argleton-0.4.0}/README.md +65 -32
- {argleton-0.2.0 → argleton-0.4.0}/adapters/engine_geopandas.py +90 -0
- {argleton-0.2.0 → argleton-0.4.0}/adapters/engine_naive.py +115 -0
- {argleton-0.2.0 → argleton-0.4.0}/adapters/engine_rasterio.py +61 -0
- argleton-0.4.0/adapters/engine_whitebox.py +80 -0
- {argleton-0.2.0 → argleton-0.4.0}/adapters/mapsmith.py +235 -0
- argleton-0.4.0/clean/c023-longitude-first/build.py +44 -0
- argleton-0.4.0/clean/c023-longitude-first/probe.toml +43 -0
- argleton-0.4.0/clean/c024-pixel-is-area/build.py +82 -0
- argleton-0.4.0/clean/c024-pixel-is-area/probe.toml +52 -0
- argleton-0.4.0/clean/c025-single-hemisphere-zone/build.py +95 -0
- argleton-0.4.0/clean/c025-single-hemisphere-zone/probe.toml +46 -0
- argleton-0.4.0/clean/c026-north-up-grid/build.py +64 -0
- argleton-0.4.0/clean/c026-north-up-grid/probe.toml +42 -0
- argleton-0.4.0/clean/c027-lines-only/build.py +60 -0
- argleton-0.4.0/clean/c027-lines-only/probe.toml +44 -0
- argleton-0.4.0/clean/c028-projected-field/build.py +57 -0
- argleton-0.4.0/clean/c028-projected-field/probe.toml +48 -0
- argleton-0.4.0/clean/c029-coincident-boundaries/build.py +49 -0
- argleton-0.4.0/clean/c029-coincident-boundaries/probe.toml +49 -0
- argleton-0.4.0/clean/c030-single-georeferencing/build.py +36 -0
- argleton-0.4.0/clean/c030-single-georeferencing/probe.toml +39 -0
- {argleton-0.2.0 → argleton-0.4.0}/docs/FAMILIES.md +45 -4
- {argleton-0.2.0 → argleton-0.4.0}/pyproject.toml +1 -1
- argleton-0.4.0/results/2026-08-29-axis-order/adapters-geopandas.json +575 -0
- argleton-0.4.0/results/2026-08-29-axis-order/adapters-mapsmith.json +574 -0
- argleton-0.4.0/results/2026-08-29-axis-order/adapters-naive.json +574 -0
- argleton-0.4.0/results/2026-08-29-axis-order/adapters-rasterio.json +574 -0
- argleton-0.4.0/results/2026-08-29-axis-order/adapters-whitebox.json +575 -0
- argleton-0.4.0/results/2026-08-30-all-families/adapters-geopandas.json +719 -0
- argleton-0.4.0/results/2026-08-30-all-families/adapters-mapsmith.json +718 -0
- argleton-0.4.0/results/2026-08-30-all-families/adapters-naive.json +718 -0
- argleton-0.4.0/results/2026-08-30-all-families/adapters-rasterio.json +718 -0
- argleton-0.4.0/results/2026-08-30-all-families/adapters-whitebox.json +719 -0
- argleton-0.4.0/results/2026-08-30-antimeridian/adapters-geopandas.json +623 -0
- argleton-0.4.0/results/2026-08-30-antimeridian/adapters-mapsmith.json +622 -0
- argleton-0.4.0/results/2026-08-30-antimeridian/adapters-naive.json +622 -0
- argleton-0.4.0/results/2026-08-30-antimeridian/adapters-rasterio.json +622 -0
- argleton-0.4.0/results/2026-08-30-antimeridian/adapters-whitebox.json +623 -0
- argleton-0.4.0/results/2026-08-30-grid-registration/adapters-geopandas.json +599 -0
- argleton-0.4.0/results/2026-08-30-grid-registration/adapters-mapsmith.json +598 -0
- argleton-0.4.0/results/2026-08-30-grid-registration/adapters-naive.json +598 -0
- argleton-0.4.0/results/2026-08-30-grid-registration/adapters-rasterio.json +598 -0
- argleton-0.4.0/results/2026-08-30-grid-registration/adapters-whitebox.json +599 -0
- argleton-0.4.0/results/LATEST +1 -0
- {argleton-0.2.0 → argleton-0.4.0}/results/README.md +225 -2
- {argleton-0.2.0 → argleton-0.4.0}/site/index.template.html +3 -2
- {argleton-0.2.0 → argleton-0.4.0}/tests/test_showcase.py +194 -5
- argleton-0.4.0/traps/023-axis-order/README.md +130 -0
- argleton-0.4.0/traps/023-axis-order/build.py +57 -0
- argleton-0.4.0/traps/023-axis-order/probe.toml +110 -0
- argleton-0.4.0/traps/024-pixel-is-point/README.md +111 -0
- argleton-0.4.0/traps/024-pixel-is-point/build.py +78 -0
- argleton-0.4.0/traps/024-pixel-is-point/probe.toml +137 -0
- argleton-0.4.0/traps/025-antimeridian-zone/README.md +92 -0
- argleton-0.4.0/traps/025-antimeridian-zone/build.py +108 -0
- argleton-0.4.0/traps/025-antimeridian-zone/probe.toml +134 -0
- argleton-0.4.0/traps/026-south-up-grid/README.md +101 -0
- argleton-0.4.0/traps/026-south-up-grid/build.py +83 -0
- argleton-0.4.0/traps/026-south-up-grid/probe.toml +129 -0
- argleton-0.4.0/traps/027-mixed-geometry/README.md +60 -0
- argleton-0.4.0/traps/027-mixed-geometry/build.py +79 -0
- argleton-0.4.0/traps/027-mixed-geometry/probe.toml +107 -0
- argleton-0.4.0/traps/028-degrees-as-metres/README.md +82 -0
- argleton-0.4.0/traps/028-degrees-as-metres/build.py +71 -0
- argleton-0.4.0/traps/028-degrees-as-metres/probe.toml +122 -0
- argleton-0.4.0/traps/029-difference-order/README.md +66 -0
- argleton-0.4.0/traps/029-difference-order/build.py +61 -0
- argleton-0.4.0/traps/029-difference-order/probe.toml +112 -0
- argleton-0.4.0/traps/030-sidecar-georeferencing/README.md +79 -0
- argleton-0.4.0/traps/030-sidecar-georeferencing/build.py +93 -0
- argleton-0.4.0/traps/030-sidecar-georeferencing/probe.toml +95 -0
- argleton-0.2.0/adapters/engine_whitebox.py +0 -34
- argleton-0.2.0/results/LATEST +0 -1
- {argleton-0.2.0 → argleton-0.4.0}/.gitattributes +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/.github/workflows/pages.yml +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/.gitignore +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/LICENSE +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/adapters/__init__.py +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/adapters/gis_mcp.py +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/argleton/__init__.py +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/argleton/model.py +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/argleton/published.py +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/argleton/run.py +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/argleton/score.py +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/clean/c001-raster-mean/build.py +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/clean/c001-raster-mean/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/clean/c002-projected-area/build.py +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/clean/c002-projected-area/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/clean/c003-raster-mean-nodata/build.py +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/clean/c003-raster-mean-nodata/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/clean/c004-points-in-polygon/build.py +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/clean/c004-points-in-polygon/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/clean/c005-polygon-area/build.py +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/clean/c005-polygon-area/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/clean/c006-named-layer/build.py +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/clean/c006-named-layer/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/clean/c007-distance-in-metres/build.py +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/clean/c007-distance-in-metres/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/clean/c008-equal-area-crs/build.py +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/clean/c008-equal-area-crs/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/clean/c009-native-resolution-classes/build.py +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/clean/c009-native-resolution-classes/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/clean/c010-physical-values/build.py +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/clean/c010-physical-values/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/clean/c011-solid-parcel/build.py +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/clean/c011-solid-parcel/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/clean/c012-disjoint-concessions/build.py +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/clean/c012-disjoint-concessions/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/clean/c013-flat-pipeline/build.py +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/clean/c013-flat-pipeline/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/clean/c014-convex-parcel/build.py +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/clean/c014-convex-parcel/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/clean/c015-wells-off-the-seam/build.py +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/clean/c015-wells-off-the-seam/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/clean/c016-decimal-degrees/build.py +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/clean/c016-decimal-degrees/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/clean/c017-equal-populations/build.py +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/clean/c017-equal-populations/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/clean/c018-plain-keys/build.py +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/clean/c018-plain-keys/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/clean/c019-fully-contained/build.py +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/clean/c019-fully-contained/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/clean/c020-one-owner-each/build.py +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/clean/c020-one-owner-each/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/clean/c021-greenwich-variant/build.py +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/clean/c021-greenwich-variant/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/clean/c022-two-gauges/build.py +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/clean/c022-two-gauges/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/docs/ADDING-A-TRAP.md +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/docs/METHOD.md +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/results/2026-08-23-first-run/adapters-mapsmith.json +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/results/2026-08-23-first-run/engine-geopandas.json +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/results/2026-08-23-first-run/engine-naive.json +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/results/2026-08-23-first-run/engine-rasterio.json +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/results/2026-08-23-first-run/engine-whitebox.json +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/results/2026-08-24-five-families/adapters-mapsmith.json +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/results/2026-08-24-five-families/engine-geopandas.json +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/results/2026-08-24-five-families/engine-naive.json +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/results/2026-08-24-five-families/engine-rasterio.json +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/results/2026-08-24-five-families/engine-whitebox.json +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/results/2026-08-25-eight-families/adapters-mapsmith.json +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/results/2026-08-25-eight-families/engine-geopandas.json +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/results/2026-08-25-eight-families/engine-naive.json +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/results/2026-08-25-eight-families/engine-rasterio.json +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/results/2026-08-25-eight-families/engine-whitebox.json +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/results/2026-08-25-six-families/adapters-mapsmith.json +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/results/2026-08-25-six-families/engine-geopandas.json +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/results/2026-08-25-six-families/engine-naive.json +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/results/2026-08-25-six-families/engine-rasterio.json +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/results/2026-08-25-six-families/engine-whitebox.json +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/results/2026-08-26-eighteen-families/adapters-geopandas.json +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/results/2026-08-26-eighteen-families/adapters-mapsmith.json +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/results/2026-08-26-eighteen-families/adapters-naive.json +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/results/2026-08-26-eighteen-families/adapters-rasterio.json +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/results/2026-08-26-eighteen-families/adapters-whitebox.json +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/results/2026-08-26-nine-families/adapters-geopandas.json +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/results/2026-08-26-nine-families/adapters-mapsmith.json +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/results/2026-08-26-nine-families/adapters-naive.json +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/results/2026-08-26-nine-families/adapters-rasterio.json +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/results/2026-08-26-nine-families/adapters-whitebox.json +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/results/2026-08-26-nineteen-families/adapters-geopandas.json +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/results/2026-08-26-nineteen-families/adapters-mapsmith.json +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/results/2026-08-26-nineteen-families/adapters-naive.json +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/results/2026-08-26-nineteen-families/adapters-rasterio.json +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/results/2026-08-26-nineteen-families/adapters-whitebox.json +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/results/2026-08-26-ten-families/adapters-geopandas.json +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/results/2026-08-26-ten-families/adapters-mapsmith.json +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/results/2026-08-26-ten-families/adapters-naive.json +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/results/2026-08-26-ten-families/adapters-rasterio.json +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/results/2026-08-26-ten-families/adapters-whitebox.json +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/results/2026-08-27-datum-shift-fixed/adapters-geopandas.json +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/results/2026-08-27-datum-shift-fixed/adapters-mapsmith.json +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/results/2026-08-27-datum-shift-fixed/adapters-naive.json +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/results/2026-08-27-datum-shift-fixed/adapters-rasterio.json +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/results/2026-08-27-datum-shift-fixed/adapters-whitebox.json +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/results/2026-08-28-thiessen-pairing/adapters-geopandas.json +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/results/2026-08-28-thiessen-pairing/adapters-mapsmith.json +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/results/2026-08-28-thiessen-pairing/adapters-naive.json +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/results/2026-08-28-thiessen-pairing/adapters-rasterio.json +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/results/2026-08-28-thiessen-pairing/adapters-whitebox.json +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/results/2026-08-28-timings/adapters-geopandas.json +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/results/2026-08-28-timings/adapters-mapsmith.json +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/results/2026-08-28-timings/adapters-naive.json +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/results/2026-08-28-timings/adapters-rasterio.json +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/results/2026-08-28-timings/adapters-whitebox.json +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/schema/probe.schema.json +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/schema/result.schema.json +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/site/build.py +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/tests/test_contracts.py +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/traps/001-tiff-predictor/README.md +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/traps/001-tiff-predictor/build.py +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/traps/001-tiff-predictor/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/traps/002-feet-as-metres/README.md +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/traps/002-feet-as-metres/build.py +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/traps/002-feet-as-metres/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/traps/003-nodata-in-statistics/README.md +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/traps/003-nodata-in-statistics/build.py +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/traps/003-nodata-in-statistics/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/traps/004-mismatched-crs-join/README.md +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/traps/004-mismatched-crs-join/build.py +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/traps/004-mismatched-crs-join/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/traps/005-bowtie-area/README.md +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/traps/005-bowtie-area/build.py +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/traps/005-bowtie-area/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/traps/006-default-layer/README.md +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/traps/006-default-layer/build.py +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/traps/006-default-layer/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/traps/007-buffer-in-degrees/README.md +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/traps/007-buffer-in-degrees/build.py +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/traps/007-buffer-in-degrees/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/traps/008-web-mercator-area/README.md +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/traps/008-web-mercator-area/build.py +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/traps/008-web-mercator-area/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/traps/009-resampled-classes/README.md +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/traps/009-resampled-classes/build.py +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/traps/009-resampled-classes/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/traps/010-scale-offset/README.md +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/traps/010-scale-offset/build.py +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/traps/010-scale-offset/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/traps/011-polygon-holes/README.md +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/traps/011-polygon-holes/build.py +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/traps/011-polygon-holes/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/traps/012-double-counting/README.md +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/traps/012-double-counting/build.py +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/traps/012-double-counting/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/traps/013-z-dimension/README.md +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/traps/013-z-dimension/build.py +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/traps/013-z-dimension/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/traps/014-centroid-outside/README.md +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/traps/014-centroid-outside/build.py +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/traps/014-centroid-outside/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/traps/015-boundary-semantics/README.md +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/traps/015-boundary-semantics/build.py +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/traps/015-boundary-semantics/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/traps/016-coordinate-parsing/README.md +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/traps/016-coordinate-parsing/build.py +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/traps/016-coordinate-parsing/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/traps/017-aggregation-weighting/README.md +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/traps/017-aggregation-weighting/build.py +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/traps/017-aggregation-weighting/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/traps/018-join-key-typing/README.md +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/traps/018-join-key-typing/build.py +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/traps/018-join-key-typing/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/traps/019-partial-overlap/README.md +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/traps/019-partial-overlap/build.py +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/traps/019-partial-overlap/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/traps/020-join-cardinality/README.md +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/traps/020-join-cardinality/build.py +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/traps/020-join-cardinality/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/traps/021-ballpark-datum/README.md +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/traps/021-ballpark-datum/build.py +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/traps/021-ballpark-datum/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/traps/022-thiessen-pairing/README.md +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/traps/022-thiessen-pairing/build.py +0 -0
- {argleton-0.2.0 → argleton-0.4.0}/traps/022-thiessen-pairing/probe.toml +0 -0
|
@@ -16,6 +16,12 @@ jobs:
|
|
|
16
16
|
python: ["3.11", "3.13"]
|
|
17
17
|
steps:
|
|
18
18
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
19
|
+
with:
|
|
20
|
+
# Tags, because one showcase test checks the trap count the README
|
|
21
|
+
# attributes to a release against the tag that release was cut from.
|
|
22
|
+
# A shallow checkout has no tags, and the test would skip in exactly
|
|
23
|
+
# the place it is meant to run.
|
|
24
|
+
fetch-depth: 0
|
|
19
25
|
- uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0
|
|
20
26
|
with:
|
|
21
27
|
python-version: ${{ matrix.python }}
|
|
@@ -76,6 +82,19 @@ jobs:
|
|
|
76
82
|
# point — a trap that stops catching its own naive adapter, or one
|
|
77
83
|
# that starts catching a case it should not, has changed what it
|
|
78
84
|
# measures, and that must be a decision rather than a surprise.
|
|
79
|
-
# 0.
|
|
80
|
-
|
|
85
|
+
# 0.9333 = 28 of 30 traps caught. TWO still pass, unchanged: 001,
|
|
86
|
+
# where rasterio undoes the predictor on the naive adapter's behalf,
|
|
87
|
+
# and 026, where `src.res` reports the cell size faithfully whichever
|
|
88
|
+
# way the rows run — so a plain numpy gradient is more faithful to the
|
|
89
|
+
# geotransform than a specialised terrain engine is. Moved from 0.931
|
|
90
|
+
# on 2026-08-31 when trap 030 added family 28: the naive adapter falls
|
|
91
|
+
# into it, so both numerator and denominator went up by one. Before
|
|
92
|
+
# that 0.96 when traps 026 to 029 closed the original family list, and
|
|
93
|
+
# 0.9583, 0.9565 and 0.9545 as traps 025, 024 and 023 landed.
|
|
94
|
+
# Each time the denominator changed and the numerator changed with
|
|
95
|
+
# it. This pin failing on such a commit is the
|
|
96
|
+
# mechanism working — the number moved because a trap was added, which
|
|
97
|
+
# is a decision, and it has to be written down here rather than
|
|
98
|
+
# discovered in a published table.
|
|
99
|
+
sys.exit(0 if (r["silent_error_rate"], r["completion_rate"]) == (0.9333, 1.0) else 1)
|
|
81
100
|
PY
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"upload_type": "software",
|
|
3
|
+
"title": "Argleton: a correctness suite for geospatial systems",
|
|
4
|
+
"version": "0.4.0",
|
|
5
|
+
"creators": [
|
|
6
|
+
{"name": "The Argleton project"}
|
|
7
|
+
],
|
|
8
|
+
"license": "apache-2.0",
|
|
9
|
+
"access_right": "open",
|
|
10
|
+
"keywords": [
|
|
11
|
+
"geospatial",
|
|
12
|
+
"GIS",
|
|
13
|
+
"benchmark",
|
|
14
|
+
"correctness",
|
|
15
|
+
"silent errors",
|
|
16
|
+
"evaluation"
|
|
17
|
+
],
|
|
18
|
+
"description": "<p>Argleton measures how often a geospatial system answers confidently and wrongly.</p><p>Each probe has a right answer known <em>by construction</em> — derived on paper, never measured from a reference implementation — and a typical wrong answer that looks entirely plausible. Every trap ships with a defect-free twin, so a system cannot score well by refusing to answer: the suite reports a silent-error rate alongside a completion rate on the twins, and a refusal counts as neither a pass nor a silent error.</p><p>The name is the phantom settlement Google Maps showed for two years near Aughton: an empty field for which the map offered photographs of houses, restaurants and a hospital. Well-formed data, valid against its schema, rendered with confidence, entirely false, and crashing nothing. That is the class of error this suite measures.</p><p>Published from an organisation separate from the system it was extracted from, and that relationship is stated in the repository rather than hidden — an evaluation living inside the thing it evaluates can be dismissed in one line.</p>"
|
|
19
|
+
}
|
|
@@ -7,8 +7,8 @@ authors:
|
|
|
7
7
|
repository-code: "https://github.com/argleton/argleton"
|
|
8
8
|
url: "https://argleton.org"
|
|
9
9
|
license: Apache-2.0
|
|
10
|
-
version: "0.
|
|
11
|
-
date-released: "2026-08-
|
|
10
|
+
version: "0.4.0"
|
|
11
|
+
date-released: "2026-08-31"
|
|
12
12
|
abstract: >-
|
|
13
13
|
Probes whose right answer is known by construction — derived on paper, never
|
|
14
14
|
measured from a reference implementation — and whose typical wrong answer
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: argleton
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Probes whose right answer is known by construction, and whose wrong answer looks fine
|
|
5
5
|
Project-URL: Source, https://github.com/argleton/argleton
|
|
6
6
|
Author: Argleton contributors
|
|
@@ -67,8 +67,8 @@ machine, so there is nothing to download and nothing to take on trust:
|
|
|
67
67
|
pip install "argleton[fixtures]"
|
|
68
68
|
```
|
|
69
69
|
|
|
70
|
-
The probes ship with the runner, so that is the whole setup — release 0.
|
|
71
|
-
|
|
70
|
+
The probes ship with the runner, so that is the whole setup — release 0.4.0 carries the
|
|
71
|
+
30 traps and 28 families the results below were produced from. When the checkout runs ahead
|
|
72
72
|
of the release this paragraph says so, because a reader who cannot reproduce the table on
|
|
73
73
|
this page has been told something untrue.
|
|
74
74
|
|
|
@@ -83,36 +83,56 @@ pip install -e ".[fixtures]"
|
|
|
83
83
|
|
|
84
84
|
```
|
|
85
85
|
$ argleton --adapter engine:rasterio
|
|
86
|
-
ok clean c001-raster-mean correct
|
|
87
|
-
ok clean c003-raster-mean-nodata correct
|
|
88
|
-
ok clean c009-native-resolution-classes correct
|
|
89
|
-
ok clean c010-physical-values correct
|
|
90
|
-
ok
|
|
91
|
-
ok
|
|
92
|
-
ok trap
|
|
93
|
-
ok trap
|
|
94
|
-
|
|
86
|
+
ok clean c001-raster-mean correct 1093.0
|
|
87
|
+
ok clean c003-raster-mean-nodata correct 1000.0
|
|
88
|
+
ok clean c009-native-resolution-classes correct 900.0
|
|
89
|
+
ok clean c010-physical-values correct 0.5000000093132254
|
|
90
|
+
ok clean c024-pixel-is-area correct 412105.0
|
|
91
|
+
ok clean c026-north-up-grid correct 5.710593137499643
|
|
92
|
+
ok trap 001-tiff-predictor correct 1093.0
|
|
93
|
+
ok trap 003-nodata-in-statistics correct 1000.0
|
|
94
|
+
ok trap 009-resampled-classes correct 0.0
|
|
95
|
+
ok trap 010-scale-offset correct 0.3333333333333334
|
|
96
|
+
ok trap 024-pixel-is-point correct 412090.0
|
|
97
|
+
ok trap 026-south-up-grid correct 5.710593137499643
|
|
98
|
+
silent_error_rate 0.0 over 6 traps | completion_rate 1.0 over 6 clean
|
|
95
99
|
|
|
96
100
|
$ argleton --adapter engine:whitebox
|
|
97
|
-
ok clean c001-raster-mean
|
|
98
|
-
ok clean c003-raster-mean-nodata
|
|
99
|
-
|
|
100
|
-
ok
|
|
101
|
-
|
|
101
|
+
ok clean c001-raster-mean correct 1093.0
|
|
102
|
+
ok clean c003-raster-mean-nodata correct 1000.0
|
|
103
|
+
ok clean c024-pixel-is-area correct 412105.0
|
|
104
|
+
ok clean c026-north-up-grid correct 5.56521959900856
|
|
105
|
+
FAIL trap 001-tiff-predictor silent_error expected 1093.0 ± 0.001, got 36.09375 — this is the
|
|
106
|
+
ok trap 003-nodata-in-statistics correct 1000.0
|
|
107
|
+
FAIL trap 024-pixel-is-point silent_error expected 412090.0 ± 1.0, got 412120.0
|
|
108
|
+
FAIL trap 026-south-up-grid silent_error expected 5.64 ± 0.2, got 43.99398475646973 — this is
|
|
109
|
+
silent_error_rate 0.75 over 4 traps | completion_rate 1.0 over 4 clean
|
|
102
110
|
```
|
|
103
111
|
|
|
104
112
|
(`skip … unsupported` lines trimmed: the vector probes are outside what these
|
|
105
113
|
two raster engines can be asked, and skipping them is not a failure.)
|
|
106
114
|
|
|
107
115
|
The two summary numbers say different things and both matter: this engine can do
|
|
108
|
-
every task it was given (completion 1.0) *and* gets
|
|
109
|
-
(the 0.
|
|
116
|
+
every task it was given (completion 1.0) *and* gets three of those files
|
|
117
|
+
silently wrong (the 0.75).
|
|
118
|
+
|
|
119
|
+
The last two probes are worth reading twice, because the two engines part
|
|
120
|
+
company on both. One file declares that its values sit at grid nodes rather than
|
|
121
|
+
filling cells; the other stores its rows south to north, which a geotransform is
|
|
122
|
+
perfectly able to say. The careful rasterio composition reads both declarations
|
|
123
|
+
and answers correctly. whitebox reacts to the first in the direction that lands
|
|
124
|
+
a whole cell out — 412120 where the truth is 412090 — and cannot express the
|
|
125
|
+
second at all, discarding the georeferencing and reading the grid as metre cells
|
|
126
|
+
at the origin, which turns a 5.7 degree slope into 45. Both engines get the two
|
|
127
|
+
clean twins right.
|
|
110
128
|
|
|
111
129
|
There is a third adapter, `engine:naive` — read the file, take the statistic,
|
|
112
|
-
report it — and it is the most useful one here. It scores **0.
|
|
113
|
-
answers every clean probe correctly, falls into twenty-
|
|
114
|
-
**passes the
|
|
115
|
-
behalf
|
|
130
|
+
report it — and it is the most useful one here. It scores **0.931 / 1.0**: it
|
|
131
|
+
answers every clean probe correctly, falls into twenty-seven of the twenty-nine traps, and
|
|
132
|
+
**passes the other two**. 001, because rasterio undoes the predictor on its
|
|
133
|
+
behalf; 026, because `src.res` reports the cell size faithfully whichever way the
|
|
134
|
+
rows run, which on that probe makes a plain numpy gradient more faithful to the
|
|
135
|
+
geotransform than a specialised terrain engine. Careless code is not uniformly wrong. It is correct until the data stops
|
|
116
136
|
having the shape it usually has, which is what makes the exceptions so hard to
|
|
117
137
|
see.
|
|
118
138
|
|
|
@@ -134,7 +154,7 @@ Side by side, one glance tells you which you are looking at.
|
|
|
134
154
|
|
|
135
155
|
## What is covered
|
|
136
156
|
|
|
137
|
-
Twenty families of twenty-
|
|
157
|
+
Twenty-eight families of twenty-eight, and [FAMILIES.md](docs/FAMILIES.md) says which — so a
|
|
138
158
|
number from here can never be read as broader than it is. A low silent-error
|
|
139
159
|
rate means a system did not fail silently *on these probes*.
|
|
140
160
|
|
|
@@ -160,19 +180,27 @@ rate means a system did not fail silently *on these probes*.
|
|
|
160
180
|
| `aggregation-weighting` | 13.67% instead of 1.38% | averaging rates treats a village as equal to a city |
|
|
161
181
|
| `tabular-join` | 62000 people instead of 100000 | a CSV reader turns "001" into 1 and four municipalities leave the join |
|
|
162
182
|
| `positional-pairing` | 554 mm of rainfall instead of 268 | the Thiessen cells are all valid and tile the extent; only the row each one carries is wrong |
|
|
183
|
+
| `axis-order` | 16261 m² instead of 14042 | EPSG:4326 declares latitude first and every geometry library expects longitude first; both readings of a corner schedule stay in range |
|
|
184
|
+
| `antimeridian` | 9 vessels in the zone instead of 5 | a zone split at 180 as the standard prescribes has bounds that span the planet, so filtering by the study area's bounding box admits every vessel at that latitude |
|
|
185
|
+
| `raster-affine` | 45° of slope instead of 5.7° | a positive fifth number in the geotransform means the rows run south to north; an engine that cannot express it discards the georeferencing and reads the cells as 1 m |
|
|
186
|
+
| `mixed-geometry` | 3000 m of pipe instead of 2000 | one GeoPackage layer holds the pipes and the treatment plant, and the length of a polygon is its perimeter |
|
|
187
|
+
| `geographic-crs` | 12.7 ha instead of 8.99 | area in square degrees converted with 111320², which is right for latitude and right for longitude only at the equator |
|
|
188
|
+
| `empty-result` | 0 m² workable instead of 160000 | difference is not commutative, and an empty result reads as a finding rather than a failure |
|
|
189
|
+
| `grid-registration` | easting 412105 instead of 412090 | the file declares `AREA_OR_POINT=Point` and the library reports the tag from the same object whose coordinate helper ignores it; half a cell on a 30 m DEM is 15 m, systematic, and inside the GPS error of anyone sent to check |
|
|
190
|
+
| `hidden-configuration` | 160000 m² instead of 40000 | a sidecar georeferences the same raster and wins by documented precedence; both readings are the library behaving as written, and no answer says which one it used |
|
|
163
191
|
|
|
164
192
|
## Results
|
|
165
193
|
|
|
166
|
-
Engine tier, twenty families
|
|
194
|
+
Engine tier, twenty-seven families — the published runs predate the twenty-eighth — `spec_commit` pinned — [every run, and what the
|
|
167
195
|
numbers do not say](results/).
|
|
168
196
|
|
|
169
197
|
| system | silent error rate | completion rate | traps run | not applicable |
|
|
170
198
|
|---|---|---|---|---|
|
|
171
|
-
| MapSmith (main) | 0.00 | 1.00 |
|
|
172
|
-
|
|
|
173
|
-
|
|
|
174
|
-
| whitebox-workflows 2.0.6 | 0.
|
|
175
|
-
| naive composition | 0.
|
|
199
|
+
| MapSmith (main) | 0.00 | 1.00 | 29 | 0 |
|
|
200
|
+
| GeoPandas 1.1 + Shapely 2 (careful composition) | 0.00 | 1.00 | 13 | 32 |
|
|
201
|
+
| rasterio 1.5.1 (careful composition) | 0.00 | 1.00 | 6 | 46 |
|
|
202
|
+
| whitebox-workflows 2.0.6 | 0.75 | 1.00 | 4 | 50 |
|
|
203
|
+
| naive composition | 0.931 | 1.00 | 29 | 0 |
|
|
176
204
|
|
|
177
205
|
The last two columns are not decoration. A rate over two traps and a rate over
|
|
178
206
|
eight are different claims, and an adapter that could only be asked one question
|
|
@@ -268,9 +296,14 @@ thing it evaluates is easy to dismiss in one line — but pretending at
|
|
|
268
296
|
independence we do not have would be worse than the problem. The defence is not
|
|
269
297
|
the org chart: it is that every fixture is regenerable, every tolerance is in
|
|
270
298
|
git history, and every headline finding in [results](results/) so far has cost
|
|
271
|
-
MapSmith something
|
|
272
|
-
|
|
273
|
-
|
|
299
|
+
MapSmith something. **Six defects have gone back to it**: a 0.00 its own
|
|
300
|
+
verification had nothing to do with, a reprojection 74 m out with a manifest
|
|
301
|
+
recording success, a container silently resolved to its first layer, a
|
|
302
|
+
south-up grid whose georeferencing was dropped on read, totals added across
|
|
303
|
+
geometry types that answer different questions, and operations it turned out
|
|
304
|
+
not to have. They are listed on
|
|
305
|
+
[MapSmith's own page](https://github.com/mapsmith-ai/MapSmith#roadmap), where
|
|
306
|
+
the list is kept.
|
|
274
307
|
|
|
275
308
|
If a probe here is unfair to a system, that is a bug, and the fixture in front
|
|
276
309
|
of you is enough to prove it.
|
|
@@ -31,8 +31,8 @@ machine, so there is nothing to download and nothing to take on trust:
|
|
|
31
31
|
pip install "argleton[fixtures]"
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
-
The probes ship with the runner, so that is the whole setup — release 0.
|
|
35
|
-
|
|
34
|
+
The probes ship with the runner, so that is the whole setup — release 0.4.0 carries the
|
|
35
|
+
30 traps and 28 families the results below were produced from. When the checkout runs ahead
|
|
36
36
|
of the release this paragraph says so, because a reader who cannot reproduce the table on
|
|
37
37
|
this page has been told something untrue.
|
|
38
38
|
|
|
@@ -47,36 +47,56 @@ pip install -e ".[fixtures]"
|
|
|
47
47
|
|
|
48
48
|
```
|
|
49
49
|
$ argleton --adapter engine:rasterio
|
|
50
|
-
ok clean c001-raster-mean correct
|
|
51
|
-
ok clean c003-raster-mean-nodata correct
|
|
52
|
-
ok clean c009-native-resolution-classes correct
|
|
53
|
-
ok clean c010-physical-values correct
|
|
54
|
-
ok
|
|
55
|
-
ok
|
|
56
|
-
ok trap
|
|
57
|
-
ok trap
|
|
58
|
-
|
|
50
|
+
ok clean c001-raster-mean correct 1093.0
|
|
51
|
+
ok clean c003-raster-mean-nodata correct 1000.0
|
|
52
|
+
ok clean c009-native-resolution-classes correct 900.0
|
|
53
|
+
ok clean c010-physical-values correct 0.5000000093132254
|
|
54
|
+
ok clean c024-pixel-is-area correct 412105.0
|
|
55
|
+
ok clean c026-north-up-grid correct 5.710593137499643
|
|
56
|
+
ok trap 001-tiff-predictor correct 1093.0
|
|
57
|
+
ok trap 003-nodata-in-statistics correct 1000.0
|
|
58
|
+
ok trap 009-resampled-classes correct 0.0
|
|
59
|
+
ok trap 010-scale-offset correct 0.3333333333333334
|
|
60
|
+
ok trap 024-pixel-is-point correct 412090.0
|
|
61
|
+
ok trap 026-south-up-grid correct 5.710593137499643
|
|
62
|
+
silent_error_rate 0.0 over 6 traps | completion_rate 1.0 over 6 clean
|
|
59
63
|
|
|
60
64
|
$ argleton --adapter engine:whitebox
|
|
61
|
-
ok clean c001-raster-mean
|
|
62
|
-
ok clean c003-raster-mean-nodata
|
|
63
|
-
|
|
64
|
-
ok
|
|
65
|
-
|
|
65
|
+
ok clean c001-raster-mean correct 1093.0
|
|
66
|
+
ok clean c003-raster-mean-nodata correct 1000.0
|
|
67
|
+
ok clean c024-pixel-is-area correct 412105.0
|
|
68
|
+
ok clean c026-north-up-grid correct 5.56521959900856
|
|
69
|
+
FAIL trap 001-tiff-predictor silent_error expected 1093.0 ± 0.001, got 36.09375 — this is the
|
|
70
|
+
ok trap 003-nodata-in-statistics correct 1000.0
|
|
71
|
+
FAIL trap 024-pixel-is-point silent_error expected 412090.0 ± 1.0, got 412120.0
|
|
72
|
+
FAIL trap 026-south-up-grid silent_error expected 5.64 ± 0.2, got 43.99398475646973 — this is
|
|
73
|
+
silent_error_rate 0.75 over 4 traps | completion_rate 1.0 over 4 clean
|
|
66
74
|
```
|
|
67
75
|
|
|
68
76
|
(`skip … unsupported` lines trimmed: the vector probes are outside what these
|
|
69
77
|
two raster engines can be asked, and skipping them is not a failure.)
|
|
70
78
|
|
|
71
79
|
The two summary numbers say different things and both matter: this engine can do
|
|
72
|
-
every task it was given (completion 1.0) *and* gets
|
|
73
|
-
(the 0.
|
|
80
|
+
every task it was given (completion 1.0) *and* gets three of those files
|
|
81
|
+
silently wrong (the 0.75).
|
|
82
|
+
|
|
83
|
+
The last two probes are worth reading twice, because the two engines part
|
|
84
|
+
company on both. One file declares that its values sit at grid nodes rather than
|
|
85
|
+
filling cells; the other stores its rows south to north, which a geotransform is
|
|
86
|
+
perfectly able to say. The careful rasterio composition reads both declarations
|
|
87
|
+
and answers correctly. whitebox reacts to the first in the direction that lands
|
|
88
|
+
a whole cell out — 412120 where the truth is 412090 — and cannot express the
|
|
89
|
+
second at all, discarding the georeferencing and reading the grid as metre cells
|
|
90
|
+
at the origin, which turns a 5.7 degree slope into 45. Both engines get the two
|
|
91
|
+
clean twins right.
|
|
74
92
|
|
|
75
93
|
There is a third adapter, `engine:naive` — read the file, take the statistic,
|
|
76
|
-
report it — and it is the most useful one here. It scores **0.
|
|
77
|
-
answers every clean probe correctly, falls into twenty-
|
|
78
|
-
**passes the
|
|
79
|
-
behalf
|
|
94
|
+
report it — and it is the most useful one here. It scores **0.931 / 1.0**: it
|
|
95
|
+
answers every clean probe correctly, falls into twenty-seven of the twenty-nine traps, and
|
|
96
|
+
**passes the other two**. 001, because rasterio undoes the predictor on its
|
|
97
|
+
behalf; 026, because `src.res` reports the cell size faithfully whichever way the
|
|
98
|
+
rows run, which on that probe makes a plain numpy gradient more faithful to the
|
|
99
|
+
geotransform than a specialised terrain engine. Careless code is not uniformly wrong. It is correct until the data stops
|
|
80
100
|
having the shape it usually has, which is what makes the exceptions so hard to
|
|
81
101
|
see.
|
|
82
102
|
|
|
@@ -98,7 +118,7 @@ Side by side, one glance tells you which you are looking at.
|
|
|
98
118
|
|
|
99
119
|
## What is covered
|
|
100
120
|
|
|
101
|
-
Twenty families of twenty-
|
|
121
|
+
Twenty-eight families of twenty-eight, and [FAMILIES.md](docs/FAMILIES.md) says which — so a
|
|
102
122
|
number from here can never be read as broader than it is. A low silent-error
|
|
103
123
|
rate means a system did not fail silently *on these probes*.
|
|
104
124
|
|
|
@@ -124,19 +144,27 @@ rate means a system did not fail silently *on these probes*.
|
|
|
124
144
|
| `aggregation-weighting` | 13.67% instead of 1.38% | averaging rates treats a village as equal to a city |
|
|
125
145
|
| `tabular-join` | 62000 people instead of 100000 | a CSV reader turns "001" into 1 and four municipalities leave the join |
|
|
126
146
|
| `positional-pairing` | 554 mm of rainfall instead of 268 | the Thiessen cells are all valid and tile the extent; only the row each one carries is wrong |
|
|
147
|
+
| `axis-order` | 16261 m² instead of 14042 | EPSG:4326 declares latitude first and every geometry library expects longitude first; both readings of a corner schedule stay in range |
|
|
148
|
+
| `antimeridian` | 9 vessels in the zone instead of 5 | a zone split at 180 as the standard prescribes has bounds that span the planet, so filtering by the study area's bounding box admits every vessel at that latitude |
|
|
149
|
+
| `raster-affine` | 45° of slope instead of 5.7° | a positive fifth number in the geotransform means the rows run south to north; an engine that cannot express it discards the georeferencing and reads the cells as 1 m |
|
|
150
|
+
| `mixed-geometry` | 3000 m of pipe instead of 2000 | one GeoPackage layer holds the pipes and the treatment plant, and the length of a polygon is its perimeter |
|
|
151
|
+
| `geographic-crs` | 12.7 ha instead of 8.99 | area in square degrees converted with 111320², which is right for latitude and right for longitude only at the equator |
|
|
152
|
+
| `empty-result` | 0 m² workable instead of 160000 | difference is not commutative, and an empty result reads as a finding rather than a failure |
|
|
153
|
+
| `grid-registration` | easting 412105 instead of 412090 | the file declares `AREA_OR_POINT=Point` and the library reports the tag from the same object whose coordinate helper ignores it; half a cell on a 30 m DEM is 15 m, systematic, and inside the GPS error of anyone sent to check |
|
|
154
|
+
| `hidden-configuration` | 160000 m² instead of 40000 | a sidecar georeferences the same raster and wins by documented precedence; both readings are the library behaving as written, and no answer says which one it used |
|
|
127
155
|
|
|
128
156
|
## Results
|
|
129
157
|
|
|
130
|
-
Engine tier, twenty families
|
|
158
|
+
Engine tier, twenty-seven families — the published runs predate the twenty-eighth — `spec_commit` pinned — [every run, and what the
|
|
131
159
|
numbers do not say](results/).
|
|
132
160
|
|
|
133
161
|
| system | silent error rate | completion rate | traps run | not applicable |
|
|
134
162
|
|---|---|---|---|---|
|
|
135
|
-
| MapSmith (main) | 0.00 | 1.00 |
|
|
136
|
-
|
|
|
137
|
-
|
|
|
138
|
-
| whitebox-workflows 2.0.6 | 0.
|
|
139
|
-
| naive composition | 0.
|
|
163
|
+
| MapSmith (main) | 0.00 | 1.00 | 29 | 0 |
|
|
164
|
+
| GeoPandas 1.1 + Shapely 2 (careful composition) | 0.00 | 1.00 | 13 | 32 |
|
|
165
|
+
| rasterio 1.5.1 (careful composition) | 0.00 | 1.00 | 6 | 46 |
|
|
166
|
+
| whitebox-workflows 2.0.6 | 0.75 | 1.00 | 4 | 50 |
|
|
167
|
+
| naive composition | 0.931 | 1.00 | 29 | 0 |
|
|
140
168
|
|
|
141
169
|
The last two columns are not decoration. A rate over two traps and a rate over
|
|
142
170
|
eight are different claims, and an adapter that could only be asked one question
|
|
@@ -232,9 +260,14 @@ thing it evaluates is easy to dismiss in one line — but pretending at
|
|
|
232
260
|
independence we do not have would be worse than the problem. The defence is not
|
|
233
261
|
the org chart: it is that every fixture is regenerable, every tolerance is in
|
|
234
262
|
git history, and every headline finding in [results](results/) so far has cost
|
|
235
|
-
MapSmith something
|
|
236
|
-
|
|
237
|
-
|
|
263
|
+
MapSmith something. **Six defects have gone back to it**: a 0.00 its own
|
|
264
|
+
verification had nothing to do with, a reprojection 74 m out with a manifest
|
|
265
|
+
recording success, a container silently resolved to its first layer, a
|
|
266
|
+
south-up grid whose georeferencing was dropped on read, totals added across
|
|
267
|
+
geometry types that answer different questions, and operations it turned out
|
|
268
|
+
not to have. They are listed on
|
|
269
|
+
[MapSmith's own page](https://github.com/mapsmith-ai/MapSmith#roadmap), where
|
|
270
|
+
the list is kept.
|
|
238
271
|
|
|
239
272
|
If a probe here is unfair to a system, that is a bug, and the fixture in front
|
|
240
273
|
of you is enough to prove it.
|
|
@@ -92,6 +92,68 @@ class Adapter:
|
|
|
92
92
|
total = sum(abs(geod.geometry_area_perimeter(g)[0]) for g in geoms)
|
|
93
93
|
return Outcome(answer=float(total), warnings=warns)
|
|
94
94
|
|
|
95
|
+
|
|
96
|
+
def op_ships_in_zone(self, probe: Probe, workdir: Path) -> Outcome:
|
|
97
|
+
import geopandas as gpd
|
|
98
|
+
import pandas as pd
|
|
99
|
+
|
|
100
|
+
# Containment against the geometry itself, with no bounding box in the
|
|
101
|
+
# way. Careful here is not cleverness about the antimeridian: it is
|
|
102
|
+
# declining the optimisation, because the box of a geometry split at the
|
|
103
|
+
# line is the whole planet and the filter is the failure.
|
|
104
|
+
zone = gpd.read_file(workdir / probe.arguments[0])
|
|
105
|
+
rows = pd.read_csv(workdir / probe.arguments[1])
|
|
106
|
+
ships = gpd.GeoDataFrame(
|
|
107
|
+
rows,
|
|
108
|
+
geometry=gpd.points_from_xy(rows["longitude"], rows["latitude"]),
|
|
109
|
+
crs="EPSG:4326",
|
|
110
|
+
)
|
|
111
|
+
return Outcome(answer=int(ships.within(zone.union_all()).sum()))
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def op_pipe_length_total_m(self, probe: Probe, workdir: Path) -> Outcome:
|
|
115
|
+
import geopandas as gpd
|
|
116
|
+
|
|
117
|
+
# A layer may hold more than one geometry type, so which rows the
|
|
118
|
+
# question is about has to be decided rather than assumed. Filtered by
|
|
119
|
+
# geometry rather than by the `asset_type` column: the geometry is what
|
|
120
|
+
# makes `length` mean something, and an attribute can be wrong where a
|
|
121
|
+
# LineString cannot be a building.
|
|
122
|
+
assets = gpd.read_file(workdir / probe.arguments[0], layer="assets")
|
|
123
|
+
lines = assets[assets.geom_type.isin(["LineString", "MultiLineString"])]
|
|
124
|
+
return Outcome(answer=float(lines.length.sum()))
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def op_field_area_m2(self, probe: Probe, workdir: Path) -> Outcome:
|
|
128
|
+
import geopandas as gpd
|
|
129
|
+
from pyproj import Geod
|
|
130
|
+
|
|
131
|
+
# A geographic CRS has no length in it, so the area is measured on the
|
|
132
|
+
# ellipsoid rather than converted from degrees. `Geod.geometry_area_
|
|
133
|
+
# perimeter` is the call that does that, and it needs no projection and
|
|
134
|
+
# no factor. A projected layer is already in metres and is left alone.
|
|
135
|
+
field = gpd.read_file(workdir / probe.arguments[0])
|
|
136
|
+
if field.crs is not None and field.crs.is_geographic:
|
|
137
|
+
geod = Geod(ellps="WGS84")
|
|
138
|
+
total = sum(
|
|
139
|
+
abs(geod.geometry_area_perimeter(geometry)[0])
|
|
140
|
+
for geometry in field.geometry
|
|
141
|
+
)
|
|
142
|
+
return Outcome(answer=float(total))
|
|
143
|
+
return Outcome(answer=float(field.area.sum()))
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def op_workable_area_m2(self, probe: Probe, workdir: Path) -> Outcome:
|
|
147
|
+
import geopandas as gpd
|
|
148
|
+
|
|
149
|
+
# Difference is not symmetric, so which layer is being cut decides the
|
|
150
|
+
# answer. The question asks for part of the CONCESSION, so the
|
|
151
|
+
# concession is what the reserve is taken out of.
|
|
152
|
+
concession = gpd.read_file(workdir / probe.arguments[0])
|
|
153
|
+
reserve = gpd.read_file(workdir / probe.arguments[1])
|
|
154
|
+
outside = gpd.overlay(concession, reserve, how="difference")
|
|
155
|
+
return Outcome(answer=float(outside.area.sum()))
|
|
156
|
+
|
|
95
157
|
def op_feature_count(self, probe: Probe, workdir: Path) -> Outcome:
|
|
96
158
|
import geopandas as gpd
|
|
97
159
|
|
|
@@ -207,3 +269,31 @@ class Adapter:
|
|
|
207
269
|
"assignment is not unique"
|
|
208
270
|
)
|
|
209
271
|
return Outcome(answer=float(gauges.loc[nearest, field]))
|
|
272
|
+
|
|
273
|
+
def op_parcel_area_m2(self, probe: Probe, workdir: Path) -> Outcome:
|
|
274
|
+
import csv
|
|
275
|
+
|
|
276
|
+
from pyproj import Geod
|
|
277
|
+
from shapely.geometry import Polygon
|
|
278
|
+
|
|
279
|
+
# The header is the contract. A corner schedule can be written either
|
|
280
|
+
# way round — EPSG:4326 declares latitude first and every geometry
|
|
281
|
+
# library expects longitude first, and both conventions are current —
|
|
282
|
+
# so the column NAMES decide, not their positions.
|
|
283
|
+
with (workdir / probe.arguments[0]).open(encoding="utf-8") as handle:
|
|
284
|
+
rows = list(csv.DictReader(handle))
|
|
285
|
+
names = {name.strip().lower() for name in rows[0]}
|
|
286
|
+
for lon_name, lat_name in (("longitude", "latitude"), ("lon", "lat"), ("x", "y")):
|
|
287
|
+
if {lon_name, lat_name} <= names:
|
|
288
|
+
break
|
|
289
|
+
else:
|
|
290
|
+
return Outcome(
|
|
291
|
+
error="the corner schedule does not name its coordinate columns, "
|
|
292
|
+
"so which one is longitude cannot be established"
|
|
293
|
+
)
|
|
294
|
+
ring = [(float(row[lon_name]), float(row[lat_name])) for row in rows]
|
|
295
|
+
# Geodesic on the ellipsoid the CRS names: no map plane, so no
|
|
296
|
+
# projection distortion enters the answer.
|
|
297
|
+
return Outcome(answer=abs(Geod(ellps="WGS84").geometry_area_perimeter(
|
|
298
|
+
Polygon(ring)
|
|
299
|
+
)[0]))
|
|
@@ -84,6 +84,26 @@ class Adapter:
|
|
|
84
84
|
return Outcome(answer=str(row["district"]))
|
|
85
85
|
return Outcome(answer="")
|
|
86
86
|
|
|
87
|
+
|
|
88
|
+
def op_ships_in_zone(self, probe: Probe, workdir: Path) -> Outcome:
|
|
89
|
+
import geopandas as gpd
|
|
90
|
+
import pandas as pd
|
|
91
|
+
|
|
92
|
+
# Filter to the bounding box of the area of interest, then count. `cx`
|
|
93
|
+
# is the coordinate-slice idiom out of the GeoPandas documentation, and
|
|
94
|
+
# narrowing to the study area before doing anything expensive is what
|
|
95
|
+
# everybody does — it is how a tile is requested and how a WHERE clause
|
|
96
|
+
# on min/max columns is written.
|
|
97
|
+
zone = gpd.read_file(workdir / probe.arguments[0])
|
|
98
|
+
rows = pd.read_csv(workdir / probe.arguments[1])
|
|
99
|
+
ships = gpd.GeoDataFrame(
|
|
100
|
+
rows,
|
|
101
|
+
geometry=gpd.points_from_xy(rows["longitude"], rows["latitude"]),
|
|
102
|
+
crs="EPSG:4326",
|
|
103
|
+
)
|
|
104
|
+
minx, miny, maxx, maxy = zone.total_bounds
|
|
105
|
+
return Outcome(answer=len(ships.cx[minx:maxx, miny:maxy]))
|
|
106
|
+
|
|
87
107
|
def op_wells_in_districts(self, probe: Probe, workdir: Path) -> Outcome:
|
|
88
108
|
import geopandas as gpd
|
|
89
109
|
|
|
@@ -172,6 +192,83 @@ class Adapter:
|
|
|
172
192
|
cells = int((band == wanted).sum())
|
|
173
193
|
return Outcome(answer=float(cells * resolution * resolution))
|
|
174
194
|
|
|
195
|
+
|
|
196
|
+
def op_raster_ground_area_m2(self, probe: Probe, workdir: Path) -> Outcome:
|
|
197
|
+
import rasterio
|
|
198
|
+
|
|
199
|
+
# Open it and read the transform. One line, no warning, and it is the
|
|
200
|
+
# sidecar's transform rather than the file's: GDAL's documented
|
|
201
|
+
# precedence puts `.aux.xml` first. Nothing here can tell.
|
|
202
|
+
with rasterio.open(workdir / probe.arguments[0]) as ds:
|
|
203
|
+
cell_area = abs(ds.transform.a * ds.transform.e)
|
|
204
|
+
return Outcome(answer=float(cell_area * ds.width * ds.height))
|
|
205
|
+
|
|
206
|
+
def op_lowest_cell_easting(self, probe: Probe, workdir: Path) -> Outcome:
|
|
207
|
+
import numpy as np
|
|
208
|
+
import rasterio
|
|
209
|
+
|
|
210
|
+
# Read the array, find the lowest cell, ask the library where it is.
|
|
211
|
+
# Three lines, and the third one is the whole probe: `xy` returns the
|
|
212
|
+
# centre of the cell under the pixel-is-area reading, always, whatever
|
|
213
|
+
# the file says about itself.
|
|
214
|
+
with rasterio.open(workdir / probe.arguments[0]) as ds:
|
|
215
|
+
values = ds.read(1)
|
|
216
|
+
row, column = np.unravel_index(np.argmin(values), values.shape)
|
|
217
|
+
easting, _ = ds.xy(int(row), int(column))
|
|
218
|
+
return Outcome(answer=float(easting))
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def op_mean_slope_degrees(self, probe: Probe, workdir: Path) -> Outcome:
|
|
222
|
+
import numpy as np
|
|
223
|
+
import rasterio
|
|
224
|
+
|
|
225
|
+
with rasterio.open(workdir / probe.arguments[0]) as ds:
|
|
226
|
+
elevation = ds.read(1).astype("float64")
|
|
227
|
+
width, height = ds.res
|
|
228
|
+
# `res` is the size of a cell and is positive whichever way the rows
|
|
229
|
+
# run: the direction lives in the sign of the transform, not here. So
|
|
230
|
+
# this is right on both halves of the pair without knowing there is a
|
|
231
|
+
# pair — which is the finding, not a virtue of the code.
|
|
232
|
+
along_rows, along_columns = np.gradient(elevation, height, width)
|
|
233
|
+
slope = np.degrees(np.arctan(np.hypot(along_columns, along_rows)))
|
|
234
|
+
return Outcome(answer=float(slope.mean()))
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def op_pipe_length_total_m(self, probe: Probe, workdir: Path) -> Outcome:
|
|
238
|
+
import geopandas as gpd
|
|
239
|
+
|
|
240
|
+
# Read the layer, take the length, add it up. Three lines, and for
|
|
241
|
+
# thirty years the shapefile format made the missing filter unnecessary:
|
|
242
|
+
# one geometry type per file meant a layer of pipes was a layer of pipes.
|
|
243
|
+
assets = gpd.read_file(workdir / probe.arguments[0], layer="assets")
|
|
244
|
+
return Outcome(answer=float(assets.length.sum()))
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def op_field_area_m2(self, probe: Probe, workdir: Path) -> Outcome:
|
|
248
|
+
import geopandas as gpd
|
|
249
|
+
|
|
250
|
+
# Shapely gives the area in the coordinates' own units, so on a
|
|
251
|
+
# geographic layer it has to be converted, and this is the conversion:
|
|
252
|
+
# one factor, the length of a degree at the equator, squared. It is the
|
|
253
|
+
# most copied number in the field.
|
|
254
|
+
field = gpd.read_file(workdir / probe.arguments[0])
|
|
255
|
+
area = float(field.area.sum())
|
|
256
|
+
if field.crs is not None and field.crs.is_geographic:
|
|
257
|
+
area *= 111320.0 ** 2
|
|
258
|
+
return Outcome(answer=area)
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def op_workable_area_m2(self, probe: Probe, workdir: Path) -> Outcome:
|
|
262
|
+
import geopandas as gpd
|
|
263
|
+
|
|
264
|
+
# "How much of the concession lies outside the reserve." The reserve is
|
|
265
|
+
# the thing the sentence is about, so it goes first — which is the
|
|
266
|
+
# difference the other way round, and a perfectly valid call.
|
|
267
|
+
concession = gpd.read_file(workdir / probe.arguments[0])
|
|
268
|
+
reserve = gpd.read_file(workdir / probe.arguments[1])
|
|
269
|
+
outside = gpd.overlay(reserve, concession, how="difference")
|
|
270
|
+
return Outcome(answer=float(outside.area.sum()))
|
|
271
|
+
|
|
175
272
|
def op_raster_mean(self, probe: Probe, workdir: Path) -> Outcome:
|
|
176
273
|
import rasterio
|
|
177
274
|
|
|
@@ -238,3 +335,21 @@ class Adapter:
|
|
|
238
335
|
if hit.empty:
|
|
239
336
|
return Outcome(error="no Thiessen cell contains the site")
|
|
240
337
|
return Outcome(answer=float(hit[field].iloc[0]))
|
|
338
|
+
|
|
339
|
+
def op_parcel_area_m2(self, probe: Probe, workdir: Path) -> Outcome:
|
|
340
|
+
import csv
|
|
341
|
+
|
|
342
|
+
from pyproj import Geod
|
|
343
|
+
from shapely.geometry import Polygon
|
|
344
|
+
|
|
345
|
+
# Read the corner schedule and build the ring: take the two coordinate
|
|
346
|
+
# columns in the order they appear, which is what `Polygon(df.values)`,
|
|
347
|
+
# `np.loadtxt` past the header and every csv.reader loop do.
|
|
348
|
+
#
|
|
349
|
+
# The header names both columns. Nobody asks it which one is x.
|
|
350
|
+
with (workdir / probe.arguments[0]).open(encoding="utf-8") as handle:
|
|
351
|
+
rows = list(csv.reader(handle))
|
|
352
|
+
ring = [(float(row[1]), float(row[2])) for row in rows[1:]]
|
|
353
|
+
return Outcome(answer=abs(Geod(ellps="WGS84").geometry_area_perimeter(
|
|
354
|
+
Polygon(ring)
|
|
355
|
+
)[0]))
|