# GEOSwift/GEOSwift: a type-safe Swift interface to GEOS, with a licensing note attached

> GEOSwift wraps the OSGeo GEOS library in a pure Swift, optional-aware API with generic coordinate types, so an XY polygon and an XYZ polygon are different types at compile time rather than a runtime surprise. It also carries an explicit warning that GEOS is LGPL 2.1 and that static linking is at least controversial.

**GEOSwift/GEOSwift** — The Swift Geometry Engine.

- Repository: https://github.com/GEOSwift/GEOSwift
- Stars: 1,503 · Forks: 185
- Language: Swift
- License: MIT
- Published: 2026-10-16 · Updated: 2026-10-16 · Language: en
- Canonical page: https://hysenlabs.com/projects/geoswift-geoswift

## A Swift face on a C geometry library

GEOSwift is a type-safe Swift interface to the OSGeo GEOS library routines, and its stated purpose is to let you handle a geometric object model, points, linestrings, polygons and the rest, along with the related topological operations such as intersections and overlaps.

The design goal is that the Swift layer should be idiomatic rather than a thin transliteration of the C API. The feature list describes a pure Swift, type-safe, optional-aware programming interface, Swift-native error handling, thread safety, and extensive testing. In practice that means a failed operation throws a Swift error instead of returning a null pointer, and it means the coordinate dimensionality is part of the type.

The licence picture has two layers and the project is careful about it. GEOSwift itself is MIT licensed. GEOS, the underlying library, is licensed under LGPL 2.1, and the README carries a note that its compatibility with static linking is at least controversial, with use of geos without dynamic linking discouraged.

That note is the one to read before you commit to this dependency, and it applies to how you ship your app rather than to how you write your code. An MIT wrapper around an LGPL library does not make the combination MIT. If your distribution model requires a single static binary with no dynamic loading, this is a legal question for whoever handles that, not a technical one, and the project has done you the favour of saying so up front rather than in a footnote.

## Requirements and the Swift Package Manager install

The minimum requirements are stated precisely: iOS 16.0, tvOS 16.0, macOS 13.0, watchOS 9.0, visionOS 1.0, and Linux, with Swift 5.9.

Installation is through Swift Package Manager, and it takes two edits to `Package.swift`. First the top-level dependency:

```swift
.package(url: "https://github.com/GEOSwift/GEOSwift.git", from: "12.0.0")
```

and then the target dependency, which is simply:

```swift
"GEOSwift"
```

There is one wrinkle the documentation flags, and it is the kind of thing that costs an afternoon if you hit it cold. In certain cases you may also need to explicitly include the `geos` package as a dependency, and the README points at issue 195 for the details.

The package manifest is a normal SwiftPM package with a `Package.resolved` checked in, and the repository also ships an `Example/` directory with an Xcode project containing unit and UI test targets, a `.swiftlint.yml` for linting, a `GEOSwift.xctestplan` for the test plan, and a `VERSION_5.md` file that documents the older API lineage.

Two sibling repositories extend the core, and they are separate projects rather than submodules: GEOSwiftMapKit for MapKit integration and GEOSwiftMapboxGL for MapboxGL. That split is a good sign about the core's scope, since the geometry engine does not know about map rendering at all.

## The quick start is three lines of real work

The example in the README shows the whole shape of the library, and it is small enough to memorise.

```swift
import GEOSwift

// Create a point
let point = Point(x: 4.5, y: 4.5)

// Create a polygon
let polygon = try Polygon<XY>(wkt: "POLYGON((0 0, 0 5, 5 5, 5 0, 0 0))")

// Check if the point is within the polygon
let isInside = try point.isWithin(polygon) // true
```

Three things stand out. The polygon is generic over its coordinate type, `Polygon<XY>`, which is where the type safety comes from. The WKT string is parsed at construction, so a malformed geometry fails there rather than in a later operation. And the predicate is a throwing method on the point, so the question is asked of the geometry rather than looked up in a free function with an argument list.

