Я пытаюсь выяснить, как лучше всего найти центр тяжести произвольной формы, наложенной на единичную сферу, причем упорядоченные по порядку (по часовой стрелке или анти-cw) вершины для границы формы. Плотность вершин нерегулярна вдоль границы, поэтому длины дуг между ними обычно не равны. Поскольку формы могут быть очень большими (половина полушария), как правило, невозможно просто проецировать вершины на плоскость и использовать плоские методы, как описано в Википедии (извините, мне не разрешено более двух гиперссылок в качестве новичков). Немного лучший подход предполагает использование планарной геометрии, управляемой сферическими координатами, но опять же, с большими полигонами этот метод терпит неудачу, как хорошо иллюстрированный здесь. На этой же странице "Cffk" выделил эту статью, в которой описывается метод вычисления центроида сферических треугольников. Я попытался реализовать этот метод, но безуспешно, и я надеюсь, что кто-то может определить проблему?
Я сохранил определения переменных, похожие на те, что указаны в документе, чтобы было легче сравнивать. Вход (данные) представляет собой список координат долготы/широты, преобразованный в координаты [x, y, z] кодом. Для каждого из треугольников я произвольно фиксировал одну точку как + z-полюс, остальные две вершины состоят из пары соседних точек вдоль границы многоугольника. Код проходит вдоль границы (начиная с произвольной точки), используя каждый граничный сегмент многоугольника в качестве треугольной стороны по очереди. Субцентроид определяется для каждого из этих отдельных сферических треугольников, и они взвешиваются в соответствии с областью треугольника и добавляются для вычисления общего центроида полигонов. Я не получаю никаких ошибок при запуске кода, но возвращаемые итоговые центроиды явно неправильны (у меня есть некоторые очень простые формы, где местоположение центра тяжести однозначно). Я не нашел какой-либо разумной картины в месте возврата центроидов... поэтому на данный момент я не уверен, что происходит не так, как в математике, так и в коде (хотя подозрение - математика).
В приведенном ниже коде должен работать copy-paste, как если бы вы хотели его попробовать. Если у вас установлен matplotlib и numpy, он будет отображать результаты (он будет игнорировать отображение, если вы этого не сделаете). Вам просто нужно поместить данные долготы/широты под кодом в текстовый файл example.txt.
from math import *
try:
import matplotlib as mpl
import matplotlib.pyplot
from mpl_toolkits.mplot3d import Axes3D
import numpy
plotting_enabled = True
except ImportError:
plotting_enabled = False
def sph_car(point):
if len(point) == 2:
point.append(1.0)
rlon = radians(float(point[0]))
rlat = radians(float(point[1]))
x = cos(rlat) * cos(rlon) * point[2]
y = cos(rlat) * sin(rlon) * point[2]
z = sin(rlat) * point[2]
return [x, y, z]
def xprod(v1, v2):
x = v1[1] * v2[2] - v1[2] * v2[1]
y = v1[2] * v2[0] - v1[0] * v2[2]
z = v1[0] * v2[1] - v1[1] * v2[0]
return [x, y, z]
def dprod(v1, v2):
dot = 0
for i in range(3):
dot += v1[i] * v2[i]
return dot
def plot(poly_xyz, g_xyz):
fig = mpl.pyplot.figure()
ax = fig.add_subplot(111, projection='3d')
# plot the unit sphere
u = numpy.linspace(0, 2 * numpy.pi, 100)
v = numpy.linspace(-1 * numpy.pi / 2, numpy.pi / 2, 100)
x = numpy.outer(numpy.cos(u), numpy.sin(v))
y = numpy.outer(numpy.sin(u), numpy.sin(v))
z = numpy.outer(numpy.ones(numpy.size(u)), numpy.cos(v))
ax.plot_surface(x, y, z, rstride=4, cstride=4, color='w', linewidth=0,
alpha=0.3)
# plot 3d and flattened polygon
x, y, z = zip(*poly_xyz)
ax.plot(x, y, z)
ax.plot(x, y, zs=0)
# plot the alleged 3d and flattened centroid
x, y, z = g_xyz
ax.scatter(x, y, z, c='r')
ax.scatter(x, y, 0, c='r')
# display
ax.set_xlim3d(-1, 1)
ax.set_ylim3d(-1, 1)
ax.set_zlim3d(0, 1)
mpl.pyplot.show()
lons, lats, v = list(), list(), list()
# put the two-column data at the bottom of the question into a file called
# example.txt in the same directory as this script
with open('example.txt') as f:
for line in f.readlines():
sep = line.split()
lons.append(float(sep[0]))
lats.append(float(sep[1]))
# convert spherical coordinates to cartesian
for lon, lat in zip(lons, lats):
v.append(sph_car([lon, lat, 1.0]))
# z unit vector/pole ('north pole'). This is an arbitrary point selected to act as one
#(fixed) vertex of the summed spherical triangles. The other two vertices of any
#triangle are composed of neighboring vertices from the polygon boundary.
np = [0.0, 0.0, 1.0]
# Gx,Gy,Gz are the cartesian coordinates of the calculated centroid
Gx, Gy, Gz = 0.0, 0.0, 0.0
for i in range(-1, len(v) - 1):
# cycle through the boundary vertices of the polygon, from 0 to n
if all((v[i][0] != v[i+1][0],
v[i][1] != v[i+1][1],
v[i][2] != v[i+1][2])):
# this just ignores redundant points which are common in my larger input files
# A,B,C are the internal angles in the triangle: 'np-v[i]-v[i+1]-np'
A = asin(sqrt((dprod(np, xprod(v[i], v[i+1])))**2
/ ((1 - (dprod(v[i+1], np))**2) * (1 - (dprod(np, v[i]))**2))))
B = asin(sqrt((dprod(v[i], xprod(v[i+1], np)))**2
/ ((1 - (dprod(np , v[i]))**2) * (1 - (dprod(v[i], v[i+1]))**2))))
C = asin(sqrt((dprod(v[i + 1], xprod(np, v[i])))**2
/ ((1 - (dprod(v[i], v[i+1]))**2) * (1 - (dprod(v[i+1], np))**2))))
# A/B/Cbar are the vertex angles, such that if 'O' is the sphere center, Abar
# is the angle (v[i]-O-v[i+1])
Abar = acos(dprod(v[i], v[i+1]))
Bbar = acos(dprod(v[i+1], np))
Cbar = acos(dprod(np, v[i]))
# e is the 'spherical excess', as defined on wikipedia
e = A + B + C - pi
# mag1/2/3 are the magnitudes of vectors np,v[i] and v[i+1].
mag1 = 1.0
mag2 = float(sqrt(v[i][0]**2 + v[i][1]**2 + v[i][2]**2))
mag3 = float(sqrt(v[i+1][0]**2 + v[i+1][1]**2 + v[i+1][2]**2))
# vec1/2/3 are cross products, defined here to simplify the equation below.
vec1 = xprod(np, v[i])
vec2 = xprod(v[i], v[i+1])
vec3 = xprod(v[i+1], np)
# multiplying vec1/2/3 by e and respective internal angles, according to the
#posted paper
for x in range(3):
vec1[x] *= Cbar / (2 * e * mag1 * mag2
* sqrt(1 - (dprod(np, v[i])**2)))
vec2[x] *= Abar / (2 * e * mag2 * mag3
* sqrt(1 - (dprod(v[i], v[i+1])**2)))
vec3[x] *= Bbar / (2 * e * mag3 * mag1
* sqrt(1 - (dprod(v[i+1], np)**2)))
Gx += vec1[0] + vec2[0] + vec3[0]
Gy += vec1[1] + vec2[1] + vec3[1]
Gz += vec1[2] + vec2[2] + vec3[2]
approx_expected_Gxyz = (0.78, -0.56, 0.27)
print('Approximate Expected Gxyz: {0}\n'
' Actual Gxyz: {1}'
''.format(approx_expected_Gxyz, (Gx, Gy, Gz)))
if plotting_enabled:
plot(v, (Gx, Gy, Gz))
Заранее благодарим за любые предложения или идеи.
EDIT: Вот цифра, показывающая проекцию единичной сферы с полигоном и итоговый центроид, который я вычисляю из кода. Ясно, что центроид ошибочен, поскольку многоугольник довольно мал и выпуклый, но центроид выходит за его периметр.
EDIT: Здесь очень похожий набор координат, как указано выше, но в исходном формате [lon, lat], который я обычно использую (который теперь преобразован в [x, y, z] обновленным кодом).
-39.366295 -1.633460
-47.282630 -0.740433
-53.912136 0.741380
-59.004217 2.759183
-63.489005 5.426812
-68.566001 8.712068
-71.394853 11.659135
-66.629580 15.362600
-67.632276 16.827507
-66.459524 19.069327
-63.819523 21.446736
-61.672712 23.532143
-57.538431 25.947815
-52.519889 28.691766
-48.606227 30.646295
-45.000447 31.089437
-41.549866 32.139873
-36.605156 32.956277
-32.010080 34.156692
-29.730629 33.756566
-26.158767 33.714080
-25.821513 34.179648
-23.614658 36.173719
-20.896869 36.977645
-17.991994 35.600074
-13.375742 32.581447
-9.554027 28.675497
-7.825604 26.535234
-7.825604 26.535234
-9.094304 23.363132
-9.564002 22.527385
-9.713885 22.217165
-9.948596 20.367878
-10.496531 16.486580
-11.151919 12.666850
-12.350144 8.800367
-15.446347 4.993373
-20.366139 1.132118
-24.784805 -0.927448
-31.532135 -1.910227
-39.366295 -1.633460
EDIT: Еще несколько примеров... с 4 вершинами, определяющими идеальный квадрат с центром в [1,0,0]. Я получаю ожидаемый результат:
Однако из несимметричного треугольника я получаю центроид, который нигде не близок... центроид действительно падает на дальнюю сторону сферы (здесь проецируется на лицевую сторону как антипод):
Интересно, что оценка центра тяжести выглядит "стабильной" в том смысле, что если я инвертирую список (иди от часовой стрелки до порядка против часовой стрелки или наоборот), то центроид точно инвертирует.