github.com/cockroachdb/cockroachdb-parser@v0.23.3-0.20240213214944-911057d40c9a/pkg/geo/parse.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  	"strconv"
    15  	"strings"
    16  
    17  	"github.com/cockroachdb/cockroachdb-parser/pkg/geo/geopb"
    18  	"github.com/cockroachdb/cockroachdb-parser/pkg/sql/pgwire/pgcode"
    19  	"github.com/cockroachdb/cockroachdb-parser/pkg/sql/pgwire/pgerror"
    20  	"github.com/cockroachdb/cockroachdb-parser/pkg/util"
    21  	"github.com/cockroachdb/errors"
    22  	"github.com/pierrre/geohash"
    23  	geom "github.com/twpayne/go-geom"
    24  	"github.com/twpayne/go-geom/encoding/ewkb"
    25  	"github.com/twpayne/go-geom/encoding/ewkbhex"
    26  	"github.com/twpayne/go-geom/encoding/geojson"
    27  	"github.com/twpayne/go-geom/encoding/wkb"
    28  	"github.com/twpayne/go-geom/encoding/wkbcommon"
    29  	"github.com/twpayne/go-geom/encoding/wkt"
    30  )
    31  
    32  // parseEWKBRaw creates a geopb.SpatialObject from an EWKB
    33  // without doing any SRID based checks.
    34  // You most likely want parseEWKB instead.
    35  func parseEWKBRaw(soType geopb.SpatialObjectType, in geopb.EWKB) (geopb.SpatialObject, error) {
    36  	t, err := ewkb.Unmarshal(in)
    37  	if err != nil {
    38  		return geopb.SpatialObject{}, pgerror.Wrapf(err, pgcode.InvalidParameterValue, "error parsing EWKB")
    39  	}
    40  	return spatialObjectFromGeomT(t, soType)
    41  }
    42  
    43  // parseAmbiguousText parses a text as a number of different options
    44  // that is available in the geospatial world using the first character as
    45  // a heuristic.
    46  // This matches the PostGIS direct cast from a string to GEOGRAPHY/GEOMETRY.
    47  func parseAmbiguousText(
    48  	soType geopb.SpatialObjectType, str string, defaultSRID geopb.SRID,
    49  ) (geopb.SpatialObject, error) {
    50  	if len(str) == 0 {
    51  		return geopb.SpatialObject{}, pgerror.Newf(pgcode.InvalidParameterValue, "geo: parsing empty string to geo type")
    52  	}
    53  
    54  	switch str[0] {
    55  	case '0':
    56  		return parseEWKBHex(soType, str, defaultSRID)
    57  	case 0x00, 0x01:
    58  		return parseEWKB(soType, []byte(str), defaultSRID, DefaultSRIDIsHint)
    59  	case '{':
    60  		return parseGeoJSON(soType, []byte(str), defaultSRID)
    61  	}
    62  
    63  	return parseEWKT(soType, geopb.EWKT(str), defaultSRID, DefaultSRIDIsHint)
    64  }
    65  
    66  // parseEWKBHex takes a given str assumed to be in EWKB hex and transforms it
    67  // into a SpatialObject.
    68  func parseEWKBHex(
    69  	soType geopb.SpatialObjectType, str string, defaultSRID geopb.SRID,
    70  ) (geopb.SpatialObject, error) {
    71  	t, err := ewkbhex.Decode(str)
    72  	if err != nil {
    73  		return geopb.SpatialObject{}, pgerror.Wrapf(err, pgcode.InvalidParameterValue, "error parsing EWKB hex")
    74  	}
    75  	if (defaultSRID != 0 && t.SRID() == 0) || int32(t.SRID()) < 0 {
    76  		AdjustGeomTSRID(t, defaultSRID)
    77  	}
    78  	return spatialObjectFromGeomT(t, soType)
    79  }
    80  
    81  // parseEWKB takes given bytes assumed to be EWKB and transforms it into a SpatialObject.
    82  // The defaultSRID will overwrite any SRID set in the EWKB if overwrite is true.
    83  func parseEWKB(
    84  	soType geopb.SpatialObjectType,
    85  	b []byte,
    86  	defaultSRID geopb.SRID,
    87  	overwrite defaultSRIDOverwriteSetting,
    88  ) (geopb.SpatialObject, error) {
    89  	t, err := ewkb.Unmarshal(b)
    90  	if err != nil {
    91  		return geopb.SpatialObject{}, pgerror.Wrapf(err, pgcode.InvalidParameterValue, "error parsing EWKB")
    92  	}
    93  	if overwrite == DefaultSRIDShouldOverwrite || (defaultSRID != 0 && t.SRID() == 0) || int32(t.SRID()) < 0 {
    94  		AdjustGeomTSRID(t, defaultSRID)
    95  	}
    96  	return spatialObjectFromGeomT(t, soType)
    97  }
    98  
    99  // parseWKB takes given bytes assumed to be WKB and transforms it into a SpatialObject.
   100  func parseWKB(
   101  	soType geopb.SpatialObjectType, b []byte, defaultSRID geopb.SRID,
   102  ) (geopb.SpatialObject, error) {
   103  	t, err := wkb.Unmarshal(b, wkbcommon.WKBOptionEmptyPointHandling(wkbcommon.EmptyPointHandlingNaN))
   104  	if err != nil {
   105  		return geopb.SpatialObject{}, pgerror.Wrapf(err, pgcode.InvalidParameterValue, "error parsing WKB")
   106  	}
   107  	AdjustGeomTSRID(t, defaultSRID)
   108  	return spatialObjectFromGeomT(t, soType)
   109  }
   110  
   111  // parseGeoJSON takes given bytes assumed to be GeoJSON and transforms it into a SpatialObject.
   112  func parseGeoJSON(
   113  	soType geopb.SpatialObjectType, b []byte, defaultSRID geopb.SRID,
   114  ) (geopb.SpatialObject, error) {
   115  	var t geom.T
   116  	if err := geojson.Unmarshal(b, &t); err != nil {
   117  		return geopb.SpatialObject{}, pgerror.Wrapf(err, pgcode.InvalidParameterValue, "error parsing GeoJSON")
   118  	}
   119  	if t == nil {
   120  		return geopb.SpatialObject{}, pgerror.Newf(pgcode.InvalidParameterValue, "invalid GeoJSON input")
   121  	}
   122  	if defaultSRID != 0 && t.SRID() == 0 {
   123  		AdjustGeomTSRID(t, defaultSRID)
   124  	}
   125  	return spatialObjectFromGeomT(t, soType)
   126  }
   127  
   128  const sridPrefix = "SRID="
   129  const sridPrefixLen = len(sridPrefix)
   130  
   131  type defaultSRIDOverwriteSetting bool
   132  
   133  const (
   134  	// DefaultSRIDShouldOverwrite implies the parsing function should overwrite
   135  	// the SRID with the defaultSRID.
   136  	DefaultSRIDShouldOverwrite defaultSRIDOverwriteSetting = true
   137  	// DefaultSRIDIsHint implies that the default SRID is only a hint
   138  	// and if the SRID is provided by the given EWKT/EWKB, it should be
   139  	// used instead.
   140  	DefaultSRIDIsHint defaultSRIDOverwriteSetting = false
   141  )
   142  
   143  // parseEWKT decodes a WKT string and transforms it into a SpatialObject.
   144  // The defaultSRID will overwrite any SRID set in the EWKT if overwrite is true.
   145  func parseEWKT(
   146  	soType geopb.SpatialObjectType,
   147  	str geopb.EWKT,
   148  	defaultSRID geopb.SRID,
   149  	overwrite defaultSRIDOverwriteSetting,
   150  ) (geopb.SpatialObject, error) {
   151  	srid := defaultSRID
   152  	if hasPrefixIgnoreCase(string(str), sridPrefix) {
   153  		end := strings.Index(string(str[sridPrefixLen:]), ";")
   154  		if end != -1 {
   155  			if overwrite != DefaultSRIDShouldOverwrite {
   156  				sridInt64, err := strconv.ParseInt(string(str[sridPrefixLen:sridPrefixLen+end]), 10, 32)
   157  				if err != nil {
   158  					return geopb.SpatialObject{}, pgerror.Wrapf(
   159  						err,
   160  						pgcode.InvalidParameterValue,
   161  						"error parsing SRID for EWKT",
   162  					)
   163  				}
   164  				// Only use the parsed SRID if the parsed SRID is > 0 and it was not
   165  				// to be overwritten by the DefaultSRID parameter.
   166  				if sridInt64 > 0 {
   167  					srid = geopb.SRID(sridInt64)
   168  				}
   169  			}
   170  			str = str[sridPrefixLen+end+1:]
   171  		} else {
   172  			return geopb.SpatialObject{}, pgerror.Newf(
   173  				pgcode.InvalidParameterValue,
   174  				"geo: failed to find ; character with SRID declaration during EWKT decode: %q",
   175  				str,
   176  			)
   177  		}
   178  	}
   179  
   180  	g, wktUnmarshalErr := wkt.Unmarshal(string(str))
   181  	if wktUnmarshalErr != nil {
   182  		return geopb.SpatialObject{}, pgerror.Wrap(
   183  			wktUnmarshalErr,
   184  			pgcode.InvalidParameterValue,
   185  			"error parsing EWKT",
   186  		)
   187  	}
   188  	AdjustGeomTSRID(g, srid)
   189  	return spatialObjectFromGeomT(g, soType)
   190  }
   191  
   192  // hasPrefixIgnoreCase returns whether a given str begins with a prefix, ignoring case.
   193  // It assumes that the string and prefix contains only ASCII bytes.
   194  func hasPrefixIgnoreCase(str string, prefix string) bool {
   195  	if len(str) < len(prefix) {
   196  		return false
   197  	}
   198  	for i := 0; i < len(prefix); i++ {
   199  		if util.ToLowerSingleByte(str[i]) != util.ToLowerSingleByte(prefix[i]) {
   200  			return false
   201  		}
   202  	}
   203  	return true
   204  }
   205  
   206  // ParseGeometryPointFromGeoHash converts a GeoHash to a Geometry Point
   207  // using a Lng/Lat Point representation of the GeoHash.
   208  func ParseGeometryPointFromGeoHash(g string, precision int) (Geometry, error) {
   209  	box, err := parseGeoHash(g, precision)
   210  	if err != nil {
   211  		return Geometry{}, err
   212  	}
   213  	point := box.Center()
   214  	geom, gErr := MakeGeometryFromPointCoords(point.Lon, point.Lat)
   215  	if gErr != nil {
   216  		return Geometry{}, gErr
   217  	}
   218  	return geom, nil
   219  }
   220  
   221  // ParseCartesianBoundingBoxFromGeoHash converts a GeoHash to a CartesianBoundingBox.
   222  func ParseCartesianBoundingBoxFromGeoHash(g string, precision int) (CartesianBoundingBox, error) {
   223  	box, err := parseGeoHash(g, precision)
   224  	if err != nil {
   225  		return CartesianBoundingBox{}, err
   226  	}
   227  	return CartesianBoundingBox{
   228  		BoundingBox: geopb.BoundingBox{
   229  			LoX: box.Lon.Min,
   230  			HiX: box.Lon.Max,
   231  			LoY: box.Lat.Min,
   232  			HiY: box.Lat.Max,
   233  		},
   234  	}, nil
   235  }
   236  
   237  func parseGeoHash(g string, precision int) (geohash.Box, error) {
   238  	if len(g) == 0 {
   239  		return geohash.Box{}, pgerror.Newf(pgcode.InvalidParameterValue, "length of GeoHash must be greater than 0")
   240  	}
   241  
   242  	// In PostGIS the parsing is case-insensitive.
   243  	g = strings.ToLower(g)
   244  
   245  	// If precision is more than the length of the geohash
   246  	// or if precision is less than 0 then set
   247  	// precision equal to length of geohash.
   248  	if precision > len(g) || precision < 0 {
   249  		precision = len(g)
   250  	}
   251  	box, err := geohash.Decode(g[:precision])
   252  	if err != nil {
   253  		return geohash.Box{}, pgerror.Wrap(err, pgcode.InvalidParameterValue, "invalid GeoHash")
   254  	}
   255  	return box, nil
   256  }
   257  
   258  // GeometryToEncodedPolyline turns the provided geometry and precision into a Polyline ASCII
   259  func GeometryToEncodedPolyline(g Geometry, p int) (string, error) {
   260  	gt, err := g.AsGeomT()
   261  	if err != nil {
   262  		return "", errors.Wrap(err, "error parsing input geometry")
   263  	}
   264  	if gt.SRID() != 4326 {
   265  		return "", pgerror.Newf(pgcode.InvalidParameterValue, "only SRID 4326 is supported")
   266  	}
   267  	if _, ok := gt.(*geom.GeometryCollection); ok {
   268  		return "", pgerror.Newf(pgcode.InvalidParameterValue, "'GeometryCollection' geometry type not supported")
   269  	}
   270  
   271  	return encodePolylinePoints(gt.FlatCoords(), p), nil
   272  }
   273  
   274  // ParseEncodedPolyline takes the encoded polyline ASCII and precision, decodes the points and returns them as a geometry
   275  func ParseEncodedPolyline(encodedPolyline string, precision int) (Geometry, error) {
   276  	flatCoords := decodePolylinePoints(encodedPolyline, precision)
   277  	ls := geom.NewLineStringFlat(geom.XY, flatCoords).SetSRID(4326)
   278  
   279  	g, err := MakeGeometryFromGeomT(ls)
   280  	if err != nil {
   281  		return Geometry{}, errors.Wrap(err, "parsing geography error")
   282  	}
   283  	return g, nil
   284  }