Skip to content

Commit 25a2e22

Browse files
authored
Add annular segments (#6)
* Add function for annular segments (#4) * Update example app to use remote dependency * Embed image in repo (#2) * Add Swiftlint * Auto fix linting * Fix manual linting errors * Linting for annular segments * Add action
1 parent b3d351e commit 25a2e22

29 files changed

Lines changed: 435 additions & 74 deletions

.github/workflows/swiftlint.yml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
name: SwiftLint
2+
3+
on:
4+
pull_request:
5+
paths:
6+
- '.github/workflows/swiftlint.yml'
7+
- '.swiftlint.yml'
8+
- '**/*.swift'
9+
workflow_dispatch:
10+
11+
jobs:
12+
SwiftLint:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- uses: actions/checkout@v1
16+
- name: GitHub Action for SwiftLint
17+
uses: norio-nomura/action-swiftlint@3.2.1
18+
- name: GitHub Action for SwiftLint with --strict
19+
uses: norio-nomura/action-swiftlint@3.2.1
20+
with:
21+
args: --strict

.swiftlint.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
excluded:
2+
- .build
3+
- Tests/ForeFlightKMLTests/UserMapShapesSampleIndividualTests.swift
4+
- Tests/ForeFlightKMLTests/UserMapShapesSampleFullTest.swift
5+
- Example/ForeFlightKMLDemo/
6+
identifier_name:
7+
min_length: 1
8+
max_length: 40

Example/ForeFlightKMLDemo/KML/KMLGenerator.swift

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,23 @@ import ForeFlightKML
66
enum KMLGenerator {
77
static func generateCircleKML(center: CLLocationCoordinate2D, radiusMeters: Double) -> String {
88
let builder = ForeFlightKMLBuilder(documentName: "Foreflight KML Demo")
9-
9+
1010
let centerCoordinate = Coordinate(latitude: center.latitude, longitude: center.longitude)
11-
12-
builder.addLineCircle(name: "Circle", center: centerCoordinate, radiusMeters: radiusMeters, numberOfPoints: 100, style: PathStyle(color: .black))
1311

14-
builder.addPolygonCircle(name: "Filled Circle" ,center: centerCoordinate, radiusMeters: radiusMeters * 2, style: PolygonStyle(outlineColor: .black, fillColor: .warning.withAlpha(0.3)))
15-
12+
builder.addLineCircle(
13+
name: "Circle",
14+
center: centerCoordinate,
15+
radiusMeters: radiusMeters,
16+
numberOfPoints: 100,
17+
style: PathStyle(color: .black)
18+
)
19+
20+
builder.addPolygonCircle(
21+
name: "Filled Circle",
22+
center: centerCoordinate,
23+
radiusMeters: radiusMeters * 2,
24+
style: PolygonStyle(outlineColor: .black, fillColor: .warning.withAlpha(0.3)))
25+
1626
return builder.build()
1727
}
1828

Example/ForeFlightKMLDemo/Map/MapViewRepresentable.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ struct MapViewRepresentable: UIViewRepresentable {
1313
map.delegate = context.coordinator
1414
map.showsUserLocation = true
1515
map.pointOfInterestFilter = .excludingAll
16-
map.setRegion(MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 51.750188, longitude: -1.581566), latitudinalMeters: 2000, longitudinalMeters: 2000),animated: false)
16+
map.setRegion(MKCoordinateRegion(
17+
center: CLLocationCoordinate2D(latitude: 51.750188, longitude: -1.581566), latitudinalMeters: 2000, longitudinalMeters: 2000),
18+
animated: false)
1719

1820
let tap = UITapGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.handleTap(_:)))
1921
tap.numberOfTapsRequired = 1

Example/ForeFlightKMLDemo/Views/ContentView.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,10 @@ struct ContentView: View {
5050
lastTapCoordinate = coord
5151

5252
let kml = KMLGenerator.generateCircleKML(center: coord, radiusMeters: defaultRadiusMeters)
53-
53+
5454
let dateFormatter = DateFormatter()
5555
dateFormatter.dateFormat = "yyyyMMdd'T'HHmmss'Z'"
56-
56+
5757
let tmpURL = FileManager.default.temporaryDirectory.appendingPathComponent("\(dateFormatter.string(from: Date())).kml")
5858
do {
5959
try kml.data(using: .utf8)?.write(to: tmpURL)

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,6 @@ let package = Package(
2323
.testTarget(
2424
name: "ForeFlightKMLTests",
2525
dependencies: ["ForeFlightKML"]
26-
),
26+
)
2727
]
2828
)

Sources/ForeFlightKML/CoreElements/LinearRing.swift

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import GeodesySpherical
22

