This is a Swift-only library to calculate and draw map projections.
The API is still evolving — pin to a specific minor version while we are
on 0.x.
- GeoProjector: Map projections, turning geographic coordinates into
projected coordinates and into screen coordinates. Includes a forward
projectand aninverseso you can map a screen-space click back to latitude/longitude. - GeoProjectorDanseiji: The six Danseiji projections by Justin Kunimune, packaged as their own product so the ~1 MB of pre-baked mesh data only ships with apps that ask for it.
- GeoDrawer: Draw GeoJSON using whichever projection you choose.
- Support a selection of map projections, but not an exhaustive list
- Provide methods for drawing those projections, draw GeoJSON content on top, and drawing just a section of the resulting map
- Provide methods for projecting points and inverting screen-space points back to geographic coordinates
- Compatibility with Apple platforms and Linux
This library is part of the Maparoni suite of mapping related Swift libraries and depends on:
- GeoJSONKit, a light-weight GeoJSON framework.
- GeoJSONKit-Turf, a fork of turf-swift with GeoJSONKit's GeoJSON enums used for the basic data models.
To install GeoProjector using the Swift Package Manager,
add the following package to the dependencies in your Package.swift file or
in Xcode:
.package(
url: "https://github.com/maparoni/geoprojector",
from: "0.1.0"
)The package vends three products. Add only the ones you need to your target's
dependencies — pulling in GeoProjectorDanseiji is what brings the mesh data
along, so leave it out unless you actually use those projections.
.target(
name: "MyApp",
dependencies: [
.product(name: "GeoProjector", package: "GeoProjector"), // projections
.product(name: "GeoDrawer", package: "GeoProjector"), // drawing helpers
.product(name: "GeoProjectorDanseiji", package: "GeoProjector"), // optional
]
)Projections are defined using the Projection protocol, which declares the
forward project(_:) and inverse inverse(_:) methods, plus metadata such as
the shape of the projection's mapBounds.
The projections themselves are available through the Projections namespace
(i.e., a caseless enum) which provides implementations of Equirectangular,
Cassini, Mercator, Gall-Peters, Equal Earth, Natural Earth, Orthographic,
Azimuthal Equidistant, and — via GeoProjectorDanseiji — Danseiji I through
VI. Note that the implementations are based on radians, but there are various
helper methods to work with GeoJSON and coordinates in degrees.
Project a coordinate to a screen-space point:
import GeoProjector
let projection = Projections.Orthographic(
reference: GeoJSON.Position(latitude: 0, longitude: 100)
)
let sydney = GeoJSON.Position(latitude: -33.8, longitude: 151.3)
let projected = projection.point(
for: sydney,
size: .init(width: 100, height: 100), // the maximum size of the canvas
coordinateSystem: .topLeft
)Convert a screen-space point (e.g., the location of a click) back to a geographic coordinate:
let click = Point(x: 60, y: 40)
let geo = projection.coordinate(
at: click,
size: .init(width: 100, height: 100),
coordinateSystem: .topLeft
) // GeoJSON.Position?, nil if the click is outside the projection's imageInverse returns nil when the click sits outside the projection's image —
e.g. clicking off the globe of an Orthographic map, or beyond the bezier
outline of Equal Earth — so you can use it to filter map-area hits.
Coordinate-system handling matches the platform convention: .topLeft puts
(0, 0) at the top-left corner (UIKit, SwiftUI, SVG) and .bottomLeft puts
it at the bottom-left (mathematical / non-flipped AppKit).
GeoDrawer ships a SwiftUI view called GeoMap (backed by GeoMapView,
which is an NSView on macOS and a UIView on iOS / tvOS / visionOS). It
draws GeoJSON content with the projection of your choice and updates async
when its inputs change.
import SwiftUI
import GeoDrawer
struct MyMap: View {
var body: some View {
GeoMap(
contents: try! GeoDrawer.Content.world(),
projection: Projections.Cassini()
)
}
}You can also draw straight into a CGContext (see GeoDrawer.draw(_:in:))
or render to SVG (GeoDrawer.drawSVG(_:)).
The code in this repo is written by myself, Adrian Schönig, along recently with help from Claude but it wouldn't have been able to do this so smoothly without the help of these precious resources:
- Justin Kunimune's jkunimune/Map-Projections, which is comprehensive suite of map projections implemented in Java, including some projections of his own making.
- The comprehensive description of map projections on Wikipedia.
This library is available under the MIT License. Use it as you please according to those terms.
The examples are public domain and can be adapted freely.