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 }