├── .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 | [![Cocoapods Compatible](https://img.shields.io/cocoapods/v/GEOSwiftMapKit)](https://cocoapods.org/pods/GEOSwiftMapKit) 4 | [![SwiftPM Compatible](https://img.shields.io/badge/SwiftPM-compatible-4BC51D.svg?style=flat)](https://swift.org/package-manager/) 5 | [![Supported Platforms](https://img.shields.io/cocoapods/p/GEOSwiftMapKit)](https://github.com/GEOSwift/GEOSwiftMapKit) 6 | [![Build Status](https://github.com/GEOSwift/GEOSwiftMapKit/actions/workflows/main.yml/badge.svg)](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 | ![Playground](/README-images/playground.png) 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 | --------------------------------------------------------------------------------