Distance between Postcodes - sql

I am trying to find out the best way of working out distance between locations (we have the postcode and locations).
Basically with each record we have a Location and Postcode, the user on the front-end can enter a postcode and select records ..
- Within 10 Miles
- Within 20 Miles
- Within 40 Miles
- Within 50 Miles
The only way I can see doing this is by getting EACH record from the database and working out the distance between each, entering it into a temporary table and then ordering this table by location and filtering it. This seems like a long-winded and time-consuming way of doing it - does anyone have any suggestions of how this could be done better? Or is this the best way of doing it?
An example would be a customer enters SO40 9AA (Southampton Postcode) and then selects "within 50 miles", this should display and records within 50 miles of that postcode or location.

Do you have the geo-code in your dataset?
If not you can add it for the zipcodes you have with a service like http://geocoder.us/help/city_state_zip.shtml
Or you can get a pre-coded database, available at http://www.zipinfo.com/products/z5ll/z5ll.htm
Once you add the code to the records, then you can use a standard mathematical distance calculation using the lat / long coordinates to calculate distance.
If you want driving distance, use the Google Maps API, otherwise this distance will be "as the crow flies."

Once you the geo coords for all your postcodes, here's a VB function to get the strait distance between any two points. This produces results in miles, but easy enough to convert it.
Function distance(lat1 As Single, lon1 As Single, lat2 As Single, lon2 As Single)
Dim theta As Single, dist As Single
theta = deg2rad(lon1 - lon2)
lat1 = deg2rad(lat1)
lat2 = deg2rad(lat2)
dist = Sin(lat1) * Sin(lat2) + Cos(lat1) * Cos(lat2) * Cos(theta)
dist = acos(dist)
dist = rad2deg(dist)
distance = dist * 60 * 1.1515
End Function
'::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
'::: This function get the arccos function from arctan function :::
'::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
Function acos(Rad)
If Abs(Rad) <> 1 Then
acos = pi / 2 - Atn(Rad / Sqr(1 - Rad * Rad))
ElseIf Rad = -1 Then
acos = pi
End If
End Function
'::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
'::: This function converts decimal degrees to radians :::
'::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
Function deg2rad(Deg As Single) As Single
deg2rad = CSng(Deg * pi / 180)
End Function
'::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
'::: This function converts radians to decimal degrees :::
'::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
Function rad2deg(Rad As Single) As Single
rad2deg = CSng(Rad * 180 / pi)
End Function

Related

SQL: Select all rows that are in range of 2 kilometres

