github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/geo/geogfn/unary_operators.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 "github.com/cockroachdb/cockroach/pkg/geo" 15 "github.com/cockroachdb/cockroach/pkg/geo/geographiclib" 16 "github.com/cockroachdb/errors" 17 "github.com/golang/geo/s2" 18 "github.com/twpayne/go-geom" 19 ) 20 21 // Area returns the area of a given Geography. 22 func Area(g *geo.Geography, useSphereOrSpheroid UseSphereOrSpheroid) (float64, error) { 23 regions, err := g.AsS2(geo.EmptyBehaviorOmit) 24 if err != nil { 25 return 0, err 26 } 27 spheroid := geographiclib.WGS84Spheroid 28 29 var totalArea float64 30 for _, region := range regions { 31 switch region := region.(type) { 32 case s2.Point, *s2.Polyline: 33 case *s2.Polygon: 34 if useSphereOrSpheroid == UseSpheroid { 35 for _, loop := range region.Loops() { 36 points := loop.Vertices() 37 area, _ := spheroid.AreaAndPerimeter(points[:len(points)-1]) 38 totalArea += float64(loop.Sign()) * area 39 } 40 } else { 41 totalArea += region.Area() 42 } 43 default: 44 return 0, errors.Newf("unknown type: %T", region) 45 } 46 } 47 if useSphereOrSpheroid == UseSphere { 48 totalArea *= spheroid.SphereRadius * spheroid.SphereRadius 49 } 50 return totalArea, nil 51 } 52 53 // Perimeter returns the perimeter of a given Geography. 54 func Perimeter(g *geo.Geography, useSphereOrSpheroid UseSphereOrSpheroid) (float64, error) { 55 gt, err := g.AsGeomT() 56 if err != nil { 57 return 0, err 58 } 59 // This check mirrors PostGIS behavior, where GeometryCollections 60 // of LineStrings include the length for perimeters. 61 switch gt.(type) { 62 case *geom.Polygon, *geom.MultiPolygon, *geom.GeometryCollection: 63 default: 64 return 0, nil 65 } 66 regions, err := geo.S2RegionsFromGeom(gt, geo.EmptyBehaviorOmit) 67 if err != nil { 68 return 0, err 69 } 70 return length(regions, useSphereOrSpheroid) 71 } 72 73 // Length returns length of a given Geography. 74 func Length(g *geo.Geography, useSphereOrSpheroid UseSphereOrSpheroid) (float64, error) { 75 gt, err := g.AsGeomT() 76 if err != nil { 77 return 0, err 78 } 79 // This check mirrors PostGIS behavior, where GeometryCollections 80 // of Polygons include the perimeters for polygons. 81 switch gt.(type) { 82 case *geom.LineString, *geom.MultiLineString, *geom.GeometryCollection: 83 default: 84 return 0, nil 85 } 86 regions, err := geo.S2RegionsFromGeom(gt, geo.EmptyBehaviorOmit) 87 if err != nil { 88 return 0, err 89 } 90 return length(regions, useSphereOrSpheroid) 91 } 92 93 // length returns the sum of the lengtsh and perimeters in the shapes of the Geography. 94 // In OGC parlance, length returns both LineString lengths _and_ Polygon perimeters. 95 func length(regions []s2.Region, useSphereOrSpheroid UseSphereOrSpheroid) (float64, error) { 96 spheroid := geographiclib.WGS84Spheroid 97 98 var totalLength float64 99 for _, region := range regions { 100 switch region := region.(type) { 101 case s2.Point: 102 case *s2.Polyline: 103 if useSphereOrSpheroid == UseSpheroid { 104 totalLength += spheroid.InverseBatch((*region)) 105 } else { 106 for edgeIdx := 0; edgeIdx < region.NumEdges(); edgeIdx++ { 107 edge := region.Edge(edgeIdx) 108 totalLength += s2.ChordAngleBetweenPoints(edge.V0, edge.V1).Angle().Radians() 109 } 110 } 111 case *s2.Polygon: 112 for _, loop := range region.Loops() { 113 if useSphereOrSpheroid == UseSpheroid { 114 totalLength += spheroid.InverseBatch(loop.Vertices()) 115 } else { 116 for edgeIdx := 0; edgeIdx < loop.NumEdges(); edgeIdx++ { 117 edge := loop.Edge(edgeIdx) 118 totalLength += s2.ChordAngleBetweenPoints(edge.V0, edge.V1).Angle().Radians() 119 } 120 } 121 } 122 default: 123 return 0, errors.Newf("unknown type: %T", region) 124 } 125 } 126 if useSphereOrSpheroid == UseSphere { 127 totalLength *= spheroid.SphereRadius 128 } 129 return totalLength, nil 130 }