├── gemfile
├── Resources
└── bento_animation3.gif
├── BentoMapExample
├── Assets
│ ├── Assets.xcassets
│ │ ├── Contents.json
│ │ ├── redHexagon.imageset
│ │ │ ├── redHexagon.png
│ │ │ ├── redHexagon@2x.png
│ │ │ ├── redHexagon@3x.png
│ │ │ └── Contents.json
│ │ ├── blueStretch.imageset
│ │ │ ├── blueStretch.png
│ │ │ ├── blueStretch@2x.png
│ │ │ ├── blueStretch@3x.png
│ │ │ └── Contents.json
│ │ └── AppIcon.appiconset
│ │ │ └── Contents.json
│ ├── Info.plist
│ └── Base.lproj
│ │ └── LaunchScreen.storyboard
├── App Delegate
│ └── AppDelegate.swift
├── App
│ ├── CoreGraphicsViewController.swift
│ ├── MainMenuTableViewController.swift
│ ├── QuadTree+SampleData.swift
│ └── MapKitViewController.swift
└── Annotations
│ ├── Annotations.swift
│ └── AnnotationView.swift
├── .swiftlint.yml
├── BentoMap.xcodeproj
├── project.xcworkspace
│ └── contents.xcworkspacedata
├── xcshareddata
│ └── xcschemes
│ │ └── BentoMap.xcscheme
└── project.pbxproj
├── BentoMap
├── Box.swift
├── Resources
│ ├── BentoMap.h
│ └── Info.plist
├── Extensions
│ ├── CGPoint+BentoCoordinate.swift
│ ├── MKMapPoint+BentoCoordinate.swift
│ ├── CLLocationCoordinate2D+BentoCoordinate.swift
│ ├── CGRect+BentoRect.swift
│ ├── CollectionTypeExtensions.swift
│ └── MKMapRect+BentoRect.swift
├── OptionalOr.swift
├── QuadrantWrapper.swift
├── OrdinalNodes.swift
├── QuadTreeResult.swift
├── QuadTreeNode.swift
├── BentoBox.swift
└── QuadTree.swift
├── BentoMapTests
├── Resources
│ └── Info.plist
├── OptionalOrTests.swift
├── QuadTreeTests.swift
└── BentoBoxTests.swift
├── BentoMap.podspec
├── LICENSE
├── .travis.yml
├── .gitignore
├── Gemfile.lock
├── CONTRIBUTING.md
└── README.md
/gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 |
3 | gem 'cocoapods', '~> 1.1'
4 |
--------------------------------------------------------------------------------
/Resources/bento_animation3.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rightpoint/BentoMap/HEAD/Resources/bento_animation3.gif
--------------------------------------------------------------------------------
/BentoMapExample/Assets/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/BentoMapExample/Assets/Assets.xcassets/redHexagon.imageset/redHexagon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rightpoint/BentoMap/HEAD/BentoMapExample/Assets/Assets.xcassets/redHexagon.imageset/redHexagon.png
--------------------------------------------------------------------------------
/BentoMapExample/Assets/Assets.xcassets/blueStretch.imageset/blueStretch.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rightpoint/BentoMap/HEAD/BentoMapExample/Assets/Assets.xcassets/blueStretch.imageset/blueStretch.png
--------------------------------------------------------------------------------
/BentoMapExample/Assets/Assets.xcassets/redHexagon.imageset/redHexagon@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rightpoint/BentoMap/HEAD/BentoMapExample/Assets/Assets.xcassets/redHexagon.imageset/redHexagon@2x.png
--------------------------------------------------------------------------------
/BentoMapExample/Assets/Assets.xcassets/redHexagon.imageset/redHexagon@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rightpoint/BentoMap/HEAD/BentoMapExample/Assets/Assets.xcassets/redHexagon.imageset/redHexagon@3x.png
--------------------------------------------------------------------------------
/.swiftlint.yml:
--------------------------------------------------------------------------------
1 | statement_position:
2 | statement_mode: uncuddled_else
3 |
4 | trailing_comma:
5 | mandatory_comma: true
6 |
7 | disabled_rules:
8 | - line_length
9 | - variable_name
10 |
11 |
--------------------------------------------------------------------------------
/BentoMapExample/Assets/Assets.xcassets/blueStretch.imageset/blueStretch@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rightpoint/BentoMap/HEAD/BentoMapExample/Assets/Assets.xcassets/blueStretch.imageset/blueStretch@2x.png
--------------------------------------------------------------------------------
/BentoMapExample/Assets/Assets.xcassets/blueStretch.imageset/blueStretch@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rightpoint/BentoMap/HEAD/BentoMapExample/Assets/Assets.xcassets/blueStretch.imageset/blueStretch@3x.png
--------------------------------------------------------------------------------
/BentoMap.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/BentoMap/Box.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Box.swift
3 | // BentoMap
4 | //
5 | // Created by Michael Skiba on 2/17/16.
6 | // Copyright © 2016 Raizlabs. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /// A workaround because Swift structs do not
12 | /// allow recursive value types
13 | class Box {
14 |
15 | var value: T
16 |
17 | init(value: T) {
18 | self.value = value
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/BentoMapExample/Assets/Assets.xcassets/redHexagon.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "redHexagon.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "redHexagon@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "redHexagon@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/BentoMap/Resources/BentoMap.h:
--------------------------------------------------------------------------------
1 | //
2 | // BentoMap.h
3 | // BentoMap
4 | //
5 | // Created by Michael Skiba on 7/6/16.
6 | // Copyright © 2016 Raizlabs. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | //! Project version number for BentoBox.
12 | FOUNDATION_EXPORT double BentoMapVersionNumber;
13 |
14 | //! Project version string for BentoBox.
15 | FOUNDATION_EXPORT const unsigned char BentoMapVersionString[];
16 |
17 | // In this header, you should import all the public headers of your framework using statements like #import
18 |
19 |
20 |
--------------------------------------------------------------------------------
/BentoMap/Extensions/CGPoint+BentoCoordinate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CGPoint+BentoCoordinate.swift
3 | // BentoMap
4 | //
5 | // Created by Matthew Buckley on 8/27/16.
6 | // Copyright © 2016 Raizlabs. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension CGPoint: BentoCoordinate {
12 |
13 | public var coordX: CGFloat {
14 | get { return x }
15 | set { x = newValue }
16 | }
17 |
18 | public var coordY: CGFloat {
19 | get { return y }
20 | set { y = newValue }
21 | }
22 |
23 | public init(coordX: CGFloat, coordY: CGFloat) {
24 | self.init(x: coordX, y: coordY)
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/BentoMap/OptionalOr.swift:
--------------------------------------------------------------------------------
1 | //
2 | // OptionalOr.swift
3 | // BentoMap
4 | //
5 | // Created by Michael Skiba on 7/7/16.
6 | // Copyright © 2016 Raizlabs. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | infix operator ||?: LogicalDisjunctionPrecedence
12 |
13 | func ||? (lhs: Bool?, rhs: Bool?) -> Bool {
14 | return (lhs ?? false) || (rhs ?? false)
15 | }
16 |
17 | func ||? (lhs: Bool, rhs: Bool?) -> Bool {
18 | return lhs || (rhs ?? false)
19 | }
20 |
21 | func ||? (lhs: Bool?, rhs: Bool) -> Bool {
22 | return (lhs ?? false) || rhs
23 | }
24 |
25 | func ||? (lhs: Bool, rhs: Bool) -> Bool {
26 | return lhs || rhs
27 | }
28 |
--------------------------------------------------------------------------------
/BentoMap/Extensions/MKMapPoint+BentoCoordinate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MKMapPoint+BentoCoordinate.swift
3 | // BentoMap
4 | //
5 | // Created by Matthew Buckley on 8/27/16.
6 | // Copyright © 2016 Raizlabs. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import MapKit
11 |
12 | extension MKMapPoint: BentoCoordinate {
13 |
14 | public var coordX: CGFloat {
15 | get { return CGFloat(x) }
16 | set { x = Double(newValue) }
17 | }
18 |
19 | public var coordY: CGFloat {
20 | get { return CGFloat(y) }
21 | set { y = Double(newValue) }
22 | }
23 |
24 | public init(coordX: CGFloat, coordY: CGFloat) {
25 | self.init(x: Double(coordX), y: Double(coordY))
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/BentoMapExample/App Delegate/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // BentoMapExample
4 | //
5 | // Created by Michael Skiba on 7/6/16.
6 | // Copyright © 2016 Raizlabs. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 |
14 | var window: UIWindow?
15 |
16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
17 |
18 | window = UIWindow(frame: UIScreen.main.bounds)
19 |
20 | window?.rootViewController = UINavigationController.init(rootViewController: MainMenuTableViewController())
21 | window?.makeKeyAndVisible()
22 | return true
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/BentoMapTests/Resources/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 |
24 |
25 |
--------------------------------------------------------------------------------
/BentoMap/Extensions/CLLocationCoordinate2D+BentoCoordinate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CLLocationCoordinate2D+BentoCoordinate.swift
3 | // BentoMap
4 | //
5 | // Created by Matthew Buckley on 8/27/16.
6 | // Copyright © 2016 Raizlabs. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import CoreLocation
11 |
12 | extension CLLocationCoordinate2D: BentoCoordinate {
13 |
14 | public var coordX: CGFloat {
15 | get { return CGFloat(latitude) }
16 | set { latitude = CLLocationDegrees(newValue) }
17 | }
18 |
19 | public var coordY: CGFloat {
20 | get { return CGFloat(longitude) }
21 | set { longitude = CLLocationDegrees(newValue) }
22 | }
23 |
24 | public init(coordX: CGFloat, coordY: CGFloat) {
25 | self.init(latitude: CLLocationDegrees(coordX), longitude: CLLocationDegrees(coordY))
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/BentoMap/Resources/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 0.3.3
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | $(CURRENT_PROJECT_VERSION)
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/BentoMap.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = 'BentoMap'
3 | s.version = '0.3.3'
4 | s.summary = 'Map Clustering for Swift.'
5 |
6 | s.description = <<-DESC
7 | A library to handle storing map annotations in a Quadtrees, and for returning
8 | clusters of annotations to allow easy grouping of map items based on proximity.
9 | DESC
10 |
11 | s.homepage = 'https://github.com/Raizlabs/BentoMap'
12 | s.license = { :type => 'MIT', :file => 'LICENSE' }
13 | s.author = { 'Michael Skiba' => 'mike.skiba@raizlabs.com', 'Rob Visentin' => 'rob.visentin@raizlabs.com', 'Matt Buckley' => 'matt.buckley@raizlabs.com' }
14 | s.source = { :git => 'https://github.com/Raizlabs/BentoMap.git', :tag => s.version.to_s }
15 | s.social_media_url = 'https://twitter.com/atelierclkwrk'
16 |
17 | s.ios.deployment_target = '8.0'
18 |
19 | s.source_files = 'BentoMap/**/*'
20 |
21 | s.frameworks = 'Foundation', 'MapKit'
22 |
23 | end
24 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Raizlabs
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 all
13 | 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 THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/BentoMap/Extensions/CGRect+BentoRect.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CGRect+BentoRect.swift
3 | // BentoMap
4 | //
5 | // Created by Matthew Buckley on 8/27/16.
6 | // Copyright © 2016 Raizlabs. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension CGRect: BentoRect {
12 |
13 | public func containsCoordinate(_ c: BentoCoordinate) -> Bool {
14 | let point = CGPoint(x: c.coordX, y: c.coordY)
15 | return self.contains(point)
16 | }
17 |
18 | public func divide(_ percent: CGFloat, edge: CGRectEdge) -> (CGRect, CGRect) {
19 | let amount: CGFloat
20 | switch edge {
21 | case .maxXEdge, .minXEdge:
22 | amount = size.width / 2.0
23 | case .maxYEdge, .minYEdge:
24 | amount = size.height / 2.0
25 | }
26 |
27 | let (slice, remainder) = divided(atDistance: amount, from: edge)
28 | return (slice: slice, remainder: remainder)
29 | }
30 |
31 | public func unionWith(_ other: CGRect) -> CGRect {
32 | return union(other)
33 | }
34 |
35 | public init(originCoordinate origin: BentoCoordinate, size: CGSize) {
36 | self.init(origin: CGPoint(x: origin.coordX, y: origin.coordY), size: size)
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/BentoMap/QuadrantWrapper.swift:
--------------------------------------------------------------------------------
1 | //
2 | // QuadrantWrapper.swift
3 | // BentoMap
4 | //
5 | // Created by Michael Skiba on 7/5/16.
6 | // Copyright © 2016 Raizlabs. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | struct QuadrantWrapper {
12 |
13 | /// top left node
14 | var northWest: Quadrant
15 |
16 | /// top right node
17 | var northEast: Quadrant
18 |
19 | /// bottom left node
20 | var southWest: Quadrant
21 |
22 | /// bottom right node
23 | var southEast: Quadrant
24 |
25 | /**
26 | Applies a closure to each quadrant.
27 |
28 | - parameter converter: a closure that takes a Quadrant type
29 | and returns another type specified in the function signature.
30 |
31 | - returns: a new QuadrantWrapper containing a new type.
32 | */
33 | func map(_ converter: (Quadrant) -> NewQuadrant) -> QuadrantWrapper {
34 | return QuadrantWrapper(northWest: converter(northWest),
35 | northEast: converter(northEast),
36 | southWest: converter(southWest),
37 | southEast: converter(southEast))
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | env:
2 | global:
3 | - LC_CTYPE=en_US.UTF-8
4 |
5 | branches:
6 | only:
7 | - develop
8 | - master
9 |
10 | os: osx
11 | language: objective-c
12 | matrix:
13 | exclude:
14 | - os: osx
15 | include:
16 | - osx_image: xcode8
17 | env:
18 | - XCODE_ACTION="clean test"
19 | XCODE_TOOLCHAIN="swift"
20 | XCODE_DESTINATION="platform=iOS Simulator,id=237D7619-4A4E-4624-8925-67A42A8A690A"
21 | - osx_image: xcode8
22 | env:
23 | - JOB=CARTHAGE
24 | script:
25 | - brew update 1> /dev/null 2> /dev/null
26 | - brew outdated carthage || brew upgrade carthage
27 | - carthage build --no-skip-current
28 | - test -d Carthage/Build/iOS/BentoMap.framework || exit 1
29 | - osx_image: xcode8
30 | env:
31 | - JOB=COCOAPODS
32 | script:
33 | - bundle exec pod lib lint --sources="https://github.com/Raizlabs/BentoMap"
34 |
35 | before_script:
36 | - killall "Simulator" || echo "No matching processes belonging to you were found"
37 |
38 | script:
39 | - travis_retry xcodebuild ${XCODE_ACTION}
40 | -toolchain ${XCODE_TOOLCHAIN}
41 | -project BentoMap.xcodeproj
42 | -scheme BentoMap
43 | -destination "${XCODE_DESTINATION}"
44 |
--------------------------------------------------------------------------------
/BentoMapExample/Assets/Assets.xcassets/blueStretch.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "resizing" : {
5 | "mode" : "3-part-horizontal",
6 | "center" : {
7 | "mode" : "stretch",
8 | "width" : 12
9 | },
10 | "cap-insets" : {
11 | "right" : 10,
12 | "left" : 10
13 | }
14 | },
15 | "idiom" : "universal",
16 | "filename" : "blueStretch.png",
17 | "scale" : "1x"
18 | },
19 | {
20 | "resizing" : {
21 | "mode" : "3-part-horizontal",
22 | "center" : {
23 | "mode" : "stretch",
24 | "width" : 18
25 | },
26 | "cap-insets" : {
27 | "right" : 23,
28 | "left" : 23
29 | }
30 | },
31 | "idiom" : "universal",
32 | "filename" : "blueStretch@2x.png",
33 | "scale" : "2x"
34 | },
35 | {
36 | "resizing" : {
37 | "mode" : "3-part-horizontal",
38 | "center" : {
39 | "mode" : "stretch",
40 | "width" : 26
41 | },
42 | "cap-insets" : {
43 | "right" : 35,
44 | "left" : 35
45 | }
46 | },
47 | "idiom" : "universal",
48 | "filename" : "blueStretch@3x.png",
49 | "scale" : "3x"
50 | }
51 | ],
52 | "info" : {
53 | "version" : 1,
54 | "author" : "xcode"
55 | }
56 | }
--------------------------------------------------------------------------------
/BentoMapExample/Assets/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 0.3.3
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 | LSRequiresIPhoneOS
24 |
25 | UILaunchStoryboardName
26 | LaunchScreen
27 | UIRequiredDeviceCapabilities
28 |
29 | armv7
30 |
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationLandscapeLeft
35 | UIInterfaceOrientationLandscapeRight
36 |
37 | UISupportedInterfaceOrientations~ipad
38 |
39 | UIInterfaceOrientationPortrait
40 | UIInterfaceOrientationPortraitUpsideDown
41 | UIInterfaceOrientationLandscapeLeft
42 | UIInterfaceOrientationLandscapeRight
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## Build generated
6 | build/
7 | DerivedData/
8 |
9 | ## Various settings
10 | *.pbxuser
11 | !default.pbxuser
12 | *.mode1v3
13 | !default.mode1v3
14 | *.mode2v3
15 | !default.mode2v3
16 | *.perspectivev3
17 | !default.perspectivev3
18 | xcuserdata/
19 |
20 | ## Other
21 | *.moved-aside
22 | *.xcuserstate
23 |
24 | ## Obj-C/Swift specific
25 | *.hmap
26 | *.ipa
27 | *.dSYM.zip
28 | *.dSYM
29 |
30 | ## Playgrounds
31 | timeline.xctimeline
32 | playground.xcworkspace
33 |
34 | # Swift Package Manager
35 | #
36 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
37 | # Packages/
38 | .build/
39 |
40 | # CocoaPods
41 | #
42 | # We recommend against adding the Pods directory to your .gitignore. However
43 | # you should judge for yourself, the pros and cons are mentioned at:
44 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
45 | #
46 | # Pods/
47 |
48 | # Carthage
49 | #
50 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
51 | # Carthage/Checkouts
52 |
53 | Carthage/Build
54 |
55 | # fastlane
56 | #
57 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
58 | # screenshots whenever they are needed.
59 | # For more information about the recommended setup visit:
60 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md
61 |
62 | fastlane/report.xml
63 | fastlane/Preview.html
64 | fastlane/screenshots
65 | fastlane/test_output
66 |
--------------------------------------------------------------------------------
/BentoMapExample/Assets/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/BentoMapExample/App/CoreGraphicsViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CoreGraphicsViewController.swift
3 | // BentoMap
4 | //
5 | // Created by Matthew Buckley on 9/5/16.
6 | // Copyright © 2016 Raizlabs. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import BentoMap
11 |
12 | class CoreGraphicsViewController: UIViewController {
13 |
14 | override func viewWillLayoutSubviews() {
15 | super.viewWillLayoutSubviews()
16 | let gridData = QuadTree.sampleGridData(forContainerRect: view.frame)
17 |
18 | let map: BentoBox = BentoBox(root: view.frame)
19 | let mapView = UIView(frame: map.root)
20 | view.addSubview(mapView)
21 | mapView.backgroundColor = .white
22 |
23 | let clusterResults = gridData.clusteredDataWithinMapRect(map.root,
24 | zoomScale: 1.0,
25 | cellSize: 64)
26 |
27 | for result in clusterResults {
28 | switch result {
29 | case .single(let node):
30 | let node = UIView(frame: CGRect(origin: node.originCoordinate, size: CGSize(width: 5.0, height: 5.0)))
31 | node.backgroundColor = .blue
32 | node.layer.cornerRadius = 2.5
33 | view.addSubview(node)
34 | case .multiple(let nodes):
35 | for node in nodes {
36 | let node = UIView(frame: CGRect(origin: node.originCoordinate, size: CGSize(width: 5.0, height: 5.0)))
37 | node.layer.cornerRadius = 2.5
38 | view.addSubview(node)
39 | node.backgroundColor = .blue
40 | }
41 | }
42 | }
43 | }
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/BentoMapExample/App/MainMenuTableViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MainMenuTableViewController.swift
3 | // BentoMap
4 | //
5 | // Created by Matthew Buckley on 9/5/16.
6 | // Copyright © 2016 Raizlabs. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class MainMenuTableViewController: UITableViewController {
12 |
13 | override func viewDidLoad() {
14 | super.viewDidLoad()
15 |
16 | tableView.register(UITableViewCell.self, forCellReuseIdentifier: "reuseIdentifier")
17 | tableView.separatorStyle = .none
18 | }
19 |
20 | // MARK: - Table view data source
21 |
22 | override func numberOfSections(in tableView: UITableView) -> Int {
23 | return 1
24 | }
25 |
26 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
27 | return 2
28 | }
29 |
30 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
31 | guard let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier") else {
32 | return UITableViewCell()
33 | }
34 | if indexPath.row == 0 {
35 | cell.textLabel?.text = "MapKit Example"
36 | }
37 | else if indexPath.row == 1 {
38 | cell.textLabel?.text = "CoreGraphics Example"
39 | }
40 | cell.accessoryType = .disclosureIndicator
41 | return cell
42 | }
43 |
44 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
45 | if indexPath.row == 0 {
46 | navigationController?.pushViewController(MapKitViewController(), animated: true)
47 | }
48 | else if indexPath.row == 1 {
49 | navigationController?.pushViewController(CoreGraphicsViewController(), animated: true)
50 | }
51 | }
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/BentoMapTests/OptionalOrTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // OptionalOrTests.swift
3 | // BentoMap
4 | //
5 | // Created by Michael Skiba on 7/7/16.
6 | // Copyright © 2016 Raizlabs. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import BentoMap
11 |
12 | class OptionalOrTests: XCTestCase {
13 | func testOptionalOr() {
14 | let truePairs: [(Bool?, Bool?)] = [
15 | (true, true),
16 | (true, false),
17 | (false, true),
18 | (true, nil),
19 | (nil, true),
20 | ]
21 | for (lhs, rhs) in truePairs {
22 | XCTAssert(lhs ||? rhs, "Should be true")
23 | if let unwrappedLeft = lhs {
24 | XCTAssert(unwrappedLeft ||? rhs, "Should be true")
25 | }
26 | if let unwrappedRight = rhs {
27 | XCTAssert(lhs ||? unwrappedRight, "Should be true")
28 | }
29 | if let unwrappedLeft = lhs, let unwrappedRight = rhs {
30 | XCTAssert(unwrappedLeft ||? unwrappedRight, "Should be true")
31 | }
32 | }
33 |
34 | let falsePairs: [(Bool?, Bool?)] = [
35 | (false, false),
36 | (nil, false),
37 | (false, nil),
38 | (nil, nil),
39 | ]
40 | for (lhs, rhs) in falsePairs {
41 | XCTAssertFalse(lhs ||? rhs, "Should be false")
42 | if let unwrappedLeft = lhs {
43 | XCTAssertFalse(unwrappedLeft ||? rhs, "Should be false")
44 | }
45 | if let unwrappedRight = rhs {
46 | XCTAssertFalse(lhs ||? unwrappedRight, "Should be false")
47 | }
48 | if let unwrappedLeft = lhs, let unwrappedRight = rhs {
49 | XCTAssertFalse(unwrappedLeft ||? unwrappedRight, "Should be false")
50 | }
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/BentoMap/Extensions/CollectionTypeExtensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CollectionTypeExtensions.swift
3 | // BentoMap
4 | //
5 | // Created by Rob Visentin on 8/1/16.
6 | // Copyright © 2016 Raizlabs. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import MapKit
11 |
12 | public extension Collection where Iterator.Element: BentoCoordinate {
13 |
14 | func bentoBox() -> BentoBox {
15 |
16 | let coordinates: [BentoCoordinate] = flatMap({return $0 as BentoCoordinate})
17 |
18 | return BentoBox(root: bb(coordinates, rectType: Rect.self))
19 | }
20 |
21 | }
22 |
23 | public extension Collection {
24 |
25 | func bb(_ coords: [BentoCoordinate], rectType: Rect.Type) -> Rect {
26 | var min: CGPoint = CGPoint(x: CGFloat.greatestFiniteMagnitude, y: CGFloat.greatestFiniteMagnitude)
27 | var max: CGPoint = CGPoint.zero
28 |
29 | for point in coords {
30 |
31 | if point.coordX < min.coordX {
32 | min.coordX = point.coordX
33 | }
34 | if point.coordX > max.coordX {
35 | max.coordX = point.coordX
36 | }
37 | if point.coordY < min.coordY {
38 | min.coordY = point.coordY
39 | }
40 | if point.coordY > max.coordY {
41 | max.coordY = point.coordY
42 | }
43 | }
44 |
45 | return Rect(originCoordinate: min, size: CGSize(width: max.x - min.x, height: max.y - min.y))
46 | }
47 | }
48 |
49 | public extension Collection where Iterator.Element: CoordinateProvider {
50 |
51 | func boundingBox() -> BentoBox {
52 | let boundingBox: [BentoCoordinate] = map({ $0.coordinate })
53 |
54 | return BentoBox(root: bb(boundingBox, rectType: Rect.self))
55 | }
56 |
57 | }
58 |
--------------------------------------------------------------------------------
/BentoMapExample/Assets/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "20x20",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "20x20",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "29x29",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "29x29",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "40x40",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "40x40",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "size" : "60x60",
36 | "scale" : "2x"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "size" : "60x60",
41 | "scale" : "3x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "20x20",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "20x20",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "29x29",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "29x29",
61 | "scale" : "2x"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "size" : "40x40",
66 | "scale" : "1x"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "size" : "40x40",
71 | "scale" : "2x"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "size" : "76x76",
76 | "scale" : "1x"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "size" : "76x76",
81 | "scale" : "2x"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "size" : "83.5x83.5",
86 | "scale" : "2x"
87 | }
88 | ],
89 | "info" : {
90 | "version" : 1,
91 | "author" : "xcode"
92 | }
93 | }
--------------------------------------------------------------------------------
/BentoMap/Extensions/MKMapRect+BentoRect.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MKMapRect+BentoRect.swift
3 | // BentoMap
4 | //
5 | // Created by Matthew Buckley on 8/27/16.
6 | // Copyright © 2016 Raizlabs. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import MapKit
11 |
12 | extension MKMapRect: BentoRect {
13 |
14 | public var minX: CGFloat {
15 | return CGFloat(MKMapRectGetMinX(self))
16 | }
17 |
18 | public var minY: CGFloat {
19 | return CGFloat(MKMapRectGetMinY(self))
20 | }
21 |
22 | public var maxX: CGFloat {
23 | return CGFloat(MKMapRectGetMaxX(self))
24 | }
25 |
26 | public var maxY: CGFloat {
27 | return CGFloat(MKMapRectGetMaxY(self))
28 | }
29 |
30 | public func containsCoordinate(_ c: BentoCoordinate) -> Bool {
31 | let originCoordinate = MKMapPoint(x: Double(c.coordX), y: Double(c.coordY))
32 | return MKMapRectContainsPoint(self, originCoordinate)
33 | }
34 |
35 | public func divide(_ percent: CGFloat, edge: CGRectEdge) -> (MKMapRect, MKMapRect) {
36 | let amount: Double
37 | switch edge {
38 | case .maxXEdge, .minXEdge:
39 | amount = size.width / 2.0
40 | case .maxYEdge, .minYEdge:
41 | amount = size.height / 2.0
42 | }
43 |
44 | let slice = UnsafeMutablePointer.allocate(capacity: 1)
45 | defer {
46 | slice.deinitialize()
47 | }
48 | let remainder = UnsafeMutablePointer.allocate(capacity: 1)
49 | defer {
50 | remainder.deinitialize()
51 | }
52 | MKMapRectDivide(self, slice, remainder, amount, edge)
53 | return (slice: slice[0], remainder: remainder[0])
54 | }
55 |
56 | public func unionWith(_ other: MKMapRect) -> MKMapRect {
57 | return MKMapRectUnion(self, other)
58 | }
59 |
60 | public init(originCoordinate origin: BentoCoordinate, size: CGSize) {
61 | self.init(origin: MKMapPoint(x: Double(origin.coordX), y: Double(origin.coordY)), size: MKMapSize(width: Double(size.width), height: Double(size.height)))
62 | }
63 |
64 | }
65 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: https://rubygems.org/
3 | specs:
4 | CFPropertyList (2.3.5)
5 | activesupport (4.2.7.1)
6 | i18n (~> 0.7)
7 | json (~> 1.7, >= 1.7.7)
8 | minitest (~> 5.1)
9 | thread_safe (~> 0.3, >= 0.3.4)
10 | tzinfo (~> 1.1)
11 | claide (1.0.1)
12 | cocoapods (1.2.0)
13 | activesupport (>= 4.0.2, < 5)
14 | claide (>= 1.0.1, < 2.0)
15 | cocoapods-core (= 1.2.0)
16 | cocoapods-deintegrate (>= 1.0.1, < 2.0)
17 | cocoapods-downloader (>= 1.1.3, < 2.0)
18 | cocoapods-plugins (>= 1.0.0, < 2.0)
19 | cocoapods-search (>= 1.0.0, < 2.0)
20 | cocoapods-stats (>= 1.0.0, < 2.0)
21 | cocoapods-trunk (>= 1.1.2, < 2.0)
22 | cocoapods-try (>= 1.1.0, < 2.0)
23 | colored (~> 1.2)
24 | escape (~> 0.0.4)
25 | fourflusher (~> 2.0.1)
26 | gh_inspector (~> 1.0)
27 | molinillo (~> 0.5.5)
28 | nap (~> 1.0)
29 | ruby-macho (~> 0.2.5)
30 | xcodeproj (>= 1.4.1, < 2.0)
31 | cocoapods-core (1.2.0)
32 | activesupport (>= 4.0.2, < 5)
33 | fuzzy_match (~> 2.0.4)
34 | nap (~> 1.0)
35 | cocoapods-deintegrate (1.0.1)
36 | cocoapods-downloader (1.1.3)
37 | cocoapods-plugins (1.0.0)
38 | nap
39 | cocoapods-search (1.0.0)
40 | cocoapods-stats (1.0.0)
41 | cocoapods-trunk (1.1.2)
42 | nap (>= 0.8, < 2.0)
43 | netrc (= 0.7.8)
44 | cocoapods-try (1.1.0)
45 | colored (1.2)
46 | escape (0.0.4)
47 | fourflusher (2.0.1)
48 | fuzzy_match (2.0.4)
49 | gh_inspector (1.0.3)
50 | i18n (0.8.0)
51 | json (1.8.6)
52 | minitest (5.10.1)
53 | molinillo (0.5.6)
54 | nanaimo (0.2.3)
55 | nap (1.1.0)
56 | netrc (0.7.8)
57 | ruby-macho (0.2.6)
58 | thread_safe (0.3.5)
59 | tzinfo (1.2.2)
60 | thread_safe (~> 0.1)
61 | xcodeproj (1.4.2)
62 | CFPropertyList (~> 2.3.3)
63 | activesupport (>= 3)
64 | claide (>= 1.0.1, < 2.0)
65 | colored (~> 1.2)
66 | nanaimo (~> 0.2.3)
67 |
68 | PLATFORMS
69 | ruby
70 |
71 | DEPENDENCIES
72 | cocoapods (~> 1.1)
73 |
74 | BUNDLED WITH
75 | 1.14.4
76 |
--------------------------------------------------------------------------------
/BentoMap/OrdinalNodes.swift:
--------------------------------------------------------------------------------
1 | //
2 | // OrdinalNodes.swift
3 | // BentoMap
4 | //
5 | // Created by Michael Skiba on 2/17/16.
6 | // Copyright © 2016 Raizlabs. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public struct OrdinalNodes {
12 |
13 | /// A type that exposes all 4 quadrants of a given QuadTree
14 | fileprivate typealias QuadTreeWrapper = QuadrantWrapper>
15 |
16 | // Recursive structs require a workaround class type
17 | // because it is impossible at runtime to calculate
18 | // how much memory must be allocated
19 | fileprivate var quadrants: Box
20 |
21 | /// The NorthWest quadrant of the QuadTree's root.
22 | var northWest: QuadTree {
23 | get {
24 | return quadrants.value.northWest
25 | }
26 | set {
27 | quadrants.value.northWest = newValue
28 | }
29 | }
30 |
31 | /// The NorthEast quadrant of the QuadTree's root.
32 | var northEast: QuadTree {
33 | get {
34 | return quadrants.value.northEast
35 | }
36 | set {
37 | quadrants.value.northEast = newValue
38 | }
39 | }
40 |
41 | /// The SouthWest quadrant of the QuadTree's root.
42 | var southWest: QuadTree {
43 | get {
44 | return quadrants.value.southWest
45 | }
46 | set {
47 | quadrants.value.southWest = newValue
48 | }
49 | }
50 |
51 | /// The SouthEast quadrant of the QuadTree's root.
52 | var southEast: QuadTree {
53 | get {
54 | return quadrants.value.southEast
55 | }
56 | set {
57 | quadrants.value.southEast = newValue
58 | }
59 | }
60 |
61 | init(northWest: QuadTree,
62 | northEast: QuadTree,
63 | southWest: QuadTree,
64 | southEast: QuadTree) {
65 | quadrants = Box(value: QuadrantWrapper(northWest: northWest,
66 | northEast: northEast,
67 | southWest: southWest,
68 | southEast: southEast))
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/BentoMapExample/Annotations/Annotations.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Annotations.swift
3 | // BentoMap
4 | //
5 | // Created by Michael Skiba on 7/7/16.
6 | // Copyright © 2016 Raizlabs. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import MapKit
11 | import BentoMap
12 |
13 | class BaseAnnotation: NSObject, MKAnnotation {
14 | let originCoordinate: MKMapPoint
15 | let root: MKMapRect
16 | var coordinate: CLLocationCoordinate2D {
17 | return MKCoordinateForMapPoint(originCoordinate)
18 | }
19 |
20 | init(originCoordinate: MKMapPoint, root: MKMapRect) {
21 | self.originCoordinate = originCoordinate
22 | self.root = root
23 | }
24 |
25 | static func makeAnnotation(_ result: QuadTreeResult) -> BaseAnnotation {
26 | let annotation: BaseAnnotation
27 | switch result {
28 | case let .single(node: node):
29 | annotation = SingleAnnotation(originCoordinate: result.originCoordinate,
30 | annotationNumber: node.content,
31 | root: result.contentRect)
32 | case let .multiple(nodes: nodes):
33 | annotation = ClusterAnnotation(originCoordinate: result.originCoordinate,
34 | annotationNumbers: nodes.map({ $0.content }),
35 | root: result.contentRect)
36 | }
37 | return annotation
38 | }
39 | }
40 |
41 | final class SingleAnnotation: BaseAnnotation {
42 |
43 | let annotationNumber: Int
44 | var title: String?
45 |
46 | init(originCoordinate: MKMapPoint, annotationNumber: Int, root: MKMapRect) {
47 | self.annotationNumber = annotationNumber
48 | super.init(originCoordinate: originCoordinate, root: root)
49 | title = "Point #\(annotationNumber)"
50 | }
51 |
52 | }
53 |
54 | final class ClusterAnnotation: BaseAnnotation {
55 |
56 | let annotationNumbers: [Int]
57 |
58 | init(originCoordinate: MKMapPoint, annotationNumbers: [Int], root: MKMapRect) {
59 | self.annotationNumbers = annotationNumbers.sorted()
60 | super.init(originCoordinate: originCoordinate, root: root)
61 | }
62 |
63 | }
64 |
65 | func == (lhs: BaseAnnotation, rhs: BaseAnnotation) -> Bool {
66 | if let lSingle = lhs as? SingleAnnotation,
67 | let rSingle = rhs as? SingleAnnotation {
68 | return lSingle.annotationNumber == rSingle.annotationNumber
69 | }
70 | else if let lMulti = lhs as? ClusterAnnotation,
71 | let rMulti = rhs as? ClusterAnnotation {
72 | return lMulti.annotationNumbers == rMulti.annotationNumbers
73 | }
74 | return false
75 | }
76 |
--------------------------------------------------------------------------------
/BentoMap/QuadTreeResult.swift:
--------------------------------------------------------------------------------
1 | //
2 | // QuadTreeResult.swift
3 | // BentoMap
4 | //
5 | // Created by Michael Skiba on 2/17/16.
6 | // Copyright © 2016 Raizlabs. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import MapKit
11 |
12 | public enum QuadTreeResult {
13 |
14 | case single(node: QuadTreeNode)
15 | case multiple(nodes: [QuadTreeNode])
16 |
17 | /// The average of the origin points of all the nodes
18 | /// contained in the QuadTree.
19 | public var originCoordinate: Coordinate {
20 | let originCoordinate: Coordinate
21 | switch self {
22 | case let .single(node):
23 | originCoordinate = node.originCoordinate
24 | case let .multiple(nodes):
25 | var x: CGFloat = 0.0
26 | var y: CGFloat = 0.0
27 | for node in nodes {
28 | x += node.originCoordinate.coordX
29 | y += node.originCoordinate.coordY
30 | }
31 | x /= CGFloat(nodes.count)
32 | y /= CGFloat(nodes.count)
33 | originCoordinate = Coordinate(coordX: x, coordY: y)
34 | }
35 | return originCoordinate
36 | }
37 |
38 | /// The smallest possible rectangle that contains the node(s) contained in this QuadTree.
39 | public var contentRect: Rect {
40 | let origin: Coordinate
41 | let size: CGSize
42 | switch self {
43 | case let .single(node: node):
44 | origin = node.originCoordinate
45 | size = CGSize()
46 | case let .multiple(nodes: nodes):
47 | var minCoordinate = CGPoint(x: CGFloat.greatestFiniteMagnitude, y: CGFloat.greatestFiniteMagnitude)
48 | var maxCoordinate = CGPoint(x: -CGFloat.greatestFiniteMagnitude, y: -CGFloat.greatestFiniteMagnitude)
49 | for node in nodes {
50 | minCoordinate.x = min(minCoordinate.coordX, node.originCoordinate.coordX)
51 | minCoordinate.y = min(minCoordinate.coordY, node.originCoordinate.coordY)
52 | maxCoordinate.x = max(maxCoordinate.coordX, node.originCoordinate.coordX)
53 | maxCoordinate.y = max(maxCoordinate.coordY, node.originCoordinate.coordY)
54 | }
55 | origin = Coordinate(coordX: minCoordinate.coordX, coordY: minCoordinate.coordY)
56 | // slightly pad the size to make sure all nodes are contained
57 | size = CGSize(width: abs(minCoordinate.x - maxCoordinate.x) + 0.001,
58 | height: abs(minCoordinate.y - maxCoordinate.y) + 0.001)
59 | }
60 | return Rect(originCoordinate: origin, size: size)
61 | }
62 |
63 | }
64 |
--------------------------------------------------------------------------------
/BentoMap/QuadTreeNode.swift:
--------------------------------------------------------------------------------
1 | //
2 | // QuadTreeNode.swift
3 | // BentoMap
4 | //
5 | // Created by Michael Skiba on 2/17/16.
6 | // Copyright © 2016 Raizlabs. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public protocol BentoCoordinate {
12 |
13 | /// The horizontal location of the coordinate
14 | var coordX: CGFloat { get set }
15 |
16 | /// The vertical location of the coordinate
17 | var coordY: CGFloat { get set }
18 |
19 | init(coordX: CGFloat, coordY: CGFloat)
20 | }
21 |
22 | public protocol BentoRect {
23 |
24 | /// The horizontal max of the rectangle.
25 | var maxX: CGFloat { get }
26 |
27 | /// The vertical max of the rectangle.
28 | var maxY: CGFloat { get }
29 |
30 | /// The horizontal min of the rectangle.
31 | var minX: CGFloat { get }
32 |
33 | /// The vertical min of the rectangle.
34 | var minY: CGFloat { get }
35 |
36 | /**
37 | A test to determine if this rectangle contains
38 | a given coordinate.
39 |
40 | - parameter c: a coordinate.
41 |
42 | - returns: a Bool indicating whether this rectangle contains the coordinate passed in.
43 | */
44 | func containsCoordinate(_ c: BentoCoordinate) -> Bool
45 |
46 | /**
47 | Divides this rectangle at a given percentage
48 | on the axis of the supplied edge.
49 |
50 | - parameter percent: the percentage at which to divide this rectangle.
51 | - parameter edge: the edge from which to divide this rectangle.
52 |
53 | - returns: a tuple containing a rectangle that is the percentage requested,
54 | and a rectangle that is the remainder.
55 | */
56 | func divide(_ percent: CGFloat, edge: CGRectEdge) -> (Self, Self)
57 |
58 | /**
59 | * Computes the geometric union of this rectangle with the supplied rectangle.
60 | *
61 | * - parameter other: a rectangle.
62 | */
63 | func unionWith(_ other: Self) -> Self
64 |
65 | init(originCoordinate: BentoCoordinate, size: CGSize)
66 | }
67 |
68 | public struct QuadTreeNode {
69 |
70 | /// The location of this node in the map's coordinate space.
71 | public var originCoordinate: Coordinate
72 |
73 | /// The data associated with this node.
74 | public var content: NodeData
75 |
76 | public init(originCoordinate: Coordinate, content: NodeData) {
77 | self.originCoordinate = originCoordinate
78 | self.content = content
79 | }
80 |
81 | }
82 |
83 | public protocol CoordinateProvider {
84 |
85 | /// The type of coordinate associated with the conforming object.
86 | associatedtype CoordinateType: BentoCoordinate
87 |
88 | /// The coordinate associated with the conforming object.
89 | var coordinate: CoordinateType { get }
90 |
91 | }
92 |
93 | extension QuadTreeNode: CoordinateProvider {
94 |
95 | public typealias CoordinateType = Coordinate
96 |
97 | /// The origin coordinate of the QuadTree.
98 | public var coordinate: Coordinate {
99 | return originCoordinate
100 | }
101 |
102 | }
103 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, gender identity and expression, level of experience,
9 | nationality, personal appearance, race, religion, or sexual identity and
10 | orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at . All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at [http://contributor-covenant.org/version/1/4][version]
72 |
73 | [homepage]: http://contributor-covenant.org
74 | [version]: http://contributor-covenant.org/version/1/4/
--------------------------------------------------------------------------------
/BentoMap/BentoBox.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BentoBox.swift
3 | // BentoMap
4 | //
5 | // Created by Michael Skiba on 2/17/16.
6 | // Copyright © 2016 Raizlabs. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public struct BentoBox {
12 |
13 | /// The root node of the tree. Can be thought of
14 | /// as the container map of the sub-rectangles
15 | public var root: Rect
16 |
17 | public init(minPoint: Coordinate, maxPoint: Coordinate) {
18 |
19 | let minX = min(minPoint.coordX, maxPoint.coordX)
20 | let minY = min(minPoint.coordY, maxPoint.coordY)
21 | let maxX = max(minPoint.coordX, maxPoint.coordX)
22 | let maxY = max(minPoint.coordY, maxPoint.coordY)
23 |
24 | root = Rect(originCoordinate: Coordinate(coordX: minX, coordY: minY),
25 | size: CGSize(width: maxX - minX, height: maxY - minY))
26 | }
27 |
28 | public init(root: Rect) {
29 | self.root = root
30 | }
31 |
32 | }
33 |
34 | // MARK: Public API
35 |
36 | public extension BentoBox {
37 |
38 | /// The coordinate at the top left corner of the root rect
39 | public var minCoordinate: Coordinate {
40 | return Coordinate(coordX: root.minX, coordY: root.minY)
41 | }
42 |
43 | /// The coordinate at the bottom right corner of the root rect
44 | public var maxCoordinate: Coordinate {
45 | return Coordinate(coordX: root.maxX, coordY: root.maxY)
46 | }
47 |
48 | }
49 |
50 | extension BentoBox {
51 |
52 | /**
53 | Returns a Bool value for whether the root contains
54 | the coordinate passed in
55 |
56 | - parameter coordinate: a BentoCoordinate
57 |
58 | - returns: whether the root contains
59 | the coordinate passed in
60 | */
61 | func containsCoordinate(_ coordinate: BentoCoordinate) -> Bool {
62 | // we aren't using MKMapRectContainsPoint(mapRect, pt) because it will test true for points that
63 | // are exatly on either edge, which means that a point exact on an edge may be counted in multiple boxes
64 | let width = root.maxX - root.minX
65 | let height = root.maxY - root.minY
66 |
67 | let isContained = coordinate.coordX >= root.minX &&
68 | coordinate.coordX < (root.minX + width) &&
69 | coordinate.coordY >= root.minY &&
70 | coordinate.coordY < (root.minY + height)
71 | return isContained
72 | }
73 |
74 | /**
75 | Returns a Bool value for whether the root intersects with the
76 | root of the bentoBox passed in
77 |
78 | - parameter bentoBox: a BentoBox
79 |
80 | - returns: whether the root intersects with the
81 | root of the bentoBox passed in
82 | */
83 | func intersectsBentoBox(_ bentoBox: BentoBox) -> Bool {
84 | return root.minX < bentoBox.root.maxX && root.maxX > bentoBox.root.minX &&
85 | root.minY < bentoBox.root.maxY && root.maxY > bentoBox.root.minY
86 | }
87 |
88 | /// A wrapper around the 4 child nodes of the root
89 | var quadrants: QuadrantWrapper {
90 | let (north, south) = root.divide(0.5, edge: .minYEdge)
91 | let (northWest, northEast) = north.divide(0.5, edge: .minXEdge)
92 | let (southWest, southEast) = south.divide(0.5, edge: .minXEdge)
93 |
94 | return QuadrantWrapper(northWest: northWest, northEast: northEast, southWest: southWest, southEast: southEast).map(BentoBox.init)
95 | }
96 |
97 | @discardableResult func union(_ other: BentoBox) -> Rect {
98 | return root.unionWith(other.root)
99 | }
100 |
101 | }
102 |
--------------------------------------------------------------------------------
/BentoMapExample/App/QuadTree+SampleData.swift:
--------------------------------------------------------------------------------
1 | //
2 | // QuadTree+sampleMapData.swift
3 | // BentoMap
4 | //
5 | // Created by Michael Skiba on 7/6/16.
6 | // Copyright © 2016 Raizlabs. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import MapKit
11 | import BentoMap
12 |
13 | extension QuadTree {
14 |
15 | static var sampleData: QuadTree {
16 | var samples = QuadTree(bentoBox: BentoBox(minPoint: MKMapPointForCoordinate(CLLocationCoordinate2D.minCoord), maxPoint: MKMapPointForCoordinate(CLLocationCoordinate2D.maxCoord)), bucketCapacity: 5)
17 | let randomData = (1...5000).map { count in
18 | return QuadTreeNode(originCoordinate: MKMapPointForCoordinate(CLLocationCoordinate2D.randomCoordinate()), content: count)
19 | }
20 | for node in randomData {
21 | samples.insertNode(node)
22 | }
23 | return samples
24 | }
25 |
26 | static func sampleGridData(forContainerRect containerRect: CGRect) -> QuadTree {
27 | let minPoint = CGPoint(x: containerRect.minX, y: containerRect.minY)
28 | let maxPoint = CGPoint(x: containerRect.maxX, y: containerRect.maxY)
29 | var samples = QuadTree(bentoBox: BentoBox(minPoint: minPoint, maxPoint: maxPoint), bucketCapacity: 5)
30 | let root = QuadTreeNode(originCoordinate: CGPoint.zero, content: 1000)
31 | samples.insertNode(root)
32 | for count in 1...500 {
33 | let node = QuadTreeNode(originCoordinate: CGPoint.randomPoint(withinRect: containerRect), content: count)
34 | samples.insertNode(node)
35 | }
36 | return samples
37 | }
38 |
39 | }
40 |
41 | private extension CGPoint {
42 |
43 | static func randomPoint(withinRect rect: CGRect) -> CGPoint {
44 | let x = Double.cappedRandom(min: Double(rect.minX), max: Double(rect.maxX))
45 | let y = Double.cappedRandom(min: Double(rect.minY), max: Double(rect.maxY))
46 | return CGPoint(x: x, y: y)
47 | }
48 |
49 | }
50 |
51 | private extension CLLocationCoordinate2D {
52 | enum Coordinates {
53 | static let bostonLatitude: Double = 42.3584300
54 | static let bostonLongitude: Double = -71.0597700
55 | static let adjustment: Double = 0.5
56 | static let minLatitude: Double = Coordinates.bostonLatitude - Coordinates.adjustment
57 | static let minLongitude: Double = Coordinates.bostonLongitude - Coordinates.adjustment
58 | static let maxLatitude: Double = Coordinates.bostonLatitude + Coordinates.adjustment
59 | static let maxLongitude: Double = Coordinates.bostonLongitude + Coordinates.adjustment
60 | }
61 |
62 | static let minCoord = CLLocationCoordinate2D(latitude: Coordinates.minLatitude,
63 | longitude: Coordinates.minLongitude)
64 | static let maxCoord = CLLocationCoordinate2D(latitude: Coordinates.maxLatitude,
65 | longitude: Coordinates.maxLongitude)
66 |
67 | static func randomCoordinate() -> CLLocationCoordinate2D {
68 | let lat = Double.cappedRandom(min: Coordinates.minLatitude,
69 | max: Coordinates.maxLatitude)
70 | let long = Double.cappedRandom(min: Coordinates.minLongitude,
71 | max: Coordinates.maxLongitude)
72 | return CLLocationCoordinate2D(latitude: lat, longitude: long)
73 | }
74 | }
75 |
76 | private extension Double {
77 | static func cappedRandom(min minValue: Double, max maxValue: Double) -> Double {
78 | let exponent: Double = 10000.0
79 | let diff = UInt32(abs(minValue - maxValue) * exponent)
80 | let randomNumber = Double(arc4random_uniform(diff)) / exponent
81 | return min(minValue, maxValue) + randomNumber
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/BentoMap.xcodeproj/xcshareddata/xcschemes/BentoMap.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
34 |
40 |
41 |
42 |
43 |
44 |
50 |
51 |
52 |
53 |
54 |
55 |
65 |
66 |
72 |
73 |
74 |
75 |
76 |
77 |
83 |
84 |
90 |
91 |
92 |
93 |
95 |
96 |
99 |
100 |
101 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # BentoMap
2 | > Map Clustering for Swift
3 |
4 | [](https://travis-ci.org/Raizlabs/BentoMap)
5 | [](http://cocoapods.org/pods/BentoMap)
6 | [](http://cocoapods.org/pods/BentoMap)
7 | [](http://cocoapods.org/pods/BentoMap)
8 | [](https://github.com/Carthage/Carthage)
9 |
10 | BentoMap is an Swift implementation of [quadtrees][wiki] for map annotation clustering, and storage. It can also allow other 2d coordinate data to conform to a protocol and be added into `BentoBox` containers.
11 |
12 | For more information, check out the [Raizlabs Developer Blog][rl]. The Android equivalent, known as "Marker Clustering," is [documented here][mk].
13 |
14 | [wiki]: https://en.wikipedia.org/wiki/Quadtree
15 | [rl]: https://www.raizlabs.com/dev/2016/08/introducing-bentomap/
16 | [mk]: https://developers.google.com/maps/documentation/android-api/utility/marker-clustering
17 |
18 | 
19 |
20 | ## Features
21 |
22 | - [x] Store annotation data in QuadTrees
23 | - [x] Fetch annotations in a region, with a clustering threshold
24 | - [x] Protocols for storing other data types
25 |
26 | ## Requirements
27 |
28 | - iOS 9.0+
29 | - Xcode 8.0
30 |
31 | ## Installation
32 |
33 | #### CocoaPods
34 | BentoMap is available through [CocoaPods][cp]. To install it, simply add the following line to your Podfile:
35 |
36 | ```ruby
37 | pod 'BentoMap'
38 | ```
39 |
40 | #### Carthage
41 | Create a `Cartfile` that lists the framework and run `carthage update`. Follow the [instructions][carthage] to add `$(SRCROOT)/Carthage/Build/iOS/BentoMap.framework` to an iOS project.
42 |
43 | ```ogdl
44 | github "Raizlabs/BentoMap"
45 | ```
46 |
47 | #### Manually
48 | 1. Download all of the `.swift` files in `BentoMap/` and `BentoMap/Extensions/` and drop them into your project.
49 | 2. Congratulations!
50 |
51 |
52 | [cp]: http://cocoapods.org/
53 | [carthage]: https://github.com/Carthage/Carthage#if-youre-building-for-ios
54 |
55 | ## Usage example
56 |
57 | To see a full implementation of loading data into a map view, check out the [example project][ex].
58 |
59 | ### Inserting Data
60 |
61 | ```swift
62 | import BentoMap
63 |
64 | static var sampleData: QuadTree {
65 | var samples = QuadTree(bentoBox: BentoBox(minPoint: MKMapPointForCoordinate(CLLocationCoordinate2D.minCoord), maxPoint: MKMapPointForCoordinate(CLLocationCoordinate2D.maxCoord)), bucketCapacity: 5)
66 | let randomData = (1...5000).map { count in
67 | return QuadTreeNode(originCoordinate: MKMapPointForCoordinate(CLLocationCoordinate2D.randomCoordinate()), content: count)
68 | }
69 | for node in randomData {
70 | samples.insertNode(node)
71 | }
72 | return samples
73 | }
74 |
75 | ```
76 |
77 | ### Updating a Map View
78 |
79 | ```swift
80 | func updateAnnotations(inMapView mapView: MKMapView,
81 | forMapRect root: MKMapRect) {
82 | guard !mapView.frame.isEmpty && !MKMapRectIsEmpty(root) else {
83 | mapView.removeAnnotations(mapView.annotations)
84 | return
85 | }
86 | let zoomScale = Double(mapView.frame.width) / root.size.width
87 | let clusterResults = mapData.clusteredDataWithinMapRect(root,
88 | zoomScale: zoomScale,
89 | cellSize: Double(MapKitViewController.cellSize))
90 | let newAnnotations = clusterResults.map(BaseAnnotation.makeAnnotation)
91 |
92 | let oldAnnotations = mapView.annotations.flatMap({ $0 as? BaseAnnotation })
93 |
94 | let toRemove = oldAnnotations.filter { annotation in
95 | return !newAnnotations.contains { newAnnotation in
96 | return newAnnotation == annotation
97 | }
98 | }
99 |
100 | mapView.removeAnnotations(toRemove)
101 |
102 | let toAdd = newAnnotations.filter { annotation in
103 | return !oldAnnotations.contains { oldAnnotation in
104 | return oldAnnotation == annotation
105 | }
106 | }
107 |
108 | mapView.addAnnotations(toAdd)
109 | }
110 | ```
111 |
112 | [ex]: https://github.com/Raizlabs/BentoMap/blob/develop/BentoMapExample/App/MapKitViewController.swift
113 |
114 | ## Contributing
115 |
116 | Issues and pull requests are welcome! Please ensure that you have the latest [SwiftLint][sl] installed before committing and that there are no style warnings generated when building.
117 |
118 | Contributors are expected to abide by the [Contributor Covenant Code of Conduct][cc].
119 |
120 | [sl]: https://github.com/realm/SwiftLint
121 | [cc]: https://github.com/Raizlabs/BentoMap/blob/develop/CONTRIBUTING.md
122 |
123 | ## License
124 |
125 | BentoMap is available under the MIT license. See the `LICENSE` file for more info.
126 |
127 | ## Authors
128 |
129 | - Michael Skiba: , [@atelierclkwrk][mstw]
130 | - Rob Visentin:
131 | - Matt Buckley: , [@mattthousand][mbtw]
132 |
133 | [mstw]: https://twitter.com/atelierclkwrk
134 | [mbtw]: https://twitter.com/mattthousand
135 |
--------------------------------------------------------------------------------
/BentoMapExample/App/MapKitViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MapKitViewController.swift
3 | // BentoMapExample
4 | //
5 | // Created by Michael Skiba on 7/6/16.
6 | // Copyright © 2016 Raizlabs. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import MapKit
11 | import BentoMap
12 |
13 | final class MapKitViewController: UIViewController {
14 |
15 | static let cellSize: CGFloat = 64
16 | // Used to make sure the map is nicely padded on the edges, and visible annotations
17 | // aren't hidden under the navigation bar
18 | static let mapInsets = UIEdgeInsets(top: cellSize, left: (cellSize / 2), bottom: (cellSize / 2), right: (cellSize / 2))
19 |
20 | let mapData = QuadTree.sampleData
21 | var mapView: MKMapView! {
22 | return view as? MKMapView
23 | }
24 |
25 | override func loadView() {
26 | let mapView = MKMapView()
27 | mapView.delegate = self
28 | view = mapView
29 | }
30 |
31 | override func viewDidLoad() {
32 | super.viewDidLoad()
33 | navigationItem.title = NSLocalizedString("BentoBox",
34 | comment: "BentoBox navbar title")
35 | }
36 |
37 | override func viewDidAppear(_ animated: Bool) {
38 | super.viewDidAppear(animated)
39 | let zoomRect = mapView.mapRectThatFits(mapData.bentoBox.root, edgePadding: type(of: self).mapInsets)
40 | mapView.setVisibleMapRect(zoomRect,
41 | edgePadding: type(of: self).mapInsets,
42 | animated: false)
43 | }
44 |
45 | }
46 |
47 | extension MapKitViewController: MKMapViewDelegate {
48 |
49 | func mapView(_ mapView: MKMapView,
50 | viewFor annotation: MKAnnotation) -> MKAnnotationView? {
51 | let annotationView: MKAnnotationView
52 | if let single = annotation as? SingleAnnotation {
53 | annotationView = mapView.dequeueAnnotationView(for: single) as SingleAnnotationView
54 | }
55 | else if let cluster = annotation as? ClusterAnnotation {
56 | annotationView = mapView.dequeueAnnotationView(for: cluster) as ClusterAnnotationView
57 | }
58 | else {
59 | fatalError("Unexpected annotation type found")
60 | }
61 | return annotationView
62 | }
63 |
64 | func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
65 | if let cluster = view as? ClusterAnnotationView, let zoomRect = cluster.typedAnnotation?.root {
66 | let adjustedZoom = mapView.mapRectThatFits(zoomRect, edgePadding: type(of: self).mapInsets)
67 | mapView.setVisibleMapRect(adjustedZoom,
68 | edgePadding: type(of: self).mapInsets,
69 | animated: true)
70 | }
71 | else {
72 | view.setSelected(true, animated: true)
73 | }
74 | }
75 |
76 | func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
77 | updateAnnotations(inMapView: mapView,
78 | forMapRect: mapView.visibleMapRect)
79 | }
80 |
81 | }
82 |
83 | private extension MapKitViewController {
84 |
85 | func updateAnnotations(inMapView mapView: MKMapView,
86 | forMapRect root: MKMapRect) {
87 | guard !mapView.frame.isEmpty && !MKMapRectIsEmpty(root) else {
88 | mapView.removeAnnotations(mapView.annotations)
89 | return
90 | }
91 | let zoomScale = Double(mapView.frame.width) / root.size.width
92 | let clusterResults = mapData.clusteredDataWithinMapRect(root,
93 | zoomScale: zoomScale,
94 | cellSize: Double(MapKitViewController.cellSize))
95 | let newAnnotations = clusterResults.map(BaseAnnotation.makeAnnotation)
96 |
97 | let oldAnnotations = mapView.annotations.flatMap({ $0 as? BaseAnnotation })
98 |
99 | let toRemove = oldAnnotations.filter { annotation in
100 | return !newAnnotations.contains { newAnnotation in
101 | return newAnnotation == annotation
102 | }
103 | }
104 |
105 | let snapshots: [UIView] = toRemove.flatMap { annotation in
106 | guard let annotationView = mapView.view(for: annotation),
107 | let snapshot = annotationView.snapshotView(afterScreenUpdates: false),
108 | mapView.frame.intersects(annotationView.frame) == true else {
109 | return nil
110 | }
111 | snapshot.frame = annotationView.frame
112 | mapView.insertSubview(snapshot, aboveSubview: annotationView)
113 | return snapshot
114 | }
115 |
116 | UIView.animate(withDuration: 0.2, animations: {
117 | for snapshot in snapshots {
118 | snapshot.transform = CGAffineTransform(scaleX: 0.01, y: 0.01)
119 | snapshot.layer.opacity = 0
120 | }
121 | },
122 | completion: { _ in
123 | for snapshot in snapshots {
124 | snapshot.removeFromSuperview()
125 | }
126 | })
127 |
128 | mapView.removeAnnotations(toRemove)
129 |
130 | let toAdd = newAnnotations.filter { annotation in
131 | return !oldAnnotations.contains { oldAnnotation in
132 | return oldAnnotation == annotation
133 | }
134 | }
135 |
136 | mapView.addAnnotations(toAdd)
137 | }
138 |
139 | }
140 |
--------------------------------------------------------------------------------
/BentoMapExample/Annotations/AnnotationView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AnnotationView.swift
3 | // BentoMap
4 | //
5 | // Created by Michael Skiba on 1/10/17.
6 | // Copyright © 2017 Raizlabs. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import MapKit
11 |
12 | private extension CGSize {
13 | static let minSize: CGSize = CGSize(width: 32, height: 32)
14 | }
15 |
16 | extension MKAnnotationView {
17 |
18 | static var classReuseIdentifier: String {
19 | return String(describing: self)
20 | }
21 |
22 | }
23 |
24 | extension MKMapView {
25 |
26 | func dequeueAnnotationView(for annotation: AnnotationView.AnnotationType,
27 | withIdentifier: String = AnnotationView.classReuseIdentifier) -> AnnotationView where AnnotationView: TypedAnnotationView {
28 | let view: AnnotationView
29 | if let annotationView = dequeueReusableAnnotationView(withIdentifier: AnnotationView.classReuseIdentifier) as? AnnotationView {
30 | annotationView.annotation = annotation
31 | view = annotationView
32 | }
33 | else {
34 | view = AnnotationView.init(annotation: annotation, reuseIdentifier: AnnotationView.classReuseIdentifier)
35 | }
36 | return view
37 | }
38 |
39 | }
40 |
41 | protocol TypedAnnotationView {
42 |
43 | associatedtype AnnotationType: MKAnnotation
44 |
45 | var annotation: MKAnnotation? { get set }
46 |
47 | func updateAnnotation()
48 | }
49 |
50 | extension TypedAnnotationView {
51 |
52 | var typedAnnotation: AnnotationType? {
53 | get {
54 | return annotation as? AnnotationType
55 | }
56 | set {
57 | annotation = newValue
58 | updateAnnotation()
59 | }
60 | }
61 |
62 | }
63 |
64 | class AnimatedAnnotationView: MKAnnotationView {
65 |
66 | override func didMoveToSuperview() {
67 | super.didMoveToSuperview()
68 | transform = CGAffineTransform(scaleX: 0.01, y: 0.01)
69 | layer.opacity = 0
70 | let animation = {
71 | self.transform = .identity
72 | self.layer.opacity = 1
73 | }
74 | UIView.animate(withDuration: 0.4,
75 | delay: 0,
76 | usingSpringWithDamping: 0.5,
77 | initialSpringVelocity: 1.0,
78 | options: [.curveEaseInOut],
79 | animations: animation,
80 | completion: nil)
81 | }
82 |
83 | }
84 |
85 | class SingleAnnotationView: AnimatedAnnotationView, TypedAnnotationView {
86 |
87 | typealias AnnotationType = SingleAnnotation
88 |
89 | override var annotation: MKAnnotation? {
90 | didSet {
91 | updateAnnotation()
92 | }
93 | }
94 |
95 | override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
96 | super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
97 | image = #imageLiteral(resourceName: "redHexagon")
98 | canShowCallout = true
99 | updateAnnotation()
100 | }
101 |
102 | required init?(coder aDecoder: NSCoder) {
103 | fatalError("init(coder:) has not been implemented")
104 | }
105 |
106 | func updateAnnotation() {
107 | }
108 |
109 | }
110 |
111 | class ClusterAnnotationView: AnimatedAnnotationView, TypedAnnotationView {
112 |
113 | typealias AnnotationType = ClusterAnnotation
114 | let label: UILabel
115 |
116 | override var annotation: MKAnnotation? {
117 | didSet {
118 | updateAnnotation()
119 | }
120 | }
121 |
122 | override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
123 | label = UILabel()
124 | label.font = UIFont.boldSystemFont(ofSize: 12)
125 | label.textColor = .white
126 | label.layer.shouldRasterize = true
127 | label.layer.rasterizationScale = UIScreen.main.scale
128 | label.layer.masksToBounds = true
129 | label.layer.shadowColor = UIColor.black.cgColor
130 | label.layer.shadowOffset = CGSize(width: 0, height: 0)
131 | label.layer.shadowRadius = 0.5
132 | label.layer.shadowOpacity = 1
133 |
134 | super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
135 |
136 | image = #imageLiteral(resourceName: "blueStretch")
137 |
138 | addSubview(label)
139 | label.translatesAutoresizingMaskIntoConstraints = false
140 | NSLayoutConstraint.activate([
141 | NSLayoutConstraint(item: label, attribute: .centerX, relatedBy: .equal, toItem: self, attribute: .centerX, multiplier: 1, constant: 0),
142 | NSLayoutConstraint(item: label, attribute: .centerY, relatedBy: .equal, toItem: self, attribute: .centerY, multiplier: 1, constant: 0),
143 | ])
144 |
145 | updateAnnotation()
146 | }
147 |
148 | required init?(coder aDecoder: NSCoder) {
149 | fatalError("init(coder:) has not been implemented")
150 | }
151 |
152 | func updateAnnotation() {
153 | label.text = typedAnnotation.map { "\($0.annotationNumbers.count)" }
154 |
155 | var size = label.systemLayoutSizeFitting(.minSize,
156 | withHorizontalFittingPriority: UILayoutPriorityFittingSizeLevel,
157 | verticalFittingPriority: UILayoutPriorityFittingSizeLevel)
158 |
159 | size.width += 8
160 | size.height += 8
161 |
162 | if size.width < CGSize.minSize.width {
163 | size.width = CGSize.minSize.width
164 | }
165 | if size.height < CGSize.minSize.height {
166 | size.height = CGSize.minSize.height
167 | }
168 |
169 | frame = CGRect(origin: CGPoint(), size: size)
170 | }
171 |
172 | }
173 |
--------------------------------------------------------------------------------
/BentoMap/QuadTree.swift:
--------------------------------------------------------------------------------
1 | //
2 | // QuadTree.swift
3 | // BentoMap
4 | //
5 | // Created by Michael Skiba on 2/17/16.
6 | // Copyright © 2016 Raizlabs. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | //following example code and text from https://robots.thoughtbot.com/how-to-handle-large-amounts-of-data-on-maps
12 | public struct QuadTree {
13 |
14 | /// The individual nodes that compose the QuadTree
15 | public var ordinalNodes: OrdinalNodes?
16 |
17 | /// The rectangular map specified by the QuadTree
18 | var root: BentoBox
19 |
20 | /// The number of coordinates or points that an individual node may contain
21 | public let bucketCapacity: Int
22 | public var points = [QuadTreeNode]()
23 |
24 | public init(bentoBox: BentoBox, bucketCapacity: Int) {
25 | precondition(bucketCapacity > 0, "Bucket capacity must be greater than 0")
26 | self.root = bentoBox
27 | self.bucketCapacity = bucketCapacity
28 | }
29 |
30 | }
31 |
32 | public extension QuadTree {
33 |
34 | /// Recursively computes the bounding box of the points in the quad tree in O(n) time.
35 | public var bentoBox: BentoBox {
36 | let boundingBox: BentoBox = points.boundingBox()
37 |
38 | if let ordinals = ordinalNodes {
39 | boundingBox.union(ordinals.northWest.bentoBox)
40 | boundingBox.union(ordinals.northEast.bentoBox)
41 | boundingBox.union(ordinals.southWest.bentoBox)
42 | boundingBox.union(ordinals.southEast.bentoBox)
43 | }
44 |
45 | return boundingBox
46 | }
47 |
48 | /**
49 | Computes clusters of data based on the zoomscale
50 | and cell size passed in.
51 |
52 | - parameter root: the root bentoBox of the Quadtree (used
53 | to compute the bounding rectangle for the map.
54 | - parameter zoomScale: used to calculate scale factor relative to the cell size passed in.
55 | - parameter cellSize: the desired size for the "clustering region" for each individual bucket.i
56 |
57 | - returns: an array of quadtree results for each cell that contains nodes.
58 | */
59 | public func clusteredDataWithinMapRect(_ root: Rect, zoomScale: Double, cellSize: Double) -> [QuadTreeResult] {
60 |
61 | let scaleFactor: Double
62 |
63 | // Prevents divide by zero errors from cropping up if handed bad data
64 | if cellSize == 0 || zoomScale == 0 {
65 | scaleFactor = 1
66 | }
67 | else {
68 | scaleFactor = zoomScale / cellSize
69 | }
70 |
71 | let stepSize = CGFloat(1.0 / scaleFactor)
72 |
73 | // normalizes the cell grid to make sure the first column is aligned to the step size to prevent jitter
74 | let minX = (root.minX - root.minX.remainder(dividingBy: stepSize)) - stepSize
75 | // normalizes the cell grid to make sure the last column is added to the buckets
76 | let maxX = (root.maxX - root.maxY.remainder(dividingBy: stepSize)) + stepSize
77 | // normalizes the cell grid to make sure the first row is aligned to the step size to prevent jitter
78 | let minY = (root.minY - root.minY.remainder(dividingBy: stepSize)) - stepSize
79 | // normalizes the cell grid to make sure the last row is added to the buckets
80 | let maxY = (root.maxY - root.maxY.remainder(dividingBy: stepSize)) + stepSize
81 |
82 | var result = [QuadTreeResult]()
83 |
84 | let mapStep = CGSize(width: Double(stepSize), height: Double(stepSize))
85 | for x in stride(from: minX, through: maxX, by: stepSize) {
86 | for y in stride(from: minY, through: maxY, by: stepSize) {
87 | let cellRectangle = Rect(originCoordinate: Coordinate(coordX: x, coordY: y), size: mapStep)
88 | let nodes = nodesInRange(BentoBox(root: cellRectangle))
89 |
90 | switch nodes.count {
91 | case 0:
92 | continue
93 | case 1:
94 | if let first = nodes.first {
95 | result.append(.single(node: first))
96 | }
97 | default:
98 | result.append(.multiple(nodes: nodes))
99 | }
100 | }
101 | }
102 | return result
103 | }
104 |
105 | /**
106 | Inserts a node if the node fits in the
107 | bucket's coordinate space and if the bucket
108 | capacty has not been reached. If the bucket
109 | capacity has been reached, it subdivides the
110 | Quadtree (if required) and inserts the node
111 | into the appropriate subnode.
112 |
113 | - parameter node: the node to insert.
114 |
115 | - returns: a Bool indicating success or failure of the insertion.
116 | */
117 | @discardableResult public mutating func insertNode(_ node: QuadTreeNode) -> Bool {
118 | guard root.containsCoordinate(node.originCoordinate) else {
119 | return false
120 | }
121 |
122 | if points.count < bucketCapacity {
123 | points.append(node)
124 | return true
125 | }
126 |
127 | if ordinalNodes == nil {
128 | subdivide()
129 | }
130 |
131 | return ordinalNodes?.northWest.insertNode(node) ||?
132 | ordinalNodes?.northEast.insertNode(node) ||?
133 | ordinalNodes?.southWest.insertNode(node) ||?
134 | ordinalNodes?.southEast.insertNode(node)
135 | }
136 |
137 | }
138 |
139 | private extension QuadTree {
140 |
141 | /**
142 | Divides the QuadTree into quadrants of equal dimension.
143 | */
144 | mutating func subdivide() {
145 | let trees = root.quadrants.map { quadrant in
146 | QuadTree(bentoBox: quadrant, bucketCapacity: self.bucketCapacity)
147 | }
148 |
149 | ordinalNodes = OrdinalNodes(northWest: trees.northWest,
150 | northEast: trees.northEast,
151 | southWest: trees.southWest,
152 | southEast: trees.southEast)
153 | }
154 |
155 | /**
156 | Computes the collection of nodes contained within a box's
157 | coordinate space.
158 |
159 | - parameter range: the containing box.
160 |
161 | - returns: a collection of nodes.
162 | */
163 | func nodesInRange(_ range: BentoBox) -> [QuadTreeNode] {
164 | var nodes = [QuadTreeNode]()
165 |
166 | guard root.intersectsBentoBox(range) else {
167 | return nodes
168 | }
169 |
170 | nodes += points.filter { point in
171 | range.containsCoordinate(point.originCoordinate)
172 | }
173 |
174 | if let ordinals = ordinalNodes {
175 | nodes += ordinals.northWest.nodesInRange(range)
176 | nodes += ordinals.northEast.nodesInRange(range)
177 | nodes += ordinals.southWest.nodesInRange(range)
178 | nodes += ordinals.southEast.nodesInRange(range)
179 | }
180 |
181 | return nodes
182 | }
183 |
184 | }
185 |
--------------------------------------------------------------------------------
/BentoMapTests/QuadTreeTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // QuadTreeTests.swift
3 | // BentoMapTests
4 | //
5 | // Created by Michael Skiba on 7/6/16.
6 | // Copyright © 2016 Raizlabs. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | import MapKit
11 | @testable import BentoMap
12 |
13 | class QuadTreeTests: XCTestCase {
14 |
15 | override func setUp() {
16 | super.setUp()
17 | }
18 |
19 | override func tearDown() {
20 | super.tearDown()
21 | }
22 |
23 | func testQuadTreeInitialization() {
24 | let box = BentoBox(root: MKMapRectWorld)
25 | let quadTree = QuadTree(bentoBox: box, bucketCapacity: 5)
26 |
27 | XCTAssert(MKMapRectEqualToRect(box.root, quadTree.root.root), "The bounding box should equal the initalized bounding box")
28 | XCTAssert(quadTree.bucketCapacity == 5, "The bucket capacity passed in is the bucket capacity used")
29 | XCTAssertNil(quadTree.ordinalNodes, "The initalized nodes should be empty")
30 | XCTAssertTrue(quadTree.points.isEmpty, "The bucket of points should be empty")
31 | }
32 |
33 | fileprivate func resultRectTester(_ result: QuadTreeResult) {
34 | let contentRect = result.contentRect
35 | switch result {
36 | case let .single(node: node):
37 | XCTAssert(MKMapPointEqualToPoint(contentRect.origin, node.originCoordinate), "Single nodes should have an origin equal to the node's point")
38 | XCTAssert(MKMapSizeEqualToSize(MKMapSize(), contentRect.size), "single nodes should have a zero-size content size")
39 | case let .multiple(nodes: nodes):
40 | for node in nodes {
41 | XCTAssert(MKMapRectContainsPoint(contentRect, node.originCoordinate), "Every node should be contained in the map content rect")
42 | }
43 | }
44 | }
45 |
46 | func testQuadTreeInsertion() {
47 |
48 | let bentoBox = BentoBox(root: MKMapRect(origin: MKMapPoint(), size: MKMapSize(width: 5000, height: 5000)))
49 | var quadTree = QuadTree(bentoBox: bentoBox, bucketCapacity: 5)
50 | var i = 0
51 | for x in stride(from: 0, to: 5000, by: 50) {
52 | for y in stride(from: 0, to: 5000, by: 50) {
53 | let originCoordinate = MKMapPoint(x: Double(x), y: Double(y))
54 | let node = QuadTreeNode(originCoordinate: originCoordinate, content: i)
55 | quadTree.insertNode(node)
56 | i += 1
57 | }
58 | }
59 |
60 | let unclusteredNodes = quadTree.clusteredDataWithinMapRect(bentoBox.root, zoomScale: 1, cellSize: 50)
61 | for point in unclusteredNodes {
62 | XCTAssert(Int(point.originCoordinate.coordX) % 50 == 0, "all map point coords should be divisible by 50")
63 | XCTAssert(Int(point.originCoordinate.coordY) % 50 == 0, "all map point coords should be divisible by 50")
64 | resultRectTester(point)
65 | }
66 | XCTAssertTrue(unclusteredNodes.count == 10000, "This should return 10k clusters")
67 | let clusteredNodes = quadTree.clusteredDataWithinMapRect(bentoBox.root, zoomScale: 1, cellSize: 500)
68 | XCTAssertTrue(clusteredNodes.count == 100, "This should return 100 clusters")
69 | var totalNodeCount = 0
70 | for cluster in clusteredNodes {
71 | XCTAssert(Int(cluster.originCoordinate.coordX - 225) % 500 == 0, "all map point coords should be divisible by 500 after centering/ point was \(cluster.originCoordinate.coordX)")
72 | XCTAssert(Int(cluster.originCoordinate.coordY - 225) % 500 == 0, "all map point coords should be divisible by 500 after centering. point was \(cluster.originCoordinate.coordY)")
73 | resultRectTester(cluster)
74 | switch cluster {
75 | case .single:
76 | totalNodeCount += 1
77 | case let .multiple(nodes: nodes):
78 | XCTAssert(nodes.count == 100, "Each cluster contains 100 nodes")
79 | totalNodeCount += nodes.count
80 | }
81 | }
82 | XCTAssertTrue(totalNodeCount == 10000, "All nodes should add up to 10k nodes")
83 |
84 | XCTAssertFalse(quadTree.insertNode(QuadTreeNode(originCoordinate: MKMapPoint(x: Double(5002), y: Double(5002)), content: 1)))
85 | let unclusteredNodes2 = quadTree.clusteredDataWithinMapRect(bentoBox.root, zoomScale: 1, cellSize: 50)
86 | XCTAssertTrue(unclusteredNodes2.count == 10000, "This should return 10k clusters as an out of bounds cluster shouldn't insert")
87 |
88 | // making sure divide by zero avoidance works
89 | let testZero = quadTree.clusteredDataWithinMapRect(MKMapRectMake(0, 0, 5, 5), zoomScale: 0, cellSize: 0)
90 | XCTAssertTrue(testZero.count == 1, "This should return 100 clusters")
91 | }
92 |
93 | func testClustering() {
94 | let bentoBox = BentoBox(root: MKMapRect(origin: MKMapPoint(), size: MKMapSize(width: 11, height: 11)))
95 | var quadTree = QuadTree(bentoBox: bentoBox, bucketCapacity: 5)
96 | var i = 0
97 | for x in 0...10 {
98 | for y in 0...10 {
99 | let originCoordinate = MKMapPoint(x: Double(x), y: Double(y))
100 | let node = QuadTreeNode(originCoordinate: originCoordinate, content: i)
101 | quadTree.insertNode(node)
102 | i += 1
103 | }
104 | }
105 |
106 | let rect = MKMapRect(origin: MKMapPoint(), size: MKMapSize(width: 9, height: 9))
107 | let unclusteredNodes = quadTree.clusteredDataWithinMapRect(rect, zoomScale: 1, cellSize: 10)
108 | XCTAssertEqual(unclusteredNodes.count, 4, "there should be four clusters in this data set")
109 | guard let cluster = unclusteredNodes.first else {
110 | XCTFail("First node should be non nil")
111 | return
112 | }
113 | let nodeCenter: MKMapPoint
114 | switch cluster {
115 | case .multiple(nodes: let nodes):
116 | XCTAssertEqual(nodes.count, 100, "there should be 100 nodes in the cluster")
117 | let reducedNodeCenter = nodes.reduce(MKMapPoint()) { total, current in
118 | return MKMapPoint(x: total.x + current.coordinate.x, y: total.y + current.coordinate.y)
119 | }
120 | nodeCenter = MKMapPoint(x: reducedNodeCenter.x / Double(nodes.count),
121 | y: reducedNodeCenter.y / Double(nodes.count))
122 | case .single(node: _):
123 | XCTFail("node should be a multiple node")
124 | return
125 | }
126 | XCTAssertEqualWithAccuracy(nodeCenter.x, 4.5, accuracy: 0.0001)
127 | XCTAssertEqualWithAccuracy(nodeCenter.y, 4.5, accuracy: 0.0001)
128 | XCTAssertEqualWithAccuracy(nodeCenter.x, cluster.originCoordinate.x, accuracy: 0.0001)
129 | XCTAssertEqualWithAccuracy(nodeCenter.x, cluster.originCoordinate.y, accuracy: 0.0001)
130 | XCTAssertEqualWithAccuracy(cluster.contentRect.minX, 0, accuracy: 0.001)
131 | XCTAssertEqualWithAccuracy(cluster.contentRect.minY, 0, accuracy: 0.001)
132 | XCTAssertEqualWithAccuracy(cluster.contentRect.maxX, 9, accuracy: 0.001)
133 | XCTAssertEqualWithAccuracy(cluster.contentRect.maxY, 9, accuracy: 0.001)
134 | }
135 |
136 | }
137 |
--------------------------------------------------------------------------------
/BentoMapTests/BentoBoxTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BentoMapTests.swift
3 | // BentoMapTests
4 | //
5 | // Created by Michael Skiba on 7/5/16.
6 | // Copyright © 2016 Raizlabs. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | import MapKit
11 | @testable import BentoMap
12 |
13 | class MapKitTests: XCTestCase {
14 |
15 | override func setUp() {
16 | super.setUp()
17 | }
18 |
19 | override func tearDown() {
20 | super.tearDown()
21 | }
22 |
23 | func testBentoBoxCreation() {
24 |
25 | let root = MKMapRectWorld
26 |
27 | let rootNodeBentoBox = BentoBox(root: root)
28 |
29 | XCTAssert(MKMapRectEqualToRect(root, rootNodeBentoBox.root), "The bounding box's map rect should be equal to the input map rect")
30 |
31 | let maxCoord = CLLocationCoordinate2D(latitude: 30, longitude: 60)
32 | let minCoord = CLLocationCoordinate2D(latitude: 20, longitude: 40)
33 |
34 | let coordBentoBox = BentoBox(minPoint: minCoord, maxPoint: maxCoord)
35 |
36 | let minLat = CGFloat(min(minCoord.coordX, maxCoord.coordX))
37 | XCTAssertEqualWithAccuracy(coordBentoBox.root.minX, minLat, accuracy: 0.001, "The bounding box's min latitude \(coordBentoBox.root.minX) should equal the smallest latitude passed in \(minLat)")
38 |
39 | let maxLat = max(minCoord.coordX, maxCoord.coordX)
40 | XCTAssertEqualWithAccuracy(coordBentoBox.maxCoordinate.coordX, maxLat, accuracy: 0.001, "The bounding box's max latitude \(coordBentoBox.maxCoordinate.coordX) should equal the largest latitude passed in \(maxLat)")
41 |
42 | let minLong = min(minCoord.coordY, maxCoord.coordY)
43 | XCTAssertEqualWithAccuracy(coordBentoBox.minCoordinate.coordY, minLong, accuracy: 0.001, "The bounding box's min longitude \(coordBentoBox.minCoordinate.coordY) should equal the smallest longitude passed in \(minLong)")
44 |
45 | let maxLong = max(minCoord.coordY, maxCoord.coordY)
46 | XCTAssertEqualWithAccuracy(coordBentoBox.maxCoordinate.coordY, maxLong, accuracy: 0.001, "The bounding box's max longitude \(coordBentoBox.minCoordinate.coordY) should equal the largest longitude passed in \(minLong)")
47 | }
48 |
49 | func testBentoBoxCoordinates() {
50 | let coordBentoBox = BentoBox(minPoint: CLLocationCoordinate2D(latitude: 30, longitude: 60),
51 | maxPoint: CLLocationCoordinate2D(latitude: 20, longitude: 40))
52 |
53 | let maxCoordinate = MKMapPoint(x: Double(coordBentoBox.maxCoordinate.coordX), y: Double(coordBentoBox.maxCoordinate.coordY))
54 | let minCoordinate = MKMapPoint(x: Double(coordBentoBox.minCoordinate.coordX), y: Double(coordBentoBox.minCoordinate.coordY))
55 |
56 | // the min or higher is inside the bounding box
57 | XCTAssert(coordBentoBox.containsCoordinate(minCoordinate.offset(latitude: 0.1, longitude: 0.1)))
58 | XCTAssert(coordBentoBox.containsCoordinate(minCoordinate.offset(longitude: 0.1)))
59 | XCTAssert(coordBentoBox.containsCoordinate(minCoordinate.offset(latitude: 0.1)))
60 | XCTAssert(coordBentoBox.containsCoordinate(minCoordinate))
61 | // slightly lower than the map is inside the mounding box
62 | XCTAssert(coordBentoBox.containsCoordinate(maxCoordinate.offset(latitude: -0.1, longitude: -0.1)))
63 | // the max or higher is outside the bounding box
64 | XCTAssertFalse(coordBentoBox.containsCoordinate(maxCoordinate))
65 | XCTAssertFalse(coordBentoBox.containsCoordinate(maxCoordinate.offset(longitude: -0.1)))
66 | XCTAssertFalse(coordBentoBox.containsCoordinate(maxCoordinate.offset(latitude: -0.1)))
67 | XCTAssertFalse(coordBentoBox.containsCoordinate(maxCoordinate.offset(latitude: 0.1, longitude: 0.1)))
68 | XCTAssertFalse(coordBentoBox.containsCoordinate(maxCoordinate.offset(longitude: 0.1)))
69 | XCTAssertFalse(coordBentoBox.containsCoordinate(maxCoordinate.offset(latitude: 0.1)))
70 | // lower than the min is outside the bounding box
71 | XCTAssertFalse(coordBentoBox.containsCoordinate(minCoordinate.offset(latitude: -0.1, longitude: -0.1)))
72 | XCTAssertFalse(coordBentoBox.containsCoordinate(minCoordinate.offset(longitude: -0.1)))
73 | XCTAssertFalse(coordBentoBox.containsCoordinate(minCoordinate.offset(latitude: -0.1)))
74 |
75 | // the middle point is inside the bounding box
76 | let midCoord = MKMapPoint(x: Double(maxCoordinate.coordX + minCoordinate.coordX) / 2.0,
77 | y: Double(maxCoordinate.coordY + minCoordinate.coordY) / 2.0)
78 | XCTAssert(coordBentoBox.containsCoordinate(midCoord))
79 | }
80 |
81 | func testBentoBoxIntersection() {
82 | let rect = MKMapRectMake(0, 0, 500, 500)
83 | let intersectingRect = MKMapRectMake(250, 250, 500, 500)
84 | let nonIntersectingRect = MKMapRectMake(500, 0, 500, 500)
85 |
86 | let bentoBox = BentoBox(root: rect)
87 | let intersectingBox = BentoBox(root: intersectingRect)
88 | let nonIntersectingBox = BentoBox(root: nonIntersectingRect)
89 |
90 | XCTAssert(bentoBox.intersectsBentoBox(intersectingBox))
91 | XCTAssertFalse(bentoBox.intersectsBentoBox(nonIntersectingBox))
92 | }
93 |
94 | func testQuadrants() {
95 | let baseRect = MKMapRectMake(0, 0, 500, 500)
96 | let nwRect = MKMapRectMake(0, 0, 250, 250)
97 | let neRect = MKMapRectMake(250, 0, 250, 250)
98 | let swRect = MKMapRectMake(0, 250, 250, 250)
99 | let seRect = MKMapRectMake(250, 250, 250, 250)
100 |
101 | let quadrants = BentoBox(root: baseRect).quadrants
102 |
103 | XCTAssert(MKMapRectEqualToRect(quadrants.northWest.root, nwRect))
104 | XCTAssert(MKMapRectEqualToRect(quadrants.northEast.root, neRect))
105 | XCTAssert(MKMapRectEqualToRect(quadrants.southWest.root, swRect))
106 | XCTAssert(MKMapRectEqualToRect(quadrants.southEast.root, seRect))
107 | }
108 |
109 | func testCollectionTypeExtensions() {
110 | let points = [
111 | MKMapPoint(x: 25.0, y: 10956.7),
112 | MKMapPoint(x: 1894.5, y: 22897.5550),
113 | MKMapPoint(x: 25278.445, y: 156.339),
114 | MKMapPoint(x: 17603.8472, y: 2456.7),
115 | ]
116 |
117 | let mapPointBentoBox: BentoBox = points.bentoBox()
118 |
119 | let minCoordinate = mapPointBentoBox.minCoordinate
120 | let maxCoordinate = mapPointBentoBox.maxCoordinate
121 |
122 | XCTAssert(MKMapPointEqualToPoint(minCoordinate, MKMapPoint(x: 25.0, y: 156.339)))
123 | XCTAssert(MKMapPointEqualToPoint(maxCoordinate, MKMapPoint(x: 25278.445, y: 22897.5550)))
124 |
125 | let coords = [
126 | CLLocationCoordinate2D(latitude: 34.6790, longitude: 28.2847),
127 | CLLocationCoordinate2D(latitude: -34.6790, longitude: 88.1349),
128 | CLLocationCoordinate2D(latitude: 61.9471, longitude: -14.1887),
129 | CLLocationCoordinate2D(latitude: 1.2898, longitude: 42.4277),
130 | ]
131 |
132 | let coordinateBentoBox: BentoBox = coords.bentoBox()
133 |
134 | XCTAssertEqualWithAccuracy(coordinateBentoBox.minCoordinate.coordX, -34.6790, accuracy: 1e-4)
135 | XCTAssertEqualWithAccuracy(coordinateBentoBox.minCoordinate.coordY, -14.1887, accuracy: 1e-4)
136 | XCTAssertEqualWithAccuracy(coordinateBentoBox.maxCoordinate.coordX, 61.9471, accuracy: 1e-4)
137 | XCTAssertEqualWithAccuracy(coordinateBentoBox.maxCoordinate.coordY, 88.1349, accuracy: 1e-4)
138 | }
139 | }
140 |
141 | class CoreGraphicsTests: XCTestCase {
142 |
143 | override func setUp() {
144 | super.setUp()
145 | }
146 |
147 | override func tearDown() {
148 | super.tearDown()
149 | }
150 |
151 | func testBentoBoxCreationForCGRect() {
152 |
153 | let origin = CGPoint.zero
154 | let gridSize = CGSize(width: 1000.0, height: 1000.0)
155 | let root = CGRect(origin: origin, size: gridSize)
156 |
157 | let rootNodeBentoBox = BentoBox(root: root)
158 |
159 | XCTAssertEqual(root, rootNodeBentoBox.root, "The bounding box's map rect should be equal to the input map rect")
160 |
161 | let maxCoord = CGPoint(x: 30.0, y: 60.0)
162 | let minCoord = CGPoint(x: 20.0, y: 40.0)
163 |
164 | let coordBentoBox = BentoBox(minPoint: minCoord, maxPoint: maxCoord)
165 |
166 | let minLat = CGFloat(min(minCoord.coordX, maxCoord.coordX))
167 | XCTAssertEqualWithAccuracy(coordBentoBox.root.minX, minLat, accuracy: 0.001, "The bounding box's min x \(coordBentoBox.root.minX) should equal the smallest x passed in \(minLat)")
168 |
169 | let maxLat = max(minCoord.coordX, maxCoord.coordX)
170 | XCTAssertEqualWithAccuracy(coordBentoBox.maxCoordinate.coordX, maxLat, accuracy: 0.001, "The bounding box's max x \(coordBentoBox.maxCoordinate.coordX) should equal the largest x passed in \(maxLat)")
171 |
172 | let minLong = min(minCoord.coordY, maxCoord.coordY)
173 | XCTAssertEqualWithAccuracy(coordBentoBox.minCoordinate.coordY, minLong, accuracy: 0.001, "The bounding box's min y \(coordBentoBox.minCoordinate.coordY) should equal the smallest y passed in \(minLong)")
174 |
175 | let maxLong = max(minCoord.coordY, maxCoord.coordY)
176 | XCTAssertEqualWithAccuracy(coordBentoBox.maxCoordinate.coordY, maxLong, accuracy: 0.001, "The bounding box's max y \(coordBentoBox.minCoordinate.coordY) should equal the largest y passed in \(minLong)")
177 | }
178 |
179 | func testBentoBoxCoordinates() {
180 | let coordBentoBox = BentoBox(minPoint: CGPoint(x: 30, y: 60),
181 | maxPoint: CGPoint(x: 20, y: 40))
182 |
183 | let maxCoordinate = CGPoint(x: coordBentoBox.maxCoordinate.coordX, y: coordBentoBox.maxCoordinate.coordY)
184 | let minCoordinate = CGPoint(x: coordBentoBox.minCoordinate.coordX, y: coordBentoBox.minCoordinate.coordY)
185 |
186 | // the min or higher is inside the bounding box
187 | XCTAssert(coordBentoBox.containsCoordinate(minCoordinate.offset(x: 0.1, y: 0.1)))
188 | XCTAssert(coordBentoBox.containsCoordinate(minCoordinate.offset(y: 0.1)))
189 | XCTAssert(coordBentoBox.containsCoordinate(minCoordinate.offset(x: 0.1)))
190 | XCTAssert(coordBentoBox.containsCoordinate(minCoordinate))
191 | // slightly lower than the map is inside the mounding box
192 | XCTAssert(coordBentoBox.containsCoordinate(maxCoordinate.offset(x: -0.1, y: -0.1)))
193 | // the max or higher is outside the bounding box
194 | XCTAssertFalse(coordBentoBox.containsCoordinate(maxCoordinate))
195 | XCTAssertFalse(coordBentoBox.containsCoordinate(maxCoordinate.offset(y: -0.1)))
196 | XCTAssertFalse(coordBentoBox.containsCoordinate(maxCoordinate.offset(x: -0.1)))
197 | XCTAssertFalse(coordBentoBox.containsCoordinate(maxCoordinate.offset(x: 0.1, y: 0.1)))
198 | XCTAssertFalse(coordBentoBox.containsCoordinate(maxCoordinate.offset(y: 0.1)))
199 | XCTAssertFalse(coordBentoBox.containsCoordinate(maxCoordinate.offset(x: 0.1)))
200 | // lower than the min is outside the bounding box
201 | XCTAssertFalse(coordBentoBox.containsCoordinate(minCoordinate.offset(x: -0.1, y: -0.1)))
202 | XCTAssertFalse(coordBentoBox.containsCoordinate(minCoordinate.offset(y: -0.1)))
203 | XCTAssertFalse(coordBentoBox.containsCoordinate(minCoordinate.offset(x: -0.1)))
204 |
205 | // the middle point is inside the bounding box
206 | let midCoord = CGPoint(x: (maxCoordinate.coordX + minCoordinate.coordX) / 2.0,
207 | y: (maxCoordinate.coordY + minCoordinate.coordY) / 2.0)
208 | XCTAssert(coordBentoBox.containsCoordinate(midCoord))
209 | }
210 |
211 | func testBentoBoxIntersection() {
212 | let rect = CGRect(x: 0, y: 0, width: 500, height: 500)
213 | let intersectingRect = CGRect(x: 250, y: 250, width: 500, height: 500)
214 | let nonIntersectingRect = CGRect(x: 500, y: 0, width: 500, height: 500)
215 |
216 | let bentoBox = BentoBox(root: rect)
217 | let intersectingBox = BentoBox(root: intersectingRect)
218 | let nonIntersectingBox = BentoBox(root: nonIntersectingRect)
219 |
220 | XCTAssert(bentoBox.intersectsBentoBox(intersectingBox))
221 | XCTAssertFalse(bentoBox.intersectsBentoBox(nonIntersectingBox))
222 | }
223 |
224 | func testQuadrants() {
225 | let baseRect = CGRect(x: 0, y: 0, width: 500, height: 500)
226 | let nwRect = CGRect(x: 0, y: 0, width: 250, height: 250)
227 | let neRect = CGRect(x: 250, y: 0, width: 250, height: 250)
228 | let swRect = CGRect(x: 0, y: 250, width: 250, height: 250)
229 | let seRect = CGRect(x: 250, y: 250, width: 250, height: 250)
230 |
231 | let quadrants = BentoBox(root: baseRect).quadrants
232 |
233 | XCTAssert(quadrants.northWest.root.equalTo(nwRect))
234 | XCTAssert(quadrants.northEast.root.equalTo(neRect))
235 | XCTAssert(quadrants.southWest.root.equalTo(swRect))
236 | XCTAssert(quadrants.southEast.root.equalTo(seRect))
237 | }
238 |
239 | func testCollectionTypeExtensions() {
240 | let points = [
241 | CGPoint(x: 25.0, y: 10956.7),
242 | CGPoint(x: 1894.5, y: 22897.5550),
243 | CGPoint(x: 25278.445, y: 156.339),
244 | CGPoint(x: 17603.8472, y: 2456.7),
245 | ]
246 |
247 | let mapPointBentoBox: BentoBox = points.bentoBox()
248 |
249 | let minCoordinate = mapPointBentoBox.minCoordinate
250 | let maxCoordinate = mapPointBentoBox.maxCoordinate
251 |
252 | XCTAssert(minCoordinate.equalTo(CGPoint(x: 25.0, y: 156.339)))
253 | XCTAssert(maxCoordinate.equalTo(CGPoint(x: 25278.445, y: 22897.5550)))
254 |
255 | let coords = [
256 | CGPoint(x: 34.6790, y: 28.2847),
257 | CGPoint(x: -34.6790, y: 88.1349),
258 | CGPoint(x: 61.9471, y: -14.1887),
259 | CGPoint(x: 1.2898, y: 42.4277),
260 | ]
261 |
262 | let coordinateBentoBox: BentoBox = coords.bentoBox()
263 |
264 | XCTAssertEqualWithAccuracy(coordinateBentoBox.minCoordinate.coordX, -34.6790, accuracy: 1e-4)
265 | XCTAssertEqualWithAccuracy(coordinateBentoBox.minCoordinate.coordY, -14.1887, accuracy: 1e-4)
266 | XCTAssertEqualWithAccuracy(coordinateBentoBox.maxCoordinate.coordX, 61.9471, accuracy: 1e-4)
267 | XCTAssertEqualWithAccuracy(coordinateBentoBox.maxCoordinate.coordY, 88.1349, accuracy: 1e-4)
268 | }
269 | }
270 |
271 | private extension MKMapPoint {
272 | func offset(latitude latitudeOffset: Double = 0, longitude longitudeOffset: Double = 0) -> MKMapPoint {
273 | return MKMapPoint(x: x + latitudeOffset, y: y + longitudeOffset)
274 | }
275 | }
276 |
277 | private extension CGPoint {
278 | func offset(x xOffset: CGFloat = 0.0, y yOffset: CGFloat = 0.0) -> CGPoint {
279 | return CGPoint(x: x + xOffset, y: y + yOffset)
280 | }
281 | }
282 |
--------------------------------------------------------------------------------
/BentoMap.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 7228026E1D2E9D9100C637A6 /* Annotations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7228026D1D2E9D9100C637A6 /* Annotations.swift */; };
11 | 723753081D2D9D6D002A1BC2 /* BentoMap.h in Headers */ = {isa = PBXBuildFile; fileRef = 723753071D2D9D6D002A1BC2 /* BentoMap.h */; settings = {ATTRIBUTES = (Public, ); }; };
12 | 7237530F1D2D9D6D002A1BC2 /* BentoMap.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 723753041D2D9D6D002A1BC2 /* BentoMap.framework */; };
13 | 723753251D2D9DA2002A1BC2 /* BentoBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7237531E1D2D9DA2002A1BC2 /* BentoBox.swift */; };
14 | 723753261D2D9DA2002A1BC2 /* Box.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7237531F1D2D9DA2002A1BC2 /* Box.swift */; };
15 | 723753271D2D9DA2002A1BC2 /* OrdinalNodes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 723753201D2D9DA2002A1BC2 /* OrdinalNodes.swift */; };
16 | 723753281D2D9DA2002A1BC2 /* QuadrantWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 723753211D2D9DA2002A1BC2 /* QuadrantWrapper.swift */; };
17 | 723753291D2D9DA2002A1BC2 /* QuadTree.swift in Sources */ = {isa = PBXBuildFile; fileRef = 723753221D2D9DA2002A1BC2 /* QuadTree.swift */; };
18 | 7237532A1D2D9DA2002A1BC2 /* QuadTreeNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 723753231D2D9DA2002A1BC2 /* QuadTreeNode.swift */; };
19 | 7237532B1D2D9DA2002A1BC2 /* QuadTreeResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 723753241D2D9DA2002A1BC2 /* QuadTreeResult.swift */; };
20 | 7237532E1D2D9DB5002A1BC2 /* BentoBoxTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7237532C1D2D9DB5002A1BC2 /* BentoBoxTests.swift */; };
21 | 7237532F1D2D9DB5002A1BC2 /* QuadTreeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7237532D1D2D9DB5002A1BC2 /* QuadTreeTests.swift */; };
22 | 723753391D2D9E75002A1BC2 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 723753381D2D9E75002A1BC2 /* AppDelegate.swift */; };
23 | 7237533B1D2D9E75002A1BC2 /* MapKitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7237533A1D2D9E75002A1BC2 /* MapKitViewController.swift */; };
24 | 723753401D2D9E75002A1BC2 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7237533F1D2D9E75002A1BC2 /* Assets.xcassets */; };
25 | 723753431D2D9E75002A1BC2 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 723753411D2D9E75002A1BC2 /* LaunchScreen.storyboard */; };
26 | 723753481D2D9E80002A1BC2 /* BentoMap.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 723753041D2D9D6D002A1BC2 /* BentoMap.framework */; };
27 | 723753491D2D9E80002A1BC2 /* BentoMap.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 723753041D2D9D6D002A1BC2 /* BentoMap.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
28 | 7237534E1D2DA0CF002A1BC2 /* QuadTree+SampleData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7237534D1D2DA0CF002A1BC2 /* QuadTree+SampleData.swift */; };
29 | 72383AB51D2ECAD00071EDE2 /* OptionalOr.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72383AB41D2ECAD00071EDE2 /* OptionalOr.swift */; };
30 | 72383AB71D2ECAF70071EDE2 /* OptionalOrTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72383AB61D2ECAF70071EDE2 /* OptionalOrTests.swift */; };
31 | 72D679441E252AD40032902B /* AnnotationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72D679431E252AD40032902B /* AnnotationView.swift */; };
32 | B50426AF1D71D710008BEF76 /* CGRect+BentoRect.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50426AE1D71D710008BEF76 /* CGRect+BentoRect.swift */; };
33 | B50426B41D71F35C008BEF76 /* MKMapPoint+BentoCoordinate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50426B31D71F35C008BEF76 /* MKMapPoint+BentoCoordinate.swift */; };
34 | B50426B61D71F775008BEF76 /* CGPoint+BentoCoordinate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50426B51D71F775008BEF76 /* CGPoint+BentoCoordinate.swift */; };
35 | B50426B81D71FA21008BEF76 /* MKMapRect+BentoRect.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50426B71D71FA21008BEF76 /* MKMapRect+BentoRect.swift */; };
36 | B50426BA1D7203C9008BEF76 /* CLLocationCoordinate2D+BentoCoordinate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50426B91D7203C9008BEF76 /* CLLocationCoordinate2D+BentoCoordinate.swift */; };
37 | B539B33E1D7E35D60089AFE6 /* CoreGraphicsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B539B33D1D7E35D60089AFE6 /* CoreGraphicsViewController.swift */; };
38 | B539B3401D7E35ED0089AFE6 /* MainMenuTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B539B33F1D7E35ED0089AFE6 /* MainMenuTableViewController.swift */; };
39 | D4C3839B1D4F9DD500556EBE /* CollectionTypeExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4C3839A1D4F9DD500556EBE /* CollectionTypeExtensions.swift */; };
40 | /* End PBXBuildFile section */
41 |
42 | /* Begin PBXContainerItemProxy section */
43 | 723753101D2D9D6D002A1BC2 /* PBXContainerItemProxy */ = {
44 | isa = PBXContainerItemProxy;
45 | containerPortal = 723752FB1D2D9D6D002A1BC2 /* Project object */;
46 | proxyType = 1;
47 | remoteGlobalIDString = 723753031D2D9D6D002A1BC2;
48 | remoteInfo = BentoMap;
49 | };
50 | 7237534A1D2D9E80002A1BC2 /* PBXContainerItemProxy */ = {
51 | isa = PBXContainerItemProxy;
52 | containerPortal = 723752FB1D2D9D6D002A1BC2 /* Project object */;
53 | proxyType = 1;
54 | remoteGlobalIDString = 723753031D2D9D6D002A1BC2;
55 | remoteInfo = BentoMap;
56 | };
57 | /* End PBXContainerItemProxy section */
58 |
59 | /* Begin PBXCopyFilesBuildPhase section */
60 | 7237534C1D2D9E80002A1BC2 /* Embed Frameworks */ = {
61 | isa = PBXCopyFilesBuildPhase;
62 | buildActionMask = 2147483647;
63 | dstPath = "";
64 | dstSubfolderSpec = 10;
65 | files = (
66 | 723753491D2D9E80002A1BC2 /* BentoMap.framework in Embed Frameworks */,
67 | );
68 | name = "Embed Frameworks";
69 | runOnlyForDeploymentPostprocessing = 0;
70 | };
71 | /* End PBXCopyFilesBuildPhase section */
72 |
73 | /* Begin PBXFileReference section */
74 | 7228026D1D2E9D9100C637A6 /* Annotations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Annotations.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
75 | 723753041D2D9D6D002A1BC2 /* BentoMap.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = BentoMap.framework; sourceTree = BUILT_PRODUCTS_DIR; };
76 | 723753071D2D9D6D002A1BC2 /* BentoMap.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = BentoMap.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; };
77 | 723753091D2D9D6D002A1BC2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
78 | 7237530E1D2D9D6D002A1BC2 /* BentoMapTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BentoMapTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
79 | 723753151D2D9D6D002A1BC2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
80 | 7237531E1D2D9DA2002A1BC2 /* BentoBox.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = BentoBox.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
81 | 7237531F1D2D9DA2002A1BC2 /* Box.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Box.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
82 | 723753201D2D9DA2002A1BC2 /* OrdinalNodes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = OrdinalNodes.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
83 | 723753211D2D9DA2002A1BC2 /* QuadrantWrapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = QuadrantWrapper.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
84 | 723753221D2D9DA2002A1BC2 /* QuadTree.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = QuadTree.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
85 | 723753231D2D9DA2002A1BC2 /* QuadTreeNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = QuadTreeNode.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
86 | 723753241D2D9DA2002A1BC2 /* QuadTreeResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = QuadTreeResult.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
87 | 7237532C1D2D9DB5002A1BC2 /* BentoBoxTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = BentoBoxTests.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
88 | 7237532D1D2D9DB5002A1BC2 /* QuadTreeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuadTreeTests.swift; sourceTree = ""; };
89 | 723753361D2D9E75002A1BC2 /* BentoMapExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BentoMapExample.app; sourceTree = BUILT_PRODUCTS_DIR; };
90 | 723753381D2D9E75002A1BC2 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
91 | 7237533A1D2D9E75002A1BC2 /* MapKitViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapKitViewController.swift; sourceTree = ""; };
92 | 7237533F1D2D9E75002A1BC2 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
93 | 723753421D2D9E75002A1BC2 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
94 | 723753441D2D9E75002A1BC2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
95 | 7237534D1D2DA0CF002A1BC2 /* QuadTree+SampleData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = "QuadTree+SampleData.swift"; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
96 | 72383AB41D2ECAD00071EDE2 /* OptionalOr.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = OptionalOr.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
97 | 72383AB61D2ECAF70071EDE2 /* OptionalOrTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = OptionalOrTests.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
98 | 72D679431E252AD40032902B /* AnnotationView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnnotationView.swift; sourceTree = ""; };
99 | B50426AE1D71D710008BEF76 /* CGRect+BentoRect.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = "CGRect+BentoRect.swift"; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
100 | B50426B31D71F35C008BEF76 /* MKMapPoint+BentoCoordinate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = "MKMapPoint+BentoCoordinate.swift"; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
101 | B50426B51D71F775008BEF76 /* CGPoint+BentoCoordinate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = "CGPoint+BentoCoordinate.swift"; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
102 | B50426B71D71FA21008BEF76 /* MKMapRect+BentoRect.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = "MKMapRect+BentoRect.swift"; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
103 | B50426B91D7203C9008BEF76 /* CLLocationCoordinate2D+BentoCoordinate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = "CLLocationCoordinate2D+BentoCoordinate.swift"; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
104 | B539B33D1D7E35D60089AFE6 /* CoreGraphicsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = CoreGraphicsViewController.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
105 | B539B33F1D7E35ED0089AFE6 /* MainMenuTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = MainMenuTableViewController.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
106 | D4C3839A1D4F9DD500556EBE /* CollectionTypeExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = CollectionTypeExtensions.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
107 | /* End PBXFileReference section */
108 |
109 | /* Begin PBXFrameworksBuildPhase section */
110 | 723753001D2D9D6D002A1BC2 /* Frameworks */ = {
111 | isa = PBXFrameworksBuildPhase;
112 | buildActionMask = 2147483647;
113 | files = (
114 | );
115 | runOnlyForDeploymentPostprocessing = 0;
116 | };
117 | 7237530B1D2D9D6D002A1BC2 /* Frameworks */ = {
118 | isa = PBXFrameworksBuildPhase;
119 | buildActionMask = 2147483647;
120 | files = (
121 | 7237530F1D2D9D6D002A1BC2 /* BentoMap.framework in Frameworks */,
122 | );
123 | runOnlyForDeploymentPostprocessing = 0;
124 | };
125 | 723753331D2D9E75002A1BC2 /* Frameworks */ = {
126 | isa = PBXFrameworksBuildPhase;
127 | buildActionMask = 2147483647;
128 | files = (
129 | 723753481D2D9E80002A1BC2 /* BentoMap.framework in Frameworks */,
130 | );
131 | runOnlyForDeploymentPostprocessing = 0;
132 | };
133 | /* End PBXFrameworksBuildPhase section */
134 |
135 | /* Begin PBXGroup section */
136 | 723752FA1D2D9D6D002A1BC2 = {
137 | isa = PBXGroup;
138 | children = (
139 | 723753061D2D9D6D002A1BC2 /* BentoMap */,
140 | 723753371D2D9E75002A1BC2 /* BentoMapExample */,
141 | 723753121D2D9D6D002A1BC2 /* BentoMapTests */,
142 | 723753051D2D9D6D002A1BC2 /* Products */,
143 | );
144 | sourceTree = "";
145 | };
146 | 723753051D2D9D6D002A1BC2 /* Products */ = {
147 | isa = PBXGroup;
148 | children = (
149 | 723753041D2D9D6D002A1BC2 /* BentoMap.framework */,
150 | 7237530E1D2D9D6D002A1BC2 /* BentoMapTests.xctest */,
151 | 723753361D2D9E75002A1BC2 /* BentoMapExample.app */,
152 | );
153 | name = Products;
154 | sourceTree = "";
155 | };
156 | 723753061D2D9D6D002A1BC2 /* BentoMap */ = {
157 | isa = PBXGroup;
158 | children = (
159 | B50426B21D71F2C8008BEF76 /* Extensions */,
160 | 723E206E1D2EBB9C00CF44FA /* Resources */,
161 | 7237531E1D2D9DA2002A1BC2 /* BentoBox.swift */,
162 | 7237531F1D2D9DA2002A1BC2 /* Box.swift */,
163 | 72383AB41D2ECAD00071EDE2 /* OptionalOr.swift */,
164 | 723753201D2D9DA2002A1BC2 /* OrdinalNodes.swift */,
165 | 723753221D2D9DA2002A1BC2 /* QuadTree.swift */,
166 | 723753231D2D9DA2002A1BC2 /* QuadTreeNode.swift */,
167 | 723753241D2D9DA2002A1BC2 /* QuadTreeResult.swift */,
168 | 723753211D2D9DA2002A1BC2 /* QuadrantWrapper.swift */,
169 | );
170 | path = BentoMap;
171 | sourceTree = "";
172 | };
173 | 723753121D2D9D6D002A1BC2 /* BentoMapTests */ = {
174 | isa = PBXGroup;
175 | children = (
176 | 723E20731D2EC00400CF44FA /* Resources */,
177 | 7237532C1D2D9DB5002A1BC2 /* BentoBoxTests.swift */,
178 | 72383AB61D2ECAF70071EDE2 /* OptionalOrTests.swift */,
179 | 7237532D1D2D9DB5002A1BC2 /* QuadTreeTests.swift */,
180 | );
181 | path = BentoMapTests;
182 | sourceTree = "";
183 | };
184 | 723753371D2D9E75002A1BC2 /* BentoMapExample */ = {
185 | isa = PBXGroup;
186 | children = (
187 | 723E20701D2EBBD000CF44FA /* Annotations */,
188 | 723E20721D2EBFF500CF44FA /* App */,
189 | 723E206F1D2EBBC000CF44FA /* App Delegate */,
190 | 723E20711D2EBC9200CF44FA /* Assets */,
191 | );
192 | path = BentoMapExample;
193 | sourceTree = "";
194 | };
195 | 723E206E1D2EBB9C00CF44FA /* Resources */ = {
196 | isa = PBXGroup;
197 | children = (
198 | 723753071D2D9D6D002A1BC2 /* BentoMap.h */,
199 | 723753091D2D9D6D002A1BC2 /* Info.plist */,
200 | );
201 | path = Resources;
202 | sourceTree = "";
203 | };
204 | 723E206F1D2EBBC000CF44FA /* App Delegate */ = {
205 | isa = PBXGroup;
206 | children = (
207 | 723753381D2D9E75002A1BC2 /* AppDelegate.swift */,
208 | );
209 | path = "App Delegate";
210 | sourceTree = "";
211 | };
212 | 723E20701D2EBBD000CF44FA /* Annotations */ = {
213 | isa = PBXGroup;
214 | children = (
215 | 7228026D1D2E9D9100C637A6 /* Annotations.swift */,
216 | 72D679431E252AD40032902B /* AnnotationView.swift */,
217 | );
218 | path = Annotations;
219 | sourceTree = "";
220 | };
221 | 723E20711D2EBC9200CF44FA /* Assets */ = {
222 | isa = PBXGroup;
223 | children = (
224 | 7237533F1D2D9E75002A1BC2 /* Assets.xcassets */,
225 | 723753441D2D9E75002A1BC2 /* Info.plist */,
226 | 723753411D2D9E75002A1BC2 /* LaunchScreen.storyboard */,
227 | );
228 | path = Assets;
229 | sourceTree = "";
230 | };
231 | 723E20721D2EBFF500CF44FA /* App */ = {
232 | isa = PBXGroup;
233 | children = (
234 | B539B33D1D7E35D60089AFE6 /* CoreGraphicsViewController.swift */,
235 | B539B33F1D7E35ED0089AFE6 /* MainMenuTableViewController.swift */,
236 | 7237533A1D2D9E75002A1BC2 /* MapKitViewController.swift */,
237 | 7237534D1D2DA0CF002A1BC2 /* QuadTree+SampleData.swift */,
238 | );
239 | path = App;
240 | sourceTree = "";
241 | };
242 | 723E20731D2EC00400CF44FA /* Resources */ = {
243 | isa = PBXGroup;
244 | children = (
245 | 723753151D2D9D6D002A1BC2 /* Info.plist */,
246 | );
247 | path = Resources;
248 | sourceTree = "";
249 | };
250 | B50426B21D71F2C8008BEF76 /* Extensions */ = {
251 | isa = PBXGroup;
252 | children = (
253 | B50426B51D71F775008BEF76 /* CGPoint+BentoCoordinate.swift */,
254 | B50426AE1D71D710008BEF76 /* CGRect+BentoRect.swift */,
255 | B50426B91D7203C9008BEF76 /* CLLocationCoordinate2D+BentoCoordinate.swift */,
256 | D4C3839A1D4F9DD500556EBE /* CollectionTypeExtensions.swift */,
257 | B50426B31D71F35C008BEF76 /* MKMapPoint+BentoCoordinate.swift */,
258 | B50426B71D71FA21008BEF76 /* MKMapRect+BentoRect.swift */,
259 | );
260 | path = Extensions;
261 | sourceTree = "";
262 | };
263 | /* End PBXGroup section */
264 |
265 | /* Begin PBXHeadersBuildPhase section */
266 | 723753011D2D9D6D002A1BC2 /* Headers */ = {
267 | isa = PBXHeadersBuildPhase;
268 | buildActionMask = 2147483647;
269 | files = (
270 | 723753081D2D9D6D002A1BC2 /* BentoMap.h in Headers */,
271 | );
272 | runOnlyForDeploymentPostprocessing = 0;
273 | };
274 | /* End PBXHeadersBuildPhase section */
275 |
276 | /* Begin PBXNativeTarget section */
277 | 723753031D2D9D6D002A1BC2 /* BentoMap */ = {
278 | isa = PBXNativeTarget;
279 | buildConfigurationList = 723753181D2D9D6D002A1BC2 /* Build configuration list for PBXNativeTarget "BentoMap" */;
280 | buildPhases = (
281 | 72B69E881DE5EB7D00272BC1 /* SwiftLint */,
282 | 723752FF1D2D9D6D002A1BC2 /* Sources */,
283 | 723753001D2D9D6D002A1BC2 /* Frameworks */,
284 | 723753011D2D9D6D002A1BC2 /* Headers */,
285 | 723753021D2D9D6D002A1BC2 /* Resources */,
286 | );
287 | buildRules = (
288 | );
289 | dependencies = (
290 | );
291 | name = BentoMap;
292 | productName = BentoMap;
293 | productReference = 723753041D2D9D6D002A1BC2 /* BentoMap.framework */;
294 | productType = "com.apple.product-type.framework";
295 | };
296 | 7237530D1D2D9D6D002A1BC2 /* BentoMapTests */ = {
297 | isa = PBXNativeTarget;
298 | buildConfigurationList = 7237531B1D2D9D6D002A1BC2 /* Build configuration list for PBXNativeTarget "BentoMapTests" */;
299 | buildPhases = (
300 | 7237530A1D2D9D6D002A1BC2 /* Sources */,
301 | 7237530B1D2D9D6D002A1BC2 /* Frameworks */,
302 | 7237530C1D2D9D6D002A1BC2 /* Resources */,
303 | 723753311D2D9E26002A1BC2 /* SwiftLint */,
304 | );
305 | buildRules = (
306 | );
307 | dependencies = (
308 | 723753111D2D9D6D002A1BC2 /* PBXTargetDependency */,
309 | );
310 | name = BentoMapTests;
311 | productName = BentoMapTests;
312 | productReference = 7237530E1D2D9D6D002A1BC2 /* BentoMapTests.xctest */;
313 | productType = "com.apple.product-type.bundle.unit-test";
314 | };
315 | 723753351D2D9E75002A1BC2 /* BentoMapExample */ = {
316 | isa = PBXNativeTarget;
317 | buildConfigurationList = 723753451D2D9E75002A1BC2 /* Build configuration list for PBXNativeTarget "BentoMapExample" */;
318 | buildPhases = (
319 | 723753321D2D9E75002A1BC2 /* Sources */,
320 | 723753331D2D9E75002A1BC2 /* Frameworks */,
321 | 723753341D2D9E75002A1BC2 /* Resources */,
322 | 7237534C1D2D9E80002A1BC2 /* Embed Frameworks */,
323 | );
324 | buildRules = (
325 | );
326 | dependencies = (
327 | 7237534B1D2D9E80002A1BC2 /* PBXTargetDependency */,
328 | );
329 | name = BentoMapExample;
330 | productName = BentoMapExample;
331 | productReference = 723753361D2D9E75002A1BC2 /* BentoMapExample.app */;
332 | productType = "com.apple.product-type.application";
333 | };
334 | /* End PBXNativeTarget section */
335 |
336 | /* Begin PBXProject section */
337 | 723752FB1D2D9D6D002A1BC2 /* Project object */ = {
338 | isa = PBXProject;
339 | attributes = {
340 | LastSwiftUpdateCheck = 0730;
341 | LastUpgradeCheck = 0810;
342 | ORGANIZATIONNAME = Raizlabs;
343 | TargetAttributes = {
344 | 723753031D2D9D6D002A1BC2 = {
345 | CreatedOnToolsVersion = 7.3.1;
346 | LastSwiftMigration = 0810;
347 | };
348 | 7237530D1D2D9D6D002A1BC2 = {
349 | CreatedOnToolsVersion = 7.3.1;
350 | LastSwiftMigration = 0810;
351 | };
352 | 723753351D2D9E75002A1BC2 = {
353 | CreatedOnToolsVersion = 7.3.1;
354 | LastSwiftMigration = 0810;
355 | };
356 | };
357 | };
358 | buildConfigurationList = 723752FE1D2D9D6D002A1BC2 /* Build configuration list for PBXProject "BentoMap" */;
359 | compatibilityVersion = "Xcode 3.2";
360 | developmentRegion = English;
361 | hasScannedForEncodings = 0;
362 | knownRegions = (
363 | en,
364 | Base,
365 | );
366 | mainGroup = 723752FA1D2D9D6D002A1BC2;
367 | productRefGroup = 723753051D2D9D6D002A1BC2 /* Products */;
368 | projectDirPath = "";
369 | projectRoot = "";
370 | targets = (
371 | 723753031D2D9D6D002A1BC2 /* BentoMap */,
372 | 7237530D1D2D9D6D002A1BC2 /* BentoMapTests */,
373 | 723753351D2D9E75002A1BC2 /* BentoMapExample */,
374 | );
375 | };
376 | /* End PBXProject section */
377 |
378 | /* Begin PBXResourcesBuildPhase section */
379 | 723753021D2D9D6D002A1BC2 /* Resources */ = {
380 | isa = PBXResourcesBuildPhase;
381 | buildActionMask = 2147483647;
382 | files = (
383 | );
384 | runOnlyForDeploymentPostprocessing = 0;
385 | };
386 | 7237530C1D2D9D6D002A1BC2 /* Resources */ = {
387 | isa = PBXResourcesBuildPhase;
388 | buildActionMask = 2147483647;
389 | files = (
390 | );
391 | runOnlyForDeploymentPostprocessing = 0;
392 | };
393 | 723753341D2D9E75002A1BC2 /* Resources */ = {
394 | isa = PBXResourcesBuildPhase;
395 | buildActionMask = 2147483647;
396 | files = (
397 | 723753431D2D9E75002A1BC2 /* LaunchScreen.storyboard in Resources */,
398 | 723753401D2D9E75002A1BC2 /* Assets.xcassets in Resources */,
399 | );
400 | runOnlyForDeploymentPostprocessing = 0;
401 | };
402 | /* End PBXResourcesBuildPhase section */
403 |
404 | /* Begin PBXShellScriptBuildPhase section */
405 | 723753311D2D9E26002A1BC2 /* SwiftLint */ = {
406 | isa = PBXShellScriptBuildPhase;
407 | buildActionMask = 2147483647;
408 | files = (
409 | );
410 | inputPaths = (
411 | );
412 | name = SwiftLint;
413 | outputPaths = (
414 | );
415 | runOnlyForDeploymentPostprocessing = 0;
416 | shellPath = /bin/sh;
417 | shellScript = "if which swiftlint >/dev/null; then\nswiftlint\nelse\necho \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi";
418 | };
419 | 72B69E881DE5EB7D00272BC1 /* SwiftLint */ = {
420 | isa = PBXShellScriptBuildPhase;
421 | buildActionMask = 2147483647;
422 | files = (
423 | );
424 | inputPaths = (
425 | );
426 | name = SwiftLint;
427 | outputPaths = (
428 | );
429 | runOnlyForDeploymentPostprocessing = 0;
430 | shellPath = /bin/sh;
431 | shellScript = "if which swiftlint >/dev/null; then\n swiftlint || echo \"warning: SwiftLint failed with exit code $?. Is SwiftLint installed and up to date?\"\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi";
432 | };
433 | /* End PBXShellScriptBuildPhase section */
434 |
435 | /* Begin PBXSourcesBuildPhase section */
436 | 723752FF1D2D9D6D002A1BC2 /* Sources */ = {
437 | isa = PBXSourcesBuildPhase;
438 | buildActionMask = 2147483647;
439 | files = (
440 | 7237532A1D2D9DA2002A1BC2 /* QuadTreeNode.swift in Sources */,
441 | 7237532B1D2D9DA2002A1BC2 /* QuadTreeResult.swift in Sources */,
442 | 72383AB51D2ECAD00071EDE2 /* OptionalOr.swift in Sources */,
443 | B50426B61D71F775008BEF76 /* CGPoint+BentoCoordinate.swift in Sources */,
444 | B50426B41D71F35C008BEF76 /* MKMapPoint+BentoCoordinate.swift in Sources */,
445 | D4C3839B1D4F9DD500556EBE /* CollectionTypeExtensions.swift in Sources */,
446 | B50426B81D71FA21008BEF76 /* MKMapRect+BentoRect.swift in Sources */,
447 | 723753251D2D9DA2002A1BC2 /* BentoBox.swift in Sources */,
448 | B50426BA1D7203C9008BEF76 /* CLLocationCoordinate2D+BentoCoordinate.swift in Sources */,
449 | 723753281D2D9DA2002A1BC2 /* QuadrantWrapper.swift in Sources */,
450 | 723753291D2D9DA2002A1BC2 /* QuadTree.swift in Sources */,
451 | 723753261D2D9DA2002A1BC2 /* Box.swift in Sources */,
452 | 723753271D2D9DA2002A1BC2 /* OrdinalNodes.swift in Sources */,
453 | B50426AF1D71D710008BEF76 /* CGRect+BentoRect.swift in Sources */,
454 | );
455 | runOnlyForDeploymentPostprocessing = 0;
456 | };
457 | 7237530A1D2D9D6D002A1BC2 /* Sources */ = {
458 | isa = PBXSourcesBuildPhase;
459 | buildActionMask = 2147483647;
460 | files = (
461 | 7237532E1D2D9DB5002A1BC2 /* BentoBoxTests.swift in Sources */,
462 | 72383AB71D2ECAF70071EDE2 /* OptionalOrTests.swift in Sources */,
463 | 7237532F1D2D9DB5002A1BC2 /* QuadTreeTests.swift in Sources */,
464 | );
465 | runOnlyForDeploymentPostprocessing = 0;
466 | };
467 | 723753321D2D9E75002A1BC2 /* Sources */ = {
468 | isa = PBXSourcesBuildPhase;
469 | buildActionMask = 2147483647;
470 | files = (
471 | 7237534E1D2DA0CF002A1BC2 /* QuadTree+SampleData.swift in Sources */,
472 | B539B3401D7E35ED0089AFE6 /* MainMenuTableViewController.swift in Sources */,
473 | 7237533B1D2D9E75002A1BC2 /* MapKitViewController.swift in Sources */,
474 | 72D679441E252AD40032902B /* AnnotationView.swift in Sources */,
475 | B539B33E1D7E35D60089AFE6 /* CoreGraphicsViewController.swift in Sources */,
476 | 7228026E1D2E9D9100C637A6 /* Annotations.swift in Sources */,
477 | 723753391D2D9E75002A1BC2 /* AppDelegate.swift in Sources */,
478 | );
479 | runOnlyForDeploymentPostprocessing = 0;
480 | };
481 | /* End PBXSourcesBuildPhase section */
482 |
483 | /* Begin PBXTargetDependency section */
484 | 723753111D2D9D6D002A1BC2 /* PBXTargetDependency */ = {
485 | isa = PBXTargetDependency;
486 | target = 723753031D2D9D6D002A1BC2 /* BentoMap */;
487 | targetProxy = 723753101D2D9D6D002A1BC2 /* PBXContainerItemProxy */;
488 | };
489 | 7237534B1D2D9E80002A1BC2 /* PBXTargetDependency */ = {
490 | isa = PBXTargetDependency;
491 | target = 723753031D2D9D6D002A1BC2 /* BentoMap */;
492 | targetProxy = 7237534A1D2D9E80002A1BC2 /* PBXContainerItemProxy */;
493 | };
494 | /* End PBXTargetDependency section */
495 |
496 | /* Begin PBXVariantGroup section */
497 | 723753411D2D9E75002A1BC2 /* LaunchScreen.storyboard */ = {
498 | isa = PBXVariantGroup;
499 | children = (
500 | 723753421D2D9E75002A1BC2 /* Base */,
501 | );
502 | name = LaunchScreen.storyboard;
503 | path = .;
504 | sourceTree = "";
505 | };
506 | /* End PBXVariantGroup section */
507 |
508 | /* Begin XCBuildConfiguration section */
509 | 723753161D2D9D6D002A1BC2 /* Debug */ = {
510 | isa = XCBuildConfiguration;
511 | buildSettings = {
512 | ALWAYS_SEARCH_USER_PATHS = NO;
513 | CLANG_ANALYZER_NONNULL = YES;
514 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
515 | CLANG_CXX_LIBRARY = "libc++";
516 | CLANG_ENABLE_MODULES = YES;
517 | CLANG_ENABLE_OBJC_ARC = YES;
518 | CLANG_WARN_BOOL_CONVERSION = YES;
519 | CLANG_WARN_CONSTANT_CONVERSION = YES;
520 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
521 | CLANG_WARN_EMPTY_BODY = YES;
522 | CLANG_WARN_ENUM_CONVERSION = YES;
523 | CLANG_WARN_INFINITE_RECURSION = YES;
524 | CLANG_WARN_INT_CONVERSION = YES;
525 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
526 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
527 | CLANG_WARN_UNREACHABLE_CODE = YES;
528 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
529 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
530 | COPY_PHASE_STRIP = NO;
531 | CURRENT_PROJECT_VERSION = 1;
532 | DEBUG_INFORMATION_FORMAT = dwarf;
533 | ENABLE_STRICT_OBJC_MSGSEND = YES;
534 | ENABLE_TESTABILITY = YES;
535 | GCC_C_LANGUAGE_STANDARD = gnu99;
536 | GCC_DYNAMIC_NO_PIC = NO;
537 | GCC_NO_COMMON_BLOCKS = YES;
538 | GCC_OPTIMIZATION_LEVEL = 0;
539 | GCC_PREPROCESSOR_DEFINITIONS = (
540 | "DEBUG=1",
541 | "$(inherited)",
542 | );
543 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
544 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
545 | GCC_WARN_UNDECLARED_SELECTOR = YES;
546 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
547 | GCC_WARN_UNUSED_FUNCTION = YES;
548 | GCC_WARN_UNUSED_VARIABLE = YES;
549 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
550 | MTL_ENABLE_DEBUG_INFO = YES;
551 | ONLY_ACTIVE_ARCH = YES;
552 | SDKROOT = iphoneos;
553 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
554 | TARGETED_DEVICE_FAMILY = "1,2";
555 | VERSIONING_SYSTEM = "apple-generic";
556 | VERSION_INFO_PREFIX = "";
557 | };
558 | name = Debug;
559 | };
560 | 723753171D2D9D6D002A1BC2 /* Release */ = {
561 | isa = XCBuildConfiguration;
562 | buildSettings = {
563 | ALWAYS_SEARCH_USER_PATHS = NO;
564 | CLANG_ANALYZER_NONNULL = YES;
565 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
566 | CLANG_CXX_LIBRARY = "libc++";
567 | CLANG_ENABLE_MODULES = YES;
568 | CLANG_ENABLE_OBJC_ARC = YES;
569 | CLANG_WARN_BOOL_CONVERSION = YES;
570 | CLANG_WARN_CONSTANT_CONVERSION = YES;
571 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
572 | CLANG_WARN_EMPTY_BODY = YES;
573 | CLANG_WARN_ENUM_CONVERSION = YES;
574 | CLANG_WARN_INFINITE_RECURSION = YES;
575 | CLANG_WARN_INT_CONVERSION = YES;
576 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
577 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
578 | CLANG_WARN_UNREACHABLE_CODE = YES;
579 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
580 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
581 | COPY_PHASE_STRIP = NO;
582 | CURRENT_PROJECT_VERSION = 1;
583 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
584 | ENABLE_NS_ASSERTIONS = NO;
585 | ENABLE_STRICT_OBJC_MSGSEND = YES;
586 | GCC_C_LANGUAGE_STANDARD = gnu99;
587 | GCC_NO_COMMON_BLOCKS = YES;
588 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
589 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
590 | GCC_WARN_UNDECLARED_SELECTOR = YES;
591 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
592 | GCC_WARN_UNUSED_FUNCTION = YES;
593 | GCC_WARN_UNUSED_VARIABLE = YES;
594 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
595 | MTL_ENABLE_DEBUG_INFO = NO;
596 | SDKROOT = iphoneos;
597 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
598 | TARGETED_DEVICE_FAMILY = "1,2";
599 | VALIDATE_PRODUCT = YES;
600 | VERSIONING_SYSTEM = "apple-generic";
601 | VERSION_INFO_PREFIX = "";
602 | };
603 | name = Release;
604 | };
605 | 723753191D2D9D6D002A1BC2 /* Debug */ = {
606 | isa = XCBuildConfiguration;
607 | buildSettings = {
608 | CLANG_ENABLE_MODULES = YES;
609 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
610 | DEFINES_MODULE = YES;
611 | DYLIB_COMPATIBILITY_VERSION = 1;
612 | DYLIB_CURRENT_VERSION = 1;
613 | DYLIB_INSTALL_NAME_BASE = "@rpath";
614 | INFOPLIST_FILE = BentoMap/Resources/Info.plist;
615 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
616 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
617 | PRODUCT_BUNDLE_IDENTIFIER = com.raizlabs.BentoMap;
618 | PRODUCT_NAME = "$(TARGET_NAME)";
619 | SKIP_INSTALL = YES;
620 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
621 | SWIFT_VERSION = 3.0;
622 | };
623 | name = Debug;
624 | };
625 | 7237531A1D2D9D6D002A1BC2 /* Release */ = {
626 | isa = XCBuildConfiguration;
627 | buildSettings = {
628 | CLANG_ENABLE_MODULES = YES;
629 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
630 | DEFINES_MODULE = YES;
631 | DYLIB_COMPATIBILITY_VERSION = 1;
632 | DYLIB_CURRENT_VERSION = 1;
633 | DYLIB_INSTALL_NAME_BASE = "@rpath";
634 | INFOPLIST_FILE = BentoMap/Resources/Info.plist;
635 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
636 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
637 | PRODUCT_BUNDLE_IDENTIFIER = com.raizlabs.BentoMap;
638 | PRODUCT_NAME = "$(TARGET_NAME)";
639 | SKIP_INSTALL = YES;
640 | SWIFT_VERSION = 3.0;
641 | };
642 | name = Release;
643 | };
644 | 7237531C1D2D9D6D002A1BC2 /* Debug */ = {
645 | isa = XCBuildConfiguration;
646 | buildSettings = {
647 | INFOPLIST_FILE = BentoMapTests/Resources/Info.plist;
648 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
649 | PRODUCT_BUNDLE_IDENTIFIER = com.raizlabs.BentoMapTests;
650 | PRODUCT_NAME = "$(TARGET_NAME)";
651 | SWIFT_VERSION = 3.0;
652 | };
653 | name = Debug;
654 | };
655 | 7237531D1D2D9D6D002A1BC2 /* Release */ = {
656 | isa = XCBuildConfiguration;
657 | buildSettings = {
658 | INFOPLIST_FILE = BentoMapTests/Resources/Info.plist;
659 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
660 | PRODUCT_BUNDLE_IDENTIFIER = com.raizlabs.BentoMapTests;
661 | PRODUCT_NAME = "$(TARGET_NAME)";
662 | SWIFT_VERSION = 3.0;
663 | };
664 | name = Release;
665 | };
666 | 723753461D2D9E75002A1BC2 /* Debug */ = {
667 | isa = XCBuildConfiguration;
668 | buildSettings = {
669 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
670 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
671 | INFOPLIST_FILE = BentoMapExample/Assets/Info.plist;
672 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
673 | PRODUCT_BUNDLE_IDENTIFIER = com.raizlabs.BentoMapExample;
674 | PRODUCT_NAME = "$(TARGET_NAME)";
675 | SWIFT_VERSION = 3.0;
676 | };
677 | name = Debug;
678 | };
679 | 723753471D2D9E75002A1BC2 /* Release */ = {
680 | isa = XCBuildConfiguration;
681 | buildSettings = {
682 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
683 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
684 | INFOPLIST_FILE = BentoMapExample/Assets/Info.plist;
685 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
686 | PRODUCT_BUNDLE_IDENTIFIER = com.raizlabs.BentoMapExample;
687 | PRODUCT_NAME = "$(TARGET_NAME)";
688 | SWIFT_VERSION = 3.0;
689 | };
690 | name = Release;
691 | };
692 | /* End XCBuildConfiguration section */
693 |
694 | /* Begin XCConfigurationList section */
695 | 723752FE1D2D9D6D002A1BC2 /* Build configuration list for PBXProject "BentoMap" */ = {
696 | isa = XCConfigurationList;
697 | buildConfigurations = (
698 | 723753161D2D9D6D002A1BC2 /* Debug */,
699 | 723753171D2D9D6D002A1BC2 /* Release */,
700 | );
701 | defaultConfigurationIsVisible = 0;
702 | defaultConfigurationName = Release;
703 | };
704 | 723753181D2D9D6D002A1BC2 /* Build configuration list for PBXNativeTarget "BentoMap" */ = {
705 | isa = XCConfigurationList;
706 | buildConfigurations = (
707 | 723753191D2D9D6D002A1BC2 /* Debug */,
708 | 7237531A1D2D9D6D002A1BC2 /* Release */,
709 | );
710 | defaultConfigurationIsVisible = 0;
711 | defaultConfigurationName = Release;
712 | };
713 | 7237531B1D2D9D6D002A1BC2 /* Build configuration list for PBXNativeTarget "BentoMapTests" */ = {
714 | isa = XCConfigurationList;
715 | buildConfigurations = (
716 | 7237531C1D2D9D6D002A1BC2 /* Debug */,
717 | 7237531D1D2D9D6D002A1BC2 /* Release */,
718 | );
719 | defaultConfigurationIsVisible = 0;
720 | defaultConfigurationName = Release;
721 | };
722 | 723753451D2D9E75002A1BC2 /* Build configuration list for PBXNativeTarget "BentoMapExample" */ = {
723 | isa = XCConfigurationList;
724 | buildConfigurations = (
725 | 723753461D2D9E75002A1BC2 /* Debug */,
726 | 723753471D2D9E75002A1BC2 /* Release */,
727 | );
728 | defaultConfigurationIsVisible = 0;
729 | defaultConfigurationName = Release;
730 | };
731 | /* End XCConfigurationList section */
732 | };
733 | rootObject = 723752FB1D2D9D6D002A1BC2 /* Project object */;
734 | }
735 |
--------------------------------------------------------------------------------