I have a list of about 60 stores with physical addresses and geocodes. I would like to make a simple interface where a user can enter a zip code, or city and state, or even a street address and have the list sort in order of proximity to the entered address. This is a very common feature of websites as I understand.
My plan to do this is to use the Google Maps API to find the geocode of the entered location and use the Pythagorean Theorem to calculate the distance from each location and sort the list by the distances and return the result (or maybe the top 5 of the result set...).
Is this the correct way to do this? Is there a more optimal method or a function built-in the Google Maps API that will do this? Since this is something so common, I would imagine there has been tried and tested in many ways and there are probably several correct answers. I am just looking for some advice if I am going about this the correct way.
Thank you.
The Pythagorean Theorem will not be enough. The curve of the Earth makes this so. It requires a bit of Euclidian geometry. The formula and a simple implementation for finding the distance between two points on Earth - as the bird flies and not actual travel distance - is (in PHP):
// pass the latitudes and longitudes in as degrees
function getDistance($lat1,$long1,$lat2,$long2)
{
$r = 3963.1; //3963.1 statute miles; 3443.9 nautical miles; 6378 km
$pi = pi();
// convert the degrees to radians
$lat1 = $lat1*($pi/180);
$lat2 = $lat2*($pi/180);
$long1 = $long1*($pi/180);
$long2 = $long2*($pi/180);
$ret = (acos(cos($lat1)*cos($long1)*cos($lat2)*cos($long2) + cos($lat1)*sin($long1)*cos($lat2)*sin($long2) + sin($lat1)*sin($lat2)) * $r) ;
return $ret;
}
You could incorporate a version of this in your code. In addition here is a possible (untested) function that is a derivative of another one I have used for MySQL.
DELIMITER $$
DROP FUNCTION IF EXISTS `FindDist` $$
CREATE FUNCTION `FindDist` (lt1 DOUBLE,lg1 DOUBLE,lt2 DOUBLE,lg2 DOUBLE) RETURNS DOUBLE
DETERMINISTIC
BEGIN
DECLARE dist,eradius DOUBLE;
SET eradius=3963.1;
SET dist=Acos(Cos(lt1) * Cos(lg1) * Cos(lt2) * Cos(lg2) + Cos(lt1) * Sin(lg1) * Cos(lt2) * Sin(lg2) + Sin(lt1) * Sin(lt2)) * eradius;
RETURN dist;
END $$
DELIMITER ;
I found this link a while back when I was researching something similar. It uses .NET but the principles would apply to any language/framework.
Store Locator: Help customers find you with Google Maps
The key part of the solution is using the Haversine Formula to find the distance between two points specified as longitude and latitude. There's a C# implementation of this formula linked to in the above article here:
Distance between locations using latitude and longitude (CodeProject)
A bit more rooting around revealed:
Calculate Distance Between Two Points on a Globe in 9 Different Languages
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 created a PhoneGap app so it's database is using HTML Storage. I have a table of locations which include their lat and long. I then have the users current location and need to find the closest 3 locations.
How would I go about this mathematically? Will I need to convert the lat long values or can they be directly comparable?
In my opinion the easiest way to do that is convert the distance between the coordinates to kms and then loop through the coordinates you have and figure out which ones you want. It's simple math, you can read more about it here if you want.
getDistanceBetweenCoordinates:function(lat1,lng1,lat2,lng2){
var distance = (3958*3.1415926*Math.sqrt((lat2-lat1)*(lat2-lat1) + Math.cos(lat2/57.29578)*Math.cos(lat1/57.29578)*(lng2-lng1)*(lng2-lng1))/180);
//console.log("distance:" + distance);
return Number(distance);
}
I would like to query for all possible streetnames within a radius of 500 meters of a given point.
Multiple posts are reffering to the google store locator example using the Haversine formula or some version of it.
But I also came across some posts that have a much more simplified solution.
They just treat the points as x,y coordinates by adding to the lat and long variables as seen below.
I was wandering if this would be the fastest way to query mysql without getting really complicated and still get a good result. I don't have a lot of data yet, so I want to know if I am on the right track.
Are there any disadvantages or inaccuracy's by using this method?
What I don't get is how this can be a radius like range, it looks more like a one directional
query?
Distance = 0.1; // Range in degrees (0.1 degrees is close to 11km)
LatN = lat + Distance;
LatS = lat - Distance;
LonE = lon + Distance;
LonW = lon - Distance;
...Query DB with something like the following:
SELECT *
FROM table_name
WHERE
(store_lat BETWEEN LatN AND LatS) AND
(store_lon BETWEEN LonE AND LonW)
You might want to ask this question on the GIS site. I've used this answer myself for similar problems. I can see how your proposed solution might be faster, but note your four points are describing a square not a circle so it would not be considered a "radius".
You could use ElasticSearch for that, MySQL will be always slower than ES.
In my project I have to find [latitude, longitude] coordinate(s) from one point in distance of 500 meters (this could be any random coordinate or an array of coordinates around my point). How can I do this?
Note: I need this in order to find multiple paths between points different from shortest one which is returned us via Google Maps Directions Api..So using my method I will define the center of the road from A to B and then find some coordinates below and above that center position and use this as another waypoint to go from A to B - I guess this might help me to find multiple paths...
Any suggestions from GIS professionals?
EDIT: UTM conversion is the most preferable one for such calculations, and I've created UTM Java class if anyone needs..
If I understand your question right you have a known point in Lat/Long and you need calculate the Lat/Long of another point or points 500m away from your starting point.
If this is what you are doing, you have several options most of which involve specialist GIS APIs. However, I'm guesing you're a programmer/mathematician rather than a Geographer so, you may prefer to opt for using the Haversine formula. You can find a discussion on this topic here plus the formula.
One caveat is that the distamce you are working with (500m is quite small) and the Earth is far from being a perfect sphere or even a slightly flattened spheroid. It is locally "lumpy" and that can put your calculation out. If you need more accuracy you will have to account for these imperfections by using an appropriate local Datum (model of the Earth - there are many e.g. see EPSG list) and to do that you will probably need to start using the GIS libraries as the maths gets very detailed otherwise.
This is the code used by google map (SphericalUtil.java)
// from SphericalUtil.java
// compile 'com.google.maps.android:android-maps-utils:0.4.4'
public static LatLng computeOffset(LatLng from, double distance, double heading) {
distance /= 6371009.0D; //earth_radius = 6371009 # in meters
heading = Math.toRadians(heading);
double fromLat = Math.toRadians(from.latitude);
double fromLng = Math.toRadians(from.longitude);
double cosDistance = Math.cos(distance);
double sinDistance = Math.sin(distance);
double sinFromLat = Math.sin(fromLat);
double cosFromLat = Math.cos(fromLat);
double sinLat = cosDistance * sinFromLat + sinDistance * cosFromLat * Math.cos(heading);
double dLng = Math.atan2(sinDistance * cosFromLat * Math.sin(heading), cosDistance - sinFromLat * sinLat);
return new LatLng(Math.toDegrees(Math.asin(sinLat)), Math.toDegrees(fromLng + dLng));
}
to use it, you just have to enter the centerLatLng, the distance in meters, and the heading in degrees from centerLatLng.
you can change the formula to the language of your preference.
I have a list of zipcoded in a MySQL Database together with their Latitude & Longitude data (Column names: ZipCode, Lat, Lon).
Now I have to make a search requests (search for the zipcode) to extract information from a website. When I make this search requests the results include all information within a radius of 50km of the zipcode.
Now, I don't want to make an unnessary high amount of search requests, so I would like to minimize the amount of zipcodes. So I'm looking for a way to filter all zipcodes, so that I have only the zipcodes where the distance between them is >50km.
Unfortunately I have no idea how to to it.
Can someone help me to solve this?
You may be interested in checking out the following presentation:
Geo/Spatial Search with MySQL by Alexander Rubin
The author describes how you can use the Haversine Formula in MySQL to limit your searches to a defined range. He also describes how to avoid a full table scan for such queries, using traditional indexes on the latitude and longitude columns.
You can use the google geocoding api , it allows you to get distances between 2 locations (lat/long, it also allows you to get zip from lat/long). From this you should be able to get the distance between each of your zipcodes and put them into a table, then you can do searches on just these.
Well, I see no other way then to iterate all rows on each request and filter them by calculating distance between selected zipcode and others (all of them), based on Lat & Lon.
I am using something similiar...
http://webarto.com/googlemaps
http://webarto.com/izrada-web-stranica/belgrade
PHP function for distance between two LL...
function distance($lat1, $lon1, $lat2, $lon2){
$theta = $lon1 - $lon2;
$dist = sin(deg2rad($lat1)) * sin(deg2rad($lat2)) + cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * cos(deg2rad($theta));
$dist = acos($dist);
$dist = rad2deg($dist);
$miles = $dist * 60 * 1.1515;
return round($miles * 1.609344,3);
}
I calculate it this way...
$sql = mysql_query("SELECT * FROM geoip WHERE city = '$city'");
while($row = mysql_fetch_array($sql)){
$ll = explode(",",$row["ll"]);
$x = distance(44.5428009033,18.6693992615,$ll[0],$ll[1]);
$road = intval($x+($x/3));
echo "Distance between ".$row["city"]." and Tuzla is ".$x." kilometers of airline, that's about ".$road." kilometers of road way.";
}
Daniel's link deals with selecting all the zip codes within 50km of a given latitude/longitude. Once you can do that, you can build a filtered list of zipcodes like this...
Select a zip code at random and add it to the filtered list
Delete all zip codes which lie within 50km of the selected zip code
Select a new zip code at random from the remaining zip codes, repeat until no more are left.
You know that you're only picking zip codes that are >50km from the ones already picked, and you know that once the original table is empty it must be because all zip codes lie within 50km of at least one of your selected zip codes.
That doesn't guarantee the smallest possible list of zip codes, and the size of the result will depend on the random choices. However, I think that this simple algorithm is likely to be "good enough", and that saving a few searches wouldn't justify the extra effort involved in finding a truly optimal solution.
The problem has been discussed previously here on SO with various solutions
I had a similar problem and I used this solution to find the answer. Not sure if you are using java or some other language but the logic can be used in any programming language
Geo Location API and finding user within a radius