features: use the GeoPackage spatial index for spatial predicates - #618
Merged
Conversation
MinMaxDeriver seeded the maxima with Double.MIN_VALUE, which is the smallest positive value rather than the most negative one, so every Math.max against it won on an axis whose coordinates are all negative and the maximum stayed at 4.9E-324. Spatial extents are derived from this, so the extent of data west of Greenwich or south of the equator came out wrong.
A spatial predicate on a GPKG provider was encoded as a bare ST_Intersects(geom, ...). Unlike PostGIS, where ST_Intersects embeds the && bounding box operator and GiST applies by itself, SQLite cannot infer from a spatial operator that the R-Tree of the geometry column is relevant: unless the query names the rtree_<table>_<column> virtual table, the operator is evaluated for every row. The dialect now contributes a bounding box predicate over that table, added as a conjunct to the exact predicate. On a 41.6 GB GeoPackage with 24.6 million rows, a bbox request for ten features goes from 149 s to 0.3 s while returning the same features. The conjunct is only added where it is implied by the exact predicate, so that it can change neither the result nor the meaning of the predicate under negation: - only for operators whose match implies that the bounding boxes intersect, which excludes S_DISJOINT - only for a geometry column of the main table, since the semi-join form of a joined property would need the predicate inside its subquery - only for a column that an R-Tree is known for, determined once per provider on startup - never below a negation, because a NULL geometry has no entry in the R-Tree while the exact predicate is NULL for it, and negating those two is not the same The index is joined on the primary key of the feature table rather than on rowid. A GeoPackage feature table is required to have an INTEGER PRIMARY KEY, which SQLite makes an alias of rowid, but a file that does not follow that requirement would otherwise match the wrong rows.
A spatial predicate on a GPKG provider was encoded as a bare ST_Intersects(geom, ...). Unlike PostGIS, where ST_Intersects embeds the && bounding box operator and GiST applies by itself, SQLite cannot infer from a spatial operator that the R-Tree of the geometry column is relevant: unless the query names the rtree_<table>_<column> virtual table, the operator is evaluated for every row. The dialect now contributes a bounding box predicate over that table, added as a conjunct to the exact predicate. On a 41.6 GB GeoPackage with 24.6 million rows, a bbox request for ten features goes from 149 s to 0.3 s while returning the same features. The conjunct is only added where it is implied by the exact predicate, so that it can change neither the result nor the meaning of the predicate under negation: - only for operators whose match implies that the bounding boxes intersect, which excludes S_DISJOINT - only for a geometry column of the main table, since the semi-join form of a joined property would need the predicate inside its subquery - only for a column that an R-Tree is known for, determined once per provider on startup - never below a negation, because a NULL geometry has no entry in the R-Tree while the exact predicate is NULL for it, and negating those two is not the same The index is joined on the primary key of the feature table rather than on rowid. A GeoPackage feature table is required to have an INTEGER PRIMARY KEY, which SQLite makes an alias of rowid, but a file that does not follow that requirement would otherwise match the wrong rows.
…latform-spatial into gpkg-spatial-index
azahnen
approved these changes
Aug 25, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes ldproxy/ldproxy#1758.
A spatial predicate on a GPKG provider was encoded as a bare
ST_Intersects(geom, ...). Unlike PostGIS, where ST_Intersects embeds the&&bounding box operator and GiST applies by itself, SQLite cannot infer from a spatial operator that the R-Tree of the geometry column is relevant: unless the query names thertree_<table>_<column>virtual table, the operator is evaluated for every row.The dialect now contributes a bounding box predicate over that table, added as a conjunct to the exact predicate. The conjunct is only added where it is implied by the exact predicate, so that it can change neither the result nor the meaning of the predicate under negation:
The changes also include a fix for another bug:
MinMaxDeriverseeded the maxima withDouble.MIN_VALUE, which is the smallest positive value rather than the most negative one, so everyMath.maxagainst it won on an axis whose coordinates are all negative and the maximum stayed at 4.9E-324. Spatial extents are derived from this, so the extent of data west of Greenwich or south of the equator came out wrong.