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 }