Skip to content

Commit ea81397

Browse files
committed
WIP spherical points and area
1 parent b60e69a commit ea81397

File tree

1 file changed

+116
-0
lines changed

1 file changed

+116
-0
lines changed
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import GeoInterface as GI
2+
import GeometryOps as GO
3+
import LinearAlgebra
4+
import LinearAlgebra: dot, cross
5+
6+
## Get the area of a LinearRing with coordinates in radians
7+
struct SphericalPoint{T <: Real}
8+
data::NTuple{3, T}
9+
end
10+
SphericalPoint(x, y, z) = SphericalPoint((x, y, z))
11+
12+
# define the 4 basic mathematical operators elementwise on the data tuple
13+
Base.:+(p::SphericalPoint, q::SphericalPoint) = SphericalPoint(p.data .+ q.data)
14+
Base.:-(p::SphericalPoint, q::SphericalPoint) = SphericalPoint(p.data .- q.data)
15+
Base.:*(p::SphericalPoint, q::SphericalPoint) = SphericalPoint(p.data .* q.data)
16+
Base.:/(p::SphericalPoint, q::SphericalPoint) = SphericalPoint(p.data ./ q.data)
17+
# Define sum on a SphericalPoint to sum across its data
18+
Base.sum(p::SphericalPoint) = sum(p.data)
19+
20+
# define dot and cross products
21+
LinearAlgebra.dot(p::SphericalPoint, q::SphericalPoint) = sum(p * q)
22+
function LinearAlgebra.cross(a::SphericalPoint, b::SphericalPoint)
23+
a1, a2, a3 = a.data
24+
b1, b2, b3 = b.data
25+
SphericalPoint((a2*b3-a3*b2, a3*b1-a1*b3, a1*b2-a2*b1))
26+
end
27+
28+
# Using Eriksson's formula for the area of spherical triangles: https://www.jstor.org/stable/2691141
29+
# This melts down only when two points are antipodal, which in our case will not happen.
30+
function _unit_spherical_triangle_area(a, b, c)
31+
#t = abs(dot(a, cross(b, c)))
32+
#t /= 1 + dot(b,c) + dot(c, a) + dot(a, b)
33+
t = abs(dot(a, (cross(b - a, c - a))) / dot(b + a, c + a))
34+
2*atan(t)
35+
end
36+
37+
_lonlat_to_sphericalpoint(p) = _lonlat_to_sphericalpoint(GI.x(p), GI.y(p))
38+
function _lonlat_to_sphericalpoint(lon, lat)
39+
lonsin, loncos = sincosd(lon)
40+
latsin, latcos = sincosd(lat)
41+
x = latcos * loncos
42+
y = latcos * lonsin
43+
z = latsin
44+
return SphericalPoint(x,y,z)
45+
end
46+
47+
48+
49+
# Extend area to spherical
50+
51+
# TODO: make this the other way around, but that can wait.
52+
GO.area(m::GO.Planar, geoms) = GO.area(geoms)
53+
54+
function GO.area(m::GO.Spherical, geoms)
55+
return GO.applyreduce(+, GI.PolygonTrait(), geoms) do poly
56+
GO.area(m, GI.PolygonTrait(), poly)
57+
end * ((-m.radius^2)/ 2) # do this after the sum, to increase accuracy and minimize calculations.
58+
end
59+
60+
function GO.area(m::GO.Spherical, ::GI.PolygonTrait, poly)
61+
area = abs(_ring_area(m, GI.getexterior(poly)))
62+
for interior in GI.gethole(poly)
63+
area -= abs(_ring_area(m, interior))
64+
end
65+
return area
66+
end
67+
68+
function _ring_area(m::GO.Spherical, ring)
69+
# convert ring to a sequence of SphericalPoints
70+
points = _lonlat_to_sphericalpoint.(GI.getpoint(ring))[1:end-1] # deliberately drop the closing point
71+
p1, p2, p3 = points[1], points[2], points[3]
72+
# For a spherical polygon, we can compute the area by splitting it into triangles
73+
# and summing their areas. We use the first point as a common vertex for all triangles.
74+
area = 0.0
75+
# Sum areas of triangles formed by first point and consecutive pairs of points
76+
np = length(points)
77+
for i in 1:np
78+
p1, p2, p3 = p2, p3, points[i]
79+
area += _unit_spherical_triangle_area(p1, p2, p3)
80+
end
81+
return area
82+
end
83+
84+
function _ring_area(m::GO.Spherical, ring)
85+
# Convert ring points to spherical coordinates
86+
points = GO.tuples(ring).geom
87+
88+
# Remove last point if it's the same as first (closed ring)
89+
if points[end] == points[1]
90+
points = points[1:end-1]
91+
end
92+
93+
n = length(points)
94+
if n < 3
95+
return 0.0
96+
end
97+
98+
area = 0.0
99+
100+
# Use L'Huilier's formula to sum the areas of spherical triangles
101+
# formed by first point and consecutive pairs of points
102+
for i in 1:n
103+
p1, p2, p3 = points[mod1(i-1, n)], points[mod1(i, n)], points[mod1(i+1, n)]
104+
area += sind(GI.y(p2)) * (GI.x(p3) - GI.x(p1))
105+
end
106+
107+
return area
108+
end
109+
110+
111+
112+
113+
# Test the area calculation
114+
p1 = GI.Polygon([GI.LinearRing(Point2f[(0, 0), (1, 0), (0, 1), (0, 0)] .- (Point2f(0.5, 0.5),))])
115+
116+
GO.area(GO.Spherical(), p1)

0 commit comments

Comments
 (0)