Skip to content

Commit

Permalink
MySQL: disable server-side filtering when SRS is geographic with MySQ…
Browse files Browse the repository at this point in the history
…L >= 8 (fixes qgis/QGIS#55463)

It seems spatial predicates are totally broken when using geographic SRS. For some reason

select MBRIntersects(ST_GeomFromText('POLYGON((-90 -90, 90 -90, 90 90, -90 90, -90 -90))', 4326), ST_GeomFromText('POINT(0 0)', 4326));

returns true as expected

But

select MBRIntersects(ST_GeomFromText('POLYGON((-179 -89, 179 -89, 179 89, -179 89, -179 -89))', 4326, 'axis-order=long-lat'), ST_GeomFromText('POINT(0 0)', 4326));

returns false !!!!

And

select MBRIntersects(ST_GeomFromText('POLYGON((-179 -89, 179 -89, 179 89, -179 89, -179 -89))', 32631), ST_GeomFromText('POINT(0 0)', 32631));

returns true as expected

Consequence, it seems safer to disable spatial filtering on layers with geographic coordinates with MySQL...
  • Loading branch information
rouault committed Jan 28, 2024
1 parent a79a07c commit e079699
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 14 deletions.
5 changes: 5 additions & 0 deletions autotest/ogr/ogr_mysql.py
Original file line number Diff line number Diff line change
Expand Up @@ -960,6 +960,11 @@ def test_ogr_mysql_longlat(mysql_ds, mysql_is_8_or_later):
pytest.fail("Not found SRID definition with GEOMETORY field.")
mysql_ds.ReleaseResultSet(sql_lyr)

lyr.SetSpatialFilterRect(-181, -91, 181, 91)
lyr.ResetReading()
f = lyr.GetNextFeature()
ogrtest.check_feature_geometry(f, geom)


###############################################################################
# Test writing and reading back geometries
Expand Down
2 changes: 2 additions & 0 deletions ogr/ogrsf_frmts/mysql/ogr_mysql.h
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,8 @@ class OGRMySQLLayer CPL_NON_FINAL : public OGRLayer
/* custom methods */
virtual OGRFeature *RecordToFeature(char **papszRow, unsigned long *);
virtual OGRFeature *GetNextRawFeature();

bool HasWorkingSpatialFilter();
};

/************************************************************************/
Expand Down
15 changes: 15 additions & 0 deletions ogr/ogrsf_frmts/mysql/ogrmysqllayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -373,3 +373,18 @@ const OGRSpatialReference *OGRMySQLGeomFieldDefn::GetSpatialRef() const

return poSRS;
}

/************************************************************************/
/* HasWorkingSpatialFilter() */
/************************************************************************/

bool OGRMySQLLayer::HasWorkingSpatialFilter()
{
if (poDS->GetMajorVersion() < 8 || poDS->IsMariaDB())
return true;
auto l_poSRS = GetSpatialRef();
// Spatial filtering with geographic SRS with MySQL >= 8 is too unreliable.
// Cf the following which returns 0...
// SELECT MBRIntersects(ST_GeomFromText('POLYGON((-179 -89, 179 -89, 179 89, -179 89, -179 -89))', 4326, 'axis-order=long-lat'), ST_GeomFromText('POINT(0 0)', 4326));
return !l_poSRS || !l_poSRS->IsGeographic();
}
45 changes: 31 additions & 14 deletions ogr/ogrsf_frmts/mysql/ogrmysqltablelayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
#include "cpl_string.h"
#include "ogr_mysql.h"

#include <algorithm>

/************************************************************************/
/* OGRMySQLTableLayer() */
/************************************************************************/
Expand Down Expand Up @@ -458,7 +460,7 @@ void OGRMySQLTableLayer::BuildWhere()
pszWHERE = (char *)CPLMalloc(nWHERELen);
pszWHERE[0] = '\0';

if (m_poFilterGeom != nullptr && pszGeomColumn)
if (m_poFilterGeom != nullptr && pszGeomColumn && HasWorkingSpatialFilter())
{
char szEnvelope[400];
OGREnvelope sEnvelope;
Expand All @@ -467,22 +469,34 @@ void OGRMySQLTableLayer::BuildWhere()
// POLYGON((MINX MINY, MAXX MINY, MAXX MAXY, MINX MAXY, MINX MINY))
m_poFilterGeom->getEnvelope(&sEnvelope);

const OGRSpatialReference *l_poSRS = GetSpatialRef();
const bool bNeedAxisOrder =
(poDS->GetMajorVersion() >= 8 && !poDS->IsMariaDB() && l_poSRS &&
l_poSRS->IsGeographic());

double dfMinX = sEnvelope.MinX;
double dfMinY = sEnvelope.MinY;
double dfMaxX = sEnvelope.MaxX;
double dfMaxY = sEnvelope.MaxY;
if (bNeedAxisOrder)
{
// MySQL is super restrictive on coordinate ranges for filter with
// geographic coordinates...
constexpr double EPS = 1e-5;
dfMinX = std::max(-180.0 + EPS, dfMinX);
dfMinY = std::max(-90.0 + EPS, dfMinY);
dfMaxX = std::min(180.0 - EPS, dfMaxX);
dfMaxY = std::min(90.0 - EPS, dfMaxY);
}

CPLsnprintf(szEnvelope, sizeof(szEnvelope),
"POLYGON((%.18g %.18g, %.18g %.18g, %.18g %.18g, %.18g "
"%.18g, %.18g %.18g))",
sEnvelope.MinX, sEnvelope.MinY, sEnvelope.MaxX,
sEnvelope.MinY, sEnvelope.MaxX, sEnvelope.MaxY,
sEnvelope.MinX, sEnvelope.MaxY, sEnvelope.MinX,
sEnvelope.MinY);

const char *pszAxisOrder = "";
OGRSpatialReference *l_poSRS = GetSpatialRef();
if (poDS->GetMajorVersion() >= 8 && !poDS->IsMariaDB() && l_poSRS &&
l_poSRS->IsGeographic())
{
pszAxisOrder = ", 'axis-order=long-lat'";
}
dfMinX, dfMinY, dfMaxX, dfMinY, dfMaxX, dfMaxY, dfMinX,
dfMaxY, dfMinX, dfMinY);

const char *pszAxisOrder =
bNeedAxisOrder ? ", 'axis-order=long-lat'" : "";
snprintf(
pszWHERE, nWHERELen, "WHERE MBRIntersects(%s('%s', %d%s), `%s`)",
poDS->GetMajorVersion() >= 8 ? "ST_GeomFromText" : "GeomFromText",
Expand All @@ -497,6 +511,9 @@ void OGRMySQLTableLayer::BuildWhere()
snprintf(pszWHERE + strlen(pszWHERE), nWHERELen - strlen(pszWHERE),
"&& (%s) ", pszQuery);
}

if (pszWHERE[0])
CPLDebug("MYSQL", "Filter: %s", pszWHERE);
}

/************************************************************************/
Expand Down Expand Up @@ -634,7 +651,7 @@ int OGRMySQLTableLayer::TestCapability(const char *pszCap)
return TRUE;

else if (EQUAL(pszCap, OLCFastSpatialFilter))
return TRUE;
return HasWorkingSpatialFilter();

else if (EQUAL(pszCap, OLCFastGetExtent))
return TRUE;
Expand Down

0 comments on commit e079699

Please sign in to comment.