带有经纬度SRID的PostGIS中的真实(大圆)距离?
时间:2020-03-06 14:36:51 来源:igfitidea点击:
我在PostGIS数据库(-4326)中使用经纬度/经度SRID。我想以一种有效的方式找到最接近给定点的点。我试图做一个
ORDER BY ST_Distance(point, ST_GeomFromText(?,-4326))
在较低的48个州给我好的结果,但是在阿拉斯加最多的州给了我垃圾。有没有一种方法可以在PostGIS中进行真实的距离计算,还是我必须提供一个合理大小的缓冲区,然后计算较大的圆距离,然后在代码中对结果进行排序?
解决方案
这是来自SQL Server,我使用Haversine的距离太短了,可能会受到我们阿拉斯加问题的困扰(可能相距一英里):
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速度很慢,但精确到1毫米以内(而且我只发现了它的一个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; }
我们正在寻找ST_distance_sphere(点,点)或者st_distance_spheroid(点,点)。
看:
http://postgis.refractions.net/documentation/manual-1.3/ch06.html#distance_sphere
http://postgis.refractions.net/documentation/manual-1.3/ch06.html#distance_spheroid
通常将其称为测地距离或者大地测量距离……虽然这两个术语的含义略有不同,但它们往往可以互换使用。
或者,我们可以投影数据并使用标准的st_distance函数...仅在短距离(使用UTM或者状态平面)或者所有距离都相对于一两个点(等距投影)时才适用。
PostGIS 1.5使用纬度和经度来处理真实的地球距离。知道纬度/经度本质上是有角度的,并且具有360度的线