Skip to content

Commit 00f7e45

Browse files
committed
feat: support flags and masks
1 parent 262c72e commit 00f7e45

File tree

10 files changed

+302
-216
lines changed

10 files changed

+302
-216
lines changed

README.md

Lines changed: 122 additions & 154 deletions
Original file line numberDiff line numberDiff line change
@@ -1,228 +1,196 @@
11
# 1 SwiftGeographicLib
2-
Ready-to-use Swift wrapper for geodesic methods from the renowned library GeographicLib. The Swift wrapper is built on top of the C version of the library.
3-
2+
3+
Ready-to-use Swift wrapper for the geodesic routines from the renowned [GeographicLib](https://geographiclib.sourceforge.io/).
4+
Under the hood it calls the C library, but exposes a Swifty, type-safe API.
5+
46
## 1.1 Installation
5-
You can install the library using Swift Package Manager. Add the following line to your `Package.swift` dependencies array:
7+
8+
Use the Swift Package Manager. In your `Package.swift`:
9+
10+
```swift
11+
dependencies: [
12+
.package(
13+
url: "https://github.com/sindreoyen/SwiftGeographicLib.git",
14+
.upToNextMinor(from: "1.0.1")
15+
)
16+
]
17+
```
18+
19+
## 1.2 Masks & Flags
20+
21+
SwiftGeographicLib provides two `OptionSet` types to mirror the C bitmasks:
622

723
```swift
8-
.package(url: "https://github.com/sindreoyen/SwiftGeographicLib.git", .upToNextMinor(from: "1.0.1"))
24+
/// geod_mask values
25+
public struct GeodesicMask: OptionSet {
26+
public let rawValue: UInt32
27+
public init(rawValue: UInt32) { self.rawValue = rawValue }
28+
29+
public static let none = GeodesicMask([])
30+
public static let latitude = GeodesicMask(rawValue: 1<<7)
31+
public static let longitude = GeodesicMask(rawValue: (1<<8)|(1<<3))
32+
public static let azimuth = GeodesicMask(rawValue: 1<<9)
33+
public static let distance = GeodesicMask(rawValue: (1<<10)|(1<<0))
34+
public static let distanceIn = GeodesicMask(rawValue: (1<<11)|(1<<0)|(1<<1))
35+
public static let reducedLength = GeodesicMask(rawValue: (1<<12)|(1<<0)|(1<<2))
36+
public static let scale = GeodesicMask(rawValue: (1<<13)|(1<<0)|(1<<2))
37+
public static let area = GeodesicMask(rawValue: (1<<14)|(1<<4))
38+
public static let all: GeodesicMask = [
39+
.latitude, .longitude, .azimuth,
40+
.distance, .distanceIn, .reducedLength,
41+
.scale, .area
42+
]
43+
}
44+
45+
/// geod_flags values
46+
public struct GeodesicFlags: OptionSet {
47+
public let rawValue: UInt32
48+
public init(rawValue: UInt32) { self.rawValue = rawValue }
49+
50+
public static let none = GeodesicFlags([])
51+
public static let arcMode = GeodesicFlags(rawValue: 1<<0)
52+
public static let unrollLong = GeodesicFlags(rawValue: 1<<15)
53+
}
954
```
1055

56+
Place those in `Sources/SwiftGeographicLib/Masks.swift` (or similar).
57+
58+
---
59+
1160
# 2 SwiftGeographicLib Usage Guide
1261

13-
This library provides Swift-friendly wrappers around the C geodesic routines from GeographicLib. It lets you compute accurate distances, bearings, and areas on an ellipsoidal model of the Earth (default WGS-84) via three main APIs:
62+
This library gives you three main APIs to solve geodesic problems on an ellipsoid:
1463

15-
1. **`Geodesic`**one-off static functions for direct/inverse problems
16-
2. **`GeodesicLine`** – incremental “line” API for stepping along a geodesic
17-
3. **`GeodesicPolygon`** – accumulate points or edges to compute perimeter & area
64+
1. **`Geodesic`** – static direct/inverse calls
65+
2. **`GeodesicLine`** – incremental “walk a geodesic” API
66+
3. **`GeodesicPolygon`** – accumulate points/edges to get perimeter & area
1867

1968
---
2069

2170
## 2.1 Geodesic
2271

2372
### `direct(from:distance:azimuth:geodesic:)`
2473

25-
Solve the **direct problem** (point + azimuth + distance ⇒ endpoint):
26-
2774
```swift
28-
let start = (lat: 40.64, lon: -73.78) // JFK Airport
29-
let d = 10_000_000.0 // 10 000 km
30-
let azi = 45.0 // north-east
31-
let dest = Geodesic.direct(from: start,
32-
distance: d,
33-
azimuth: azi)
34-
// dest.latitude, dest.longitude hold the endpoint coordinates
75+
let start = (lat: 40.64, lon: -73.78) // JFK
76+
let dest = Geodesic.direct(
77+
from: start,
78+
distance: 10_000_000, // 10 000 km
79+
azimuth: 45.0 // north-east
80+
)
81+
// dest.latitude, dest.longitude
3582
```
3683

3784
### `generalDirect(from:azimuth:flags:s12_a12:geodesic:)`
3885

39-
Solve the **general direct problem**, returning extra quantities:
40-
4186
```swift
4287
let (lat2, lon2, azi2,
4388
s12, m12, M12, M21, S12,
4489
a12) = Geodesic.generalDirect(
45-
from: start,
46-
azimuth: azi,
47-
flags: GEOD_ALL, // request all outputs
48-
s12_a12: d
49-
)
50-
51-
// • lat2, lon2, azi2: endpoint lat/lon/bearing
52-
// • s12 : distance (m)
53-
// • m12 : reduced length (m)
54-
// • M12, M21 : geodesic scales (dimensionless)
55-
// • S12 : area under the geodesic (m²)
56-
// • a12 : arc length (°)
90+
from: start,
91+
azimuth: 45,
92+
flags: .all, // all outputs
93+
s12_a12: 10e6
94+
)
5795
```
5896

59-
---
60-
6197
### `inverse(between:and:geodesic:)`
6298

63-
Solve the **inverse problem** (two points ⇒ distance & azimuths):
64-
6599
```swift
66-
let a = (lat: 40.64, lon: -73.78) // JFK
67-
let b = (lat: 1.36, lon: 103.99) // Singapore Changi
68-
let (s, fwd, rev) = Geodesic.inverse(between: a, and: b)
69-
// s = distance in meters
70-
// fwd = forward azimuth at A (°)
71-
// rev = forward azimuth at B (°)
100+
let a = (lat: 40.64, lon: -73.78)
101+
let b = (lat: 1.36, lon: 103.99)
102+
let (distance, fwd, rev) = Geodesic.inverse(between: a, and: b)
72103
```
73104

74105
### `generalInverse(between:and:geodesic:)`
75106

76-
Get extended inverse outputs:
77-
78107
```swift
79108
let (a12, s12, azi1, azi2, m12, M12, M21, S12) =
80-
Geodesic.generalInverse(between: a, and: b)
81-
// same fields as generalDirect but for the inverse problem
109+
Geodesic.generalInverse(between: a, and: b)
82110
```
83111

84112
---
85113

86114
## 2.2 GeodesicLine
87115

88-
Use `GeodesicLine` when you want to **walk** along a geodesic:
116+
Use this when you want to step along a geodesic:
89117

90118
### Initialize
91119

92-
- **Basic** (no endpoint known yet):
93-
94-
```swift
95-
let caps: UInt32 = GEOD_DISTANCE_IN // allow distance as input
96-
| GEOD_LONGITUDE // allow computing longitude
97-
let line = GeodesicLine(
98-
from: (lat: 40.64, lon: -73.78),
99-
azimuth: 45.0,
100-
caps: caps
101-
)
102-
```
103-
104-
- **Direct** (endpoint fixed at construction):
105-
106-
```swift
107-
let line2 = GeodesicLine(
108-
directFrom: (lat: 40.64, lon: -73.78),
109-
azimuth: 45.0,
110-
distance: 10_000_000,
111-
caps: GEOD_ALL
112-
)
113-
```
114-
115-
### Query Points
116-
117-
- **Position by distance**
118-
(requires `GEOD_DISTANCE_IN` + `GEOD_LONGITUDE` in `caps`)
119-
120-
```swift
121-
let pt = line.position(distance: 1_000_000)
122-
// pt.latitude, pt.longitude
123-
```
124-
125-
- **General position**
126-
to retrieve all requested quantities for a given distance or arc:
127-
128-
```swift
129-
let (lat, lon, azi2, s12, m12, M12, M21, S12, a12) =
130-
line.genPosition(
131-
flags: GEOD_ARCMODE, // or GEOD_NOFLAGS
132-
s12_a12: 100.0 // meters or degrees
133-
)
134-
```
135-
136-
### Change the “third point”
137-
138-
After a basic init (without endpoint):
139-
140-
- **Set by distance**:
141-
```swift
142-
line.setDistance(500_000)
143-
```
144-
- **Set by arc or distance**:
145-
```swift
146-
line.genSetDistance(flags: GEOD_ARCMODE,
147-
s13_a13: 4.5) // arc-length in degrees
148-
```
149-
150-
---
151-
152-
## 2.3 GeodesicPolygon
120+
```swift
121+
import SwiftGeographicLib
122+
123+
// 1) Basic init (no endpoint pinned)
124+
let caps: GeodesicMask = [.distanceIn, .longitude]
125+
let line = GeodesicLine(
126+
from: (lat: 40.64, lon: -73.78),
127+
azimuth: 45.0,
128+
caps: caps
129+
)
153130

154-
Compute **perimeter** & **area** by streaming in vertices or edges.
131+
// 2) Direct init (endpoint fixed)
132+
let line2 = GeodesicLine(
133+
directFrom: (lat: 40.64, lon: -73.78),
134+
azimuth: 45.0,
135+
distance: 10_000_000,
136+
caps: .all
137+
)
138+
```
155139

156-
### Initialize
140+
### Query positions
157141

158142
```swift
159-
// polygon: area + perimeter
160-
let poly = GeodesicPolygon(polyline: false)
143+
// Simple: by distance
144+
let pt1 = line.position(distance: 1_000_000)
161145

162-
// or a polyline: only perimeter
163-
let pl = GeodesicPolygon(polyline: true)
146+
// Full: all outputs
147+
let (lat, lon, azi2, s12, m12, M12, M21, S12, a12) =
148+
line.genPosition(flags: .arcMode, s12_a12: 100.0)
164149
```
165150

166-
### Add by point
151+
### Adjust “third point
167152

168153
```swift
169-
poly.addPoint((lat: 0.0, lon: 0.0))
170-
poly.addPoint((lat: 0.0, lon: 1.0))
171-
poly.addPoint((lat: 1.0, lon: 1.0))
154+
line.setDistance(500_000)
155+
line.genSetDistance(flags: .arcMode, s13_a13: 4.5)
172156
```
173157

174-
### Add by edge
158+
---
175159

176-
```swift
177-
// from the last point, go 90° for 111 km
178-
poly.addEdge(azimuth: 90.0, distance: 111_000)
179-
```
160+
## 2.3 GeodesicPolygon
180161

181-
### Compute results
162+
Accumulate vertices or edges to get perimeter & area:
182163

183164
```swift
184-
let (count, area, perimeter) = poly.compute(
185-
reverse: false,
186-
signed: true
187-
)
188-
// • count : #points/edges processed
189-
// • area : area in m² (0 for polyline)
190-
// • perimeter : length in m
165+
let poly = GeodesicPolygon(polyline: false)
166+
poly.addPoint((lat: 0.0, lon: 0.0))
167+
poly.addEdge(azimuth: 90, distance: 111_000)
168+
let (count, area, perimeter) = poly.compute(reverse: false, signed: true)
191169
```
192170

193171
### “Test” methods
194172

195-
Compute what _would_ happen if you added one more point or edge, **without** modifying the internal state:
196-
197173
```swift
198-
let (n, testArea, testPerim) =
199-
poly.testPoint((lat: 1.5, lon: 0.5))
200-
201-
let (m, testArea2, testPerim2) =
202-
poly.testEdge(azimuth: 180.0, distance: 50_000)
174+
let (_, testArea, testPerim) =
175+
poly.testPoint((lat: 1.5, lon: 0.5))
176+
let (_, testArea2, testPerim2) =
177+
poly.testEdge(azimuth: 180, distance: 50_000)
178+
// none of these mutate `poly`—a fresh compute() still returns the original.
203179
```
204180

205-
Internal state remains unchanged, so a subsequent `poly.compute()` returns the same original `(area, perimeter)`.
206-
207-
---
208-
209-
### One-Line Polygon Area
210-
211-
For quick closed‐polygon area/perimeter in one call:
181+
### Quick one-liner
212182

213183
```swift
214-
let coords = [
215-
(lat: 0.0, lon: 0.0),
216-
(lat: 0.0, lon: 1.0),
217-
(lat: 1.0, lon: 1.0),
218-
(lat: 1.0, lon: 0.0)
219-
]
220-
let (area, perimeter) = GeodesicPolygon.area(of: coords)
184+
let coords = [(lat:0, lon:0), (lat:0, lon:1), (lat:1, lon:1), (lat:1, lon:0)]
185+
let (area, peri) = GeodesicPolygon.area(of: coords)
221186
```
222187

188+
> **Tip:** All APIs default to WGS-84 unless you supply another `GeodGeodesic` model.
189+
223190
---
224191

225-
> **Tip:** All routines default to WGS-84 unless you pass a custom `GeodGeoDesic` model when calling.
226-
227192
# 3 Contributing
228-
If you encounter any bugs or want to suggest new features, please submit a pull request. You can also report any issues, and I will address them when I have the opportunity. All contributions are welcome! Please note that all implemented methods should be organized in the appropriate folder locations and tested using the latest testing framework, `Swift Testing`.
193+
194+
Feel free to open issues, suggest features, or submit pull requests.
195+
All methods live in `Sources/SwiftGeographicLib` and tests in `Tests/SwiftGeographicLibTests`.
196+
Thank you for your contributions!

0 commit comments

Comments
 (0)