3-
/// Defines a closed line string, typically the outer boundary of a Polygon. Optionally, a LinearRing can also be used as the inner boundary of a Polygon to create holes in the Polygon. A Polygon can contain multiple <LinearRing> elements used as inner boundaries.
3+
/// Defines a closed line string, typically the outer boundary of a Polygon.
4+
/// Optionally, a LinearRing can also be used as the inner boundary of a Polygon to create holes in the Polygon.
5+
/// A Polygon can contain multiple <LinearRing> elements used as inner boundaries.
46
/// Note: LinearRing does NOT support altitudeMode - that is specified at the Polygon level.
57
/// However, individual coordinates can still have altitude values.
68
public struct LinearRing: CoordinateContainer {
@@ -9,7 +11,7 @@ public struct LinearRing: CoordinateContainer {
911
public var altitude: Double?
1012
/// LinearRing doesn't define altitude mode (handled by parent Polygon)
1113
public var altitudeMode: AltitudeMode? { nil }
12-
public var tessellate: Bool? = nil
14+
public var tessellate: Bool?
1315

1416
/// Create a new linear ring.
1517
/// - Parameters:
@@ -21,8 +23,7 @@ public struct LinearRing: CoordinateContainer {
2123

2224
// Auto-close the ring if needed
2325
if let first = coordinates.first, let last = coordinates.last,
24-
first.latitude != last.latitude || first.longitude != last.longitude
25-
{
26+
first.latitude != last.latitude || first.longitude != last.longitude {
2627
self.coordinates = coordinates + [first]
2728
} else {
2829
self.coordinates = coordinates

Sources/ForeFlightKML/ForeFlightKML+Convenience.swift

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,4 +245,50 @@ extension ForeFlightKMLBuilder {
245245
let placemark = Placemark(name: name, geometry: segment, style: style)
246246
return addPlacemark(placemark)
247247
}
248+
249+
/// Add a filled annular (ring) segment polygon.
250+
/// This creates a segment between two radii, excluding the inner circle area.
251+
/// - Parameters:
252+
/// - name: Display name in ForeFlight (optional)
253+
/// - center: Center point of the segment
254+
/// - innerRadius: Inner radius in meters (the "hole" size)
255+
/// - outerRadius: Outer radius in meters
256+
/// - startAngle: Starting angle in degrees (0° = North, clockwise)
257+
/// - endAngle: Ending angle in degrees (0° = North, clockwise)
258+
/// - numberOfPoints: Number of points for each arc (default: 64)
259+
/// - altitude: Altitude in meters (optional)
260+
/// - tessellate: Whether to follow ground contours (default: false)
261+
/// - style: Polygon style defining outline and optional fill (optional)
262+
/// - Returns: Self for method chaining
263+
@discardableResult
264+
public func addPolygonAnnularSegment(
265+
name: String? = nil,
266+
center: Coordinate,
267+
innerRadius: Double,
268+
outerRadius: Double,
269+
startAngle: Double,
270+
endAngle: Double,
271+
numberOfPoints: Int = 64,
272+
altitude: Double? = nil,
273+
tessellate: Bool = false,
274+
style: PolygonStyle? = nil
275+
) -> Self {
276+
precondition(innerRadius > 0, "Inner radius must be positive")
277+
precondition(outerRadius > innerRadius, "Outer radius must be greater than inner radius")
278+
precondition(numberOfPoints >= 3, "Need at least 3 segments for an annular segment")
279+
280+
let segment = PolygonAnnularSegment(
281+
center: center,
282+
innerRadius: innerRadius,
283+
outerRadius: outerRadius,
284+
startAngle: startAngle,
285+
endAngle: endAngle,
286+
numberOfPoints: numberOfPoints,
287+
altitude: altitude,
288+
tessellate: tessellate
289+
)
290+
291+
let placemark = Placemark(name: name, geometry: segment, style: style)
292+
return addPlacemark(placemark)
293+
}
248294
}

Sources/ForeFlightKML/ForeFlightKML.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public final class ForeFlightKMLBuilder {
1616
"xmlns": "http://www.opengis.net/kml/2.2",
1717
"xmlns:gx": "http://www.google.com/kml/ext/2.2",
1818
"xmlns:kml": "http://www.opengis.net/kml/2.2",
19-
"xmlns:atom": "http://www.w3.org/2005/Atom",
19+
"xmlns:atom": "http://www.w3.org/2005/Atom"
2020
]
2121

2222
/// Create a new builder.
@@ -80,7 +80,7 @@ public final class ForeFlightKMLBuilder {
8080
}
8181

8282
/// Produce the KML document as `Data` using the given text encoding.
83-
/// - Parameter encoding: The `String.Encoding` to use when converting the KML string into data. Defaults to `.utf8`.
83+
/// - Parameter encoding: The `String.Encoding` to use when converting the KML string into data.
8484
/// - Returns: `Data` containing the encoded KML, or an empty `Data` if encoding fails.
8585
public func kmlData(encoding: String.Encoding = .utf8) -> Data {
8686
return kmlString().data(using: encoding) ?? Data()
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import GeodesySpherical
2+
3+
public struct PolygonAnnularSegment: KMLElement, AltitudeSupport {
4+
let polygon: Polygon
5+
6+
public var altitudeMode: AltitudeMode? { polygon.altitudeMode }
7+
public init(
8+
center: Coordinate,
9+
innerRadius: Double,
10+
outerRadius: Double,
11+
startAngle: Double,
12+
endAngle: Double,
13+
numberOfPoints: Int = 100,
14+
altitude: Double? = nil,
15+
altitudeMode: AltitudeMode? = nil,
16+
tessellate: Bool? = nil
17+
) {
18+
let coordinates = SegmentGeometry.generateAnnularSegmentPoints(
19+
center: center,
20+
innerRadius: innerRadius,
21+
outerRadius: outerRadius,
22+
startAngle: startAngle,
23+
endAngle: endAngle,
24+
numberOfPoints: numberOfPoints
25+
)
26+
27+
let ring = LinearRing(coordinates: coordinates, altitude: altitude)
28+
self.polygon = Polygon(outer: ring, altitudeMode: altitudeMode, tessellate: tessellate)
29+
}
30+
31+
public func kmlString() -> String {
32+
return polygon.kmlString()
33+
}
34+
}

0 commit comments

Comments
 (0)