github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/geo/geogfn/segmentize.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 geogfn 12 13 import ( 14 "math" 15 16 "github.com/cockroachdb/cockroach/pkg/geo" 17 "github.com/cockroachdb/cockroach/pkg/geo/geographiclib" 18 "github.com/cockroachdb/errors" 19 "github.com/golang/geo/s2" 20 "github.com/twpayne/go-geom" 21 ) 22 23 // Segmentize return modified Geography having no segment longer 24 // that given maximum segment length. 25 // This works by dividing each segment by a power of 2 to find the 26 // smallest power less than or equal to the segmentMaxLength. 27 func Segmentize(geography *geo.Geography, segmentMaxLength float64) (*geo.Geography, error) { 28 geometry, err := geography.AsGeomT() 29 if err != nil { 30 return nil, err 31 } 32 switch geometry := geometry.(type) { 33 case *geom.Point, *geom.MultiPoint: 34 return geography, nil 35 default: 36 if segmentMaxLength <= 0 { 37 return nil, errors.Newf("maximum segment length must be positive") 38 } 39 spheroid := geographiclib.WGS84Spheroid 40 // Convert segmentMaxLength to Angle with respect to earth sphere as 41 // further calculation is done considering segmentMaxLength as Angle. 42 segmentMaxAngle := segmentMaxLength / spheroid.SphereRadius 43 segGeometry, err := segmentizeGeom(geometry, segmentMaxAngle) 44 if err != nil { 45 return nil, err 46 } 47 return geo.NewGeographyFromGeom(segGeometry) 48 } 49 } 50 51 // segmentizeGeom returns a modified geom.T having no segment longer than 52 // the given maximum segment length. 53 func segmentizeGeom(geometry geom.T, segmentMaxAngle float64) (geom.T, error) { 54 if geometry.Empty() { 55 return geometry, nil 56 } 57 switch geometry := geometry.(type) { 58 case *geom.Point, *geom.MultiPoint: 59 return geometry, nil 60 case *geom.LineString: 61 var allFlatCoordinates []float64 62 for pointIdx := 1; pointIdx < geometry.NumCoords(); pointIdx++ { 63 allFlatCoordinates = append( 64 allFlatCoordinates, 65 segmentizeCoords(geometry.Coord(pointIdx-1), geometry.Coord(pointIdx), segmentMaxAngle)..., 66 ) 67 } 68 // Appending end point as it wasn't included in the iteration of coordinates. 69 allFlatCoordinates = append(allFlatCoordinates, geometry.Coord(geometry.NumCoords()-1)...) 70 return geom.NewLineStringFlat(geom.XY, allFlatCoordinates).SetSRID(geometry.SRID()), nil 71 case *geom.MultiLineString: 72 segMultiLine := geom.NewMultiLineString(geom.XY).SetSRID(geometry.SRID()) 73 for lineIdx := 0; lineIdx < geometry.NumLineStrings(); lineIdx++ { 74 l, err := segmentizeGeom(geometry.LineString(lineIdx), segmentMaxAngle) 75 if err != nil { 76 return nil, err 77 } 78 err = segMultiLine.Push(l.(*geom.LineString)) 79 if err != nil { 80 return nil, err 81 } 82 } 83 return segMultiLine, nil 84 case *geom.LinearRing: 85 var allFlatCoordinates []float64 86 for pointIdx := 1; pointIdx < geometry.NumCoords(); pointIdx++ { 87 allFlatCoordinates = append( 88 allFlatCoordinates, 89 segmentizeCoords(geometry.Coord(pointIdx-1), geometry.Coord(pointIdx), segmentMaxAngle)..., 90 ) 91 } 92 // Appending end point as it wasn't included in the iteration of coordinates. 93 allFlatCoordinates = append(allFlatCoordinates, geometry.Coord(geometry.NumCoords()-1)...) 94 return geom.NewLinearRingFlat(geom.XY, allFlatCoordinates).SetSRID(geometry.SRID()), nil 95 case *geom.Polygon: 96 segPolygon := geom.NewPolygon(geom.XY).SetSRID(geometry.SRID()) 97 for loopIdx := 0; loopIdx < geometry.NumLinearRings(); loopIdx++ { 98 l, err := segmentizeGeom(geometry.LinearRing(loopIdx), segmentMaxAngle) 99 if err != nil { 100 return nil, err 101 } 102 err = segPolygon.Push(l.(*geom.LinearRing)) 103 if err != nil { 104 return nil, err 105 } 106 } 107 return segPolygon, nil 108 case *geom.MultiPolygon: 109 segMultiPolygon := geom.NewMultiPolygon(geom.XY).SetSRID(geometry.SRID()) 110 for polygonIdx := 0; polygonIdx < geometry.NumPolygons(); polygonIdx++ { 111 p, err := segmentizeGeom(geometry.Polygon(polygonIdx), segmentMaxAngle) 112 if err != nil { 113 return nil, err 114 } 115 err = segMultiPolygon.Push(p.(*geom.Polygon)) 116 if err != nil { 117 return nil, err 118 } 119 } 120 return segMultiPolygon, nil 121 case *geom.GeometryCollection: 122 segGeomCollection := geom.NewGeometryCollection().SetSRID(geometry.SRID()) 123 for geoIdx := 0; geoIdx < geometry.NumGeoms(); geoIdx++ { 124 g, err := segmentizeGeom(geometry.Geom(geoIdx), segmentMaxAngle) 125 if err != nil { 126 return nil, err 127 } 128 err = segGeomCollection.Push(g) 129 if err != nil { 130 return nil, err 131 } 132 } 133 return segGeomCollection, nil 134 } 135 return nil, errors.Newf("unknown type: %T", geometry) 136 } 137 138 // segmentizeCoords inserts multiple points between given two-coordinate and 139 // return resultant points as flat []float64. Such that distance between any two 140 // points is less than given maximum segment's length, the total number of 141 // segments is the power of 2, and all the segments are of the same length. 142 // NOTE: List of points does not consist of end point. 143 func segmentizeCoords(a geom.Coord, b geom.Coord, segmentMaxAngle float64) []float64 { 144 // Converted geom.Coord into s2.Point so we can segmentize the coordinates. 145 pointA := s2.PointFromLatLng(s2.LatLngFromDegrees(a.Y(), a.X())) 146 pointB := s2.PointFromLatLng(s2.LatLngFromDegrees(b.Y(), b.X())) 147 148 allSegmentizedCoordinates := a.Clone() 149 chordAngleBetweenPoints := s2.ChordAngleBetweenPoints(pointA, pointB).Angle().Radians() 150 if segmentMaxAngle <= chordAngleBetweenPoints { 151 // This calculation is to determine the total number of segment between given 152 // 2 coordinates, ensuring that the segments are divided into parts divisible by 153 // a power of 2. 154 // 155 // For that fraction by segment must be less than or equal to 156 // the fraction of max segment length to distance between point, since the 157 // total number of segment must be power of 2 therefore we can write as 158 // 1 / (2^n)[numberOfSegmentToCreate] <= segmentMaxLength / distanceBetweenPoints < 1 / (2^(n-1)) 159 // (2^n)[numberOfSegmentToCreate] >= distanceBetweenPoints / segmentMaxLength > 2^(n-1) 160 // therefore n = ceil(log2(segmentMaxLength/distanceBetweenPoints)). Hence 161 // numberOfSegmentToCreate = 2^(ceil(log2(segmentMaxLength/distanceBetweenPoints))). 162 numberOfSegmentToCreate := int(math.Pow(2, math.Ceil(math.Log2(chordAngleBetweenPoints/segmentMaxAngle)))) 163 for pointInserted := 1; pointInserted < numberOfSegmentToCreate; pointInserted++ { 164 newPoint := s2.Interpolate(float64(pointInserted)/float64(numberOfSegmentToCreate), pointA, pointB) 165 latLng := s2.LatLngFromPoint(newPoint) 166 allSegmentizedCoordinates = append(allSegmentizedCoordinates, latLng.Lng.Degrees(), latLng.Lat.Degrees()) 167 } 168 } 169 return allSegmentizedCoordinates 170 }