github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/geo/geogfn/distance_test.go (about) 1 // Copyright 2020 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 package geogfn 12 13 import ( 14 "fmt" 15 "math" 16 "testing" 17 18 "github.com/cockroachdb/cockroach/pkg/geo" 19 "github.com/stretchr/testify/require" 20 ) 21 22 var distanceTestCases = []struct { 23 desc string 24 a string 25 b string 26 expectedSphereDistance float64 27 expectedSpheroidDistance float64 28 }{ 29 { 30 "POINT to itself", 31 "POINT(1.0 1.0)", 32 "POINT(1.0 1.0)", 33 0, 34 0, 35 }, 36 { 37 "POINT to POINT (CDG to LAX)", 38 "POINT(-118.4079 33.9434)", 39 "POINT(2.5559 49.0083)", 40 9.103087983009e+06, 41 9124665.27317673, 42 }, 43 { 44 "LINESTRING to POINT where POINT is on vertex", 45 "LINESTRING(2.0 2.0, 3.0 3.0)", 46 "POINT(3.0 3.0)", 47 0, 48 0, 49 }, 50 { 51 "LINESTRING to POINT where POINT is closest to a vertex", 52 "LINESTRING(1.0 1.0, 2.0 2.0, 3.0 3.0)", 53 "POINT(5.0 5.0)", 54 314116.2410064, 55 313424.65220079, 56 }, 57 { 58 "LINESTRING to POINT where POINT is closer than the edge vertices", 59 "LINESTRING(1.0 1.0, 2.0 2.0, 3.0 3.0)", 60 "POINT(2.4 2.6)", 61 15695.12116722, 62 15660.43959933, 63 }, 64 { 65 "LINESTRING to POINT on the line", 66 "LINESTRING(0.0 0.0, 1.0 1.0, 2.0 2.0, 3.0 3.0)", 67 "POINT(1.5 1.5001714)", 68 0, 69 0, 70 }, 71 { 72 "POLYGON to POINT inside the polygon", 73 "POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0))", 74 "POINT(0.5 0.5)", 75 0, 76 0, 77 }, 78 { 79 "POLYGON to POINT on vertex of the polygon", 80 "POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0))", 81 "POINT(1.0 1.0)", 82 0, 83 0, 84 }, 85 { 86 "POLYGON to POINT outside the polygon", 87 "POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0))", 88 "POINT(1.5 1.6)", 89 86836.81014284, 90 86591.2400406, 91 }, 92 { 93 "POLYGON to POINT where POINT is inside a hole", 94 "POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0), (0.2 0.2, 0.2 0.4, 0.4 0.4, 0.4 0.2, 0.2 0.2))", 95 "POINT(0.3 0.3)", 96 11119.35554984, 97 11131.79750667, // 11057.396042 is the truer min distance, see "updateMinDistance". 98 }, 99 { 100 "LINESTRING to intersecting LINESTRING", 101 "LINESTRING(0.0 0.0, 1.0 1.0)", 102 "LINESTRING(0.0 1.0, 1.0 0.0)", 103 0, 104 0, 105 }, 106 { 107 "LINESTRING to faraway LINESTRING", 108 "LINESTRING(0.0 0.0, 1.0 1.0)", 109 "LINESTRING(5.0 5.0, 6.0 6.0)", 110 628519.03378753, 111 627129.50261075, 112 }, 113 { 114 "LINESTRING to intersecting LINESTRING, duplicate points", 115 "LINESTRING(0.0 0.0, 0.0 0.0, 1.0 1.0)", 116 "LINESTRING(0.0 1.0, 1.0 0.0, 1.0 0.0)", 117 0, 118 0, 119 }, 120 { 121 "LINESTRING to faraway LINESTRING, duplicate points", 122 "LINESTRING(0.0 0.0, 1.0 1.0)", 123 "LINESTRING(5.0 5.0, 6.0 6.0, 6.0 6.0, 6.0 6.0)", 124 628519.03378753, 125 627129.50261075, 126 }, 127 { 128 "LINESTRING to intersecting POLYGON where LINESTRING is inside the polygon", 129 "POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0))", 130 "LINESTRING(0.1 0.1, 0.15 0.15)", 131 0, 132 0, 133 }, 134 { 135 "LINESTRING to intersecting POLYGON with a hole where LINESTRING is inside the polygon", 136 "POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0), (0.2 0.2, 0.2 0.4, 0.4 0.4, 0.4 0.2, 0.2 0.2))", 137 "LINESTRING(0.1 0.1, 0.15 0.15)", 138 0, 139 0, 140 }, 141 { 142 "LINESTRING to intersecting POLYGON where LINESTRING is outside the polygon", 143 "POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0), (0.2 0.2, 0.2 0.4, 0.4 0.4, 0.4 0.2, 0.2 0.2))", 144 "LINESTRING(-1.0 -1.0, 1.0 1.0)", 145 0, 146 0, 147 }, 148 { 149 "LINESTRING to POLYGON inside its hole", 150 "POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0), (0.2 0.2, 0.2 0.4, 0.4 0.4, 0.4 0.2, 0.2 0.2))", 151 "LINESTRING(0.25 0.25, 0.35 0.35)", 152 5559.65025416, 153 5565.87138621, // 5528.689389 is the truer min distance, see "updateMinDistance". 154 }, 155 { 156 "LINESTRING to POLYGON inside its hole but intersects through hole", 157 "POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0), (0.2 0.2, 0.2 0.4, 0.4 0.4, 0.4 0.2, 0.2 0.2))", 158 "LINESTRING(0.25 0.25, 0.6 0.6)", 159 0, 160 0, 161 }, 162 { 163 "LINESTRING to faraway POLYGON", 164 "POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0), (0.2 0.2, 0.2 0.4, 0.4 0.4, 0.4 0.2, 0.2 0.2))", 165 "LINESTRING(7.0 7.0, 5.0 5.0)", 166 628519.03378753, 167 627129.50261075, 168 }, 169 { 170 "POLYGON to intersecting POLYGON", 171 "POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0))", 172 "POLYGON((-1.0 0.0, 1.0 0.0, 1.0 1.0, -1.0 1.0, -1.0 0.0))", 173 0, 174 0, 175 }, 176 { 177 "POLYGON to POLYGON completely inside its hole", 178 "POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0), (0.2 0.2, 0.2 0.4, 0.4 0.4, 0.4 0.2, 0.2 0.2))", 179 "POLYGON((0.25 0.25, 0.35 0.25, 0.35 0.35, 0.25 0.35, 0.25 0.25))", 180 5559.65025416, 181 5565.87138621, 182 }, 183 { 184 "POLYGON to POLYGON intersecting through its hole", 185 "POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0), (0.2 0.2, 0.2 0.4, 0.4 0.4, 0.4 0.2, 0.2 0.2))", 186 "POLYGON((0.15 0.25, 0.35 0.25, 0.35 0.35, 0.25 0.35, 0.15 0.25))", 187 0, 188 0, 189 }, 190 { 191 "POLYGON to POLYGON completely inside its hole, duplicate points", 192 "POLYGON((0.0 0.0, 0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0), (0.2 0.2, 0.2 0.4, 0.2 0.4, 0.4 0.4, 0.4 0.2, 0.2 0.2))", 193 "POLYGON((0.25 0.25, 0.25 0.25, 0.35 0.35, 0.25 0.35, 0.25 0.35, 0.25 0.25))", 194 5559.65025416, 195 5565.87138621, 196 }, 197 { 198 "POLYGON to POLYGON intersecting through its hole, duplicate points", 199 "POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 1.0, 0.0 0.0), (0.2 0.2, 0.2 0.4, 0.2 0.4, 0.4 0.4, 0.4 0.2, 0.2 0.2))", 200 "POLYGON((0.15 0.25, 0.15 0.25, 0.35 0.25, 0.35 0.35, 0.25 0.35, 0.25 0.35, 0.15 0.25))", 201 0, 202 0, 203 }, 204 { 205 "POLYGON to faraway POLYGON", 206 "POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0))", 207 "POLYGON((-8.0 -8.0, -4.0 -8.0, -4.0 -4.0, -8.0 -4.0, -8.0 -8.0))", 208 628758.66301809, 209 627363.8420706, 210 }, 211 { 212 "MULTIPOINT to MULTIPOINT", 213 "MULTIPOINT((1.0 1.0), (2.0 2.0))", 214 "MULTIPOINT((2.5 2.5), (3.0 3.0))", 215 78596.36618378, 216 78421.9811006, 217 }, 218 { 219 "MULTIPOINT to MULTILINESTRING", 220 "MULTILINESTRING((1.0 1.0, 1.1 1.1), (2.0 2.0, 2.1 2.1))", 221 "MULTIPOINT(2.0 2.0, 1.0 1.0, 3.0 3.0)", 222 0, 223 0, 224 }, 225 { 226 "MULTIPOINT to MULTIPOLYGON", 227 "MULTIPOINT ((2.0 3.0), (10 42))", 228 "MULTIPOLYGON (((15 5, 40 10, 10 20, 5 10, 15 5)),((30 20, 45 40, 10 40, 30 20)))", 229 217957.10767526, 230 217713.9665776, 231 }, 232 { 233 "MULTILINESTRING to MULTILINESTRING", 234 "MULTILINESTRING((1.0 1.0, 1.1 1.1), (2.0 2.0, 2.1 2.1), (3.0 3.0, 3.1 3.1))", 235 "MULTILINESTRING((2.0 2.0, 2.1 2.1), (4.0 3.0, 3.1 3.1))", 236 0, 237 0, 238 }, 239 { 240 "MULTILINESTRING to MULTIPOLYGON", 241 "MULTIPOLYGON (((15 5, 40 10, 10 20, 5 10, 15 5)),((30 20, 45 40, 10 40, 30 20)))", 242 "MULTILINESTRING((3 3, -4 -4), (45 41, 48 48, 52 52))", 243 108979.20910668, 244 108848.28520095, 245 }, 246 { 247 "MULTIPOLYGON to MULTIPOLYGON", 248 "MULTIPOLYGON (((15 5, 40 10, 10 20, 5 10, 15 5)),((30 20, 45 40, 10 40, 30 20)))", 249 "MULTIPOLYGON (((30 20, 45 40, 15 45, 30 20)))", 250 0, 251 0, 252 }, 253 } 254 255 func TestDistance(t *testing.T) { 256 for _, tc := range distanceTestCases { 257 t.Run(tc.desc, func(t *testing.T) { 258 a, err := geo.ParseGeography(tc.a) 259 require.NoError(t, err) 260 b, err := geo.ParseGeography(tc.b) 261 require.NoError(t, err) 262 263 // Allow a 1cm margin of error for results. 264 for _, subTC := range []struct { 265 desc string 266 expected float64 267 useSphereOrSpheroid UseSphereOrSpheroid 268 }{ 269 {"sphere", tc.expectedSphereDistance, UseSphere}, 270 {"spheroid", tc.expectedSpheroidDistance, UseSpheroid}, 271 } { 272 t.Run(subTC.desc, func(t *testing.T) { 273 distance, err := Distance(a, b, subTC.useSphereOrSpheroid) 274 require.NoError(t, err) 275 require.LessOrEqualf( 276 t, 277 math.Abs(subTC.expected-distance), 278 0.01, 279 "expected %f, got %f", 280 subTC.expected, 281 distance, 282 ) 283 distance, err = Distance(b, a, subTC.useSphereOrSpheroid) 284 require.NoError(t, err) 285 require.LessOrEqualf( 286 t, 287 math.Abs(subTC.expected-distance), 288 0.01, 289 "expected %f, got %f", 290 subTC.expected, 291 distance, 292 ) 293 }) 294 } 295 }) 296 } 297 298 t.Run("errors if SRIDs mismatch", func(t *testing.T) { 299 _, err := Distance(mismatchingSRIDGeographyA, mismatchingSRIDGeographyB, UseSpheroid) 300 requireMismatchingSRIDError(t, err) 301 }) 302 303 t.Run("empty geographies always error", func(t *testing.T) { 304 for _, tc := range []struct { 305 a string 306 b string 307 }{ 308 {"GEOMETRYCOLLECTION EMPTY", "GEOMETRYCOLLECTION EMPTY"}, 309 {"GEOMETRYCOLLECTION EMPTY", "GEOMETRYCOLLECTION (POINT(1.0 1.0), LINESTRING EMPTY)"}, 310 {"POINT(1.0 1.0)", "GEOMETRYCOLLECTION (POINT(1.0 1.0), LINESTRING EMPTY)"}, 311 } { 312 for _, useSphereOrSpheroid := range []UseSphereOrSpheroid{ 313 UseSphere, 314 UseSpheroid, 315 } { 316 t.Run(fmt.Sprintf("Distance(%s,%s),spheroid=%t", tc.a, tc.b, useSphereOrSpheroid), func(t *testing.T) { 317 a, err := geo.ParseGeography(tc.a) 318 require.NoError(t, err) 319 b, err := geo.ParseGeography(tc.b) 320 require.NoError(t, err) 321 _, err = Distance(a, b, useSphereOrSpheroid) 322 require.Error(t, err) 323 require.True(t, geo.IsEmptyGeometryError(err)) 324 }) 325 } 326 } 327 }) 328 }