Skip to content

Commit 192e237

Browse files
committed
More additions
1 parent 26946a9 commit 192e237

14 files changed

+2103
-346
lines changed

Sources/R1Interval.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,20 +26,20 @@ public struct R1Interval: Equatable { //, Hashable {
2626
public static let empty = R1Interval(lo: 1, hi: 0)
2727

2828
/// Convenience method to construct an interval containing a single point.
29-
public static func from(point p: Double) -> R1Interval {
30-
return R1Interval(lo: p, hi: p)
29+
public init(point p: Double) {
30+
self.init(lo: p, hi: p)
3131
}
3232

3333
/**
3434
Convenience method to construct the minimal interval containing the two
3535
given points. This is equivalent to starting with an empty interval and
3636
calling AddPoint() twice, but it is more efficient.
3737
*/
38-
public static func fromPointPair(p1: Double, p2: Double) -> R1Interval {
38+
public init(p1: Double, p2: Double) {
3939
if p1 <= p2 {
40-
return R1Interval(lo: p1, hi: p2)
40+
self.init(lo: p1, hi: p2)
4141
} else {
42-
return R1Interval(lo: p2, hi: p1)
42+
self.init(lo: p2, hi: p1)
4343
}
4444
}
4545

@@ -95,7 +95,7 @@ public struct R1Interval: Equatable { //, Hashable {
9595
/// Expand the interval so that it contains the given point "p".
9696
public func add(point p: Double) -> R1Interval {
9797
if isEmpty {
98-
return R1Interval.from(point: p)
98+
return R1Interval(point: p)
9999
} else if p < lo {
100100
return R1Interval(lo: p, hi: hi)
101101
} else if p > hi {

Sources/S1Interval.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ public struct S1Interval {
223223
[2,Pi] intersect, for example.
224224
*/
225225
public func intersects(with y: S1Interval) -> Bool {
226-
if (isEmpty || y.isEmpty) {
226+
if isEmpty || y.isEmpty {
227227
return false
228228
}
229229
if isInverted {

Sources/S2.swift

Lines changed: 91 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,6 @@ internal extension Double {
2020

2121
public struct S2 {
2222

23-
enum Error: ErrorProtocol {
24-
case IllegalArgumentException
25-
}
26-
2723
// Together these flags define a cell orientation. If SWAP_MASK
2824
// is true, then canonical traversal order is flipped around the
2925
// diagonal (i.e. i and j are swapped with each other). If
@@ -68,8 +64,8 @@ public struct S2 {
6864

6965
- Returns: A bit mask containing some combination of {@link #SWAP_MASK} and {@link #INVERT_MASK}.
7066
*/
71-
public static func posToOrientation(position: Int) throws -> Int {
72-
guard 0 <= position && position < 4 else { throw Error.IllegalArgumentException }
67+
public static func posToOrientation(position: Int) -> Int {
68+
precondition(0 <= position && position < 4)
7369
return posToOrientation[position]
7470
}
7571

@@ -93,9 +89,9 @@ public struct S2 {
9389

9490
- Returns: The IJ-index where `0->(0,0), 1->(0,1), 2->(1,0), 3->(1,1)`.
9591
*/
96-
public static func posToIJ(orientation: Int, position: Int) throws -> Int {
97-
guard 0 <= orientation && orientation < 4 else { throw Error.IllegalArgumentException }
98-
guard 0 <= position && position < 4 else { throw Error.IllegalArgumentException }
92+
public static func posToIJ(orientation: Int, position: Int) -> Int {
93+
precondition(0 <= orientation && orientation < 4)
94+
precondition(0 <= position && position < 4)
9995
return posToIJ[orientation][position]
10096
}
10197

@@ -119,9 +115,9 @@ public struct S2 {
119115

120116
- Returns: The position of the subcell in the Hilbert traversal, in the range [0,3].
121117
*/
122-
public static func toPos(orientation: Int, ijIndex: Int) throws -> Int {
123-
guard 0 <= orientation && orientation < 4 else { throw Error.IllegalArgumentException }
124-
guard 0 <= ijIndex && ijIndex < 4 else { throw Error.IllegalArgumentException }
118+
public static func toPos(orientation: Int, ijIndex: Int) -> Int {
119+
precondition(0 <= orientation && orientation < 4)
120+
precondition(0 <= ijIndex && ijIndex < 4)
125121
return ijToPos[orientation][ijIndex]
126122
}
127123

@@ -310,7 +306,7 @@ public struct S2 {
310306
let sa = b.angle(to: c)
311307
let sb = c.angle(to: a)
312308
let sc = a.angle(to: b)
313-
let s = 0.5 * (sa + sb + sc);
309+
let s = 0.5 * (sa + sb + sc)
314310
if s >= 3e-4 {
315311
// Consider whether Girard's formula might be more accurate.
316312
let s2 = s * s
@@ -324,12 +320,7 @@ public struct S2 {
324320
}
325321
}
326322
// Use l'Huilier's formula.
327-
return 4
328-
* atan(
329-
sqrt(
330-
max(0.0,
331-
tan(0.5 * s) * tan(0.5 * (s - sa)) * tan(0.5 * (s - sb))
332-
* tan(0.5 * (s - sc)))))
323+
return 4 * atan(sqrt(max(0.0, tan(0.5 * s) * tan(0.5 * (s - sa)) * tan(0.5 * (s - sb)) * tan(0.5 * (s - sc)))))
333324
}
334325

335326
/**
@@ -353,6 +344,77 @@ public struct S2 {
353344
return area(a: a, b: b, c: c) * Double(robustCCW(a: a, b: b, c: c))
354345
}
355346

347+
// About centroids:
348+
// ----------------
349+
//
350+
// There are several notions of the "centroid" of a triangle. First, there
351+
// // is the planar centroid, which is simply the centroid of the ordinary
352+
// (non-spherical) triangle defined by the three vertices. Second, there is
353+
// the surface centroid, which is defined as the intersection of the three
354+
// medians of the spherical triangle. It is possible to show that this
355+
// point is simply the planar centroid projected to the surface of the
356+
// sphere. Finally, there is the true centroid (mass centroid), which is
357+
// defined as the area integral over the spherical triangle of (x,y,z)
358+
// divided by the triangle area. This is the point that the triangle would
359+
// rotate around if it was spinning in empty space.
360+
//
361+
// The best centroid for most purposes is the true centroid. Unlike the
362+
// planar and surface centroids, the true centroid behaves linearly as
363+
// regions are added or subtracted. That is, if you split a triangle into
364+
// pieces and compute the average of their centroids (weighted by triangle
365+
// area), the result equals the centroid of the original triangle. This is
366+
// not true of the other centroids.
367+
//
368+
// Also note that the surface centroid may be nowhere near the intuitive
369+
// "center" of a spherical triangle. For example, consider the triangle
370+
// with vertices A=(1,eps,0), B=(0,0,1), C=(-1,eps,0) (a quarter-sphere).
371+
// The surface centroid of this triangle is at S=(0, 2*eps, 1), which is
372+
// within a distance of 2*eps of the vertex B. Note that the median from A
373+
// (the segment connecting A to the midpoint of BC) passes through S, since
374+
// this is the shortest path connecting the two endpoints. On the other
375+
// hand, the true centroid is at M=(0, 0.5, 0.5), which when projected onto
376+
// the surface is a much more reasonable interpretation of the "center" of
377+
// this triangle.
378+
379+
/**
380+
Return the centroid of the planar triangle ABC. This can be normalized to
381+
unit length to obtain the "surface centroid" of the corresponding spherical
382+
triangle, i.e. the intersection of the three medians. However, note that
383+
for large spherical triangles the surface centroid may be nowhere near the
384+
intuitive "center" (see example above).
385+
*/
386+
public static func planarCentroid(a: S2Point, b: S2Point, c: S2Point) -> S2Point {
387+
return S2Point(x: (a.x + b.x + c.x) / 3.0, y: (a.y + b.y + c.y) / 3.0, z: (a.z + b.z + c.z) / 3.0)
388+
}
389+
390+
/**
391+
Returns the true centroid of the spherical triangle ABC multiplied by the
392+
signed area of spherical triangle ABC. The reasons for multiplying by the
393+
signed area are (1) this is the quantity that needs to be summed to compute
394+
the centroid of a union or difference of triangles, and (2) it's actually
395+
easier to calculate this way.
396+
*/
397+
public static func trueCentroid(a: S2Point, b: S2Point, c: S2Point) -> S2Point {
398+
// I couldn't find any references for computing the true centroid of a
399+
// spherical triangle... I have a truly marvellous demonstration of this
400+
// formula which this margin is too narrow to contain :)
401+
402+
// assert (isUnitLength(a) && isUnitLength(b) && isUnitLength(c));
403+
let sina = b.crossProd(c).norm
404+
let sinb = c.crossProd(a).norm
405+
let sinc = a.crossProd(b).norm
406+
let ra = (sina == 0) ? 1 : (asin(sina) / sina)
407+
let rb = (sinb == 0) ? 1 : (asin(sinb) / sinb)
408+
let rc = (sinc == 0) ? 1 : (asin(sinc) / sinc)
409+
410+
// Now compute a point M such that M.X = rX * det(ABC) / 2 for X in A,B,C.
411+
let x = S2Point(x: a.x, y: b.x, z: c.x)
412+
let y = S2Point(x: a.y, y: b.y, z: c.y)
413+
let z = S2Point(x: a.z, y: b.z, z: c.z)
414+
let r = S2Point(x: ra, y: rb, z: rc)
415+
return S2Point(x: 0.5 * y.crossProd(z).dotProd(r), y: 0.5 * z.crossProd(x).dotProd(r), z: 0.5 * x.crossProd(y).dotProd(r))
416+
}
417+
356418
/**
357419
Return true if the points A, B, C are strictly counterclockwise. Return
358420
false if the points are clockwise or colinear (i.e. if they are all
@@ -632,6 +694,16 @@ public struct S2 {
632694
return (robustCCW(a: a, b: b, c: c) > 0) ? outAngle : -outAngle
633695
}
634696

697+
/// Return true if two points are within the given distance of each other (mainly useful for testing).
698+
public static func approxEquals(a: S2Point, b: S2Point, maxError: Double = 1e-15) -> Bool {
699+
return a.angle(to: b) <= maxError
700+
}
701+
702+
/// Return true if two points are within the given distance of each other (mainly useful for testing).
703+
public static func approxEquals(a: Double, b: Double, maxError: Double = 1e-15) -> Bool {
704+
return abs(a - b) <= maxError
705+
}
706+
635707
// Don't instantiate
636708
private init() { }
637709

Sources/S2AreaCentroid.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@
1616
public struct S2AreaCentroid {
1717

1818
public let area: Double
19-
public let centroid: S2Point?
19+
public let centroid: S2Point
2020

21-
public init(area: Double, centroid: S2Point? = nil) {
21+
public init(area: Double, centroid: S2Point) {
2222
self.area = area
2323
self.centroid = centroid
2424
}

Sources/S2Cell.swift

Lines changed: 64 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -36,16 +36,16 @@ public struct S2Cell: S2Region, Equatable {
3636
}
3737

3838
/// An S2Cell always corresponds to a particular S2CellId. The other constructors are just convenience methods.
39-
public init(id: S2CellId) {
40-
cellId = id
39+
public init(cellId: S2CellId) {
40+
self.cellId = cellId
4141

4242
var i = 0
4343
var j = 0
4444
var mOrientation: Int? = 0
4545

46-
face = UInt8(id.toFaceIJOrientation(i: &i, j: &j, orientation: &mOrientation))
46+
face = UInt8(cellId.toFaceIJOrientation(i: &i, j: &j, orientation: &mOrientation))
4747
orientation = UInt8(mOrientation!)
48-
level = UInt8(id.level)
48+
level = UInt8(cellId.level)
4949

5050
let cellSize = 1 << (S2CellId.maxLevel - Int(level))
5151
var _uv: [[Double]] = [[0, 0], [0, 0]]
@@ -61,16 +61,16 @@ public struct S2Cell: S2Region, Equatable {
6161

6262
// This is a static method in order to provide named parameters.
6363
public init(face: Int, pos: UInt8, level: Int) {
64-
self.init(id: S2CellId(face: face, pos: Int64(pos), level: level))
64+
self.init(cellId: S2CellId(face: face, pos: Int64(pos), level: level))
6565
}
6666

6767
// Convenience methods.
6868
public init(point: S2Point) {
69-
self.init(id: S2CellId(point: point))
69+
self.init(cellId: S2CellId(point: point))
7070
}
7171

7272
public init(latlng: S2LatLng) {
73-
self.init(id: S2CellId(latlng: latlng))
73+
self.init(cellId: S2CellId(latlng: latlng))
7474
}
7575

7676
public var isLeaf: Bool {
@@ -121,7 +121,7 @@ public struct S2Cell: S2Region, Equatable {
121121

122122
except that it is more than two times faster.
123123
*/
124-
public func subdivide() throws -> [S2Cell] {
124+
public func subdivide() -> [S2Cell] {
125125
// This function is equivalent to just iterating over the child cell ids
126126
// and calling the S2Cell constructor, but it is about 2.5 times faster.
127127

@@ -136,15 +136,15 @@ public struct S2Cell: S2Region, Equatable {
136136
for pos in 0 ..< 4 {
137137

138138
var _uv: [[Double]] = [[0, 0], [0, 0]]
139-
let ij = try S2.posToIJ(orientation: Int(orientation), position: pos)
139+
let ij = S2.posToIJ(orientation: Int(orientation), position: pos)
140140

141141
for d in 0 ..< 2 {
142142
// The dimension 0 index (i/u) is in bit 1 of ij.
143143
let m = 1 - ((ij >> (1 - d)) & 1)
144144
_uv[d][m] = uvMid.get(index: d)
145145
_uv[d][1 - m] = uv[d][1 - m]
146146
}
147-
let child = try S2Cell(cellId: id, face: face, level: level + 1, orientation: orientation ^ UInt8(S2.posToOrientation(position: pos)), uv: _uv)
147+
let child = S2Cell(cellId: id, face: face, level: level + 1, orientation: orientation ^ UInt8(S2.posToOrientation(position: pos)), uv: _uv)
148148
children.append(child)
149149

150150
id = id.next()
@@ -197,6 +197,57 @@ public struct S2Cell: S2Region, Equatable {
197197
return uvPoint.x >= uv[0][0] && uvPoint.x <= uv[0][1] && uvPoint.y >= uv[1][0] && uvPoint.y <= uv[1][1]
198198
}
199199

200+
/**
201+
* Return the average area for cells at the given level.
202+
*/
203+
public static func averageArea(level: Int) -> Double {
204+
return S2Projections.avgArea.getValue(level: level)
205+
}
206+
207+
/**
208+
Return the average area of cells at this level. This is accurate to within
209+
a factor of 1.7 (for S2_QUADRATIC_PROJECTION) and is extremely cheap to compute.
210+
*/
211+
public var averageArea: Double {
212+
return S2Cell.averageArea(level: Int(level))
213+
}
214+
215+
/**
216+
Return the approximate area of this cell. This method is accurate to within
217+
3% percent for all cell sizes and accurate to within 0.1% for cells at
218+
level 5 or higher (i.e. 300km square or smaller). It is moderately cheap to compute.
219+
*/
220+
public var approxArea: Double {
221+
// All cells at the first two levels have the same area.
222+
if level < 2 { return averageArea }
223+
224+
// First, compute the approximate area of the cell when projected
225+
// perpendicular to its normal. The cross product of its diagonals gives
226+
// the normal, and the length of the normal is twice the projected area.
227+
let flatArea = 0.5 * (getVertex(2) - getVertex(0)).crossProd((getVertex(3) - getVertex(1))).norm
228+
229+
// Now, compensate for the curvature of the cell surface by pretending
230+
// that the cell is shaped like a spherical cap. The ratio of the
231+
// area of a spherical cap to the area of its projected disc turns out
232+
// to be 2 / (1 + sqrt(1 - r*r)) where "r" is the radius of the disc.
233+
// For example, when r=0 the ratio is 1, and when r=1 the ratio is 2.
234+
// Here we set Pi*r*r == flat_area to find the equivalent disc.
235+
return flatArea * 2 / (1 + sqrt(1 - min(M_1_PI * flatArea, 1.0)))
236+
}
237+
238+
/**
239+
Return the area of this cell as accurately as possible. This method is more
240+
expensive but it is accurate to 6 digits of precision even for leaf cells
241+
(whose area is approximately 1e-18).
242+
*/
243+
public var exactArea: Double {
244+
let v0 = getVertex(0)
245+
let v1 = getVertex(1)
246+
let v2 = getVertex(2)
247+
let v3 = getVertex(3)
248+
return S2.area(a: v0, b: v1, c: v2) + S2.area(a: v0, b: v2, c: v3)
249+
}
250+
200251
////////////////////////////////////////////////////////////////////////
201252
// MARK: S2Region
202253
////////////////////////////////////////////////////////////////////////
@@ -236,7 +287,7 @@ public struct S2Cell: S2Region, Equatable {
236287
let i = S2Projections.getUAxis(face: Int(face)).z == 0 ? (u < 0 ? 1 : 0) : (u > 0 ? 1 : 0)
237288
let j = S2Projections.getVAxis(face: Int(face)).z == 0 ? (v < 0 ? 1 : 0) : (v > 0 ? 1 : 0)
238289

239-
var lat = R1Interval.fromPointPair(p1: getLatitude(i: i, j: j), p2: getLatitude(i: 1 - i, j: 1 - j))
290+
var lat = R1Interval(p1: getLatitude(i: i, j: j), p2: getLatitude(i: 1 - i, j: 1 - j))
240291
lat = lat.expanded(radius: S2Cell.maxError).intersection(with: S2LatLngRect.fullLat)
241292
if (lat.lo == -M_PI_2 || lat.hi == M_PI_2) {
242293
return S2LatLngRect(lat: lat, lng: S1Interval.full)
@@ -264,11 +315,11 @@ public struct S2Cell: S2Region, Equatable {
264315
}
265316

266317
public func contains(cell: S2Cell) -> Bool {
267-
return false
318+
return cellId.contains(other: cell.cellId)
268319
}
269320

270321
public func mayIntersect(cell: S2Cell) -> Bool {
271-
return false
322+
return cellId.intersects(other: cell.cellId)
272323
}
273324

274325
// Return the latitude or longitude of the cell vertex given by (i,j),

0 commit comments

Comments
 (0)