diff --git a/README.md b/README.md index ba05f9c..17720a5 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ This package provides a small, focused API for composing KML documents suitable ## Quick highlights -- Compose `Placemark`s with `Point`, `LineString`, `Polygon` and derived geometry helpers (circles, arc segments, etc.). +- 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` document string. - Lightweight — no UI code. @@ -66,11 +66,11 @@ presentShareSheet(with: url) - `addPoint` Add a point with style. - `addLine` Add a line connecting multiple coordinates. - `addLineCircle` Add a circular line (approximated by line segments). - - `addLineSegment` Add an arc segment line geometry. + - `addLineSector` Add an arc sector line geometry. - `addPolygon` Add a polygon with outer boundary and optional holes. - `addPolygonCircle` Add a polygon with outer boundary and optional holes. - - `addPolygonSegment` Add a filled segment polygon (pie slice). - - `addPolygonAnnularSegment` Add a filled annular (ring) segment polygon. + - `addPolygonSector` Add a filled sector polygon (pie slice). + - `addPolygonAnnularSector` Add a filled annular (ring) sector polygon. - `addLabel` Add a text-only label placemark at a coordinate. ### ForeflightKMLBuilder Export formats @@ -81,7 +81,7 @@ presentShareSheet(with: url) ### Underlying elements - `Placemark` — a Feature containing a geometry (must implement `KMLElement`). Optionally attach a `KMLStyle`. -- Geometry types: `Point`, `Line`, `LineCircle`, `LineSegment` (segment of a Circle), `Polygon`, `PolygonCircle` (filled circle), `PolygonSegment` (filled segment) `LinearRing`. +- Geometry types: `Point`, `Line`, `LineCircle`, `LineSector` (sector of a Circle), `Polygon`, `PolygonCircle` (filled circle), `PolygonSector` (filled sector) `LinearRing`. - `Style` and substyles: `LineStyle`, `PolyStyle`, `IconStyle`, `LabelStyle`. - `KMLColor` — helper to create the aabbggrr color values used by KML. diff --git a/Sources/ForeFlightKML/ForeFlightKML+Convenience.swift b/Sources/ForeFlightKML/ForeFlightKML+Convenience.swift index 0be0591..23effa2 100644 --- a/Sources/ForeFlightKML/ForeFlightKML+Convenience.swift +++ b/Sources/ForeFlightKML/ForeFlightKML+Convenience.swift @@ -97,7 +97,7 @@ extension ForeFlightKMLBuilder { return addPlacemark(placemark) } - /// Add an arc segment line geometry. + /// Add an arc sector line geometry. /// - Parameters: /// - name: Display name in ForeFlight (optional) /// - center: Center point of the arc @@ -110,7 +110,7 @@ extension ForeFlightKMLBuilder { /// - style: Path style defining line appearance (optional) /// - Returns: Self for method chaining @discardableResult - public func addLineSegment( + public func addLineSector( name: String? = nil, center: Coordinate, radiusMeters: Double, @@ -124,7 +124,7 @@ extension ForeFlightKMLBuilder { precondition(radiusMeters > 0, "Radius must be positive") precondition(numberOfPoints >= 3, "Need at least 3 segments for an arc") - let segment = LineSegment( + let sector = LineSector( center: center, radius: radiusMeters, startAngle: startAngle, @@ -134,7 +134,7 @@ extension ForeFlightKMLBuilder { tessellate: tessellate ) - let placemark = Placemark(name: name, geometry: segment, style: style) + let placemark = Placemark(name: name, geometry: sector, style: style) return addPlacemark(placemark) } @@ -205,7 +205,7 @@ extension ForeFlightKMLBuilder { return addPlacemark(placemark) } - /// Add a filled segment polygon (pie slice). + /// Add a filled sector polygon (pie slice). /// - Parameters: /// - name: Display name in ForeFlight (optional) /// - center: Center point of the arc @@ -218,7 +218,7 @@ extension ForeFlightKMLBuilder { /// - style: Polygon style defining outline and optional fill (optional) /// - Returns: Self for method chaining @discardableResult - public func addPolygonSegment( + public func addPolygonSector( name: String? = nil, center: Coordinate, radiusMeters: Double, @@ -232,7 +232,7 @@ extension ForeFlightKMLBuilder { precondition(radiusMeters > 0, "Radius must be positive") precondition(numberOfPoints >= 3, "Need at least 3 segments for a segment") - let segment = PolygonSegment( + let sector = PolygonSector( center: center, radius: radiusMeters, startAngle: startAngle, @@ -242,15 +242,15 @@ extension ForeFlightKMLBuilder { tessellate: tessellate ) - let placemark = Placemark(name: name, geometry: segment, style: style) + let placemark = Placemark(name: name, geometry: sector, style: style) return addPlacemark(placemark) } - /// Add a filled annular (ring) segment polygon. - /// This creates a segment between two radii, excluding the inner circle area. + /// Add a filled annular (ring) sector polygon. + /// This creates a sector between two radii, excluding the inner circle area. /// - Parameters: /// - name: Display name in ForeFlight (optional) - /// - center: Center point of the segment + /// - center: Center point of the sector /// - innerRadius: Inner radius in meters (the "hole" size) /// - outerRadius: Outer radius in meters /// - startAngle: Starting angle in degrees (0° = North, clockwise) @@ -261,7 +261,7 @@ extension ForeFlightKMLBuilder { /// - style: Polygon style defining outline and optional fill (optional) /// - Returns: Self for method chaining @discardableResult - public func addPolygonAnnularSegment( + public func addPolygonAnnularSector( name: String? = nil, center: Coordinate, innerRadius: Double, @@ -277,7 +277,7 @@ extension ForeFlightKMLBuilder { precondition(outerRadius > innerRadius, "Outer radius must be greater than inner radius") precondition(numberOfPoints >= 3, "Need at least 3 segments for an annular segment") - let segment = PolygonAnnularSegment( + let sector = PolygonAnnularSector( center: center, innerRadius: innerRadius, outerRadius: outerRadius, @@ -288,7 +288,7 @@ extension ForeFlightKMLBuilder { tessellate: tessellate ) - let placemark = Placemark(name: name, geometry: segment, style: style) + let placemark = Placemark(name: name, geometry: sector, style: style) return addPlacemark(placemark) } diff --git a/Sources/ForeFlightKML/Geometry/Segment/LineSegment.swift b/Sources/ForeFlightKML/Geometry/Segment/LineSector.swift similarity index 84% rename from Sources/ForeFlightKML/Geometry/Segment/LineSegment.swift rename to Sources/ForeFlightKML/Geometry/Segment/LineSector.swift index 64a35d9..b1ba148 100644 --- a/Sources/ForeFlightKML/Geometry/Segment/LineSegment.swift +++ b/Sources/ForeFlightKML/Geometry/Segment/LineSector.swift @@ -2,13 +2,13 @@ import GeodesySpherical /// An arc or sector geometry (like a pie slice) defined by center, radius, and angular extents. /// -/// The segment starts from the center, extends to the radius at the start angle, +/// The sector starts from the center, extends to the radius at the start angle, /// follows the arc to the end angle, then returns to center, creating a closed shape. /// /// Angles are measured in degrees clockwise from North (0°). /// -public struct LineSegment: KMLElement, LineLike { - /// The coordinates that define this arc segment +public struct LineSector: KMLElement, LineLike { + /// The coordinates that define this arc sector public var coordinates: [Coordinate] /// Optional altitude in meters applied to all coordinates public var altitude: Double? @@ -17,7 +17,7 @@ public struct LineSegment: KMLElement, LineLike { /// Whether to tessellate (follow ground contours) when rendering public var tessellate: Bool? - /// Create a new arc segment geometry. + /// Create a new arc sector geometry. /// - Parameters: /// - center: The center point of the arc /// - radius: Radius in meters (must be positive) @@ -39,7 +39,7 @@ public struct LineSegment: KMLElement, LineLike { self.altitude = altitude self.tessellate = tessellate self.altitudeMode = altitudeMode - self.coordinates = SegmentGeometry.generateSegmentPoints( + self.coordinates = SectorGeometry.generateSectorPoints( center: center, radius: radius, startAngle: startAngle, endAngle: endAngle, numberOfPoints: numberOfPoints) } diff --git a/Sources/ForeFlightKML/Geometry/Segment/PolygonAnnularSegment.swift b/Sources/ForeFlightKML/Geometry/Segment/PolygonAnnularSector.swift similarity index 86% rename from Sources/ForeFlightKML/Geometry/Segment/PolygonAnnularSegment.swift rename to Sources/ForeFlightKML/Geometry/Segment/PolygonAnnularSector.swift index 8de364a..a40d801 100644 --- a/Sources/ForeFlightKML/Geometry/Segment/PolygonAnnularSegment.swift +++ b/Sources/ForeFlightKML/Geometry/Segment/PolygonAnnularSector.swift @@ -1,6 +1,6 @@ import GeodesySpherical -public struct PolygonAnnularSegment: KMLElement, AltitudeSupport { +public struct PolygonAnnularSector: KMLElement, AltitudeSupport { let polygon: Polygon public var altitudeMode: AltitudeMode? { polygon.altitudeMode } @@ -15,7 +15,7 @@ public struct PolygonAnnularSegment: KMLElement, AltitudeSupport { altitudeMode: AltitudeMode? = nil, tessellate: Bool? = nil ) { - let coordinates = SegmentGeometry.generateAnnularSegmentPoints( + let coordinates = SectorGeometry.generateAnnularSectorPoints( center: center, innerRadius: innerRadius, outerRadius: outerRadius, diff --git a/Sources/ForeFlightKML/Geometry/Segment/PolygonSegment.swift b/Sources/ForeFlightKML/Geometry/Segment/PolygonSector.swift similarity index 86% rename from Sources/ForeFlightKML/Geometry/Segment/PolygonSegment.swift rename to Sources/ForeFlightKML/Geometry/Segment/PolygonSector.swift index 0c5074a..c1b5136 100644 --- a/Sources/ForeFlightKML/Geometry/Segment/PolygonSegment.swift +++ b/Sources/ForeFlightKML/Geometry/Segment/PolygonSector.swift @@ -1,6 +1,6 @@ import GeodesySpherical -public struct PolygonSegment: KMLElement, AltitudeSupport { +public struct PolygonSector: KMLElement, AltitudeSupport { let polygon: Polygon public var altitudeMode: AltitudeMode? { polygon.altitudeMode } @@ -15,7 +15,7 @@ public struct PolygonSegment: KMLElement, AltitudeSupport { altitudeMode: AltitudeMode? = nil, tessellate: Bool? = nil ) { - let coordinates = SegmentGeometry.generateSegmentPoints( + let coordinates = SectorGeometry.generateSectorPoints( center: center, radius: radius, startAngle: startAngle, endAngle: endAngle, numberOfPoints: numberOfPoints) diff --git a/Sources/ForeFlightKML/Geometry/Shared/SegmentGeometry.swift b/Sources/ForeFlightKML/Geometry/Shared/SectorGeometry.swift similarity index 87% rename from Sources/ForeFlightKML/Geometry/Shared/SegmentGeometry.swift rename to Sources/ForeFlightKML/Geometry/Shared/SectorGeometry.swift index 9773149..57c670c 100644 --- a/Sources/ForeFlightKML/Geometry/Shared/SegmentGeometry.swift +++ b/Sources/ForeFlightKML/Geometry/Shared/SectorGeometry.swift @@ -1,13 +1,13 @@ import Foundation import GeodesySpherical -internal enum SegmentGeometry { - static func generateSegmentPoints( +internal enum SectorGeometry { + static func generateSectorPoints( center: Coordinate, radius: Double, startAngle: Double, endAngle: Double, numberOfPoints: Int ) -> [Coordinate] { - var segmentPoints: [Coordinate] = [] - segmentPoints.append(center) + var sectorPoints: [Coordinate] = [] + sectorPoints.append(center) let start = startAngle.truncatingRemainder(dividingBy: 360) let end = endAngle.truncatingRemainder(dividingBy: 360) @@ -25,15 +25,15 @@ internal enum SegmentGeometry { let endPoint = center.destination(with: radius, bearing: currentAngle) - segmentPoints.append(endPoint) + sectorPoints.append(endPoint) } - segmentPoints.append(center) - return segmentPoints + sectorPoints.append(center) + return sectorPoints } // swiftlint:disable:next function_parameter_count - static func generateAnnularSegmentPoints( + static func generateAnnularSectorPoints( center: Coordinate, innerRadius: Double, outerRadius: Double, diff --git a/Sources/ForeFlightKML/Styles/Geometry/PathStyle.swift b/Sources/ForeFlightKML/Styles/Geometry/PathStyle.swift index e937970..3251dc8 100644 --- a/Sources/ForeFlightKML/Styles/Geometry/PathStyle.swift +++ b/Sources/ForeFlightKML/Styles/Geometry/PathStyle.swift @@ -4,7 +4,7 @@ import Foundation /// /// Path styles define how lines are drawn. They apply to: /// - Line and LineString geometries -/// - LineCircle and LineSegment geometries +/// - LineCircle and LineSector geometries /// public struct PathStyle: KMLStyle { public let stroke: LineStyle diff --git a/Sources/ForeFlightKML/Styles/LineStyle.swift b/Sources/ForeFlightKML/Styles/LineStyle.swift index b9ca7c7..c5b31f0 100644 --- a/Sources/ForeFlightKML/Styles/LineStyle.swift +++ b/Sources/ForeFlightKML/Styles/LineStyle.swift @@ -6,7 +6,7 @@ /// /// This style applies to: /// - Line and LineString geometries -/// - LineCircle and LineSegment geometries +/// - LineCircle and LineSector geometries /// - Polygon outline borders /// - Any other line-based geometry /// diff --git a/Tests/ForeFlightKMLTests/GeometryTests/LineSegmentTests.swift b/Tests/ForeFlightKMLTests/GeometryTests/LineSectorTests.swift similarity index 73% rename from Tests/ForeFlightKMLTests/GeometryTests/LineSegmentTests.swift rename to Tests/ForeFlightKMLTests/GeometryTests/LineSectorTests.swift index 45d8239..407b5af 100644 --- a/Tests/ForeFlightKMLTests/GeometryTests/LineSegmentTests.swift +++ b/Tests/ForeFlightKMLTests/GeometryTests/LineSectorTests.swift @@ -3,34 +3,34 @@ import XCTest @testable import ForeFlightKML -final class LineSegmentTests: XCTestCase { - func testBuildSegment() throws { +final class LineSectorTests: XCTestCase { + func testBuildSector() throws { let builder = ForeFlightKMLBuilder(documentName: "My Test KML") let center = Coordinate(latitude: 38.8700980, longitude: -77.055967) - let segmentElement = LineSegment.init( + let sectorElement = LineSector.init( center: center, radius: 200, startAngle: 120.0, endAngle: 150.0) - builder.addPlacemark(Placemark(name: "Pizza Wedge", geometry: segmentElement)) + builder.addPlacemark(Placemark(name: "Pizza Wedge", geometry: sectorElement)) let kml = builder.kmlString() XCTAssertTrue(kml.contains(""), "Expected KML to contain the LineSTring name") } - func testBuildSegmentAltitude() throws { + func testBuildSectorAltitude() throws { let builder = ForeFlightKMLBuilder(documentName: "My Test KML") let center = Coordinate(latitude: 38.8700980, longitude: -77.055967) - let segmentElementRed = LineSegment.init( + let sectorElementRed = LineSector.init( center: center, radius: 200, startAngle: 90.0, endAngle: 150.0, altitude: 2500) - let segmentElementBlue = LineSegment.init( + let sectorElementBlue = LineSector.init( center: center, radius: 200, startAngle: 330.0, endAngle: 30.0, altitude: 3000) builder.addPlacemark( - Placemark(name: "Red Pizza Wedge", geometry: segmentElementRed)) + Placemark(name: "Red Pizza Wedge", geometry: sectorElementRed)) builder.addPlacemark( Placemark( - name: "Blue Pizza Wedge", geometry: segmentElementBlue)) + name: "Blue Pizza Wedge", geometry: sectorElementBlue)) let kml = builder.kmlString() diff --git a/Tests/ForeFlightKMLTests/GeometryTests/PolygonAnnularSegmentTests.swift b/Tests/ForeFlightKMLTests/GeometryTests/PolygonAnnularSectorTests.swift similarity index 88% rename from Tests/ForeFlightKMLTests/GeometryTests/PolygonAnnularSegmentTests.swift rename to Tests/ForeFlightKMLTests/GeometryTests/PolygonAnnularSectorTests.swift index 80a5c09..382a111 100644 --- a/Tests/ForeFlightKMLTests/GeometryTests/PolygonAnnularSegmentTests.swift +++ b/Tests/ForeFlightKMLTests/GeometryTests/PolygonAnnularSectorTests.swift @@ -3,7 +3,7 @@ import XCTest @testable import ForeFlightKML -final class PolygonAnnularSegmentTests: XCTestCase { +final class PolygonAnnularSectorTests: XCTestCase { struct Quadrant { let name: String @@ -12,11 +12,11 @@ final class PolygonAnnularSegmentTests: XCTestCase { let color: KMLColor } - func testBasicAnnularSegment() throws { + func testBasicAnnularSector() throws { let builder = ForeFlightKMLBuilder(documentName: "Annular Test") let center = Coordinate(latitude: 38.8700980, longitude: -77.055967) - let segment = PolygonAnnularSegment( + let sector = PolygonAnnularSector( center: center, innerRadius: 1000, // 1km inner radius outerRadius: 2000, // 2km outer radius @@ -24,7 +24,7 @@ final class PolygonAnnularSegmentTests: XCTestCase { endAngle: 90 // East ) - let placemark = Placemark(name: "Northeast Quadrant", geometry: segment) + let placemark = Placemark(name: "Northeast Quadrant", geometry: sector) builder.addPlacemark(placemark) let kml = builder.kmlString() @@ -34,7 +34,7 @@ final class PolygonAnnularSegmentTests: XCTestCase { XCTAssertTrue(kml.contains("")) } - func testFourQuadrantAnnularSegments() throws { + func testFourQuadrantAnnularSectors() throws { let builder = ForeFlightKMLBuilder(documentName: "Ring Quadrants") let center = Coordinate(latitude: 51.750188, longitude: -1.581566) @@ -49,7 +49,7 @@ final class PolygonAnnularSegmentTests: XCTestCase { ] for quadrant in quadrants { - builder.addPolygonAnnularSegment( + builder.addPolygonAnnularSector( name: quadrant.name, center: center, innerRadius: innerRadius, @@ -74,11 +74,11 @@ final class PolygonAnnularSegmentTests: XCTestCase { XCTAssertEqual(placemarkCount, 4) } - func testAnnularSegmentWithStyle() throws { + func testAnnularSectorWithStyle() throws { let builder = ForeFlightKMLBuilder(documentName: "Styled Ring") let center = Coordinate(latitude: 38.8700980, longitude: -77.055967) - builder.addPolygonAnnularSegment( + builder.addPolygonAnnularSector( name: "Warning Sector", center: center, innerRadius: 1500, @@ -99,12 +99,12 @@ final class PolygonAnnularSegmentTests: XCTestCase { XCTAssertTrue(kml.contains("")) } - func testAnnularSegmentCrossingNorth() throws { - // Test a segment that crosses 0° (wraps around North) + func testAnnularSectorCrossingNorth() throws { + // Test a sector that crosses 0° (wraps around North) let builder = ForeFlightKMLBuilder(documentName: "Crossing North") let center = Coordinate(latitude: 38.8700980, longitude: -77.055967) - let segment = PolygonAnnularSegment( + let sector = PolygonAnnularSector( center: center, innerRadius: 1000, outerRadius: 2000, @@ -112,19 +112,19 @@ final class PolygonAnnularSegmentTests: XCTestCase { endAngle: 30 // 30° after North ) - builder.addPlacemark(Placemark(name: "North Crossing", geometry: segment)) + builder.addPlacemark(Placemark(name: "North Crossing", geometry: sector)) let kml = builder.kmlString() XCTAssertTrue(kml.contains("North Crossing")) XCTAssertTrue(kml.contains("")) } - func testNarrowAnnularSegment() throws { - // Test a thin ring segment (5° arc) - let builder = ForeFlightKMLBuilder(documentName: "Narrow Segment") + func testNarrowAnnularSector() throws { + // Test a thin ring sector (5° arc) + let builder = ForeFlightKMLBuilder(documentName: "Narrow Sector") let center = Coordinate(latitude: 38.8700980, longitude: -77.055967) - let segment = PolygonAnnularSegment( + let sector = PolygonAnnularSector( center: center, innerRadius: 1000, outerRadius: 2000, @@ -133,14 +133,14 @@ final class PolygonAnnularSegmentTests: XCTestCase { numberOfPoints: 16 ) - builder.addPlacemark(Placemark(name: "Narrow", geometry: segment)) + builder.addPlacemark(Placemark(name: "Narrow", geometry: sector)) let kml = builder.kmlString() XCTAssertTrue(kml.contains("Narrow")) } func testGenerateCompleteDemoKML() throws { - let builder = ForeFlightKMLBuilder(documentName: "Annular Segments Demo") + let builder = ForeFlightKMLBuilder(documentName: "Annular Sector Demo") let center = Coordinate(latitude: 38.8700980, longitude: -77.055967) let innerRadius: Double = 1000 @@ -154,7 +154,7 @@ final class PolygonAnnularSegmentTests: XCTestCase { ] for quadrant in quadrants { - builder.addPolygonAnnularSegment( + builder.addPolygonAnnularSector( name: quadrant.name, center: center, innerRadius: innerRadius, diff --git a/Tests/ForeFlightKMLTests/GeometryTests/PolygonSegmentTests.swift b/Tests/ForeFlightKMLTests/GeometryTests/PolygonSegmentTests.swift index 07504f4..e525f88 100644 --- a/Tests/ForeFlightKMLTests/GeometryTests/PolygonSegmentTests.swift +++ b/Tests/ForeFlightKMLTests/GeometryTests/PolygonSegmentTests.swift @@ -3,14 +3,14 @@ import XCTest @testable import ForeFlightKML -final class PolygonSegmentsTests: XCTestCase { - func testBuildBasicSegments() throws { +final class PolygonSectorTests: XCTestCase { + func testBuildBasicSectors() throws { let builder = ForeFlightKMLBuilder(documentName: "My Test KML") let center = Coordinate(latitude: 38.8700980, longitude: -77.055967) - let circle = PolygonSegment(center: center, radius: 500, startAngle: 45, endAngle: 135) - let pm = Placemark(name: "Nice Segment", geometry: circle) + let circle = PolygonSector(center: center, radius: 500, startAngle: 45, endAngle: 135) + let pm = Placemark(name: "Nice Sector", geometry: circle) builder.addPlacemark(pm) let kml = builder.kmlString() diff --git a/Tests/ForeFlightKMLTests/StyleTests/IconStyle+HiddenTests.swift b/Tests/ForeFlightKMLTests/StyleTests/IconStyle+HiddenTests.swift index 8b0f889..0d8e27c 100644 --- a/Tests/ForeFlightKMLTests/StyleTests/IconStyle+HiddenTests.swift +++ b/Tests/ForeFlightKMLTests/StyleTests/IconStyle+HiddenTests.swift @@ -27,11 +27,11 @@ final class LabelOnlyTests: XCTestCase { XCTAssertTrue(kml.contains("")) } - func test_builderAddLabel_emitsPlacemarkNameAndHiddenIconScale() throws { + func test_builderAddLabel_emitsPlacemarkName() throws { let builder = ForeFlightKMLBuilder() builder.addLabel("Label Warning", coordinate: .init(latitude: 51.2345, longitude: -1.2345), color: .warning) - let kml = try builder.build() + let kml = builder.kmlString() XCTAssertTrue(kml.contains("")) XCTAssertTrue(kml.contains("