Distance réelle (grand cercle) dans PostGIS avec SRID lat / long?
Question
J'utilise un SRID lat / long dans ma base de données PostGIS (-4326). Je voudrais trouver les points les plus proches d'un point d'une manière efficace. J'ai essayé de faire un
ORDER BY ST_Distance(point, ST_GeomFromText(?,-4326))
ce qui me donne des résultats satisfaisants dans les 48 États inférieurs, mais en Alaska, cela me laisse des ordures. Existe-t-il un moyen de faire de vrais calculs de distance dans PostGIS, ou vais-je devoir donner une mémoire tampon de taille raisonnable, puis calculer les grandes distances du cercle et trier les résultats dans le code par la suite?
La solution
Vous recherchez ST_distance_sphere (point, point) ou st_distance_spheroid (point, point).
Voir:
http://postgis.refractions.net/documentation/manual- 1.3 / ch06.html # distance_sphere http://postgis.refractions.net/documentation/manual-1.3/ch06 .html # distance_spheroid
Il est normalement fait référence à une distance géodésique ou géodésique ... alors que les deux termes ont des significations légèrement différentes, ils ont tendance à être utilisés de manière interchangeable.
Vous pouvez également projeter les données et utiliser la fonction st_distance standard ... ceci n’est pratique que sur de courtes distances (avec UTM ou un plan d’état) ou si toutes les distances sont relatives à un ou deux points (projections équidistantes).
Autres conseils
PostGIS 1.5 gère les distances réelles du globe à l’aide de lat long et de mètres. Il est conscient que lat / long est de nature angulaire et présente une ligne à 360 degrés
Cela vient de SQL Server et j'utilise Haversine pour une distance ridiculement rapide qui pourrait être affectée par votre problème en Alaska (peut être décalé d'un kilomètre):
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 est lent, mais précis à 1 mm (et je n’en ai trouvé qu’un javascript):
/*
* 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;
}