Distance between POINT and GEOMETRYCOLLECTION objects in MYSQL - mysql

MYSQL ver 5.7
Requirement:
I have a bunch of POINT geometries in MYSQL table and I have to find all the POINT geometries that are within 5km distance/radius of a GEOMETRYCOLLECTION object.
GEOMETRYCOLLECTION may contain more than one type of geometries like POINT, POLYGON etc.
Sample GEOMETRYCOLLECTION data:
SET #g1 = ST_GeomFromText('GEOMETRYCOLLECTION(POINT (-156.366489591715 66.913750389327),POLYGON ((-156.357608905242 66.906958164897, -156.360302383363 66.9066027336476, -156.361997104194 66.9067073607308, -156.363616093774 66.9066368440642, -156.365477697938 66.9065867326059, -156.368127298976 66.9065970034393, -156.370061891681 66.9066888794808, -156.37182258022 66.9068547305222, -156.373286981259 66.9070724523969, -156.374390675008 66.9072952721882, -156.376359777088 66.9077681138541, -156.377706173961 66.9080113180204, -156.379222192708 66.9081328753119, -156.380729601039 66.9081591586452, -156.382562289578 66.9081211961453, -156.387571662487 66.9099676951007, -156.389320598943 66.9125180930134, -156.389291120818 66.9145787836353, -156.384722634367 66.9167899596735, -156.37955035 66.9195246586276, -156.372520662511 66.9209119638337, -156.360432280238 66.9215118034161, -156.355776993787 66.9203754471679, -156.34906598338 66.9180659711298, -156.347941981299 66.9174007836309, -156.346853913592 66.9167568252985, -156.34605399901 66.9158971169665, -156.346982815675 66.9151925950926, -156.346794497967 66.9144321773854, -156.345642955261 66.9140107294695, -156.343831364638 66.9136152003034, -156.342996512556 66.9130307378043, -156.343113243806 66.9123137492637, -156.343498096931 66.9119029992644, -156.344661664637 66.9111819440571, -156.345080786511 66.9105884961414, -156.345524286511 66.9099605023924, -156.347168040675 66.9098486503092, -156.348952756297 66.9096090419763, -156.348689200048 66.9089614565606, -156.349495732338 66.908706844061, -156.350786711503 66.9082992794783, -156.352211271917 66.9083472388533, -156.353952768789 66.90829894302, -156.355389368787 66.9082072242701, -156.356512531285 66.9079768284371, -156.356677961493 66.9078075857291, -156.356422527119 66.907644261771, -156.355901372953 66.9072802273965, -156.357608905242 66.906958164897)))');
Sample POINT data:
SET #p1 = ST_GeomFromText('GEOMETRYCOLLECTION(POINT (-156.342840017 66.9320439348))');
I have tried ST_DISTANCE_SPHERE(#g1,#p1) spatial function (which returns the value in meters) but it seems it doesn't support geometry types other than POINT and MULTIPOINT.
Then I have used:
ST_DISTANCE(#g1,#p1)
'0.015301834064271899'
I am unable to understand the what is the UNIT of this returned value in MYSQL 5.7?
I have searched a lot on the internet and there is no proper documentation available regarding the same. In POSTGIS, this can be done but I am struggling to do this in MYSQL ver 5.7.
Any help is appreciated.
Thanks in advance!

ST_Distance returns "distance" in degrees here - i.e. the flat map view of the shortest distance between shapes. This value cannot be mapped to real distance, as real world distance of 1 degree along parallel is different from distance of 1 degree along meridian except near the equator.
Looks like MySQL cannot correctly compute distance here. You would be better served by systems with more geospatial support, like PostgreSQL + PostGIS, or Google BigQuery, etc. They give you correct answer, you just need to replace ST_GeomFromText with ST_GeogFromText to work with spherical geographies.

Related

MySQL can't check if point is within distance ... MBRContains query mistake or Bug?

I have the following latitude and longitude:
lat - 18.9802767
lng - 72.8142511
I am trying the following query for places withint 10 kms from the point of interest.
select mbrcontains( geomfromtext(
'LINESTRING(72.8993663648088 19.0702857,72.72913583519122 18.8902677)'
),
geomfromtext(
'point(18.9802767 72.8142511)'
) );
The Linestring geometry object is derived from the exact point that I am trying to determine is within using the method for mysql 5.1 and above from this example using the formula :
linestring(point(#lng+10/(111.1/cos(radians(#lat))),#lat+10/111.1), point(#lng-10/(111.1/cos(radians(#lat))),#lat-10/111.1))
From what I understand the point falls within the Minimum Bounding Rectangle (MBR). However the query returns a 0 for the answer. The above is following the principles given in this example.
What is wrong with the query? How can I know if the point is within a certain geospatial distance (in this case the MBR points are calculated using 10 kms from the point given by co-ordinates: lat - 18.9802767, lng - 72.8142511).
BTW, I am using MySQL 5.5.32.
Your point does not fall within the MBR of the line. Looks like you've reversed the latitude or longitude coordinates on either the line or the point. Switch the X and Y in 'point(18.9802767 72.8142511)' to get this point, which will be within the MBR of the line:
POINT (72.8142511 18.9802767)
If you are tied to MySQL, you may consider updating to MySQL 5.6, then using the Buffer function to create a circular area from your point of interest, and then use ST_Within to find anything within that circular area.
If you are not tied to MySQL, consider PostGIS, which provides a nice ST_DWithin function that makes these comparisons very easy.

Check if a point is near a linestring with MySQL Spatial Extension (untrustable)

I have to discover if a given point is at least 500 meters (ou other distance) from a route line that exist in my database recorded using the MySQL Spatial.
I see there is no similar function in MySQL Spatial, and find a previous answer that doesn't work for me because the line is too big (more than 300km) to check with this solution point by point:
Find N Nearest LineString From A Point Using MySQL Spatial Extensions
I'm even can't create a buffer (a circle/polygon with a given radius) to the point to check if is even touch.
UPDATE - 12/7
I did it, but MySQL Spatial it is seams not trustable.
I made a createBuffer function, to create a 20 points Polygon around the given Point with a meters given distance for the radius: http://pastebin.com/xEFb8ZXi
I'm testing with the QGis the given results from this buffer, and everything is fine with the function (except the meters to decimal degress value that generate smaller then expected, but it is not the issue right now).
And made a few Intersects checks, and this is aways return true, even if the result polygon is not intersects the line.
I remade the same tests using just the center point, and the results is the same.
I discovery now that the INTERSECT doesn't check the LineString with the Point or Polygon, but the Bounding Box of the LineString, when a indicate a point OUTSIDE the Linestring BBox.
Intersects QUERY Where "rota" is the Linestring data:
SELECT Intersects(rota, createBuffer(GeomFromText('POINT(-19.7736 -43.7255)'),500))
FROM log_viagem WHERE rota IS NOT NULL;
How can I trust the MySQL Spatial now?
Or my concept about INTERSECTS is wrong?
SOLVED:
I didn't read the important note at 5.5 version of MySQL:
Note
Currently, MySQL does not implement these functions according to the specification. Those that are implemented return the same result as the corresponding MBR-based functions.
The Solution is taking with the server administrator to update to 5.6.1, there is an upgrade in the note
Note
MySQL originally implemented these functions such that they used
object bounding rectangles and returned the same result as the
corresponding MBR-based functions. As of MySQL 5.6.1, corresponding
versions are available that use precise object shapes. These versions
are named with an ST_ prefix. For example, Contains() uses object
bounding rectangles, whereas ST_Contains() uses object shapes.
As of MySQL 5.6.1, there are also ST_ aliases for existing spatial
functions that were already exact. For example, ST_IsEmpty() is an
alias for IsEmpty()

MBR Within not accurate in mysql

I have been using the MBRWithin function for quite a lot of times. Suddenly I notice on google map this POINT(101.11857 4.34475) is out of the geo fence which I specify but it still give a value of 1 in mysql any reason or tweaking need to be done?
SELECT MBRWithin(GeomFromText('POINT(101.11857 4.34475)'),GeomFromText('POLYGON((101.12112522125244 4.3531723687957164,101.11846446990967 4.351417913665312,101.13138198852539 4.336397898951581,101.13477230072021 4.33211863778494,101.14065170288086 4.321933898868271,101.14992141723633 4.306699328215635,101.15455627441406 4.30978050198082,101.1397933959961 4.334600612212089,101.12112522125244 4.3531723687957164,101.12112522125244 4.3531723687957164))')) As geoFenceStatus
MySQL 5.6.1 and later have exact geometry algorithms in addition to the earlier functions that only operated on MBR.
You can use ST_WITHIN rather than MBR_WITHIN. See documentation. Like this
SELECT ST_Within(GeomFromText('POINT(101.11857 4.34475)'),
GeomFromText('POLYGON((101.12112522125244 4.3531723687957164,101.11846446990967
4.351417913665312,101.13138198852539 4.336397898951581,101.13477230072021
4.33211863778494,101.14065170288086 4.321933898868271,101.14992141723633
4.306699328215635,101.15455627441406 4.30978050198082,101.1397933959961
4.334600612212089,101.12112522125244 4.3531723687957164,101.12112522125244
4.3531723687957164))')) As geoFenceStatus
MBRWithin() will return results based on the minimum bounding rectangle of it's parameters. Your polygon contains both larger and smaller values for both coordinates than the point, so it will be within the polygon's MBR.
MySQL has no built-in point in polygon algorithm, so you'll either have to roll your own or find one elsewhere. This one seems to be a good candidate.

Storing Lat Lng values in MySQL using Spatial Point Type

Tech used: MySQL 5.1 and PHP 5.3
I am just designing a new database for a site I am writing. I am looking at the best way of now storing Lat and Lng values.
In the past I have been using DECIMAL and using a PHP/MySQL select in the form:
SQRT(POW(69.1 * (fld_lat - ( $lat )), 2) + POW(69.1 * (($lon) - fld_lon) * COS(fld_lat / 57.3 ), 2 )) AS distance
to find nearest matching places.
Starting to read up more on new technologies I am wondering if I should use Spatial Extensions. http://dev.mysql.com/doc/refman/5.1/en/geometry-property-functions.html
Information is quite thin on the ground though and had a question on how to store the data. Instead of using DECIMAL, would I now use POINT as a Datatype?
Also, once stored as a POINT is it easy just to get the Lat Lng values from it in case I want to plot it on a map or should I additionally store the lat lngs as DECIMALS again as well?
I know I should prob use PostGIS as most posts on here say I just don't want to learn a new DB though!
Follow up
I have been playing with the new POINT type. I have been able to add Lat Lng values using the following:
INSERT INTO spatialTable (placeName, geoPoint) VALUES( "London School of Economics", GeomFromText( 'POINT(51.514 -0.1167)' ));
I can then get the Lat and Lng values back from the Db using:
SELECT X(geoPoint), Y(geoPoint) FROM spatialTable;
This all looks good, however the calculation for distance is the bit I need to solve. Apparently MySQL has a place-holder for a distance function but won't be released for a while. In a few posts I have found I need to do something like the below, however I think my code is slightly wrong:
SELECT
placeName,
ROUND(GLength(
LineStringFromWKB(
LineString(
geoPoint,
GeomFromText('POINT(52.5177, -0.0968)')
)
)
))
AS distance
FROM spatialTable
ORDER BY distance ASC;
In this example geoPoint is a POINT entered into the DB using the INSERT above.
GeomFromText('POINT(52.5177, -0.0968)' is a Lat Lng value I want to calculate a distance from.
More Follow-up
Rather stupidly I had just put in the ROUND part of the SQL without really thinking. Taking this out gives me:
SELECT
placeName,
(GLength(
LineStringFromWKB(
LineString(
geoPoint,
GeomFromText('POINT(51.5177 -0.0968)')
)
)
))
AS distance
FROM spatialTable
ORDER BY distance ASC
Which seems to give me the correct distances I need.
I suppose the only thing currently that needs answering is any thoughts on whether I am just making life difficult for myself by using Spatial now or future-proofing myself...
I think you should always use the highest level abstraction easily available. If your data is geospatial, then use geospatial objects.
But be careful. Mysql is the worst geospatial database there is. Its OK for points but all its polygon functions are completely broken - they change the polygon to its bounding rectangle and then do the answer on that.
The worst example that hit me is that if you have a polygon representing Japan and you ask what places are in Japan, Vladivostok gets into the list!
Oracle and PostGIS don't have this problem. I expect MSSQL doesn't and any Java database using JTS as its engine doesn't. Geospatial Good. MySQL Geospatial Bad.
Just read here How do you use MySQL spatial queries to find all records in X radius? that its fixed in 5.6.1.
Hoorah!
Mysql GIS yagni:
If you have no experience with GIS, learning spatial extensions is practically like learning a new database, plus a little math, and a lot of acronyms. Maps, projections, srids, formats... Do you have to learn all that to calculate distances between points given a certain lat/long: probably not, will you be integrating 3rd party GIS data or working with anything more complex than points, what coordinate system will you be using?
Going back to yagni: do things as simple as posible, in this case implement your code in php or with simple SQL. Once you reach a barrier and decide you need spatial, read up on GIS system, coordinate systems, projects, and conventions.
By then, you will probably want PostGIS.
It's a good thing, because then you get to use spatial indexes on your queries. Limit to a bounding box, for example, to limit how many rows to compare against.
If you can affor placing some extra code into your backend, use Geohash.
It encodes a coordinate into a string in a way that prefixes denote a broader area. The longer your string is, the more precision you have.
And it has bindings for many languages.
http://en.wikipedia.org/wiki/Geohash
https://www.elastic.co/guide/en/elasticsearch/guide/current/geohashes.html

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)