github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/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  	"fmt"
    15  	"strconv"
    16  	"strings"
    17  
    18  	"github.com/cockroachdb/cockroach/pkg/geo/geopb"
    19  	"github.com/cockroachdb/cockroach/pkg/geo/geos"
    20  	"github.com/cockroachdb/cockroach/pkg/util"
    21  	"github.com/twpayne/go-geom"
    22  	"github.com/twpayne/go-geom/encoding/ewkb"
    23  	"github.com/twpayne/go-geom/encoding/ewkbhex"
    24  	"github.com/twpayne/go-geom/encoding/geojson"
    25  	"github.com/twpayne/go-geom/encoding/wkb"
    26  )
    27  
    28  // parseEWKBRaw creates a geopb.SpatialObject from an EWKB
    29  // without doing any SRID based checks.
    30  // You most likely want parseEWKB instead.
    31  func parseEWKBRaw(in geopb.EWKB) (geopb.SpatialObject, error) {
    32  	t, err := ewkb.Unmarshal(in)
    33  	if err != nil {
    34  		return geopb.SpatialObject{}, err
    35  	}
    36  	return spatialObjectFromGeom(t)
    37  }
    38  
    39  // parseAmbiguousText parses a text as a number of different options
    40  // that is available in the geospatial world using the first character as
    41  // a heuristic.
    42  // This matches the PostGIS direct cast from a string to GEOGRAPHY/GEOMETRY.
    43  func parseAmbiguousText(str string, defaultSRID geopb.SRID) (geopb.SpatialObject, error) {
    44  	if len(str) == 0 {
    45  		return geopb.SpatialObject{}, fmt.Errorf("geo: parsing empty string to geo type")
    46  	}
    47  
    48  	switch str[0] {
    49  	case '0':
    50  		return parseEWKBHex(str, defaultSRID)
    51  	case 0x00, 0x01:
    52  		return parseEWKB([]byte(str), defaultSRID, DefaultSRIDIsHint)
    53  	case '{':
    54  		return parseGeoJSON([]byte(str), defaultSRID)
    55  	}
    56  
    57  	return parseEWKT(geopb.EWKT(str), defaultSRID, DefaultSRIDIsHint)
    58  }
    59  
    60  // parseEWKBHex takes a given str assumed to be in EWKB hex and transforms it
    61  // into a SpatialObject.
    62  func parseEWKBHex(str string, defaultSRID geopb.SRID) (geopb.SpatialObject, error) {
    63  	t, err := ewkbhex.Decode(str)
    64  	if err != nil {
    65  		return geopb.SpatialObject{}, err
    66  	}
    67  	// TODO(otan): check SRID is valid against spatial_ref_sys.
    68  	if defaultSRID != 0 && t.SRID() == 0 {
    69  		adjustGeomSRID(t, defaultSRID)
    70  	}
    71  	return spatialObjectFromGeom(t)
    72  }
    73  
    74  // parseEWKB takes given bytes assumed to be EWKB and transforms it into a SpatialObject.
    75  // The defaultSRID will overwrite any SRID set in the EWKB if overwrite is true.
    76  func parseEWKB(
    77  	b []byte, defaultSRID geopb.SRID, overwrite defaultSRIDOverwriteSetting,
    78  ) (geopb.SpatialObject, error) {
    79  	t, err := ewkb.Unmarshal(b)
    80  	if err != nil {
    81  		return geopb.SpatialObject{}, err
    82  	}
    83  	// TODO(otan): check SRID is valid against spatial_ref_sys.
    84  	if overwrite == DefaultSRIDShouldOverwrite || (defaultSRID != 0 && t.SRID() == 0) {
    85  		adjustGeomSRID(t, defaultSRID)
    86  	}
    87  	return spatialObjectFromGeom(t)
    88  }
    89  
    90  // parseWKB takes given bytes assumed to be WKB and transforms it into a SpatialObject.
    91  func parseWKB(b []byte, defaultSRID geopb.SRID) (geopb.SpatialObject, error) {
    92  	t, err := wkb.Unmarshal(b)
    93  	if err != nil {
    94  		return geopb.SpatialObject{}, err
    95  	}
    96  	adjustGeomSRID(t, defaultSRID)
    97  	return spatialObjectFromGeom(t)
    98  }
    99  
   100  // parseGeoJSON takes given bytes assumed to be GeoJSON and transforms it into a SpatialObject.
   101  func parseGeoJSON(b []byte, defaultSRID geopb.SRID) (geopb.SpatialObject, error) {
   102  	var f geojson.Feature
   103  	if err := f.UnmarshalJSON(b); err != nil {
   104  		return geopb.SpatialObject{}, err
   105  	}
   106  	t := f.Geometry
   107  	// TODO(otan): check SRID from properties.
   108  	if defaultSRID != 0 && t.SRID() == 0 {
   109  		adjustGeomSRID(t, defaultSRID)
   110  	}
   111  	return spatialObjectFromGeom(t)
   112  }
   113  
   114  // adjustGeomSRID adjusts the SRID of a given geom.T.
   115  // Ideally SetSRID is an interface of geom.T, but that is not the case.
   116  func adjustGeomSRID(t geom.T, srid geopb.SRID) {
   117  	switch t := t.(type) {
   118  	case *geom.Point:
   119  		t.SetSRID(int(srid))
   120  	case *geom.LineString:
   121  		t.SetSRID(int(srid))
   122  	case *geom.Polygon:
   123  		t.SetSRID(int(srid))
   124  	case *geom.GeometryCollection:
   125  		t.SetSRID(int(srid))
   126  	case *geom.MultiPoint:
   127  		t.SetSRID(int(srid))
   128  	case *geom.MultiLineString:
   129  		t.SetSRID(int(srid))
   130  	case *geom.MultiPolygon:
   131  		t.SetSRID(int(srid))
   132  	default:
   133  		panic(fmt.Errorf("geo: unknown geom type: %v", t))
   134  	}
   135  }
   136  
   137  const sridPrefix = "SRID="
   138  const sridPrefixLen = len(sridPrefix)
   139  
   140  type defaultSRIDOverwriteSetting bool
   141  
   142  const (
   143  	// DefaultSRIDShouldOverwrite implies the parsing function should overwrite
   144  	// the SRID with the defaultSRID.
   145  	DefaultSRIDShouldOverwrite defaultSRIDOverwriteSetting = true
   146  	// DefaultSRIDIsHint implies that the default SRID is only a hint
   147  	// and if the SRID is provided by the given EWKT/EWKB, it should be
   148  	// used instead.
   149  	DefaultSRIDIsHint defaultSRIDOverwriteSetting = false
   150  )
   151  
   152  // parseEWKT decodes a WKT string and transforms it into a SpatialObject.
   153  // The defaultSRID will overwrite any SRID set in the EWKT if overwrite is true.
   154  func parseEWKT(
   155  	str geopb.EWKT, defaultSRID geopb.SRID, overwrite defaultSRIDOverwriteSetting,
   156  ) (geopb.SpatialObject, error) {
   157  	srid := defaultSRID
   158  	if hasPrefixIgnoreCase(string(str), sridPrefix) {
   159  		end := strings.Index(string(str[sridPrefixLen:]), ";")
   160  		if end != -1 {
   161  			if overwrite != DefaultSRIDShouldOverwrite {
   162  				sridInt64, err := strconv.ParseInt(string(str[sridPrefixLen:sridPrefixLen+end]), 10, 32)
   163  				if err != nil {
   164  					return geopb.SpatialObject{}, err
   165  				}
   166  				// Only use the parsed SRID if the parsed SRID is not zero and it was not
   167  				// to be overwritten by the DefaultSRID parameter.
   168  				if sridInt64 != 0 {
   169  					srid = geopb.SRID(sridInt64)
   170  				}
   171  			}
   172  			str = str[sridPrefixLen+end+1:]
   173  		} else {
   174  			return geopb.SpatialObject{}, fmt.Errorf(
   175  				"geo: failed to find ; character with SRID declaration during EWKT decode: %q",
   176  				str,
   177  			)
   178  		}
   179  	}
   180  
   181  	ewkb, err := geos.WKTToEWKB(geopb.WKT(str), srid)
   182  	if err != nil {
   183  		return geopb.SpatialObject{}, err
   184  	}
   185  	return parseEWKBRaw(ewkb)
   186  }
   187  
   188  // hasPrefixIgnoreCase returns whether a given str begins with a prefix, ignoring case.
   189  // It assumes that the string and prefix contains only ASCII bytes.
   190  func hasPrefixIgnoreCase(str string, prefix string) bool {
   191  	if len(str) < len(prefix) {
   192  		return false
   193  	}
   194  	for i := 0; i < len(prefix); i++ {
   195  		if util.ToLowerSingleByte(str[i]) != util.ToLowerSingleByte(prefix[i]) {
   196  			return false
   197  		}
   198  	}
   199  	return true
   200  }