Skip to content

Commit 37c9bb4

Browse files
committed
PG: add a SPATIAL_FILTER_INTERSECTION=LOCAL/SERVER open option
- .. oo:: SPATIAL_FILTER_INTERSECTION :choices: LOCAL, SERVER :default: LOCAL :since: 3.13 Since GDAL 3.13, spatial filters are evaluated using full geometry intersection rather than only bounding box intersection, as in earlier versions. The ``SPATIAL_FILTER_INTERSECTION`` open option controls where this intersection operation is performed. Even when the intersection is evaluated locally (which is the default), the server still applies a bounding box-based spatial filter to efficiently retrieve candidate features. Note that for PostGIS geometry columns of type GEOGRAPHY, the current implementation performs the intersection test on the client side, by dealing with geographies as if they were cartesian geometries.
1 parent 77eb999 commit 37c9bb4

File tree

7 files changed

+53
-10
lines changed

7 files changed

+53
-10
lines changed

autotest/ogr/ogr_pg.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6384,13 +6384,19 @@ def test_ogr_pg_field_truncation(pg_ds):
63846384

63856385
@gdaltest.enable_exceptions()
63866386
@pytest.mark.parametrize("GEOM_TYPE", ["geometry", "geography"])
6387-
def test_ogr_pg_geometry_intersection_spatial_filter(pg_ds, use_postgis, GEOM_TYPE):
6387+
@pytest.mark.parametrize("open_options", [None, ["SPATIAL_FILTER_INTERSECTION=SERVER"]])
6388+
@pytest.mark.require_geos
6389+
def test_ogr_pg_geometry_intersection_spatial_filter(
6390+
pg_ds, use_postgis, GEOM_TYPE, open_options
6391+
):
6392+
6393+
ds = reconnect(pg_ds, open_options=open_options)
63886394

