gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

point.go (9373B)


      1 // Copyright 2014 Google Inc. All rights reserved.
      2 //
      3 // Licensed under the Apache License, Version 2.0 (the "License");
      4 // you may not use this file except in compliance with the License.
      5 // You may obtain a copy of the License at
      6 //
      7 //     http://www.apache.org/licenses/LICENSE-2.0
      8 //
      9 // Unless required by applicable law or agreed to in writing, software
     10 // distributed under the License is distributed on an "AS IS" BASIS,
     11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     12 // See the License for the specific language governing permissions and
     13 // limitations under the License.
     14 
     15 package s2
     16 
     17 import (
     18 	"fmt"
     19 	"io"
     20 	"math"
     21 	"sort"
     22 
     23 	"github.com/golang/geo/r3"
     24 	"github.com/golang/geo/s1"
     25 )
     26 
     27 // Point represents a point on the unit sphere as a normalized 3D vector.
     28 // Fields should be treated as read-only. Use one of the factory methods for creation.
     29 type Point struct {
     30 	r3.Vector
     31 }
     32 
     33 // sortPoints sorts the slice of Points in place.
     34 func sortPoints(e []Point) {
     35 	sort.Sort(points(e))
     36 }
     37 
     38 // points implements the Sort interface for slices of Point.
     39 type points []Point
     40 
     41 func (p points) Len() int           { return len(p) }
     42 func (p points) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }
     43 func (p points) Less(i, j int) bool { return p[i].Cmp(p[j].Vector) == -1 }
     44 
     45 // PointFromCoords creates a new normalized point from coordinates.
     46 //
     47 // This always returns a valid point. If the given coordinates can not be normalized
     48 // the origin point will be returned.
     49 //
     50 // This behavior is different from the C++ construction of a S2Point from coordinates
     51 // (i.e. S2Point(x, y, z)) in that in C++ they do not Normalize.
     52 func PointFromCoords(x, y, z float64) Point {
     53 	if x == 0 && y == 0 && z == 0 {
     54 		return OriginPoint()
     55 	}
     56 	return Point{r3.Vector{x, y, z}.Normalize()}
     57 }
     58 
     59 // OriginPoint returns a unique "origin" on the sphere for operations that need a fixed
     60 // reference point. In particular, this is the "point at infinity" used for
     61 // point-in-polygon testing (by counting the number of edge crossings).
     62 //
     63 // It should *not* be a point that is commonly used in edge tests in order
     64 // to avoid triggering code to handle degenerate cases (this rules out the
     65 // north and south poles). It should also not be on the boundary of any
     66 // low-level S2Cell for the same reason.
     67 func OriginPoint() Point {
     68 	return Point{r3.Vector{-0.0099994664350250197, 0.0025924542609324121, 0.99994664350250195}}
     69 }
     70 
     71 // PointCross returns a Point that is orthogonal to both p and op. This is similar to
     72 // p.Cross(op) (the true cross product) except that it does a better job of
     73 // ensuring orthogonality when the Point is nearly parallel to op, it returns
     74 // a non-zero result even when p == op or p == -op and the result is a Point.
     75 //
     76 // It satisfies the following properties (f == PointCross):
     77 //
     78 //   (1) f(p, op) != 0 for all p, op
     79 //   (2) f(op,p) == -f(p,op) unless p == op or p == -op
     80 //   (3) f(-p,op) == -f(p,op) unless p == op or p == -op
     81 //   (4) f(p,-op) == -f(p,op) unless p == op or p == -op
     82 func (p Point) PointCross(op Point) Point {
     83 	// NOTE(dnadasi): In the C++ API the equivalent method here was known as "RobustCrossProd",
     84 	// but PointCross more accurately describes how this method is used.
     85 	x := p.Add(op.Vector).Cross(op.Sub(p.Vector))
     86 
     87 	// Compare exactly to the 0 vector.
     88 	if x == (r3.Vector{}) {
     89 		// The only result that makes sense mathematically is to return zero, but
     90 		// we find it more convenient to return an arbitrary orthogonal vector.
     91 		return Point{p.Ortho()}
     92 	}
     93 
     94 	return Point{x}
     95 }
     96 
     97 // OrderedCCW returns true if the edges OA, OB, and OC are encountered in that
     98 // order while sweeping CCW around the point O.
     99 //
    100 // You can think of this as testing whether A <= B <= C with respect to the
    101 // CCW ordering around O that starts at A, or equivalently, whether B is
    102 // contained in the range of angles (inclusive) that starts at A and extends
    103 // CCW to C. Properties:
    104 //
    105 //  (1) If OrderedCCW(a,b,c,o) && OrderedCCW(b,a,c,o), then a == b
    106 //  (2) If OrderedCCW(a,b,c,o) && OrderedCCW(a,c,b,o), then b == c
    107 //  (3) If OrderedCCW(a,b,c,o) && OrderedCCW(c,b,a,o), then a == b == c
    108 //  (4) If a == b or b == c, then OrderedCCW(a,b,c,o) is true
    109 //  (5) Otherwise if a == c, then OrderedCCW(a,b,c,o) is false
    110 func OrderedCCW(a, b, c, o Point) bool {
    111 	sum := 0
    112 	if RobustSign(b, o, a) != Clockwise {
    113 		sum++
    114 	}
    115 	if RobustSign(c, o, b) != Clockwise {
    116 		sum++
    117 	}
    118 	if RobustSign(a, o, c) == CounterClockwise {
    119 		sum++
    120 	}
    121 	return sum >= 2
    122 }
    123 
    124 // Distance returns the angle between two points.
    125 func (p Point) Distance(b Point) s1.Angle {
    126 	return p.Vector.Angle(b.Vector)
    127 }
    128 
    129 // ApproxEqual reports whether the two points are similar enough to be equal.
    130 func (p Point) ApproxEqual(other Point) bool {
    131 	return p.approxEqual(other, s1.Angle(epsilon))
    132 }
    133 
    134 // approxEqual reports whether the two points are within the given epsilon.
    135 func (p Point) approxEqual(other Point, eps s1.Angle) bool {
    136 	return p.Vector.Angle(other.Vector) <= eps
    137 }
    138 
    139 // ChordAngleBetweenPoints constructs a ChordAngle corresponding to the distance
    140 // between the two given points. The points must be unit length.
    141 func ChordAngleBetweenPoints(x, y Point) s1.ChordAngle {
    142 	return s1.ChordAngle(math.Min(4.0, x.Sub(y.Vector).Norm2()))
    143 }
    144 
    145 // regularPoints generates a slice of points shaped as a regular polygon with
    146 // the numVertices vertices, all located on a circle of the specified angular radius
    147 // around the center. The radius is the actual distance from center to each vertex.
    148 func regularPoints(center Point, radius s1.Angle, numVertices int) []Point {
    149 	return regularPointsForFrame(getFrame(center), radius, numVertices)
    150 }
    151 
    152 // regularPointsForFrame generates a slice of points shaped as a regular polygon
    153 // with numVertices vertices, all on a circle of the specified angular radius around
    154 // the center. The radius is the actual distance from the center to each vertex.
    155 func regularPointsForFrame(frame matrix3x3, radius s1.Angle, numVertices int) []Point {
    156 	// We construct the loop in the given frame coordinates, with the center at
    157 	// (0, 0, 1). For a loop of radius r, the loop vertices have the form
    158 	// (x, y, z) where x^2 + y^2 = sin(r) and z = cos(r). The distance on the
    159 	// sphere (arc length) from each vertex to the center is acos(cos(r)) = r.
    160 	z := math.Cos(radius.Radians())
    161 	r := math.Sin(radius.Radians())
    162 	radianStep := 2 * math.Pi / float64(numVertices)
    163 	var vertices []Point
    164 
    165 	for i := 0; i < numVertices; i++ {
    166 		angle := float64(i) * radianStep
    167 		p := Point{r3.Vector{r * math.Cos(angle), r * math.Sin(angle), z}}
    168 		vertices = append(vertices, Point{fromFrame(frame, p).Normalize()})
    169 	}
    170 
    171 	return vertices
    172 }
    173 
    174 // CapBound returns a bounding cap for this point.
    175 func (p Point) CapBound() Cap {
    176 	return CapFromPoint(p)
    177 }
    178 
    179 // RectBound returns a bounding latitude-longitude rectangle from this point.
    180 func (p Point) RectBound() Rect {
    181 	return RectFromLatLng(LatLngFromPoint(p))
    182 }
    183 
    184 // ContainsCell returns false as Points do not contain any other S2 types.
    185 func (p Point) ContainsCell(c Cell) bool { return false }
    186 
    187 // IntersectsCell reports whether this Point intersects the given cell.
    188 func (p Point) IntersectsCell(c Cell) bool {
    189 	return c.ContainsPoint(p)
    190 }
    191 
    192 // ContainsPoint reports if this Point contains the other Point.
    193 // (This method is named to satisfy the Region interface.)
    194 func (p Point) ContainsPoint(other Point) bool {
    195 	return p.Contains(other)
    196 }
    197 
    198 // CellUnionBound computes a covering of the Point.
    199 func (p Point) CellUnionBound() []CellID {
    200 	return p.CapBound().CellUnionBound()
    201 }
    202 
    203 // Contains reports if this Point contains the other Point.
    204 // (This method matches all other s2 types where the reflexive Contains
    205 // method does not contain the type's name.)
    206 func (p Point) Contains(other Point) bool { return p == other }
    207 
    208 // Encode encodes the Point.
    209 func (p Point) Encode(w io.Writer) error {
    210 	e := &encoder{w: w}
    211 	p.encode(e)
    212 	return e.err
    213 }
    214 
    215 func (p Point) encode(e *encoder) {
    216 	e.writeInt8(encodingVersion)
    217 	e.writeFloat64(p.X)
    218 	e.writeFloat64(p.Y)
    219 	e.writeFloat64(p.Z)
    220 }
    221 
    222 // Decode decodes the Point.
    223 func (p *Point) Decode(r io.Reader) error {
    224 	d := &decoder{r: asByteReader(r)}
    225 	p.decode(d)
    226 	return d.err
    227 }
    228 
    229 func (p *Point) decode(d *decoder) {
    230 	version := d.readInt8()
    231 	if d.err != nil {
    232 		return
    233 	}
    234 	if version != encodingVersion {
    235 		d.err = fmt.Errorf("only version %d is supported", encodingVersion)
    236 		return
    237 	}
    238 	p.X = d.readFloat64()
    239 	p.Y = d.readFloat64()
    240 	p.Z = d.readFloat64()
    241 }
    242 
    243 // Rotate the given point about the given axis by the given angle. p and
    244 // axis must be unit length; angle has no restrictions (e.g., it can be
    245 // positive, negative, greater than 360 degrees, etc).
    246 func Rotate(p, axis Point, angle s1.Angle) Point {
    247 	// Let M be the plane through P that is perpendicular to axis, and let
    248 	// center be the point where M intersects axis. We construct a
    249 	// right-handed orthogonal frame (dx, dy, center) such that dx is the
    250 	// vector from center to P, and dy has the same length as dx. The
    251 	// result can then be expressed as (cos(angle)*dx + sin(angle)*dy + center).
    252 	center := axis.Mul(p.Dot(axis.Vector))
    253 	dx := p.Sub(center)
    254 	dy := axis.Cross(p.Vector)
    255 	// Mathematically the result is unit length, but normalization is necessary
    256 	// to ensure that numerical errors don't accumulate.
    257 	return Point{dx.Mul(math.Cos(angle.Radians())).Add(dy.Mul(math.Sin(angle.Radians()))).Add(center).Normalize()}
    258 }