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  }