github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/geo/geo_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 geo
    12  
    13  import (
    14  	"encoding/hex"
    15  	"fmt"
    16  	"testing"
    17  
    18  	"github.com/cockroachdb/cockroach/pkg/geo/geopb"
    19  	"github.com/cockroachdb/cockroach/pkg/geo/geos"
    20  	"github.com/cockroachdb/cockroach/pkg/util/leaktest"
    21  	"github.com/cockroachdb/datadriven"
    22  	"github.com/golang/geo/s2"
    23  	"github.com/stretchr/testify/require"
    24  	"github.com/twpayne/go-geom"
    25  )
    26  
    27  var (
    28  	testGeomPoint              = geom.NewPointFlat(geom.XY, []float64{1.0, 2.0})
    29  	testGeomLineString         = geom.NewLineStringFlat(geom.XY, []float64{1.0, 1.0, 2.0, 2.0})
    30  	testGeomPolygon            = geom.NewPolygonFlat(geom.XY, []float64{1.0, 1.0, 2.0, 2.0, 1.0, 2.0, 1.0, 1.0}, []int{8})
    31  	testGeomMultiPoint         = geom.NewMultiPointFlat(geom.XY, []float64{1.0, 1.0, 2.0, 2.0})
    32  	testGeomMultiLineString    = geom.NewMultiLineStringFlat(geom.XY, []float64{1.0, 1.0, 2.0, 2.0, 3.0, 3.0, 4.0, 4.0}, []int{4, 8})
    33  	testGeomMultiPolygon       = geom.NewMultiPolygon(geom.XY)
    34  	testGeomGeometryCollection = geom.NewGeometryCollection()
    35  )
    36  var (
    37  	emptyGeomPoint                       = geom.NewPointEmpty(geom.XY)
    38  	emptyGeomLineString                  = geom.NewLineString(geom.XY)
    39  	emptyGeomPolygon                     = geom.NewPolygon(geom.XY)
    40  	emptyGeomMultiPoint                  = geom.NewMultiPoint(geom.XY)
    41  	emptyGeomMultiLineString             = geom.NewMultiLineString(geom.XY)
    42  	emptyGeomMultiPolygon                = geom.NewMultiPolygon(geom.XY)
    43  	emptyGeomGeometryCollection          = geom.NewGeometryCollection()
    44  	emptyGeomObjectsInGeometryCollection = geom.NewGeometryCollection()
    45  	emptyGeomPointInGeometryCollection   = geom.NewGeometryCollection()
    46  )
    47  
    48  func init() {
    49  	testGeomLineString.SetSRID(4326)
    50  	// Initialize geomTestMultiPolygon.
    51  	{
    52  		for _, polygon := range []*geom.Polygon{
    53  			testGeomPolygon,
    54  			geom.NewPolygonFlat(geom.XY, []float64{3.0, 3.0, 4.0, 4.0, 3.0, 4.0, 3.0, 3.0}, []int{8}),
    55  		} {
    56  			err := testGeomMultiPolygon.Push(polygon)
    57  			if err != nil {
    58  				panic(err)
    59  			}
    60  		}
    61  	}
    62  	// Initialize testGeomGeometryCollection.
    63  	{
    64  		err := testGeomGeometryCollection.Push(testGeomPoint, testGeomMultiPoint)
    65  		if err != nil {
    66  			panic(err)
    67  		}
    68  	}
    69  	// Initialize emptyGeomPointInGeometryCollection.
    70  	{
    71  		err := emptyGeomPointInGeometryCollection.Push(
    72  			geom.NewLineStringFlat(geom.XY, []float64{1.0, 1.0, 2.0, 2.0}),
    73  			emptyGeomPoint,
    74  		)
    75  		if err != nil {
    76  			panic(err)
    77  		}
    78  	}
    79  	// Initialize emptyGeomObjectsInGeometryCollection.
    80  	{
    81  		err := emptyGeomObjectsInGeometryCollection.Push(
    82  			emptyGeomPoint,
    83  			emptyGeomLineString,
    84  			emptyGeomPolygon,
    85  		)
    86  		if err != nil {
    87  			panic(err)
    88  		}
    89  	}
    90  }
    91  
    92  func mustDecodeEWKBFromString(t *testing.T, h string) geopb.EWKB {
    93  	decoded, err := hex.DecodeString(h)
    94  	require.NoError(t, err)
    95  	return geopb.EWKB(decoded)
    96  }
    97  
    98  func TestGeospatialTypeFitsColumnMetadata(t *testing.T) {
    99  	testCases := []struct {
   100  		t             GeospatialType
   101  		srid          geopb.SRID
   102  		shape         geopb.Shape
   103  		errorContains string
   104  	}{
   105  		{MustParseGeometry("POINT(1.0 1.0)"), 0, geopb.Shape_Geometry, ""},
   106  		{MustParseGeometry("POINT(1.0 1.0)"), 0, geopb.Shape_Unset, ""},
   107  		{MustParseGeometry("SRID=4326;POINT(1.0 1.0)"), 0, geopb.Shape_Geometry, ""},
   108  		{MustParseGeometry("SRID=4326;POINT(1.0 1.0)"), 0, geopb.Shape_Unset, ""},
   109  		{MustParseGeometry("SRID=4326;POINT(1.0 1.0)"), 0, geopb.Shape_LineString, "type Point does not match column type LineString"},
   110  		{MustParseGeometry("POINT(1.0 1.0)"), 4326, geopb.Shape_Geometry, "SRID 0 does not match column SRID 4326"},
   111  	}
   112  
   113  	for _, tc := range testCases {
   114  		t.Run(fmt.Sprintf("%#v_fits_%d_%s", tc.t, tc.srid, tc.shape), func(t *testing.T) {
   115  			err := GeospatialTypeFitsColumnMetadata(tc.t, tc.srid, tc.shape)
   116  			if tc.errorContains != "" {
   117  				require.Error(t, err)
   118  				require.Contains(t, err.Error(), tc.errorContains)
   119  			} else {
   120  				require.NoError(t, err)
   121  			}
   122  		})
   123  	}
   124  }
   125  
   126  func TestSpatialObjectFromGeom(t *testing.T) {
   127  	testCases := []struct {
   128  		desc string
   129  		g    geom.T
   130  		ret  geopb.SpatialObject
   131  	}{
   132  		{
   133  			"point",
   134  			testGeomPoint,
   135  			geopb.SpatialObject{
   136  				EWKB:        mustDecodeEWKBFromString(t, "0101000000000000000000F03F0000000000000040"),
   137  				SRID:        0,
   138  				Shape:       geopb.Shape_Point,
   139  				BoundingBox: &geopb.BoundingBox{MinX: 1, MaxX: 1, MinY: 2, MaxY: 2},
   140  			},
   141  		},
   142  		{
   143  			"linestring",
   144  			testGeomLineString,
   145  			geopb.SpatialObject{
   146  				EWKB:        mustDecodeEWKBFromString(t, "0102000020E610000002000000000000000000F03F000000000000F03F00000000000000400000000000000040"),
   147  				SRID:        4326,
   148  				Shape:       geopb.Shape_LineString,
   149  				BoundingBox: &geopb.BoundingBox{MinX: 1, MaxX: 2, MinY: 1, MaxY: 2},
   150  			},
   151  		},
   152  		{
   153  			"polygon",
   154  			testGeomPolygon,
   155  			geopb.SpatialObject{
   156  				EWKB:        mustDecodeEWKBFromString(t, "01030000000100000004000000000000000000F03F000000000000F03F00000000000000400000000000000040000000000000F03F0000000000000040000000000000F03F000000000000F03F"),
   157  				SRID:        0,
   158  				Shape:       geopb.Shape_Polygon,
   159  				BoundingBox: &geopb.BoundingBox{MinX: 1, MaxX: 2, MinY: 1, MaxY: 2},
   160  			},
   161  		},
   162  		{
   163  			"multipoint",
   164  			testGeomMultiPoint,
   165  			geopb.SpatialObject{
   166  				EWKB:        mustDecodeEWKBFromString(t, "0104000000020000000101000000000000000000F03F000000000000F03F010100000000000000000000400000000000000040"),
   167  				SRID:        0,
   168  				Shape:       geopb.Shape_MultiPoint,
   169  				BoundingBox: &geopb.BoundingBox{MinX: 1, MaxX: 2, MinY: 1, MaxY: 2},
   170  			},
   171  		},
   172  		{
   173  			"multilinestring",
   174  			testGeomMultiLineString,
   175  			geopb.SpatialObject{
   176  				EWKB:        mustDecodeEWKBFromString(t, "010500000002000000010200000002000000000000000000F03F000000000000F03F000000000000004000000000000000400102000000020000000000000000000840000000000000084000000000000010400000000000001040"),
   177  				SRID:        0,
   178  				Shape:       geopb.Shape_MultiLineString,
   179  				BoundingBox: &geopb.BoundingBox{MinX: 1, MaxX: 4, MinY: 1, MaxY: 4},
   180  			},
   181  		},
   182  		{
   183  			"multipolygon",
   184  			testGeomMultiPolygon,
   185  			geopb.SpatialObject{
   186  				EWKB:        mustDecodeEWKBFromString(t, "01060000000200000001030000000100000004000000000000000000F03F000000000000F03F00000000000000400000000000000040000000000000F03F0000000000000040000000000000F03F000000000000F03F0103000000010000000400000000000000000008400000000000000840000000000000104000000000000010400000000000000840000000000000104000000000000008400000000000000840"),
   187  				SRID:        0,
   188  				Shape:       geopb.Shape_MultiPolygon,
   189  				BoundingBox: &geopb.BoundingBox{MinX: 1, MaxX: 4, MinY: 1, MaxY: 4},
   190  			},
   191  		},
   192  		{
   193  			"geometrycollection",
   194  			testGeomGeometryCollection,
   195  			geopb.SpatialObject{
   196  				EWKB:        mustDecodeEWKBFromString(t, "0107000000020000000101000000000000000000F03F00000000000000400104000000020000000101000000000000000000F03F000000000000F03F010100000000000000000000400000000000000040"),
   197  				SRID:        0,
   198  				Shape:       geopb.Shape_GeometryCollection,
   199  				BoundingBox: &geopb.BoundingBox{MinX: 1, MaxX: 2, MinY: 1, MaxY: 2},
   200  			},
   201  		},
   202  	}
   203  	for _, tc := range testCases {
   204  		t.Run(tc.desc, func(t *testing.T) {
   205  			so, err := spatialObjectFromGeom(tc.g)
   206  			require.NoError(t, err)
   207  			require.Equal(t, tc.ret, so)
   208  		})
   209  	}
   210  }
   211  
   212  func TestGeographyAsS2(t *testing.T) {
   213  	testCases := []struct {
   214  		wkt      string
   215  		expected []s2.Region
   216  	}{
   217  		{
   218  			"POINT(1.0 5.0)",
   219  			[]s2.Region{s2.PointFromLatLng(s2.LatLngFromDegrees(5.0, 1.0))},
   220  		},
   221  		{
   222  			"LINESTRING(1.0 5.0, 6.0 7.0)",
   223  			[]s2.Region{
   224  				s2.PolylineFromLatLngs([]s2.LatLng{
   225  					s2.LatLngFromDegrees(5.0, 1.0),
   226  					s2.LatLngFromDegrees(7.0, 6.0),
   227  				}),
   228  			},
   229  		},
   230  		{
   231  			`POLYGON(
   232  				(0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0),
   233  				(0.2 0.2, 0.2 0.4, 0.4 0.4, 0.4 0.2, 0.2 0.2)
   234  			)`,
   235  			[]s2.Region{
   236  				s2.PolygonFromLoops([]*s2.Loop{
   237  					s2.LoopFromPoints([]s2.Point{
   238  						s2.PointFromLatLng(s2.LatLngFromDegrees(0.0, 0.0)),
   239  						s2.PointFromLatLng(s2.LatLngFromDegrees(0.0, 1.0)),
   240  						s2.PointFromLatLng(s2.LatLngFromDegrees(1.0, 1.0)),
   241  						s2.PointFromLatLng(s2.LatLngFromDegrees(1.0, 0.0)),
   242  						s2.PointFromLatLng(s2.LatLngFromDegrees(0.0, 0.0)),
   243  					}),
   244  					s2.LoopFromPoints([]s2.Point{
   245  						s2.PointFromLatLng(s2.LatLngFromDegrees(0.2, 0.2)),
   246  						s2.PointFromLatLng(s2.LatLngFromDegrees(0.2, 0.4)),
   247  						s2.PointFromLatLng(s2.LatLngFromDegrees(0.4, 0.4)),
   248  						s2.PointFromLatLng(s2.LatLngFromDegrees(0.4, 0.2)),
   249  						s2.PointFromLatLng(s2.LatLngFromDegrees(0.2, 0.2)),
   250  					}),
   251  				}),
   252  			},
   253  		},
   254  		{
   255  			`POLYGON(
   256  				(0.0 0.0, 0.5 0.0, 0.75 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0),
   257  				(0.2 0.2, 0.2 0.4, 0.4 0.4, 0.4 0.2, 0.2 0.2)
   258  			)`,
   259  			[]s2.Region{
   260  				s2.PolygonFromLoops([]*s2.Loop{
   261  					s2.LoopFromPoints([]s2.Point{
   262  						s2.PointFromLatLng(s2.LatLngFromDegrees(0.0, 0.0)),
   263  						s2.PointFromLatLng(s2.LatLngFromDegrees(0.0, 0.5)),
   264  						s2.PointFromLatLng(s2.LatLngFromDegrees(0.0, 0.75)),
   265  						s2.PointFromLatLng(s2.LatLngFromDegrees(0.0, 1.0)),
   266  						s2.PointFromLatLng(s2.LatLngFromDegrees(1.0, 1.0)),
   267  						s2.PointFromLatLng(s2.LatLngFromDegrees(1.0, 0.0)),
   268  						s2.PointFromLatLng(s2.LatLngFromDegrees(0.0, 0.0)),
   269  					}),
   270  					s2.LoopFromPoints([]s2.Point{
   271  						s2.PointFromLatLng(s2.LatLngFromDegrees(0.2, 0.2)),
   272  						s2.PointFromLatLng(s2.LatLngFromDegrees(0.2, 0.4)),
   273  						s2.PointFromLatLng(s2.LatLngFromDegrees(0.4, 0.4)),
   274  						s2.PointFromLatLng(s2.LatLngFromDegrees(0.4, 0.2)),
   275  						s2.PointFromLatLng(s2.LatLngFromDegrees(0.2, 0.2)),
   276  					}),
   277  				}),
   278  			},
   279  		},
   280  		{
   281  			`POLYGON(
   282  				(0.0 0.0, 0.0 1.0, 1.0 1.0, 1.0 0.0, 0.0 0.0),
   283  				(0.2 0.2, 0.2 0.4, 0.4 0.4, 0.4 0.2, 0.2 0.2)
   284  			)`,
   285  			[]s2.Region{
   286  				s2.PolygonFromLoops([]*s2.Loop{
   287  					s2.LoopFromPoints([]s2.Point{
   288  						s2.PointFromLatLng(s2.LatLngFromDegrees(0.0, 0.0)),
   289  						s2.PointFromLatLng(s2.LatLngFromDegrees(0.0, 1.0)),
   290  						s2.PointFromLatLng(s2.LatLngFromDegrees(1.0, 1.0)),
   291  						s2.PointFromLatLng(s2.LatLngFromDegrees(1.0, 0.0)),
   292  						s2.PointFromLatLng(s2.LatLngFromDegrees(0.0, 0.0)),
   293  					}),
   294  					s2.LoopFromPoints([]s2.Point{
   295  						s2.PointFromLatLng(s2.LatLngFromDegrees(0.2, 0.2)),
   296  						s2.PointFromLatLng(s2.LatLngFromDegrees(0.2, 0.4)),
   297  						s2.PointFromLatLng(s2.LatLngFromDegrees(0.4, 0.4)),
   298  						s2.PointFromLatLng(s2.LatLngFromDegrees(0.4, 0.2)),
   299  						s2.PointFromLatLng(s2.LatLngFromDegrees(0.2, 0.2)),
   300  					}),
   301  				}),
   302  			},
   303  		},
   304  		{
   305  			`POLYGON(
   306  				(0.0 0.0, 0.0 0.5, 0.0 1.0, 1.0 1.0, 1.0 0.0, 0.0 0.0),
   307  				(0.2 0.2, 0.2 0.4, 0.4 0.4, 0.4 0.2, 0.2 0.2)
   308  			)`,
   309  			[]s2.Region{
   310  				s2.PolygonFromLoops([]*s2.Loop{
   311  					s2.LoopFromPoints([]s2.Point{
   312  						s2.PointFromLatLng(s2.LatLngFromDegrees(0.0, 0.0)),
   313  						s2.PointFromLatLng(s2.LatLngFromDegrees(0.0, 1.0)),
   314  						s2.PointFromLatLng(s2.LatLngFromDegrees(1.0, 1.0)),
   315  						s2.PointFromLatLng(s2.LatLngFromDegrees(1.0, 0.0)),
   316  						s2.PointFromLatLng(s2.LatLngFromDegrees(0.5, 0.0)),
   317  						s2.PointFromLatLng(s2.LatLngFromDegrees(0.0, 0.0)),
   318  					}),
   319  					s2.LoopFromPoints([]s2.Point{
   320  						s2.PointFromLatLng(s2.LatLngFromDegrees(0.2, 0.2)),
   321  						s2.PointFromLatLng(s2.LatLngFromDegrees(0.2, 0.4)),
   322  						s2.PointFromLatLng(s2.LatLngFromDegrees(0.4, 0.4)),
   323  						s2.PointFromLatLng(s2.LatLngFromDegrees(0.4, 0.2)),
   324  						s2.PointFromLatLng(s2.LatLngFromDegrees(0.2, 0.2)),
   325  					}),
   326  				}),
   327  			},
   328  		},
   329  		{
   330  			`GEOMETRYCOLLECTION(POINT(1.0 2.0), POLYGON(
   331  				(0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0),
   332  				(0.2 0.2, 0.2 0.4, 0.4 0.4, 0.4 0.2, 0.2 0.2)
   333  			))`,
   334  			[]s2.Region{
   335  				s2.PointFromLatLng(s2.LatLngFromDegrees(2.0, 1.0)),
   336  				s2.PolygonFromLoops([]*s2.Loop{
   337  					s2.LoopFromPoints([]s2.Point{
   338  						s2.PointFromLatLng(s2.LatLngFromDegrees(0.0, 0.0)),
   339  						s2.PointFromLatLng(s2.LatLngFromDegrees(0.0, 1.0)),
   340  						s2.PointFromLatLng(s2.LatLngFromDegrees(1.0, 1.0)),
   341  						s2.PointFromLatLng(s2.LatLngFromDegrees(1.0, 0.0)),
   342  						s2.PointFromLatLng(s2.LatLngFromDegrees(0.0, 0.0)),
   343  					}),
   344  					s2.LoopFromPoints([]s2.Point{
   345  						s2.PointFromLatLng(s2.LatLngFromDegrees(0.2, 0.2)),
   346  						s2.PointFromLatLng(s2.LatLngFromDegrees(0.2, 0.4)),
   347  						s2.PointFromLatLng(s2.LatLngFromDegrees(0.4, 0.4)),
   348  						s2.PointFromLatLng(s2.LatLngFromDegrees(0.4, 0.2)),
   349  						s2.PointFromLatLng(s2.LatLngFromDegrees(0.2, 0.2)),
   350  					}),
   351  				}),
   352  			},
   353  		},
   354  		{
   355  			"MULTIPOINT((1.0 5.0), (3.0 4.0))",
   356  			[]s2.Region{
   357  				s2.PointFromLatLng(s2.LatLngFromDegrees(5.0, 1.0)),
   358  				s2.PointFromLatLng(s2.LatLngFromDegrees(4.0, 3.0)),
   359  			},
   360  		},
   361  		{
   362  			"MULTILINESTRING((1.0 5.0, 6.0 7.0), (3.0 4.0, 5.0 6.0))",
   363  			[]s2.Region{
   364  				s2.PolylineFromLatLngs([]s2.LatLng{
   365  					s2.LatLngFromDegrees(5.0, 1.0),
   366  					s2.LatLngFromDegrees(7.0, 6.0),
   367  				}),
   368  				s2.PolylineFromLatLngs([]s2.LatLng{
   369  					s2.LatLngFromDegrees(4.0, 3.0),
   370  					s2.LatLngFromDegrees(6.0, 5.0),
   371  				}),
   372  			},
   373  		},
   374  		{
   375  			`MULTIPOLYGON(
   376  				((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0),
   377  				(0.2 0.2, 0.2 0.4, 0.4 0.4, 0.4 0.2, 0.2 0.2)),
   378  
   379  				((3.0 3.0, 4.0 3.0, 4.0 4.0, 3.0 4.0, 3.0 3.0),
   380  				(3.2 3.2, 3.2 3.4, 3.4 3.4, 3.4 3.2, 3.2 3.2))
   381  			)`,
   382  			[]s2.Region{
   383  				s2.PolygonFromLoops([]*s2.Loop{
   384  					s2.LoopFromPoints([]s2.Point{
   385  						s2.PointFromLatLng(s2.LatLngFromDegrees(0.0, 0.0)),
   386  						s2.PointFromLatLng(s2.LatLngFromDegrees(0.0, 1.0)),
   387  						s2.PointFromLatLng(s2.LatLngFromDegrees(1.0, 1.0)),
   388  						s2.PointFromLatLng(s2.LatLngFromDegrees(1.0, 0.0)),
   389  						s2.PointFromLatLng(s2.LatLngFromDegrees(0.0, 0.0)),
   390  					}),
   391  					s2.LoopFromPoints([]s2.Point{
   392  						s2.PointFromLatLng(s2.LatLngFromDegrees(0.2, 0.2)),
   393  						s2.PointFromLatLng(s2.LatLngFromDegrees(0.2, 0.4)),
   394  						s2.PointFromLatLng(s2.LatLngFromDegrees(0.4, 0.4)),
   395  						s2.PointFromLatLng(s2.LatLngFromDegrees(0.4, 0.2)),
   396  						s2.PointFromLatLng(s2.LatLngFromDegrees(0.2, 0.2)),
   397  					}),
   398  				}),
   399  				s2.PolygonFromLoops([]*s2.Loop{
   400  					s2.LoopFromPoints([]s2.Point{
   401  						s2.PointFromLatLng(s2.LatLngFromDegrees(3.0, 3.0)),
   402  						s2.PointFromLatLng(s2.LatLngFromDegrees(3.0, 4.0)),
   403  						s2.PointFromLatLng(s2.LatLngFromDegrees(4.0, 4.0)),
   404  						s2.PointFromLatLng(s2.LatLngFromDegrees(4.0, 3.0)),
   405  						s2.PointFromLatLng(s2.LatLngFromDegrees(3.0, 3.0)),
   406  					}),
   407  					s2.LoopFromPoints([]s2.Point{
   408  						s2.PointFromLatLng(s2.LatLngFromDegrees(3.2, 3.2)),
   409  						s2.PointFromLatLng(s2.LatLngFromDegrees(3.2, 3.4)),
   410  						s2.PointFromLatLng(s2.LatLngFromDegrees(3.4, 3.4)),
   411  						s2.PointFromLatLng(s2.LatLngFromDegrees(3.4, 3.2)),
   412  						s2.PointFromLatLng(s2.LatLngFromDegrees(3.2, 3.2)),
   413  					}),
   414  				}),
   415  			},
   416  		},
   417  	}
   418  
   419  	for _, tc := range testCases {
   420  		t.Run(tc.wkt, func(t *testing.T) {
   421  			g, err := ParseGeography(tc.wkt)
   422  			require.NoError(t, err)
   423  
   424  			shapes, err := g.AsS2(EmptyBehaviorError)
   425  			require.NoError(t, err)
   426  
   427  			require.Equal(t, tc.expected, shapes)
   428  		})
   429  	}
   430  
   431  	// Test when things are empty.
   432  	emptyTestCases := []struct {
   433  		wkt          string
   434  		expectedOmit []s2.Region
   435  	}{
   436  		{
   437  			"GEOMETRYCOLLECTION ( LINESTRING EMPTY, MULTIPOINT((1.0 5.0), (3.0 4.0)) )",
   438  			[]s2.Region{
   439  				s2.PointFromLatLng(s2.LatLngFromDegrees(5.0, 1.0)),
   440  				s2.PointFromLatLng(s2.LatLngFromDegrees(4.0, 3.0)),
   441  			},
   442  		},
   443  		{
   444  			"GEOMETRYCOLLECTION EMPTY",
   445  			nil,
   446  		},
   447  		{
   448  			"MULTILINESTRING (EMPTY, (1.0 2.0, 3.0 4.0))",
   449  			[]s2.Region{
   450  				s2.PolylineFromLatLngs([]s2.LatLng{
   451  					s2.LatLngFromDegrees(2.0, 1.0),
   452  					s2.LatLngFromDegrees(4.0, 3.0),
   453  				}),
   454  			},
   455  		},
   456  		{
   457  			"MULTILINESTRING (EMPTY, EMPTY)",
   458  			nil,
   459  		},
   460  	}
   461  
   462  	for _, tc := range emptyTestCases {
   463  		t.Run(tc.wkt, func(t *testing.T) {
   464  			g, err := ParseGeography(tc.wkt)
   465  			require.NoError(t, err)
   466  
   467  			_, err = g.AsS2(EmptyBehaviorError)
   468  			require.Error(t, err)
   469  			require.True(t, IsEmptyGeometryError(err))
   470  
   471  			shapes, err := g.AsS2(EmptyBehaviorOmit)
   472  			require.NoError(t, err)
   473  			require.Equal(t, tc.expectedOmit, shapes)
   474  		})
   475  	}
   476  }
   477  
   478  func TestClipEWKBByRect(t *testing.T) {
   479  	defer leaktest.AfterTest(t)()
   480  
   481  	var g *Geometry
   482  	var err error
   483  	datadriven.RunTest(t, "testdata/clip", func(t *testing.T, d *datadriven.TestData) string {
   484  		switch d.Cmd {
   485  		case "geometry":
   486  			g, err = ParseGeometry(d.Input)
   487  			if err != nil {
   488  				return err.Error()
   489  			}
   490  			return ""
   491  		case "clip":
   492  			var xMin, yMin, xMax, yMax int
   493  			d.ScanArgs(t, "xmin", &xMin)
   494  			d.ScanArgs(t, "ymin", &yMin)
   495  			d.ScanArgs(t, "xmax", &xMax)
   496  			d.ScanArgs(t, "ymax", &yMax)
   497  			ewkb, err := geos.ClipEWKBByRect(
   498  				g.EWKB(), float64(xMin), float64(yMin), float64(xMax), float64(yMax))
   499  			if err != nil {
   500  				return err.Error()
   501  			}
   502  			// TODO(sumeer):
   503  			// - add WKB to WKT and print exact output
   504  			// - expand test with more inputs
   505  			return fmt.Sprintf(
   506  				"%d => %d (srid: %d)",
   507  				len(g.EWKB()),
   508  				len(ewkb),
   509  				g.SRID(),
   510  			)
   511  		default:
   512  			return fmt.Sprintf("unknown command: %s", d.Cmd)
   513  		}
   514  	})
   515  }