使用从笛卡尔空间生成的纬度和经度以及世界文件计算多边形区域面积

10

给定一系列GPS坐标对,我需要计算多边形(n-gon)的面积。这个面积相对较小(不大于50,000平方英尺)。地理编码是通过使用来自世界文件的数据进行仿射变换创建的。

我已经尝试使用两步方法,将地理编码转换为笛卡尔坐标:

double xPos = (lon-lonAnchor)*( Math.toRadians( 6378137 ) )*Math.cos( latAnchor );
double yPos = (lat-latAnchor)*( Math.toRadians( 6378137 ) );

然后我使用叉积计算来确定面积。

问题是结果的准确度有些偏差(大约1%)。有什么方法可以改进吗?

谢谢。

10个回答

12

我在互联网上查找了各种多边形面积公式(或代码),但没有找到任何一个好的或容易实现的。

现在我已经编写了代码片段,可以计算绘制在地球表面上的多边形的面积。该多边形可以有n个顶点,每个顶点都有自己的纬度和经度。

几个重要的要点

  1. 传递给此函数的数组输入将具有“n + 1”个元素。最后一个元素将与第一个元素具有相同的值。
  2. 我编写的是非常基本的C#代码,因此其他人也可以将其应用于其他语言。
  3. 6378137是以米为单位的地球半径的值。
  4. 输出面积的单位为平方米

    private static double CalculatePolygonArea(IList<MapPoint> coordinates)
    {
        double area = 0;
    
        if (coordinates.Count > 2)
        {
            for (var i = 0; i < coordinates.Count - 1; i++)
            {
                MapPoint p1 = coordinates[i];
                MapPoint p2 = coordinates[i + 1];
                area += ConvertToRadian(p2.Longitude - p1.Longitude) * (2 + Math.Sin(ConvertToRadian(p1.Latitude)) + Math.Sin(ConvertToRadian(p2.Latitude)));
            }
    
            area = area * 6378137 * 6378137 / 2;
        }
    
        return Math.Abs(area);
    }
    
    private static double ConvertToRadian(double input)
    {
        return input * Math.PI / 180;
    }
    

我尝试了你的代码,但有些不对。有什么想法吗?参见:代码 - pogorman
你把“area = area * R * R / 2;”放在了for循环内部,但它应该在循环外部。 - Risky Pathak
1
我认为你应该将 p1.Longitudep2.Longitude 也转换为弧度。在进行这个修改后,我得到了与 google.maps.geometry.spherical.computeArea 函数相似的结果。 - MC X
经过更正,这看起来很好。并且与Open Layers中的getGeodesicArea非常相似(减去投影部分)。请参见:https://github.com/openlayers/openlayers/blob/v2.13.1/lib/OpenLayers/Geometry/LinearRing.js#L251 - Nux

3
我正在修改谷歌地图,使用户可以通过单击顶点计算多边形的面积。在确保 Math.cos(latAnchor) 首先以弧度为单位之前,它并没有给出正确的面积。因此:
double xPos = (lon-lonAnchor)*( Math.toRadians( 6378137 ) )*Math.cos( latAnchor );

成为:

double xPos = (lon-lonAnchor)*( 6378137*PI/180 ) )*Math.cos( latAnchor*PI/180 );

其中lon、lonAnchor和latAnchor的单位为度。现在完美运作。


2
1%的误差似乎有点高,这可能是由于您的近似值。您是在与实际测量结果或某些理想计算进行比较吗?请记住,GPS中也存在误差,这可能会导致误差。
如果您想要更准确的方法,请参考此问题的良好答案。如果您想要更快速的方法,可以使用WGS84大地水准面代替您的参考球体进行转换为笛卡尔坐标(ECEF)。这里是维基链接,用于该转换。

我正在与已知区域的实际测量值进行比较。有趣的是,如果我将GPS坐标通过Haversine方法运行,我会得到非常准确的距离计算结果,从而得出准确的周长值。 - Heath Lilley
抱歉回复晚了,最终使用了WG84大地水准面和proj4 java库,效果很好,感谢您的帮助。 - Heath Lilley

1
这个“1%”的差异是由于地球略微呈椭圆形,因此使用球形模型计算会产生通常高达0.3%左右的误差,具体取决于位置。

0

我不确定为什么,但是使用上述公式得到的结果与Google地图测量同一区域时返回的结果相去甚远。

因此,我进行了搜索,并提出了这种由GPS坐标定义的多边形面积计算的JavaScript方法:

const PI = Math.PI;
const EARTH_RADIUS_IN_METERS = 6378137;
const EARTH_CIRCUMFERENCE_IN_METERS = 2*EARTH_RADIUS_IN_METERS*PI;

function areaClaculator(points) {
    let area = null;
    if (!isValueEmpty(points) && points.length > 2) {
        let p0 = points[0]
        let newPoints = [];
        for (let i=1; i<points.length; i++) {
            let p = points[i];
            
            let y = (p.lat - p0.lat) / 360 * EARTH_CIRCUMFERENCE_IN_METERS;
            let x = (p.lng - p0.lng) / 360 * EARTH_CIRCUMFERENCE_IN_METERS * Math.cos(rad(p.lat));
            let entry = {};
            entry.x = x;
            entry.y = y;
            newPoints.push(entry);
        }
        
        if (!isValueEmpty(newPoints) && newPoints.length > 1) {
            area = 0;
            for (let i=0;i< newPoints.length - 1; i++) {
                let p1 = newPoints[i];
                let p2 = newPoints[i+1];
                
                area += ((p1.y * p2.x) - (p1.x*p2.y))/2;
            }
            area = Math.abs(area);
        }
    }
    return area;
}