That shape scales up. The README documents the same style across topological operations, predicates, geometry creation from WKB and WKT, from GeoJSON, and from direct initialisers, and it has a Playground for experimenting, so the library is meant to be explored in a REPL rather than only read in source.

## Coordinate dimensionality lives in the type

GEOSwift, like GEOS, supports geometry with two, three and four coordinates, that is `XY`, `XYZ` and `XYM`, and `XYZM`. The library makes that dimension a generic parameter, so a geometry that carries three coordinates is not the same type as one that carries two.

The initialisers show how much that buys. You can build a line string from coordinate values directly, with `XYZ(1, 2, 3)` style values, or use tuples to imply the coordinate type, which is explicitly not available for `XYM`. There is a copy constructor that downcasts coordinate types, so a `LineString<XYZM>` becomes a `LineString<XYZ>`, and the README notes that the different constructions compare equal, which is what you want for a value type.

One API is marked deprecated, and the reason is instructive. Initialising from an array of `Point` values is deprecated in favour of the coordinate based initialisers, which is a hint about where the library wants its type information to live: in the coordinate type, not in a point that carries a different generic parameter than the collection you are building.

The mixing rules are the part to be careful with, and the README flags them as important for encoding, decoding and geometric operations. The safer approach when you do not know the dimensionality of incoming data is to erase it down to `XY`, which always succeeds, and work from there.

## Strict decoding, or readAny when you do not know the type

The WKB and WKT section draws a distinction that is easy to miss and expensive to learn the hard way: you can decode strictly, or you can ask what you are holding.

If you know the expected geometry and coordinate types, you decode directly from the `Geometry<>` type. The comments in the example are the specification. All valid geometries decode successfully as `XY`, so `Geometry<XY>(wkb: wkb)` never fails on dimensionality. Decoding a linestring as a point throws, and decoding a point with no Z as `Point<XYZ>` throws.

If you do not know the expected types of the encoded geometry, the readers are the way to go. `WKBReader().readAny(wkb: wkb)` returns an `AnyGeometry` enum from which you can recover both the coordinate and the geometry types by switching over the cases.

And there is a third path for the case where you do not care about the dimension at all. Erasing the coordinate type down to `XY` always succeeds, and the example switches on `anyGeometry.asXY()` to get at a point.

So there are three strategies, and picking between them is a design decision rather than an accident. Strict decoding is right when your data contract is fixed. `readAny` is right when you must handle whatever arrives. The `asXY()` erasure is right when the dimensionality is genuinely irrelevant to the operation you are about to perform.

## GeoJSON through Codable, and the NaN that comes with it

GeoJSON support is described as robust and built on `Codable`, and it comes with one decision you have to make up front: when decoding, you must specify the expected `CoordinateType` of the geometry.

Only two coordinate types are compatible, `XY` and `XYZ`, because GeoJSON does not support M coordinates. Decoding as `Geometry<XY>` drops any Z values present. Decoding as `Geometry<XYZ>` depends on a `CodingUserInfo` key named `geoJSONSetMissingZNan`. With the default of `false`, decoding throws a `GEOSwiftError.invalidCoordinates` if no Z value exists. With `true`, missing Z values are treated as `Double.nan`.

The setting is described as useful when you are dealing with geometry of unknown dimensionality and want to preserve Z where possible, or with mixed dimensionality data. The example shows the three outcomes in three lines: a plain XY decode that works, an XYZ decode that throws because there is no Z value, and the same decode with the flag turned on, which gives a NaN for Z.

The note that follows is the one to internalise. Take special care with `Double.nan` in Swift, because `Double.nan != Double.nan` and that can interfere with equality checks. The suggested mitigation is to convert the geometry to `XY` with the conversion initialisers when Z is not needed. The underlying GEOS operations generally handle Z equal to NaN fine, since Z is not considered in topological operations, but your own equality comparisons are a different matter.

So the NaN mode is safe for geometry work and hazardous for value semantics, and the decision belongs to whoever knows the shape of the incoming data.

