Skip to content

CoverageValidator rebuilds every adjacent polygon's indexes once per target #1503

Description

@bkeepers

CoverageValidator::validate() loops over targets and calls the static CoveragePolygonValidator::validate(target, nearGeoms, gapWidth), which calls toCoveragePolygons(adjPolygons), constructing a fresh CoveragePolygon for every adjacent polygon of every target. CoveragePolygon lazily caches an IndexedPointInAreaLocator, but the wrapper itself never survives to the next target, so each polygon's point-in-area index is rebuilt once per adjacent target rather than once per validation run.

On a coverage with one large polygon adjacent to nearly everything, this dominates. Profiling coverage_is_valid on a real coastal coverage of 88,997 polygons / 20.7M vertices whose largest part (4.26M vertices) has an envelope intersecting 88,697 of the others (macOS sample, 11,230 samples, GEOS 3.13.1):

CoverageValidator::validate()
 └ CoveragePolygonValidator::validate()                       10,598
    └ checkTargetRings → markInvalidInteriorSegments           8,905
       └ markInvalidInteriorSection
          └ CoveragePolygon::contains(CoordinateXY)            8,905
             └ IndexedPointInAreaLocator::locate()             7,694  (68.5% of total)
                └ TemplateSTRtree<SegmentView>::build()        7,694
                   └ createParentNodes → std::__introsort      7,159

Scaling, measured with geosop coverageValidate on main (a03789b), fixtures of N real parts plus the one large polygon:

parts wall
1,001 7.97 s
4,001 31.07 s
16,001 134.9 s

That is near-linear in part count (≈N^1.06), 8.4 ms per part at 16k. Extrapolated to the full 88,997-part coverage: a floor around 850–900 s, several times the CoverageUnion (194 s) the validation guards. The sensitivity to a single member is stark: adding the one large polygon to a 1,000-part subset moves coverage_is_valid from 0.030 s to 39.8 s.

The relevant code is unchanged between 3.13.1 and current main, and JTS shares the design (CoverageValidator.java constructs a fresh CoveragePolygonValidator per target and caches nothing across targets).

Evidence: https://github.com/openwatersio/geos/tree/coverage-validator-cache-diagnostic (commit defa895)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions