├── .github
└── workflows
│ └── main.yml
├── .gitignore
├── .swiftlint.yml
├── .swiftpm
└── xcode
│ ├── package.xcworkspace
│ └── contents.xcworkspacedata
│ └── xcshareddata
│ └── xcschemes
│ └── GEOSwiftMapKit.xcscheme
├── CHANGELOG.md
├── GEOSwiftMapKit.podspec
├── GEOSwiftMapKit.xctestplan
├── LICENSE
├── Package.resolved
├── Package.swift
├── Playgrounds
└── GEOSwiftMapKit.playground
│ ├── Contents.swift
│ ├── Resources
│ ├── example.wkb
│ └── multipolygon.geojson
│ ├── contents.xcplayground
│ └── playground.xcworkspace
│ └── contents.xcworkspacedata
├── README-images
└── playground.png
├── README.md
├── Sources
└── GEOSwiftMapKit
│ ├── GEOSwift+MapKit.swift
│ └── GEOSwift+MapKitQuickLook.swift
└── Tests
├── .swiftlint.yml
└── GEOSwiftMapKitTests
├── GEOSwift+MapKitTests.swift
└── MapKit+Equatable.swift
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: GEOSwiftMapKit
3 |
4 | on:
5 | push:
6 | branches: [main]
7 | pull_request:
8 | branches: [main]
9 |
10 | jobs:
11 | swiftlint:
12 | runs-on: macos-14
13 | steps:
14 | - uses: actions/checkout@v3
15 | - name: Install SwiftLint
16 | run: brew install swiftlint
17 | - name: Swiftlint
18 | run: swiftlint --strict
19 | podspec:
20 | name: Lint Podspec for ${{ matrix.platform }}
21 | runs-on: macos-14
22 | strategy:
23 | matrix:
24 | platform: [ios, macos, tvos]
25 | steps:
26 | - uses: actions/checkout@v3
27 | - name: Update CocoaPods Specs
28 | run: pod repo update
29 | - name: Lint Podspec
30 | run: pod lib lint --platforms=${{ matrix.platform }}
31 | xcodebuild:
32 | name: ${{ matrix.name }}
33 | runs-on: ${{ matrix.os }}
34 | strategy:
35 | matrix:
36 | include:
37 | - name: "xcodebuild (iOS 18.0, Xcode 16.0)"
38 | os: macos-14
39 | xcode-version: "16"
40 | sdk: iphonesimulator18.0
41 | destination: "platform=iOS Simulator,OS=18.0,name=iPhone 16"
42 | - name: "xcodebuild (tvOS 18.0, Xcode 16.0)"
43 | os: macos-14
44 | xcode-version: "16"
45 | sdk: appletvsimulator18.0
46 | destination: "platform=tvOS Simulator,OS=18.0,name=Apple TV"
47 | - name: "xcodebuild (macOS 15.0, Xcode 16.0)"
48 | os: macos-14
49 | xcode-version: "16"
50 | sdk: macosx15.0
51 | destination: "platform=OS X"
52 | - name: "xcodebuild (iOS 17.5, Xcode 15.4)"
53 | os: macos-14
54 | xcode-version: "15.4"
55 | sdk: iphonesimulator17.5
56 | destination: "platform=iOS Simulator,OS=17.5,name=iPhone 15"
57 | - name: "xcodebuild (tvOS 17.5, Xcode 15.4)"
58 | os: macos-14
59 | xcode-version: "15.4"
60 | sdk: appletvsimulator17.5
61 | destination: "platform=tvOS Simulator,OS=17.5,name=Apple TV"
62 | - name: "xcodebuild (macOS 14.7, Xcode 15.4)"
63 | os: macos-14
64 | xcode-version: "15.4"
65 | sdk: macosx14.5
66 | destination: "platform=OS X"
67 | steps:
68 | - uses: actions/checkout@v3
69 | - name: Select Xcode Version
70 | run: sudo xcode-select -s /Applications/Xcode_${{ matrix.xcode-version }}.app/Contents/Developer
71 | - name: Install xcpretty
72 | run: gem install xcpretty
73 | - name: Build & Test
74 | run: |
75 | set -o pipefail && xcodebuild \
76 | -scheme GEOSwiftMapKit \
77 | -sdk "${{ matrix.sdk }}" \
78 | -destination "${{ matrix.destination }}" \
79 | clean test | xcpretty -c;
80 | swift-cli-macos:
81 | name: "swift-cli (${{ matrix.os }}, Xcode ${{matrix.xcode-version}})"
82 | runs-on: ${{ matrix.os }}
83 | strategy:
84 | matrix:
85 | include:
86 | - os: macos-14
87 | xcode-version: "15.4"
88 | - os: macos-14
89 | xcode-version: "16"
90 | steps:
91 | - uses: actions/checkout@v3
92 | - name: Select Xcode Version
93 | run: sudo xcode-select -s /Applications/Xcode_${{ matrix.xcode-version }}.app/Contents/Developer
94 | - name: Build & Test
95 | run: swift test
96 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Mac
2 |
3 | .DS_Store
4 |
5 | # Xcode
6 |
7 | build/
8 | *.pbxuser
9 | !default.pbxuser
10 | *.mode1v3
11 | !default.mode1v3
12 | *.mode2v3
13 | !default.mode2v3
14 | *.perspectivev3
15 | !default.perspectivev3
16 | xcuserdata
17 | *.xccheckout
18 | *.moved-aside
19 | DerivedData
20 | *.hmap
21 | *.ipa
22 | *.xcuserstate
23 | *.xctimeline
24 |
25 | # Carthage
26 |
27 | Carthage/Checkouts
28 | Carthage/Build
29 |
30 | # Swift Package Manager
31 |
32 | .build/
33 |
--------------------------------------------------------------------------------
/.swiftlint.yml:
--------------------------------------------------------------------------------
1 | ---
2 | included:
3 | - Sources
4 | - Tests
5 | disabled_rules:
6 | - force_cast
7 | - identifier_name
8 | - type_name
9 | opt_in_rules:
10 | - closure_spacing
11 | - empty_count
12 | - implicit_return
13 | - pattern_matching_keywords
14 | - vertical_parameter_alignment_on_call
15 | line_length: 110
16 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/xcshareddata/xcschemes/GEOSwiftMapKit.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
9 |
10 |
16 |
22 |
23 |
24 |
25 |
26 |
31 |
32 |
35 |
36 |
37 |
38 |
40 |
46 |
47 |
48 |
49 |
50 |
60 |
61 |
67 |
68 |
74 |
75 |
76 |
77 |
79 |
80 |
83 |
84 |
85 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## 5.0.0
2 |
3 | * Updates to GEOSwift 11.0.0
4 | * Increases min Swift version to 5.9
5 | * Increases min deployment targets for Apple platforms
6 | * Expands QuickLook support to tvOS
7 | * Fixes some warnings when building with Xcode 16
8 |
9 | ## 4.0.0
10 |
11 | * Updates to GEOSwift 10.0.0
12 | * Fixes build error on macCatalyst
13 | * Increases min Swift version to 5.5
14 | * Drops support for Carthage
15 |
16 | ## 3.0.0
17 |
18 | * Updated for Xcode 12
19 | * Drops support for iOS 8
20 | * Switches to SPM as primary development environment
21 | * Updates GEOSwiftMapKit.xcodeproj to use GEOSwift.xcframework and
22 | geos.xcframework instead of the old-style fat frameworks due to a change
23 | in Xcode 12.3. This breaks (hopefully only temporarily) compatibility
24 | with Carthage unless you use the as-of-yet-unreleased Carthage version
25 | which adds the `--use-xcframeworks` flag. Carthage support will be
26 | reevaluated as its situation evolves.
27 | * Increases min GEOSwift to 8.0.0
28 |
29 | ## 2.0.0
30 |
31 | * [#9](https://github.com/GEOSwift/GEOSwiftMapKit/pull/9) Update to GEOSwift 7
32 |
33 | ## 1.2.0
34 |
35 | * [#7](https://github.com/GEOSwift/GEOSwiftMapKit/pull/7) Swift PM Support
36 | * Add support for Swift PM on iOS, tvOS, and macOS (Fixes
37 | [#4](https://github.com/GEOSwift/GEOSwiftMapKit/issues/4))
38 |
39 | ## 1.1.0
40 |
41 | * Relaxed GEOSwift dependency requirement for CocoaPods
42 | * Added support for MKMultiPolyline and MKMultiPolygon
43 |
44 | ## 1.0.0
45 |
46 | * Spun out of GEOSwift as part of its v5 rewrite
47 |
--------------------------------------------------------------------------------
/GEOSwiftMapKit.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = 'GEOSwiftMapKit'
3 | s.version = '5.0.0'
4 | s.swift_version = '5.9'
5 | s.cocoapods_version = '~> 1.10'
6 | s.summary = 'MapKit support for GEOSwift'
7 | s.description = <<~DESC
8 | Easily handle a geometric object model (points, linestrings, polygons etc.) and related
9 | topological operations (intersections, overlapping etc.). A type-safe, MIT-licensed Swift
10 | interface to the OSGeo's GEOS library routines, nicely integrated with MapKit.
11 | DESC
12 | s.homepage = 'https://github.com/GEOSwift/GEOSwiftMapKit'
13 | s.license = {
14 | type: 'MIT',
15 | file: 'LICENSE'
16 | }
17 | s.authors = 'Andrew Hershberger'
18 | s.ios.deployment_target = '12.0'
19 | s.macos.deployment_target = '10.13'
20 | s.tvos.deployment_target = '12.0'
21 | s.source = {
22 | git: 'https://github.com/GEOSwift/GEOSwiftMapKit.git',
23 | tag: s.version
24 | }
25 | s.source_files = 'Sources/**/*.swift'
26 | s.macos.exclude_files = 'GEOSwiftMapKit/GEOSwift+MapKitQuickLook.swift'
27 | s.dependency 'GEOSwift', '~> 11.0'
28 | end
29 |
--------------------------------------------------------------------------------
/GEOSwiftMapKit.xctestplan:
--------------------------------------------------------------------------------
1 | {
2 | "configurations" : [
3 | {
4 | "id" : "13C39BF1-17D7-4105-AD5C-3810194BC2B3",
5 | "name" : "Test Scheme Action",
6 | "options" : {
7 |
8 | }
9 | }
10 | ],
11 | "defaultOptions" : {
12 | "codeCoverage" : false
13 | },
14 | "testTargets" : [
15 | {
16 | "target" : {
17 | "containerPath" : "container:",
18 | "identifier" : "GEOSwiftMapKitTests",
19 | "name" : "GEOSwiftMapKitTests"
20 | }
21 | }
22 | ],
23 | "version" : 1
24 | }
25 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Andrea Cremaschi
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "pins" : [
3 | {
4 | "identity" : "geos",
5 | "kind" : "remoteSourceControl",
6 | "location" : "https://github.com/GEOSwift/geos.git",
7 | "state" : {
8 | "revision" : "4d8af495e7507f48f1ae9104102debdd2043917b",
9 | "version" : "9.0.0"
10 | }
11 | },
12 | {
13 | "identity" : "geoswift",
14 | "kind" : "remoteSourceControl",
15 | "location" : "https://github.com/GEOSwift/GEOSwift.git",
16 | "state" : {
17 | "revision" : "e42c062c2feb0df61373ae8cd6031a823a0dc981",
18 | "version" : "11.0.0"
19 | }
20 | }
21 | ],
22 | "version" : 2
23 | }
24 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.9
2 | import PackageDescription
3 |
4 | let package = Package(
5 | name: "GEOSwiftMapKit",
6 | platforms: [.iOS(.v12), .macOS(.v10_13), .tvOS(.v12)],
7 | products: [
8 | .library(name: "GEOSwiftMapKit", targets: ["GEOSwiftMapKit"])
9 | ],
10 | dependencies: [
11 | .package(url: "https://github.com/GEOSwift/GEOSwift.git", from: "11.0.0")
12 | ],
13 | targets: [
14 | .target(
15 | name: "GEOSwiftMapKit",
16 | dependencies: ["GEOSwift"]
17 | ),
18 | .testTarget(
19 | name: "GEOSwiftMapKitTests",
20 | dependencies: ["GEOSwiftMapKit"]
21 | )
22 | ]
23 | )
24 |
--------------------------------------------------------------------------------
/Playgrounds/GEOSwiftMapKit.playground/Contents.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import GEOSwift
3 | import GEOSwiftMapKit
4 | import MapKit
5 |
6 | try! Point(wkt: "POINT(10 45)")
7 | //: # GEOSwift
8 | //: _The Swift Geometry Engine_
9 | //:
10 | //: Easily handle geometric objects (points, linestrings, polygons etc.)
11 | //: and the main related topographical operations (intersections,
12 | //: overlapping etc.). GEOSwift is a MIT-licensed Swift interface to the
13 | //: OSGeo's GEOS library routines*, plus some convenience features such as:
14 | //:
15 | //: * A pure-Swift, type-safe, optional-aware programming interface
16 | //: * Automatically-typed geometry deserialization from WKT and WKB
17 | //: representations
18 | //: * MapKit and MapboxGL integration
19 | //: * Quicklook integration
20 | //: * GEOJSON support via Codable
21 | //: * Equatable & Hashable support
22 | //: * Thread-safe
23 | //: * Robust error handling
24 | //: * Extensively tested
25 | //:
26 | //: ## Handle a geometric data model
27 | //:
28 | //: GEOSwift lets you easily create geometry objects for all the geometry
29 | //: types supported by GEOS:
30 | //:
31 | //: * Point
32 | //: * LineString
33 | //: * Polygon
34 | //: * MultiPoint
35 | //: * MultiLineString
36 | //: * MultiPolygon
37 | //: * GeometryCollection
38 | //:
39 | //: Geometries can be deserialized from and serialized back to their Well
40 | //: Known Text (WKT) or Well Known Binary (WKB) representations.
41 | // Create a POINT from its WKT representation.
42 | let point = try! Point(wkt: "POINT(10 45)")
43 |
44 | // Create a POLYGON from its WKT representation. Here: the Notre Dame cathedral building footprint
45 | let polygon = try! Polygon(wkt: "POLYGON ((2.349252343714653 48.85347829980472, 2.3489192520770246 48.853050271993254, 2.35034958675422 48.852599033892346, 2.350565116637 48.852593876862244, 2.3507845652447372 48.852712488426135, 2.3508237524963533 48.852874934242834, 2.350682678390797 48.85304253651725, 2.349252343714653 48.85347829980472))")
46 |
47 | // If the expected type is unknown, you can use Geometry(wkt:) and the
48 | // returned value will be one of the Geometry enum cases
49 | let geometry1 = try! Geometry(wkt: "POLYGON((35 10, 45 45.5, 15 40, 10 20, 35 10),(20 30, 35 35, 30 20, 20 30))")
50 |
51 | // The same geometry can be represented in binary form as WKB
52 | guard let wkbURL = Bundle.main.url(forResource: "example", withExtension: "wkb"),
53 | let wkbData = try? Data(contentsOf: wkbURL) else {
54 | exit(1)
55 | }
56 | let geometry2 = try! Geometry(wkb: wkbData)
57 |
58 | if geometry1 == geometry2 && geometry1 != .point(point) {
59 | print("The two geometries are equal!")
60 | }
61 |
62 | // Examples of valid WKT geometries representations are:
63 | // POINT(6 10)
64 | // LINESTRING(35 10, 45 45, 15 40, 10 20, 35 10)
65 | // LINESTRING(3 4,10 50,20 25)
66 | // POLYGON((1 1,5 1,5 5,1 5,1 1),(2 2, 3 2, 3 3, 2 3,2 2))
67 | // MULTIPOINT(3.5 5.6,4.8 10.5)
68 | // MULTILINESTRING((3 4,10 50,20 25),(-5 -8,-10 -8,-15 -4))
69 | // MULTIPOLYGON(((1 1,5 1,5 5,1 5,1 1),(2 2, 3 2, 3 3, 2 3,2 2)),((3 3,6 2,6 4,3 3)))
70 | // GEOMETRYCOLLECTION(POINT(4 6),LINESTRING(4 6,7 10))
71 | //: ## MapKit Integration
72 | //:
73 | //: Convert the geometries to annotations and overlays, ready to be added
74 | //: to a MKMapView
75 | let shape1 = MKPointAnnotation(point: point)
76 | let shape2 = try! GeometryMapShape(geometry: geometry1)
77 | let annotations = [shape1, shape2]
78 | //: ## Quicklook integration
79 | //:
80 | //: GEOSwiftMapKit adds QuickLook support to GEOSwift types! This means that
81 | //: while debugging you can inspect complex geometries and see what they
82 | //: represent: just stop on the variable with the mouse cursor or select
83 | //: the geometry and press space in the debug area to see a preview. In
84 | //: playgrounds you can display them just as any other object, like this:
85 | geometry2
86 | //: ### GEOJSON parsing
87 | //:
88 | //: Your geometries can be loaded from GEOJSON using JSONDecoder:
89 | let jsonDecoder = JSONDecoder()
90 | if let geoJSONURL = Bundle.main.url(forResource: "multipolygon", withExtension: "geojson"),
91 | let data = try? Data(contentsOf: geoJSONURL),
92 | let featureCollection = try? jsonDecoder.decode(FeatureCollection.self, from: data),
93 | case let .multiPolygon(italy)? = featureCollection.features.first?.geometry {
94 |
95 | italy
96 | //: ### Topological operations:
97 | try! italy.buffer(by: 1)
98 | try! italy.boundary()
99 | try! italy.centroid()
100 | try! italy.convexHull()
101 | try! italy.envelope()
102 | try! italy.envelope().geometry.difference(with: italy)
103 | try! italy.pointOnSurface()
104 | try! italy.intersection(with: geometry2)
105 | try! italy.difference(with: geometry2)
106 | try! italy.union(with: geometry2)
107 | //: ### Predicates:
108 | try! italy.isDisjoint(with: geometry2)
109 | try! italy.touches(geometry2)
110 | try! italy.intersects(geometry2)
111 | try! italy.crosses(geometry2)
112 | try! italy.isWithin(geometry2)
113 | try! italy.contains(geometry2)
114 | try! italy.overlaps(geometry2)
115 | try! italy.isTopologicallyEquivalent(to: geometry2)
116 | try! italy.relate(geometry2, mask: "T*****FF*")
117 | }
118 |
--------------------------------------------------------------------------------
/Playgrounds/GEOSwiftMapKit.playground/Resources/example.wkb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GEOSwift/GEOSwiftMapKit/0bbac9e251bd73c2b8ac3f6a39d7c25b717d49cf/Playgrounds/GEOSwiftMapKit.playground/Resources/example.wkb
--------------------------------------------------------------------------------
/Playgrounds/GEOSwiftMapKit.playground/Resources/multipolygon.geojson:
--------------------------------------------------------------------------------
1 | {
2 | "type": "FeatureCollection",
3 | "features": [
4 | {
5 | "type": "Feature",
6 | "properties": {
7 | "scalerank": 1,
8 | "featurecla": "Admin-0 country",
9 | "labelrank": 2,
10 | "sovereignt": "Italy",
11 | "sov_a3": "ITA",
12 | "adm0_dif": 0,
13 | "level": 2,
14 | "type": "Sovereign country",
15 | "admin": "Italy",
16 | "adm0_a3": "ITA",
17 | "geou_dif": 0,
18 | "geounit": "Italy",
19 | "gu_a3": "ITA",
20 | "su_dif": 0,
21 | "subunit": "Italy",
22 | "su_a3": "ITA",
23 | "brk_diff": 0,
24 | "name": "Italy",
25 | "name_long": "Italy",
26 | "brk_a3": "ITA",
27 | "brk_name": "Italy",
28 | "brk_group": null,
29 | "abbrev": "Italy",
30 | "postal": "I",
31 | "formal_en": "Italian Republic",
32 | "formal_fr": null,
33 | "note_adm0": null,
34 | "note_brk": null,
35 | "name_sort": "Italy",
36 | "name_alt": null,
37 | "mapcolor7": 6,
38 | "mapcolor8": 7,
39 | "mapcolor9": 8,
40 | "mapcolor13": 7,
41 | "pop_est": 58126212,
42 | "gdp_md_est": 1823000,
43 | "pop_year": -99,
44 | "lastcensus": 2012,
45 | "gdp_year": -99,
46 | "economy": "1. Developed region: G7",
47 | "income_grp": "1. High income: OECD",
48 | "wikipedia": -99,
49 | "fips_10": null,
50 | "iso_a2": "IT",
51 | "iso_a3": "ITA",
52 | "iso_n3": "380",
53 | "un_a3": "380",
54 | "wb_a2": "IT",
55 | "wb_a3": "ITA",
56 | "woe_id": -99,
57 | "adm0_a3_is": "ITA",
58 | "adm0_a3_us": "ITA",
59 | "adm0_a3_un": -99,
60 | "adm0_a3_wb": -99,
61 | "continent": "Europe",
62 | "region_un": "Europe",
63 | "subregion": "Southern Europe",
64 | "region_wb": "Europe & Central Asia",
65 | "name_len": 5,
66 | "long_len": 5,
67 | "abbrev_len": 5,
68 | "tiny": -99,
69 | "homepart": 1
70 | },
71 | "geometry": {
72 | "type": "MultiPolygon",
73 | "coordinates": [
74 | [
75 | [
76 | [
77 | 15.520376010813834,
78 | 38.23115509699147
79 | ],
80 | [
81 | 15.160242954171736,
82 | 37.44404551853782
83 | ],
84 | [
85 | 15.309897902089006,
86 | 37.1342194687318
87 | ],
88 | [
89 | 15.09998823411945,
90 | 36.6199872909954
91 | ],
92 | [
93 | 14.335228712632016,
94 | 36.996630967754754
95 | ],
96 | [
97 | 13.82673261887993,
98 | 37.1045313583802
99 | ],
100 | [
101 | 12.431003859108813,
102 | 37.61294993748382
103 | ],
104 | [
105 | 12.570943637755136,
106 | 38.12638113051969
107 | ],
108 | [
109 | 13.741156447004585,
110 | 38.03496552179536
111 | ],
112 | [
113 | 14.76124922044616,
114 | 38.143873602850505
115 | ],
116 | [
117 | 15.520376010813834,
118 | 38.23115509699147
119 | ]
120 | ]
121 | ],
122 | [
123 | [
124 | [
125 | 9.210011834356266,
126 | 41.20999136002422
127 | ],
128 | [
129 | 9.809975213264977,
130 | 40.5000088567661
131 | ],
132 | [
133 | 9.669518670295673,
134 | 39.177376410471794
135 | ],
136 | [
137 | 9.21481774255949,
138 | 39.240473334300134
139 | ],
140 | [
141 | 8.80693566247973,
142 | 38.90661774347848
143 | ],
144 | [
145 | 8.428302443077115,
146 | 39.17184703221662
147 | ],
148 | [
149 | 8.38825320805094,
150 | 40.378310858718805
151 | ],
152 | [
153 | 8.15999840661766,
154 | 40.95000722916379
155 | ],
156 | [
157 | 8.709990675500109,
158 | 40.89998444270523
159 | ],
160 | [
161 | 9.210011834356266,
162 | 41.20999136002422
163 | ]
164 | ]
165 | ],
166 | [
167 | [
168 | [
169 | 12.376485223040845,
170 | 46.76755910906988
171 | ],
172 | [
173 | 13.806475457421556,
174 | 46.50930613869119
175 | ],
176 | [
177 | 13.698109978905478,
178 | 46.016778062517375
179 | ],
180 | [
181 | 13.937630242578336,
182 | 45.591015936864665
183 | ],
184 | [
185 | 13.141606479554298,
186 | 45.73669179949542
187 | ],
188 | [
189 | 12.328581170306308,
190 | 45.381778062514854
191 | ],
192 | [
193 | 12.383874952858605,
194 | 44.88537425391908
195 | ],
196 | [
197 | 12.261453484759159,
198 | 44.600482082694015
199 | ],
200 | [
201 | 12.589237094786483,
202 | 44.091365871754476
203 | ],
204 | [
205 | 13.526905958722494,
206 | 43.58772736263791
207 | ],
208 | [
209 | 14.029820997787027,
210 | 42.76100779883248
211 | ],
212 | [
213 | 15.142569614327954,
214 | 41.955139675456905
215 | ],
216 | [
217 | 15.926191033601896,
218 | 41.96131500911574
219 | ],
220 | [
221 | 16.169897088290412,
222 | 41.740294908203424
223 | ],
224 | [
225 | 15.889345737377795,
226 | 41.5410822617182
227 | ],
228 | [
229 | 16.785001661860576,
230 | 41.179605617836586
231 | ],
232 | [
233 | 17.519168735431208,
234 | 40.87714345963224
235 | ],
236 | [
237 | 18.376687452882578,
238 | 40.35562490494266
239 | ],
240 | [
241 | 18.480247023195403,
242 | 40.168866278639825
243 | ],
244 | [
245 | 18.2933850440281,
246 | 39.81077444107325
247 | ],
248 | [
249 | 17.738380161213286,
250 | 40.2776710068303
251 | ],
252 | [
253 | 16.869595981522338,
254 | 40.44223460546385
255 | ],
256 | [
257 | 16.448743116937322,
258 | 39.79540070246648
259 | ],
260 | [
261 | 17.1714896989715,
262 | 39.42469981542072
263 | ],
264 | [
265 | 17.052840610429342,
266 | 38.902871202137305
267 | ],
268 | [
269 | 16.635088331781844,
270 | 38.8435724960824
271 | ],
272 | [
273 | 16.100960727613057,
274 | 37.98589874933418
275 | ],
276 | [
277 | 15.684086948314501,
278 | 37.90884918878703
279 | ],
280 | [
281 | 15.68796268073632,
282 | 38.214592800441864
283 | ],
284 | [
285 | 15.891981235424709,
286 | 38.750942491199226
287 | ],
288 | [
289 | 16.109332309644316,
290 | 38.96454702407769
291 | ],
292 | [
293 | 15.718813510814641,
294 | 39.544072374014945
295 | ],
296 | [
297 | 15.413612501698822,
298 | 40.04835683853517
299 | ],
300 | [
301 | 14.998495721098237,
302 | 40.17294871679093
303 | ],
304 | [
305 | 14.70326826341477,
306 | 40.604550279292624
307 | ],
308 | [
309 | 14.060671827865264,
310 | 40.78634796809544
311 | ],
312 | [
313 | 13.627985060285397,
314 | 41.188287258461656
315 | ],
316 | [
317 | 12.88808190273042,
318 | 41.25308950455562
319 | ],
320 | [
321 | 12.10668257004491,
322 | 41.70453481705741
323 | ],
324 | [
325 | 11.191906365614187,
326 | 42.35542531998968
327 | ],
328 | [
329 | 10.511947869517797,
330 | 42.931462510747224
331 | ],
332 | [
333 | 10.200028924204048,
334 | 43.920006822274615
335 | ],
336 | [
337 | 9.702488234097814,
338 | 44.03627879493132
339 | ],
340 | [
341 | 8.88894616052687,
342 | 44.36633616797954
343 | ],
344 | [
345 | 8.428560825238577,
346 | 44.23122813575242
347 | ],
348 | [
349 | 7.850766635783202,
350 | 43.76714793555524
351 | ],
352 | [
353 | 7.435184767291844,
354 | 43.69384491634918
355 | ],
356 | [
357 | 7.549596388386163,
358 | 44.12790110938482
359 | ],
360 | [
361 | 7.007562290076663,
362 | 44.25476675066139
363 | ],
364 | [
365 | 6.749955275101712,
366 | 45.02851797136759
367 | ],
368 | [
369 | 7.096652459347837,
370 | 45.333098863295874
371 | ],
372 | [
373 | 6.802355177445662,
374 | 45.70857982032868
375 | ],
376 | [
377 | 6.843592970414562,
378 | 45.99114655210067
379 | ],
380 | [
381 | 7.273850945676685,
382 | 45.77694774025076
383 | ],
384 | [
385 | 7.755992058959833,
386 | 45.82449005795928
387 | ],
388 | [
389 | 8.31662967289438,
390 | 46.163642483090854
391 | ],
392 | [
393 | 8.489952426801295,
394 | 46.00515086525175
395 | ],
396 | [
397 | 8.966305779667834,
398 | 46.036931871111165
399 | ],
400 | [
401 | 9.182881707403112,
402 | 46.44021474871698
403 | ],
404 | [
405 | 9.922836541390353,
406 | 46.31489940040919
407 | ],
408 | [
409 | 10.363378126678668,
410 | 46.483571275409844
411 | ],
412 | [
413 | 10.442701450246602,
414 | 46.893546250997446
415 | ],
416 | [
417 | 11.048555942436508,
418 | 46.7513585475464
419 | ],
420 | [
421 | 11.164827915093326,
422 | 46.94157949481274
423 | ],
424 | [
425 | 12.153088006243081,
426 | 47.11539317482644
427 | ],
428 | [
429 | 12.376485223040845,
430 | 46.76755910906988
431 | ]
432 | ]
433 | ]
434 | ]
435 | }
436 | }
437 | ]
438 | }
--------------------------------------------------------------------------------
/Playgrounds/GEOSwiftMapKit.playground/contents.xcplayground:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/Playgrounds/GEOSwiftMapKit.playground/playground.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/README-images/playground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GEOSwift/GEOSwiftMapKit/0bbac9e251bd73c2b8ac3f6a39d7c25b717d49cf/README-images/playground.png
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # GEOSwiftMapKit
2 |
3 | [](https://cocoapods.org/pods/GEOSwiftMapKit)
4 | [](https://swift.org/package-manager/)
5 | [](https://github.com/GEOSwift/GEOSwiftMapKit)
6 | [](https://github.com/GEOSwift/GEOSwiftMapKit/actions/workflows/main.yml)
7 |
8 | See [GEOSwift](https://github.com/GEOSwift/GEOSwift) for full details
9 |
10 | ## Minimum Requirements
11 |
12 | * iOS 12.0, tvOS 12.0, macOS 10.13
13 | * Swift 5.9 compiler
14 |
15 | > GEOS is licensed under LGPL 2.1 and its compatibility with static linking is
16 | at least controversial. Use of geos without dynamic linking is discouraged.
17 |
18 | ## Installation
19 |
20 | ### CocoaPods
21 |
22 | 1. Update your `Podfile` to include:
23 |
24 | use_frameworks!
25 | pod 'GEOSwiftMapKit'
26 |
27 | 2. Run `$ pod install`
28 |
29 | ### Swift Package Manager
30 |
31 | 1. Update the top-level dependencies in your `Package.swift` to include:
32 |
33 | .package(url: "https://github.com/GEOSwift/GEOSwiftMapKit.git", from: "5.0.0")
34 |
35 | 2. Update the target dependencies in your `Package.swift` to include
36 |
37 | "GEOSwiftMapKit"
38 |
39 | ### Playground
40 |
41 | Explore more, interactively, in the playground. Open Package.swift in Xcode,
42 | and open the playground file from the file navigator.
43 |
44 | 
45 |
46 | ## Contributing
47 |
48 | To make a contribution:
49 |
50 | * Fork the repo
51 | * Start from the `main` branch and create a branch with a name that describes
52 | your contribution
53 | * Run `$ xed Package.swift` to open the project in Xcode.
54 | * Run `$ swiftlint` from the repo root and resolve any issues.
55 | * Push your branch and create a pull request to `main`
56 | * One of the maintainers will review your code and may request changes
57 | * If your pull request is accepted, one of the maintainers should update the
58 | changelog before merging it
59 |
60 | ## Maintainer
61 |
62 | * Andrew Hershberger ([@macdrevx](https://github.com/macdrevx))
63 |
64 | ## Past Maintainers
65 |
66 | * Virgilio Favero Neto ([@vfn](https://github.com/vfn))
67 | * Andrea Cremaschi ([@andreacremaschi](https://twitter.com/andreacremaschi))
68 | (original author)
69 |
70 | ## License
71 |
72 | * GEOSwift was released by Andrea Cremaschi
73 | ([@andreacremaschi](https://twitter.com/andreacremaschi)) under a MIT license.
74 | See LICENSE for more information.
75 | * [GEOS](http://trac.osgeo.org/geos/) stands for Geometry Engine - Open Source,
76 | and is a C++ library, ported from the
77 | [Java Topology Suite](http://sourceforge.net/projects/jts-topo-suite/). GEOS
78 | implements the OpenGIS
79 | [Simple Features for SQL](http://www.opengeospatial.org/standards/sfs) spatial
80 | predicate functions and spatial operators. GEOS, now an OSGeo project, was
81 | initially developed and maintained by
82 | [Refractions Research](http://www.refractions.net/) of Victoria, Canada.
83 |
--------------------------------------------------------------------------------
/Sources/GEOSwiftMapKit/GEOSwift+MapKit.swift:
--------------------------------------------------------------------------------
1 | import GEOSwift
2 | import MapKit
3 |
4 | public extension CLLocationCoordinate2D {
5 | init(_ point: Point) {
6 | self.init(latitude: point.y, longitude: point.x)
7 | }
8 | }
9 |
10 | public extension Point {
11 | init(longitude: Double, latitude: Double) {
12 | self.init(x: longitude, y: latitude)
13 | }
14 |
15 | init(_ coordinate: CLLocationCoordinate2D) {
16 | self.init(x: coordinate.longitude, y: coordinate.latitude)
17 | }
18 | }
19 |
20 | public extension GEOSwift.Polygon {
21 | static var world: GEOSwift.Polygon {
22 | // swiftlint:disable:next force_try
23 | try! Polygon(exterior: Polygon.LinearRing(points: [
24 | Point(x: -180, y: 90),
25 | Point(x: -180, y: -90),
26 | Point(x: 180, y: -90),
27 | Point(x: 180, y: 90),
28 | Point(x: -180, y: 90)]))
29 | }
30 | }
31 |
32 | // Note that this does not currently do a good job of supporting geometries that cross the anti-meridian
33 | public extension MKCoordinateRegion {
34 | init(containing geometry: GeometryConvertible) throws {
35 | let envelope = try geometry.geometry.envelope()
36 | let center = try CLLocationCoordinate2D(envelope.geometry.centroid())
37 | let span = MKCoordinateSpan(
38 | latitudeDelta: envelope.maxY - envelope.minY,
39 | longitudeDelta: envelope.maxX - envelope.minX)
40 | self.init(center: center, span: span)
41 | }
42 | }
43 |
44 | public extension MKPointAnnotation {
45 | convenience init(point: Point) {
46 | self.init()
47 | coordinate = CLLocationCoordinate2D(point)
48 | }
49 | }
50 |
51 | public extension MKPlacemark {
52 | convenience init(point: Point) {
53 | self.init(coordinate: CLLocationCoordinate2D(point), addressDictionary: nil)
54 | }
55 | }
56 |
57 | public extension MKPolyline {
58 | convenience init(lineString: LineString) {
59 | var coordinates = lineString.points.map(CLLocationCoordinate2D.init)
60 | self.init(coordinates: &coordinates, count: coordinates.count)
61 | }
62 | }
63 |
64 | public extension MKPolygon {
65 | convenience init(linearRing: GEOSwift.Polygon.LinearRing) {
66 | var coordinates = linearRing.points.map(CLLocationCoordinate2D.init)
67 | self.init(coordinates: &coordinates, count: coordinates.count)
68 | }
69 |
70 | convenience init(polygon: GEOSwift.Polygon) {
71 | var exteriorCoordinates = polygon.exterior.points.map(CLLocationCoordinate2D.init)
72 | self.init(
73 | coordinates: &exteriorCoordinates,
74 | count: exteriorCoordinates.count,
75 | interiorPolygons: polygon.holes.map(MKPolygon.init))
76 | }
77 | }
78 |
79 | @available(iOS 13.0, tvOS 13.0, macOS 10.15, *)
80 | public extension MKMultiPolyline {
81 | convenience init(multiLineString: MultiLineString) {
82 | self.init(multiLineString.lineStrings.map(MKPolyline.init))
83 | }
84 | }
85 |
86 | @available(iOS 13.0, tvOS 13.0, macOS 10.15, *)
87 | public extension MKMultiPolygon {
88 | convenience init(multiPolygon: MultiPolygon) {
89 | self.init(multiPolygon.polygons.map(MKPolygon.init))
90 | }
91 | }
92 |
93 | // Note that this does not currently do a good job of supporting geometries that cross the anti-meridian
94 | open class GeometryMapShape: MKShape, MKOverlay {
95 | public let geometry: GeometryConvertible
96 |
97 | private let _coordinate: CLLocationCoordinate2D
98 | override open var coordinate: CLLocationCoordinate2D {
99 | _coordinate
100 | }
101 |
102 | public let boundingMapRect: MKMapRect
103 |
104 | public init(geometry: GeometryConvertible) throws {
105 | self.geometry = geometry
106 | self._coordinate = try CLLocationCoordinate2D(geometry.centroid())
107 | let envelope = try geometry.envelope()
108 | let topLeft = MKMapPoint(CLLocationCoordinate2D(envelope.minXMaxY))
109 | let bottomRight = MKMapPoint(CLLocationCoordinate2D(envelope.maxXMinY))
110 | self.boundingMapRect = MKMapRect(
111 | origin: topLeft,
112 | size: MKMapSize(
113 | width: bottomRight.x - topLeft.x,
114 | height: bottomRight.y - topLeft.y))
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/Sources/GEOSwiftMapKit/GEOSwift+MapKitQuickLook.swift:
--------------------------------------------------------------------------------
1 | #if os(iOS) || os(tvOS)
2 | import UIKit
3 | import MapKit
4 | import GEOSwift
5 |
6 | #if compiler(>=6)
7 | extension Point: @retroactive CustomPlaygroundDisplayConvertible {
8 | public var playgroundDescription: Any { makePlaygroundDescription(for: self) }
9 | }
10 |
11 | extension MultiPoint: @retroactive CustomPlaygroundDisplayConvertible {
12 | public var playgroundDescription: Any { makePlaygroundDescription(for: self) }
13 | }
14 |
15 | extension LineString: @retroactive CustomPlaygroundDisplayConvertible {
16 | public var playgroundDescription: Any { makePlaygroundDescription(for: self) }
17 | }
18 |
19 | extension MultiLineString: @retroactive CustomPlaygroundDisplayConvertible {
20 | public var playgroundDescription: Any { makePlaygroundDescription(for: self) }
21 | }
22 |
23 | extension GEOSwift.Polygon.LinearRing: @retroactive CustomPlaygroundDisplayConvertible {
24 | public var playgroundDescription: Any { makePlaygroundDescription(for: self) }
25 | }
26 |
27 | extension GEOSwift.Polygon: @retroactive CustomPlaygroundDisplayConvertible {
28 | public var playgroundDescription: Any { makePlaygroundDescription(for: self) }
29 | }
30 |
31 | extension MultiPolygon: @retroactive CustomPlaygroundDisplayConvertible {
32 | public var playgroundDescription: Any { makePlaygroundDescription(for: self) }
33 | }
34 |
35 | extension GeometryCollection: @retroactive CustomPlaygroundDisplayConvertible {
36 | public var playgroundDescription: Any { makePlaygroundDescription(for: self) }
37 | }
38 |
39 | extension Geometry: @retroactive CustomPlaygroundDisplayConvertible {
40 | public var playgroundDescription: Any { makePlaygroundDescription(for: self) }
41 | }
42 |
43 | extension Envelope: @retroactive CustomPlaygroundDisplayConvertible {
44 | public var playgroundDescription: Any { makePlaygroundDescription(for: self) }
45 | }
46 | #else
47 | extension Point: CustomPlaygroundDisplayConvertible {
48 | public var playgroundDescription: Any { makePlaygroundDescription(for: self) }
49 | }
50 |
51 | extension MultiPoint: CustomPlaygroundDisplayConvertible {
52 | public var playgroundDescription: Any { makePlaygroundDescription(for: self) }
53 | }
54 |
55 | extension LineString: CustomPlaygroundDisplayConvertible {
56 | public var playgroundDescription: Any { makePlaygroundDescription(for: self) }
57 | }
58 |
59 | extension MultiLineString: CustomPlaygroundDisplayConvertible {
60 | public var playgroundDescription: Any { makePlaygroundDescription(for: self) }
61 | }
62 |
63 | extension GEOSwift.Polygon.LinearRing: CustomPlaygroundDisplayConvertible {
64 | public var playgroundDescription: Any { makePlaygroundDescription(for: self) }
65 | }
66 |
67 | extension GEOSwift.Polygon: CustomPlaygroundDisplayConvertible {
68 | public var playgroundDescription: Any { makePlaygroundDescription(for: self) }
69 | }
70 |
71 | extension MultiPolygon: CustomPlaygroundDisplayConvertible {
72 | public var playgroundDescription: Any { makePlaygroundDescription(for: self) }
73 | }
74 |
75 | extension GeometryCollection: CustomPlaygroundDisplayConvertible {
76 | public var playgroundDescription: Any { makePlaygroundDescription(for: self) }
77 | }
78 |
79 | extension Geometry: CustomPlaygroundDisplayConvertible {
80 | public var playgroundDescription: Any { makePlaygroundDescription(for: self) }
81 | }
82 |
83 | extension Envelope: CustomPlaygroundDisplayConvertible {
84 | public var playgroundDescription: Any { makePlaygroundDescription(for: self) }
85 | }
86 | #endif
87 |
88 | private func makePlaygroundDescription(for g: (some GEOSwiftQuickLook)) -> Any {
89 | let defaultReturnValue: Any = (try? g.geometry.wkt()) ?? g
90 | var bufferValue: Double = 0
91 | if case .point = g.geometry {
92 | bufferValue = 0.1
93 | }
94 | guard let buffered = try? g.geometry.buffer(by: bufferValue)?.intersection(with: Polygon.world),
95 | let region = try? MKCoordinateRegion(containing: buffered) else {
96 | return defaultReturnValue
97 | }
98 |
99 | let options = MKMapSnapshotter.Options()
100 | options.region = region
101 | options.size = CGSize(width: 400, height: 400)
102 | var snapshot: MKMapSnapshotter.Snapshot?
103 | let backgroundQueue = DispatchQueue.global(qos: .userInitiated)
104 | let snapshotter = MKMapSnapshotter(options: options)
105 | let semaphore = DispatchSemaphore(value: 0)
106 | snapshotter.start(with: backgroundQueue) { s, _ in
107 | snapshot = s
108 | semaphore.signal()
109 | }
110 | _ = semaphore.wait(timeout: .now() + 3)
111 | guard let snapshot else {
112 | return defaultReturnValue
113 | }
114 | let format = UIGraphicsImageRendererFormat.preferred()
115 | format.opaque = true
116 | format.scale = snapshot.image.scale
117 | return UIGraphicsImageRenderer(size: snapshot.image.size, format: format).image { context in
118 | snapshot.image.draw(at: .zero)
119 | g.quickLookDraw(in: context.cgContext, snapshot: snapshot)
120 | }
121 | }
122 |
123 | private protocol GEOSwiftQuickLook: GeometryConvertible {
124 | func quickLookDraw(in context: CGContext, snapshot: MKMapSnapshotter.Snapshot)
125 | }
126 |
127 | private extension GEOSwiftQuickLook {
128 | func draw(
129 | in context: CGContext,
130 | imageSize: CGSize,
131 | mapRect: MKMapRect,
132 | renderer: MKOverlayRenderer
133 | ) {
134 | context.saveGState()
135 |
136 | // scale the content to fit inside the image
137 | let scaleX = imageSize.width / CGFloat(mapRect.size.width)
138 | let scaleY = imageSize.height / CGFloat(mapRect.size.height)
139 | context.scaleBy(x: scaleX, y: scaleY)
140 |
141 | // the renderer will draw the geometry at (0,0), so offset CoreGraphics by the right measure
142 | let upperCorner = renderer.mapPoint(for: .zero)
143 | context.translateBy(x: CGFloat(upperCorner.x - mapRect.origin.x),
144 | y: CGFloat(upperCorner.y - mapRect.origin.y))
145 |
146 | renderer.draw(mapRect, zoomScale: imageSize.width / CGFloat(mapRect.size.width), in: context)
147 |
148 | context.restoreGState()
149 | }
150 | }
151 |
152 | extension Point: GEOSwiftQuickLook {
153 | func quickLookDraw(in context: CGContext, snapshot: MKMapSnapshotter.Snapshot) {
154 | let coord = CLLocationCoordinate2D(self)
155 | let point = snapshot.point(for: coord)
156 | let rect = CGRect(origin: point, size: .zero).insetBy(dx: -10, dy: -10)
157 | UIColor.red.setFill()
158 | context.fillEllipse(in: rect)
159 | }
160 | }
161 |
162 | extension MultiPoint: GEOSwiftQuickLook {
163 | func quickLookDraw(in context: CGContext, snapshot: MKMapSnapshotter.Snapshot) {
164 | for point in points {
165 | point.quickLookDraw(in: context, snapshot: snapshot)
166 | }
167 | }
168 | }
169 |
170 | extension LineString: GEOSwiftQuickLook {
171 | func quickLookDraw(in context: CGContext, snapshot: MKMapSnapshotter.Snapshot) {
172 | UIColor.blue.withAlphaComponent(0.7).setStroke()
173 | let path = UIBezierPath()
174 | path.move(to: snapshot.point(for: CLLocationCoordinate2D(firstPoint)))
175 | for point in points[1...] {
176 | path.addLine(to: snapshot.point(for: CLLocationCoordinate2D(point)))
177 | }
178 | path.lineWidth = 2
179 | path.stroke()
180 | }
181 | }
182 |
183 | extension MultiLineString: GEOSwiftQuickLook {
184 | func quickLookDraw(in context: CGContext, snapshot: MKMapSnapshotter.Snapshot) {
185 | for lineString in lineStrings {
186 | lineString.quickLookDraw(in: context, snapshot: snapshot)
187 | }
188 | }
189 | }
190 |
191 | extension GEOSwift.Polygon.LinearRing: GEOSwiftQuickLook {
192 | func quickLookDraw(in context: CGContext, snapshot: MKMapSnapshotter.Snapshot) {
193 | lineString.quickLookDraw(in: context, snapshot: snapshot)
194 | }
195 | }
196 |
197 | extension GEOSwift.Polygon: GEOSwiftQuickLook {
198 | func quickLookDraw(in context: CGContext, snapshot: MKMapSnapshotter.Snapshot) {
199 | UIColor.blue.withAlphaComponent(0.7).setStroke()
200 | UIColor.cyan.withAlphaComponent(0.2).setFill()
201 | let path = UIBezierPath()
202 | path.move(to: snapshot.point(for: CLLocationCoordinate2D(exterior.points.first!)))
203 | for point in exterior.points[1...] {
204 | path.addLine(to: snapshot.point(for: CLLocationCoordinate2D(point)))
205 | }
206 | path.close()
207 | for hole in holes {
208 | path.move(to: snapshot.point(for: CLLocationCoordinate2D(hole.points.first!)))
209 | for point in hole.points[1...] {
210 | path.addLine(to: snapshot.point(for: CLLocationCoordinate2D(point)))
211 | }
212 | path.close()
213 | }
214 | path.lineWidth = 2
215 | path.fill()
216 | path.stroke()
217 | }
218 | }
219 |
220 | extension MultiPolygon: GEOSwiftQuickLook {
221 | func quickLookDraw(in context: CGContext, snapshot: MKMapSnapshotter.Snapshot) {
222 | for polygon in polygons {
223 | polygon.quickLookDraw(in: context, snapshot: snapshot)
224 | }
225 | }
226 | }
227 |
228 | extension GeometryCollection: GEOSwiftQuickLook {
229 | func quickLookDraw(in context: CGContext, snapshot: MKMapSnapshotter.Snapshot) {
230 | for geometry in geometries {
231 | geometry.quickLookDraw(in: context, snapshot: snapshot)
232 | }
233 | }
234 | }
235 |
236 | extension Geometry: GEOSwiftQuickLook {
237 | func quickLookDraw(in context: CGContext, snapshot: MKMapSnapshotter.Snapshot) {
238 | switch self {
239 | case let .point(point):
240 | point.quickLookDraw(in: context, snapshot: snapshot)
241 | case let .multiPoint(multiPoint):
242 | multiPoint.quickLookDraw(in: context, snapshot: snapshot)
243 | case let .lineString(lineString):
244 | lineString.quickLookDraw(in: context, snapshot: snapshot)
245 | case let .multiLineString(multiLineString):
246 | multiLineString.quickLookDraw(in: context, snapshot: snapshot)
247 | case let .polygon(polygon):
248 | polygon.quickLookDraw(in: context, snapshot: snapshot)
249 | case let .multiPolygon(multiPolygon):
250 | multiPolygon.quickLookDraw(in: context, snapshot: snapshot)
251 | case let .geometryCollection(geometryCollection):
252 | geometryCollection.quickLookDraw(in: context, snapshot: snapshot)
253 | }
254 | }
255 | }
256 |
257 | extension Envelope: GEOSwiftQuickLook {
258 | func quickLookDraw(in context: CGContext, snapshot: MKMapSnapshotter.Snapshot) {
259 | geometry.quickLookDraw(in: context, snapshot: snapshot)
260 | }
261 | }
262 |
263 | #endif
264 |
--------------------------------------------------------------------------------
/Tests/.swiftlint.yml:
--------------------------------------------------------------------------------
1 | ---
2 | disabled_rules:
3 | - force_try
4 | - type_body_length
5 | - file_length
6 |
--------------------------------------------------------------------------------
/Tests/GEOSwiftMapKitTests/GEOSwift+MapKitTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | import MapKit
3 | import GEOSwift
4 | import GEOSwiftMapKit
5 |
6 | final class MapKitTests: XCTestCase {
7 |
8 | func testCreateCLLocationCoordinate2DFromPoint() {
9 | let point = Point(x: 45, y: 9)
10 |
11 | let coordinate = CLLocationCoordinate2D(point)
12 |
13 | XCTAssertEqual(coordinate, CLLocationCoordinate2D(latitude: 9, longitude: 45))
14 | }
15 |
16 | func testCreatePointWithLatAndLong() {
17 | let point = Point(longitude: 1, latitude: 2)
18 |
19 | XCTAssertEqual(point.x, 1)
20 | XCTAssertEqual(point.y, 2)
21 | }
22 |
23 | func testCreatePointWithCLLocationCoordinate2D() {
24 | let coord = CLLocationCoordinate2D(latitude: 2, longitude: 1)
25 |
26 | XCTAssertEqual(Point(coord), Point(x: 1, y: 2))
27 | }
28 |
29 | func testWorldPolygon() {
30 | // just make sure it doesn't crash
31 | _ = GEOSwift.Polygon.world
32 | }
33 |
34 | func testCreateMKCoordinateRegionContainingGeometry() {
35 | let lineString = try! LineString(wkt: "LINESTRING(3 4,10 50,20 25)")
36 |
37 | let region = try? MKCoordinateRegion(containing: lineString)
38 |
39 | XCTAssertEqual(region, MKCoordinateRegion(
40 | center: CLLocationCoordinate2D(latitude: 27, longitude: 11.5),
41 | span: MKCoordinateSpan(latitudeDelta: 46, longitudeDelta: 17)))
42 | }
43 |
44 | func testCreateMKPointAnnotationFromPoint() {
45 | let point = Point(x: 45, y: 9)
46 |
47 | let annotation = MKPointAnnotation(point: point)
48 |
49 | XCTAssertEqual(annotation.coordinate, CLLocationCoordinate2D(latitude: 9, longitude: 45))
50 | }
51 |
52 | func testCreateMKPlacemarkFromPoint() {
53 | let point = Point(x: 45, y: 9)
54 |
55 | let placemark = MKPlacemark(point: point)
56 |
57 | XCTAssertEqual(placemark.coordinate, CLLocationCoordinate2D(latitude: 9, longitude: 45))
58 | }
59 |
60 | func testCreateMKPolylineFromLineString() {
61 | let lineString = try! LineString(wkt: "LINESTRING(3 4,10 50,20 25)")
62 |
63 | let polyline = MKPolyline(lineString: lineString)
64 |
65 | XCTAssertEqual(polyline.pointCount, 3)
66 | }
67 |
68 | func testCreateMKPolygonFromLinearRing() {
69 | let linearRing = try! Polygon.LinearRing(
70 | points: [
71 | Point(x: 35, y: 10),
72 | Point(x: 45, y: 45),
73 | Point(x: 15, y: 40),
74 | Point(x: 10, y: 20),
75 | Point(x: 35, y: 10)])
76 |
77 | let mkPolygon = MKPolygon(linearRing: linearRing)
78 |
79 | XCTAssertEqual(mkPolygon.pointCount, 5)
80 | if #available(iOS 13.0, tvOS 13.0, macOS 10.15, *) {
81 | XCTAssertNil(mkPolygon.interiorPolygons)
82 | } else {
83 | XCTAssertEqual(mkPolygon.interiorPolygons?.count, 0)
84 | }
85 | }
86 |
87 | func testCreateMKPolygonFromPolygon() {
88 | let polygon = try! Polygon(
89 | wkt: "POLYGON((35 10, 45 45, 15 40, 10 20, 35 10),(20 30, 35 35, 30 20, 20 30))")
90 |
91 | let mkPolygon = MKPolygon(polygon: polygon)
92 |
93 | XCTAssertEqual(mkPolygon.pointCount, 5)
94 | XCTAssertEqual(mkPolygon.interiorPolygons?.count, 1)
95 | XCTAssertEqual(mkPolygon.interiorPolygons?.first?.pointCount, 4)
96 | }
97 |
98 | func testCreateMKMultiPolylineFromMultiLineString() {
99 | guard #available(iOS 13.0, tvOS 13.0, macOS 10.15, *) else {
100 | return
101 | }
102 |
103 | let multiLineString = try! MultiLineString(lineStrings: [
104 | LineString(points: [Point(x: 0, y: 0), Point(x: 0, y: 1)]),
105 | LineString(points: [Point(x: 0, y: 0), Point(x: 1, y: 0)])])
106 |
107 | let mkMultiPolyline = MKMultiPolyline(multiLineString: multiLineString)
108 |
109 | XCTAssertEqual(mkMultiPolyline.polylines.count, multiLineString.lineStrings.count)
110 | }
111 |
112 | func testCreateMKMultiPolygonFromMultiPolygon() {
113 | guard #available(iOS 13.0, tvOS 13.0, macOS 10.15, *) else {
114 | return
115 | }
116 |
117 | let multiPolygon = try! MultiPolygon(polygons: [
118 | Polygon(wkt: "POLYGON((35 10, 45 45, 15 40, 10 20, 35 10),(20 30, 35 35, 30 20, 20 30))"),
119 | Polygon(wkt: "POLYGON((35 10, 45 45, 15 40, 10 20, 35 10))")])
120 |
121 | let mkMultiPolygon = MKMultiPolygon(multiPolygon: multiPolygon)
122 |
123 | XCTAssertEqual(mkMultiPolygon.polygons.count, multiPolygon.polygons.count)
124 | }
125 |
126 | func testGeometryMapShape() {
127 | let polygon = try! Polygon(wkt: "POLYGON((0 0, 2 0, 2 2, 0 2, 0 0))")
128 |
129 | let geometryMapShape = try! GeometryMapShape(geometry: polygon)
130 |
131 | let topLeft = MKMapPoint(CLLocationCoordinate2D(
132 | latitude: 2,
133 | longitude: 0))
134 | let bottomRight = MKMapPoint(CLLocationCoordinate2D(
135 | latitude: 0,
136 | longitude: 2))
137 | let boundingMapRect = MKMapRect(
138 | origin: topLeft,
139 | size: MKMapSize(
140 | width: bottomRight.x - topLeft.x,
141 | height: bottomRight.y - topLeft.y))
142 | XCTAssertEqual(geometryMapShape.boundingMapRect, boundingMapRect)
143 | XCTAssertEqual(geometryMapShape.geometry.geometry, polygon.geometry)
144 | XCTAssertEqual(geometryMapShape.coordinate, CLLocationCoordinate2D(latitude: 1, longitude: 1))
145 | }
146 |
147 | // Test case for Issue #134
148 | // https://github.com/GEOSwift/GEOSwift/issues/134
149 | func testGeometryMapShapeHasBoundingMapRectWithPositiveWidthAndHeight() {
150 | let lineString = try! LineString(points: [Point(x: -1, y: 1), Point(x: 1, y: -1)])
151 | let geometryCollection = GeometryCollection(geometries: [lineString])
152 | let geometryMapShape = try! GeometryMapShape(geometry: geometryCollection)
153 |
154 | let boundingMapRect = geometryMapShape.boundingMapRect
155 |
156 | XCTAssertGreaterThan(boundingMapRect.height, 0)
157 | XCTAssertGreaterThan(boundingMapRect.width, 0)
158 | }
159 | }
160 |
--------------------------------------------------------------------------------
/Tests/GEOSwiftMapKitTests/MapKit+Equatable.swift:
--------------------------------------------------------------------------------
1 | import MapKit
2 |
3 | #if compiler(>=6)
4 | extension CLLocationCoordinate2D: @retroactive Equatable {
5 | public static func == (lhs: CLLocationCoordinate2D, rhs: CLLocationCoordinate2D) -> Bool {
6 | lhs.latitude == rhs.latitude
7 | && lhs.longitude == rhs.longitude
8 | }
9 | }
10 |
11 | extension MKCoordinateSpan: @retroactive Equatable {
12 | public static func == (lhs: MKCoordinateSpan, rhs: MKCoordinateSpan) -> Bool {
13 | lhs.latitudeDelta == rhs.latitudeDelta
14 | && lhs.longitudeDelta == rhs.longitudeDelta
15 | }
16 | }
17 |
18 | extension MKCoordinateRegion: @retroactive Equatable {
19 | public static func == (lhs: MKCoordinateRegion, rhs: MKCoordinateRegion) -> Bool {
20 | lhs.center == rhs.center
21 | && lhs.span == rhs.span
22 | }
23 | }
24 |
25 | extension MKMapPoint: @retroactive Equatable {
26 | public static func == (lhs: MKMapPoint, rhs: MKMapPoint) -> Bool {
27 | lhs.x == rhs.x
28 | && lhs.y == rhs.y
29 | }
30 | }
31 |
32 | extension MKMapSize: @retroactive Equatable {
33 | public static func == (lhs: MKMapSize, rhs: MKMapSize) -> Bool {
34 | lhs.width == rhs.width
35 | && lhs.height == rhs.height
36 | }
37 | }
38 |
39 | extension MKMapRect: @retroactive Equatable {
40 | public static func == (lhs: MKMapRect, rhs: MKMapRect) -> Bool {
41 | lhs.origin == rhs.origin
42 | && lhs.size == rhs.size
43 | }
44 | }
45 | #else
46 | extension CLLocationCoordinate2D: Equatable {
47 | public static func == (lhs: CLLocationCoordinate2D, rhs: CLLocationCoordinate2D) -> Bool {
48 | lhs.latitude == rhs.latitude
49 | && lhs.longitude == rhs.longitude
50 | }
51 | }
52 |
53 | extension MKCoordinateSpan: Equatable {
54 | public static func == (lhs: MKCoordinateSpan, rhs: MKCoordinateSpan) -> Bool {
55 | lhs.latitudeDelta == rhs.latitudeDelta
56 | && lhs.longitudeDelta == rhs.longitudeDelta
57 | }
58 | }
59 |
60 | extension MKCoordinateRegion: Equatable {
61 | public static func == (lhs: MKCoordinateRegion, rhs: MKCoordinateRegion) -> Bool {
62 | lhs.center == rhs.center
63 | && lhs.span == rhs.span
64 | }
65 | }
66 |
67 | extension MKMapPoint: Equatable {
68 | public static func == (lhs: MKMapPoint, rhs: MKMapPoint) -> Bool {
69 | lhs.x == rhs.x
70 | && lhs.y == rhs.y
71 | }
72 | }
73 |
74 | extension MKMapSize: Equatable {
75 | public static func == (lhs: MKMapSize, rhs: MKMapSize) -> Bool {
76 | lhs.width == rhs.width
77 | && lhs.height == rhs.height
78 | }
79 | }
80 |
81 | extension MKMapRect: Equatable {
82 | public static func == (lhs: MKMapRect, rhs: MKMapRect) -> Bool {
83 | lhs.origin == rhs.origin
84 | && lhs.size == rhs.size
85 | }
86 | }
87 | #endif
88 |
--------------------------------------------------------------------------------