@@ -240,55 +240,58 @@ class GeoGeometry {
240
240
vararg polygonPoints : PointCoordinates
241
241
): Boolean {
242
242
fun wrapLongitude (diff : Double ): Double = when {
243
- diff > 180 -> diff - 360
244
- diff < - 180 -> diff + 360
245
- else -> diff
243
+ diff > 180 -> diff - 360
244
+ diff < - 180 -> diff + 360
245
+ else -> diff
246
246
}
247
+
247
248
validate(latitude, longitude, false )
248
249
require(polygonPoints.size >= 3 ) { " a polygon must have at least three points" }
249
250
251
+ // used for rounding errors
252
+ val epsilon = 1e- 9
250
253
251
- // Shift longitudes so test-point’s lon → 0, deals with antimeridian
254
+ // Normalize longitudes around test point
252
255
val norm = polygonPoints.map {
253
- doubleArrayOf(
254
- wrapLongitude(it[0 ] - longitude),
255
- it[1 ]
256
- )
256
+ doubleArrayOf(wrapLongitude(it[0 ] - longitude), it[1 ])
257
257
}.toTypedArray()
258
258
259
- // Quick bbox check around (0, latitude)
260
- val bbox = boundingBox(norm)
261
- if (! bboxContains(bbox, latitude, 0.0 )) {
262
- return false
263
- }
264
- if (polygonPoints.firstOrNull() { it.latitude == latitude && it.longitude== longitude } != null ) {
265
- // edge case for vertices
259
+ // Early-out bbox check
260
+ if (! bboxContains(boundingBox(norm), latitude, 0.0 )) return false
261
+
262
+ // Direct vertex match
263
+ if (polygonPoints.any { abs(it.latitude - latitude) < epsilon && abs(it.longitude - longitude) < epsilon }) {
266
264
return true
267
265
}
268
266
269
- // Check if the point lies exactly on any of the polygon's edges
267
+ // Edge match
270
268
for (i in norm.indices) {
271
269
val (x1, y1) = norm[i]
272
270
val (x2, y2) = norm[(i + 1 ) % norm.size]
273
- val x = 0.0
274
- val y = latitude
275
- val onSegment = onSegment(x, y, x1, y1, x2, y2)
276
- if (onSegment) {
271
+ if ((x1 != x2 || y1 != y2) && onSegment(0.0 , latitude, x1, y1, x2, y2)) {
277
272
return true
278
273
}
279
274
}
280
- // Ray-cast from (0,latitude) eastward
275
+
276
+ // Ray-cast eastward from (0, latitude)
281
277
var hits = 0
282
- val y = latitude
283
278
for (i in norm.indices) {
284
279
val (x1, y1) = norm[i]
285
280
val (x2, y2) = norm[(i + 1 ) % norm.size]
286
- if ((y1 > y) != (y2 > y)) {
287
- val xInt = x1 + (y - y1) * (x2 - x1) / (y2 - y1)
281
+
282
+ // Ignore horizontal segments
283
+ if (abs(y1 - y2) < epsilon) continue
284
+
285
+ // Handle crossing the horizontal ray
286
+ val lower = if (y1 < y2) y1 else y2
287
+ val upper = if (y1 < y2) y2 else y1
288
+ if (latitude > lower && latitude <= upper) {
289
+ val xInt = x1 + (latitude - y1) * (x2 - x1) / (y2 - y1)
288
290
if (xInt > 0 ) hits++
289
291
}
290
292
}
291
- return hits and 1 == 1
293
+
294
+ return hits % 2 == 1
292
295
}
293
296
294
297
/* *
0 commit comments