63896395
if use_postgis:
63906396
srs = osr.SpatialReference(epsg=4326)
6391-
lyr = pg_ds.CreateLayer("test", srs, options=["GEOM_TYPE=" + GEOM_TYPE])
6397+
lyr = ds.CreateLayer("test", srs, options=["GEOM_TYPE=" + GEOM_TYPE])
63926398
else:
6393-
lyr = pg_ds.CreateLayer("test")
6399+
lyr = ds.CreateLayer("test")
63946400
f = ogr.Feature(lyr.GetLayerDefn())
63956401
f.SetGeometry(ogr.CreateGeometryFromWkt("POINT (0.5 0.5)"))
63966402
lyr.CreateFeature(f)
@@ -6413,7 +6419,7 @@ def test_ogr_pg_geometry_intersection_spatial_filter(pg_ds, use_postgis, GEOM_TY
64136419

64146420
lyr.SetSpatialFilter(None)
64156421

6416-
with pg_ds.ExecuteSQL("SELECT * FROM test") as sql_lyr:
6422+
with ds.ExecuteSQL("SELECT * FROM test") as sql_lyr:
64176423
sql_lyr.SetSpatialFilter(
64186424
ogr.CreateGeometryFromWkt(
64196425
"POLYGON ((0 0,0 1,1 1,1 0.6,0.8 0.6,0.8 0.4,1 0.4,1 0,0 0))"

doc/source/drivers/vector/pg.rst

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,23 @@ The following open options are supported:
243243
:oo::`PRELUDE_STATEMENTS`, the appropriate CLOSING_STATEMENTS would be
244244
"COMMIT".
245245

246+
- .. oo:: SPATIAL_FILTER_INTERSECTION
247+
:choices: LOCAL, SERVER
248+
:default: LOCAL
249+
:since: 3.13
250+
251+
Since GDAL 3.13, spatial filters are evaluated using full geometry intersection
252+
rather than only bounding box intersection, as in earlier versions.
253+
The ``SPATIAL_FILTER_INTERSECTION`` open option controls where this
254+
intersection operation is performed. Even when the intersection is evaluated
255+
locally (which is the default), the server still applies a bounding
256+
box-based spatial filter to efficiently retrieve candidate features.
257+
258+
Note that for PostGIS geometry columns of type GEOGRAPHY, the current
259+
implementation performs the intersection test on the client side, by dealing
260+
with geographies as if they were cartesian geometries.
261+
262+
246263
Dataset Creation Options
247264
~~~~~~~~~~~~~~~~~~~~~~~~
248265

ogr/ogrsf_frmts/pg/ogr_pg.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -604,6 +604,8 @@ class OGRPGDataSource final : public GDALDataset
604604
bool m_bHasWritePermissionsOnMetadataTableRun = false;
605605
bool m_bHasWritePermissionsOnMetadataTableSuccess = false;
606606

607+
bool m_bSpatialFilterIntersectionIsLocal = true;
608+
607609
void LoadTables();
608610

609611
CPLString osDebugLastTransactionCommand{};
@@ -714,6 +716,11 @@ class OGRPGDataSource final : public GDALDataset
714716
bool CreateMetadataTableIfNeeded();
715717
bool HasOgrSystemTablesMetadataTable();
716718
bool HasWritePermissionsOnMetadataTable();
719+
720+
bool IsSpatialFilterIntersectionLocal() const
721+
{
722+
return m_bSpatialFilterIntersectionIsLocal;
723+
}
717724
};
718725

719726
#endif /* ndef OGR_PG_H_INCLUDED */

ogr/ogrsf_frmts/pg/ogrpgdatasource.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,11 @@ int OGRPGDataSource::Open(const char *pszNewName, int bUpdate, int bTestOpen,
261261
CPLAssert(nLayers == 0);
262262
papszOpenOptions = CSLDuplicate(papszOpenOptionsIn);
263263

264+
m_bSpatialFilterIntersectionIsLocal =
265+
EQUAL(CSLFetchNameValueDef(papszOpenOptions,
266+
"SPATIAL_FILTER_INTERSECTION", "LOCAL"),
267+
"LOCAL");
268+
264269
const char *pszPreludeStatements =
265270
CSLFetchNameValue(papszOpenOptions, "PRELUDE_STATEMENTS");
266271
if (pszPreludeStatements)

ogr/ogrsf_frmts/pg/ogrpgdrivercore.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,12 @@ void OGRPGDriverSetCommonMetadata(GDALDriver *poDriver)
7979
" <Option name='SKIP_VIEWS' type='boolean' description='Whether "
8080
"views should be omitted from the list' "
8181
"default='NO'/>"
82+
" <Option name='SPATIAL_FILTER_INTERSECTION' type='string-select' "
83+
"description='Whether geometry intersection of spatial filter is "
84+
"evaluated locally or on the server' default='LOCAL'>"
85+
" <Value>LOCAL</Value>"
86+
" <Value>DATABASE</Value>"
87+
" </Option>"
8288
"</OpenOptionList>");
8389

8490
poDriver->SetMetadataItem(GDAL_DMD_CREATIONOPTIONLIST,

ogr/ogrsf_frmts/pg/ogrpgresultlayer.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,8 @@ OGRFeature *OGRPGResultLayer::GetNextFeature()
275275

276276
if ((m_poFilterGeom == nullptr || poGeomFieldDefn == nullptr ||
277277
(poGeomFieldDefn->ePostgisType == GEOM_TYPE_GEOMETRY &&
278-
poGeomFieldDefn->nSRSId > 0) ||
278+
poGeomFieldDefn->nSRSId > 0 &&
279+
!poDS->IsSpatialFilterIntersectionLocal()) ||
279280
FilterGeometry(poFeature->GetGeomFieldRef(m_iGeomFieldFilter))) &&
280281
(m_poAttrQuery == nullptr || m_poAttrQuery->Evaluate(poFeature)))
281282
return poFeature;
@@ -305,7 +306,8 @@ OGRErr OGRPGResultLayer::ISetSpatialFilter(int iGeomField,
305306
if (m_poFilterGeom != nullptr && poDS->sPostGISVersion.nMajor >= 0)
306307
{
307308
if (poGeomFieldDefn->ePostgisType == GEOM_TYPE_GEOMETRY &&
308-
poGeomFieldDefn->nSRSId > 0)
309+
poGeomFieldDefn->nSRSId > 0 &&
310+
!poDS->IsSpatialFilterIntersectionLocal())
309311
{
310312
char *pszHexEWKB = OGRGeometryToHexEWKB(
311313
m_poFilterGeom, poGeomFieldDefn->nSRSId,

ogr/ogrsf_frmts/pg/ogrpgtablelayer.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1060,7 +1060,8 @@ void OGRPGTableLayer::BuildWhere()
10601060
poGeomFieldDefn->ePostgisType == GEOM_TYPE_GEOGRAPHY))
10611061
{
10621062
poGeomFieldDefn->GetSpatialRef(); // make sure nSRSId is resolved
1063-
if (poGeomFieldDefn->ePostgisType == GEOM_TYPE_GEOMETRY)
1063+
if (poGeomFieldDefn->ePostgisType == GEOM_TYPE_GEOMETRY &&
1064+
!poDS->IsSpatialFilterIntersectionLocal())
10641065
{
10651066
char *pszHexEWKB = OGRGeometryToHexEWKB(
10661067
m_poFilterGeom, poGeomFieldDefn->nSRSId,
@@ -1188,12 +1189,11 @@ OGRFeature *OGRPGTableLayer::GetNextFeature()
11881189
return nullptr;
11891190

11901191
/* We just have to look if there is a geometry filter */
1191-
/* If there's a PostGIS geometry column, the spatial filter */
1192-
/* is already taken into account in the select request */
11931192
/* The attribute filter is always taken into account by the select
11941193
* request */
11951194
if (m_poFilterGeom == nullptr || poGeomFieldDefn == nullptr ||
1196-
poGeomFieldDefn->ePostgisType == GEOM_TYPE_GEOMETRY ||
1195+
(poGeomFieldDefn->ePostgisType == GEOM_TYPE_GEOMETRY &&
1196+
!poDS->IsSpatialFilterIntersectionLocal()) ||
11971197
FilterGeometry(poFeature->GetGeomFieldRef(m_iGeomFieldFilter)))
11981198
{
11991199
if (iFIDAsRegularColumnIndex >= 0)

0 commit comments

Comments
 (0)