1
+ package com.jillesvangurp.geojson
2
+
3
+ import com.jillesvangurp.geo.GeoGeometry
4
+
5
+ fun Geometry.intersects (other : Geometry ): Boolean {
6
+ val bbox1 = this .boundingBox()
7
+ val bbox2 = other.boundingBox()
8
+ if (! bboxesIntersect(bbox1, bbox2)) return false
9
+
10
+ return when (this ) {
11
+ is Geometry .Point -> other.contains(this .coordinates ? : return false )
12
+ is Geometry .MultiPoint -> this .coordinates?.any { other.contains(it) } == true
13
+ is Geometry .LineString -> intersectsLine(this .coordinates ? : return false , other)
14
+ is Geometry .MultiLineString -> this .coordinates?.any { intersectsLine(it, other) } == true
15
+ is Geometry .Polygon -> intersectsPolygon(this .coordinates ? : return false , other)
16
+ is Geometry .MultiPolygon -> this .coordinates?.any { intersectsPolygon(it, other) } == true
17
+ is Geometry .GeometryCollection -> this .geometries.any { it.intersects(other) }
18
+ }
19
+ }
20
+
21
+ private fun bboxesIntersect (b1 : BoundingBox , b2 : BoundingBox ): Boolean {
22
+ // Pick the shorter east-going arc for each raw interval
23
+ fun normalize (w : Double , e : Double ): Pair <Double , Double > {
24
+ // span measured modulo 360
25
+ val span = ( (e - w + 360 ) % 360 )
26
+ return if (span > 180 ) e to w else w to e
27
+ }
28
+
29
+ fun lonIntersects (w1 : Double , e1 : Double , w2 : Double , e2 : Double ): Boolean {
30
+ val (nW1, nE1) = normalize(w1, e1)
31
+ val (nW2, nE2) = normalize(w2, e2)
32
+ // now split only if it still wraps
33
+ val arcs1 = if (nW1 <= nE1) listOf (nW1 to nE1)
34
+ else listOf (nW1 to 180.0 , - 180.0 to nE1)
35
+ val arcs2 = if (nW2 <= nE2) listOf (nW2 to nE2)
36
+ else listOf (nW2 to 180.0 , - 180.0 to nE2)
37
+
38
+ return arcs1.any { (a0, a1) ->
39
+ arcs2.any { (b0, b1) ->
40
+ ! (a0 > b1 || a1 < b0)
41
+ }
42
+ }
43
+ }
44
+
45
+ val lonOverlap = lonIntersects(
46
+ b1.westLongitude, b1.eastLongitude,
47
+ b2.westLongitude, b2.eastLongitude
48
+ )
49
+ val latOverlap = ! (b1.northLatitude < b2.southLatitude ||
50
+ b1.southLatitude > b2.northLatitude)
51
+
52
+ return lonOverlap && latOverlap
53
+ }
54
+
55
+ private fun intersectsLine (line : LineStringCoordinates , other : Geometry ): Boolean {
56
+ val segments = line.zipWithNextCompat()
57
+ return segments.any { (start, end) ->
58
+ when (other) {
59
+ is Geometry .Point -> other.coordinates?.onLineSegment(start, end) ? : false
60
+ is Geometry .LineString -> other.coordinates?.zipWithNextCompat()?.any { (oStart, oEnd) ->
61
+ GeoGeometry .linesCross(start, end, oStart, oEnd)
62
+ } == true
63
+ is Geometry .MultiLineString -> other.coordinates?.any { oLine ->
64
+ oLine.zipWithNextCompat().any { (oStart, oEnd) ->
65
+ GeoGeometry .linesCross(start, end, oStart, oEnd)
66
+ }
67
+ } == true
68
+ is Geometry .Polygon -> other.outerCoordinates.zipWithNextCompat().any { (oStart, oEnd) ->
69
+ GeoGeometry .linesCross(start, end, oStart, oEnd)
70
+ } || other.contains(start)
71
+ is Geometry .MultiPolygon -> other.coordinates?.any { poly ->
72
+ poly.first().zipWithNextCompat().any { (oStart, oEnd) ->
73
+ GeoGeometry .linesCross(start, end, oStart, oEnd)
74
+ } || GeoGeometry .polygonContains(start.latitude, start.longitude, poly)
75
+ } == true
76
+ is Geometry .GeometryCollection -> other.geometries.any { intersectsLine(line, it) }
77
+ else -> false
78
+ }
79
+ }
80
+ }
81
+
82
+ private fun intersectsPolygon (polygon : PolygonCoordinates , other : Geometry ): Boolean {
83
+ val outer = polygon.first()
84
+ return outer.zipWithNextCompat().any { (start, end) ->
85
+ intersectsLine(arrayOf(start, end), other)
86
+ } || when (other) {
87
+ is Geometry .Point -> other.coordinates?.let { GeoGeometry .polygonContains(it.latitude, it.longitude, polygon) } ? : false
88
+ is Geometry .MultiPoint -> other.coordinates?.any { GeoGeometry .polygonContains(it.latitude, it.longitude, polygon) } == true
89
+ else -> false
90
+ }
91
+ }
92
+
93
+ private fun <T > Array<T>.zipWithNextCompat (): List <Pair <T , T >> {
94
+ // multiplatform doesn't have zipWithNext
95
+ val result = mutableListOf<Pair <T , T >>()
96
+ for (i in 0 until this .size - 1 ) {
97
+ result.add(this [i] to this [i + 1 ])
98
+ }
99
+ return result
100
+ }
0 commit comments