## Where GEOSwift is the wrong tool

The first wrong case is a distribution model that cannot load a dynamic library. The project says outright that use of GEOS without dynamic linking is discouraged, and that the compatibility of the LGPL 2.1 licence with static linking is at least controversial. An app distributed through a channel that forbids dynamic loading, or an embedded product where a shared library is unacceptable, has a problem here that no amount of Swift nicety solves.

The second wrong case is a mapping application that expects a mapping library. GEOSwift is a geometry engine. It does not draw tiles, and it does not know about map views. The MapKit and MapboxGL integrations are separate repositories for that reason, and choosing this library means choosing a geometry core and then, possibly, adding one of those on top.

The third is a simple case. If you need to test whether a point is in a bounding box, this is a large dependency, with a native library behind it, for a problem that is a few lines of arithmetic. The type safety and the topology are real value, but they are value in proportion to how much geometry you are doing.

On maintenance, the version line is healthy and the major bump is documented. Release 11.1.0 was published on 2025-02-12, 11.2.0 on 2025-03-21 and 12.0.0 on 2026-09-05, with the last push on the same day, and the README carries a migration guide for anyone coming from the 11.x line. The repository is not archived, and the licence is MIT for the Swift layer.

## Conclusion

Adopt GEOSwift if you are writing Swift on Apple platforms or Linux and need a real geometry engine, since it gives you GEOS's topology with a type-safe surface and native error handling rather than a C interop layer you have to maintain yourself. Do not adopt it if you cannot ship a dynamically linked LGPL library, because the project states that use of GEOS without dynamic linking is discouraged, and do not expect it to be a mapping library, since MapKit and MapboxGL integration live in separate repositories. Verify first that your deployment can load GEOS dynamically, that your Swift and platform versions meet the stated minimums, and that the 12.0.0 major release does not break your call sites, since the repository carries a migration guide for the 11.x line.

## FAQ

### What does GEOSwift provide?

It is a type-safe Swift interface to the OSGeo GEOS library for a geometric object model of points, linestrings and polygons, with topological operations, WKT and WKB reading and writing, GeoJSON support through Codable, thread safety and Swift-native error handling.

### How do I add GEOSwift to a Swift package?

Add `.package(url: "https://github.com/GEOSwift/GEOSwift.git", from: "12.0.0")` to the top-level dependencies in `Package.swift` and `"GEOSwift"` to the target dependencies. In certain cases you may also need to include the `geos` package explicitly, as described in issue 195.

### What are the minimum requirements for GEOSwift?

iOS 16.0, tvOS 16.0, macOS 13.0, watchOS 9.0, visionOS 1.0 and Linux, with Swift 5.9. The package also works with Swift Package Manager.

### How do I decode GeoJSON when the Z coordinate may be missing?

You must specify the expected coordinate type, and only XY and XYZ are compatible. By default, decoding as `Geometry<XYZ>` throws `GEOSwiftError.invalidCoordinates` if no Z exists, but setting `geoJSONSetMissingZNan` to true in the decoder's userInfo treats missing Z values as `Double.nan`. The README warns to take care with NaN, since `Double.nan != Double.nan` can interfere with equality checks.

### Can I statically link GEOS with GEOSwift?

The project advises against it. GEOS is licensed under LGPL 2.1 and its compatibility with static linking is described as at least controversial, and the use of GEOS without dynamic linking is discouraged. GEOSwift itself is MIT licensed, but that does not change the terms of the underlying library.

## Sources

- [GEOSwift/GEOSwift on GitHub](https://github.com/GEOSwift/GEOSwift)
- [Issues](https://github.com/GEOSwift/GEOSwift/issues)
- [License: MIT](https://github.com/GEOSwift/GEOSwift/blob/main/LICENSE)
- [README](https://github.com/GEOSwift/GEOSwift/blob/main/README.md)
- [Releases](https://github.com/GEOSwift/GEOSwift/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/geoswift-geoswift
