{"id":16754662,"url":"https://github.com/outdooractive/gis-tools","last_synced_at":"2025-03-17T02:31:10.746Z","repository":{"id":46861228,"uuid":"378939370","full_name":"Outdooractive/gis-tools","owner":"Outdooractive","description":"A collection of GIS tools, including a GeoJSON implementation with projection support and WKB/WKT coders as well as many algorithms ported from turf.js","archived":false,"fork":false,"pushed_at":"2025-03-13T15:03:17.000Z","size":556,"stargazers_count":15,"open_issues_count":24,"forks_count":6,"subscribers_count":7,"default_branch":"main","last_synced_at":"2025-03-16T07:23:04.971Z","etag":null,"topics":["algorithm","computational-geometry","geojson","geojson-data","geojson-library","geojson-parser","geospatial","gis","ios","linux","macos","swift","swiftdata","turf","tvos","watchos","wkb","wkt"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Outdooractive.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-06-21T13:26:11.000Z","updated_at":"2025-03-13T15:03:07.000Z","dependencies_parsed_at":"2023-02-09T03:00:20.882Z","dependency_job_id":"3cb0f17d-46a5-4a6c-a889-f69259cd47c6","html_url":"https://github.com/Outdooractive/gis-tools","commit_stats":{"total_commits":75,"total_committers":2,"mean_commits":37.5,"dds":"0.026666666666666616","last_synced_commit":"bf9d9a201e8e5db0438a4a1320546667326c7fc7"},"previous_names":[],"tags_count":104,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Outdooractive%2Fgis-tools","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Outdooractive%2Fgis-tools/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Outdooractive%2Fgis-tools/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Outdooractive%2Fgis-tools/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Outdooractive","download_url":"https://codeload.github.com/Outdooractive/gis-tools/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243961693,"owners_count":20375291,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["algorithm","computational-geometry","geojson","geojson-data","geojson-library","geojson-parser","geospatial","gis","ios","linux","macos","swift","swiftdata","turf","tvos","watchos","wkb","wkt"],"created_at":"2024-10-13T03:05:31.757Z","updated_at":"2025-03-17T02:31:10.196Z","avatar_url":"https://github.com/Outdooractive.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![][image-1]][1]  \n[![][image-2]][2]  \n[![](https://img.shields.io/github/license/Outdooractive/gis-tools)](https://github.com/Outdooractive/gis-tools/blob/main/LICENSE)  \n[![](https://img.shields.io/github/v/release/Outdooractive/gis-tools?sort=semver\u0026display_name=tag)](https://github.com/Outdooractive/gis-tools/releases) [![](https://img.shields.io/github/release-date/Outdooractive/gis-tools?display_date=published_at\n)](https://github.com/Outdooractive/gis-tools/releases)  \n[![](https://img.shields.io/github/issues/Outdooractive/gis-tools\n)](https://github.com/Outdooractive/gis-tools/issues) [![](https://img.shields.io/github/issues-pr/Outdooractive/gis-tools\n)](https://github.com/Outdooractive/gis-tools/pulls)  \n[![](https://img.shields.io/github/check-runs/Outdooractive/gis-tools/main)](https://github.com/Outdooractive/gis-tools/actions)\n\n\n# GISTools\nGIS tools for Swift, including a [GeoJSON][3] implementation and many algorithms ported from [https://turfjs.org][4].\n\n## Features\n\n- Supports the full [GeoJSON standard][6], with some exceptions (see [TODO.md][7])\n- Load and write GeoJSON objects from and to `[String:Any]`, `URL`, `Data` and `String`\n- Supports `Codable` and `SwiftData` (see below)\n- Supports EPSG:3857 (web mercator) and EPSG:4326 (geodetic) conversions\n- Supports WKT/WKB, also with different projections\n- Spatial search with a R-tree\n- Includes many spatial algorithms (ported from turf.js), and more to come\n- Has a helper for working with x/y/z map tiles (center/bounding box/resolution/…)\n- Can encode/decode Polylines\n- Pure Swift without external dependencies\n- Swift 6 ready\n\n## Notes\n\nThis package makes some assumptions about what is equal, i.e. coordinates that are inside of `1e-10` degrees are regarded as equal (that's μm precision and is probably overkill). See [GISTool.equalityDelta][5].\n\n## Requirements\n\nThis package requires Swift 5.10 or higher (at least Xcode 14), and compiles on iOS (\\\u003e= iOS 13), macOS (\\\u003e= macOS 10.15), tvOS (\\\u003e= tvOS 13), watchOS (\\\u003e= watchOS 6) as well as Linux.\n\n## Installation with Swift Package Manager\n\n```swift\ndependencies: [\n    .package(url: \"https://github.com/Outdooractive/gis-tools\", from: \"1.8.2\"),\n],\ntargets: [\n    .target(name: \"MyTarget\", dependencies: [\n        .product(name: \"GISTools\", package: \"gis-tools\"),\n    ]),\n]\n```\n\n## Usage\n\nPlease see also the [API documentation][8] (via Swift Package Index).\n\n```swift\nimport GISTools\n\nvar feature = Feature(Point(Coordinate3D(latitude: 3.870163, longitude: 11.518585)))\nfeature.properties = [\n    \"test\": 1,\n    \"test2\": 5.567,\n    \"test3\": [1, 2, 3],\n    \"test4\": [\n        \"sub1\": 1,\n        \"sub2\": 2\n    ]\n]\n\n// To and from String:\nlet jsonString = feature.asJsonString(prettyPrinted: true)\nlet feature = Feature(jsonString: jsonString)\n\n// To and from Data:\nlet jsonData = feature.asJsonData(prettyPrinted: true)\nlet feature = Feature(jsonData: jsonData)\n\n// Using Codable:\nlet jsonData = try JSONEncoder().encode(feature)\nlet feature = try JSONDecoder().decode(Feature.self, from: jsonData)\n\n// Generic:\nlet someGeoJson = GeoJsonReader.geoJsonFrom(json: [\n    \"type\": \"Point\",\n    \"coordinates\": [100.0, 0.0],\n])\nlet someGeoJson = GeoJsonReader.geoJsonFrom(contentsOf: URL(...))\nlet someGeoJson = GeoJsonReader.geoJsonFrom(jsonData: Data(...))\nlet someGeoJson = GeoJsonReader.geoJsonFrom(jsonString: \"{\\\"type\\\":\\\"Point\\\",\\\"coordinates\\\":[100.0,0.0]}\")\n\nswitch someGeoJson {\ncase let point as Point: ...\n}\n// or\nswitch someGeoJson.type {\ncase .point: ...\n}\n\n// Wraps *any* GeoJSON into a FeatureCollection\nlet featureCollection = FeatureCollection(jsonData: someData)\nlet featureCollection = try JSONDecoder().decode(FeatureCollection.self, from: someData)\n\n...\n```\n\nSee the [tests for more examples][9] and also the [API documentation][10].\n\n# GeoJSON\nTo quote from the [RFC 7946][11]:\n\u003e GeoJSON is a geospatial data interchange format based on JavaScript Object Notation (JSON).  \n\u003e It defines several types of JSON objects and the manner in which they are combined to represent data about geographic features, their properties, and their spatial extents.  \n\u003e GeoJSON uses a geographic coordinate reference system, World Geodetic System 1984, and units of decimal degrees.\n\nPlease read the RFC first to get an overview of what GeoJSON is and is not (in the somewhat unlikely case that you don’t already know all of this… 🙂).\n\n## GeoJson protocol\n[Implementation][12]\n\nThe basics for every GeoJSON object:\n```swift\n/// All permitted GeoJSON types.\npublic enum GeoJsonType: String {\n    case point              = \"Point\"\n    case multiPoint         = \"MultiPoint\"\n    case lineString         = \"LineString\"\n    case multiLineString    = \"MultiLineString\"\n    case polygon            = \"Polygon\"\n    case multiPolygon       = \"MultiPolygon\"\n    case geometryCollection = \"GeometryCollection\"\n    case feature            = \"Feature\"\n    case featureCollection  = \"FeatureCollection\"\n}\n\n/// GeoJSON object type.\nvar type: GeoJsonType { get }\n\n/// The GeoJSON's projection, which should typically be EPSG:4326.\nvar projection: Projection { get }\n\n/// All of the receiver's coordinates.\nvar allCoordinates: [Coordinate3D] { get }\n\n/// Any foreign members, i.e. keys in the JSON that are\n/// not part of the GeoJSON standard.\nvar foreignMembers: [String: Any] { get set }\n\n/// Try to initialize a GeoJSON object from any JSON and calculate a bounding box if necessary.\ninit?(json: Any?, calculateBoundingBox: Bool)\n\n/// Type erased equality check.\nfunc isEqualTo(_ other: GeoJson) -\u003e Bool\n```\n\n## BoundingBoxRepresentable protocol\n[Implementation][13]\n\nAll GeoJSON objects may have a bounding box. It is required though if you want to use the R-tree spatial index (see below).\n\n```swift\n/// The GeoJSON's projection.\nvar projection: Projection { get }\n\n/// The receiver's bounding box.\nvar boundingBox: BoundingBox? { get set }\n\n/// Calculates and returns the receiver's bounding box.\nfunc calculateBoundingBox() -\u003e BoundingBox?\n\n/// Calculates the receiver's bounding box and updates the `boundingBox` property.\n///\n/// - parameter ifNecessary: Only update the bounding box if the receiver doesn't already have one.\n@discardableResult\nmutating func updateBoundingBox(onlyIfNecessary ifNecessary: Bool) -\u003e BoundingBox?\n\n/// Check if the receiver is inside or crosses  the other bounding box.\n///\n/// - parameter otherBoundingBox: The bounding box to check.\nfunc intersects(_ otherBoundingBox: BoundingBox) -\u003e Bool\n```\n\n## GeoJsonConvertible protocol / GeoJsonCodable\n[Implementation][14]\n\nGeoJSON objects can be initialized from a variety of sources:\n```swift\n/// Try to initialize a GeoJSON object from any JSON.\ninit?(json: Any?)\n\n/// Try to initialize a GeoJSON object from a file.\ninit?(contentsOf url: URL)\n\n/// Try to initialize a GeoJSON object from a data object.\ninit?(jsonData: Data)\n\n/// Try to initialize a GeoJSON object from a string.\ninit?(jsonString: String)\n\n/// Try to initialize a GeoJSON object from a Decoder.\ninit(from decoder: Decoder) throws\n```\n\nThey can also be exported in several ways:\n```swift\n/// Return the GeoJson object as Key/Value pairs.\nvar asJson: [String: Any] { get }\n\n/// Dump the object as JSON data.\nfunc asJsonData(prettyPrinted: Bool = false) -\u003e Data?\n\n/// Dump the object as a JSON string.\nfunc asJsonString(prettyPrinted: Bool = false) -\u003e String?\n\n/// Write the object in it's JSON represenation to a file.\nfunc write(to url: URL, prettyPrinted: Bool = false) throws\n\n/// Write the GeoJSON object to an Encoder.\nfunc encode(to encoder: Encoder) throws\n```\n\nExample:\n```swift\nlet point = Point(jsonString: \"{\\\"type\\\":\\\"Point\\\",\\\"coordinates\\\":[100.0,0.0]}\")!\nprint(point.allCoordinates)\nprint(point.asJsonString(prettyPrinted: true)!)\n\nlet encoder = JSONEncoder()\nencoder.outputFormatting = [.prettyPrinted, .sortedKeys]\nlet data = try encoder.encode(point)\n\n// This works because `FeatureCollection` will wrap any valid GeoJSON object.\n// This is a good way to enforce a common structure for all loaded objects.\nlet featureCollection = FeatureCollection(jsonData: data)!\n```\n\nImportant note: Import and export will always be done in EPSG:4326, with one exception: GeoJSON objects with no SRID will be exported as-is.\n\n## GeoJsonReader\n[Implementation][15]\n\nThis is a generic way to create GeoJSON objects from anything that looks like GeoJSON:\n```swift\n/// Try to initialize a GeoJSON object from any JSON.\nstatic func geoJsonFrom(json: Any?) -\u003e GeoJson?\n\n/// Try to initialize a GeoJSON object from a file.\nstatic func geoJsonFrom(contentsOf url: URL) -\u003e GeoJson?\n\n/// Try to initialize a GeoJSON object from a data object.\nstatic func geoJsonFrom(jsonData: Data) -\u003e GeoJson?\n\n/// Try to initialize a GeoJSON object from a string.\nstatic func geoJsonFrom(jsonString: String) -\u003e GeoJson?\n```\n\nExample:\n```swift\nlet json: [String: Any] = [\n    \"type\": \"Point\",\n    \"coordinates\": [100.0, 0.0],\n    \"other\": \"something\",\n]\nlet geoJson = GeoJsonReader.geoJsonFrom(json: json)!\nprint(\"Type is \\(geoJson.type.rawValue)\")\nprint(\"Foreign members: \\(geoJson.foreignMembers)\")\n\nswitch geoJson {\ncase let point as Point:\n    print(\"It's a Point!\")\ncase let multiPoint as MultiPoint:\n    print(\"It's a MultiPoint!\")\ncase let lineString as LineString:\n    print(\"It's a LineString!\")\ncase let multiLineString as MultiLineString:\n    print(\"It's a MultiLineString!\")\ncase let polygon as Polygon:\n    print(\"It's a Polygon!\")\ncase let multiPolygon as MultiPolygon:\n    print(\"It's a MultiPolygon!\")\ncase let geometryCollection as GeometryCollection:\n    print(\"It's a GeometryCollection!\")\ncase let feature as Feature:\n    print(\"It's a Feature!\")\ncase let featureCollection as FeatureCollection:\n    print(\"It's a FeatureCollection!\")\ndefault: \n    assertionFailure(\"Missed an object type?\")\n}\n```\n\nImportant note: Import will always be done in EPSG:4326.\n\n## Coordinate3D\n[Implementation][16] / [Coordinate test cases][17]\n\nCoordinates are the most basic building block in this package. Every object and algorithm builds on them:\n```swift\n/// The coordinates projection, either EPSG:4326 or EPSG:3857.\nlet projection: Projection\n\n/// The coordinate's `latitude`.\nvar latitude: CLLocationDegrees\n/// The coordinate's `longitude`.\nvar longitude: CLLocationDegrees\n/// The coordinate's `altitude`.\nvar altitude: CLLocationDistance?\n\n/// Linear referencing, timestamp or whatever you want it to use for.\n///\n/// The GeoJSON specification doesn't specifiy the meaning of this value,\n/// and it doesn't guarantee that parsers won't ignore or discard it. See\n/// https://datatracker.ietf.org/doc/html/rfc7946#section-3.1.1.\n/// - Important: The JSON for a coordinate will contain a `null` altitude value\n///              if `altitude` is `nil` so that `m` won't get lost (since it is\n///              the 4th value).\n///              This might lead to compatibilty issues with other GeoJSON readers.\nvar m: Double?\n\n/// Alias for longitude\nvar x: Double { longitude }\n\n/// Alias for latitude\nvar y: Double { latitude }\n\n/// Create a coordinate with `latitude`, `longitude`, `altitude` and `m`.\n/// Projection will be EPSG:4326.\ninit(latitude: CLLocationDegrees,\n     longitude: CLLocationDegrees,\n     altitude: CLLocationDistance? = nil,\n     m: Double? = nil)\n\n/// Create a coordinate with ``x``, ``y``, ``z`` and ``m``.\n/// Default projection will we EPSG:3857 but can be overridden.\ninit(\n    x: Double,\n    y: Double,\n    z: Double? = nil,\n    m: Double? = nil,\n    projection: Projection = .epsg3857)\n\n/// Reproject this coordinate.\nfunc projected(to newProjection: Projection) -\u003e Coordinate3D\n```\n\nExample:\n```swift\nlet coordinate = Coordinate3D(latitude: 0.0, longitude: 0.0)\nprint(coordinate.isZero)\n```\n\n## BoundingBox\n[Implementation][18] / [BoundingBox test cases][19]\n\nEach GeoJSON object can have a rectangular BoundingBox (see `BoundingBoxRepresentable` above):\n```swift\n/// The bounding box's `projection`.\nlet projection: Projection\n\n/// The bounding boxes south-west (bottom-left) coordinate.\nvar southWest: Coordinate3D\n/// The bounding boxes north-east (upper-right) coordinate.\nvar northEast: Coordinate3D\n\n/// Create a bounding box with a `southWest` and `northEast` coordinate.\ninit(southWest: Coordinate3D, northEast: Coordinate3D)\n\n/// Create a bounding box from `coordinates` and an optional padding in kilometers.\ninit?(coordinates: [Coordinate3D], paddingKilometers: Double = 0.0)\n\n/// Create a bounding box from other bounding boxes.\ninit?(boundingBoxes: [BoundingBox])\n\n/// Reproject this bounding box.\nfunc projected(to newProjection: Projection) -\u003e BoundingBox\n```\n\nExample:\n```swift\nlet point = Point(Coordinat3D(latitude: 47.56, longitude: 10.22), calculateBoundingBox: true)\nprint(point.boundingBox!)\n```\n\n## Point\n[Implementation][20] / [Point test cases][21]\n\nA `Point` is a wrapper around a single coordinate:\n```swift\n/// The receiver's coordinate.\nlet coordinate: Coordinate3D\n\n/// Initialize a Point with a coordinate.\ninit(_ coordinate: Coordinate3D, calculateBoundingBox: Bool = false)\n\n/// Reproject the Point.\nfunc projected(to newProjection: Projection) -\u003e Point\n```\n\nExample:\n```swift\nlet point = Point(Coordinate3D(latitude: 47.56, longitude: 10.22))\n```\n\n## MultiPoint\n[Implementation][22] / [MultiPoint test cases][23]\n\nA `MultiPoint` is an array of coordinates:\n```swift\n/// The receiver's coordinates.\nlet coordinates: [Coordinate3D]\n\n/// The receiver’s coordinates converted to Points.\nvar points: [Point]\n\n/// Try to initialize a MultiPoint with some coordinates.\ninit?(_ coordinates: [Coordinate3D], calculateBoundingBox: Bool = false)\n\n/// Try to initialize a MultiPoint with some Points.\ninit?(_ points: [Point], calculateBoundingBox: Bool = false)\n\n/// Reproject the MultiPoint.\nfunc projected(to newProjection: Projection) -\u003e MultiPoint\n```\n\nExample:\n```swift\nlet multiPoint = MultiPoint([\n    Coordinate3D(latitude: 0.0, longitude: 100.0),\n    Coordinate3D(latitude: 1.0, longitude: 101.0)\n])!\n```\n\n## LineString\n[Implementation][24] / [LineString test cases][25]\n\n`LineString` is an array of two or more coordinates that form a line:\n```swift\n/// The LineString's coordinates.\nlet coordinates: [Coordinate3D]\n\n/// Try to initialize a LineString with some coordinates.\ninit?(_ coordinates: [Coordinate3D], calculateBoundingBox: Bool = false)\n\n/// Initialize a LineString with a LineSegment.\ninit(_ lineSegment: LineSegment, calculateBoundingBox: Bool = false)\n\n/// Try to initialize a LineString with some LineSegments.\ninit?(_ lineSegments: [LineSegment], calculateBoundingBox: Bool = false)\n\n/// Reproject the LineString.\nfunc projected(to newProjection: Projection) -\u003e LineString\n```\n\nExample:\n```swift\nlet lineString = LineString([\n    Coordinate3D(latitude: 0.0, longitude: 100.0),\n    Coordinate3D(latitude: 1.0, longitude: 101.0)\n])!\n\nlet segment = LineSegment(\n    first: Coordinate3D(latitude: 0.0, longitude: 100.0),\n    second: Coordinate3D(latitude: 1.0, longitude: 101.0))\nlet lineString = LineString(lineSegment)\n```\n\n## MultiLineString\n[Implementation][26] / [MultiLineString test cases][27]\n\nA `MultiLineString` is array of `LineString`s:\n```swift\n/// The MultiLineString's coordinates.\nlet coordinates: [[Coordinate3D]]\n\n/// The receiver’s coordinates converted to LineStrings.\nvar lineStrings: [LineString]\n\n/// Try to initialize a MultiLineString with some coordinates.\ninit?(_ coordinates: [[Coordinate3D]], calculateBoundingBox: Bool = false)\n\n/// Try to initialize a MultiLineString with some LineStrings.\ninit?(_ lineStrings: [LineString], calculateBoundingBox: Bool = false)\n\n/// Try to initialize a MultiLineString with some LineSegments. Each LineSegment will result in one LineString.\ninit?(_ lineSegments: [LineSegment], calculateBoundingBox: Bool = false)\n\n/// Reproject the MultiLineString.\nfunc projected(to newProjection: Projection) -\u003e MultiLineString\n```\n\nExample:\n```swift\nlet multiLineString = MultiLineString([\n    [Coordinate3D(latitude: 0.0, longitude: 100.0), Coordinate3D(latitude: 1.0, longitude: 101.0)],\n    [Coordinate3D(latitude: 2.0, longitude: 102.0), Coordinate3D(latitude: 3.0, longitude: 103.0)],\n])!\n```\n\n## Polygon\n[Implementation][28] / [Polygon test cases][29]\n\nA `Polygon` is a shape consisting of one or more rings, where the first ring is the outer ring bounding the surface, and the inner rings bound holes within the surface. Please see [section 3.1.6][30] in the RFC for more information.\n```swift\n/// The receiver's coordinates.\nlet coordinates: [[Coordinate3D]]\n\n/// The receiver's outer ring.\nvar outerRing: Ring?\n\n/// All of the receiver's inner rings.\nvar innerRings: [Ring]?\n\n/// All of the receiver's rings (outer + inner).\nvar rings: [Ring]\n\n/// Try to initialize a Polygon with some coordinates.\ninit?(_ coordinates: [[Coordinate3D]], calculateBoundingBox: Bool = false)\n\n/// Try to initialize a Polygon with some Rings.\ninit?(_ rings: [Ring], calculateBoundingBox: Bool = false)\n\n/// Reproject the Polygon.\nfunc projected(to newProjection: Projection) -\u003e Polygon\n```\n\nExample:\n```swift\nlet polygonWithHole = Polygon([\n    [\n        Coordinate3D(latitude: 0.0, longitude: 100.0),\n        Coordinate3D(latitude: 0.0, longitude: 101.0),\n        Coordinate3D(latitude: 1.0, longitude: 101.0),\n        Coordinate3D(latitude: 1.0, longitude: 100.0),\n        Coordinate3D(latitude: 0.0, longitude: 100.0)\n    ],\n    [\n        Coordinate3D(latitude: 1.0, longitude: 100.8),\n        Coordinate3D(latitude: 0.0, longitude: 100.8),\n        Coordinate3D(latitude: 0.0, longitude: 100.2),\n        Coordinate3D(latitude: 1.0, longitude: 100.2),\n        Coordinate3D(latitude: 1.0, longitude: 100.8)\n    ],\n])!\nprint(polygonWithHole.area)\n```\n\n## MultiPolygon\n[Implementation][31] / [MultiPolygon test cases][32]\n\nA `MultiPolygon` is an array of `Polygon`s:\n```swift\n/// The receiver's coordinates.\nlet coordinates: [[[Coordinate3D]]]\n\n/// The receiver’s coordinates converted to Polygons.\nvar polygons: [Polygon]\n\n/// Try to initialize a MultiPolygon with some coordinates.\ninit?(_ coordinates: [[[Coordinate3D]]], calculateBoundingBox: Bool = false)\n\n/// Try to initialize a MultiPolygon with some Polygons.\ninit?(_ polygons: [Polygon], calculateBoundingBox: Bool = false)\n\n/// Reproject the MultiPolygon.\nfunc projected(to newProjection: Projection) -\u003e MultiPolygon\n```\n\nExample:\n```swift\nlet multiPolygon = MultiPolygon([\n    [\n        [\n            Coordinate3D(latitude: 2.0, longitude: 102.0),\n            Coordinate3D(latitude: 2.0, longitude: 103.0),\n            Coordinate3D(latitude: 3.0, longitude: 103.0),\n            Coordinate3D(latitude: 3.0, longitude: 102.0),\n            Coordinate3D(latitude: 2.0, longitude: 102.0),\n        ]\n    ],\n    [\n        [\n            Coordinate3D(latitude: 0.0, longitude: 100.0),\n            Coordinate3D(latitude: 0.0, longitude: 101.0),\n            Coordinate3D(latitude: 1.0, longitude: 101.0),\n            Coordinate3D(latitude: 1.0, longitude: 100.0),\n            Coordinate3D(latitude: 0.0, longitude: 100.0),\n        ],\n        [\n            Coordinate3D(latitude: 0.0, longitude: 100.2),\n            Coordinate3D(latitude: 1.0, longitude: 100.2),\n            Coordinate3D(latitude: 1.0, longitude: 100.8),\n            Coordinate3D(latitude: 0.0, longitude: 100.8),\n            Coordinate3D(latitude: 0.0, longitude: 100.2),\n        ]\n    ]\n])!\n```\n\n## GeometryCollection\n[Implementation][33] / [GeometryCollection test cases][34]\n\nA `GeometryCollection` is an array of GeoJSON geometries, i.e. `Point`, `MultiPoint`, `LineString`, `MultiLineString`, `Polygon`, `MultiPolygon` or even `GeometryCollection`, though the latter is not recommended. Please see [section 3.1.8][35] in the RFC for more information.\n```swift\n/// The GeometryCollection's geometry objects.\nlet geometries: [GeoJsonGeometry]\n\n/// Initialize a GeometryCollection with a geometry object.\ninit(_ geometry: GeoJsonGeometry, calculateBoundingBox: Bool = false)\n\n/// Initialize a GeometryCollection with some geometry objects.\ninit(_ geometries: [GeoJsonGeometry], calculateBoundingBox: Bool = false)\n\n/// Reproject the GeometryCollection.\nfunc projected(to newProjection: Projection) -\u003e GeometryCollection\n```\n\n## Feature\n[Implementation][36] / [Feature test cases][37]\n\nA `Feature` is sort of a container for exactly one GeoJSON geometry (`Point`, `MultiPoint`, `LineString`, `MultiLineString`, `Polygon`, `MultiPolygon`, `GeometryCollection`) together with some `properties` and an optional `id`:\n```swift\n/// A GeoJSON identifier that can either be a string or number.\n/// Any parsed integer value `Int64.min ⪬ i ⪬ Int64.max`  will be cast to `Int`\n/// (or `Int64` on 32-bit platforms), values above `Int64.max` will be cast to `UInt`\n/// (or `UInt64` on 32-bit platforms).\nenum Identifier: Equatable, Hashable, CustomStringConvertible {\n    case string(String)\n    case int(Int)\n    case uint(UInt)\n    case double(Double)\n}\n\n/// An arbitrary identifier.\nvar id: Identifier?\n\n/// The `Feature`s geometry object.\nlet geometry: GeoJsonGeometry\n\n/// Only 'Feature' objects may have properties.\nvar properties: [String: Any]\n\n/// Create a ``Feature`` from any ``GeoJsonGeometry`` object.\ninit(_ geometry: GeoJsonGeometry,\n     id: Identifier? = nil,\n     properties: [String: Any] = [:],\n     calculateBoundingBox: Bool = false)\n\n/// Reproject the Feature.\nfunc projected(to newProjection: Projection) -\u003e Feature\n```\n\n## FeatureCollection\n[Implementation][38] / [FeatureCollection test cases][39]\n\nA `FeatureCollection` is an array of `Feature` objects:\n```swift\n/// The FeatureCollection's Feature objects.\nprivate(set) var features: [Feature]\n\n/// Initialize a FeatureCollection with one Feature.\ninit(_ feature: Feature, calculateBoundingBox: Bool = false)\n\n/// Initialize a FeatureCollection with some geometry objects.\ninit(_ geometries: [GeoJsonGeometry], calculateBoundingBox: Bool = false)\n\n/// Normalize any GeoJSON object into a FeatureCollection.\ninit?(_ geoJson: GeoJson?, calculateBoundingBox: Bool = false)\n\n/// Reproject the FeatureCollection.\nfunc projected(to newProjection: Projection) -\u003e FeatureCollection\n```\n\nThis type is somewhat special since its initializers will accept any valid GeoJSON object and return a `FeatureCollection` with the input wrapped in `Feature` objects if the input are geometries, or by collecting the input if it’s a `Feature`.\n\n# SwiftData\n\nYou need to use a transformer for using GeoJson with SwiftData (also have a look at the [SwiftData test cases](https://github.com/Outdooractive/gis-tools/blob/main/Tests/GISToolsTests/GeoJson/SwiftDataTests.swift)).\n\nFirst, register the transformer like this:\n```swift\nGeoJsonTransformer.register()\n```\n\nThen create your models like this:\n```swift\n@Attribute(.transformable(by: GeoJsonTransformer.name.rawValue)) var geoJson: GeoJson?\n@Attribute(.transformable(by: GeoJsonTransformer.name.rawValue)) var point: Point?\n...\n```\n\nThis is necessary because SwiftData doesn't work well with the default Codable implementation, so you need to do the serialization for yourself...\n\n# WKB/WKT\nThe following geometry types are supported: `point`, `linestring`, `linearring`, `polygon`, `multipoint`, `multilinestring`, `multipolygon`, `geometrycollection` and `triangle`. Please open an issue if you need more.\n\nEvery GeoJSON object has convenience methods to encode and decode themselves to and from WKB/WKT, and there are extensions for `Data` and `String` to decode from WKB and WKT to GeoJSON. In the end, they all forward to `WKBCoder` and `WKTCoder` which do the heavy lifting.\n\n## WKB\nAlso have a look at  the [WKB test cases][40].\n\nDecoding:\n```swift\n// SELECT 'POINT Z (1 2 3)'::geometry;\nprivate let pointZData = Data(hex: \"0101000080000000000000F03F00000000000000400000000000000840\")!\n\n// Generic\nlet point = try WKBCoder.decode(wkb: pointData, sourceProjection: .epsg4326) as! Point\nlet point = pointZData.asGeoJsonGeometry(sourceProjection: .epsg4326) as! Point\n\n// Or create the geometry directly\nlet point = Point(wkb: pointZData, sourceProjection: .epsg4326)!\n\n// Or create a Feature that contains the geometry\nlet feature = Feature(wkb: pointZData, sourceProjection: .epsg4326)\nlet feature = pointZData.asFeature(sourceProjection: .epsg4326)\n\n// Or create a FeatureCollection that contains a feature with the geometry\nlet featureCollection = FeatureCollection(wkb: pointZData, sourceProjection: .epsg4326)\nlet featureCollection = pointZData.asFeatureCollection(sourceProjection: .epsg4326)\n\n// Can also reproject on the fly\nlet point = try WKBCoder.decode(\n    wkb: pointData,\n    sourceProjection: .epsg4326,\n    targetProjection: .epsg3857\n) as! Point\nprint(point.projection)\n```\n\nEncoding:\n```swift\nlet point = Point(Coordinate3D(latitude: 0.0, longitude: 100.0))\n\n// Generic\nlet encodedPoint = WKBCoder.encode(geometry: point, targetProjection: nil)\n\n// Convenience\nlet encodedPoint = point.asWKB\n```\n\n## WKT\nThis is exactly the same as WKB… Also have a look at the tests to see how it works: [WKT test cases][41]\n\nDecoding:\n```swift\nprivate let pointZString = \"POINT Z (1 2 3)\"\n\n// Generic\nlet point = try WKTCoder.decode(wkt: pointZString, sourceProjection: .epsg4326) as! Point\nlet point = pointZString.asGeoJsonGeometry(sourceProjection: .epsg4326) as! Point\n\n// Or create the geometry directly\nlet point = Point(wkt: pointZString, sourceProjection: .epsg4326)!\n\n// Or create a Feature that contains the geometry\nlet feature = Feature(wkt: pointZString, sourceProjection: .epsg4326)\nlet feature = pointZString.asFeature(sourceProjection: .epsg4326)\n\n// Or create a FeatureCollection that contains a feature with the geometry\nlet featureCollection = FeatureCollection(wkt: pointZString, sourceProjection: .epsg4326)\nlet featureCollection = pointZString.asFeatureCollection(sourceProjection: .epsg4326)\n\n// Can also reproject on the fly\nlet point = try WKTCoder.decode(\n    wkt: pointZString,\n    sourceProjection: .epsg4326,\n    targetProjection: .epsg3857\n) as! Point\nprint(point.projection) // EPSG:3857\n```\n\nEncoding:\n```swift\nlet point = Point(Coordinate3D(latitude: 0.0, longitude: 100.0))\n\n// Generic\nlet encodedPoint = WKTCoder.encode(geometry: point, targetProjection: nil)\n\n// Convenience\nlet encodedPoint = point.asWKT\n```\n\n# Spatial index\nThis package includes a simple R-tree implementation: [RTree test cases][42]\n\n```swift\nvar nodes: [Point] = []\n50.times {\n    nodes.append(Point(Coordinate3D(\n        latitude: Double.random(in: -10.0 ... 10.0),\n        longitude: Double.random(in: -10.0 ... 10.0))))\n    }\n\nlet rTree = RTree(nodes)\nlet objects = rTree.search(inBoundingBox: boundingBox)\nlet objectsAround = rTree.search(aroundCoordinate: center, maximumDistance: maximumDistance)\n```\n\n# MapTile\nThis is a helper for working with x/y/z map tiles.\n\n```swift\nlet tile1 = MapTile(x: 138513, y: 91601, z: 18)\nlet center = tile1.centerCoordinate(projection: .epsg4326) // default\nlet boundingBox = tile1.boundingBox(projection: .epsg4326) // default\n\nlet tile2 = MapTile(coordinate: Coordinate3D(latitude: 47.56, longitude: 10.22), atZoom: 14)\nlet parent = tile2.parent\nlet firstChild = tile2.child\nlet allChildren = tile2.children\n\nlet quadkey = tile1.quadkey\nlet tile3 = MapTile(quadkey: \"1202211303220032\")\n```\n\nAlso, not directly related to map tiles:\n```swift\nlet mpp = MapTile.metersPerPixel(at: 15.0, latitude: 45.0)\n```\n\n# Polylines\nProvides an encoder/decoder for Polylines.\n\n```swift\nlet polyline = [Coordinate3D(latitude: 47.56, longitude: 10.22)].encodePolyline()\nlet coordinates = polyline.decodePolyline()\n```\n\n# Algorithms\nHint: Most algorithms are optimized for EPSG:4326. Using other projections will have a performance penalty due to added projections.\n\n| Name                        | Example                                                                                                                               |     | Source/Tests                 |\n| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | --- | ---------------------------- |\n| along                       | `let coordinate = lineString.coordinateAlong(distance: 100.0)`                                                                        |     | [Source][43] / [Tests][44]   |\n| area                        | `Polygon(…).area`                                                                                                                     |     | [Source][45]                 |\n| bearing                     | `Coordinate3D(…).bearing(to: Coordinate3D(…))`                                                                                        |     | [Source][46] / [Tests][47]   |\n| boolean-clockwise           | `Polygon(…).outerRing?.isClockwise`                                                                                                   |     | [Source][48] / [Tests][49]   |\n| boolean-crosses             | TODO                                                                                                                                  |     | [Source][50]                 |\n| boolean-disjoint            | `let result = polygon.isDisjoint(with: lineString)`                                                                                   |     | [Source][126] / [Tests][127] |\n| boolean-intersects          | `let result = polygon.intersects(with: lineString)`                                                                                   |     | [Source][128]                |\n| boolean-overlap             | `lineString1.isOverlapping(with: lineString2)`                                                                                        |     | [Source][52] / [Tests][53]   |\n| boolean-parallel            | `lineString1.isParallel(to: lineString2)`                                                                                             |     | [Source][54] / [Tests][55]   |\n| boolean-point-in-polygon    | `polygon.contains(Coordinate3D(…))`                                                                                                   |     | [Source][56]                 |\n| boolean-point-on-line       | `lineString.checkIsOnLine(Coordinate3D(…))`                                                                                           |     | [Source][57]                 |\n| boolean-valid               | `anyGeometry.isValid`                                                                                                                 |     | [Source][58]                 |\n| bbox-clip                   | `let clipped = lineString.clipped(to: boundingBox)`                                                                                   |     | [Source][59] / [Tests][60]   |\n| buffer                      | TODO                                                                                                                                  |     | [Source][61]                 |\n| center/centroid/center-mean | `let center = polygon.center`                                                                                                         |     | [Source][62]                 |\n| circle                      | `let circle = point.circle(radius: 5000.0)`                                                                                           |     | [Source][63] / [Tests][64]   |\n| conversions/helpers         | `let distance = GISTool.convert(length: 1.0, from: .miles, to: .meters)`                                                              |     | [Source][65]                 |\n| destination                 | `let destination = coordinate.destination(distance: 1000.0, bearing: 173.0)`                                                          |     | [Source][66] / [Tests][67]   |\n| distance                    | `let distance = coordinate1.distance(from: coordinate2)`                                                                              |     | [Source][68] / [Tests][69]   |\n| flatten                     | `let featureCollection = anyGeometry.flattened`                                                                                       |     | [Source][70] / [Tests][71]   |\n| frechetDistance             | `let distance = lineString.frechetDistance(from: other)`                                                                              |     | [Source][72] / [Tests][73]   |\n| length                      | `let length = lineString.length`                                                                                                      |     | [Source][74] / [Tests][75]   |\n| line-arc                    | `let lineArc = point.lineArc(radius: 5000.0, bearing1: 20.0, bearing2: 60.0)`                                                         |     | [Source][76] / [Tests][77]   |\n| line-chunk                  | `let chunks = lineString.chunked(segmentLength: 1000.0).lineStrings` `let dividedLine = lineString.evenlyDivided(segmentLength: 1.0)` |     | [Source][78] / [Tests][79]   |\n| line-intersect              | `let intersections = feature1.intersections(other: feature2)`                                                                         |     | [Source][80] / [Tests][81]   |\n| line-overlap                | `let overlappingSegments = lineString1.overlappingSegments(with: lineString2)`                                                        |     | [Source][82] / [Tests][83]   |\n| line-segments               | `let segments = anyGeometry.lineSegments`                                                                                             |     | [Source][84] / [Tests][85]   |\n| line-slice                  | `let slice = lineString.slice(start: Coordinate3D(…), end: Coordinate3D(…))`                                                          |     | [Source][86] / [Tests][87]   |\n| line-slice-along            | `let sliced = lineString.sliceAlong(startDistance: 50.0, stopDistance: 2000.0)`                                                       |     | [Source][88] / [Tests][89]   |\n| midpoint                    | `let middle = coordinate1.midpoint(to: coordinate2)`                                                                                  |     | [Source][90] / [Tests][91]   |\n| nearest-point               | `let nearest = anyGeometry.nearestCoordinate(from: Coordinate3D(…))`                                                                  |     | [Source][92]                 |\n| nearest-point-on-feature    | `let nearest = anyGeometry. nearestCoordinateOnFeature(from: Coordinate3D(…))`                                                        |     | [Source][93]                 |\n| nearest-point-on-line       | `let nearest = lineString.nearestCoordinateOnLine(from: Coordinate3D(…))?.coordinate`                                                 |     | [Source][94] / [Tests][95]   |\n| nearest-point-to-line       | `let nearest = lineString. nearestCoordinate(outOf: coordinates)`                                                                     |     | [Source][96]                 |\n| point-on-feature            | `let coordinate = anyGeometry.coordinateOnFeature`                                                                                    |     | [Source][97]                 |\n| points-within-polygon       | `let within = polygon.coordinatesWithin(coordinates)`                                                                                 |     | [Source][98]                 |\n| point-to-line-distance      | `let distance = lineString.distanceFrom(coordinate: Coordinate3D(…))`                                                                 |     | [Source][99] / [Tests][100]  |\n| pole-of-inaccessibility     | TODO                                                                                                                                  |     | [Source][101]                |\n| polygon-to-line             | `var lineStrings = polygon.lineStrings`                                                                                               |     | [Source][129]                |\n| reverse                     | `let lineStringReversed = lineString.reversed`                                                                                        |     | [Source][102] / [Tests][103] |\n| rhumb-bearing               | `let bearing = start.rhumbBearing(to: end)`                                                                                           |     | [Source][104] / [Tests][105] |\n| rhumb-destination           | `let destination = coordinate.rhumbDestination(distance: 1000.0, bearing: 0.0)`                                                       |     | [Source][106] / [Tests][107] |\n| rhumb-distance              | `let distance = coordinate1.rhumbDistance(from: coordinate2)`                                                                         |     | [Source][108] / [Tests][109] |\n| simplify                    | `let simplified = lineString. simplified(tolerance: 5.0, highQuality: false)`                                                         |     | [Source][110] / [Tests][111] |\n| tile-cover                  | `let tileCover = anyGeometry.tileCover(atZoom: 14)`                                                                                   |     | [Source][112] / [Tests][113] |\n| transform-coordinates       | `let transformed = anyGeometry.transformCoordinates({ $0 })`                                                                          |     | [Source][114] / [Tests][115] |\n| transform-rotate            | `let transformed = anyGeometry. transformedRotate(angle: 25.0, pivot: Coordinate3D(…))`                                               |     | [Source][116] / [Tests][117] |\n| transform-scale             | `let transformed = anyGeometry. transformedScale(factor: 2.5, anchor: .center)`                                                       |     | [Source][118] / [Tests][119] |\n| transform-translate         | `let transformed = anyGeometry. transformedTranslate(distance: 1000.0, direction: 25.0)`                                              |     | [Source][120] / [Tests][121] |\n| truncate                    | `let truncated = lineString.truncated(precision: 2, removeAltitude: true)`                                                            |     | [Source][122] / [Tests][123] |\n| union                       | TODO                                                                                                                                  |     | [Source][124]                |\n\n# Related packages\nCurrently only two:\n- [mvt-tools][125]: Vector tiles reader/writer for Swift\n- [mvt-postgis][130]: Creates vector tiles from Postgis databases\n\n# Contributing\nPlease [create an issue](https://github.com/Outdooractive/gis-tools/issues) or [open a pull request](https://github.com/Outdooractive/gis-tools/pulls) with a fix or enhancement.\n\n# License\nMIT\n\n# Authors\nThomas Rasch, Outdooractive\n\n[1]:\thttps://swiftpackageindex.com/Outdooractive/gis-tools\n[2]:\thttps://swiftpackageindex.com/Outdooractive/gis-tools\n[3]:\thttps://www.rfc-editor.org/rfc/rfc7946\n[4]:\thttps://github.com/Turfjs/turf/tree/master/packages\n[5]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Sources/GISTools/GISTool.swift\n[6]:\thttps://www.rfc-editor.org/rfc/rfc7946\n[7]:\thttps://github.com/Outdooractive/gis-tools/blob/main/TODO.md\n[8]:\thttps://swiftpackageindex.com/Outdooractive/gis-tools/main/documentation/gistools\n[9]:\thttps://github.com/Outdooractive/gis-tools/tree/main/Tests/GISToolsTests/GeoJson\n[10]:\thttps://swiftpackageindex.com/Outdooractive/gis-tools/main/documentation/gistools\n[11]:\thttps://www.rfc-editor.org/rfc/rfc7946\n[12]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Sources/GISTools/GeoJson/GeoJson.swift \"GeoJson.swift\"\n[13]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Sources/GISTools/GeoJson/BoundingBoxRepresentable.swift\n[14]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Sources/GISTools/GeoJson/GeoJsonConvertible.swift\n[15]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Sources/GISTools/GeoJson/GeoJsonReader.swift\n[16]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Sources/GISTools/GeoJson/Coordinate3D.swift\n[17]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Tests/GISToolsTests/GeoJson/CoordinateTests.swift\n[18]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Sources/GISTools/GeoJson/BoundingBox.swift\n[19]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Tests/GISToolsTests/GeoJson/BoundingBoxTests.swift\n[20]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Sources/GISTools/GeoJson/Point.swift\n[21]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Tests/GISToolsTests/GeoJson/PointTests.swift\n[22]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Sources/GISTools/GeoJson/MultiPoint.swift\n[23]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Tests/GISToolsTests/GeoJson/MultiPointTests.swift\n[24]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Sources/GISTools/GeoJson/LineString.swift\n[25]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Tests/GISToolsTests/GeoJson/LineStringTests.swift\n[26]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Sources/GISTools/GeoJson/MultiLineString.swift\n[27]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Tests/GISToolsTests/GeoJson/MultiLineStringTests.swift\n[28]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Sources/GISTools/GeoJson/Polygon.swift\n[29]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Tests/GISToolsTests/GeoJson/PolygonTests.swift\n[30]:\thttps://www.rfc-editor.org/rfc/rfc7946#section-3.1.6 \"3.1.6\"\n[31]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Sources/GISTools/GeoJson/MultiPolygon.swift\n[32]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Tests/GISToolsTests/GeoJson/MultiPolygonTests.swift\n[33]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Sources/GISTools/GeoJson/GeometryCollection.swift\n[34]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Tests/GISToolsTests/GeoJson/GeometryCollectionTests.swift\n[35]:\thttps://www.rfc-editor.org/rfc/rfc7946#section-3.1.8 \"3.1.8\"\n[36]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Sources/GISTools/GeoJson/Feature.swift\n[37]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Tests/GISToolsTests/GeoJson/FeatureTests.swift\n[38]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Sources/GISTools/GeoJson/FeatureCollection.swift\n[39]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Tests/GISToolsTests/GeoJson/FeatureCollectionTests.swift\n[40]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Tests/GISToolsTests/GeoJson/WKBTests.swift\n[41]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Tests/GISToolsTests/GeoJson/WKTTests.swift\n[42]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Tests/GISToolsTests/GeoJson/RTreeTests.swift\n[43]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Sources/GISTools/Algorithms/Along.swift \"Along\"\n[44]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Tests/GISToolsTests/Algorithms/AlongTests.swift \"AlongTests\"\n[45]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Sources/GISTools/Algorithms/Area.swift \"Area\"\n[46]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Sources/GISTools/Algorithms/Bearing.swift \"Bearing\"\n[47]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Tests/GISToolsTests/Algorithms/BearingTests.swift \"BearingTests\"\n[48]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Sources/GISTools/Algorithms/BooleanClockwise.swift \"BooleanClockwise\"\n[49]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Tests/GISToolsTests/Algorithms/BooleanClockwiseTests.swift \"BooleanClockwiseTests\"\n[50]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Sources/GISTools/Algorithms/BooleanCrosses.swift \"BooleanCrosses\"\n[51]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Sources/GISTools/Algorithms/BooleanIntersects.swift \"BooleanIntersects\"\n[52]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Sources/GISTools/Algorithms/BooleanOverlap.swift \"BooleanOverlap\"\n[53]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Tests/GISToolsTests/Algorithms/BooleanOverlapTests.swift \"BooleanOverlapTests\"\n[54]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Sources/GISTools/Algorithms/BooleanParallel.swift \"BooleanParallel\"\n[55]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Tests/GISToolsTests/Algorithms/BooleanParallelTests.swift \"BooleanParallelTests\"\n[56]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Sources/GISTools/Algorithms/BooleanPointInPolygon.swift \"BooleanPointInPolygon\"\n[57]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Sources/GISTools/Algorithms/BooleanPointOnLine.swift \"BooleanPointOnLine\"\n[58]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Sources/GISTools/Algorithms/Validatable.swift \"Validatable\"\n[59]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Sources/GISTools/Algorithms/BoundingBoxClip.swift \"BoundingBoxClip\"\n[60]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Tests/GISToolsTests/Algorithms/BoundingBoxClipTests.swift \"BoundingBoxClipTests\"\n[61]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Sources/GISTools/Algorithms/Buffer.swift \"Buffer\"\n[62]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Sources/GISTools/Algorithms/Center.swift \"Center\"\n[63]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Sources/GISTools/Algorithms/Circle.swift \"Circle\"\n[64]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Tests/GISToolsTests/Algorithms/CircleTests.swift \"CircleTests\"\n[65]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Sources/GISTools/Algorithms/Conversions.swift \"Conversions\"\n[66]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Sources/GISTools/Algorithms/Destination.swift \"Destination\"\n[67]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Tests/GISToolsTests/Algorithms/DestinationTests.swift \"DestinationTests\"\n[68]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Sources/GISTools/Algorithms/Distance.swift \"Distance\"\n[69]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Tests/GISToolsTests/Algorithms/DistanceTests.swift \"DistanceTests\"\n[70]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Sources/GISTools/Algorithms/Flatten.swift \"Flatten\"\n[71]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Tests/GISToolsTests/Algorithms/FlattenTests.swift \"FlattenTests\"\n[72]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Sources/GISTools/Algorithms/FrechetDistance.swift \"FrechetDistance\"\n[73]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Tests/GISToolsTests/Algorithms/FrechetDistanceTests.swift \"FrechetDistanceTests\"\n[74]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Sources/GISTools/Algorithms/Length.swift \"Length\"\n[75]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Tests/GISToolsTests/Algorithms/LengthTests.swift \"LengthTests\"\n[76]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Sources/GISTools/Algorithms/LineArc.swift \"LineArc\"\n[77]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Tests/GISToolsTests/Algorithms/LineArcTests.swift \"LineArcTests\"\n[78]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Sources/GISTools/Algorithms/LineChunk.swift \"LineChunk\"\n[79]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Tests/GISToolsTests/Algorithms/LineChunkTests.swift \"LineChunkTests\"\n[80]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Sources/GISTools/Algorithms/LineIntersect.swift \"LineIntersect\"\n[81]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Tests/GISToolsTests/Algorithms/LineIntersectionTests.swift \"LineIntersectionTests\"\n[82]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Sources/GISTools/Algorithms/LineOverlap.swift \"LineOverlap\"\n[83]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Tests/GISToolsTests/Algorithms/LineOverlapTests.swift \"LineOverlapTests\"\n[84]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Sources/GISTools/Algorithms/LineSegments.swift \"LineSegments\"\n[85]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Tests/GISToolsTests/Algorithms/LineSegmentsTests.swift \"LineSegmentsTests\"\n[86]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Sources/GISTools/Algorithms/LineSlice.swift \"LineSlice\"\n[87]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Tests/GISToolsTests/Algorithms/LineSliceTests.swift \"LineSliceTests\"\n[88]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Sources/GISTools/Algorithms/LineSliceAlong.swift \"LineSliceAlong\"\n[89]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Tests/GISToolsTests/Algorithms/LineSliceAlongTests.swift \"LineSliceAlongTests\"\n[90]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Sources/GISTools/Algorithms/MidPoint.swift \"MidPoint\"\n[91]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Tests/GISToolsTests/Algorithms/MidPointTests.swift \"MidPointTests\"\n[92]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Sources/GISTools/Algorithms/NearestPoint.swift \"NearestPoint\"\n[93]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Sources/GISTools/Algorithms/NearestPointOnFeature.swift \"NearestPointOnFeature\"\n[94]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Sources/GISTools/Algorithms/NearestPointOnLine.swift \"NearestPointOnLine\"\n[95]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Tests/GISToolsTests/Algorithms/NearestCoordinateOnLineTests.swift \"NearestCoordinateOnLineTests\"\n[96]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Sources/GISTools/Algorithms/NearestPointToLine.swift \"NearestPointToLine\"\n[97]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Sources/GISTools/Algorithms/PointOnFeature.swift \"PointOnFeature\"\n[98]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Sources/GISTools/Algorithms/PointsWithinPolygon.swift \"PointsWithinPolygon\"\n[99]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Sources/GISTools/Algorithms/PointToLineDistance.swift \"PointToLineDistance\"\n[100]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Tests/GISToolsTests/Algorithms/PointToLineDistanceTests.swift \"PointToLineDistanceTests\"\n[101]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Sources/GISTools/Algorithms/PoleOfInaccessibility.swift \"PoleOfInaccessibility\"\n[102]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Sources/GISTools/Algorithms/Reverse.swift \"Reverse\"\n[103]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Tests/GISToolsTests/Algorithms/ReverseTests.swift \"ReverseTests\"\n[104]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Sources/GISTools/Algorithms/RhumbBearing.swift \"RhumbBearing\"\n[105]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Tests/GISToolsTests/Algorithms/RhumbBearingTests.swift \"RhumbBearingTests\"\n[106]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Sources/GISTools/Algorithms/RhumbDestination.swift \"RhumbDestination\"\n[107]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Tests/GISToolsTests/Algorithms/RhumbDestinationTests.swift \"RhumbDestinationTests\"\n[108]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Sources/GISTools/Algorithms/RhumbDistance.swift \"RhumbDistance\"\n[109]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Tests/GISToolsTests/Algorithms/RhumbDistanceTests.swift \"RhumbDistanceTests\"\n[110]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Sources/GISTools/Algorithms/Simplify.swift \"Simplify\"\n[111]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Tests/GISToolsTests/Algorithms/SimplifyTests.swift \"SimplifyTests\"\n[112]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Sources/GISTools/Algorithms/TileCover.swift \"TileCover\"\n[113]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Tests/GISToolsTests/Algorithms/TileCoverTests.swift \"TileCoverTests\"\n[114]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Sources/GISTools/Algorithms/TransformCoordinates.swift \"TransformCoordinates\"\n[115]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Tests/GISToolsTests/Algorithms/TransformCoordinatesTests.swift \"TransformCoordinatesTests\"\n[116]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Sources/GISTools/Algorithms/TransformRotate.swift \"TransformRotate\"\n[117]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Tests/GISToolsTests/Algorithms/TransformRotateTests.swift \"TransformRotateTests\"\n[118]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Sources/GISTools/Algorithms/TransformScale.swift \"TransformScale\"\n[119]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Tests/GISToolsTests/Algorithms/TransformScaleTests.swift \"TransformScaleTests\"\n[120]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Sources/GISTools/Algorithms/TransformTranslate.swift \"TransformTranslate\"\n[121]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Tests/GISToolsTests/Algorithms/TransformTranslateTests.swift \"TransformTranslateTests\"\n[122]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Sources/GISTools/Algorithms/Truncate.swift \"Truncate\"\n[123]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Tests/GISToolsTests/Algorithms/TruncateTests.swift \"TruncateTests\"\n[124]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Sources/GISTools/Algorithms/Union.swift \"Union\"\n[125]:\thttps://github.com/Outdooractive/mvt-tools\n[126]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Sources/GISTools/Algorithms/BooleanDisjoint.swift \"BooleanDisjoint\"\n[127]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Tests/GISToolsTests/Algorithms/BooleanDisjointTests.swift \"BooleanDisjointTests\"\n[128]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Sources/GISTools/Algorithms/BooleanIntersects.swift \"BooleanIntersects\"\n[129]:\thttps://github.com/Outdooractive/gis-tools/blob/main/Sources/GISTools/Algorithms/PoygonToLine.swift \"PoygonToLine\"\n[130]:  https://github.com/Outdooractive/mvt-postgis\n\n[image-1]:\thttps://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2FOutdooractive%2Fgis-tools%2Fbadge%3Ftype%3Dswift-versions\n[image-2]:\thttps://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2FOutdooractive%2Fgis-tools%2Fbadge%3Ftype%3Dplatforms\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foutdooractive%2Fgis-tools","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Foutdooractive%2Fgis-tools","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foutdooractive%2Fgis-tools/lists"}