├── LICENSE ├── README.md ├── TBAnnotationClustering.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ └── theo.xcuserdatad │ │ └── WorkspaceSettings.xcsettings └── xcuserdata │ └── theo.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ ├── TBAnnotationClustering.xcscheme │ └── xcschememanagement.plist ├── TBAnnotationClustering ├── Images.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── LaunchImage.launchimage │ │ └── Contents.json ├── TBAnnotationClustering-Info.plist ├── TBAnnotationClustering-Prefix.pch ├── TBAppDelegate.h ├── TBAppDelegate.m ├── TBClusterAnnotation.h ├── TBClusterAnnotation.m ├── TBClusterAnnotationView.h ├── TBClusterAnnotationView.m ├── TBCoordinateQuadTree.h ├── TBCoordinateQuadTree.m ├── TBMapViewController.h ├── TBMapViewController.m ├── TBQuadTree.h ├── TBQuadTree.m ├── USA-HotelMotel.csv ├── en.lproj │ └── InfoPlist.strings └── main.m └── TBAnnotationClusteringTests ├── TBAnnotationClusteringTests-Info.plist ├── TBAnnotationClusteringTests.m └── en.lproj └── InfoPlist.strings /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Theodore Calmes 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | TBAnnotationClustering 2 | ====================== 3 | 4 | Example project from [blog post](http://robots.thoughtbot.com/how-to-handle-large-amounts-of-data-on-maps/). 5 | 6 | Quad Tree 7 | --------- 8 | If you want to grab the quad tree you can get it from my [GitHub](https://github.com/theocalmes/TBQuadTree) 9 | 10 | or just install using cocoapods 11 | 12 | ```pod 'TBQuadTree', '~> 0.0'``` 13 | -------------------------------------------------------------------------------- /TBAnnotationClustering.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | E44063B617FF44A500E3A194 /* TBClusterAnnotationView.m in Sources */ = {isa = PBXBuildFile; fileRef = E44063B517FF44A500E3A194 /* TBClusterAnnotationView.m */; }; 11 | E47A6E8D17F62C0700A468D1 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E47A6E8C17F62C0700A468D1 /* Foundation.framework */; }; 12 | E47A6E8F17F62C0700A468D1 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E47A6E8E17F62C0700A468D1 /* CoreGraphics.framework */; }; 13 | E47A6E9117F62C0700A468D1 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E47A6E9017F62C0700A468D1 /* UIKit.framework */; }; 14 | E47A6E9717F62C0700A468D1 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = E47A6E9517F62C0700A468D1 /* InfoPlist.strings */; }; 15 | E47A6E9917F62C0700A468D1 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = E47A6E9817F62C0700A468D1 /* main.m */; }; 16 | E47A6E9D17F62C0700A468D1 /* TBAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = E47A6E9C17F62C0700A468D1 /* TBAppDelegate.m */; }; 17 | E47A6E9F17F62C0700A468D1 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E47A6E9E17F62C0700A468D1 /* Images.xcassets */; }; 18 | E47A6EA617F62C0700A468D1 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E47A6EA517F62C0700A468D1 /* XCTest.framework */; }; 19 | E47A6EA717F62C0700A468D1 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E47A6E8C17F62C0700A468D1 /* Foundation.framework */; }; 20 | E47A6EA817F62C0700A468D1 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E47A6E9017F62C0700A468D1 /* UIKit.framework */; }; 21 | E47A6EB017F62C0700A468D1 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = E47A6EAE17F62C0700A468D1 /* InfoPlist.strings */; }; 22 | E47A6EB217F62C0700A468D1 /* TBAnnotationClusteringTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E47A6EB117F62C0700A468D1 /* TBAnnotationClusteringTests.m */; }; 23 | E47A6EBC17F62C1000A468D1 /* USA-HotelMotel.csv in Resources */ = {isa = PBXBuildFile; fileRef = E47A6EBB17F62C1000A468D1 /* USA-HotelMotel.csv */; }; 24 | E480B06C1804F047006247FA /* TBClusterAnnotation.m in Sources */ = {isa = PBXBuildFile; fileRef = E480B06B1804F047006247FA /* TBClusterAnnotation.m */; }; 25 | E4FB04C417F6302000FC288B /* TBQuadTree.m in Sources */ = {isa = PBXBuildFile; fileRef = E4FB04C317F6302000FC288B /* TBQuadTree.m */; }; 26 | E4FB04CD17F6320B00FC288B /* TBCoordinateQuadTree.m in Sources */ = {isa = PBXBuildFile; fileRef = E4FB04CC17F6320B00FC288B /* TBCoordinateQuadTree.m */; }; 27 | E4FB04CF17F6396700FC288B /* MapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E4FB04CE17F6396700FC288B /* MapKit.framework */; }; 28 | E4FB04D217F6522700FC288B /* TBMapViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = E4FB04D117F6522700FC288B /* TBMapViewController.m */; }; 29 | E4FB04D417F654F500FC288B /* CoreLocation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E4FB04D317F654F500FC288B /* CoreLocation.framework */; }; 30 | /* End PBXBuildFile section */ 31 | 32 | /* Begin PBXContainerItemProxy section */ 33 | E47A6EA917F62C0700A468D1 /* PBXContainerItemProxy */ = { 34 | isa = PBXContainerItemProxy; 35 | containerPortal = E47A6E8117F62C0700A468D1 /* Project object */; 36 | proxyType = 1; 37 | remoteGlobalIDString = E47A6E8817F62C0700A468D1; 38 | remoteInfo = TBAnnotationClustering; 39 | }; 40 | /* End PBXContainerItemProxy section */ 41 | 42 | /* Begin PBXFileReference section */ 43 | E44063B417FF44A500E3A194 /* TBClusterAnnotationView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TBClusterAnnotationView.h; sourceTree = ""; }; 44 | E44063B517FF44A500E3A194 /* TBClusterAnnotationView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TBClusterAnnotationView.m; sourceTree = ""; }; 45 | E47A6E8917F62C0700A468D1 /* TBAnnotationClustering.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TBAnnotationClustering.app; sourceTree = BUILT_PRODUCTS_DIR; }; 46 | E47A6E8C17F62C0700A468D1 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 47 | E47A6E8E17F62C0700A468D1 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 48 | E47A6E9017F62C0700A468D1 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 49 | E47A6E9417F62C0700A468D1 /* TBAnnotationClustering-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "TBAnnotationClustering-Info.plist"; sourceTree = ""; }; 50 | E47A6E9617F62C0700A468D1 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 51 | E47A6E9817F62C0700A468D1 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 52 | E47A6E9A17F62C0700A468D1 /* TBAnnotationClustering-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "TBAnnotationClustering-Prefix.pch"; sourceTree = ""; }; 53 | E47A6E9B17F62C0700A468D1 /* TBAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TBAppDelegate.h; sourceTree = ""; }; 54 | E47A6E9C17F62C0700A468D1 /* TBAppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TBAppDelegate.m; sourceTree = ""; }; 55 | E47A6E9E17F62C0700A468D1 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 56 | E47A6EA417F62C0700A468D1 /* TBAnnotationClusteringTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TBAnnotationClusteringTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 57 | E47A6EA517F62C0700A468D1 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; 58 | E47A6EAD17F62C0700A468D1 /* TBAnnotationClusteringTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "TBAnnotationClusteringTests-Info.plist"; sourceTree = ""; }; 59 | E47A6EAF17F62C0700A468D1 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 60 | E47A6EB117F62C0700A468D1 /* TBAnnotationClusteringTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TBAnnotationClusteringTests.m; sourceTree = ""; }; 61 | E47A6EBB17F62C1000A468D1 /* USA-HotelMotel.csv */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "USA-HotelMotel.csv"; sourceTree = ""; }; 62 | E480B06A1804F047006247FA /* TBClusterAnnotation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TBClusterAnnotation.h; sourceTree = ""; }; 63 | E480B06B1804F047006247FA /* TBClusterAnnotation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TBClusterAnnotation.m; sourceTree = ""; }; 64 | E4FB04C217F6302000FC288B /* TBQuadTree.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TBQuadTree.h; sourceTree = ""; }; 65 | E4FB04C317F6302000FC288B /* TBQuadTree.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TBQuadTree.m; sourceTree = ""; }; 66 | E4FB04CB17F6320B00FC288B /* TBCoordinateQuadTree.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TBCoordinateQuadTree.h; sourceTree = ""; }; 67 | E4FB04CC17F6320B00FC288B /* TBCoordinateQuadTree.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TBCoordinateQuadTree.m; sourceTree = ""; }; 68 | E4FB04CE17F6396700FC288B /* MapKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MapKit.framework; path = System/Library/Frameworks/MapKit.framework; sourceTree = SDKROOT; }; 69 | E4FB04D017F6522700FC288B /* TBMapViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TBMapViewController.h; sourceTree = ""; }; 70 | E4FB04D117F6522700FC288B /* TBMapViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TBMapViewController.m; sourceTree = ""; }; 71 | E4FB04D317F654F500FC288B /* CoreLocation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreLocation.framework; path = System/Library/Frameworks/CoreLocation.framework; sourceTree = SDKROOT; }; 72 | /* End PBXFileReference section */ 73 | 74 | /* Begin PBXFrameworksBuildPhase section */ 75 | E47A6E8617F62C0700A468D1 /* Frameworks */ = { 76 | isa = PBXFrameworksBuildPhase; 77 | buildActionMask = 2147483647; 78 | files = ( 79 | E4FB04D417F654F500FC288B /* CoreLocation.framework in Frameworks */, 80 | E4FB04CF17F6396700FC288B /* MapKit.framework in Frameworks */, 81 | E47A6E8F17F62C0700A468D1 /* CoreGraphics.framework in Frameworks */, 82 | E47A6E9117F62C0700A468D1 /* UIKit.framework in Frameworks */, 83 | E47A6E8D17F62C0700A468D1 /* Foundation.framework in Frameworks */, 84 | ); 85 | runOnlyForDeploymentPostprocessing = 0; 86 | }; 87 | E47A6EA117F62C0700A468D1 /* Frameworks */ = { 88 | isa = PBXFrameworksBuildPhase; 89 | buildActionMask = 2147483647; 90 | files = ( 91 | E47A6EA617F62C0700A468D1 /* XCTest.framework in Frameworks */, 92 | E47A6EA817F62C0700A468D1 /* UIKit.framework in Frameworks */, 93 | E47A6EA717F62C0700A468D1 /* Foundation.framework in Frameworks */, 94 | ); 95 | runOnlyForDeploymentPostprocessing = 0; 96 | }; 97 | /* End PBXFrameworksBuildPhase section */ 98 | 99 | /* Begin PBXGroup section */ 100 | E47A6E8017F62C0700A468D1 = { 101 | isa = PBXGroup; 102 | children = ( 103 | E47A6E9217F62C0700A468D1 /* TBAnnotationClustering */, 104 | E47A6EAB17F62C0700A468D1 /* TBAnnotationClusteringTests */, 105 | E47A6E8B17F62C0700A468D1 /* Frameworks */, 106 | E47A6E8A17F62C0700A468D1 /* Products */, 107 | ); 108 | sourceTree = ""; 109 | }; 110 | E47A6E8A17F62C0700A468D1 /* Products */ = { 111 | isa = PBXGroup; 112 | children = ( 113 | E47A6E8917F62C0700A468D1 /* TBAnnotationClustering.app */, 114 | E47A6EA417F62C0700A468D1 /* TBAnnotationClusteringTests.xctest */, 115 | ); 116 | name = Products; 117 | sourceTree = ""; 118 | }; 119 | E47A6E8B17F62C0700A468D1 /* Frameworks */ = { 120 | isa = PBXGroup; 121 | children = ( 122 | E4FB04D317F654F500FC288B /* CoreLocation.framework */, 123 | E4FB04CE17F6396700FC288B /* MapKit.framework */, 124 | E47A6E8C17F62C0700A468D1 /* Foundation.framework */, 125 | E47A6E8E17F62C0700A468D1 /* CoreGraphics.framework */, 126 | E47A6E9017F62C0700A468D1 /* UIKit.framework */, 127 | E47A6EA517F62C0700A468D1 /* XCTest.framework */, 128 | ); 129 | name = Frameworks; 130 | sourceTree = ""; 131 | }; 132 | E47A6E9217F62C0700A468D1 /* TBAnnotationClustering */ = { 133 | isa = PBXGroup; 134 | children = ( 135 | E47A6E9B17F62C0700A468D1 /* TBAppDelegate.h */, 136 | E47A6E9C17F62C0700A468D1 /* TBAppDelegate.m */, 137 | E4FB04C217F6302000FC288B /* TBQuadTree.h */, 138 | E4FB04C317F6302000FC288B /* TBQuadTree.m */, 139 | E4FB04CB17F6320B00FC288B /* TBCoordinateQuadTree.h */, 140 | E4FB04CC17F6320B00FC288B /* TBCoordinateQuadTree.m */, 141 | E4FB04D017F6522700FC288B /* TBMapViewController.h */, 142 | E4FB04D117F6522700FC288B /* TBMapViewController.m */, 143 | E480B06A1804F047006247FA /* TBClusterAnnotation.h */, 144 | E480B06B1804F047006247FA /* TBClusterAnnotation.m */, 145 | E44063B417FF44A500E3A194 /* TBClusterAnnotationView.h */, 146 | E44063B517FF44A500E3A194 /* TBClusterAnnotationView.m */, 147 | E47A6E9E17F62C0700A468D1 /* Images.xcassets */, 148 | E47A6E9317F62C0700A468D1 /* Supporting Files */, 149 | ); 150 | path = TBAnnotationClustering; 151 | sourceTree = ""; 152 | }; 153 | E47A6E9317F62C0700A468D1 /* Supporting Files */ = { 154 | isa = PBXGroup; 155 | children = ( 156 | E47A6EBB17F62C1000A468D1 /* USA-HotelMotel.csv */, 157 | E47A6E9417F62C0700A468D1 /* TBAnnotationClustering-Info.plist */, 158 | E47A6E9517F62C0700A468D1 /* InfoPlist.strings */, 159 | E47A6E9817F62C0700A468D1 /* main.m */, 160 | E47A6E9A17F62C0700A468D1 /* TBAnnotationClustering-Prefix.pch */, 161 | ); 162 | name = "Supporting Files"; 163 | sourceTree = ""; 164 | }; 165 | E47A6EAB17F62C0700A468D1 /* TBAnnotationClusteringTests */ = { 166 | isa = PBXGroup; 167 | children = ( 168 | E47A6EB117F62C0700A468D1 /* TBAnnotationClusteringTests.m */, 169 | E47A6EAC17F62C0700A468D1 /* Supporting Files */, 170 | ); 171 | path = TBAnnotationClusteringTests; 172 | sourceTree = ""; 173 | }; 174 | E47A6EAC17F62C0700A468D1 /* Supporting Files */ = { 175 | isa = PBXGroup; 176 | children = ( 177 | E47A6EAD17F62C0700A468D1 /* TBAnnotationClusteringTests-Info.plist */, 178 | E47A6EAE17F62C0700A468D1 /* InfoPlist.strings */, 179 | ); 180 | name = "Supporting Files"; 181 | sourceTree = ""; 182 | }; 183 | /* End PBXGroup section */ 184 | 185 | /* Begin PBXNativeTarget section */ 186 | E47A6E8817F62C0700A468D1 /* TBAnnotationClustering */ = { 187 | isa = PBXNativeTarget; 188 | buildConfigurationList = E47A6EB517F62C0700A468D1 /* Build configuration list for PBXNativeTarget "TBAnnotationClustering" */; 189 | buildPhases = ( 190 | E47A6E8517F62C0700A468D1 /* Sources */, 191 | E47A6E8617F62C0700A468D1 /* Frameworks */, 192 | E47A6E8717F62C0700A468D1 /* Resources */, 193 | ); 194 | buildRules = ( 195 | ); 196 | dependencies = ( 197 | ); 198 | name = TBAnnotationClustering; 199 | productName = TBAnnotationClustering; 200 | productReference = E47A6E8917F62C0700A468D1 /* TBAnnotationClustering.app */; 201 | productType = "com.apple.product-type.application"; 202 | }; 203 | E47A6EA317F62C0700A468D1 /* TBAnnotationClusteringTests */ = { 204 | isa = PBXNativeTarget; 205 | buildConfigurationList = E47A6EB817F62C0700A468D1 /* Build configuration list for PBXNativeTarget "TBAnnotationClusteringTests" */; 206 | buildPhases = ( 207 | E47A6EA017F62C0700A468D1 /* Sources */, 208 | E47A6EA117F62C0700A468D1 /* Frameworks */, 209 | E47A6EA217F62C0700A468D1 /* Resources */, 210 | ); 211 | buildRules = ( 212 | ); 213 | dependencies = ( 214 | E47A6EAA17F62C0700A468D1 /* PBXTargetDependency */, 215 | ); 216 | name = TBAnnotationClusteringTests; 217 | productName = TBAnnotationClusteringTests; 218 | productReference = E47A6EA417F62C0700A468D1 /* TBAnnotationClusteringTests.xctest */; 219 | productType = "com.apple.product-type.bundle.unit-test"; 220 | }; 221 | /* End PBXNativeTarget section */ 222 | 223 | /* Begin PBXProject section */ 224 | E47A6E8117F62C0700A468D1 /* Project object */ = { 225 | isa = PBXProject; 226 | attributes = { 227 | CLASSPREFIX = TB; 228 | LastUpgradeCheck = 0500; 229 | ORGANIZATIONNAME = "Theodore Calmes"; 230 | TargetAttributes = { 231 | E47A6EA317F62C0700A468D1 = { 232 | TestTargetID = E47A6E8817F62C0700A468D1; 233 | }; 234 | }; 235 | }; 236 | buildConfigurationList = E47A6E8417F62C0700A468D1 /* Build configuration list for PBXProject "TBAnnotationClustering" */; 237 | compatibilityVersion = "Xcode 3.2"; 238 | developmentRegion = English; 239 | hasScannedForEncodings = 0; 240 | knownRegions = ( 241 | en, 242 | ); 243 | mainGroup = E47A6E8017F62C0700A468D1; 244 | productRefGroup = E47A6E8A17F62C0700A468D1 /* Products */; 245 | projectDirPath = ""; 246 | projectRoot = ""; 247 | targets = ( 248 | E47A6E8817F62C0700A468D1 /* TBAnnotationClustering */, 249 | E47A6EA317F62C0700A468D1 /* TBAnnotationClusteringTests */, 250 | ); 251 | }; 252 | /* End PBXProject section */ 253 | 254 | /* Begin PBXResourcesBuildPhase section */ 255 | E47A6E8717F62C0700A468D1 /* Resources */ = { 256 | isa = PBXResourcesBuildPhase; 257 | buildActionMask = 2147483647; 258 | files = ( 259 | E47A6EBC17F62C1000A468D1 /* USA-HotelMotel.csv in Resources */, 260 | E47A6E9717F62C0700A468D1 /* InfoPlist.strings in Resources */, 261 | E47A6E9F17F62C0700A468D1 /* Images.xcassets in Resources */, 262 | ); 263 | runOnlyForDeploymentPostprocessing = 0; 264 | }; 265 | E47A6EA217F62C0700A468D1 /* Resources */ = { 266 | isa = PBXResourcesBuildPhase; 267 | buildActionMask = 2147483647; 268 | files = ( 269 | E47A6EB017F62C0700A468D1 /* InfoPlist.strings in Resources */, 270 | ); 271 | runOnlyForDeploymentPostprocessing = 0; 272 | }; 273 | /* End PBXResourcesBuildPhase section */ 274 | 275 | /* Begin PBXSourcesBuildPhase section */ 276 | E47A6E8517F62C0700A468D1 /* Sources */ = { 277 | isa = PBXSourcesBuildPhase; 278 | buildActionMask = 2147483647; 279 | files = ( 280 | E4FB04C417F6302000FC288B /* TBQuadTree.m in Sources */, 281 | E47A6E9D17F62C0700A468D1 /* TBAppDelegate.m in Sources */, 282 | E44063B617FF44A500E3A194 /* TBClusterAnnotationView.m in Sources */, 283 | E480B06C1804F047006247FA /* TBClusterAnnotation.m in Sources */, 284 | E4FB04D217F6522700FC288B /* TBMapViewController.m in Sources */, 285 | E4FB04CD17F6320B00FC288B /* TBCoordinateQuadTree.m in Sources */, 286 | E47A6E9917F62C0700A468D1 /* main.m in Sources */, 287 | ); 288 | runOnlyForDeploymentPostprocessing = 0; 289 | }; 290 | E47A6EA017F62C0700A468D1 /* Sources */ = { 291 | isa = PBXSourcesBuildPhase; 292 | buildActionMask = 2147483647; 293 | files = ( 294 | E47A6EB217F62C0700A468D1 /* TBAnnotationClusteringTests.m in Sources */, 295 | ); 296 | runOnlyForDeploymentPostprocessing = 0; 297 | }; 298 | /* End PBXSourcesBuildPhase section */ 299 | 300 | /* Begin PBXTargetDependency section */ 301 | E47A6EAA17F62C0700A468D1 /* PBXTargetDependency */ = { 302 | isa = PBXTargetDependency; 303 | target = E47A6E8817F62C0700A468D1 /* TBAnnotationClustering */; 304 | targetProxy = E47A6EA917F62C0700A468D1 /* PBXContainerItemProxy */; 305 | }; 306 | /* End PBXTargetDependency section */ 307 | 308 | /* Begin PBXVariantGroup section */ 309 | E47A6E9517F62C0700A468D1 /* InfoPlist.strings */ = { 310 | isa = PBXVariantGroup; 311 | children = ( 312 | E47A6E9617F62C0700A468D1 /* en */, 313 | ); 314 | name = InfoPlist.strings; 315 | sourceTree = ""; 316 | }; 317 | E47A6EAE17F62C0700A468D1 /* InfoPlist.strings */ = { 318 | isa = PBXVariantGroup; 319 | children = ( 320 | E47A6EAF17F62C0700A468D1 /* en */, 321 | ); 322 | name = InfoPlist.strings; 323 | sourceTree = ""; 324 | }; 325 | /* End PBXVariantGroup section */ 326 | 327 | /* Begin XCBuildConfiguration section */ 328 | E47A6EB317F62C0700A468D1 /* Debug */ = { 329 | isa = XCBuildConfiguration; 330 | buildSettings = { 331 | ALWAYS_SEARCH_USER_PATHS = NO; 332 | ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)"; 333 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 334 | CLANG_CXX_LIBRARY = "libc++"; 335 | CLANG_ENABLE_MODULES = YES; 336 | CLANG_ENABLE_OBJC_ARC = YES; 337 | CLANG_WARN_BOOL_CONVERSION = YES; 338 | CLANG_WARN_CONSTANT_CONVERSION = YES; 339 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 340 | CLANG_WARN_EMPTY_BODY = YES; 341 | CLANG_WARN_ENUM_CONVERSION = YES; 342 | CLANG_WARN_INT_CONVERSION = YES; 343 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 344 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 345 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 346 | COPY_PHASE_STRIP = NO; 347 | GCC_C_LANGUAGE_STANDARD = gnu99; 348 | GCC_DYNAMIC_NO_PIC = NO; 349 | GCC_OPTIMIZATION_LEVEL = 0; 350 | GCC_PREPROCESSOR_DEFINITIONS = ( 351 | "DEBUG=1", 352 | "$(inherited)", 353 | ); 354 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 355 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 356 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 357 | GCC_WARN_UNDECLARED_SELECTOR = YES; 358 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 359 | GCC_WARN_UNUSED_FUNCTION = YES; 360 | GCC_WARN_UNUSED_VARIABLE = YES; 361 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 362 | ONLY_ACTIVE_ARCH = YES; 363 | SDKROOT = iphoneos; 364 | }; 365 | name = Debug; 366 | }; 367 | E47A6EB417F62C0700A468D1 /* Release */ = { 368 | isa = XCBuildConfiguration; 369 | buildSettings = { 370 | ALWAYS_SEARCH_USER_PATHS = NO; 371 | ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)"; 372 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 373 | CLANG_CXX_LIBRARY = "libc++"; 374 | CLANG_ENABLE_MODULES = YES; 375 | CLANG_ENABLE_OBJC_ARC = YES; 376 | CLANG_WARN_BOOL_CONVERSION = YES; 377 | CLANG_WARN_CONSTANT_CONVERSION = YES; 378 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 379 | CLANG_WARN_EMPTY_BODY = YES; 380 | CLANG_WARN_ENUM_CONVERSION = YES; 381 | CLANG_WARN_INT_CONVERSION = YES; 382 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 383 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 384 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 385 | COPY_PHASE_STRIP = YES; 386 | ENABLE_NS_ASSERTIONS = NO; 387 | GCC_C_LANGUAGE_STANDARD = gnu99; 388 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 389 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 390 | GCC_WARN_UNDECLARED_SELECTOR = YES; 391 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 392 | GCC_WARN_UNUSED_FUNCTION = YES; 393 | GCC_WARN_UNUSED_VARIABLE = YES; 394 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 395 | SDKROOT = iphoneos; 396 | VALIDATE_PRODUCT = YES; 397 | }; 398 | name = Release; 399 | }; 400 | E47A6EB617F62C0700A468D1 /* Debug */ = { 401 | isa = XCBuildConfiguration; 402 | buildSettings = { 403 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 404 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 405 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 406 | GCC_PREFIX_HEADER = "TBAnnotationClustering/TBAnnotationClustering-Prefix.pch"; 407 | INFOPLIST_FILE = "TBAnnotationClustering/TBAnnotationClustering-Info.plist"; 408 | PRODUCT_NAME = "$(TARGET_NAME)"; 409 | WRAPPER_EXTENSION = app; 410 | }; 411 | name = Debug; 412 | }; 413 | E47A6EB717F62C0700A468D1 /* Release */ = { 414 | isa = XCBuildConfiguration; 415 | buildSettings = { 416 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 417 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 418 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 419 | GCC_PREFIX_HEADER = "TBAnnotationClustering/TBAnnotationClustering-Prefix.pch"; 420 | INFOPLIST_FILE = "TBAnnotationClustering/TBAnnotationClustering-Info.plist"; 421 | PRODUCT_NAME = "$(TARGET_NAME)"; 422 | WRAPPER_EXTENSION = app; 423 | }; 424 | name = Release; 425 | }; 426 | E47A6EB917F62C0700A468D1 /* Debug */ = { 427 | isa = XCBuildConfiguration; 428 | buildSettings = { 429 | ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)"; 430 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/TBAnnotationClustering.app/TBAnnotationClustering"; 431 | FRAMEWORK_SEARCH_PATHS = ( 432 | "$(SDKROOT)/Developer/Library/Frameworks", 433 | "$(inherited)", 434 | "$(DEVELOPER_FRAMEWORKS_DIR)", 435 | ); 436 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 437 | GCC_PREFIX_HEADER = "TBAnnotationClustering/TBAnnotationClustering-Prefix.pch"; 438 | GCC_PREPROCESSOR_DEFINITIONS = ( 439 | "DEBUG=1", 440 | "$(inherited)", 441 | ); 442 | INFOPLIST_FILE = "TBAnnotationClusteringTests/TBAnnotationClusteringTests-Info.plist"; 443 | PRODUCT_NAME = "$(TARGET_NAME)"; 444 | TEST_HOST = "$(BUNDLE_LOADER)"; 445 | WRAPPER_EXTENSION = xctest; 446 | }; 447 | name = Debug; 448 | }; 449 | E47A6EBA17F62C0700A468D1 /* Release */ = { 450 | isa = XCBuildConfiguration; 451 | buildSettings = { 452 | ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)"; 453 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/TBAnnotationClustering.app/TBAnnotationClustering"; 454 | FRAMEWORK_SEARCH_PATHS = ( 455 | "$(SDKROOT)/Developer/Library/Frameworks", 456 | "$(inherited)", 457 | "$(DEVELOPER_FRAMEWORKS_DIR)", 458 | ); 459 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 460 | GCC_PREFIX_HEADER = "TBAnnotationClustering/TBAnnotationClustering-Prefix.pch"; 461 | INFOPLIST_FILE = "TBAnnotationClusteringTests/TBAnnotationClusteringTests-Info.plist"; 462 | PRODUCT_NAME = "$(TARGET_NAME)"; 463 | TEST_HOST = "$(BUNDLE_LOADER)"; 464 | WRAPPER_EXTENSION = xctest; 465 | }; 466 | name = Release; 467 | }; 468 | /* End XCBuildConfiguration section */ 469 | 470 | /* Begin XCConfigurationList section */ 471 | E47A6E8417F62C0700A468D1 /* Build configuration list for PBXProject "TBAnnotationClustering" */ = { 472 | isa = XCConfigurationList; 473 | buildConfigurations = ( 474 | E47A6EB317F62C0700A468D1 /* Debug */, 475 | E47A6EB417F62C0700A468D1 /* Release */, 476 | ); 477 | defaultConfigurationIsVisible = 0; 478 | defaultConfigurationName = Release; 479 | }; 480 | E47A6EB517F62C0700A468D1 /* Build configuration list for PBXNativeTarget "TBAnnotationClustering" */ = { 481 | isa = XCConfigurationList; 482 | buildConfigurations = ( 483 | E47A6EB617F62C0700A468D1 /* Debug */, 484 | E47A6EB717F62C0700A468D1 /* Release */, 485 | ); 486 | defaultConfigurationIsVisible = 0; 487 | defaultConfigurationName = Release; 488 | }; 489 | E47A6EB817F62C0700A468D1 /* Build configuration list for PBXNativeTarget "TBAnnotationClusteringTests" */ = { 490 | isa = XCConfigurationList; 491 | buildConfigurations = ( 492 | E47A6EB917F62C0700A468D1 /* Debug */, 493 | E47A6EBA17F62C0700A468D1 /* Release */, 494 | ); 495 | defaultConfigurationIsVisible = 0; 496 | defaultConfigurationName = Release; 497 | }; 498 | /* End XCConfigurationList section */ 499 | }; 500 | rootObject = E47A6E8117F62C0700A468D1 /* Project object */; 501 | } 502 | -------------------------------------------------------------------------------- /TBAnnotationClustering.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /TBAnnotationClustering.xcodeproj/project.xcworkspace/xcuserdata/theo.xcuserdatad/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | HasAskedToTakeAutomaticSnapshotBeforeSignificantChanges 6 | 7 | SnapshotAutomaticallyBeforeSignificantChanges 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /TBAnnotationClustering.xcodeproj/xcuserdata/theo.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /TBAnnotationClustering.xcodeproj/xcuserdata/theo.xcuserdatad/xcschemes/TBAnnotationClustering.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 61 | 62 | 68 | 69 | 70 | 71 | 72 | 73 | 79 | 80 | 86 | 87 | 88 | 89 | 91 | 92 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /TBAnnotationClustering.xcodeproj/xcuserdata/theo.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | TBAnnotationClustering.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | E47A6E8817F62C0700A468D1 16 | 17 | primary 18 | 19 | 20 | E47A6EA317F62C0700A468D1 21 | 22 | primary 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /TBAnnotationClustering/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "40x40", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "60x60", 16 | "scale" : "2x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /TBAnnotationClustering/Images.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "portrait", 5 | "idiom" : "iphone", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "7.0", 8 | "scale" : "2x" 9 | }, 10 | { 11 | "orientation" : "portrait", 12 | "idiom" : "iphone", 13 | "subtype" : "retina4", 14 | "extent" : "full-screen", 15 | "minimum-system-version" : "7.0", 16 | "scale" : "2x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /TBAnnotationClustering/TBAnnotationClustering-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIdentifier 12 | thoughtbot.${PRODUCT_NAME:rfc1034identifier} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1.0 25 | LSRequiresIPhoneOS 26 | 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /TBAnnotationClustering/TBAnnotationClustering-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #import 8 | 9 | #ifndef __IPHONE_3_0 10 | #warning "This project uses features only available in iOS SDK 3.0 and later." 11 | #endif 12 | 13 | #ifdef __OBJC__ 14 | #import 15 | #import 16 | #import 17 | #endif 18 | -------------------------------------------------------------------------------- /TBAnnotationClustering/TBAppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // TBAppDelegate.h 3 | // TBAnnotationClustering 4 | // 5 | // Created by Theodore Calmes on 9/27/13. 6 | // Copyright (c) 2013 Theodore Calmes. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TBAppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /TBAnnotationClustering/TBAppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // TBAppDelegate.m 3 | // TBAnnotationClustering 4 | // 5 | // Created by Theodore Calmes on 9/27/13. 6 | // Copyright (c) 2013 Theodore Calmes. All rights reserved. 7 | // 8 | 9 | #import "TBAppDelegate.h" 10 | #import "TBMapViewController.h" 11 | 12 | @implementation TBAppDelegate 13 | 14 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 15 | { 16 | self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; 17 | // Override point for customization after application launch. 18 | self.window.backgroundColor = [UIColor whiteColor]; 19 | [self.window makeKeyAndVisible]; 20 | 21 | TBMapViewController *mapController = [[TBMapViewController alloc] init]; 22 | [self.window setRootViewController:mapController]; 23 | 24 | return YES; 25 | } 26 | 27 | - (void)applicationWillResignActive:(UIApplication *)application 28 | { 29 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 30 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 31 | } 32 | 33 | - (void)applicationDidEnterBackground:(UIApplication *)application 34 | { 35 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 36 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 37 | } 38 | 39 | - (void)applicationWillEnterForeground:(UIApplication *)application 40 | { 41 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 42 | } 43 | 44 | - (void)applicationDidBecomeActive:(UIApplication *)application 45 | { 46 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 47 | } 48 | 49 | - (void)applicationWillTerminate:(UIApplication *)application 50 | { 51 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 52 | } 53 | 54 | @end 55 | -------------------------------------------------------------------------------- /TBAnnotationClustering/TBClusterAnnotation.h: -------------------------------------------------------------------------------- 1 | // 2 | // TBClusterAnnotation.h 3 | // TBAnnotationClustering 4 | // 5 | // Created by Theodore Calmes on 10/8/13. 6 | // Copyright (c) 2013 Theodore Calmes. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TBClusterAnnotation : NSObject 12 | 13 | @property (assign, nonatomic) CLLocationCoordinate2D coordinate; 14 | @property (copy, nonatomic) NSString *title; 15 | @property (copy, nonatomic) NSString *subtitle; 16 | @property (assign, nonatomic) NSInteger count; 17 | 18 | - (id)initWithCoordinate:(CLLocationCoordinate2D)coordinate count:(NSInteger)count; 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /TBAnnotationClustering/TBClusterAnnotation.m: -------------------------------------------------------------------------------- 1 | // 2 | // TBClusterAnnotation.m 3 | // TBAnnotationClustering 4 | // 5 | // Created by Theodore Calmes on 10/8/13. 6 | // Copyright (c) 2013 Theodore Calmes. All rights reserved. 7 | // 8 | 9 | #import "TBClusterAnnotation.h" 10 | 11 | @implementation TBClusterAnnotation 12 | 13 | - (id)initWithCoordinate:(CLLocationCoordinate2D)coordinate count:(NSInteger)count 14 | { 15 | self = [super init]; 16 | if (self) { 17 | _coordinate = coordinate; 18 | _title = [NSString stringWithFormat:@"%d hotels in this area", count]; 19 | _count = count; 20 | } 21 | return self; 22 | } 23 | 24 | - (NSUInteger)hash 25 | { 26 | NSString *toHash = [NSString stringWithFormat:@"%.5F%.5F", self.coordinate.latitude, self.coordinate.longitude]; 27 | return [toHash hash]; 28 | } 29 | 30 | - (BOOL)isEqual:(id)object 31 | { 32 | return [self hash] == [object hash]; 33 | } 34 | 35 | @end 36 | -------------------------------------------------------------------------------- /TBAnnotationClustering/TBClusterAnnotationView.h: -------------------------------------------------------------------------------- 1 | // 2 | // TBClusterAnnotationView.h 3 | // TBAnnotationClustering 4 | // 5 | // Created by Theodore Calmes on 10/4/13. 6 | // Copyright (c) 2013 Theodore Calmes. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TBClusterAnnotationView : MKAnnotationView 12 | 13 | @property (assign, nonatomic) NSUInteger count; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /TBAnnotationClustering/TBClusterAnnotationView.m: -------------------------------------------------------------------------------- 1 | // 2 | // TBClusterAnnotationView.m 3 | // TBAnnotationClustering 4 | // 5 | // Created by Theodore Calmes on 10/4/13. 6 | // Copyright (c) 2013 Theodore Calmes. All rights reserved. 7 | // 8 | 9 | #import "TBClusterAnnotationView.h" 10 | 11 | CGPoint TBRectCenter(CGRect rect) 12 | { 13 | return CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect)); 14 | } 15 | 16 | CGRect TBCenterRect(CGRect rect, CGPoint center) 17 | { 18 | CGRect r = CGRectMake(center.x - rect.size.width/2.0, 19 | center.y - rect.size.height/2.0, 20 | rect.size.width, 21 | rect.size.height); 22 | return r; 23 | } 24 | 25 | static CGFloat const TBScaleFactorAlpha = 0.3; 26 | static CGFloat const TBScaleFactorBeta = 0.4; 27 | 28 | CGFloat TBScaledValueForValue(CGFloat value) 29 | { 30 | return 1.0 / (1.0 + expf(-1 * TBScaleFactorAlpha * powf(value, TBScaleFactorBeta))); 31 | } 32 | 33 | @interface TBClusterAnnotationView () 34 | @property (strong, nonatomic) UILabel *countLabel; 35 | @end 36 | 37 | @implementation TBClusterAnnotationView 38 | 39 | - (id)initWithAnnotation:(id)annotation reuseIdentifier:(NSString *)reuseIdentifier 40 | { 41 | self = [super initWithAnnotation:annotation reuseIdentifier:reuseIdentifier]; 42 | if (self) { 43 | self.backgroundColor = [UIColor clearColor]; 44 | [self setupLabel]; 45 | [self setCount:1]; 46 | } 47 | return self; 48 | } 49 | 50 | - (void)setupLabel 51 | { 52 | _countLabel = [[UILabel alloc] initWithFrame:self.frame]; 53 | _countLabel.backgroundColor = [UIColor clearColor]; 54 | _countLabel.textColor = [UIColor whiteColor]; 55 | _countLabel.textAlignment = NSTextAlignmentCenter; 56 | _countLabel.shadowColor = [UIColor colorWithWhite:0.0 alpha:0.75]; 57 | _countLabel.shadowOffset = CGSizeMake(0, -1); 58 | _countLabel.adjustsFontSizeToFitWidth = YES; 59 | _countLabel.numberOfLines = 1; 60 | _countLabel.font = [UIFont boldSystemFontOfSize:12]; 61 | _countLabel.baselineAdjustment = UIBaselineAdjustmentAlignCenters; 62 | [self addSubview:_countLabel]; 63 | } 64 | 65 | - (void)setCount:(NSUInteger)count 66 | { 67 | _count = count; 68 | 69 | CGRect newBounds = CGRectMake(0, 0, roundf(44 * TBScaledValueForValue(count)), roundf(44 * TBScaledValueForValue(count))); 70 | self.frame = TBCenterRect(newBounds, self.center); 71 | 72 | CGRect newLabelBounds = CGRectMake(0, 0, newBounds.size.width / 1.3, newBounds.size.height / 1.3); 73 | self.countLabel.frame = TBCenterRect(newLabelBounds, TBRectCenter(newBounds)); 74 | self.countLabel.text = [@(_count) stringValue]; 75 | 76 | [self setNeedsDisplay]; 77 | } 78 | 79 | - (void)drawRect:(CGRect)rect 80 | { 81 | CGContextRef context = UIGraphicsGetCurrentContext(); 82 | 83 | CGContextSetAllowsAntialiasing(context, true); 84 | 85 | UIColor *outerCircleStrokeColor = [UIColor colorWithWhite:0 alpha:0.25]; 86 | UIColor *innerCircleStrokeColor = [UIColor whiteColor]; 87 | UIColor *innerCircleFillColor = [UIColor colorWithRed:(255.0 / 255.0) green:(95 / 255.0) blue:(42 / 255.0) alpha:1.0]; 88 | 89 | CGRect circleFrame = CGRectInset(rect, 4, 4); 90 | 91 | [outerCircleStrokeColor setStroke]; 92 | CGContextSetLineWidth(context, 5.0); 93 | CGContextStrokeEllipseInRect(context, circleFrame); 94 | 95 | [innerCircleStrokeColor setStroke]; 96 | CGContextSetLineWidth(context, 4); 97 | CGContextStrokeEllipseInRect(context, circleFrame); 98 | 99 | [innerCircleFillColor setFill]; 100 | CGContextFillEllipseInRect(context, circleFrame); 101 | } 102 | 103 | @end 104 | -------------------------------------------------------------------------------- /TBAnnotationClustering/TBCoordinateQuadTree.h: -------------------------------------------------------------------------------- 1 | // 2 | // TBCoordinateQuadTree.h 3 | // TBAnnotationClustering 4 | // 5 | // Created by Theodore Calmes on 9/27/13. 6 | // Copyright (c) 2013 Theodore Calmes. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "TBQuadTree.h" 11 | 12 | @interface TBCoordinateQuadTree : NSObject 13 | 14 | @property (assign, nonatomic) TBQuadTreeNode* root; 15 | @property (strong, nonatomic) MKMapView *mapView; 16 | 17 | - (void)buildTree; 18 | - (NSArray *)clusteredAnnotationsWithinMapRect:(MKMapRect)rect withZoomScale:(double)zoomScale; 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /TBAnnotationClustering/TBCoordinateQuadTree.m: -------------------------------------------------------------------------------- 1 | // 2 | // TBCoordinateQuadTree.m 3 | // TBAnnotationClustering 4 | // 5 | // Created by Theodore Calmes on 9/27/13. 6 | // Copyright (c) 2013 Theodore Calmes. All rights reserved. 7 | // 8 | 9 | #import "TBCoordinateQuadTree.h" 10 | #import "TBClusterAnnotation.h" 11 | 12 | typedef struct TBHotelInfo { 13 | char* hotelName; 14 | char* hotelPhoneNumber; 15 | } TBHotelInfo; 16 | 17 | TBQuadTreeNodeData TBDataFromLine(NSString *line) 18 | { 19 | NSArray *components = [line componentsSeparatedByString:@","]; 20 | double latitude = [components[1] doubleValue]; 21 | double longitude = [components[0] doubleValue]; 22 | 23 | TBHotelInfo* hotelInfo = malloc(sizeof(TBHotelInfo)); 24 | 25 | NSString *hotelName = [components[2] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; 26 | hotelInfo->hotelName = malloc(sizeof(char) * hotelName.length + 1); 27 | strncpy(hotelInfo->hotelName, [hotelName UTF8String], hotelName.length + 1); 28 | 29 | NSString *hotelPhoneNumber = [[components lastObject] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; 30 | hotelInfo->hotelPhoneNumber = malloc(sizeof(char) * hotelPhoneNumber.length + 1); 31 | strncpy(hotelInfo->hotelPhoneNumber, [hotelPhoneNumber UTF8String], hotelPhoneNumber.length + 1); 32 | 33 | return TBQuadTreeNodeDataMake(latitude, longitude, hotelInfo); 34 | } 35 | 36 | TBBoundingBox TBBoundingBoxForMapRect(MKMapRect mapRect) 37 | { 38 | CLLocationCoordinate2D topLeft = MKCoordinateForMapPoint(mapRect.origin); 39 | CLLocationCoordinate2D botRight = MKCoordinateForMapPoint(MKMapPointMake(MKMapRectGetMaxX(mapRect), MKMapRectGetMaxY(mapRect))); 40 | 41 | CLLocationDegrees minLat = botRight.latitude; 42 | CLLocationDegrees maxLat = topLeft.latitude; 43 | 44 | CLLocationDegrees minLon = topLeft.longitude; 45 | CLLocationDegrees maxLon = botRight.longitude; 46 | 47 | return TBBoundingBoxMake(minLat, minLon, maxLat, maxLon); 48 | } 49 | 50 | MKMapRect TBMapRectForBoundingBox(TBBoundingBox boundingBox) 51 | { 52 | MKMapPoint topLeft = MKMapPointForCoordinate(CLLocationCoordinate2DMake(boundingBox.x0, boundingBox.y0)); 53 | MKMapPoint botRight = MKMapPointForCoordinate(CLLocationCoordinate2DMake(boundingBox.xf, boundingBox.yf)); 54 | 55 | return MKMapRectMake(topLeft.x, botRight.y, fabs(botRight.x - topLeft.x), fabs(botRight.y - topLeft.y)); 56 | } 57 | 58 | NSInteger TBZoomScaleToZoomLevel(MKZoomScale scale) 59 | { 60 | double totalTilesAtMaxZoom = MKMapSizeWorld.width / 256.0; 61 | NSInteger zoomLevelAtMaxZoom = log2(totalTilesAtMaxZoom); 62 | NSInteger zoomLevel = MAX(0, zoomLevelAtMaxZoom + floor(log2f(scale) + 0.5)); 63 | 64 | return zoomLevel; 65 | } 66 | 67 | float TBCellSizeForZoomScale(MKZoomScale zoomScale) 68 | { 69 | NSInteger zoomLevel = TBZoomScaleToZoomLevel(zoomScale); 70 | 71 | switch (zoomLevel) { 72 | case 13: 73 | case 14: 74 | case 15: 75 | return 64; 76 | case 16: 77 | case 17: 78 | case 18: 79 | return 32; 80 | case 19: 81 | return 16; 82 | 83 | default: 84 | return 88; 85 | } 86 | } 87 | 88 | @implementation TBCoordinateQuadTree 89 | 90 | - (void)buildTree 91 | { 92 | @autoreleasepool { 93 | NSString *data = [NSString stringWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"USA-HotelMotel" ofType:@"csv"] encoding:NSASCIIStringEncoding error:nil]; 94 | NSArray *lines = [data componentsSeparatedByString:@"\n"]; 95 | 96 | NSInteger count = lines.count - 1; 97 | 98 | TBQuadTreeNodeData *dataArray = malloc(sizeof(TBQuadTreeNodeData) * count); 99 | for (NSInteger i = 0; i < count; i++) { 100 | dataArray[i] = TBDataFromLine(lines[i]); 101 | } 102 | 103 | TBBoundingBox world = TBBoundingBoxMake(19, -166, 72, -53); 104 | _root = TBQuadTreeBuildWithData(dataArray, count, world, 4); 105 | } 106 | } 107 | 108 | - (NSArray *)clusteredAnnotationsWithinMapRect:(MKMapRect)rect withZoomScale:(double)zoomScale 109 | { 110 | double TBCellSize = TBCellSizeForZoomScale(zoomScale); 111 | double scaleFactor = zoomScale / TBCellSize; 112 | 113 | NSInteger minX = floor(MKMapRectGetMinX(rect) * scaleFactor); 114 | NSInteger maxX = floor(MKMapRectGetMaxX(rect) * scaleFactor); 115 | NSInteger minY = floor(MKMapRectGetMinY(rect) * scaleFactor); 116 | NSInteger maxY = floor(MKMapRectGetMaxY(rect) * scaleFactor); 117 | 118 | NSMutableArray *clusteredAnnotations = [[NSMutableArray alloc] init]; 119 | for (NSInteger x = minX; x <= maxX; x++) { 120 | for (NSInteger y = minY; y <= maxY; y++) { 121 | MKMapRect mapRect = MKMapRectMake(x / scaleFactor, y / scaleFactor, 1.0 / scaleFactor, 1.0 / scaleFactor); 122 | 123 | __block double totalX = 0; 124 | __block double totalY = 0; 125 | __block int count = 0; 126 | 127 | NSMutableArray *names = [[NSMutableArray alloc] init]; 128 | NSMutableArray *phoneNumbers = [[NSMutableArray alloc] init]; 129 | 130 | TBQuadTreeGatherDataInRange(self.root, TBBoundingBoxForMapRect(mapRect), ^(TBQuadTreeNodeData data) { 131 | totalX += data.x; 132 | totalY += data.y; 133 | count++; 134 | 135 | TBHotelInfo hotelInfo = *(TBHotelInfo *)data.data; 136 | [names addObject:[NSString stringWithFormat:@"%s", hotelInfo.hotelName]]; 137 | [phoneNumbers addObject:[NSString stringWithFormat:@"%s", hotelInfo.hotelPhoneNumber]]; 138 | }); 139 | 140 | if (count == 1) { 141 | CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(totalX, totalY); 142 | TBClusterAnnotation *annotation = [[TBClusterAnnotation alloc] initWithCoordinate:coordinate count:count]; 143 | annotation.title = [names lastObject]; 144 | annotation.subtitle = [phoneNumbers lastObject]; 145 | [clusteredAnnotations addObject:annotation]; 146 | } 147 | 148 | if (count > 1) { 149 | CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(totalX / count, totalY / count); 150 | TBClusterAnnotation *annotation = [[TBClusterAnnotation alloc] initWithCoordinate:coordinate count:count]; 151 | [clusteredAnnotations addObject:annotation]; 152 | } 153 | } 154 | } 155 | 156 | return [NSArray arrayWithArray:clusteredAnnotations]; 157 | } 158 | 159 | @end 160 | -------------------------------------------------------------------------------- /TBAnnotationClustering/TBMapViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // TBMapViewController.h 3 | // TBAnnotationClustering 4 | // 5 | // Created by Theodore Calmes on 9/27/13. 6 | // Copyright (c) 2013 Theodore Calmes. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TBMapViewController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /TBAnnotationClustering/TBMapViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // TBMapViewController.m 3 | // TBAnnotationClustering 4 | // 5 | // Created by Theodore Calmes on 9/27/13. 6 | // Copyright (c) 2013 Theodore Calmes. All rights reserved. 7 | // 8 | 9 | #import "TBMapViewController.h" 10 | #import "TBCoordinateQuadTree.h" 11 | #import "TBClusterAnnotationView.h" 12 | #import "TBClusterAnnotation.h" 13 | 14 | @interface TBMapViewController () 15 | @property (strong, nonatomic) MKMapView *mapView; 16 | @property (strong, nonatomic) TBCoordinateQuadTree *coordinateQuadTree; 17 | @end 18 | 19 | @implementation TBMapViewController 20 | 21 | - (void)viewDidLoad 22 | { 23 | [super viewDidLoad]; 24 | self.mapView = [[MKMapView alloc] initWithFrame:self.view.bounds]; 25 | self.mapView.delegate = self; 26 | 27 | [self.view addSubview:self.mapView]; 28 | 29 | self.coordinateQuadTree = [[TBCoordinateQuadTree alloc] init]; 30 | self.coordinateQuadTree.mapView = self.mapView; 31 | [self.coordinateQuadTree buildTree]; 32 | } 33 | 34 | - (void)addBounceAnnimationToView:(UIView *)view 35 | { 36 | CAKeyframeAnimation *bounceAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale"]; 37 | 38 | bounceAnimation.values = @[@(0.05), @(1.1), @(0.9), @(1)]; 39 | 40 | bounceAnimation.duration = 0.6; 41 | NSMutableArray *timingFunctions = [[NSMutableArray alloc] initWithCapacity:bounceAnimation.values.count]; 42 | for (NSUInteger i = 0; i < bounceAnimation.values.count; i++) { 43 | [timingFunctions addObject:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]]; 44 | } 45 | [bounceAnimation setTimingFunctions:timingFunctions.copy]; 46 | bounceAnimation.removedOnCompletion = NO; 47 | 48 | [view.layer addAnimation:bounceAnimation forKey:@"bounce"]; 49 | } 50 | 51 | - (void)updateMapViewAnnotationsWithAnnotations:(NSArray *)annotations 52 | { 53 | NSMutableSet *before = [NSMutableSet setWithArray:self.mapView.annotations]; 54 | [before removeObject:[self.mapView userLocation]]; 55 | NSSet *after = [NSSet setWithArray:annotations]; 56 | 57 | NSMutableSet *toKeep = [NSMutableSet setWithSet:before]; 58 | [toKeep intersectSet:after]; 59 | 60 | NSMutableSet *toAdd = [NSMutableSet setWithSet:after]; 61 | [toAdd minusSet:toKeep]; 62 | 63 | NSMutableSet *toRemove = [NSMutableSet setWithSet:before]; 64 | [toRemove minusSet:after]; 65 | 66 | [[NSOperationQueue mainQueue] addOperationWithBlock:^{ 67 | [self.mapView addAnnotations:[toAdd allObjects]]; 68 | [self.mapView removeAnnotations:[toRemove allObjects]]; 69 | }]; 70 | } 71 | 72 | #pragma mark - MKMapViewDelegate 73 | 74 | - (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated 75 | { 76 | [[NSOperationQueue new] addOperationWithBlock:^{ 77 | double scale = self.mapView.bounds.size.width / self.mapView.visibleMapRect.size.width; 78 | NSArray *annotations = [self.coordinateQuadTree clusteredAnnotationsWithinMapRect:mapView.visibleMapRect withZoomScale:scale]; 79 | 80 | [self updateMapViewAnnotationsWithAnnotations:annotations]; 81 | }]; 82 | } 83 | 84 | - (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id)annotation 85 | { 86 | static NSString *const TBAnnotatioViewReuseID = @"TBAnnotatioViewReuseID"; 87 | 88 | TBClusterAnnotationView *annotationView = (TBClusterAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:TBAnnotatioViewReuseID]; 89 | 90 | if (!annotationView) { 91 | annotationView = [[TBClusterAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:TBAnnotatioViewReuseID]; 92 | } 93 | 94 | annotationView.canShowCallout = YES; 95 | annotationView.count = [(TBClusterAnnotation *)annotation count]; 96 | 97 | return annotationView; 98 | } 99 | 100 | - (void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)views 101 | { 102 | for (UIView *view in views) { 103 | [self addBounceAnnimationToView:view]; 104 | } 105 | } 106 | 107 | @end 108 | -------------------------------------------------------------------------------- /TBAnnotationClustering/TBQuadTree.h: -------------------------------------------------------------------------------- 1 | // 2 | // TBQuadTree.h 3 | // TBQuadTree 4 | // 5 | // Created by Theodore Calmes on 9/19/13. 6 | // Copyright (c) 2013 Theodore Calmes. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | typedef struct TBQuadTreeNodeData { 12 | double x; 13 | double y; 14 | void* data; 15 | } TBQuadTreeNodeData; 16 | TBQuadTreeNodeData TBQuadTreeNodeDataMake(double x, double y, void* data); 17 | 18 | typedef struct TBBoundingBox { 19 | double x0; double y0; 20 | double xf; double yf; 21 | } TBBoundingBox; 22 | TBBoundingBox TBBoundingBoxMake(double x0, double y0, double xf, double yf); 23 | 24 | typedef struct quadTreeNode { 25 | struct quadTreeNode* northWest; 26 | struct quadTreeNode* northEast; 27 | struct quadTreeNode* southWest; 28 | struct quadTreeNode* southEast; 29 | TBBoundingBox boundingBox; 30 | int bucketCapacity; 31 | TBQuadTreeNodeData *points; 32 | int count; 33 | } TBQuadTreeNode; 34 | TBQuadTreeNode* TBQuadTreeNodeMake(TBBoundingBox boundary, int bucketCapacity); 35 | 36 | void TBFreeQuadTreeNode(TBQuadTreeNode* node); 37 | 38 | bool TBBoundingBoxContainsData(TBBoundingBox box, TBQuadTreeNodeData data); 39 | bool TBBoundingBoxIntersectsBoundingBox(TBBoundingBox b1, TBBoundingBox b2); 40 | 41 | typedef void(^TBQuadTreeTraverseBlock)(TBQuadTreeNode* currentNode); 42 | void TBQuadTreeTraverse(TBQuadTreeNode* node, TBQuadTreeTraverseBlock block); 43 | 44 | typedef void(^TBDataReturnBlock)(TBQuadTreeNodeData data); 45 | void TBQuadTreeGatherDataInRange(TBQuadTreeNode* node, TBBoundingBox range, TBDataReturnBlock block); 46 | 47 | bool TBQuadTreeNodeInsertData(TBQuadTreeNode* node, TBQuadTreeNodeData data); 48 | TBQuadTreeNode* TBQuadTreeBuildWithData(TBQuadTreeNodeData *data, int count, TBBoundingBox boundingBox, int capacity); 49 | -------------------------------------------------------------------------------- /TBAnnotationClustering/TBQuadTree.m: -------------------------------------------------------------------------------- 1 | // 2 | // TBQuadTree.m 3 | // TBQuadTree 4 | // 5 | // Created by Theodore Calmes on 9/19/13. 6 | // Copyright (c) 2013 Theodore Calmes. All rights reserved. 7 | // 8 | 9 | #import "TBQuadTree.h" 10 | 11 | #pragma mark - Constructors 12 | 13 | TBQuadTreeNodeData TBQuadTreeNodeDataMake(double x, double y, void* data) 14 | { 15 | TBQuadTreeNodeData d; d.x = x; d.y = y; d.data = data; 16 | return d; 17 | } 18 | 19 | TBBoundingBox TBBoundingBoxMake(double x0, double y0, double xf, double yf) 20 | { 21 | TBBoundingBox bb; bb.x0 = x0; bb.y0 = y0; bb.xf = xf; bb.yf = yf; 22 | return bb; 23 | } 24 | 25 | TBQuadTreeNode* TBQuadTreeNodeMake(TBBoundingBox boundary, int bucketCapacity) 26 | { 27 | TBQuadTreeNode* node = malloc(sizeof(TBQuadTreeNode)); 28 | node->northWest = NULL; 29 | node->northEast = NULL; 30 | node->southWest = NULL; 31 | node->southEast = NULL; 32 | 33 | node->boundingBox = boundary; 34 | node->bucketCapacity = bucketCapacity; 35 | node->count = 0; 36 | node->points = malloc(sizeof(TBQuadTreeNodeData) * bucketCapacity); 37 | 38 | return node; 39 | } 40 | 41 | #pragma mark - Bounding Box Functions 42 | 43 | bool TBBoundingBoxContainsData(TBBoundingBox box, TBQuadTreeNodeData data) 44 | { 45 | bool containsX = box.x0 <= data.x && data.x <= box.xf; 46 | bool containsY = box.y0 <= data.y && data.y <= box.yf; 47 | 48 | return containsX && containsY; 49 | } 50 | 51 | bool TBBoundingBoxIntersectsBoundingBox(TBBoundingBox b1, TBBoundingBox b2) 52 | { 53 | return (b1.x0 <= b2.xf && b1.xf >= b2.x0 && b1.y0 <= b2.yf && b1.yf >= b2.y0); 54 | } 55 | 56 | #pragma mark - Quad Tree Functions 57 | 58 | void TBQuadTreeNodeSubdivide(TBQuadTreeNode* node) 59 | { 60 | TBBoundingBox box = node->boundingBox; 61 | 62 | double xMid = (box.xf + box.x0) / 2.0; 63 | double yMid = (box.yf + box.y0) / 2.0; 64 | 65 | TBBoundingBox northWest = TBBoundingBoxMake(box.x0, box.y0, xMid, yMid); 66 | node->northWest = TBQuadTreeNodeMake(northWest, node->bucketCapacity); 67 | 68 | TBBoundingBox northEast = TBBoundingBoxMake(xMid, box.y0, box.xf, yMid); 69 | node->northEast = TBQuadTreeNodeMake(northEast, node->bucketCapacity); 70 | 71 | TBBoundingBox southWest = TBBoundingBoxMake(box.x0, yMid, xMid, box.yf); 72 | node->southWest = TBQuadTreeNodeMake(southWest, node->bucketCapacity); 73 | 74 | TBBoundingBox southEast = TBBoundingBoxMake(xMid, yMid, box.xf, box.yf); 75 | node->southEast = TBQuadTreeNodeMake(southEast, node->bucketCapacity); 76 | } 77 | 78 | bool TBQuadTreeNodeInsertData(TBQuadTreeNode* node, TBQuadTreeNodeData data) 79 | { 80 | if (!TBBoundingBoxContainsData(node->boundingBox, data)) { 81 | return false; 82 | } 83 | 84 | if (node->count < node->bucketCapacity) { 85 | node->points[node->count++] = data; 86 | return true; 87 | } 88 | 89 | if (node->northWest == NULL) { 90 | TBQuadTreeNodeSubdivide(node); 91 | } 92 | 93 | if (TBQuadTreeNodeInsertData(node->northWest, data)) return true; 94 | if (TBQuadTreeNodeInsertData(node->northEast, data)) return true; 95 | if (TBQuadTreeNodeInsertData(node->southWest, data)) return true; 96 | if (TBQuadTreeNodeInsertData(node->southEast, data)) return true; 97 | 98 | return false; 99 | } 100 | 101 | void TBQuadTreeGatherDataInRange(TBQuadTreeNode* node, TBBoundingBox range, TBDataReturnBlock block) 102 | { 103 | if (!TBBoundingBoxIntersectsBoundingBox(node->boundingBox, range)) { 104 | return; 105 | } 106 | 107 | for (int i = 0; i < node->count; i++) { 108 | if (TBBoundingBoxContainsData(range, node->points[i])) { 109 | block(node->points[i]); 110 | } 111 | } 112 | 113 | if (node->northWest == NULL) { 114 | return; 115 | } 116 | 117 | TBQuadTreeGatherDataInRange(node->northWest, range, block); 118 | TBQuadTreeGatherDataInRange(node->northEast, range, block); 119 | TBQuadTreeGatherDataInRange(node->southWest, range, block); 120 | TBQuadTreeGatherDataInRange(node->southEast, range, block); 121 | } 122 | 123 | void TBQuadTreeTraverse(TBQuadTreeNode* node, TBQuadTreeTraverseBlock block) 124 | { 125 | block(node); 126 | 127 | if (node->northWest == NULL) { 128 | return; 129 | } 130 | 131 | TBQuadTreeTraverse(node->northWest, block); 132 | TBQuadTreeTraverse(node->northEast, block); 133 | TBQuadTreeTraverse(node->southWest, block); 134 | TBQuadTreeTraverse(node->southEast, block); 135 | } 136 | 137 | TBQuadTreeNode* TBQuadTreeBuildWithData(TBQuadTreeNodeData *data, int count, TBBoundingBox boundingBox, int capacity) 138 | { 139 | TBQuadTreeNode* root = TBQuadTreeNodeMake(boundingBox, capacity); 140 | for (int i = 0; i < count; i++) { 141 | TBQuadTreeNodeInsertData(root, data[i]); 142 | } 143 | 144 | return root; 145 | } 146 | 147 | void TBFreeQuadTreeNode(TBQuadTreeNode* node) 148 | { 149 | if (node->northWest != NULL) TBFreeQuadTreeNode(node->northWest); 150 | if (node->northEast != NULL) TBFreeQuadTreeNode(node->northEast); 151 | if (node->southWest != NULL) TBFreeQuadTreeNode(node->southWest); 152 | if (node->southEast != NULL) TBFreeQuadTreeNode(node->southEast); 153 | 154 | for (int i=0; i < node->count; i++) { 155 | free(node->points[i].data); 156 | } 157 | free(node->points); 158 | free(node); 159 | } 160 | -------------------------------------------------------------------------------- /TBAnnotationClustering/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /TBAnnotationClustering/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // TBAnnotationClustering 4 | // 5 | // Created by Theodore Calmes on 9/27/13. 6 | // Copyright (c) 2013 Theodore Calmes. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "TBAppDelegate.h" 12 | 13 | int main(int argc, char * argv[]) 14 | { 15 | @autoreleasepool { 16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([TBAppDelegate class])); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /TBAnnotationClusteringTests/TBAnnotationClusteringTests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | thoughtbot.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /TBAnnotationClusteringTests/TBAnnotationClusteringTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // TBAnnotationClusteringTests.m 3 | // TBAnnotationClusteringTests 4 | // 5 | // Created by Theodore Calmes on 9/27/13. 6 | // Copyright (c) 2013 Theodore Calmes. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TBAnnotationClusteringTests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation TBAnnotationClusteringTests 16 | 17 | - (void)setUp 18 | { 19 | [super setUp]; 20 | // Put setup code here. This method is called before the invocation of each test method in the class. 21 | } 22 | 23 | - (void)tearDown 24 | { 25 | // Put teardown code here. This method is called after the invocation of each test method in the class. 26 | [super tearDown]; 27 | } 28 | 29 | - (void)testExample 30 | { 31 | XCTFail(@"No implementation for \"%s\"", __PRETTY_FUNCTION__); 32 | } 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /TBAnnotationClusteringTests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | --------------------------------------------------------------------------------