Repository navigation
Conversation
Implements issue #242 in two steps on this branch. Phase A - struct-based Projection: - Projection becomes a public struct with SRID identity: custom ==/hash and a Codable implementation that encodes the raw SRID identically to the previous enum wire format. All 125 built-in projections keep their constant names, so dot-shorthand usage (x == .epsg4326) keeps compiling unchanged. - The generated case lists are gone: kind, capability flags, wraparoundExtent and crsLength resolve through the registry. - init?(srid:) resolves through an SRID alias table (the historical EPSG:3857 aliases) plus a registry check; init?(wkt:) and the UTM token short-circuit are unchanged; descriptions unchanged. Phase B - add-only custom registration: - public CustomProjection struct: a value-type descriptor with @sendable forward/inverse closures relative to the EPSG:4326 (WGS84) pivot, plus kind, wraparound extent, valid extent (public ProjectionExtent struct), world bounding box and WKT fragment sets. The pivot contract makes datum-capable custom definitions possible. - public Projection.register(CustomProjection) is add-only: rejected for SRID <= 0, duplicates and built-in shadowing; there is no unregister/deactivate path by design. - ProjectionExtent becomes the public extent type used by all definition metadata. - The registry keeps a Mutex-guarded add-only snapshot: built-ins seeded once, custom definitions appended, never removed or replaced. Hot paths NEVER read the registry - Projection values capture their definition at construction - so projected(to:), kind, clamped(), isValid and wraparoundExtent resolve via the stored definition with zero locking. The registry is only used for lookups (init?(srid:), Codable), WKT matching and registration. Performance (release benchmarks, before -> after): - full projected(to:) path: ~178 ns/coordinate (35x baseline) -> ~1.7x of the direct-math baseline - No hot-path registry access remains; a parallel 2488-test run previously melted down (924s wall) on per-access Mutex reads, which motivated the definition capture. Tests (+6): custom registration/rejection rules, end-to-end round trips, registry-driven capabilities on a custom definition, WKT matching, Codable semantics (register-before-decode) and a concurrent registration storm. Note for test authors: Swift Testing runs tests in parallel against the process-global registry - reserve SRID blocks and use unique WKT fragments. Also adds a Projections section with a custom-projection example to the README.
Contributor
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements #242 in the agreed design: a struct-based
Projectionwith SRID identity, plus an add-only custom registration API. Stacks on #245.While a major (breaking) change by nature (public enum → struct), the source impact is deliberately minimized: all 125 built-in projections keep their constant names, so dot-shorthand usage (
x == .epsg4326,switchon.kind, etc.) keeps compiling unchanged — the whole in-repo codebase needed only 3 tinyrawValue→sridrenames.Phase A — struct-based
Projectionpublic struct Projection: Hashable, Codable, Sendable, CustomStringConvertiblewith SRID identity (custom==/hash);Codableencodes the raw SRID identically to the enum's wire format (decode throws for unregistered SRIDs)static letconstants;init?(srid:)(with the historical EPSG:3857 aliases),init?(wkt:)(registry-driven + UTM token short-circuit),init?(utmZone:hemisphere:),description,kind/flags,wraparoundExtent,crsLength(fromMeters:),utmZone,utmHemisphereall preserved in shapePhase B — add-only registration
public struct CustomProjection: value-type descriptor with@Sendableforward/inverse closures relative to the EPSG:4326 (WGS84) pivot — explicitly documented as the seam where datum-capable definitions (e.g. NAD27, issue Support CRSs on other datums (NAD27, OSGB, CH1903+, ...) #243) would perform datum transformations. Also carrieskind,wraparoundExtent,validExtent(newpublic struct ProjectionExtent),worldBoundingBox, WKT fragment setsProjection.register(CustomProjection) -> Bool— add-only by design: rejected for SRID ≤ 0, duplicates and built-in shadowing; no unregister/deactivate path (per review decision — enabling/disabling of built-ins was rejected earlier as performance-neutral and correctness-degrading)Mutex-guarded add-only snapshot store; nothing is ever removed or replacedPerformance — the big win of the definition-capture design
This branch's parallel test run exposed catastrophic contention from per-access registry reads (~924 s full-suite wall time; a graph perf test at 370×). The fix is architectural:
Projectionvalues capture their definition at construction — hot paths (projected(to:),kind,clamped(),isValid,wraparoundExtent) read the stored definition with zero locking, and the registry is only used for cold-path lookups (init?(srid:),Codable), WKT matching and registration.Release-build benchmarks (per coordinate, before → after):
projected(to:)4326↔3857Testing (+6)
CustomProjectionTests: registration rules (duplicate/<= 0/shadowed SRIDs rejected), end-to-end round trips through a custom definition, registry-driven capabilities (validity/clamp/normalize/kind-dispatch on a custom), WKT matching, Codable semantics (register-before-decode), and an 8-task concurrent registration storm.Note for test authors (surfaced by the suite, documented in the tests): Swift Testing runs tests in parallel against the process-global registry — reserve SRID blocks and use per-registration WKT fragments.
Also adds a Projections section with a CustomProjection example to the README and full DocC throughout.
Full suite: 2488 tests in 180 suites, zero warnings.