Skip to content

maparoni/GeoProjector

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

73 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

GitHub License CI

GeoProjector

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 project and an inverse so 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.

Goals of this library

  • 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

Dependencies

This library is part of the Maparoni suite of mapping related Swift libraries and depends on:

Usage

Installation

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

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 image

Inverse 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).

Maps

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(_:)).

Credits

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.

License

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.

About

Native Swift library for drawing map projections

Topics

Resources

License

Stars

Watchers

Forks

Contributors

Languages