function rad(degrees) {
  return degrees * PI / 180;
}

points 存储的值如下:{lng: -73.462556156587, lat: 45.48566183708046}


0

尝试在Swift Playground中执行此操作,但结果偏差很大。我正在将示例坐标(39.58571008386715,-104.94522892318253)输入函数中。

func deg2rad(_ number: Double) -> Double {
        return number * .pi / 180
    }
    
func areaCalc(lat: [Double]?, lon: [Double]?){
    guard let lat = lat,
       let lon = lon
    else { return }
    var area: Double = 0.0
    if(lat.count > 2){
        for i in stride(from: 0, to: lat.count - 1, by: 1) {
            
            let p1lon = lon[i]
            let p1lat = lat[i]
            let p2lon = lon[i+1]
            let p2lat = lat[i+1]
            
            area = area + (deg2rad(p2lon - p1lon)) * (2 + sin(deg2rad(p1lat))) + (sin(deg2rad(p2lat)))
        }
        area = area * 6378137.0 * 6378137.0
        area = abs(area / 2)
    }
}

目前你的回答不够清晰,请编辑并添加更多细节,以帮助其他人理解它如何回答问题。你可以在帮助中心找到有关如何编写好答案的更多信息。 - Community

0

将RiskyPathak的片段改编为PHP

function CalculatePolygonArea($coordinates) {
    $area = 0;
    $coordinatesCount = sizeof($coordinates);
    if ($coordinatesCount > 2) {
      for ($i = 0; $i < $coordinatesCount - 1; $i++) {
        $p1 = $coordinates[$i];
        $p2 = $coordinates[$i + 1];
        $p1Longitude = $p1[0];
        $p2Longitude = $p2[0];
        $p1Latitude = $p1[1];
        $p2Latitude = $p2[1];
        $area += ConvertToRadian($p2Longitude - $p1Longitude) * (2 + sin(ConvertToRadian($p1Latitude)) + sin(ConvertToRadian($p2Latitude)));
      }
    $area = $area * 6378137 * 6378137 / 2;
    }
    return abs(round(($area));
}

function ConvertToRadian($input) {
    $output = $input * pi() / 180;
    return $output;
}

0

基于Risky Pathak的解决方案,这里提供了SQL(Redshift)计算GeoJSON多边形面积的解决方案(假设linestring 0是最外层多边形)。

create or replace view geo_area_area as 
with points as (
    select ga.id as key_geo_area
    , ga.name, gag.linestring
    , gag.position
    , radians(gag.longitude) as x
    , radians(gag.latitude) as y
    from geo_area ga
    join geo_area_geometry gag on (gag.key_geo_area = ga.id)
)
, polygons as (
    select key_geo_area, name, linestring, position 
    , x
    , lag(x) over (partition by key_geo_area, linestring order by position) as prev_x
    , y
    , lag(y) over (partition by key_geo_area, linestring order by position) as prev_y
    from points
)
, area_linestrings as (
    select key_geo_area, name, linestring
    , abs( sum( (x - prev_x) * (2 + sin(y) + sin(prev_y)) ) ) * 6378137 * 6378137 / 2 / 10^6 as area_km_squared
    from polygons
    where position != 0
    group by 1, 2, 3
)
select key_geo_area, name
, sum(case when linestring = 0 then area_km_squared else -area_km_squared end) as area_km_squared
from area_linestrings
group by 1, 2
;


0

谢谢Risky Pathak!

为了分享精神,这是我在Delphi中的改编:

interface

uses 
  System.Math; 

TMapGeoPoint = record
  Latitude: Double;
  Longitude: Double;
end;


function AreaInAcres(AGeoPoints: TList<TMapGeoPoint>): Double;

implementation

function AreaInAcres(AGeoPoints: TList<TMapGeoPoint>): Double;
var
  Area: Double;
  i: Integer;
  P1, P2: TMapGeoPoint;
begin
 Area := 0;

 // We need at least 2 points
 if (AGeoPoints.Count > 2) then
 begin
   for I := 0 to AGeoPoints.Count - 1 do
   begin
     P1 := AGeoPoints[i];
     if i < AGeoPoints.Count - 1  then
       P2 := AGeoPoints[i + 1]
     else
       P2 := AGeoPoints[0];
     Area := Area + DegToRad(P2.Longitude - P1.Longitude) * (2 + 
        Sin(DegToRad(P1.Latitude)) + Sin(DegToRad(P2.Latitude)));
    end;

    Area := Area * 6378137 * 6378137 / 2;

  end;

  Area := Abs(Area); //Area (in sq meters)

  // 1 Square Meter = 0.000247105 Acres
  result := Area * 0.000247105;
end;

0

将 RiskyPathak 的片段改编为 Ruby

def deg2rad(input)
  input * Math::PI / 180.0
end

def polygone_area(coordinates)
  return 0.0 unless coordinates.size > 2

  area = 0.0
  coor_p = coordinates.first
  coordinates[1..-1].each{ |coor|
    area += deg2rad(coor[1] - coor_p[1]) * (2 + Math.sin(deg2rad(coor_p[0])) + Math.sin(deg2rad(coor[0])))
    coor_p = coor
  }

  (area * 6378137 * 6378137 / 2.0).abs # 6378137 Earth's radius in meters
end

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接