Skip to content

Cull non-finite projected positions to fix orthographic OOM - #593

Merged
teunbrand merged 4 commits into
posit-dev:mainfrom
cpsievert:fix/orthographic-mixed-visibility-oom
Oct 15, 2026
Merged

teunbrand merged 4 commits into
posit-dev:mainfrom
cpsievert:fix/orthographic-mixed-visibility-oom

Conversation

@cpsievert

Copy link
Copy Markdown
Collaborator

Fixes #592.

Problem

ST_Transform returns (inf, inf) for points behind the horizon of an azimuthal projection (orthographic, stereographic, gnomonic). The spatial geom clips geometries to the horizon, but position geoms (point, line, path, polygon, …) projected positions without culling, so inf coordinates flowed into the materialized layer data.

A point set split across the horizon (one point visible, one behind) then produced a geographic scale domain of [min, inf], and graticule_breaks iterated (start..=end) with end saturated to i64::MAX — allocating unboundedly until the process was OOM-killed. Any world-spanning point dataset under PROJECT TO orthographic was fatal (found via querychat's maps capability, where it killed the Shiny server with exit 137).

Fix

Two layers:

  1. Cull non-finite projected positions in project_position_columns (the shared projection path for all position geoms): rows whose projected x/y are non-finite are filtered via a new SqlDialect::sql_is_finite helper. The default predicate (ABS(x) < 1e308) is portable — three-valued logic plus NaN-above-all ordering on DuckDB/Postgres/Trino/Snowflake (and NaN→NULL on SQLite) makes it cull NULL/NaN/±inf uniformly; dialects with a native predicate can override.
  2. Guard ScaleTypeTrait::resolve_breaks against non-finite domains (returns None), so no break generator can iterate an unbounded range no matter how infinities reach a domain (e.g. inf in user data on any scale).

Tests

  • New integration test: mixed-visibility point set under PROJECT TO orthographic executes and returns exactly the visible point; an all-behind set executes and returns 0 rows (previously: OOM / min >= max early-return accident).
  • New unit test: resolve_breaks returns None for inf/NaN domains.
  • Golden SQL blessed for the five spatial-capable dialects (duckdb, postgres, snowflake, sqlite, trino) — the only diff is the new WHERE ABS(ST_X(...)) < 1e308 AND ... predicate on projected-layer materialization.
  • Full suite: 2077 passed, 0 failed (--features spatial).

Verified end-to-end through ggsql-python + querychat: the Python minimal repro from the issue executes at 0.11 GB peak RSS (previously killed at a 3 GB supervisor cap, ~9 GB unsupervised).

…on-finite domains

Fixes posit-dev#592.

ST_Transform returns (inf, inf) for points behind the horizon of an
azimuthal projection (orthographic, stereographic, gnomonic). The spatial
geom clips geometries to the horizon, but position geoms (point, line,
path, ...) projected positions without culling, so inf coordinates flowed
into the materialized layer data. A mixed-visibility point set (one point
visible, one behind the horizon) then produced a scale domain of
[min, inf], and graticule_breaks iterated (start..=i64::MAX), allocating
unboundedly — OOM-killing the process (observed via querychat's maps
capability, where any world-spanning point set under PROJECT TO
orthographic killed the Shiny server).

Two-layer fix:

- project_position_columns now filters rows whose projected coordinates
  are non-finite, via a new SqlDialect::sql_is_finite helper (portable
  default predicate; DuckDB/Postgres/Trino/Snowflake NaN ordering and
  three-valued logic make it cull NaN/inf/NULL uniformly). This culls
  behind-horizon points for all position geoms.
- ScaleTypeTrait::resolve_breaks now returns None for non-finite domains,
  so no break generator can iterate an unbounded range regardless of how
  infinities reach the domain (e.g. inf in user data).

Regression tests: mixed-visibility and all-behind point sets under
PROJECT TO orthographic execute and return 1 and 0 rows respectively;
resolve_breaks returns None for inf/NaN domains. Golden SQL for the five
spatial-capable dialects blessed with the new predicate.
@cpsievert
cpsievert requested a review from teunbrand October 14, 2026 23:48
Graticules never span more than the globe, so clamp the input range to
±180 before picking a step. Besides being the right semantics, this
prevents the (start..=end) collect from iterating ~i64::MAX times when
the domain is non-finite (e.g. infinite coordinates from degenerate
projections) or merely huge (as i64 saturates), either of which
allocates unboundedly.

The ±180 boundary conditions below the clamp are now equality checks.
Degenerate projections (e.g. points behind an orthographic horizon)
can yield infinite extents. Every bbox in the map pipeline flows
through BBox::from_df, so rejecting non-finite components there drops
a poisoned layer extent and lets the frame fall back to the world/clip
bbox instead.
DuckDB (isfinite), Trino (is_finite), and ClickHouse (isFinite) have a
dedicated predicate; BigQuery has IS_INF/IS_NAN but no single function.
These avoid the portable default's false positive on legitimate finite
values in [1e308, 1.8e308]. Other dialects keep the ABS(x) < 1e308
default (Spark's isnan alone doesn't cover infinity). Golden SQL for
duckdb/trino re-blessed with the new predicate.

@teunbrand teunbrand left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this Carson! I've added a few commits that I think are perhaps superfluously defensive, but probably can't hurt. If you could check at your end that this works as you expect, then I think this can be merged.

@cpsievert

Copy link
Copy Markdown
Collaborator Author

LGTM, thanks! 👍

@teunbrand
teunbrand merged commit 6509774 into posit-dev:main Oct 15, 2026
23 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

PROJECT TO orthographic OOMs on point layers with mixed-visibility points (split across the horizon)

2 participants