Spatial Queries

Spatial Relations, Distance, and GeoJSON

ST_Contains(), ST_Within(), ST_Intersects(), ST_Disjoint(), ST_Touches(), ST_Overlaps(), ST_Crosses() and ST_Equals() test exact shapes and return 1 or 0; the MBR... variants test only bounding rectangles. ST_Distance_Sphere() returns meters on a sphere; ST_Distance() on a geographic SRID measures on the ellipsoid. Distance cannot use the R-tree, so filter first with a shape the index can check: a buffer around the customer.

Nearest stores to a customer in Petaling Jaya, within 2,000 kmSQL
SET @me = ST_SRID(POINT(101.6067, 3.1073), 4326);
SELECT city, ROUND(ST_Distance_Sphere(loc, @me) / 1000, 1) AS sphere_km,
       ROUND(ST_Distance(loc, @me, 'kilometre'), 1) AS ellipsoid_km
  FROM stores WHERE ST_Within(loc, ST_Buffer(@me, 2000000)) ORDER BY sphere_km;
Output
+---------------+-----------+--------------+
| city          | sphere_km | ellipsoid_km |
+---------------+-----------+--------------+
| Kuala Lumpur  |       9.6 |          9.6 |
| Singapore     |     313.9 |        313.5 |
| Kota Kinabalu |    1634.9 |       1636.3 |
+---------------+-----------+--------------+

EXPLAIN FORMAT=JSON showed ["sp_loc", "index_range_scan"] for the filter. The buffer is a 32-segment polygon, so add ST_Distance_Sphere(loc, @me) <= r when the radius must be exact. The cheaper sphere was 1.4 km off over 1,635 km. On SRID 0, ST_Distance() is plain Euclidean geometry.

GeoJSON, the format browser maps consume, is always longitude first: ST_AsGeoJSON(loc) for Kota Kinabalu gave {"type": "Point", "coordinates": [116.0735, 5.9804]}, and ST_GeomFromGeoJSON() reads such text back as SRID 4326. Nest ST_AsGeoJSON(loc) in JSON_OBJECT('type', 'Feature', 'geometry', ...), aggregate with JSON_ARRAYAGG() (GROUP_CONCAT and JSON), and one query returns a whole FeatureCollection that PHP can hand to Leaflet 19,080 or OpenLayers 46,747 .