mirror of
https://github.com/Luzifer/staticmap.git
synced 2024-12-21 13:21:18 +00:00
465 lines
15 KiB
Go
465 lines
15 KiB
Go
// Copyright 2014 Google Inc. All rights reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package s2
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"math"
|
|
|
|
"github.com/golang/geo/r1"
|
|
"github.com/golang/geo/r3"
|
|
"github.com/golang/geo/s1"
|
|
)
|
|
|
|
// Rect represents a closed latitude-longitude rectangle.
|
|
type Rect struct {
|
|
Lat r1.Interval
|
|
Lng s1.Interval
|
|
}
|
|
|
|
var (
|
|
validRectLatRange = r1.Interval{-math.Pi / 2, math.Pi / 2}
|
|
validRectLngRange = s1.FullInterval()
|
|
)
|
|
|
|
// EmptyRect returns the empty rectangle.
|
|
func EmptyRect() Rect { return Rect{r1.EmptyInterval(), s1.EmptyInterval()} }
|
|
|
|
// FullRect returns the full rectangle.
|
|
func FullRect() Rect { return Rect{validRectLatRange, validRectLngRange} }
|
|
|
|
// RectFromLatLng constructs a rectangle containing a single point p.
|
|
func RectFromLatLng(p LatLng) Rect {
|
|
return Rect{
|
|
Lat: r1.Interval{p.Lat.Radians(), p.Lat.Radians()},
|
|
Lng: s1.Interval{p.Lng.Radians(), p.Lng.Radians()},
|
|
}
|
|
}
|
|
|
|
// RectFromCenterSize constructs a rectangle with the given size and center.
|
|
// center needs to be normalized, but size does not. The latitude
|
|
// interval of the result is clamped to [-90,90] degrees, and the longitude
|
|
// interval of the result is FullRect() if and only if the longitude size is
|
|
// 360 degrees or more.
|
|
//
|
|
// Examples of clamping (in degrees):
|
|
// center=(80,170), size=(40,60) -> lat=[60,90], lng=[140,-160]
|
|
// center=(10,40), size=(210,400) -> lat=[-90,90], lng=[-180,180]
|
|
// center=(-90,180), size=(20,50) -> lat=[-90,-80], lng=[155,-155]
|
|
func RectFromCenterSize(center, size LatLng) Rect {
|
|
half := LatLng{size.Lat / 2, size.Lng / 2}
|
|
return RectFromLatLng(center).expanded(half)
|
|
}
|
|
|
|
// IsValid returns true iff the rectangle is valid.
|
|
// This requires Lat ⊆ [-π/2,π/2] and Lng ⊆ [-π,π], and Lat = ∅ iff Lng = ∅
|
|
func (r Rect) IsValid() bool {
|
|
return math.Abs(r.Lat.Lo) <= math.Pi/2 &&
|
|
math.Abs(r.Lat.Hi) <= math.Pi/2 &&
|
|
r.Lng.IsValid() &&
|
|
r.Lat.IsEmpty() == r.Lng.IsEmpty()
|
|
}
|
|
|
|
// IsEmpty reports whether the rectangle is empty.
|
|
func (r Rect) IsEmpty() bool { return r.Lat.IsEmpty() }
|
|
|
|
// IsFull reports whether the rectangle is full.
|
|
func (r Rect) IsFull() bool { return r.Lat.Equal(validRectLatRange) && r.Lng.IsFull() }
|
|
|
|
// IsPoint reports whether the rectangle is a single point.
|
|
func (r Rect) IsPoint() bool { return r.Lat.Lo == r.Lat.Hi && r.Lng.Lo == r.Lng.Hi }
|
|
|
|
// Vertex returns the i-th vertex of the rectangle (i = 0,1,2,3) in CCW order
|
|
// (lower left, lower right, upper right, upper left).
|
|
func (r Rect) Vertex(i int) LatLng {
|
|
var lat, lng float64
|
|
|
|
switch i {
|
|
case 0:
|
|
lat = r.Lat.Lo
|
|
lng = r.Lng.Lo
|
|
case 1:
|
|
lat = r.Lat.Lo
|
|
lng = r.Lng.Hi
|
|
case 2:
|
|
lat = r.Lat.Hi
|
|
lng = r.Lng.Hi
|
|
case 3:
|
|
lat = r.Lat.Hi
|
|
lng = r.Lng.Lo
|
|
}
|
|
return LatLng{s1.Angle(lat) * s1.Radian, s1.Angle(lng) * s1.Radian}
|
|
}
|
|
|
|
// Lo returns one corner of the rectangle.
|
|
func (r Rect) Lo() LatLng {
|
|
return LatLng{s1.Angle(r.Lat.Lo) * s1.Radian, s1.Angle(r.Lng.Lo) * s1.Radian}
|
|
}
|
|
|
|
// Hi returns the other corner of the rectangle.
|
|
func (r Rect) Hi() LatLng {
|
|
return LatLng{s1.Angle(r.Lat.Hi) * s1.Radian, s1.Angle(r.Lng.Hi) * s1.Radian}
|
|
}
|
|
|
|
// Center returns the center of the rectangle.
|
|
func (r Rect) Center() LatLng {
|
|
return LatLng{s1.Angle(r.Lat.Center()) * s1.Radian, s1.Angle(r.Lng.Center()) * s1.Radian}
|
|
}
|
|
|
|
// Size returns the size of the Rect.
|
|
func (r Rect) Size() LatLng {
|
|
return LatLng{s1.Angle(r.Lat.Length()) * s1.Radian, s1.Angle(r.Lng.Length()) * s1.Radian}
|
|
}
|
|
|
|
// Area returns the surface area of the Rect.
|
|
func (r Rect) Area() float64 {
|
|
if r.IsEmpty() {
|
|
return 0
|
|
}
|
|
capDiff := math.Abs(math.Sin(r.Lat.Hi) - math.Sin(r.Lat.Lo))
|
|
return r.Lng.Length() * capDiff
|
|
}
|
|
|
|
// AddPoint increases the size of the rectangle to include the given point.
|
|
func (r Rect) AddPoint(ll LatLng) Rect {
|
|
if !ll.IsValid() {
|
|
return r
|
|
}
|
|
return Rect{
|
|
Lat: r.Lat.AddPoint(ll.Lat.Radians()),
|
|
Lng: r.Lng.AddPoint(ll.Lng.Radians()),
|
|
}
|
|
}
|
|
|
|
// expanded returns a rectangle that has been expanded by margin.Lat on each side
|
|
// in the latitude direction, and by margin.Lng on each side in the longitude
|
|
// direction. If either margin is negative, then it shrinks the rectangle on
|
|
// the corresponding sides instead. The resulting rectangle may be empty.
|
|
//
|
|
// The latitude-longitude space has the topology of a cylinder. Longitudes
|
|
// "wrap around" at +/-180 degrees, while latitudes are clamped to range [-90, 90].
|
|
// This means that any expansion (positive or negative) of the full longitude range
|
|
// remains full (since the "rectangle" is actually a continuous band around the
|
|
// cylinder), while expansion of the full latitude range remains full only if the
|
|
// margin is positive.
|
|
//
|
|
// If either the latitude or longitude interval becomes empty after
|
|
// expansion by a negative margin, the result is empty.
|
|
//
|
|
// Note that if an expanded rectangle contains a pole, it may not contain
|
|
// all possible lat/lng representations of that pole, e.g., both points [π/2,0]
|
|
// and [π/2,1] represent the same pole, but they might not be contained by the
|
|
// same Rect.
|
|
//
|
|
// If you are trying to grow a rectangle by a certain distance on the
|
|
// sphere (e.g. 5km), refer to the ExpandedByDistance() C++ method implementation
|
|
// instead.
|
|
func (r Rect) expanded(margin LatLng) Rect {
|
|
lat := r.Lat.Expanded(margin.Lat.Radians())
|
|
lng := r.Lng.Expanded(margin.Lng.Radians())
|
|
|
|
if lat.IsEmpty() || lng.IsEmpty() {
|
|
return EmptyRect()
|
|
}
|
|
|
|
return Rect{
|
|
Lat: lat.Intersection(validRectLatRange),
|
|
Lng: lng,
|
|
}
|
|
}
|
|
|
|
func (r Rect) String() string { return fmt.Sprintf("[Lo%v, Hi%v]", r.Lo(), r.Hi()) }
|
|
|
|
// PolarClosure returns the rectangle unmodified if it does not include either pole.
|
|
// If it includes either pole, PolarClosure returns an expansion of the rectangle along
|
|
// the longitudinal range to include all possible representations of the contained poles.
|
|
func (r Rect) PolarClosure() Rect {
|
|
if r.Lat.Lo == -math.Pi/2 || r.Lat.Hi == math.Pi/2 {
|
|
return Rect{r.Lat, s1.FullInterval()}
|
|
}
|
|
return r
|
|
}
|
|
|
|
// Union returns the smallest Rect containing the union of this rectangle and the given rectangle.
|
|
func (r Rect) Union(other Rect) Rect {
|
|
return Rect{
|
|
Lat: r.Lat.Union(other.Lat),
|
|
Lng: r.Lng.Union(other.Lng),
|
|
}
|
|
}
|
|
|
|
// Intersection returns the smallest rectangle containing the intersection of
|
|
// this rectangle and the given rectangle. Note that the region of intersection
|
|
// may consist of two disjoint rectangles, in which case a single rectangle
|
|
// spanning both of them is returned.
|
|
func (r Rect) Intersection(other Rect) Rect {
|
|
lat := r.Lat.Intersection(other.Lat)
|
|
lng := r.Lng.Intersection(other.Lng)
|
|
|
|
if lat.IsEmpty() || lng.IsEmpty() {
|
|
return EmptyRect()
|
|
}
|
|
return Rect{lat, lng}
|
|
}
|
|
|
|
// Intersects reports whether this rectangle and the other have any points in common.
|
|
func (r Rect) Intersects(other Rect) bool {
|
|
return r.Lat.Intersects(other.Lat) && r.Lng.Intersects(other.Lng)
|
|
}
|
|
|
|
// CapBound returns a cap that countains Rect.
|
|
func (r Rect) CapBound() Cap {
|
|
// We consider two possible bounding caps, one whose axis passes
|
|
// through the center of the lat-long rectangle and one whose axis
|
|
// is the north or south pole. We return the smaller of the two caps.
|
|
|
|
if r.IsEmpty() {
|
|
return EmptyCap()
|
|
}
|
|
|
|
var poleZ, poleAngle float64
|
|
if r.Lat.Hi+r.Lat.Lo < 0 {
|
|
// South pole axis yields smaller cap.
|
|
poleZ = -1
|
|
poleAngle = math.Pi/2 + r.Lat.Hi
|
|
} else {
|
|
poleZ = 1
|
|
poleAngle = math.Pi/2 - r.Lat.Lo
|
|
}
|
|
poleCap := CapFromCenterAngle(Point{r3.Vector{0, 0, poleZ}}, s1.Angle(poleAngle)*s1.Radian)
|
|
|
|
// For bounding rectangles that span 180 degrees or less in longitude, the
|
|
// maximum cap size is achieved at one of the rectangle vertices. For
|
|
// rectangles that are larger than 180 degrees, we punt and always return a
|
|
// bounding cap centered at one of the two poles.
|
|
if math.Remainder(r.Lng.Hi-r.Lng.Lo, 2*math.Pi) >= 0 && r.Lng.Hi-r.Lng.Lo < 2*math.Pi {
|
|
midCap := CapFromPoint(PointFromLatLng(r.Center())).AddPoint(PointFromLatLng(r.Lo())).AddPoint(PointFromLatLng(r.Hi()))
|
|
if midCap.Height() < poleCap.Height() {
|
|
return midCap
|
|
}
|
|
}
|
|
return poleCap
|
|
}
|
|
|
|
// RectBound returns itself.
|
|
func (r Rect) RectBound() Rect {
|
|
return r
|
|
}
|
|
|
|
// Contains reports whether this Rect contains the other Rect.
|
|
func (r Rect) Contains(other Rect) bool {
|
|
return r.Lat.ContainsInterval(other.Lat) && r.Lng.ContainsInterval(other.Lng)
|
|
}
|
|
|
|
// ContainsCell reports whether the given Cell is contained by this Rect.
|
|
func (r Rect) ContainsCell(c Cell) bool {
|
|
// A latitude-longitude rectangle contains a cell if and only if it contains
|
|
// the cell's bounding rectangle. This test is exact from a mathematical
|
|
// point of view, assuming that the bounds returned by Cell.RectBound()
|
|
// are tight. However, note that there can be a loss of precision when
|
|
// converting between representations -- for example, if an s2.Cell is
|
|
// converted to a polygon, the polygon's bounding rectangle may not contain
|
|
// the cell's bounding rectangle. This has some slightly unexpected side
|
|
// effects; for instance, if one creates an s2.Polygon from an s2.Cell, the
|
|
// polygon will contain the cell, but the polygon's bounding box will not.
|
|
return r.Contains(c.RectBound())
|
|
}
|
|
|
|
// ContainsLatLng reports whether the given LatLng is within the Rect.
|
|
func (r Rect) ContainsLatLng(ll LatLng) bool {
|
|
if !ll.IsValid() {
|
|
return false
|
|
}
|
|
return r.Lat.Contains(ll.Lat.Radians()) && r.Lng.Contains(ll.Lng.Radians())
|
|
}
|
|
|
|
// ContainsPoint reports whether the given Point is within the Rect.
|
|
func (r Rect) ContainsPoint(p Point) bool {
|
|
return r.ContainsLatLng(LatLngFromPoint(p))
|
|
}
|
|
|
|
// CellUnionBound computes a covering of the Rect.
|
|
func (r Rect) CellUnionBound() []CellID {
|
|
return r.CapBound().CellUnionBound()
|
|
}
|
|
|
|
// intersectsLatEdge reports whether the edge AB intersects the given edge of constant
|
|
// latitude. Requires the points to have unit length.
|
|
func intersectsLatEdge(a, b Point, lat s1.Angle, lng s1.Interval) bool {
|
|
// Unfortunately, lines of constant latitude are curves on
|
|
// the sphere. They can intersect a straight edge in 0, 1, or 2 points.
|
|
|
|
// First, compute the normal to the plane AB that points vaguely north.
|
|
z := Point{a.PointCross(b).Normalize()}
|
|
if z.Z < 0 {
|
|
z = Point{z.Mul(-1)}
|
|
}
|
|
|
|
// Extend this to an orthonormal frame (x,y,z) where x is the direction
|
|
// where the great circle through AB achieves its maximium latitude.
|
|
y := Point{z.PointCross(PointFromCoords(0, 0, 1)).Normalize()}
|
|
x := y.Cross(z.Vector)
|
|
|
|
// Compute the angle "theta" from the x-axis (in the x-y plane defined
|
|
// above) where the great circle intersects the given line of latitude.
|
|
sinLat := math.Sin(float64(lat))
|
|
if math.Abs(sinLat) >= x.Z {
|
|
// The great circle does not reach the given latitude.
|
|
return false
|
|
}
|
|
|
|
cosTheta := sinLat / x.Z
|
|
sinTheta := math.Sqrt(1 - cosTheta*cosTheta)
|
|
theta := math.Atan2(sinTheta, cosTheta)
|
|
|
|
// The candidate intersection points are located +/- theta in the x-y
|
|
// plane. For an intersection to be valid, we need to check that the
|
|
// intersection point is contained in the interior of the edge AB and
|
|
// also that it is contained within the given longitude interval "lng".
|
|
|
|
// Compute the range of theta values spanned by the edge AB.
|
|
abTheta := s1.IntervalFromPointPair(
|
|
math.Atan2(a.Dot(y.Vector), a.Dot(x)),
|
|
math.Atan2(b.Dot(y.Vector), b.Dot(x)))
|
|
|
|
if abTheta.Contains(theta) {
|
|
// Check if the intersection point is also in the given lng interval.
|
|
isect := x.Mul(cosTheta).Add(y.Mul(sinTheta))
|
|
if lng.Contains(math.Atan2(isect.Y, isect.X)) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
if abTheta.Contains(-theta) {
|
|
// Check if the other intersection point is also in the given lng interval.
|
|
isect := x.Mul(cosTheta).Sub(y.Mul(sinTheta))
|
|
if lng.Contains(math.Atan2(isect.Y, isect.X)) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// intersectsLngEdge reports whether the edge AB intersects the given edge of constant
|
|
// longitude. Requires the points to have unit length.
|
|
func intersectsLngEdge(a, b Point, lat r1.Interval, lng s1.Angle) bool {
|
|
// The nice thing about edges of constant longitude is that
|
|
// they are straight lines on the sphere (geodesics).
|
|
return CrossingSign(a, b, PointFromLatLng(LatLng{s1.Angle(lat.Lo), lng}),
|
|
PointFromLatLng(LatLng{s1.Angle(lat.Hi), lng})) == Cross
|
|
}
|
|
|
|
// IntersectsCell reports whether this rectangle intersects the given cell. This is an
|
|
// exact test and may be fairly expensive.
|
|
func (r Rect) IntersectsCell(c Cell) bool {
|
|
// First we eliminate the cases where one region completely contains the
|
|
// other. Once these are disposed of, then the regions will intersect
|
|
// if and only if their boundaries intersect.
|
|
if r.IsEmpty() {
|
|
return false
|
|
}
|
|
if r.ContainsPoint(Point{c.id.rawPoint()}) {
|
|
return true
|
|
}
|
|
if c.ContainsPoint(PointFromLatLng(r.Center())) {
|
|
return true
|
|
}
|
|
|
|
// Quick rejection test (not required for correctness).
|
|
if !r.Intersects(c.RectBound()) {
|
|
return false
|
|
}
|
|
|
|
// Precompute the cell vertices as points and latitude-longitudes. We also
|
|
// check whether the Cell contains any corner of the rectangle, or
|
|
// vice-versa, since the edge-crossing tests only check the edge interiors.
|
|
vertices := [4]Point{}
|
|
latlngs := [4]LatLng{}
|
|
|
|
for i := range vertices {
|
|
vertices[i] = c.Vertex(i)
|
|
latlngs[i] = LatLngFromPoint(vertices[i])
|
|
if r.ContainsLatLng(latlngs[i]) {
|
|
return true
|
|
}
|
|
if c.ContainsPoint(PointFromLatLng(r.Vertex(i))) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
// Now check whether the boundaries intersect. Unfortunately, a
|
|
// latitude-longitude rectangle does not have straight edges: two edges
|
|
// are curved, and at least one of them is concave.
|
|
for i := range vertices {
|
|
edgeLng := s1.IntervalFromEndpoints(latlngs[i].Lng.Radians(), latlngs[(i+1)&3].Lng.Radians())
|
|
if !r.Lng.Intersects(edgeLng) {
|
|
continue
|
|
}
|
|
|
|
a := vertices[i]
|
|
b := vertices[(i+1)&3]
|
|
if edgeLng.Contains(r.Lng.Lo) && intersectsLngEdge(a, b, r.Lat, s1.Angle(r.Lng.Lo)) {
|
|
return true
|
|
}
|
|
if edgeLng.Contains(r.Lng.Hi) && intersectsLngEdge(a, b, r.Lat, s1.Angle(r.Lng.Hi)) {
|
|
return true
|
|
}
|
|
if intersectsLatEdge(a, b, s1.Angle(r.Lat.Lo), r.Lng) {
|
|
return true
|
|
}
|
|
if intersectsLatEdge(a, b, s1.Angle(r.Lat.Hi), r.Lng) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Encode encodes the Rect.
|
|
func (r Rect) Encode(w io.Writer) error {
|
|
e := &encoder{w: w}
|
|
r.encode(e)
|
|
return e.err
|
|
}
|
|
|
|
func (r Rect) encode(e *encoder) {
|
|
e.writeInt8(encodingVersion)
|
|
e.writeFloat64(r.Lat.Lo)
|
|
e.writeFloat64(r.Lat.Hi)
|
|
e.writeFloat64(r.Lng.Lo)
|
|
e.writeFloat64(r.Lng.Hi)
|
|
}
|
|
|
|
// Decode decodes a rectangle.
|
|
func (r *Rect) Decode(rd io.Reader) error {
|
|
d := &decoder{r: asByteReader(rd)}
|
|
r.decode(d)
|
|
return d.err
|
|
}
|
|
|
|
func (r *Rect) decode(d *decoder) {
|
|
if version := d.readUint8(); int(version) != int(encodingVersion) && d.err == nil {
|
|
d.err = fmt.Errorf("can't decode version %d; my version: %d", version, encodingVersion)
|
|
return
|
|
}
|
|
r.Lat.Lo = d.readFloat64()
|
|
r.Lat.Hi = d.readFloat64()
|
|
r.Lng.Lo = d.readFloat64()
|
|
r.Lng.Hi = d.readFloat64()
|
|
return
|
|
}
|
|
|
|
// BUG: The major differences from the C++ version are:
|
|
// - GetCentroid, Get*Distance, Vertex, InteriorContains(LatLng|Rect|Point)
|