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  }