ST_Distance_Sphere in mysql not giving accurate distance between two locations - mysql

My requirement is to calculate the distance between two locations on a given map using mysql. I found a function in mysql named ST_Distance_Sphere which returns the minimum spherical distance between two locations and/or multi locations on a sphere in meters.
When I computed the distance between two locations using ST_Distance_Sphere and the lat_lng_distance function , I found that the ST_Distance_Sphere is not giving the same distance as that of the lat_lng_distance function.
My lat_lng_distance function code is as follows
CREATE FUNCTION `lat_lng_distance` (lat1 FLOAT, lng1 FLOAT, lat2 FLOAT, lng2 FLOAT)
RETURNS FLOAT
DETERMINISTIC
BEGIN
RETURN 6371 * 2 * ASIN(SQRT(
POWER(SIN((lat1 - abs(lat2)) * pi()/180 / 2),
2) + COS(lat1 * pi()/180 ) * COS(abs(lat2) *
pi()/180) * POWER(SIN((lng1 - lng2) *
pi()/180 / 2), 2) ));
END
The two locations ((38.898556,-77.037852),(38.897147,-77.043934)) passed to the ST_Distance_Sphere and lat_lng_distance function is as follows
SET #pt1 = ST_GeomFromText('POINT (38.898556 -77.037852)');
SET #pt2 = ST_GeomFromText('POINT (38.897147 -77.043934 )');
SELECT ST_Distance_Sphere(#pt1, #pt2)/1000,lat_lng_distance(38.898556,-77.037852,38.897147,-77.043934 );
The Results Obtained is as follows
I checked the distance between the two locations on google maps and found that lat_lng_distance is close to the actual distance between the two locations. Can someone let me know why is the ST_Distance_Sphere not giving accurate distance between two locations?

ST_DISTANCE_SPHERE requires points to be expressed as POINT(longitude, latitude), you have them reversed in your code
set #lat1 = 38.898556;
set #lon1 = -77.037852;
set #lat2 = 38.897147;
set #lon2 = -77.043934;
SET #pt1 = point(#lon1, #lat1);
SET #pt2 = point(#lon2, #lat2);
SELECT ST_Distance_Sphere(#pt1, #pt2)/1000,
lat_lng_distance(#lat1,#lon1,#lat2,#lon2);
+-------------------------------------+-------------------------------------------+
| ST_Distance_Sphere(#pt1, #pt2)/1000 | lat_lng_distance(#lat1,#lon1,#lat2,#lon2) |
+-------------------------------------+-------------------------------------------+
| 0.549154584458455 | 0.5496311783790588 |
+-------------------------------------+-------------------------------------------+
This gives a result that is much closer to the value returned by your function.

For all who are working with MYSQL 8:
For all mysql geolocation functions there must be the right SRID used, otherwise you won't get the right results.
Most commenly used is SRID 4326 (GPS Coordinates, Google Earth) AND SRID 3857 (used on Google Maps, OpenStreetMap, and most other web maps).
Example of a correct distance calculation between two points:
SELECT ST_Distance(ST_GeomFromText('POINT(51.513 -0.08)', 4326), ST_GeomFromText('POINT(37.745 -122.4383)', 4326)) / 1000 AS km;
Here is a good explanation of this topic:
https://medium.com/maatwebsite/the-best-way-to-locate-in-mysql-8-e47a59892443
There are some very good explanations from the mysqlserverteam:
https://mysqlserverteam.com/spatial-reference-systems-in-mysql-8-0/
https://mysqlserverteam.com/geography-in-mysql-8-0/

First of all, you could not use default SRID of 0 to do any calculations. When you use geometry from text function you have to provide 4326 (SRID that is degrees) as this is what your input format is. MYSQL might not care about it, but it should done as every serious GIS database does care and demands that input SRID was specified.
Second longitude is X and latitude is Y (not another way around)
SET #pt1 = ST_GeomFromText('POINT (-77.037852 38.898556 )', 4326);
SET #pt2 = ST_GeomFromText('POINT (-77.043934 38.897147 )',4326);
Last but not least when you are calculating distance you must transform coordinates to a local most precise SRID available for the region you are.
For example SRID 2877 is used for USA (where according your coordinates you are).
MYSQL ST_Distance_Sphere function does not care about input SRID and always return results in meters.
However it is not generally right and all other database use designated SRID units of measures applicable to it.
Bellow I am trying to do things right and transforming SRID to 2877 even MYSQL would work the same way if we left everything as 4326 (google mercator).
For 2877 PostGRES would return results in feet for the same query but MYSQL is still giving back meters. So output is devided by 1609 and we are getting the correct result of around 0.34 miles. It is a correct value as was tested using different methods
SELECT ST_Distance_Sphere(ST_GeomFromText(ST_AsText(#pt1),2877), ST_GeomFromText(ST_AsText(#pt2),2877))/1609.344;

As an aside, MySQL internally implements this with an obscure and dated constant. So it really depends on your definition of accurate.
ST_Distance_Sphere
So in essence, the radius in MySQL was lifted from a lazy-copy-job from PostGIS that converted a radius in miles to meters from an obscure constant from a random 20-year old PostgreSQL module.

Related

MariaDB : calculate distance between 2 points in km

I try to get some approximate distance between 2 points in mariaDB.
I use that SQL:
SELECT st_distance(POINT(50.6333,3.0667),p) from p
It outputs results such as:
0
1.9128040446888426
8.103248262271125
It seems mariaDB does not handle SRID.
Is there a way to convert these values to km ? (looks like multiplying by 110 is quite correct)
My goal is to avoid handling maths such as sin, cos, atan and approximate result is ok for me.
The result returned by st_distance are not kilometer but minutes.
For a circumference of the equator of d = 40.075km, the distance between two minutes is d / 360 = 111,319 km.
While the distance between the latitudes is constant, the distance between the longitudes from the equator to the pole caps decreases constantly. According to your example the point from one location must be somewhere in France, where the distance between longitudes is around 70km.
Since you don't want to use the Haversine formula, you can also use the Pythagorean theorem to get a more accurate result:
# Distance between Eiffel tower and Lille
SELECT ST_DISTANCE(GeomFromText("Point(50.6333 3.0669)"), GeomFromText("Point(48.853. 2.348)")) * 111.38;
-> 213.84627307672486
select sqrt(pow((50.63333 - 48.853) * 111.38,2) + pow((3.0669 - 2.348) * 70, 2));
->204.57903071304386
ST_DISTANCE is designed for flat-earth advocates.
ST_DISTANCE_SPHERE is available in InnoDB as of 5.7.
https://dev.mysql.com/doc/refman/5.7/en/spatial-convenience-functions.html#function_st-distance-sphere

Distance in meters between two Spacial Points in MySQL query

I am trying to query a MySQL database (version 5.7.15) to retrieve all locations that are within 300 meters from some coordinates (40.7542, -73.9961 in my case):
SELECT *
FROM location
WHERE st_distance_sphere(latlng, POINT(40.7542, -73.9961)) <= 300
From the MySQL documentation:
ST_Distance_Sphere(g1, g2 [, radius])
Returns the mimimum spherical distance between two points and/or
multipoints on a sphere, in meters, or NULL if any geometry argument
is NULL or empty.
Unfortunately, the query also returns points that are more than 300 meters away from POINT(40.7542, -73.9961) such as:
POINT(40.7501, -73.9949) (~ 470 meters in real life)
POINT(40.7498, -73.9937) (~ 530 meters in real life)
Note that in MySql the order of coordinates are:
1. POINT(lng, lat) - no SRID
2. ST_GeomFromText('POINT(lat lng)', 4326) - with SRID
select st_distance_sphere(POINT(-73.9949,40.7501), POINT( -73.9961,40.7542))
will return 466.9696023582369, as expected, and 466.9696023582369 > 300 of course
Just to make it clear for future people (like myself):
Mituha Sergey has answered the question in comments on the OP. The problem was that OP was using POINT(lat, lng) when in fact MySQL expects POINT(lng, lat).
Not sure about the time OP posted the question, but as of today the official documentation makes it a bit clearer:
The geometry arguments should consist of points that specify
(longitude, latitude) coordinate values:
Longitude and latitude are the first and second coordinates of the point, respectively.
Both coordinates are in degrees.
Longitude values must be in the range (-180, 180]. Positive values are east of the prime meridian.
Latitude values must be in the range [-90, 90]. Positive values are north of the equator.
From: https://dev.mysql.com/doc/refman/5.7/en/spatial-convenience-functions.html#function_st-distance-sphere
And if you're getting "invalid arguments" errors it's probably because of that. Try adding WHERE lat between -90 and 90 AND lng between -180 and 180 just to be on the safe side haha :)

postgis get geometries within a radius of meters using wgs84

select *
from zones z
where st_intersects(z.geom, st_Buffer(ST_SetSRID(ST_Point( 2.336031, 48.863172), 3857),1));
It looks like the '1' in the st_buffer is being interpreted as degrees, I need to find geometries within n meters of a given point.
I did find a reference to a new function (st_Buffer_Meters) but that didn't seem to do anything different.
The actual query that ended up working was:
select nom
from zones s
where ST_DWITHIN(Geography(ST_Transform(s.geom,4326)), ST_Point($1, $2) ,$3);`
Where $1 is Longitude, $2 is Latitude and $3 is distance in meters.
In addition I changed my imported data from SRID 3857 to SRID 4326
Here's the gis.stackexchange question: https://gis.stackexchange.com/questions/118472/postgis-get-geometries-within-a-radius-of-n-meters-using-wgs84/118476#118476
And here's the 'This has been asked before link https://gis.stackexchange.com/questions/77688/postgis-get-the-points-that-are-x-meters-near-another-point-in-meters
First, I simplified your query to use a distance function, which will do the same thing as your query performs with fewer words. ST_Distance_Sphere will return the minimum distance between two geometries in meters. However I had some difficulty in using your SRID so I use ST_Transform to transform the SRID to 4326
select *
from zones z
where st_distance_sphere(z.geom,
ST_TRANSFORM(ST_SetSRID(ST_Point( 2.336031, 48.863172), 3857), 4326
)< 1000;

Get spatial points within radius using NHibernate Spatial

I'm currently trying to naivly get the k-nearest neighbors of a set of points, given a value k, a coordinate to use as center and a radius serving as the max distance to find points within. I'm using geographical points (SRID 4326) on a MSSQL 2008 database.
The neighbors are naivly found ordering the query by the distance to the point and limiting the result.
My trouble starts at limiting the points by the given radius. The distances returned by the Distance function are much larger than expected, which I understand to be a caused by the SRID 4326. This is OK as along as it is only used for ordering, but when I have to compare these values to a relative distance, say 200 metres, these large numbers wont do.
My question then is: is there a smarter way of limiting the points by a radius using NHibernate Spatial queries, or is there a way to convert this radius into the some meassurement similar to that used by the Distance function?
This is my query as it looks now:
output = NHibernateSession.GetSession().CreateQuery(#"select p from POI p
where NHSP.Distance(p.PointCoord, :coord) <= :maxDistance
order by NHSP.Distance(p.PointCoord, :coord)")
.SetParameter("coord", coord,
NHibernateUtil.Custom(typeof(Wgs84GeographyType)))
.SetParameter<double>("maxDistance", radius)
.SetMaxResults(k)
.Enumerable<POI>();
Just as an example I have these two points:
POINT(7 1)
POINT(7 3)
My expected distance is 2, but the distance calculated by the mssql STDistance function gives 221151.479533501 as a result. I just cant get my mind to make sense about this.
this happens because of the difference between the geography data-type and geometry data type.
Best explained with an example.
declare #point1 as geography
declare #point2 as geography
set #point1 = geography::STGeomFromText('POINT (7 1)', 4326)
set #point2 = geography::STGeomFromText('POINT (7 3)', 4326)
select #point1.STDistance(#point2)
declare #point3 as geometry
declare #point4 as geometry
set #point3 = geometry::STGeomFromText('POINT (7 1)', 4326)
set #point4 = geometry::STGeomFromText('POINT (7 3)', 4326)
select #point3.STDistance(#point4)
If you run this directly in SQL Server Management Studio you get 221151.479533501 in the first result and 2 in the second.
This is basically because in the geography data type the unit is chosen according to the supplied SRID. In your case, being 4326, its in meters. So, you're asking for the distance, in meters, between the coordinates (lon:7; lat:1) and (lon:7; lat:3). It returns about 221 Km.
When using the geometry type (second example), it's a planar projection where the distance works as you would expect, thus returning 2.
Regarding your NH Spatial code, seems ok. Just supply the maxDistance parameter in meters and you should be fine.

MySQL GIS Lat/Lon to X/Y: which is which?

Is there a convention for whether GIS points in MySQL should be stored as POINT($latitude $longitude) or POINT($longitude $latitude)?
Having longitude correspond to X on a cartesian map would visually make more sense with north pointing up, but common parlance is to say "latitude and longitude."
In MySQL you will probably use the GeomFromText() function to insert data in a spatial field. This function uses the WKT (Well-Known Text) format to define the geometries, and in the POINT case, it is defined as:
POINT ($longitude $latitude)
The accepted answer is NOT CORRECT for working with GPS coordinates in MySQL 8+ and it will get into trouble (haven't tested it with previous version of MySQL).
TL;DR; Use 'POINT($lat $long)' as WKT string but POINT($long, $lat) with the POINT() function in MySQL 8+.
Full answer:
Using WKT notation as 'POINT($longitude $latitude)' while using SRID 4326 (the one you should use for GPS coordinates system) leads to incorrect distance calculations even if consistently used throughout the app. Read on for details.
For example, let's consider the direct distance between CN Tower in Toronto and One World Trade Center in NYC which is approx. 549,18km according to Google Maps.
GPS coordinates:
CN Tower: 43.64386666880877, -79.38670551139633
One World Trade Centre: 40.689321781458446, -74.04415571126154
Expected distance: 549.18km
Following query yields the correct result:
SELECT
ST_DISTANCE(
ST_GEOMFROMTEXT('POINT(40.689321781458446 -74.04415571126154)', 4326),
ST_GEOMFROMTEXT('POINT(43.64386666880877 -79.38670551139633)', 4326),
'metre'
)
FROM DUAL;
-- results in 549902.432032006 meters which is around 549.9km (CORRECT)
However, if you provide longitude first in your WKT (as suggested in the accepted answer) you get a wrong distance calculated:
SELECT
ST_DISTANCE(
ST_GEOMFROMTEXT('POINT(-74.04415571126154 40.689321781458446)', 4326),
ST_GEOMFROMTEXT('POINT(-79.38670551139633 43.64386666880877)', 4326),
'metre'
)
FROM DUAL;
-- results in 601012.8595500318 which is around 601km (WRONG)
As you can see the POINT($long $lat) WKT string approach is incorrect and is off by approx. 51km compared to POINT($lat $long) approach which is almost 10% error. And it actually gets worse the farther you go.
Explanation:
It seems to happen because when MySQL considers a WKT string in the context of GPS coordinates it considers first argument as latitude and the second one as longitude. Try running the following query:
SELECT
ST_Latitude(ST_GEOMFROMTEXT('POINT(40.689321781458446 -74.04415571126154)',4326)) as latitude,
ST_Longitude(ST_GEOMFROMTEXT('POINT(40.689321781458446 -74.04415571126154)',4326)) as longitude
FROM dual;
-- results in
latitude, longitude
40.689321781458446,-74.04415571126154
Beware though, that the opposite is true when using the POINT(x, y) function instead of a WKT string!
Example:
SELECT
ST_DISTANCE(
ST_SRID(POINT(-74.04415571126154, 40.689321781458446), 4326),
ST_SRID(POINT(-79.38670551139633, 43.64386666880877), 4326),
'metre'
)
FROM DUAL;
-- results in 549902.432032006 meters which is around 549.9km (CORRECT)