I'm using a lat/long SRID in my PostGIS database (-4326). I would like to find the nearest points to a given point in an efficient manner. I tried doing an
ORDER BY ST_Distance(point, ST_GeomFromText(?,-4326))
which gives me ok results in the lower 48 states, but up in Alaska it gives me garbage. Is there a way to do real distance calculations in PostGIS, or am I going to have to give a reasonable sized buffer and then calculate the great circle distances and sort the results in the code afterwards?
You are looking for ST_distance_sphere(point,point) or st_distance_spheroid(point,point).
See:
http://postgis.refractions.net/documentation/manual-1.3/ch06.html#distance_sphere
http://postgis.refractions.net/documentation/manual-1.3/ch06.html#distance_spheroid
This is normally referred to a geodesic or geodetic distance... while the two terms have slightly different meanings, they tend to be used interchangably.
Alternatively, you can project the data and use the standard st_distance function... this is only practical over short distances (using UTM or state plane) or if all distances are relative to a one or two points (equidistant projections).
PostGIS 1.5 handles true globe distances using lat longs and meters. It is aware that lat/long is angular in nature and has a 360 degree line
This is from SQL Server, and I use Haversine for a ridiculously fast distance that may suffer from your Alaska issue (can be off by a mile):
ALTER function [dbo].[getCoordinateDistance]
(
#Latitude1 decimal(16,12),
#Longitude1 decimal(16,12),
#Latitude2 decimal(16,12),
#Longitude2 decimal(16,12)
)
returns decimal(16,12)
as
/*
fUNCTION: getCoordinateDistance
Computes the Great Circle distance in kilometers
between two points on the Earth using the
Haversine formula distance calculation.
Input Parameters:
#Longitude1 - Longitude in degrees of point 1
#Latitude1 - Latitude in degrees of point 1
#Longitude2 - Longitude in degrees of point 2
#Latitude2 - Latitude in degrees of point 2
*/
begin
declare #radius decimal(16,12)
declare #lon1 decimal(16,12)
declare #lon2 decimal(16,12)
declare #lat1 decimal(16,12)
declare #lat2 decimal(16,12)
declare #a decimal(16,12)
declare #distance decimal(16,12)
-- Sets average radius of Earth in Kilometers
set #radius = 6366.70701949371
-- Convert degrees to radians
set #lon1 = radians( #Longitude1 )
set #lon2 = radians( #Longitude2 )
set #lat1 = radians( #Latitude1 )
set #lat2 = radians( #Latitude2 )
set #a = sqrt(square(sin((#lat2-#lat1)/2.0E)) +
(cos(#lat1) * cos(#lat2) * square(sin((#lon2-#lon1)/2.0E))) )
set #distance =
#radius * ( 2.0E *asin(case when 1.0E < #a then 1.0E else #a end ) )
return #distance
end
Vicenty is slow, but accurate to within 1 mm (and I only found a javascript imp of it):
/*
* Calculate geodesic distance (in m) between two points specified by latitude/longitude (in numeric degrees)
* using Vincenty inverse formula for ellipsoids
*/
function distVincenty(lat1, lon1, lat2, lon2) {
var a = 6378137, b = 6356752.3142, f = 1/298.257223563; // WGS-84 ellipsiod
var L = (lon2-lon1).toRad();
var U1 = Math.atan((1-f) * Math.tan(lat1.toRad()));
var U2 = Math.atan((1-f) * Math.tan(lat2.toRad()));
var sinU1 = Math.sin(U1), cosU1 = Math.cos(U1);
var sinU2 = Math.sin(U2), cosU2 = Math.cos(U2);
var lambda = L, lambdaP = 2*Math.PI;
var iterLimit = 20;
while (Math.abs(lambda-lambdaP) > 1e-12 && --iterLimit>0) {
var sinLambda = Math.sin(lambda), cosLambda = Math.cos(lambda);
var sinSigma = Math.sqrt((cosU2*sinLambda) * (cosU2*sinLambda) +
(cosU1*sinU2-sinU1*cosU2*cosLambda) * (cosU1*sinU2-sinU1*cosU2*cosLambda));
if (sinSigma==0) return 0; // co-incident points
var cosSigma = sinU1*sinU2 + cosU1*cosU2*cosLambda;
var sigma = Math.atan2(sinSigma, cosSigma);
var sinAlpha = cosU1 * cosU2 * sinLambda / sinSigma;
var cosSqAlpha = 1 - sinAlpha*sinAlpha;
var cos2SigmaM = cosSigma - 2*sinU1*sinU2/cosSqAlpha;
if (isNaN(cos2SigmaM)) cos2SigmaM = 0; // equatorial line: cosSqAlpha=0 (ยง6)
var C = f/16*cosSqAlpha*(4+f*(4-3*cosSqAlpha));
lambdaP = lambda;
lambda = L + (1-C) * f * sinAlpha *
(sigma + C*sinSigma*(cos2SigmaM+C*cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)));
}
if (iterLimit==0) return NaN // formula failed to converge
var uSq = cosSqAlpha * (a*a - b*b) / (b*b);
var A = 1 + uSq/16384*(4096+uSq*(-768+uSq*(320-175*uSq)));
var B = uSq/1024 * (256+uSq*(-128+uSq*(74-47*uSq)));
var deltaSigma = B*sinSigma*(cos2SigmaM+B/4*(cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)-
B/6*cos2SigmaM*(-3+4*sinSigma*sinSigma)*(-3+4*cos2SigmaM*cos2SigmaM)));
var s = b*A*(sigma-deltaSigma);
s = s.toFixed(3); // round to 1mm precision
return s;
}
Related
I have a latitude and longitude of some point I used the following function to fin distance between points. Then I used the next function to convert latitude and longitude to cartesian coordinates. when I use the following formula to find distance of point with cartesian coordinates, the result is different than the first method. why?
(x^2+y^2+z^2)^0.5
def distance(lat2,lat1,lon2,lon1):
distance=[]
speed=[]
accelerate=[]
radius = 6371 # km
dlat = math.radians(lat2-lat1)
dlon = math.radians(lon2-lon1)
a = math.sin(dlat/2) * math.sin(dlat/2) + math.cos(math.radians(lat1)) \
* math.cos(math.radians(lat2)) * math.sin(dlon/2) * math.sin(dlon/2)
c = 2 * math.atan2(math.sqrt(a), math.sqrt(1-a))
d = radius * c
distance.append(d)
for i in range(len(distance)-1):
speed.append((distance[i])/0.25)
for i in range(len(speed)-1):
accelerate.append((speed[i+1]-speed[i])/0.25)
return distance,speed,accelerate
def X_Y_Z(latitude,longitude):
R=6371
X=[]
Y=[]
Z=[]
for lat,lon in zip(latitude,longitude):
x = R * math.cos(lat) * math.cos(lon)
y = R * math.cos(lat) * math.sin(lon)
z = R *math.sin(lat)
X.append(x)
Y.append(y)
Z.append(z)
return X,Y,Z
for example the distance between (40.714531 ,-73.725607) and (40.714527 , -73.725591) is 0.001377. But cartesian coordinates give me different distance:
(642.430295 , -6287.620268 , 801.720448) and (642.527042 -6287.607109 801.746119)
I am requesting data from backend (laravel) based on a set distance between 2 points latitude and longitude eg. Lat= 78.3232 and Long = 65.3234 and distance = 30 miles/kilometers , get the rows within the 30 miles.
The problem I'm having is with distance.
I am using react-native-maps and the zoom in/out is based on the latitudeDelta and longitudeDelta and I don't know how to calculate the radius/distance of them when the user zooms in/out to be sent to the backend and get data based on the points and radius/distance
at the moment, I have this function on the client-side. to determine when to fetch new data when user changes the region. but it has a problem of hard-coded distance (5.0 KM)
const _onRegionChangeComplete = (onCompletedregion: Region) => {
if (initialRegion) {
const KMDistance = helper.distance(
initialRegion?.latitude,
initialRegion?.longitude,
onCompletedregion.latitude,
onCompletedregion.longitude,
"K"
);
if (KMDistance > 5.0) {
props.getNearByReports({ // request backend if distance difference more than 5 kilometers
latitude: onCompletedregion.latitude,
longitude: onCompletedregion.longitude
});
}
}
};
helper.distance // get the difference in distance between the initial points and after the user done changing the region
function distance(
lat1: number,
lon1: number,
lat2: number,
lon2: number,
unit: string
) {
var radlat1 = (Math.PI * lat1) / 180;
var radlat2 = (Math.PI * lat2) / 180;
var theta = lon1 - lon2;
var radtheta = (Math.PI * theta) / 180;
var dist =
Math.sin(radlat1) * Math.sin(radlat2) +
Math.cos(radlat1) * Math.cos(radlat2) * Math.cos(radtheta);
dist = Math.acos(dist);
dist = (dist * 180) / Math.PI;
dist = dist * 60 * 1.1515;
if (unit == "K") {
dist = dist * 1.609344;
}
if (unit == "M") {
dist = dist * 0.8684;
}
return dist;
}
The distance value is hard coded in the backend and frontend (which shouldn't be) cuz when the user zooms in/out , the radius/distance should change too. I am unable to calculate the distance from latitudeDelta and longitudeDelta
// Backend query code (simplified)
SELECT
id, (
6371 * acos (
cos ( radians(78.3232) )
* cos( radians( lat ) )
* cos( radians( lng ) - radians(65.3234) )
+ sin ( radians(78.3232) )
* sin( radians( lat ) )
)
) AS distance
FROM markers
HAVING distance < 5 // hard-coded distance
ORDER BY distance
LIMIT 0 , 20;
latitudeDelta is the amount of degrees that are visible on the screen. 1 degree is always equal to approx. 69 miles, so you can calculate the diameter of the currently visible area in miles with this diameter = latitudeDelta * 69. This is assuming the screen is in portrait mode and, therefore, latitudeDelta is the larger value. If not, use longitudeDelta instead. I hope I understood your question correctly and this is helpful.
I have created an SSRS reports in which i'm using map along with bing map. Also i have using the Point layer in the map.
How can i able to get distance between 2 points in the map?
Kindly advice...
The below function gives you distance between two GeoCoordinates in kilo metres
CREATE FUNCTION dbo.fnCalcDistanceKM(#lat1 FLOAT, #lat2 FLOAT, #lon1 FLOAT, #lon2 FLOAT)
RETURNS FLOAT
AS
BEGIN
RETURN ACOS(SIN(PI()*#lat1/180.0)*SIN(PI()*#lat2/180.0)+COS(PI()*#lat1/180.0)*COS(PI()*#lat2/180.0)*COS(PI()*#lon2/180.0-PI()*#lon1/180.0))*6371
END
The below function gives you distance between two GeoCoordinates in miles
create function [dbo].[fnCalcDistanceMiles] (#Lat1 decimal(8,4), #Long1 decimal(8,4), #Lat2 decimal(8,4), #Long2 decimal(8,4))
returns decimal (8,4) as
begin
declare #d decimal(28,10)
-- Convert to radians
set #Lat1 = #Lat1 / 57.2958
set #Long1 = #Long1 / 57.2958
set #Lat2 = #Lat2 / 57.2958
set #Long2 = #Long2 / 57.2958
-- Calc distance
set #d = (Sin(#Lat1) * Sin(#Lat2)) + (Cos(#Lat1) * Cos(#Lat2) * Cos(#Long2 - #Long1))
-- Convert to miles
if #d <> 0
begin
set #d = 3958.75 * Atan(Sqrt(1 - power(#d, 2)) / #d);
end
return #d
end
Usage:
select [dbo].[fnCalcDistanceKM](13.077085,80.262675,13.065701,80.258916)
Reference
If you know the OS co-ordinates (latitude and longitude) of both points you can calculate it using a bit of Pythagoras:
SQRT(((lat1 - lat2) * (lat1 - lat2)) + ((long1 - long2) * (long1 - long2)))
This calculates the differences between the two points on the X and Y axis which become the two short sides of an equatorial triangle with the longest side, the hypotenuse, being the distance you want to calculate. So it becomes the squares of the two known sides added together gives the square of the hypotenuse, the square root of which gives you you distance.
I'm currently using this query to get a town inside my DB by giving a coordinate :
SELECT *
FROM citiesTable
WHERE latitude >= ? AND latitude <= ?
AND longitude >= ? AND longitude <= ?;
This code works well and do I want but sometimes the query doesn't match anything...
So, in this case, I would put a bird distance to this query in order to get adjacent town.
Any suggestion on how can I do that ?
Searching for "distance from longitude and latitude" gave a link to http://www.movable-type.co.uk/scripts/latlong.html which specifies this code for calculating the distance:
var R = 6371; // km
var dLat = (lat2-lat1).toRad();
var dLon = (lon2-lon1).toRad();
var lat1 = lat1.toRad();
var lat2 = lat2.toRad();
var a = Math.sin(dLat/2) * Math.sin(dLat/2) +
Math.sin(dLon/2) * Math.sin(dLon/2) * Math.cos(lat1) * Math.cos(lat2);
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
var d = R * c;
A couple more minutes of searching led to http://www.movable-type.co.uk/scripts/latlong-db.html
That page is chock-full of information. Here's a simple (but inefficient) solution, assuming your town is at $lon,$lat and $R is 6371 (which is km), and finally that you want the cities to be within $rad of your town:
Select ID, Postcode, Lat, Lon,
acos(sin($lat)*sin(radians(Lat)) + cos($lat)*cos(radians(Lat))*cos(radians(Lon)-$lon))*$R As D
From MyTable
Where acos(sin($lat)*sin(radians(Lat)) + cos($lat)*cos(radians(Lat))*cos(radians(Lon)-$lon))*$R < $rad
SELECT postcode, lat, lng,
truncate(
(degrees(acos
(sin(radians(lat))
*
sin( radians('.$latitude.'))
+
cos(radians(lat))
*
cos( radians('.$latitude.'))
*
cos( radians(lng - ('.$longitude.')))
)
)
* 69.172), 2)
as distance
FROM myData
This query calculates distance (in miles). But when I check distance for same lat and longitude at google maps my result doesnt match. If the distance is around 10 miles then my result is a bit accurate, but over that it gets wrong (for example, my result showed 13 miles and google showed 22 miles for same post code values)
I got this query from http://forums.mysql.com/read.php?23,3868,3868#msg-3868
How can I get it accurate. Any ideas?
Thanks for help.
UPDATE
I tried #Blixt code in PHP. Picked up 2 sample postcodes and their lats longs
//B28 9ET
$lat1 = 52.418819;
$long1 = -1.8481053;
//CV5 8BX
$lat2 = 52.4125573;
$long2 = -1.5407743;
$dtr = M_PI / 180;
$latA = $lat1 * $dtr;
$lonA = $long1 * $dtr;
$latB = $lat2 * $dtr;
$lonB = $long2 * $dtr;
$EarthRadius = 3958.76; //miles
echo $distance = $EarthRadius * acos(cos($latA) * cos($latB) * cos($lonB - $lonA) + sin($latA) * sin($latB));
Results:
My app - 12.95 miles
Google - 17.8 miles
Any ideas how to get it right?
Have a look at this source code. When I tested it against various other measurement services it seemed to get the same results. It's C# but the math should be easy enough to convert.
Here are the relevant pieces:
public const double EarthRadius = 6371.0072; // Kilometers
//public const double EarthRadius = 3958.76; // Miles
/* ... */
const double dtr = Math.PI / 180;
double latA = this.Latitude * dtr;
double lonA = this.Longitude * dtr;
double latB = other.Latitude * dtr;
double lonB = other.Longitude * dtr;
return GpsLocation.EarthRadius * Math.Acos(Math.Cos(latA) * Math.Cos(latB) * Math.Cos(lonB - lonA) + Math.Sin(latA) * Math.Sin(latB));
Note: The Earth is not perfectly spherical, so the constants used may differ. There is no easy way to make the measurements truly exact. See Wikipedia.