Assuming I have the following PostgresSQL table locations name/longitude/latitude:
name | longitude | latitude
-----------------------------------------
A | 14.02023923239 | 13.020239232393
B | 23.29328403231 | 20.203923847782
C | 8.02392784729 | 50.302398462732
D | 28.23828482848 | 29.845102045853
E | 32.20328328849 | 39.923828328782
How can I select the rows that are in a radius of (for example) 10 kilometers from a starting point longitude 13.99999999999 and latitude 12.99999999999?
I accept any answer that gives me a concrete SQL statement.
You need to transform longitude and latitude into a different coordinate system that maps lenghts accurately.
Install the PostGIS extension and use st_transform to do that transformation. Then use st_dwithin to find the matching rows.
It's an easy task if you have additional module earthdistance installed (or if you can install it):
select *
from mytable
where (point(lon, lat) <#> point(14, 13)) <= 10 / 1.609
<#> gives you a distance in miles, so we need to convert the kilometers to miles on the right operand of the comparison.
Accuracy might vary (the module assumes that Earth is a perfect sphere) - if you need something more accurate, you want to use PostGIS.
Thanks guys. I found this stored procedure on some website (forgot the link).
It works really well implementing the Haversine formula:
CREATE OR REPLACE FUNCTION calculate_distance(lat1 float, lon1 float, lat2 float, lon2 float, units varchar)
RETURNS float AS $dist$
DECLARE
dist float = 0;
radlat1 float;
radlat2 float;
theta float;
radtheta float;
BEGIN
IF lat1 = lat2 OR lon1 = lon2
THEN RETURN dist;
ELSE
radlat1 = pi() * lat1 / 180;
radlat2 = pi() * lat2 / 180;
theta = lon1 - lon2;
radtheta = pi() * theta / 180;
dist = sin(radlat1) * sin(radlat2) + cos(radlat1) * cos(radlat2) * cos(radtheta);
IF dist > 1 THEN dist = 1; END IF;
dist = acos(dist);
dist = dist * 180 / pi();
dist = dist * 60 * 1.1515;
IF units = 'K' THEN dist = dist * 1.609344; END IF;
IF units = 'N' THEN dist = dist * 0.8684; END IF;
RETURN dist;
END IF;
END;
$dist$ LANGUAGE plpgsql;
With that stored procedure, I was able to build a SQL select:
select
*
from
locations
where
calculate_distance(latitude, longitude, 13.99999999999, 12.99999999999, 'K') < 2

Given a long/lat, convert meters into longitude/latitude degrees

I have a longitude and latitude stored as a geometry with SRID 4326.
I want to make a line that is exactly 1000 meters long that is 90 degrees (to the right).
I know that the conversion from a longitudal/latitudal degree to a meter varies about where you are on the globe. That is why I will pass a reference long/lat that can be taken into consideration.
I am looking for something "good enough" that assumes that the distance you want will be no greater than 100 miles.
Given a long/lat and a meter distance of 1000 meters, return to me the size of the line long/lat degrees.
This question is specific to one example but I am looking for a general solution, because I have many functions that work upon "SRID Units" and the SRID I work with is long/lat (4326) but I always want to deal with meters and not degrees.
The hope is that I can call scaler function to convert the meters I want to the 4326 units.
Some hacks I have considered for finding X meters is converting the geometry into a geography and using the STBuffer(X) to make a circle with a radius equal to that of X, then create a long line that intersects the buffer and to find the point of the intersection, which will be the long/lat of exactly X meters away. This seems very hacky and inefficient but might be the most accurate
Edit:
To find the deltaX and deltaY the function is doing the the following:
DeltaX = Cos(#AngleRads) * #DistanceInDegrees;
DeltaY = Sin(#AngleRads) * #DistanceInDegrees;
I can supply #AngleRads and #DistanceInDegrees.
The problem is that #DistanceInDegrees has to match 4326 units (degrees). Is it even possible to find a #DistanceInDegrees that will correspond to 1000 meters no matter what angle is given?
When I use the formula
#DistanceInDegrees = (#Meters / 6371008.7714) * (180 / pi()) / cos(#Lat1 * pi()/180) and a angle of 90 degrees, then the length of the line is 1002 meters, close but not identically 1000.. If I use a degree of 45 the length of the line is 1191.67 meters.
If I understand your question, this can be done with a little math.
This also assumes the mean radius of the earth to be 6,371,008.7714 meters.
Example
Declare #Lat1 float = -37.786570
Declare #Lng1 float = 145.178179
Declare #Meters float = -1000
Select Lat1 = #Lat1
,Lng1 = #Lng1
,Lat2 = #Lat1
,Lng2 = #Lng1 + (#Meters / 6371008.7714) * (180 / pi()) / cos(#Lat1 * pi()/180)
Returns
The results can be validated here
After your EDIT, It seems you are looking for DISTANCE and BEARING
Here is a Table-Valued Function which may help
Example
Select * from [dbo].[tvf-Geo-Distance-Bearing](-37.786570,145.178179,1000,-45)
Returns
RetLat RetLng
-37.7802105711301 145.170133170589
Now, when I calculate the distance, I get 999.99999448737 meters
The Table-Valued Function If Interested
CREATE Function [dbo].[tvf-Geo-Distance-Bearing] (#Lat1 float,#Lng1 float,#Dist Float,#Degr float)
Returns #LatLng Table (RetLat float,RetLng Float)
As
Begin
Declare #Lat2 float,#Lng2 float,#eRad float = 6371008.7714
Select #Lat1 = RADIANS(#Lat1)
,#Lng1 = RADIANS(#Lng1)
,#Degr = RADIANS(#Degr)
Set #Lat2 = Asin(Sin(#Lat1) * Cos(#Dist / #eRad ) + Cos(#Lat1) * Sin(#Dist / #eRad ) * Cos(#Degr ))
Set #Lng2 = #Lng1 + Atn2(Sin(#Degr) * Sin(#Dist / #eRad ) * Cos(#Lat1), Cos(#Dist / #eRad ) - Sin(#Lat1) * Sin(#Lat2))
Insert Into #LatLng
Select Degrees(#Lat2)
,Degrees(#Lng2)
Return;
End

zipcodes within a specific radius

I'm looking for an SQL query to let me retrieve Zipcodes within a specific radius.
I have a table called tblZip with columns: Zipcode, Lat(latitude), Long(Longitude).
tblZip
Zipcode | Lat | Long
Short Text | Number | Number
I have checked few other answers that suggest use of Great-circle Distance but I don't seem to understand how it works. I get a headache just seeing all the conversions from / to radians and degrees and I really tried to understand it, but I'm so bad with mathematics.
I appreciate your help and guidance.
PS: I'm using a Microsoft Access Database.
Consider using the haversine distance formula in a VBA function (borrowed here). Then have it called in your SQL query. And even have your query filtered by distance with WHERE clause per your needs. Do note: this solution will only work inside the MSAccess.exe program. You will not be able to call it externally via ODBC/OLEDB.
And since you need comparison geocodes, SQL query below uses a cross join where each record is compared with every other record in table. Additionally, query avoids reverse duplicates and same-matched pairings reducing the size. But be careful if table is large as cross joins on self returns N2 records, that is before the filtering of duplicates.
SQL (Zipcode pairings with distance of 5 km or less)
SELECT z1.Zipcode, z2.Zipcode,
GetDistance(z1.Lat, z1.Lon, z2.Lat, z2.Lon) As km_distance
FROM tblZip z1, tblZip z2
WHERE z1.Zipcode > z2.Zipcode
AND GetDistance(s1.lat, s1.lon, s1.lat, s2.lon) <= 5;
VBA (save in a standalone module)
Function GetDistance(lat1Degrees As Double, lon1Degrees As Double, lat2Degrees As Double, lon2Degrees As Double) As Double
Dim earthSphereRadiusKilometers As Double
Dim kilometerConversionToMilesFactor As Double
Dim lat1Radians As Double
Dim lon1Radians As Double
Dim lat2Radians As Double
Dim lon2Radians As Double
Dim AsinBase As Double
Dim DerivedAsin As Double
'Mean radius of the earth (replace with 3443.89849 to get nautical miles)
earthSphereRadiusKilometers = 6371
'Convert kilometers into miles (replace 0.621371 with 1 to keep in kilometers)
kilometerConversionToMilesFactor = 0.621371
'Convert each decimal degree to radians
lat1Radians = (lat1Degrees / 180) * (4 * ATN(1))
lon1Radians = (lon1Degrees / 180) * (4 * ATN(1))
lat2Radians = (lat2Degrees / 180) * (4 * ATN(1))
lon2Radians = (lon2Degrees / 180) * (4 * ATN(1))
AsinBase = Sin(Sqr(Sin((lat1Radians - lat2Radians) / 2) ^ 2 + Cos(lat1Radians) * Cos(lat2Radians) * Sin((lon1Radians - lon2Radians) / 2) ^ 2))
DerivedAsin = (AsinBase / Sqr(-AsinBase * AsinBase + 1))
'Get distance from [lat1,lon1] to [lat2,lon2]
GetMiles = Round(2 * DerivedAsin * (earthSphereRadiusKilometers * kilometerConversionToMilesFactor), 2)
End Function

Gps distance between two points

I am developing a Meter app (in android). I am calculating distance between two points by formula (in back ground)
double calculateDistancs(double lat1, double long1, double lat2,
double long2) {
double earthRadius = 6371000; // meters
double dLat = Math.toRadians(lat2 - lat1);
double dLng = Math.toRadians(long2 - long1);
double a = Math.sin(dLat / 2) * Math.sin(dLat / 2)
+ Math.cos(Math.toRadians(lat1))
* Math.cos(Math.toRadians(lat2)) * Math.sin(dLng / 2)
* Math.sin(dLng / 2);
double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
float dist = (float) (earthRadius * c);
return dist;
}
I am not getting same and accurate distance between my home to metro station .
Provide other approach if available in android.
So second option is use google api for getting rout distance bn two latlong but in this case it will hit api many times.I want t know which approach is best.
I am getting different latlong at same place, may some one please make me understand for this.
The problem most probably is not caused by the function you posted.
Probably you are summing up jumping gps locations.
Just summing up the distance from one location to the other is not sufficient, especially at low speeds, like walking speed.
However there are weak points in the formula:
float dist = (float) (earthRadius * c);
You are always using double, and then downgrade to the lower precision float, but return the double. This is makes no sense.
Fix that with:
double dist = earthRadius * c;

transform k m into gps Coordinates

I have to see if some GPS coordinates are in a circle that I create.when I say I am creating that circle I' refering to this: I have lat1, long1,
my actual location and I want to test if there is any data surrounding this location but
not far then 1km. I am trying to use circle inequation : (x2-a2)2+(y2-b2)2 <R2 where a=lat1, b=long1 and R=radius. I know that R=1km but how do I transform 1km into data that can be compared to GPS coordinates? and x, y are every value from a collection that are testet to see if they fit.
Lat/Lng coordinates don't work with the Phytagoras Formula, as Earth is a sphere, and not flat...
Take a look at this manual:
http://www.movable-type.co.uk/scripts/latlong.html
You need the distance equation, to see if the distance between the lat/lng you have and the lat/lng of the center of the circle is smaller than R.
It's not that simple. You can't generally use the euclidean distance. This is how i think you could do it:
The correct way
The correct way to do this would be to calculate the great circle distance (the shortest walking distance on the surface), which I never really understood, but it's pretty easy to google (and adamk already gave you a link to it).
The lazy way
Assume that your points are not too close to the poles and that they are close to each other. Then you can use the latitude and longitude as if they were euclidean coordinates. (this is basically some cylindrical projection). 1 degree of latitude will then be (earth_circumference / 360) long and 1 degree of longitude will be (cos(lat) * earth_circumference / 360) long.
the complete code would look something like this:
double distance_lazy(double lat1, double lon1, double lat2, double lon2){
double xDist = (lat2 - lat1) * EARTH_CIRCUMFERENCE / 360.0;
double yDist = cos(lat1) * (lon2 - lon1) * EARTH_CIRCUMFERENCE / 360.0;
return sqrt(xDist^2 + yDist^2);
}
If your distances will be only a few km from each other, this should work pretty ok for the whole europe ... something like that.
The "weird definition" way
Another thing you can say is, that "distance" is the length straight line between the points, even though it goes through the earth. Then this would become calculating the 3D coordinates of a point and then their euclidean distance
double distance_straight_line(double lat1, double lon1, double lat2, double lon2){
double x1 = cos(lat1) * cos(lon1) * EARTH_RADIUS;
double y1 = sin(lat1) * cos(lon1) * EARTH_RADIUS;
double z1 = sin(lon1) * EARTH_RADIUS;
double x2 = cos(lat2) * cos(lon2) * EARTH_RADIUS;
double y2 = sin(lat2) * cos(lon2) * EARTH_RADIUS;
double z2 = sin(lon2) * EARTH_RADIUS;
return sqrt((x2 - x1)^2 + (y2 - y1)^2 + (z2 - z1)^2);
}
Again this will work as expected for points close to each other, this time they can be anywhere in the world. If you give it points far from each other, the output will be correct, but prety useless (unless you are very good at digging).
Hope it helps.