@@ -26,11 +26,11 @@ Base.@kwdef struct CutAtAntimeridianAndPoles <: GeometryCorrection
26
26
" The value at the south pole, in your angular units"
27
27
southpole:: Float64 = - 90.0 # TODO not used!!!
28
28
" The value at the left edge of the antimeridian, in your angular units"
29
- left:: Float64 = - 180.0 # TODO not used!!!
29
+ left:: Float64 = - 180.0
30
30
" The value at the right edge of the antimeridian, in your angular units"
31
- right:: Float64 = 180.0 # TODO not used!!!
31
+ right:: Float64 = 180.0
32
32
" The period of the cyclic / cylindrical coordinate system's x value, usually computed automatically so you don't have to provide it."
33
- period:: Float64 = right - left # TODO not used!!!
33
+ period:: Float64 = right - left
34
34
" If the polygon is known to enclose the north pole, set this to true"
35
35
force_north_pole:: Bool = false # TODO not used!!!
36
36
" If the polygon is known to enclose the south pole, set this to true"
@@ -77,16 +77,16 @@ function spherical_degrees_to_cartesian(point::Tuple{Float64,Float64})::Tuple{Fl
77
77
end
78
78
79
79
# Calculate crossing latitude using great circle method
80
- function crossing_latitude_great_circle (start:: Tuple{Float64,Float64} , endpoint:: Tuple{Float64,Float64} ):: Float64
80
+ function crossing_latitude_great_circle (c :: CutAtAntimeridianAndPoles , start:: Tuple{Float64,Float64} , endpoint:: Tuple{Float64,Float64} ):: Float64
81
81
# Convert points to 3D vectors
82
82
p1 = spherical_degrees_to_cartesian (start)
83
83
p2 = spherical_degrees_to_cartesian (endpoint)
84
84
85
85
# Cross product defines plane through both points
86
86
n1 = _cross (p1, p2)
87
87
88
- # Unit vector -Y defines meridian plane
89
- n2 = ( 0.0 , - 1.0 , 0.0 )
88
+ # Unit vector that defines the meridian plane
89
+ n2 = spherical_degrees_to_cartesian (c . left , 0.0 )
90
90
91
91
# Intersection of planes defined by cross product
92
92
intersection = _cross (n1, n2)
@@ -98,52 +98,52 @@ function crossing_latitude_great_circle(start::Tuple{Float64,Float64}, endpoint:
98
98
end
99
99
100
100
# Calculate crossing latitude using flat projection method
101
- function crossing_latitude_flat (start:: Tuple{Float64,Float64} , endpoint :: Tuple{Float64,Float64} ):: Float64
102
- lat_delta = endpoint [2 ] - start[2 ]
103
- if endpoint [1 ] > 0
101
+ function crossing_latitude_flat (c :: CutAtAntimeridianAndPoles , start:: Tuple{Float64,Float64} , stop :: Tuple{Float64,Float64} ):: Float64
102
+ lat_delta = stop [2 ] - start[2 ]
103
+ if stop [1 ] > 0
104
104
round (
105
- start[2 ] + (180.0 - start[1 ]) * lat_delta / (endpoint [1 ] + 360.0 - start[1 ]),
105
+ start[2 ] + (c . right - start[1 ]) * lat_delta / (stop [1 ] + c . period - start[1 ]),
106
106
digits= 7
107
107
)
108
108
else
109
109
round (
110
- start[2 ] + (start[1 ] + 180.0 ) * lat_delta / (start[1 ] + 360.0 - endpoint [1 ]),
110
+ start[2 ] + (start[1 ] - c . left ) * lat_delta / (start[1 ] + c . period - stop [1 ]),
111
111
digits= 7
112
112
)
113
113
end
114
114
end
115
115
116
116
# Main crossing latitude calculation function
117
- function crossing_latitude (start:: Tuple{Float64,Float64} , endpoint:: Tuple{Float64,Float64} , great_circle:: Bool ):: Float64
117
+ function crossing_latitude (c :: CutAtAntimeridianAndPoles , start:: Tuple{Float64,Float64} , endpoint:: Tuple{Float64,Float64} , great_circle:: Bool ):: Float64
118
118
abs (start[1 ]) == 180 && return start[2 ]
119
119
abs (endpoint[1 ]) == 180 && return endpoint[2 ]
120
120
121
- return great_circle ? crossing_latitude_great_circle (start, endpoint) : crossing_latitude_flat (start, endpoint)
121
+ return great_circle ? crossing_latitude_great_circle (c, start, endpoint) : crossing_latitude_flat (c, start, endpoint)
122
122
end
123
123
124
124
# Normalize coordinates to ensure longitudes are between -180 and 180
125
- function normalize_coords (coords:: Vector{Tuple{Float64,Float64}} ):: Vector{Tuple{Float64,Float64}}
125
+ function normalize_coords (c :: CutAtAntimeridianAndPoles , coords:: Vector{Tuple{Float64,Float64}} ):: Vector{Tuple{Float64,Float64}}
126
126
normalized = deepcopy (coords)
127
127
all_on_antimeridian = true
128
128
129
129
for i in eachindex (normalized)
130
130
point = normalized[i]
131
131
prev_point = normalized[mod1 (i- 1 , length (normalized))]
132
132
133
- if isapprox (point[1 ], 180 )
134
- if abs (point[2 ]) != 90 && isapprox (prev_point[1 ], - 180 )
135
- normalized[i] = (- 180.0 , point[2 ])
133
+ if isapprox (point[1 ], c . right )
134
+ if abs (point[2 ]) != c . northpole && isapprox (prev_point[1 ], c . left )
135
+ normalized[i] = (c . left , point[2 ])
136
136
else
137
- normalized[i] = (180.0 , point[2 ])
137
+ normalized[i] = (c . right , point[2 ])
138
138
end
139
- elseif isapprox (point[1 ], - 180 )
140
- if abs (point[2 ]) != 90 && isapprox (prev_point[1 ], 180 )
141
- normalized[i] = (180.0 , point[2 ])
139
+ elseif isapprox (point[1 ], c . left )
140
+ if abs (point[2 ]) != c . northpole && isapprox (prev_point[1 ], c . right )
141
+ normalized[i] = (c . right , point[2 ])
142
142
else
143
- normalized[i] = (- 180.0 , point[2 ])
143
+ normalized[i] = (c . left , point[2 ])
144
144
end
145
145
else
146
- normalized[i] = (((point[1 ] + 180 ) % 360 ) - 180 , point[2 ])
146
+ normalized[i] = (((point[1 ] - c . left ) % c . period) + c . left , point[2 ])
147
147
all_on_antimeridian = false
148
148
end
149
149
end
@@ -152,7 +152,7 @@ function normalize_coords(coords::Vector{Tuple{Float64,Float64}})::Vector{Tuple{
152
152
end
153
153
154
154
# Segment a ring of coordinates at antimeridian crossings
155
- function segment_ring (coords:: Vector{Tuple{Float64,Float64}} , great_circle:: Bool ):: Vector{Vector{Tuple{Float64,Float64}}}
155
+ function segment_ring (c :: CutAtAntimeridianAndPoles , coords:: Vector{Tuple{Float64,Float64}} , great_circle:: Bool ):: Vector{Vector{Tuple{Float64,Float64}}}
156
156
segments = Vector {Vector{Tuple{Float64,Float64}}} ()
157
157
current_segment = Tuple{Float64,Float64}[]
158
158
@@ -163,12 +163,12 @@ function segment_ring(coords::Vector{Tuple{Float64,Float64}}, great_circle::Bool
163
163
164
164
# Check for antimeridian crossing
165
165
if (endpoint[1 ] - start[1 ] > 180 ) && (endpoint[1 ] - start[1 ] != 360 ) # left crossing
166
- lat = crossing_latitude (start, endpoint, great_circle)
166
+ lat = crossing_latitude (c, start, endpoint, great_circle)
167
167
push! (current_segment, (- 180.0 , lat))
168
168
push! (segments, current_segment)
169
169
current_segment = [(180.0 , lat)]
170
170
elseif (start[1 ] - endpoint[1 ] > 180 ) && (start[1 ] - endpoint[1 ] != 360 ) # right crossing
171
- lat = crossing_latitude (endpoint, start, great_circle)
171
+ lat = crossing_latitude (c, endpoint, start, great_circle)
172
172
push! (current_segment, (180.0 , lat))
173
173
push! (segments, current_segment)
174
174
current_segment = [(- 180.0 , lat)]
@@ -190,32 +190,32 @@ function segment_ring(coords::Vector{Tuple{Float64,Float64}}, great_circle::Bool
190
190
end
191
191
192
192
# Check if a segment is self-closing
193
- function is_self_closing (segment:: Vector{Tuple{Float64,Float64}} ):: Bool
194
- is_right = segment[end ][1 ] == 180
193
+ function is_self_closing (c :: CutAtAntimeridianAndPoles , segment:: Vector{Tuple{Float64,Float64}} ):: Bool
194
+ is_right = segment[end ][1 ] == c . right
195
195
return segment[1 ][1 ] == segment[end ][1 ] && (
196
196
(is_right && segment[1 ][2 ] > segment[end ][2 ]) ||
197
197
(! is_right && segment[1 ][2 ] < segment[end ][2 ])
198
198
)
199
199
end
200
200
201
201
# Build polygons from segments
202
- function build_polygons (segments:: Vector{Vector{Tuple{Float64,Float64}}} ):: Vector{GI.Polygon}
202
+ function build_polygons (c :: CutAtAntimeridianAndPoles , segments:: Vector{Vector{Tuple{Float64,Float64}}} ):: Vector{GI.Polygon}
203
203
isempty (segments) && return GI. Polygon[]
204
204
205
205
segment = pop! (segments)
206
- is_right = segment[end ][1 ] == 180
206
+ is_right = segment[end ][1 ] == c . right
207
207
candidates = Tuple{Union{Nothing,Int},Float64}[]
208
208
209
- if is_self_closing (segment)
209
+ if is_self_closing (c, segment)
210
210
push! (candidates, (nothing , segment[1 ][2 ]))
211
211
end
212
212
213
213
for (i, s) in enumerate (segments)
214
214
if s[1 ][1 ] == segment[end ][1 ]
215
215
if (is_right && s[1 ][2 ] > segment[end ][2 ] &&
216
- (! is_self_closing (s) || s[end ][2 ] < segment[1 ][2 ])) ||
216
+ (! is_self_closing (c, s) || s[end ][2 ] < segment[1 ][2 ])) ||
217
217
(! is_right && s[1 ][2 ] < segment[end ][2 ] &&
218
- (! is_self_closing (s) || s[end ][2 ] > segment[1 ][2 ]))
218
+ (! is_self_closing (c, s) || s[end ][2 ] > segment[1 ][2 ]))
219
219
push! (candidates, (i, s[1 ][2 ]))
220
220
end
221
221
end
@@ -230,10 +230,10 @@ function build_polygons(segments::Vector{Vector{Tuple{Float64,Float64}}})::Vecto
230
230
# Join segments and recurse
231
231
segment = vcat (segment, segments[index])
232
232
segments[index] = segment
233
- return build_polygons (segments)
233
+ return build_polygons (c, segments)
234
234
else
235
235
# Handle self-joining segment
236
- polygons = build_polygons (segments)
236
+ polygons = build_polygons (c, segments)
237
237
if ! all (p == segment[1 ] for p in segment)
238
238
push! (polygons, GI. Polygon ([segment]))
239
239
end
@@ -245,9 +245,12 @@ function build_polygons(segments::Vector{Vector{Tuple{Float64,Float64}}})::Vecto
245
245
end
246
246
247
247
# Main function to cut a polygon at the antimeridian
248
- cut_at_antimeridian (x) = cut_at_antimeridian (GI. trait (x), x)
248
+ cut_at_antimeridian (x) = cut_at_antimeridian (CutAtAntimeridianAndPoles (), GI. trait (x), x)
249
+ cut_at_antimeridian (c:: CutAtAntimeridianAndPoles , x) = cut_at_antimeridian (c, GI. trait (x), x)
250
+ cut_at_antimeridian (t:: GI.AbstractTrait , x) = cut_at_antimeridian (CutAtAntimeridianAndPoles (), t, x)
249
251
250
252
function cut_at_antimeridian (
253
+ c:: CutAtAntimeridianAndPoles ,
251
254
:: GI.PolygonTrait ,
252
255
polygon:: T ;
253
256
force_north_pole:: Bool = false ,
@@ -257,16 +260,16 @@ function cut_at_antimeridian(
257
260
) where {T}
258
261
# Get exterior ring
259
262
exterior = GO. tuples (GI. getexterior (polygon)). geom
260
- exterior = normalize_coords (exterior)
263
+ exterior = normalize_coords (c, exterior)
261
264
262
265
# Segment the exterior ring
263
- segments = segment_ring (exterior, great_circle)
266
+ segments = segment_ring (c, exterior, great_circle)
264
267
265
268
if isempty (segments)
266
269
# No antimeridian crossing
267
270
if fix_winding && GO. isclockwise (GI. LinearRing (exterior))
268
271
coord_vecs = reverse .(getproperty .(GO. tuples .(GI. getring (polygon)), :geom ))
269
- return GI. Polygon (normalize_coords .(coord_vecs))
272
+ return GI. Polygon (normalize_coords .((c,), coord_vecs))
270
273
end
271
274
return polygon
272
275
end
@@ -275,13 +278,13 @@ function cut_at_antimeridian(
275
278
holes = Vector {Vector{Tuple{Float64,Float64}}} ()
276
279
for hole_idx in 1 : GI. nhole (polygon)
277
280
hole = GO. tuples (GI. gethole (polygon, hole_idx)). geom
278
- hole_segments = segment_ring (hole, great_circle)
281
+ hole_segments = segment_ring (c, hole, great_circle)
279
282
280
283
if ! isempty (hole_segments)
281
284
if fix_winding
282
285
unwrapped = [(x % 360 , y) for (x, y) in hole]
283
286
if ! GO. isclockwise (GI. LineString (unwrapped))
284
- hole_segments = segment_ring (reverse (hole), great_circle)
287
+ hole_segments = segment_ring (c, reverse (hole), great_circle)
285
288
end
286
289
end
287
290
append! (segments, hole_segments)
@@ -310,9 +313,9 @@ function cut_at_antimeridian(
310
313
return length (result_polygons) == 1 ? result_polygons[1 ] : GI. MultiPolygon (result_polygons)
311
314
end
312
315
313
- function cut_at_antimeridian (:: GI.AbstractCurveTrait , line:: T ; great_circle:: Bool = true ) where {T}
316
+ function cut_at_antimeridian (c :: CutAtAntimeridianAndPoles , t :: GI.AbstractCurveTrait , line:: T ; great_circle:: Bool = true ) where {T}
314
317
coords = GO. tuples (line). geom
315
- segments = segment_ring (coords, great_circle)
318
+ segments = segment_ring (c, coords, great_circle)
316
319
317
320
if isempty (segments)
318
321
return line
@@ -322,10 +325,10 @@ function cut_at_antimeridian(::GI.AbstractCurveTrait, line::T; great_circle::Boo
322
325
end
323
326
324
327
325
- function cut_at_antimeridian (:: GI.MultiPolygonTrait , x; kwargs... )
328
+ function cut_at_antimeridian (c :: CutAtAntimeridianAndPoles , t :: GI.MultiPolygonTrait , x; kwargs... )
326
329
results = GI. Polygon[]
327
330
for poly in GI. getgeom (x)
328
- result = cut_at_antimeridian (GI. PolygonTrait (), poly; kwargs... )
331
+ result = cut_at_antimeridian (c, GI. PolygonTrait (), poly; kwargs... )
329
332
if result isa GI. Polygon
330
333
push! (results, result)
331
334
elseif result isa GI. MultiPolygon
@@ -335,11 +338,11 @@ function cut_at_antimeridian(::GI.MultiPolygonTrait, x; kwargs...)
335
338
return GI. MultiPolygon (results)
336
339
end
337
340
338
- function cut_at_antimeridian (:: GI.MultiLineStringTrait , multiline:: T ; great_circle:: Bool = true ) where {T}
341
+ function cut_at_antimeridian (c :: CutAtAntimeridianAndPoles , t :: GI.MultiLineStringTrait , multiline:: T ; great_circle:: Bool = true ) where {T}
339
342
linestrings = Vector {Vector{Tuple{Float64,Float64}}} ()
340
343
341
344
for line in GI. getgeom (multiline)
342
- fixed = cut_at_antimeridian (GI. LineStringTrait (), line; great_circle)
345
+ fixed = cut_at_antimeridian (c, GI. LineStringTrait (), line; great_circle)
343
346
if fixed isa GI. LineString
344
347
push! (linestrings, GO. tuples (fixed). geom)
345
348
else
0 commit comments