diff --git a/.github/workflows/restrict-main.yml b/.github/workflows/restrict-main.yml index d4316bd..f10a424 100644 --- a/.github/workflows/restrict-main.yml +++ b/.github/workflows/restrict-main.yml @@ -4,14 +4,23 @@ on: pull_request: branches: - main + # edited: re-check when an existing PR is retargeted to main. + types: [opened, synchronize, reopened, edited] + +permissions: {} jobs: check-branch: runs-on: ubuntu-latest steps: - name: Ensure PR comes from develop + # Passed through env, not ${{ }} in the script: branch names are set by + # whoever opens the PR, so interpolating one into the shell would run it. + env: + HEAD_REF: ${{ github.head_ref }} + HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }} run: | - if [ "${{ github.head_ref }}" != "develop" ]; then - echo "Only develop can be merged into main." + if [ "$HEAD_REF" != "develop" ] || [ "$HEAD_REPO" != "$GITHUB_REPOSITORY" ]; then + echo "Only develop can be merged into main (got '$HEAD_REF' from $HEAD_REPO)." exit 1 fi diff --git a/.github/workflows/swiftlint.yml b/.github/workflows/swiftlint.yml index 187f4e7..f9c002b 100644 --- a/.github/workflows/swiftlint.yml +++ b/.github/workflows/swiftlint.yml @@ -6,14 +6,14 @@ on: - '.github/workflows/swiftlint.yml' - '.swiftlint.yml' - '**/*.swift' - workflow_dispatch: + workflow_dispatch: jobs: SwiftLint: runs-on: ubuntu-latest + # Official SwiftLint image, pinned to match local development. + container: ghcr.io/realm/swiftlint:0.65.1 steps: - - uses: actions/checkout@v1 - - name: GitHub Action for SwiftLint with --strict - uses: norio-nomura/action-swiftlint@3.2.1 - with: - args: --fix --quiet + - uses: actions/checkout@v7 + - name: SwiftLint --strict + run: swiftlint lint --strict --reporter github-actions-logging diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..a3c77ff --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,45 @@ +name: Tests + +on: + pull_request: + branches: + - develop + - main + push: + branches: + - develop + - main + workflow_dispatch: + +concurrency: + group: tests-${{ github.ref }} + cancel-in-progress: true + +jobs: + test: + name: swift test (macOS) + iOS build + runs-on: macos-26 + timeout-minutes: 30 + steps: + - uses: actions/checkout@v7 + + # Package.swift needs Swift 6.2 tools, which ship with Xcode 26. + - uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: '26.x' + + - name: Toolchain + run: | + xcodebuild -version + swift --version + + # Tests use XMLDocument, which is macOS-only, so they run on macOS rather than iOS. + - name: Test (macOS) + run: swift test --parallel + + - name: Build (iOS Simulator) + run: | + xcodebuild build \ + -scheme ForeFlightKML \ + -destination 'generic/platform=iOS Simulator' \ + CODE_SIGNING_ALLOWED=NO diff --git a/.swiftlint.yml b/.swiftlint.yml index dde5b9b..24cdef2 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -1,7 +1,7 @@ excluded: - .build - - Tests/ForeFlightKMLTests/UserMapShapesSampleIndividualTests.swift - - Tests/ForeFlightKMLTests/UserMapShapesSampleFullTest.swift + - Tests/UserMapShapeTests/UserMapShapesSampleIndividualTests.swift + - Tests/UserMapShapeTests/UserMapShapesSampleFullTest.swift - Example/ForeFlightKMLDemo/ identifier_name: min_length: 1 diff --git a/Example/ForeFlightKMLDemo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Example/ForeFlightKMLDemo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 36f24aa..d417bbb 100644 --- a/Example/ForeFlightKMLDemo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Example/ForeFlightKMLDemo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -2,12 +2,12 @@ "originHash" : "9cb73a7eb66a94e9ff3b48c0d33c79d885ba74172d84e80b923fabe67536dcb9", "pins" : [ { - "identity" : "geodesy", + "identity" : "vincenty", "kind" : "remoteSourceControl", - "location" : "https://github.com/florianreinhart/Geodesy", + "location" : "https://github.com/Haelix-Code/Vincenty", "state" : { - "revision" : "c72d7ea459c6eee4d041272c61f84df61d850091", - "version" : "0.2.2" + "revision" : "e029255ef3313dcf77cecb4faf64cf94a0812608", + "version" : "0.0.1" } }, { diff --git a/Example/ForeFlightKMLDemo/KML/KMLGenerator.swift b/Example/ForeFlightKMLDemo/KML/KMLGenerator.swift index 573307a..2f37688 100644 --- a/Example/ForeFlightKMLDemo/KML/KMLGenerator.swift +++ b/Example/ForeFlightKMLDemo/KML/KMLGenerator.swift @@ -1,5 +1,4 @@ import Foundation -import GeodesySpherical import CoreLocation import ForeFlightKML diff --git a/Example/ForeFlightKMLDemo/Map/MapViewRepresentable.swift b/Example/ForeFlightKMLDemo/Map/MapViewRepresentable.swift index d54fe99..6d56d10 100644 --- a/Example/ForeFlightKMLDemo/Map/MapViewRepresentable.swift +++ b/Example/ForeFlightKMLDemo/Map/MapViewRepresentable.swift @@ -1,7 +1,6 @@ import SwiftUI import MapKit import CoreLocation -import GeodesySpherical import ForeFlightKML struct MapViewRepresentable: UIViewRepresentable { diff --git a/Package.resolved b/Package.resolved index a95a72e..ed5a83d 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,12 +1,13 @@ { + "originHash" : "17e50782305f6c29b65dd5cb050f0836dda7428e80849631e5782b62be7267e0", "pins" : [ { - "identity" : "geodesy", + "identity" : "vincenty", "kind" : "remoteSourceControl", - "location" : "https://github.com/florianreinhart/Geodesy", + "location" : "https://github.com/Haelix-Code/Vincenty", "state" : { - "revision" : "c72d7ea459c6eee4d041272c61f84df61d850091", - "version" : "0.2.2" + "revision" : "e029255ef3313dcf77cecb4faf64cf94a0812608", + "version" : "0.0.1" } }, { @@ -19,5 +20,5 @@ } } ], - "version" : 2 + "version" : 3 } diff --git a/Package.swift b/Package.swift index 03bc596..47f0fe4 100644 --- a/Package.swift +++ b/Package.swift @@ -1,9 +1,9 @@ -// swift-tools-version:5.7 +// swift-tools-version:6.2 import PackageDescription let package = Package( name: "ForeFlightKML", - platforms: [.iOS(.v15)], + platforms: [.iOS(.v15), .macOS(.v12)], products: [ .library( name: "ForeFlightKML", @@ -11,14 +11,14 @@ let package = Package( ) ], dependencies: [ - .package(url: "https://github.com/florianreinhart/Geodesy", .upToNextMajor(from: "0.2.2")), + .package(url: "https://github.com/Haelix-Code/Vincenty", .upToNextMinor(from: "0.0.1")), .package(url: "https://github.com/weichsel/ZIPFoundation.git", from: "0.9.0") ], targets: [ .target( name: "ForeFlightKML", dependencies: [ - .product(name: "Geodesy", package: "Geodesy"), + .product(name: "Vincenty", package: "Vincenty"), .product(name: "ZIPFoundation", package: "ZIPFoundation") ], resources: [ diff --git a/README.md b/README.md index ce0dc48..2a04f1a 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ This package provides a small, focused API for composing KML/KMZ documents suita - Compose `Placemark`s with `Point`, `LineString`, `Polygon` and derived geometry helpers (circles, arc sectors, etc.). - Create reusable styles (`Style`, `LineStyle`, `PolyStyle`, `IconStyle`, `LabelStyle`) and assign them to placemarks. - `ForeFlightKMLBuilder` collects placemarks and styles, emits a complete `kml` or `kmz` document. -- Thread-safe — `ForeFlightKMLBuilder` is `Sendable`, so KML generation can run off the main thread. +- Swift 6 concurrency-ready — geometry, styles, `Placemark` and `BuildResult` are `Sendable`, so KML generation can run off the main thread. - Lightweight — no UI code. ## Install @@ -30,7 +30,6 @@ See `/Tests/UserMapShapeTests/UserMapShapesSampleFullTest.swift` ```swift import ForeFlightKML -import GeodesySpherical let builder = ForeFlightKMLBuilder(documentName: "Airport with ATZ") @@ -120,13 +119,13 @@ Full public API surface is visible in the package sources. ## Performance - **Buffer-based generation** — KML is built using a single mutable `String` buffer rather than array concatenation, avoiding intermediate allocations. -- **Sendable** — `ForeFlightKMLBuilder` conforms to `Sendable`, allowing KML/KMZ generation to run on a background thread for a responsive UI. +- **Background generation** — build KML/KMZ off the main thread for a responsive UI. Geometry, styles, `Placemark` and `BuildResult` are `Sendable`. `ForeFlightKMLBuilder` deliberately isn't (it holds unsynchronised mutable state): create and build it within one task, or hand it to another task or actor and stop using it. - **Smart compression** — small documents (< 100 KB) skip DEFLATE compression in KMZ output, reducing overhead. - **Efficient geometry** — circle point generation uses direct arithmetic rather than `Measurement` conversions. ## Notes, conventions and gotchas -- **Coordinates order**: KML requires `longitude,latitude[,altitude]`. The public API accepts `Coordinate(latitude:..., longitude:...)` (from `Geodesy`) and the framework emits coordinates in the KML `lon,lat[,alt]` order. +- **Coordinates order**: KML requires `longitude,latitude[,altitude]`. The public API accepts `Coordinate(latitude:..., longitude:...)` (a typealias for [Vincenty](https://github.com/Haelix-Code/Vincenty)'s `GeodeticCoordinate`) and the framework emits coordinates in the KML `lon,lat[,alt]` order. - **Units**: Distances (e.g. `LineCircle.radius`) are in **meters**. - **Angles/bearings**: bearings (for arc & circle generation) are interpreted in degrees (0..360). The bearing convention is clockwise from north. - **Altitude**: When you provide altitudes, the `AltitudeMode` is emitted (defaults to `.absolute` in most geometries). diff --git a/Sources/ForeFlightKML/CoreElements/LineString.swift b/Sources/ForeFlightKML/CoreElements/LineString.swift index 069a689..1c215b9 100644 --- a/Sources/ForeFlightKML/CoreElements/LineString.swift +++ b/Sources/ForeFlightKML/CoreElements/LineString.swift @@ -1,4 +1,4 @@ -import GeodesySpherical +import Vincenty /// A line string connecting multiple coordinates in order. /// Used for routes, boundaries, or any connected line geometry. diff --git a/Sources/ForeFlightKML/CoreElements/LinearRing.swift b/Sources/ForeFlightKML/CoreElements/LinearRing.swift index a799b4e..283bb23 100644 --- a/Sources/ForeFlightKML/CoreElements/LinearRing.swift +++ b/Sources/ForeFlightKML/CoreElements/LinearRing.swift @@ -1,4 +1,4 @@ -import GeodesySpherical +import Vincenty /// 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. diff --git a/Sources/ForeFlightKML/CoreElements/Point.swift b/Sources/ForeFlightKML/CoreElements/Point.swift index e121bb0..e6317ee 100644 --- a/Sources/ForeFlightKML/CoreElements/Point.swift +++ b/Sources/ForeFlightKML/CoreElements/Point.swift @@ -1,4 +1,4 @@ -import GeodesySpherical +import Vincenty /// A geographic point in 3D space that can be displayed as a marker in ForeFlight. /// diff --git a/Sources/ForeFlightKML/Enums/OutputFormat.swift b/Sources/ForeFlightKML/Enums/OutputFormat.swift index 5ead170..638f01e 100644 --- a/Sources/ForeFlightKML/Enums/OutputFormat.swift +++ b/Sources/ForeFlightKML/Enums/OutputFormat.swift @@ -1,4 +1,4 @@ -public enum OutputFormat { +public enum OutputFormat: Sendable { case kml case kmz } diff --git a/Sources/ForeFlightKML/ForeFlightKML+Convenience.swift b/Sources/ForeFlightKML/ForeFlightKML+Convenience.swift index 23effa2..35ae01f 100644 --- a/Sources/ForeFlightKML/ForeFlightKML+Convenience.swift +++ b/Sources/ForeFlightKML/ForeFlightKML+Convenience.swift @@ -1,5 +1,5 @@ import Foundation -import GeodesySpherical +import Vincenty extension ForeFlightKMLBuilder { @@ -293,8 +293,10 @@ extension ForeFlightKMLBuilder { } /// Add a text-only label placemark at a coordinate. - /// This uses a transparent 1×1 icon to enable ForeFlight’s “badge” rendering, with the badge color driven by `IconStyle.color`. - /// - Important: Because this relies on a local icon asset, the output **must be exported as KMZ** (not plain KML) for the label to render correctly. + /// This uses a transparent 1×1 icon to enable ForeFlight’s “badge” rendering, + /// with the badge color driven by `IconStyle.color`. + /// - Important: Because this relies on a local icon asset, the output **must be exported as KMZ** + /// (not plain KML) for the label to render correctly. /// - Parameters: /// - text: Label text displayed in ForeFlight /// - coordinate: Geographic coordinate diff --git a/Sources/ForeFlightKML/ForeFlightKML.swift b/Sources/ForeFlightKML/ForeFlightKML.swift index 0e6409a..024c9aa 100644 --- a/Sources/ForeFlightKML/ForeFlightKML.swift +++ b/Sources/ForeFlightKML/ForeFlightKML.swift @@ -1,13 +1,14 @@ import Foundation -import GeodesySpherical +import Vincenty /// A Builder for composing a KML Document (styles + placemarks). /// Use this to create KML documents compatible with ForeFlight's User Map Shapes feature. /// -/// - Note: Marked `@unchecked Sendable` because instances are intended -/// to be created, populated, and built within a single scope (not shared across threads). -/// This allows callers to use the builder from any actor/thread context. -public final class ForeFlightKMLBuilder: Building, @unchecked Sendable { +/// - Note: Not `Sendable`: the builder holds unsynchronised mutable state, so create, populate +/// and build it within a single scope. It can still be handed off to another task or actor +/// as long as the sender stops using it. The `Placemark`s and styles it takes, and the +/// `BuildResult` it produces, are all `Sendable`. +public final class ForeFlightKMLBuilder: Building { /// Optional name for the `` element. private var documentName: String? /// Maximum decimal places for coordinate values. Trailing zeros are trimmed, diff --git a/Sources/ForeFlightKML/Geometry/Circle/LineCircle.swift b/Sources/ForeFlightKML/Geometry/Circle/LineCircle.swift index a3eafba..a1736b3 100644 --- a/Sources/ForeFlightKML/Geometry/Circle/LineCircle.swift +++ b/Sources/ForeFlightKML/Geometry/Circle/LineCircle.swift @@ -1,5 +1,5 @@ import Foundation -import GeodesySpherical +import Vincenty /// A circular line geometry approximated with multiple line segments. /// diff --git a/Sources/ForeFlightKML/Geometry/Circle/PolygonCircle.swift b/Sources/ForeFlightKML/Geometry/Circle/PolygonCircle.swift index 34a4322..ee27bdd 100644 --- a/Sources/ForeFlightKML/Geometry/Circle/PolygonCircle.swift +++ b/Sources/ForeFlightKML/Geometry/Circle/PolygonCircle.swift @@ -1,4 +1,4 @@ -import GeodesySpherical +import Vincenty /// A circular polygon geometry approximated with multiple line segments. /// diff --git a/Sources/ForeFlightKML/Geometry/Segment/LineSector.swift b/Sources/ForeFlightKML/Geometry/Segment/LineSector.swift index b1ba148..9c6bd62 100644 --- a/Sources/ForeFlightKML/Geometry/Segment/LineSector.swift +++ b/Sources/ForeFlightKML/Geometry/Segment/LineSector.swift @@ -1,4 +1,4 @@ -import GeodesySpherical +import Vincenty /// An arc or sector geometry (like a pie slice) defined by center, radius, and angular extents. /// diff --git a/Sources/ForeFlightKML/Geometry/Segment/PolygonAnnularSector.swift b/Sources/ForeFlightKML/Geometry/Segment/PolygonAnnularSector.swift index 70dbb68..0ffd1e2 100644 --- a/Sources/ForeFlightKML/Geometry/Segment/PolygonAnnularSector.swift +++ b/Sources/ForeFlightKML/Geometry/Segment/PolygonAnnularSector.swift @@ -1,4 +1,4 @@ -import GeodesySpherical +import Vincenty public struct PolygonAnnularSector: KMLElement, AltitudeSupport { let polygon: Polygon diff --git a/Sources/ForeFlightKML/Geometry/Segment/PolygonSector.swift b/Sources/ForeFlightKML/Geometry/Segment/PolygonSector.swift index 72c021e..71dadff 100644 --- a/Sources/ForeFlightKML/Geometry/Segment/PolygonSector.swift +++ b/Sources/ForeFlightKML/Geometry/Segment/PolygonSector.swift @@ -1,4 +1,4 @@ -import GeodesySpherical +import Vincenty public struct PolygonSector: KMLElement, AltitudeSupport { let polygon: Polygon diff --git a/Sources/ForeFlightKML/Geometry/Shared/CircleGeometry.swift b/Sources/ForeFlightKML/Geometry/Shared/CircleGeometry.swift index 68b46cb..1af834e 100644 --- a/Sources/ForeFlightKML/Geometry/Shared/CircleGeometry.swift +++ b/Sources/ForeFlightKML/Geometry/Shared/CircleGeometry.swift @@ -1,4 +1,4 @@ -import GeodesySpherical +import Vincenty internal enum CircleGeometry { diff --git a/Sources/ForeFlightKML/Geometry/Shared/Line.swift b/Sources/ForeFlightKML/Geometry/Shared/Line.swift index dc94257..01d13a4 100644 --- a/Sources/ForeFlightKML/Geometry/Shared/Line.swift +++ b/Sources/ForeFlightKML/Geometry/Shared/Line.swift @@ -1,4 +1,4 @@ -import GeodesySpherical +import Vincenty /// A simple line connecting multiple coordinates in sequence. /// diff --git a/Sources/ForeFlightKML/Geometry/Shared/SectorGeometry.swift b/Sources/ForeFlightKML/Geometry/Shared/SectorGeometry.swift index 57c670c..bfe6e37 100644 --- a/Sources/ForeFlightKML/Geometry/Shared/SectorGeometry.swift +++ b/Sources/ForeFlightKML/Geometry/Shared/SectorGeometry.swift @@ -1,5 +1,5 @@ import Foundation -import GeodesySpherical +import Vincenty internal enum SectorGeometry { static func generateSectorPoints( diff --git a/Sources/ForeFlightKML/Models/AltitudeSupport.swift b/Sources/ForeFlightKML/Models/AltitudeSupport.swift index 2a6da4f..68c153b 100644 --- a/Sources/ForeFlightKML/Models/AltitudeSupport.swift +++ b/Sources/ForeFlightKML/Models/AltitudeSupport.swift @@ -18,7 +18,7 @@ extension AltitudeSupport { /// Supported Altitude Modes per KML 2.2 specification /// See: https://developers.google.com/kml/documentation/kmlreference#altitudemode -public enum AltitudeMode: String, Equatable { +public enum AltitudeMode: String, Equatable, Sendable { case absolute case clampToGround case relativeToGround diff --git a/Sources/ForeFlightKML/Models/CoordinateContainer.swift b/Sources/ForeFlightKML/Models/CoordinateContainer.swift index 06d2cd4..5306e5d 100644 --- a/Sources/ForeFlightKML/Models/CoordinateContainer.swift +++ b/Sources/ForeFlightKML/Models/CoordinateContainer.swift @@ -1,4 +1,4 @@ -import GeodesySpherical +import Vincenty /// Protocol for KML elements that contain coordinate arrays and support altitude/tessellation. /// Used by LineString, LinearRing, and similar coordinate-based geometries. diff --git a/Sources/ForeFlightKML/Models/Placemark.swift b/Sources/ForeFlightKML/Models/Placemark.swift index ff1032c..6cf5990 100644 --- a/Sources/ForeFlightKML/Models/Placemark.swift +++ b/Sources/ForeFlightKML/Models/Placemark.swift @@ -1,4 +1,4 @@ -import GeodesySpherical +import Vincenty /// Represents a Feature Placemark that combines geometry with styling and metadata. /// @@ -7,7 +7,7 @@ import GeodesySpherical /// - Optional name that appears in ForeFlight /// - Optional style that controls visual appearance /// -public struct Placemark { +public struct Placemark: Sendable { public private(set) var name: String? public private(set) var geometry: KMLElement public private(set) var styleUrl: String? diff --git a/Sources/ForeFlightKML/Styles/Colors.swift b/Sources/ForeFlightKML/Styles/Colors.swift index 5ef3882..8fd8750 100644 --- a/Sources/ForeFlightKML/Styles/Colors.swift +++ b/Sources/ForeFlightKML/Styles/Colors.swift @@ -1,6 +1,6 @@ import Foundation -public struct KMLColor: Equatable, CustomStringConvertible { +public struct KMLColor: Equatable, CustomStringConvertible, Sendable { public var description: String { kmlHexString } /// Single internal storage: bits [31..24]=A, [23..16]=R, [15..8]=G, [7..0]=B diff --git a/Sources/ForeFlightKML/Styles/IconStyle.swift b/Sources/ForeFlightKML/Styles/IconStyle.swift index dc80caf..9391281 100644 --- a/Sources/ForeFlightKML/Styles/IconStyle.swift +++ b/Sources/ForeFlightKML/Styles/IconStyle.swift @@ -93,7 +93,7 @@ public struct IconStyle: KMLSubStyle { /// Icon shapes that work with Google's predefined color system. /// These are the "paddle" style icons that only support fixed colors. -public enum PredefinedIconType: String, Codable { +public enum PredefinedIconType: String, Codable, Sendable { case pushpin case circle case square @@ -102,7 +102,7 @@ public enum PredefinedIconType: String, Codable { /// Icon shapes that support custom colors. /// These are the "shapes" style icons that can be any color via KML's color tag. -public enum CustomIconType: Codable { +public enum CustomIconType: Codable, Sendable { case opendiamond case triangle case forbidden @@ -155,7 +155,7 @@ public enum CustomIconType: Codable { /// Predefined icon colors available for paddle-style icons. /// These are the standard colors provided by Google's KML icon set. -public enum DefinedIconColor: String, Codable { +public enum DefinedIconColor: String, Codable, Sendable { case purple = "purple" case white = "wht" case green = "grn" diff --git a/Sources/ForeFlightKML/Utils/Geodesy+KML.swift b/Sources/ForeFlightKML/Utils/Coordinate+KML.swift similarity index 76% rename from Sources/ForeFlightKML/Utils/Geodesy+KML.swift rename to Sources/ForeFlightKML/Utils/Coordinate+KML.swift index 352c0ea..8d64aba 100644 --- a/Sources/ForeFlightKML/Utils/Geodesy+KML.swift +++ b/Sources/ForeFlightKML/Utils/Coordinate+KML.swift @@ -1,5 +1,6 @@ import Foundation -import GeodesySpherical +// Re-exported so `Coordinate` is fully usable with just `import ForeFlightKML`. +@_exported import Vincenty // KML uses 3D geographic coordinates: longitude, latitude and altitude, in that order. // The longitude and latitude components are in decimal degrees @@ -39,7 +40,21 @@ internal func formatCoordinate(_ value: Double, precision: Int) -> String { return String(formatted[formatted.startIndex.. Coordinate { + guard let solution = try? Ellipsoid.wgs84.direct(from: self, distance: distance, initialBearing: bearing) else { + return Coordinate(latitude: .nan, longitude: .nan) + } + return solution.destination + } + /// Format coordinate as KML `longitude,latitude` string with given precision. /// Trailing zeros are trimmed (e.g. `2.0` not `2.00000000`). public func kmlString(precision: Int = 8) -> String { @@ -49,7 +64,9 @@ extension GeodesySpherical.Coordinate { } /// Write coordinate in KML format directly into a buffer, with optional altitude. - internal func writeKML(to buffer: inout String, altitude: Double? = nil, precision: Int = kDefaultCoordinatePrecision) { + internal func writeKML( + to buffer: inout String, altitude: Double? = nil, precision: Int = kDefaultCoordinatePrecision + ) { buffer.append(formatCoordinate(self.longitude, precision: precision)) buffer.append(",") buffer.append(formatCoordinate(self.latitude, precision: precision)) @@ -68,7 +85,7 @@ internal struct Coordinate3D: Hashable { public let altitude: Double? public init( - latitude: GeodesySpherical.Degrees, longitude: GeodesySpherical.Degrees, + latitude: Double, longitude: Double, altitude: Double? = nil ) { self.coordinate = Coordinate(latitude: latitude, longitude: longitude) @@ -80,8 +97,8 @@ internal struct Coordinate3D: Hashable { self.altitude = altitude } - public var latitude: GeodesySpherical.Degrees { coordinate.latitude } - public var longitude: GeodesySpherical.Degrees { coordinate.longitude } + public var latitude: Double { coordinate.latitude } + public var longitude: Double { coordinate.longitude } public func kmlString(precision: Int = kDefaultCoordinatePrecision) -> String { let lon = formatCoordinate(self.longitude, precision: precision) diff --git a/Sources/ForeFlightKML/Utils/DataFormat.swift b/Sources/ForeFlightKML/Utils/DataFormat.swift index bf80777..0df55b5 100644 --- a/Sources/ForeFlightKML/Utils/DataFormat.swift +++ b/Sources/ForeFlightKML/Utils/DataFormat.swift @@ -1,6 +1,6 @@ import Foundation -public struct BuildResult { +public struct BuildResult: Sendable { public let data: Data public let fileExtension: String public let mimetype: String diff --git a/Sources/ForeFlightKML/Utils/Protocols.swift b/Sources/ForeFlightKML/Utils/Protocols.swift index 379ca57..68e1b98 100644 --- a/Sources/ForeFlightKML/Utils/Protocols.swift +++ b/Sources/ForeFlightKML/Utils/Protocols.swift @@ -2,7 +2,7 @@ /// to a KML fragment string. /// /// Implementers must produce valid KML (as a `String`) for that element. -public protocol KMLElement { +public protocol KMLElement: Sendable { func kmlString() -> String /// Write KML directly into a mutable string buffer for better performance. /// Default implementation falls back to `kmlString()`. @@ -25,7 +25,7 @@ public extension KMLElement { /// Represents a top-level KML `Style` that has an id and a KML body. /// Use this to create reusable styles that placemarks can reference. -public protocol KMLStyle { +public protocol KMLStyle: Sendable { // Top-level style that must provide an id and full KML (usually a ) func id() -> String func kmlString() -> String @@ -44,7 +44,7 @@ public extension KMLStyle { /// Represents a style *sub-element* like `` or ``. /// These are embedded inside a top-level `