Как рассчитать площадь многоугольника на поверхности земли с помощью питона?

Название в основном говорит все. Мне нужно вычислить площадь внутри многоугольника на поверхности Земли, используя Python. Вычисление области, заключенной в произвольный многоугольник на поверхности Земли, говорит о чем-то, но остается неопределенным по техническим деталям:

Если вы хотите сделать это с помощью более "ГИС", тогда вам нужно выбрать единицу измерения для вашего района и найти соответствующую проекцию, которая (не все). Поскольку вы говорят о вычислении произвольный многоугольник, я бы использовал что-то вроде Ламберта азимутального Проецирование равных площадей. Установить начало/центр проекции центр вашего полигона, проект многоугольник к новой координате системы, затем вычислить область, используя стандартные плоские методы.

Итак, как мне это сделать в Python?

Ответ 1

Скажем, у вас есть представление о состоянии Колорадо в формате GeoJSON

{"type": "Polygon", 
 "coordinates": [[
   [-102.05, 41.0], 
   [-102.05, 37.0], 
   [-109.05, 37.0], 
   [-109.05, 41.0]
 ]]}

Все координаты - долгота, широта. Вы можете использовать pyproj для проецирования координат и Shapely найти область любого проекционного многоугольника:

co = {"type": "Polygon", "coordinates": [
    [(-102.05, 41.0),
     (-102.05, 37.0),
     (-109.05, 37.0),
     (-109.05, 41.0)]]}
lon, lat = zip(*co['coordinates'][0])
from pyproj import Proj
pa = Proj("+proj=aea +lat_1=37.0 +lat_2=41.0 +lat_0=39.0 +lon_0=-106.55")

То, что проекция на равную площадь сосредоточена в центре и разбивает интересующую область. Теперь сделайте новое проектируемое представление GeoJSON, превратитесь в геометрический объект Shapely и возьмите область:

x, y = pa(lon, lat)
cop = {"type": "Polygon", "coordinates": [zip(x, y)]}
from shapely.geometry import shape
shape(cop).area  # 268952044107.43506

Это очень близкое приближение к исследуемой области. Для более сложных функций вам нужно будет пробовать по краям между вершинами, чтобы получить точные значения. Все оговорки выше о датах и ​​т.д. Применяются. Если вас интересует только область, вы можете перевести свою функцию в сторону от линии передачи данных перед проецированием.

Ответ 2

Самый простой способ сделать это (на мой взгляд) - проецировать вещи на (очень простую) проекцию равных площадей и использовать один из обычных планарных методов вычисления области.

Прежде всего, я собираюсь предположить, что сферическая земля достаточно близко для ваших целей, если вы задаете этот вопрос. Если нет, то вам нужно перепрограммировать свои данные с помощью соответствующего эллипсоида, и в этом случае вам захочется использовать фактическую проекционную библиотеку (в наши дни все использует proj4 за кадром), такие как привязки python к GDAL/OGR или (гораздо более дружелюбный) pyproj.

Однако, если вы в порядке со сферической землей, это довольно просто сделать без каких-либо специализированных библиотек.

Простейшая проекция равной площади для вычисления представляет собой синусоидальную проекцию . В принципе, вы просто умножаете широту на длину одной градусной широты и долготу на длину градуса широты и косинус широты.

def reproject(latitude, longitude):
    """Returns the x & y coordinates in meters using a sinusoidal projection"""
    from math import pi, cos, radians
    earth_radius = 6371009 # in meters
    lat_dist = pi * earth_radius / 180.0

    y = [lat * lat_dist for lat in latitude]
    x = [long * lat_dist * cos(radians(lat)) 
                for lat, long in zip(latitude, longitude)]
    return x, y

Хорошо... Теперь все, что нам нужно сделать, это вычислить площадь произвольного многоугольника в плоскости.

Существует несколько способов сделать это. Я собираюсь использовать возможно, самый распространенный вариант здесь.

def area_of_polygon(x, y):
    """Calculates the area of an arbitrary polygon given its verticies"""
    area = 0.0
    for i in range(-1, len(x)-1):
        area += x[i] * (y[i+1] - y[i-1])
    return abs(area) / 2.0

Надеюсь, это будет указывать на вас в правильном направлении, во всяком случае...

Ответ 3

