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.
Related
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.
I have a SQL database set of places to which I am assigned coordinates (lat, long). I would like to ask those points that lie within a radius of 5km from my point inside. I wonder how to construct a query in a way that does not collect unnecessary records?
Since you are talking about small distances of about 5 km and we are probably not in the direct vicinity of the north or south pole we can work with an approximated grid system of longitude and latitude values. Each degree in latidude is equivalent to a distance of km_per_lat=6371km*2*pi/360degrees = 111.195km. The distance between two longitudinal lines that are 1 degree apart depends on the actual latitude:
km_per_long=km_per_lat * cos(lat)
For areas here in North Germany (51 degrees north) this value would be around 69.98km.
So, assuming we are interested in small distances around lat0 and long0 we can safely assume that the translation factors for longitudinal and latitudinal angles will stay the same and we can simply apply the formula
SELECT 111.195*sqrt(power(lat-#lat0,2)
+power(cos(pi()/180*#lat0)*(long-#long0),2)) dist_in_km FROM tbl
Since you want to use the formula in the WHERE clause of your select you could use the following:
SELECT * FROM tbl
WHERE 111.195*sqrt(power(lat-#lat0,2)
+power(cos(pi()/180*#lat0)*(long-#long0),2)) < 5
The select statement will work for latitude and longitude values given in degree (in a decimal notation). Because of that we have to convert the value inside the cos() function to radians by multiplying it with pi()/180.
If you have to work with larger distances (>500km) then it is probably better to apply the appropriate distance formula used in navigation like
cos(delta)=cos(lat0)*cos(lat)*cos(long-long0) + sin(lat0)*sin(lat)
After calculating the actual angle delta by applying acos() you simply multiply that value by the earth's radius R = 6371km = 180/pi()*111.195km and you have your desired distance (see here: Wiki: great circle distance)
Update (reply to comment):
Not sure what you intend to do. If there is only one reference position you want to compare against then you can of course precompile your distance calculation a bit like
SELECT #lat0:=51,#long0:=-9; -- assuming a base position of: 51°N 9°E
SELECT #rad:=PI()/180,#fx:=#rad*6371,#fy:=#fx*cos(#rad*#lat0);
Your distance calculation will then simplify to just
SELECT #dist:=sqrt(power(#fx*(lat-#lat0),2)+power(#fy*(long-#long0),2))
with current positions in lat and long (no more cosine functions necessary). It is up to you whether you want to store all incoming positions in the database first or whether you want to do the calculations somewhere outside in Spring, Java or whatever language you are using. The equations are there and easy to use.
I would go with Euklid. dist=sqrt(power(x1-x2,2)+power(y1-y2,2)) . It works everywhere. Maybe you have to add a conversion to the x/y-coordinates, if degrees can't be translated in km that easy.
Than you can go and select everything you like WHERE x IS BETWEEN (x-5) AND (x+5) AND y IS BETWEEN (y-5) AND (y+5) . Now you can check the results with Euklid.
With an optimisation of the result order, you can get better results at first. Maybe there's a way to take Euklid to SQL, too.
I am just starting to work with spatial data in SQL Server (2008 r2). I am looking to calculate the distance between two coordinates (miles).
DECLARE #source geography
DECLARE #target geography
SET #source = geography::STGeomFromText('POINT (43.420026 -83.974472)', 4326);
SET #target = geography::STGeomFromText('POINT (43.458786 -84.029471)', 4326);
SELECT #source.STDistance(#target)/1609.344 -- meters to miles
My query results in a value of 3.827 miles but I checked it against the site linked below and they are returning a distance of 3.85 miles. Am I doing this incorrectly?
http://www.boulter.com/gps/distance/?from=43.420026+-83.974472&to=43.458786+-84.029471&units=m
I don't think you're doing anything wrong. Your SQL query looks reasonable to me. But…
I'm far from an expert on spatial reference systems and geodesic stuff (have you considered asking the expert folks over at GIS SE?); nevertheless, here's three possibilites that come to my mind:
Perhaps they calculate the distance along a straight line instead of the geodesic distance (i.e. the distance along a curved line, the Earth isn't flat after all). SQL Server's geography type should account for that.
This does not seem very plausible, given that "their" distance is greater than "yours": you'd expect straight-line distance to be smaller than geodesic distance.
Perhaps they can do the calculation more accurately than SQL Server (see note on the MSDN reference page for geography.STDistance):
"STDistance() returns the shortest LineString between two geography types. This is a close approximate to the geodesic distance. The deviation of STDistance() on common earth models from the exact geodesic distance is no more than .25%. This avoids confusion over the subtle differences between length and distance in geodesic types."
Or, both calculations are somewhat inaccurate, but in opposite directions. If I'm not mistaken, the two results you're citing differ by something like 0.5%, that could just be the sum of their deviation and SQL Server's.
But I could be completely wrong.
I'm trying to query any locations within a specified distance from another location. The query is not the problem, but the distance returned by geography.STDistance is.
It seems STDistance makes fairly accurate calculations on locations close to the equator, but I need this to work with locations in the nordic countries. Norway, Sweden, Finland and so on...
According to my calculations, made on locations in northern Sweden, the distance is wrong by a factor of around 2.38?!
Expected result is 1070 meters and returned distance is 2537,28850694302 meters
My query looks like this:
DECLARE #g geography = geography::STGeomFromText('POINT(65.580254 22.179428)', 4326)
SELECT name, [pos].STSrid as srdi, [pos].STDistance(#g) as d
FROM [GPSCHAT].[dbo].[USERS]
and the "other location" has coordinates (65,578541 22,202286) (stored with SRID 4326)
I'm guessing this has to do with the distance from the equator (close to the polar circle), but there has to be a way to calculate this more accurately based on the Latitude or am i wrong?
It looks like you're creating your point using 'X, Y'.
When creating a point from text, use 'Y, X' instead.
Check out this MSDN Article for some more info.
Why don't you make use of another spatial reference identifier which fits better the earth curvature around your position. SRID 4326 might not been measured as accurate as other local referential systems
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)