Skip to content

Fix spatial operator edge cases and expand in-memory tests - #173

Open
sdrichards-bc wants to merge 4 commits into
mainfrom
pr/operator-bugs
Open

sdrichards-bc wants to merge 4 commits into
mainfrom
pr/operator-bugs

Conversation

@sdrichards-bc

Copy link
Copy Markdown
Collaborator

Summary

This PR fixes three edge cases found while refactoring the overlay, proximity, and adjacency tests to use shared AOI stubs and controlled in-memory geometry:

  • Missing pandas feature IDs could appear as strings such as "nan" or "<NA>" instead of falling back to the source index.
  • One-shot keep_properties iterables could be exhausted while building read options, leaving result records without the requested attributes.
  • within_distance() accepted a zero radius at its own validation boundary even though the downstream spatial filter rejected it.

The production changes are confined to overlay.py and proximity.py. The three test modules provide more explicit checks of measurements, result ordering, attributes, boundary cases, and adapter requests.

Operator fixes

Handle missing feature IDs consistently

Both operators now use pd.notna(value) in _extract_feature_id().

  • None, NaN, and pd.NA fall back to the source row's index label, converted to a string.
  • A valid ID of 0 remains "0".
  • Missing ID columns continue to use the source index fallback.

The fallback uses the original index label, so filtering and sorting do not replace it with a new row position.

Preserve properties supplied through one-shot iterables

intersection(), within_distance(), and nearest() convert a supplied keep_properties iterable to a tuple before constructing read options.

This lets column selection and result construction reuse the same names. Iterators and generators can therefore retain the requested result attributes instead of being consumed during the adapter request. Regression tests exercise both a list and a one-shot iterator and check the requested columns and returned properties together.

Require a positive within-distance radius

within_distance() now rejects distance_m <= 0 with ValueError("distance_m must be positive") before reading from the adapter. This aligns its input validation with the spatial filter's positive-distance requirement, including when callers supply their own ReadOptions.

A positive search radius still includes features whose measured distance is zero or exactly equal to that radius. nearest(max_distance_m=0) remains supported for selecting zero-distance candidates.

Test refactoring and coverage

The tests use shared AOI stubs, generated Shapely geometries, and InMemorySpatialAdapter in place of file-based scenarios. Small fixtures give independently calculable areas, lengths, and distances. Adapter recordings verify request construction without requiring file or database access. Module docstrings explain each suite's purpose and how to extend it.

Test module Coverage
test_overlay.py Polygon areas, line lengths, interior and boundary point counts; descending overlap order; ID/property retention; null-ID and iterator regressions; typed empty results; geometry inference and overrides; edge/endpoint contacts; separated; CRS rejection and adapter request forwarding.
test_proximity.py Known distances; inclusive radius and distance-cap boundaries; nearest-first sorting and k limits; zero-distance matches; empty results; ID/property regressions; distances across separated AOI polygons; invalid arguments and CRS rejection before reads; default and explicit read options.
test_adjacent.py Full-edge shared lengths; corner-only exclusion; gaps below and above tolerance; empty results; descending shared-length order with associated IDs/properties; invalid inputs; touches versus within_distance search requests.

Files changed

File Change
overlay.py Use pandas-aware null-ID detection and materialize keep_properties before reuse.
proximity.py Apply the same ID and iterable fixes to proximity processing; reject nonpositive within_distance() radii.
test_overlay.py Refactor fixtures and expand overlay behavior and regression checks.
test_proximity.py Refactor fixtures and expand coverage shared by within_distance() and nearest().
test_adjacent.py Refactor fixtures and strengthen adjacency, tolerance, ordering, and request checks.

Compatibility and dependencies

Public function signatures and result types remain unchanged. Callers using within_distance(distance_m=0) now receive an immediate operator-level ValueError; use a positive radius for that operation.

The test modules import helpers from ast_engine./tests/helpers/aoi_cases.py, aoi_geometry.py, and spatial_adapters.py

Check List

  • [Y] Code runs locally
  • [N] Tests pass**
  • [Y] New behavior has tests
  • [Y] Documentation updated if behavior changed
  • [Y] No secrets, credentials, or local-only paths committed
  • [Y] Logging uses module-level loggers
  • [Y] Exceptions and validation results follow engine convention

Other Comments?

Requires pr/aoi-builder merged to main to allow tests to pass.

@thaynesbc thaynesbc added this to the Analysis Operators milestone Sep 16, 2026
@thaynesbc thaynesbc added core items associated with the core AST (runs by client execution) operator related to data operators labels Sep 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core items associated with the core AST (runs by client execution) operator related to data operators

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants