diff --git a/packages/drizzle/src/queries/parseParams.ts b/packages/drizzle/src/queries/parseParams.ts index 01ecb51630e..94a25e2342a 100644 --- a/packages/drizzle/src/queries/parseParams.ts +++ b/packages/drizzle/src/queries/parseParams.ts @@ -349,13 +349,13 @@ export function parseParams({ if (typeof maxDistance === 'number' && !Number.isNaN(maxDistance)) { geoConstraints.push( - sql`ST_DWithin(ST_Transform(${table[columnName]}, 3857), ST_Transform(ST_SetSRID(ST_MakePoint(${lng}, ${lat}), 4326), 3857), ${maxDistance})`, + sql`ST_DWithin(${table[columnName]}::geography, ST_SetSRID(ST_MakePoint(${lng}, ${lat}), 4326)::geography, ${maxDistance})`, ) } if (typeof minDistance === 'number' && !Number.isNaN(minDistance)) { geoConstraints.push( - sql`ST_Distance(ST_Transform(${table[columnName]}, 3857), ST_Transform(ST_SetSRID(ST_MakePoint(${lng}, ${lat}), 4326), 3857)) >= ${minDistance}`, + sql`ST_Distance(${table[columnName]}::geography, ST_SetSRID(ST_MakePoint(${lng}, ${lat}), 4326)::geography) >= ${minDistance}`, ) } if (geoConstraints.length) { diff --git a/test/collections-rest/int.spec.ts b/test/collections-rest/int.spec.ts index 41111d4eb62..d5997253f4f 100644 --- a/test/collections-rest/int.spec.ts +++ b/test/collections-rest/int.spec.ts @@ -1225,6 +1225,49 @@ describe('collections-rest', () => { expect(result.docs).toHaveLength(0) }) + // https://github.com/payloadcms/payload/issues/14471 - ensure geospatial queries use true geodetic meters, not the distorted meters of EPSG:3857 + it('should use true geodetic meters at high latitudes', async () => { + if (payload.db.name === 'sqlite') { + return + } + + // A point ~10 km north of NYC: lat += 10000/111320 ≈ 0.0898° + const queryLng = -74.0059 + const queryLat = 40.7128 + const pointLat = queryLat + 0.0898 // ~10 km north + let createdId: number | string | undefined + + try { + const created = await payload.create({ + collection: pointSlug, + data: { point: [queryLng, pointLat] }, + }) + + createdId = created.id + + // Query with 12 km radius — the point at ~10 km should be within range. + // With the old EPSG:3857 approach, the effective radius at this latitude was + // only ~9 km, causing the point to be missed. + const response = await restClient.GET(`/${pointSlug}`, { + query: { + where: { + point: { + near: `${queryLng}, ${queryLat}, 12000`, + }, + }, + }, + }) + const result: any = await response.json() + + expect(response.status).toEqual(200) + expect(result.docs.map((d: { id: number | string }) => d.id)).toContain(createdId) + } finally { + if (createdId !== undefined) { + await payload.delete({ collection: pointSlug, id: createdId }) + } + } + }) + it('should not return a point far away', async () => { if (payload.db.name === 'sqlite') { return