Skip to content

iter_features hard-codes f=json, so a caller needing GeoJSON still copies the offset loop #84

Description

@ChelseaKR

What

iter_features gained return_geometry and out_sr in #75, which closed most of the consuming project's gap 2. It hard-codes the output format:

query: dict[str, Any] = {
    ...
    "f": "json",
}

This asks for an output-format argument alongside them, defaulting to "json" so the request this project makes by default stays byte-for-byte what it has always been and the pinned retrievals in sources.py stay reproducible.

Why it matters

wildfire-service-territory-overlap consumes this package under a pinned commit, and its docs/UPSTREAM.md gap 2 asked for "geometry, output format and output spatial reference as arguments to fetch_layer, or the offset loop factored out". #75 delivered the offset loop and two of the three arguments. Re-audited at the pin 3a6aa47 on 2026-09-07, the gap is still open on the third, and it is the one that keeps the duplication alive.

That project reads three polygon layers, the two CEC service-territory layers and a county boundary layer, as f=geojson and writes them as GeoJSON feature collections, which is what its geometry.py consumes. iter_features yields GeoServices features, {"attributes": ..., "geometry": ...} with Esri rings. Routing those layers through it would mean converting Esri geometry to GeoJSON in the consumer, which is more re-implementation than the offset loop it would retire, and it would be conversion of the geometry every measurement in that project runs on.

So its fetch_feature_pages stays, and with it a second copy of the offset rule this package exists to stop being copied:

step the offset by the rows received, never by the page size asked for

which iter_features's own comment explains at length, and which was wrong once here. There are two implementations of it in that project's dependency graph today, and only one of them gets fixed when the pin moves.

It also keeps a second copy of the refusals alive. That project's local _get duplicates this one's HTTPS-only check, its stop on 401, 403 and 429, its refusal of a non-JSON challenge page and its refusal of an error payload, because fetch_feature_pages needs a fetch and _get is private. Those refusals are this project's promises about somebody else's server, and a fix to them currently reaches the consumer for one of its four layers.

Shape

One keyword argument, out_format: str = "json" (or the narrower geojson: bool = False), threaded into the query dict beside out_sr, which is already sent only when given for exactly this reason.

fetch_layer should probably not take it, since it reads feature["attributes"] and a GeoJSON page has none. That is an argument for putting it on iter_features only and saying so in the docstring.

Out of scope

  • Changing what this project acquires by default. The default request must not move: sources.py pins hashes of files fetched with it.
  • Any GeoJSON parsing, validation or conversion here. The caller asks for a format and gets what the service sends, which is what iter_features already promises when it says a feature is yielded whole.

Done when

  • A caller can request geojson and receives the service's own feature objects unaltered.
  • A test asserts the default query is byte-for-byte the one sent today, in the shape of test_the_default_request_is_the_one_this_project_has_always_made.
  • The paging rule is exercised under the non-default format too, so the offset stepping is known to be shared rather than assumed to be.

Pointers

Raised from the consuming project under its roadmap item 4.4, which says an acquisition gap found there is contributed here rather than worked around locally.

Prepared with AI assistance; reviewed before submission.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestfeatureMajor new capability proposal

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions