Skip to content

Commit 843306c

Browse files
authored
fix(db-postgres): near query can give incorrect results (#15907)
Fixes #14471 Ensure geospatial queries use true geodetic meters with the `::geography` cast instead of `ST_TRansform` to `EPSG 3857`
1 parent 0f67215 commit 843306c

2 files changed

Lines changed: 45 additions & 2 deletions

File tree

packages/drizzle/src/queries/parseParams.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -353,13 +353,13 @@ export function parseParams({
353353

354354
if (typeof maxDistance === 'number' && !Number.isNaN(maxDistance)) {
355355
geoConstraints.push(
356-
sql`ST_DWithin(ST_Transform(${table[columnName]}, 3857), ST_Transform(ST_SetSRID(ST_MakePoint(${lng}, ${lat}), 4326), 3857), ${maxDistance})`,
356+
sql`ST_DWithin(${table[columnName]}::geography, ST_SetSRID(ST_MakePoint(${lng}, ${lat}), 4326)::geography, ${maxDistance})`,
357357
)
358358
}
359359

360360
if (typeof minDistance === 'number' && !Number.isNaN(minDistance)) {
361361
geoConstraints.push(
362-
sql`ST_Distance(ST_Transform(${table[columnName]}, 3857), ST_Transform(ST_SetSRID(ST_MakePoint(${lng}, ${lat}), 4326), 3857)) >= ${minDistance}`,
362+
sql`ST_Distance(${table[columnName]}::geography, ST_SetSRID(ST_MakePoint(${lng}, ${lat}), 4326)::geography) >= ${minDistance}`,
363363
)
364364
}
365365
if (geoConstraints.length) {

test/collections-rest/int.spec.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1225,6 +1225,49 @@ describe('collections-rest', () => {
12251225
expect(result.docs).toHaveLength(0)
12261226
})
12271227

1228+
// https://github.com/payloadcms/payload/issues/14471 - ensure geospatial queries use true geodetic meters, not the distorted meters of EPSG:3857
1229+
it('should use true geodetic meters at high latitudes', async () => {
1230+
if (payload.db.name === 'sqlite') {
1231+
return
1232+
}
1233+
1234+
// A point ~10 km north of NYC: lat += 10000/111320 ≈ 0.0898°
1235+
const queryLng = -74.0059
1236+
const queryLat = 40.7128
1237+
const pointLat = queryLat + 0.0898 // ~10 km north
1238+
let createdId: number | string | undefined
1239+
1240+
try {
1241+
const created = await payload.create({
1242+
collection: pointSlug,
1243+
data: { point: [queryLng, pointLat] },
1244+
})
1245+
1246+
createdId = created.id
1247+
1248+
// Query with 12 km radius — the point at ~10 km should be within range.
1249+
// With the old EPSG:3857 approach, the effective radius at this latitude was
1250+
// only ~9 km, causing the point to be missed.
1251+
const response = await restClient.GET(`/${pointSlug}`, {
1252+
query: {
1253+
where: {
1254+
point: {
1255+
near: `${queryLng}, ${queryLat}, 12000`,
1256+
},
1257+
},
1258+
},
1259+
})
1260+
const result: any = await response.json()
1261+
1262+
expect(response.status).toEqual(200)
1263+
expect(result.docs.map((d: { id: number | string }) => d.id)).toContain(createdId)
1264+
} finally {
1265+
if (createdId !== undefined) {
1266+
await payload.delete({ collection: pointSlug, id: createdId })
1267+
}
1268+
}
1269+
})
1270+
12281271
it('should not return a point far away', async () => {
12291272
if (payload.db.name === 'sqlite') {
12301273
return

0 commit comments

Comments
 (0)