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 }