Возможно, немного поздно, но вот другой метод, используя теорему Жирарда. Он утверждает, что площадь многоугольника больших кругов равна R ** в 2 раза больше суммы углов между полигонами минус (N-2) * pi, где N - число углов.

Я думал, что это будет стоить публикации, поскольку он не полагается на какие-либо другие библиотеки, кроме numpy, и это совсем другой метод, чем другие. Конечно, это работает только на сфере, поэтому при применении ее к Земле будет некоторая неточность.

Во-первых, я определяю функцию для вычисления угла опоры от точки 1 вдоль большого круга до точки 2:

import numpy as np
from numpy import cos, sin, arctan2

d2r = np.pi/180

def greatCircleBearing(lon1, lat1, lon2, lat2):
    dLong = lon1 - lon2

    s = cos(d2r*lat2)*sin(d2r*dLong)
    c = cos(d2r*lat1)*sin(d2r*lat2) - sin(lat1*d2r)*cos(d2r*lat2)*cos(d2r*dLong)

    return np.arctan2(s, c)

Теперь я могу использовать это, чтобы найти углы, а затем область (В следующем случае, конечно, должны быть указаны интервалы и латы, и они должны быть в правильном порядке. Также необходимо указать радиус сферы.)

N = len(lons)

angles = np.empty(N)
for i in range(N):

    phiB1, phiA, phiB2 = np.roll(lats, i)[:3]
    LB1, LA, LB2 = np.roll(lons, i)[:3]

    # calculate angle with north (eastward)
    beta1 = greatCircleBearing(LA, phiA, LB1, phiB1)
    beta2 = greatCircleBearing(LA, phiA, LB2, phiB2)

    # calculate angle between the polygons and add to angle array
    angles[i] = np.arccos(cos(-beta1)*cos(-beta2) + sin(-beta1)*sin(-beta2))

area = (sum(angles) - (N-2)*np.pi)*R**2

С координатами Колорадо, указанными в другом ответе, и с радиусом Земли 6371 км, я получаю, что область 268930758560.74808

Ответ 4

Поскольку земля является закрытой поверхностью, замкнутый многоугольник, нарисованный на его поверхности, создает ДВЕ многоугольные области. Вам также нужно определить, какой из них находится внутри и снаружи!

В большинстве случаев люди будут иметь дело с маленькими полигонами, и поэтому это "очевидно", но как только у вас есть вещи размером с океаны или континенты, вы должны убедиться, что вы правильно это сделаете.

Кроме того, помните, что строки могут идти от (-179,0) до (+179,0) двумя разными способами. Один из них намного длиннее другого. Опять же, в основном вы сделаете предположение, что это строка, которая идет от (-179,0) до (-180,0), которая равна (+180,0), а затем (+179,0), но одна день... это не будет.

Рассмотрение lat-long как простой (x, y) системы координат или даже пренебрежение тем фактом, что любая проекция координат будет иметь искажения и разрывы, может заставить вас терпеть неудачу в сферах.

Ответ 5

Вот решение, которое использует basemap вместо pyproj и shapely для преобразования координат. Идея такая же, как и предложенная @sgillies. Учтите, что я добавил пятую точку, чтобы путь был замкнутым контуром.

import numpy
from mpl_toolkits.basemap import Basemap

coordinates=numpy.array([
[-102.05, 41.0], 
[-102.05, 37.0], 
[-109.05, 37.0], 
[-109.05, 41.0],
[-102.05, 41.0]])

lats=coordinates[:,1]
lons=coordinates[:,0]

lat1=numpy.min(lats)
lat2=numpy.max(lats)
lon1=numpy.min(lons)
lon2=numpy.max(lons)

bmap=Basemap(projection='cea',llcrnrlat=lat1,llcrnrlon=lon1,urcrnrlat=lat2,urcrnrlon=lon2)
xs,ys=bmap(lons,lats)

area=numpy.abs(0.5*numpy.sum(ys[:-1]*numpy.diff(xs)-xs[:-1]*numpy.diff(ys)))
area=area/1e6

print area

Результат - 268993.609651 в км ^ 2.

Ответ 6

Или просто используйте библиотеку: https://github.com/scisco/area

from area import area
>>> obj = {'type':'Polygon','coordinates':[[[-180,-90],[-180,90],[180,90],[180,-90],[-180,-90]]]}
>>> area(obj)
511207893395811.06

... возвращает площадь в квадратных метрах.