├── MapClustering.png ├── CCHMapClusterController Example OS X ├── CCHMapClusterController Example OS X │ ├── en.lproj │ │ ├── InfoPlist.strings │ │ └── Credits.rtf │ ├── CCHMapClusterController Example OS X-Prefix.pch │ ├── main.m │ ├── AppDelegate.h │ ├── Images.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── CCHMapClusterController Example OS X-Info.plist │ └── AppDelegate.m ├── CCHMapClusterController Example OS XTests │ ├── en.lproj │ │ └── InfoPlist.strings │ └── CCHMapClusterController Example OS XTests-Info.plist └── CCHMapClusterController Example OS X.xcodeproj │ ├── project.xcworkspace │ └── contents.xcworkspacedata │ └── xcshareddata │ └── xcschemes │ └── CCHMapClusterController Example OS X.xcscheme ├── CCHMapClusterController Example iOS ├── CCHMapClusterController Example iOS │ ├── en.lproj │ │ └── InfoPlist.strings │ ├── Images.xcassets │ │ ├── SquareRed.imageset │ │ │ ├── square21.png │ │ │ ├── square21-s.png │ │ │ └── Contents.json │ │ ├── CircleBlue21.imageset │ │ │ ├── circle21.png │ │ │ ├── circle21@2x.png │ │ │ └── Contents.json │ │ ├── CircleBlue24.imageset │ │ │ ├── circle24.png │ │ │ ├── circle24@2x.png │ │ │ └── Contents.json │ │ ├── CircleBlue25.imageset │ │ │ ├── circle25.png │ │ │ ├── circle25@2x.png │ │ │ └── Contents.json │ │ ├── CircleBlue28.imageset │ │ │ ├── circle28.png │ │ │ ├── circle28@2x.png │ │ │ └── Contents.json │ │ ├── CircleBlue31.imageset │ │ │ ├── circle31.png │ │ │ ├── circle31@2x.png │ │ │ └── Contents.json │ │ ├── CircleBlue34.imageset │ │ │ ├── circle34.png │ │ │ ├── circle34@2x.png │ │ │ └── Contents.json │ │ ├── CircleBlue36.imageset │ │ │ ├── circle36.png │ │ │ ├── circle36@2x.png │ │ │ └── Contents.json │ │ ├── CircleBlue38.imageset │ │ │ ├── circle38.png │ │ │ ├── circle38@2x.png │ │ │ └── Contents.json │ │ ├── CircleBlue39.imageset │ │ │ ├── circle39.png │ │ │ ├── circle39@2x.png │ │ │ └── Contents.json │ │ ├── CircleRed21.imageset │ │ │ ├── circle21.png │ │ │ ├── circle21@2x.png │ │ │ └── Contents.json │ │ ├── CircleRed24.imageset │ │ │ ├── circle24.png │ │ │ ├── circle24@2x.png │ │ │ └── Contents.json │ │ ├── CircleRed25.imageset │ │ │ ├── circle25.png │ │ │ ├── circle25@2x.png │ │ │ └── Contents.json │ │ ├── CircleRed28.imageset │ │ │ ├── circle28.png │ │ │ ├── circle28@2x.png │ │ │ └── Contents.json │ │ ├── CircleRed31.imageset │ │ │ ├── circle31.png │ │ │ ├── circle31@2x.png │ │ │ └── Contents.json │ │ ├── CircleRed34.imageset │ │ │ ├── circle34.png │ │ │ ├── circle34@2x.png │ │ │ └── Contents.json │ │ ├── CircleRed36.imageset │ │ │ ├── circle36.png │ │ │ ├── circle36@2x.png │ │ │ └── Contents.json │ │ ├── CircleRed38.imageset │ │ │ ├── circle38.png │ │ │ ├── circle38@2x.png │ │ │ └── Contents.json │ │ ├── CircleRed39.imageset │ │ │ ├── circle39.png │ │ │ ├── circle39@2x.png │ │ │ └── Contents.json │ │ ├── SquareBlue.imageset │ │ │ ├── Marker-Square2.png │ │ │ ├── Marker-Square2@2x.png │ │ │ └── Contents.json │ │ ├── LaunchImage.launchimage │ │ │ └── Contents.json │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── MapViewController.h │ ├── AppDelegate.h │ ├── DataReaderDelegate.h │ ├── CCHMapClusterController Example iOS-Prefix.pch │ ├── main.m │ ├── SettingsViewController.h │ ├── DataReader.h │ ├── ClusterAnnotationView.h │ ├── Settings.h │ ├── Settings.m │ ├── CCHMapClusterController Example iOS-Info.plist │ ├── Launch Screen.storyboard │ ├── AppDelegate.m │ ├── ClusterAnnotationView.m │ └── DataReader.m ├── CCHMapClusterController Example iOSTests │ ├── en.lproj │ │ └── InfoPlist.strings │ ├── CCHMapClusterController Example iOSTests-Info.plist │ ├── CCHMapViewDelegateProxyViewForOverlayTests.m │ └── CCHMapViewDelegateProxyTests.m └── CCHMapClusterController Example iOS.xcodeproj │ ├── project.xcworkspace │ └── contents.xcworkspacedata │ └── xcshareddata │ └── xcschemes │ └── CCHMapClusterController Example iOS.xcscheme ├── CCHMapClusterController Tests ├── QTree │ ├── QTree-Prefix.pch │ ├── QCluster.m │ ├── QTreeInsertable.h │ ├── QCluster.h │ ├── QTreeGeometryUtils.h │ ├── QTree.h │ ├── QNode.h │ ├── QTree.m │ └── QTreeGeometryUtils.m ├── KPAnnotationTree │ ├── KPTreeNode.m │ ├── KPAnnotationTree.h │ ├── KPTreeNode.h │ ├── NSArray+KP.h │ ├── KPAnnotation.h │ ├── NSArray+KP.m │ ├── KPTreeController.h │ ├── KPAnnotation.m │ └── KPAnnotationTree.m ├── TBQuadTree │ ├── TBQuadTree.h │ └── TBQuadTree.m ├── CCHFadeInOutMapAnimatorTests.m ├── CCHCenterOfMassMapClustererTests.m ├── CCHNearCenterMapClustererTests.m ├── CCHMapClusterAnnotationTests.m ├── CCHMapViewDelegateProxyOverlayTests.m └── CCHMapViewDelegateProxyTests.m ├── .gitignore ├── CCHMapClusterController.xcworkspace ├── xcshareddata │ └── IDEWorkspaceChecks.plist └── contents.xcworkspacedata ├── .travis.yml ├── CCHMapClusterController.podspec ├── LICENSE ├── CCHMapClusterController ├── CCHMapClusterControllerDebugPolygon.m ├── CCHMapClusterControllerDebugPolygon.h ├── CCHMapViewDelegateProxy.h ├── CCHMapTree.h ├── CCHMapClusterer.h ├── CCHNearCenterMapClusterer.h ├── CCHCenterOfMassMapClusterer.h ├── CCHCenterOfMassMapClusterer.m ├── CCHFadeInOutMapAnimator.h ├── CCHNearCenterMapClusterer.m ├── CCHMapAnimator.h ├── CCHMapClusterOperation.h ├── CCHMapClusterAnnotation.h ├── CCHMapClusterControllerDelegate.h ├── CCHMapClusterControllerUtils.h ├── CCHFadeInOutMapAnimator.m ├── CCHMapClusterAnnotation.m ├── CCHMapTreeUtils.h ├── CCHMapClusterController.h ├── CCHMapTree.m └── CCHMapViewDelegateProxy.m └── CHANGES.md /MapClustering.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/choefele/CCHMapClusterController/HEAD/MapClustering.png -------------------------------------------------------------------------------- /CCHMapClusterController Example OS X/CCHMapClusterController Example OS X/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /CCHMapClusterController Example OS X/CCHMapClusterController Example OS XTests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOSTests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /CCHMapClusterController Tests/QTree/QTree-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 | #ifdef __OBJC__ 8 | @import Foundation; 9 | #endif -------------------------------------------------------------------------------- /CCHMapClusterController Tests/QTree/QCluster.m: -------------------------------------------------------------------------------- 1 | // 2 | // This file is subject to the terms and conditions defined in 3 | // file 'LICENSE.md', which is part of this source code package. 4 | // 5 | 6 | #import "QCluster.h" 7 | 8 | @implementation QCluster 9 | 10 | @end 11 | -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/SquareRed.imageset/square21.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/choefele/CCHMapClusterController/HEAD/CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/SquareRed.imageset/square21.png -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleBlue21.imageset/circle21.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/choefele/CCHMapClusterController/HEAD/CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleBlue21.imageset/circle21.png -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleBlue24.imageset/circle24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/choefele/CCHMapClusterController/HEAD/CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleBlue24.imageset/circle24.png -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleBlue25.imageset/circle25.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/choefele/CCHMapClusterController/HEAD/CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleBlue25.imageset/circle25.png -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleBlue28.imageset/circle28.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/choefele/CCHMapClusterController/HEAD/CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleBlue28.imageset/circle28.png -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleBlue31.imageset/circle31.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/choefele/CCHMapClusterController/HEAD/CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleBlue31.imageset/circle31.png -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleBlue34.imageset/circle34.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/choefele/CCHMapClusterController/HEAD/CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleBlue34.imageset/circle34.png -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleBlue36.imageset/circle36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/choefele/CCHMapClusterController/HEAD/CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleBlue36.imageset/circle36.png -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleBlue38.imageset/circle38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/choefele/CCHMapClusterController/HEAD/CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleBlue38.imageset/circle38.png -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleBlue39.imageset/circle39.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/choefele/CCHMapClusterController/HEAD/CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleBlue39.imageset/circle39.png -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleRed21.imageset/circle21.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/choefele/CCHMapClusterController/HEAD/CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleRed21.imageset/circle21.png -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleRed24.imageset/circle24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/choefele/CCHMapClusterController/HEAD/CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleRed24.imageset/circle24.png -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleRed25.imageset/circle25.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/choefele/CCHMapClusterController/HEAD/CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleRed25.imageset/circle25.png -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleRed28.imageset/circle28.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/choefele/CCHMapClusterController/HEAD/CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleRed28.imageset/circle28.png -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleRed31.imageset/circle31.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/choefele/CCHMapClusterController/HEAD/CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleRed31.imageset/circle31.png -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleRed34.imageset/circle34.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/choefele/CCHMapClusterController/HEAD/CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleRed34.imageset/circle34.png -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleRed36.imageset/circle36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/choefele/CCHMapClusterController/HEAD/CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleRed36.imageset/circle36.png -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleRed38.imageset/circle38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/choefele/CCHMapClusterController/HEAD/CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleRed38.imageset/circle38.png -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleRed39.imageset/circle39.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/choefele/CCHMapClusterController/HEAD/CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleRed39.imageset/circle39.png -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/SquareRed.imageset/square21-s.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/choefele/CCHMapClusterController/HEAD/CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/SquareRed.imageset/square21-s.png -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleBlue21.imageset/circle21@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/choefele/CCHMapClusterController/HEAD/CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleBlue21.imageset/circle21@2x.png -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleBlue24.imageset/circle24@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/choefele/CCHMapClusterController/HEAD/CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleBlue24.imageset/circle24@2x.png -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleBlue25.imageset/circle25@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/choefele/CCHMapClusterController/HEAD/CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleBlue25.imageset/circle25@2x.png -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleBlue28.imageset/circle28@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/choefele/CCHMapClusterController/HEAD/CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleBlue28.imageset/circle28@2x.png -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleBlue31.imageset/circle31@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/choefele/CCHMapClusterController/HEAD/CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleBlue31.imageset/circle31@2x.png -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleBlue34.imageset/circle34@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/choefele/CCHMapClusterController/HEAD/CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleBlue34.imageset/circle34@2x.png -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleBlue36.imageset/circle36@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/choefele/CCHMapClusterController/HEAD/CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleBlue36.imageset/circle36@2x.png -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleBlue38.imageset/circle38@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/choefele/CCHMapClusterController/HEAD/CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleBlue38.imageset/circle38@2x.png -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleBlue39.imageset/circle39@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/choefele/CCHMapClusterController/HEAD/CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleBlue39.imageset/circle39@2x.png -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleRed21.imageset/circle21@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/choefele/CCHMapClusterController/HEAD/CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleRed21.imageset/circle21@2x.png -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleRed24.imageset/circle24@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/choefele/CCHMapClusterController/HEAD/CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleRed24.imageset/circle24@2x.png -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleRed25.imageset/circle25@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/choefele/CCHMapClusterController/HEAD/CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleRed25.imageset/circle25@2x.png -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleRed28.imageset/circle28@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/choefele/CCHMapClusterController/HEAD/CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleRed28.imageset/circle28@2x.png -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleRed31.imageset/circle31@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/choefele/CCHMapClusterController/HEAD/CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleRed31.imageset/circle31@2x.png -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleRed34.imageset/circle34@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/choefele/CCHMapClusterController/HEAD/CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleRed34.imageset/circle34@2x.png -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleRed36.imageset/circle36@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/choefele/CCHMapClusterController/HEAD/CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleRed36.imageset/circle36@2x.png -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleRed38.imageset/circle38@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/choefele/CCHMapClusterController/HEAD/CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleRed38.imageset/circle38@2x.png -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleRed39.imageset/circle39@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/choefele/CCHMapClusterController/HEAD/CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleRed39.imageset/circle39@2x.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | .DS_Store 3 | */build/* 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | profile 14 | *.moved-aside 15 | DerivedData 16 | .idea/ 17 | *.hmap 18 | *.xccheckout 19 | -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/SquareBlue.imageset/Marker-Square2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/choefele/CCHMapClusterController/HEAD/CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/SquareBlue.imageset/Marker-Square2.png -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/SquareBlue.imageset/Marker-Square2@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/choefele/CCHMapClusterController/HEAD/CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/SquareBlue.imageset/Marker-Square2@2x.png -------------------------------------------------------------------------------- /CCHMapClusterController Example OS X/CCHMapClusterController Example OS X.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /CCHMapClusterController Example OS X/CCHMapClusterController Example OS X/CCHMapClusterController Example OS X-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 | #ifdef __OBJC__ 8 | #import 9 | #endif 10 | -------------------------------------------------------------------------------- /CCHMapClusterController.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /CCHMapClusterController Tests/QTree/QTreeInsertable.h: -------------------------------------------------------------------------------- 1 | // 2 | // This file is subject to the terms and conditions defined in 3 | // file 'LICENSE.md', which is part of this source code package. 4 | // 5 | 6 | @import CoreLocation; 7 | 8 | @protocol QTreeInsertable 9 | 10 | @property(nonatomic, assign, readonly) CLLocationCoordinate2D coordinate; 11 | 12 | @end 13 | -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/MapViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // CCHMapClusterController Example iOS 4 | // 5 | // Created by Hoefele, Claus(choefele) on 27.11.13. 6 | // Copyright (c) 2013 Claus Höfele. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface MapViewController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /CCHMapClusterController Example OS X/CCHMapClusterController Example OS X/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // CCHMapClusterController Example OS X 4 | // 5 | // Created by Hoefele, Claus(choefele) on 27.11.13. 6 | // Copyright (c) 2013 Claus Höfele. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | int main(int argc, const char * argv[]) 12 | { 13 | return NSApplicationMain(argc, argv); 14 | } 15 | -------------------------------------------------------------------------------- /CCHMapClusterController.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // CCHMapClusterController Example iOS 4 | // 5 | // Created by Hoefele, Claus(choefele) on 27.11.13. 6 | // Copyright (c) 2013 Claus Höfele. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (nonatomic) UIWindow *window; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /CCHMapClusterController Tests/QTree/QCluster.h: -------------------------------------------------------------------------------- 1 | // 2 | // This file is subject to the terms and conditions defined in 3 | // file 'LICENSE.md', which is part of this source code package. 4 | // 5 | 6 | @import CoreLocation; 7 | 8 | #import "QTreeInsertable.h" 9 | 10 | @interface QCluster : NSObject 11 | 12 | @property(nonatomic, assign) CLLocationCoordinate2D coordinate; 13 | @property(nonatomic, assign) NSInteger objectsCount; 14 | @property(nonatomic, assign) CLLocationDegrees radius; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/DataReaderDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // DataReaderDelegate.h 3 | // Macoun 2013 4 | // 5 | // Created by Hoefele, Claus(choefele) on 20.09.13. 6 | // Copyright (c) 2013 Hoefele, Claus(choefele). All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class DataReader; 12 | 13 | @protocol DataReaderDelegate 14 | 15 | - (void)dataReader:(DataReader *)dataReader addAnnotations:(NSArray *)annotations; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/CCHMapClusterController Example iOS-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_5_0 10 | #warning "This project uses features only available in iOS SDK 5.0 and later." 11 | #endif 12 | 13 | #ifdef __OBJC__ 14 | #import 15 | #import 16 | #endif 17 | -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // CCHMapClusterController Example iOS 4 | // 5 | // Created by Hoefele, Claus(choefele) on 27.11.13. 6 | // Copyright (c) 2013 Claus Höfele. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "AppDelegate.h" 12 | 13 | int main(int argc, char * argv[]) 14 | { 15 | @autoreleasepool { 16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/SquareRed.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "square21-s.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "square21.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleBlue21.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "circle21.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "circle21@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleBlue24.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "circle24.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "circle24@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleBlue25.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "circle25.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "circle25@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleBlue28.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "circle28.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "circle28@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleBlue31.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "circle31.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "circle31@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleBlue34.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "circle34.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "circle34@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleBlue36.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "circle36.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "circle36@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleBlue38.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "circle38.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "circle38@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleBlue39.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "circle39.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "circle39@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleRed21.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "circle21.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "circle21@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleRed24.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "circle24.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "circle24@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleRed25.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "circle25.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "circle25@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleRed28.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "circle28.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "circle28@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleRed31.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "circle31.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "circle31@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleRed34.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "circle34.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "circle34@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleRed36.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "circle36.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "circle36@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleRed38.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "circle38.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "circle38@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/CircleRed39.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "circle39.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "circle39@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /CCHMapClusterController Example OS X/CCHMapClusterController Example OS X/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // CCHMapClusterController Example OS X 4 | // 5 | // Created by Hoefele, Claus(choefele) on 27.11.13. 6 | // Copyright (c) 2013 Claus Höfele. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface AppDelegate : NSObject 13 | 14 | @property (assign) IBOutlet NSWindow *window; 15 | @property (nonatomic, strong) IBOutlet MKMapView *mapView; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Images.xcassets/SquareBlue.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Marker-Square2.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "Marker-Square2@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/SettingsViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // SettingsViewController.h 3 | // CCHMapClusterController Example iOS 4 | // 5 | // Created by Claus Höfele on 07.02.14. 6 | // Copyright (c) 2014 Claus Höfele. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class Settings; 12 | 13 | @interface SettingsViewController : UITableViewController 14 | 15 | @property (nonatomic, copy) void (^completionBlock)(Settings *settings); 16 | @property (nonatomic, copy) Settings *settings; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /CCHMapClusterController Tests/QTree/QTreeGeometryUtils.h: -------------------------------------------------------------------------------- 1 | // 2 | // This file is subject to the terms and conditions defined in 3 | // file 'LICENSE.md', which is part of this source code package. 4 | // 5 | 6 | @import CoreLocation; 7 | @import MapKit; 8 | 9 | BOOL MKCoordinateRegionIntersectsRegion(MKCoordinateRegion region1, MKCoordinateRegion region2); 10 | BOOL MKCoordinateRegionContainsCoordinate(MKCoordinateRegion region, CLLocationCoordinate2D coordinate); 11 | 12 | CLLocationDistance CLMetersBetweenCoordinates(CLLocationCoordinate2D c1, CLLocationCoordinate2D c2); 13 | 14 | -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/DataReader.h: -------------------------------------------------------------------------------- 1 | // 2 | // DataReader.h 3 | // Macoun 2013 4 | // 5 | // Created by Hoefele, Claus(choefele) on 20.09.13. 6 | // Copyright (c) 2013 Hoefele, Claus(choefele). All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @protocol DataReaderDelegate; 12 | 13 | @interface DataReader : NSObject 14 | 15 | @property (nonatomic, weak) id delegate; 16 | 17 | - (void)startReadingBerlinData; 18 | - (void)startReadingUSData; 19 | - (void)stopReadingData; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/ClusterAnnotationView.h: -------------------------------------------------------------------------------- 1 | // 2 | // ClusterAnnotationView.h 3 | // CCHMapClusterController Example iOS 4 | // 5 | // Created by Hoefele, Claus(choefele) on 09.01.14. 6 | // Copyright (c) 2014 Claus Höfele. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ClusterAnnotationView : MKAnnotationView 12 | 13 | @property (nonatomic) NSUInteger count; 14 | @property (nonatomic, getter = isBlue) BOOL blue; 15 | @property (nonatomic, getter = isUniqueLocation) BOOL uniqueLocation; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | osx_image: xcode9.2 3 | before_install: 4 | - gem install xcpretty --no-rdoc --no-ri --no-document --quiet 5 | script: 6 | - export LC_CTYPE=en_US.UTF-8 7 | - set -o pipefail 8 | - xcodebuild -workspace CCHMapClusterController.xcworkspace -scheme 'CCHMapClusterController Example iOS' -sdk iphonesimulator -configuration Debug -destination 'platform=iOS Simulator,name=iPhone X,OS=11.2' test | xcpretty -c 9 | - xcodebuild -workspace CCHMapClusterController.xcworkspace -scheme 'CCHMapClusterController Example OS X' -sdk macosx -configuration Debug test | xcpretty -c 10 | 11 | -------------------------------------------------------------------------------- /CCHMapClusterController Example OS X/CCHMapClusterController Example OS X/en.lproj/Credits.rtf: -------------------------------------------------------------------------------- 1 | {\rtf0\ansi{\fonttbl\f0\fswiss Helvetica;} 2 | {\colortbl;\red255\green255\blue255;} 3 | \paperw9840\paperh8400 4 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural 5 | 6 | \f0\b\fs24 \cf0 Engineering: 7 | \b0 \ 8 | Some people\ 9 | \ 10 | 11 | \b Human Interface Design: 12 | \b0 \ 13 | Some other people\ 14 | \ 15 | 16 | \b Testing: 17 | \b0 \ 18 | Hopefully not nobody\ 19 | \ 20 | 21 | \b Documentation: 22 | \b0 \ 23 | Whoever\ 24 | \ 25 | 26 | \b With special thanks to: 27 | \b0 \ 28 | Mom\ 29 | } 30 | -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/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 | } -------------------------------------------------------------------------------- /CCHMapClusterController Tests/QTree/QTree.h: -------------------------------------------------------------------------------- 1 | // 2 | // This file is subject to the terms and conditions defined in 3 | // file 'LICENSE.md', which is part of this source code package. 4 | // 5 | 6 | @import CoreLocation; 7 | @import MapKit; 8 | 9 | #import "QTreeInsertable.h" 10 | 11 | @interface QTree : NSObject 12 | 13 | -(void)insertObject:(id)insertableObject; 14 | 15 | @property(nonatomic, readonly) NSUInteger count; 16 | 17 | - (void) cleanup; 18 | 19 | -(NSArray*)getObjectsInRegion:(MKCoordinateRegion)region minNonClusteredSpan:(CLLocationDegrees)span; 20 | // Returned array is sorted from the least to the most distant 21 | -(NSArray*)neighboursForLocation:(CLLocationCoordinate2D)location limitCount:(NSUInteger)limit; 22 | 23 | @end -------------------------------------------------------------------------------- /CCHMapClusterController Tests/KPAnnotationTree/KPTreeNode.m: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2012 Bryan Bonczek 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | #import "KPTreeNode.h" 18 | 19 | @implementation KPTreeNode 20 | // pass 21 | @end 22 | -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/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" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOSTests/CCHMapClusterController Example iOSTests-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 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /CCHMapClusterController Example OS X/CCHMapClusterController Example OS XTests/CCHMapClusterController Example OS XTests-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 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /CCHMapClusterController Tests/KPAnnotationTree/KPAnnotationTree.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2012 Bryan Bonczek 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | 18 | #import 19 | #import 20 | 21 | @interface KPAnnotationTree : NSObject 22 | 23 | @property (nonatomic, readonly) NSSet *annotations; 24 | 25 | - (id)initWithAnnotations:(NSArray *)annotations; 26 | 27 | - (NSArray *)annotationsInMapRect:(MKMapRect)rect; 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /CCHMapClusterController.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |spec| 2 | spec.name = 'CCHMapClusterController' 3 | spec.version = '1.7.2' 4 | spec.license = 'MIT' 5 | spec.summary = 'High-performance map clustering with MapKit for iOS and OS X. Integrate with 4 lines of code.' 6 | spec.homepage = 'https://github.com/choefele/CCHMapClusterController' 7 | spec.authors = { 'Claus Höfele' => 'claus@claushoefele.com' } 8 | spec.social_media_url = 'https://twitter.com/claushoefele' 9 | spec.source = { :git => 'https://github.com/choefele/CCHMapClusterController.git', :tag => spec.version.to_s } 10 | spec.frameworks = 'MapKit', 'CoreLocation' 11 | spec.requires_arc = true 12 | 13 | spec.ios.deployment_target = '7.0' 14 | spec.osx.deployment_target = '10.9' 15 | 16 | spec.source_files = 'CCHMapClusterController/*.{h,m}' 17 | spec.private_header_files = 'CCHMapClusterController/{CCHMapTree,CCHMapTreeUtils,CCHMapClusterControllerUtils,CCHMapClusterControllerDebugPolygon,CCHMapClusterOperation,CCHMapViewDelegateProxy}.h' 18 | end 19 | -------------------------------------------------------------------------------- /CCHMapClusterController Tests/KPAnnotationTree/KPTreeNode.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2012 Bryan Bonczek 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | #import 18 | #import 19 | #import 20 | 21 | 22 | @interface KPTreeNode : NSObject 23 | 24 | @property (nonatomic) id annotation; 25 | @property (nonatomic) KPTreeNode *left; 26 | @property (nonatomic) KPTreeNode *right; 27 | @property (nonatomic) MKMapPoint mapPoint; 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /CCHMapClusterController Tests/QTree/QNode.h: -------------------------------------------------------------------------------- 1 | // 2 | // This file is subject to the terms and conditions defined in 3 | // file 'LICENSE.md', which is part of this source code package. 4 | // 5 | 6 | @import CoreLocation; 7 | @import MapKit; 8 | 9 | #import "QCluster.h" 10 | 11 | @interface QNode : NSObject 12 | 13 | +(instancetype)nodeWithRegion:(MKCoordinateRegion)region; 14 | 15 | 16 | -(instancetype)initWithRegion:(MKCoordinateRegion)region; 17 | 18 | @property(nonatomic, readonly) MKCoordinateRegion region; 19 | @property(nonatomic, readonly) NSUInteger count; 20 | // Shortcuts 21 | @property(nonatomic, readonly) CLLocationDegrees centerLatitude; 22 | @property(nonatomic, readonly) CLLocationDegrees centerLongitude; 23 | 24 | -(BOOL)insertObject:(id)insertableObject; 25 | 26 | -(NSArray*)getObjectsInRegion:(MKCoordinateRegion)region minNonClusteredSpan:(CLLocationDegrees)span; 27 | // Returned array is sorted from the least to the most distant 28 | -(NSArray*)neighboursForLocation:(CLLocationCoordinate2D)location limitCount:(NSUInteger)limit; 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Claus Höfele 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Settings.h: -------------------------------------------------------------------------------- 1 | // 2 | // Settings.h 3 | // CCHMapClusterController Example iOS 4 | // 5 | // Created by Claus Höfele on 08.02.14. 6 | // Copyright (c) 2014 Claus Höfele. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | typedef enum { 13 | SettingsDataSetBerlin, 14 | SettingsDataSetUS 15 | } SettingsDataSet; 16 | 17 | typedef enum { 18 | SettingsClustererCenterOfMass, 19 | SettingsClustererNearCenter 20 | } SettingsClusterer; 21 | 22 | typedef enum { 23 | SettingsAnimatorFadeInOut 24 | } SettingsAnimator; 25 | 26 | @interface Settings : NSObject 27 | 28 | @property (nonatomic, getter = isDebuggingEnabled) BOOL debuggingEnabled; 29 | @property (nonatomic) double cellSize; 30 | @property (nonatomic) double marginFactor; 31 | @property (nonatomic) SettingsDataSet dataSet; 32 | @property (nonatomic, getter = isGroupingEnabled) BOOL groupingEnabled; 33 | @property (nonatomic) SettingsClusterer clusterer; 34 | @property (nonatomic) double maxZoomLevelForClustering; 35 | @property (nonatomic) NSUInteger minUniqueLocationsForClustering; 36 | @property (nonatomic) SettingsAnimator animator; 37 | 38 | @end 39 | -------------------------------------------------------------------------------- /CCHMapClusterController Example OS X/CCHMapClusterController Example OS X/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "size" : "16x16", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "size" : "16x16", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "size" : "32x32", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "size" : "32x32", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "size" : "128x128", 26 | "scale" : "1x" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "size" : "128x128", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "size" : "256x256", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "size" : "256x256", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "size" : "512x512", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "size" : "512x512", 51 | "scale" : "2x" 52 | } 53 | ], 54 | "info" : { 55 | "version" : 1, 56 | "author" : "xcode" 57 | } 58 | } -------------------------------------------------------------------------------- /CCHMapClusterController Tests/QTree/QTree.m: -------------------------------------------------------------------------------- 1 | // 2 | // This file is subject to the terms and conditions defined in 3 | // file 'LICENSE.md', which is part of this source code package. 4 | // 5 | 6 | #import "QTree.h" 7 | #import "QNode.h" 8 | 9 | @interface QTree() 10 | 11 | @property(nonatomic, strong) QNode* rootNode; 12 | 13 | @end 14 | 15 | @implementation QTree 16 | 17 | -(id)init 18 | { 19 | self = [super init]; 20 | if( !self ) { 21 | return nil; 22 | } 23 | [self cleanup]; 24 | return self; 25 | } 26 | 27 | - (void) cleanup 28 | { 29 | self.rootNode = [[QNode alloc] initWithRegion:MKCoordinateRegionForMapRect(MKMapRectWorld)]; 30 | } 31 | 32 | -(void)insertObject:(id)insertableObject 33 | { 34 | [self.rootNode insertObject:insertableObject]; 35 | } 36 | 37 | -(NSUInteger)count 38 | { 39 | return self.rootNode.count; 40 | } 41 | 42 | -(NSArray*)getObjectsInRegion:(MKCoordinateRegion)region minNonClusteredSpan:(CLLocationDegrees)span 43 | { 44 | return [self.rootNode getObjectsInRegion:region minNonClusteredSpan:span]; 45 | } 46 | 47 | -(NSArray*)neighboursForLocation:(CLLocationCoordinate2D)location limitCount:(NSUInteger)limit 48 | { 49 | return [self.rootNode neighboursForLocation:location limitCount:limit]; 50 | } 51 | 52 | @end 53 | -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Settings.m: -------------------------------------------------------------------------------- 1 | // 2 | // Settings.m 3 | // CCHMapClusterController Example iOS 4 | // 5 | // Created by Claus Höfele on 08.02.14. 6 | // Copyright (c) 2014 Claus Höfele. All rights reserved. 7 | // 8 | 9 | #import "Settings.h" 10 | 11 | @implementation Settings 12 | 13 | - (instancetype)init 14 | { 15 | self = [super init]; 16 | if (self) { 17 | _cellSize = 60; 18 | _marginFactor = 0.5; 19 | _maxZoomLevelForClustering = 16; 20 | _minUniqueLocationsForClustering = 3; 21 | } 22 | 23 | return self; 24 | } 25 | 26 | - (id)copyWithZone:(NSZone *)zone 27 | { 28 | Settings *settings = [[Settings alloc] init]; 29 | settings.debuggingEnabled = self.isDebuggingEnabled; 30 | settings.cellSize = self.cellSize; 31 | settings.marginFactor = self.marginFactor; 32 | settings.dataSet = self.dataSet; 33 | settings.groupingEnabled = self.groupingEnabled; 34 | settings.clusterer = self.clusterer; 35 | settings.maxZoomLevelForClustering = self.maxZoomLevelForClustering; 36 | settings.minUniqueLocationsForClustering = self.minUniqueLocationsForClustering; 37 | settings.animator = self.animator; 38 | 39 | return settings; 40 | } 41 | 42 | @end 43 | -------------------------------------------------------------------------------- /CCHMapClusterController Example OS X/CCHMapClusterController Example OS X/CCHMapClusterController Example OS X-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 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 25 | LSMinimumSystemVersion 26 | ${MACOSX_DEPLOYMENT_TARGET} 27 | NSHumanReadableCopyright 28 | Copyright © 2013 Claus Höfele. All rights reserved. 29 | NSMainNibFile 30 | MainMenu 31 | NSPrincipalClass 32 | NSApplication 33 | 34 | 35 | -------------------------------------------------------------------------------- /CCHMapClusterController Tests/QTree/QTreeGeometryUtils.m: -------------------------------------------------------------------------------- 1 | // 2 | // This file is subject to the terms and conditions defined in 3 | // file 'LICENSE.md', which is part of this source code package. 4 | // 5 | 6 | #import "QTreeGeometryUtils.h" 7 | 8 | BOOL MKCoordinateRegionIntersectsRegion(MKCoordinateRegion region1, MKCoordinateRegion region2) 9 | { 10 | const CLLocationDegrees dstLat = ABS(region1.center.latitude - region2.center.latitude); 11 | const CLLocationDegrees dstLng = ABS(region1.center.longitude - region2.center.longitude); 12 | return (dstLat < (region1.span.latitudeDelta + region2.span.latitudeDelta) / 2) 13 | && (dstLng < (region1.span.longitudeDelta + region2.span.longitudeDelta) / 2); 14 | } 15 | 16 | BOOL MKCoordinateRegionContainsCoordinate(MKCoordinateRegion region, CLLocationCoordinate2D coordinate) 17 | { 18 | CLLocationDegrees dstLat = ABS(region.center.latitude - coordinate.latitude); 19 | CLLocationDegrees dstLng = ABS(region.center.longitude - coordinate.longitude); 20 | return (dstLat < region.span.latitudeDelta / 2) && (dstLng < region.span.longitudeDelta / 2); 21 | } 22 | 23 | CLLocationDistance CLMetersBetweenCoordinates(CLLocationCoordinate2D c1, CLLocationCoordinate2D c2) 24 | { 25 | return MKMetersBetweenMapPoints(MKMapPointForCoordinate(c1), MKMapPointForCoordinate(c2)); 26 | } 27 | 28 | -------------------------------------------------------------------------------- /CCHMapClusterController Tests/KPAnnotationTree/NSArray+KP.h: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2011 by Bryan Bonczek 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. */ 20 | 21 | #import 22 | 23 | //NOTE: this may require LLVM 2.0 to compile 24 | 25 | @interface NSArray (KP) 26 | 27 | - (NSArray *)kp_filter:(BOOL (^)(id))block; 28 | - (NSArray *)kp_map:(id (^)(id))block; 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /CCHMapClusterController/CCHMapClusterControllerDebugPolygon.m: -------------------------------------------------------------------------------- 1 | // 2 | // CCHMapClusterControllerDebugPolygon.h 3 | // CCHMapClusterController 4 | // 5 | // Copyright (C) 2014 Claus Höfele 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | // 25 | 26 | #import "CCHMapClusterControllerDebugPolygon.h" 27 | 28 | @implementation CCHMapClusterControllerDebugPolygon 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/CCHMapClusterController Example iOS-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 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 | UILaunchStoryboardName 28 | Launch Screen 29 | UIMainStoryboardFile 30 | Main 31 | UIRequiredDeviceCapabilities 32 | 33 | armv7 34 | 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationLandscapeRight 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /CCHMapClusterController Tests/KPAnnotationTree/KPAnnotation.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2012 Bryan Bonczek 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | #import 18 | #import 19 | #import 20 | 21 | @interface KPAnnotation : NSObject 22 | 23 | @property (nonatomic, assign) CLLocationCoordinate2D coordinate; 24 | @property (nonatomic, readwrite, copy) NSString *title; 25 | @property (nonatomic, readwrite, copy) NSString *subtitle; 26 | 27 | @property (nonatomic, readonly) float radius; 28 | @property (nonatomic, readonly) NSSet *annotations; 29 | 30 | 31 | - (id)initWithAnnotations:(NSArray *)annotations; 32 | - (id)initWithAnnotationSet:(NSSet *)set; 33 | 34 | // Helpers 35 | 36 | // returns NO if the KPAnnotation only contains one annotation 37 | - (BOOL)isCluster; 38 | 39 | 40 | // Private (used by the internal clustering algorithm) 41 | @property (nonatomic) NSValue *_annotationPointInMapView; 42 | 43 | @end 44 | -------------------------------------------------------------------------------- /CCHMapClusterController/CCHMapClusterControllerDebugPolygon.h: -------------------------------------------------------------------------------- 1 | // 2 | // CCHMapClusterControllerDebugPolygon.h 3 | // CCHMapClusterController 4 | // 5 | // Copyright (C) 2014 Claus Höfele 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | // 25 | 26 | #import 27 | 28 | @class CCHMapClusterController; 29 | 30 | @interface CCHMapClusterControllerDebugPolygon : MKPolygon 31 | 32 | @property (nonatomic, weak) CCHMapClusterController *mapClusterController; 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /CCHMapClusterController/CCHMapViewDelegateProxy.h: -------------------------------------------------------------------------------- 1 | // 2 | // CCHMapViewDelegateProxy.h 3 | // CCHMapClusterController 4 | // 5 | // Copyright (C) 2013 Claus Höfele 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | // 25 | 26 | #import 27 | #import 28 | 29 | @interface CCHMapViewDelegateProxy : NSObject 30 | 31 | @property (nonatomic, readonly) NSHashTable *delegates; 32 | @property (nonatomic, weak, readonly) NSObject *target; 33 | 34 | - (instancetype)initWithMapView:(MKMapView *)mapView delegate:(NSObject *)delegate; 35 | - (void)addDelegate:(NSObject *)delegate; 36 | 37 | @end 38 | -------------------------------------------------------------------------------- /CCHMapClusterController Tests/TBQuadTree/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 | -------------------------------------------------------------------------------- /CCHMapClusterController/CCHMapTree.h: -------------------------------------------------------------------------------- 1 | // 2 | // CCHMapTree.h 3 | // CCHMapClusterController 4 | // 5 | // Copyright (C) 2013 Claus Höfele 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | // 25 | 26 | #import 27 | #import 28 | 29 | @interface CCHMapTree : NSObject 30 | 31 | @property (nonatomic, copy, readonly) NSSet *annotations; 32 | 33 | - (instancetype)init; 34 | - (instancetype)initWithNodeCapacity:(NSUInteger)nodeCapacity minLatitude:(double)minLatitude maxLatitude:(double)maxLatitude minLongitude:(double)minLongitude maxLongitude:(double)maxLongitude; 35 | 36 | - (BOOL)addAnnotations:(NSArray *)annotations; 37 | - (BOOL)removeAnnotations:(NSArray *)annotations; 38 | - (NSSet *)annotationsInMapRect:(MKMapRect)mapRect; 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /CCHMapClusterController Tests/KPAnnotationTree/NSArray+KP.m: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2011 by Bryan Bonczek 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. */ 20 | 21 | #import "NSArray+KP.h" 22 | 23 | 24 | @implementation NSArray (KP) 25 | 26 | - (NSArray *)kp_map:(id (^)(id))block { 27 | 28 | __block NSMutableArray *array = [NSMutableArray array]; 29 | 30 | [self foreach:^(id obj) { 31 | [array addObject:block(obj)]; 32 | }]; 33 | 34 | return array; 35 | } 36 | 37 | - (NSArray *)kp_filter:(BOOL (^)(id))block { 38 | 39 | __block NSMutableArray *array = [NSMutableArray array]; 40 | 41 | [self foreach:^(id obj) { 42 | if(block(obj)) { 43 | [array addObject:obj]; 44 | } 45 | }]; 46 | 47 | return array; 48 | } 49 | 50 | - (void)foreach:(void (^)(id))block { 51 | for(id obj in self) block(obj); 52 | } 53 | 54 | @end 55 | -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/Launch Screen.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 | 29 | -------------------------------------------------------------------------------- /CCHMapClusterController/CCHMapClusterer.h: -------------------------------------------------------------------------------- 1 | // 2 | // CCHMapClusterer.h 3 | // CCHMapClusterController 4 | // 5 | // Copyright (C) 2013 Claus Höfele 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | // 25 | 26 | #import 27 | #import 28 | 29 | @class CCHMapClusterController; 30 | 31 | /** 32 | A custom strategy that defines where clusters are positioned must implement this protocol. 33 | */ 34 | @protocol CCHMapClusterer 35 | 36 | /** 37 | Called on a background thread to determine the location of the cluster for the given annotations. 38 | @param mapClusterController map cluster controller. 39 | @param annotations annotations in this cluster (annotations are of type `CCHMapClusterAnnotation`). 40 | @param mapRect the area that's covered by this cluster. 41 | */ 42 | - (CLLocationCoordinate2D)mapClusterController:(CCHMapClusterController *)mapClusterController coordinateForAnnotations:(NSSet *)annotations inMapRect:(MKMapRect)mapRect; 43 | 44 | @end 45 | -------------------------------------------------------------------------------- /CCHMapClusterController/CCHNearCenterMapClusterer.h: -------------------------------------------------------------------------------- 1 | // 2 | // CCHNearCenterMapClusterer.h 3 | // CCHMapClusterController 4 | // 5 | // Copyright (C) 2013 Claus Höfele 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | // 25 | 26 | #import 27 | 28 | #import "CCHMapClusterer.h" 29 | 30 | /** 31 | `CCHMapClusterer` implementation that positions a cluster annotation near the center of the cluster cell. 32 | */ 33 | @interface CCHNearCenterMapClusterer : NSObject 34 | 35 | /** 36 | Returns the coordinate of the annotation closest to the center of the cell. 37 | @param mapClusterController map cluster controller. 38 | @param annotations annotations in this cluster (annotations are of type `CCHMapClusterAnnotation`). 39 | @param mapRect the area that's covered by this cluster. 40 | */ 41 | - (CLLocationCoordinate2D)mapClusterController:(CCHMapClusterController *)mapClusterController coordinateForAnnotations:(NSSet *)annotations inMapRect:(MKMapRect)mapRect; 42 | 43 | @end 44 | -------------------------------------------------------------------------------- /CCHMapClusterController/CCHCenterOfMassMapClusterer.h: -------------------------------------------------------------------------------- 1 | // 2 | // CCHCenterOfMassMapClusterer.h 3 | // CCHMapClusterController 4 | // 5 | // Copyright (C) 2013 Claus Höfele 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | // 25 | 26 | #import 27 | 28 | #import "CCHMapClusterer.h" 29 | 30 | /** 31 | `CCHMapClusterer` implementation that positions a cluster annotation at the average coordinate 32 | of all annotations in a cluster. 33 | */ 34 | @interface CCHCenterOfMassMapClusterer : NSObject 35 | 36 | /** 37 | Returns the average (or center of mass) coordinate of all annotations. 38 | @param mapClusterController map cluster controller. 39 | @param annotations annotations in this cluster (annotations are of type `CCHMapClusterAnnotation`). 40 | @param mapRect the area that's covered by this cluster. 41 | */ 42 | - (CLLocationCoordinate2D)mapClusterController:(CCHMapClusterController *)mapClusterController coordinateForAnnotations:(NSSet *)annotations inMapRect:(MKMapRect)mapRect; 43 | 44 | @end 45 | -------------------------------------------------------------------------------- /CCHMapClusterController/CCHCenterOfMassMapClusterer.m: -------------------------------------------------------------------------------- 1 | // 2 | // CCHCenterOfMassMapClusterer.m 3 | // CCHMapClusterController 4 | // 5 | // Copyright (C) 2013 Claus Höfele 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | // 25 | 26 | #import "CCHCenterOfMassMapClusterer.h" 27 | 28 | @implementation CCHCenterOfMassMapClusterer 29 | 30 | - (CLLocationCoordinate2D)mapClusterController:(CCHMapClusterController *)mapClusterController coordinateForAnnotations:(NSSet *)annotations inMapRect:(MKMapRect)mapRect 31 | { 32 | double latitude = 0, longitude = 0; 33 | for (id annotation in annotations) { 34 | latitude += annotation.coordinate.latitude; 35 | longitude += annotation.coordinate.longitude; 36 | } 37 | 38 | CLLocationCoordinate2D coordinate; 39 | if (annotations.count > 0) { 40 | double count = (double)annotations.count; 41 | coordinate = CLLocationCoordinate2DMake(latitude / count, longitude / count); 42 | } else { 43 | coordinate = CLLocationCoordinate2DMake(0, 0); 44 | } 45 | 46 | return coordinate; 47 | } 48 | 49 | @end 50 | -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // CCHMapClusterController Example iOS 4 | // 5 | // Created by Hoefele, Claus(choefele) on 27.11.13. 6 | // Copyright (c) 2013 Claus Höfele. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @implementation AppDelegate 12 | 13 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 14 | { 15 | // Override point for customization after application launch. 16 | return YES; 17 | } 18 | 19 | - (void)applicationWillResignActive:(UIApplication *)application 20 | { 21 | // 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. 22 | // 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. 23 | } 24 | 25 | - (void)applicationDidEnterBackground:(UIApplication *)application 26 | { 27 | // 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. 28 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 29 | } 30 | 31 | - (void)applicationWillEnterForeground:(UIApplication *)application 32 | { 33 | // 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. 34 | } 35 | 36 | - (void)applicationDidBecomeActive:(UIApplication *)application 37 | { 38 | // 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. 39 | } 40 | 41 | - (void)applicationWillTerminate:(UIApplication *)application 42 | { 43 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 44 | } 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /CCHMapClusterController Tests/KPAnnotationTree/KPTreeController.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2012 Bryan Bonczek 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | 18 | #import 19 | #import 20 | #import 21 | 22 | @class KPAnnotation; 23 | 24 | @protocol KPTreeControllerDelegate; 25 | 26 | @interface KPTreeController : NSObject 27 | 28 | @property (nonatomic, weak) id delegate; 29 | @property (nonatomic) CGSize gridSize; 30 | @property (nonatomic) CGSize annotationSize; 31 | @property (nonatomic) CGPoint annotationCenterOffset; 32 | @property (nonatomic) CGFloat animationDuration; 33 | @property (nonatomic) UIViewAnimationOptions animationOptions; 34 | @property (nonatomic) BOOL clusteringEnabled; 35 | @property (nonatomic) BOOL debuggingEnabled; 36 | 37 | /** 38 | If debuggingEnabled is YES, returns a list of polylines for the grid to be shown on the map 39 | */ 40 | @property (nonatomic, readonly) NSArray *gridPolylines; 41 | 42 | - (id)initWithMapView:(MKMapView *)mapView; 43 | - (void)setAnnotations:(NSArray *)annoations; 44 | - (void)refresh:(BOOL)animated; 45 | 46 | @end 47 | 48 | 49 | @protocol KPTreeControllerDelegate 50 | 51 | @optional 52 | 53 | - (void)treeController:(KPTreeController *)tree configureAnnotationForDisplay:(KPAnnotation *)annotation; 54 | - (void)treeController:(KPTreeController *)tree willAnimateAnnotation:(KPAnnotation *)annotation fromAnnotation:(KPAnnotation *)fromAnntation toAnnotation:(KPAnnotation *)toAnnotation; 55 | - (void)treeController:(KPTreeController *)tree didAnimateAnnotation:(KPAnnotation *)annotation fromAnnotation:(KPAnnotation *)fromAnntation toAnnotation:(KPAnnotation *)toAnnotation; 56 | 57 | @end 58 | -------------------------------------------------------------------------------- /CCHMapClusterController/CCHFadeInOutMapAnimator.h: -------------------------------------------------------------------------------- 1 | // 2 | // CCHFadeInOutMapAnimator.h 3 | // CCHMapClusterController 4 | // 5 | // Copyright (C) 2013 Claus Höfele 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | // 25 | 26 | #import 27 | 28 | #import "CCHMapAnimator.h" 29 | 30 | /** 31 | `CCHMapAnimator` implementation that fades annotation views in and out. 32 | */ 33 | @interface CCHFadeInOutMapAnimator : NSObject 34 | 35 | /** The animation's duration. */ 36 | @property (nonatomic, assign) NSTimeInterval duration; 37 | 38 | /** 39 | Fades annotation views in to make the visible. 40 | @param mapClusterController map cluster controller. 41 | @param annotationViews . 42 | */ 43 | - (void)mapClusterController:(CCHMapClusterController *)mapClusterController didAddAnnotationViews:(NSArray *)annotationViews; 44 | 45 | /** 46 | Fades annotation views out to hide them. 47 | @param mapClusterController map cluster controller. 48 | @param annotations annotations to animate (annotations are of type `CCHMapClusterAnnotation`). 49 | @param completionHandler this completion handler must be called after the animation has finished. 50 | */ 51 | - (void)mapClusterController:(CCHMapClusterController *)mapClusterController willRemoveAnnotations:(NSArray *)annotations withCompletionHandler:(void (^)(void))completionHandler; 52 | 53 | @end 54 | -------------------------------------------------------------------------------- /CCHMapClusterController/CCHNearCenterMapClusterer.m: -------------------------------------------------------------------------------- 1 | // 2 | // CCHNearCenterMapClusterer.m 3 | // CCHMapClusterController 4 | // 5 | // Copyright (C) 2013 Claus Höfele 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | // 25 | 26 | #import "CCHNearCenterMapClusterer.h" 27 | 28 | #import 29 | 30 | @implementation CCHNearCenterMapClusterer 31 | 32 | id findClosestAnnotation(NSSet *annotations, MKMapPoint mapPoint) 33 | { 34 | id closestAnnotation; 35 | CLLocationDistance closestDistance = __DBL_MAX__; 36 | for (id annotation in annotations) { 37 | MKMapPoint annotationAsMapPoint = MKMapPointForCoordinate(annotation.coordinate); 38 | CLLocationDistance distance = MKMetersBetweenMapPoints(mapPoint, annotationAsMapPoint); 39 | if (distance < closestDistance) { 40 | closestDistance = distance; 41 | closestAnnotation = annotation; 42 | } 43 | } 44 | 45 | return closestAnnotation; 46 | } 47 | 48 | - (CLLocationCoordinate2D)mapClusterController:(CCHMapClusterController *)mapClusterController coordinateForAnnotations:(NSSet *)annotations inMapRect:(MKMapRect)mapRect 49 | { 50 | MKMapPoint centerMapPoint = MKMapPointMake(MKMapRectGetMidX(mapRect), MKMapRectGetMidY(mapRect)); 51 | id closestAnnotation = findClosestAnnotation(annotations, centerMapPoint); 52 | return closestAnnotation.coordinate; 53 | } 54 | 55 | @end 56 | -------------------------------------------------------------------------------- /CCHMapClusterController/CCHMapAnimator.h: -------------------------------------------------------------------------------- 1 | // 2 | // CCHMapAnimator.h 3 | // CCHMapClusterController 4 | // 5 | // Copyright (C) 2013 Claus Höfele 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | // 25 | 26 | #import 27 | 28 | @class CCHMapClusterController; 29 | 30 | /** 31 | A custom strategy that defines how annotation views for `CCHClusterAnnotation`s are animated 32 | must implement this protocol. 33 | */ 34 | @protocol CCHMapAnimator 35 | 36 | /** 37 | Called on the main thread to animate in the given annotation views. At this point, the views' annotations 38 | have already been added to the map view. 39 | @param mapClusterController map cluster controller. 40 | @param annotationViews . 41 | */ 42 | - (void)mapClusterController:(CCHMapClusterController *)mapClusterController didAddAnnotationViews:(NSArray *)annotationViews; 43 | 44 | /** 45 | Called on the main thread to animate out the given annotations. The views' annotations will be removed 46 | when calling the completion handler. 47 | @param mapClusterController map cluster controller. 48 | @param annotations annotations to animate (annotations are of type `CCHMapClusterAnnotation`). 49 | @param completionHandler this completion handler must be called after the animation has finished. 50 | */ 51 | - (void)mapClusterController:(CCHMapClusterController *)mapClusterController willRemoveAnnotations:(NSArray *)annotations withCompletionHandler:(void (^)(void))completionHandler; 52 | 53 | @end 54 | -------------------------------------------------------------------------------- /CCHMapClusterController Tests/CCHFadeInOutMapAnimatorTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // CCHFadeInOutMapAnimatorTests.m 3 | // CCHMapClusterController 4 | // 5 | // Copyright (C) 2014 Claus Höfele 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | // 25 | 26 | #import "CCHFadeInOutMapAnimator.h" 27 | 28 | #import 29 | #import 30 | 31 | @interface CCHFadeInOutMapAnimatorTests : XCTestCase 32 | 33 | @property (nonatomic) CCHFadeInOutMapAnimator *animator; 34 | @property (nonatomic) BOOL done; 35 | 36 | @end 37 | 38 | @implementation CCHFadeInOutMapAnimatorTests 39 | 40 | - (void)setUp 41 | { 42 | [super setUp]; 43 | 44 | self.animator = [[CCHFadeInOutMapAnimator alloc] init]; 45 | self.done = NO; 46 | } 47 | 48 | #if TARGET_OS_IPHONE 49 | - (void)testFadeIn 50 | { 51 | MKPinAnnotationView *annotationView = [[MKPinAnnotationView alloc] init]; 52 | annotationView.alpha = 0; 53 | [self.animator mapClusterController:nil didAddAnnotationViews:@[annotationView]]; 54 | XCTAssertEqualWithAccuracy(annotationView.alpha, 1.0, __FLT_EPSILON__); 55 | } 56 | #endif 57 | 58 | - (void)testFadeOutCompletionBlock 59 | { 60 | XCTestExpectation *expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)]; 61 | [self.animator mapClusterController:nil willRemoveAnnotations:nil withCompletionHandler:^{ 62 | self.done = YES; 63 | [expectation fulfill]; 64 | }]; 65 | [self waitForExpectationsWithTimeout:10 handler:NULL]; 66 | } 67 | 68 | @end 69 | -------------------------------------------------------------------------------- /CCHMapClusterController/CCHMapClusterOperation.h: -------------------------------------------------------------------------------- 1 | // 2 | // CCHMapClusterOperation.h 3 | // CCHMapClusterController 4 | // 5 | // Copyright (C) 2014 Claus Höfele 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | // 25 | 26 | #import 27 | #import 28 | 29 | @class CCHMapClusterController; 30 | @class CCHMapClusterAnnotation; 31 | @class CCHMapTree; 32 | @protocol CCHMapClusterer; 33 | @protocol CCHMapAnimator; 34 | @protocol CCHMapClusterControllerDelegate; 35 | 36 | @interface CCHMapClusterOperation : NSOperation 37 | 38 | @property (nonatomic) CCHMapTree *allAnnotationsMapTree; 39 | @property (nonatomic) CCHMapTree *visibleAnnotationsMapTree; 40 | @property (nonatomic) id clusterer; 41 | @property (nonatomic) id animator; 42 | @property (nonatomic, weak) id clusterControllerDelegate; 43 | @property (nonatomic, weak) CCHMapClusterController *clusterController; 44 | 45 | - (instancetype)initWithMapView:(MKMapView *)mapView cellSize:(double)cellSize marginFactor:(double)marginFactor reuseExistingClusterAnnotations:(BOOL)reuseExistingClusterAnnotation maxZoomLevelForClustering:(double)maxZoomLevelForClustering minUniqueLocationsForClustering:(NSUInteger)minUniqueLocationsForClustering; 46 | 47 | + (double)cellMapSizeForCellSize:(double)cellSize withMapView:(MKMapView *)mapView; 48 | + (MKMapRect)gridMapRectForMapRect:(MKMapRect)mapRect withCellMapSize:(double)cellMapSize marginFactor:(double)marginFactor; 49 | 50 | @end 51 | -------------------------------------------------------------------------------- /CCHMapClusterController/CCHMapClusterAnnotation.h: -------------------------------------------------------------------------------- 1 | // 2 | // CCHMapClusterAnnotation.h 3 | // CCHMapClusterController 4 | // 5 | // Copyright (C) 2013 Claus Höfele 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | // 25 | 26 | #import 27 | #import 28 | 29 | @protocol CCHMapClusterControllerDelegate; 30 | @class CCHMapClusterController; 31 | 32 | /** 33 | Container for clustered annotations. 34 | */ 35 | @interface CCHMapClusterAnnotation : NSObject 36 | 37 | @property (nonatomic, weak) CCHMapClusterController *mapClusterController; 38 | 39 | /** The string containing the annotation's title. */ 40 | @property (nonatomic, copy) NSString *title; 41 | /** The string containing the annotation's subtitle. */ 42 | @property (nonatomic, copy) NSString *subtitle; 43 | /** The center point of the annotation. */ 44 | @property (nonatomic) CLLocationCoordinate2D coordinate; 45 | 46 | /** Custom titles and subtitles are retrieved via this delegate. */ 47 | @property (nonatomic, weak) id delegate; 48 | 49 | /** Annotations contained in this cluster. */ 50 | @property (nonatomic, copy) NSSet *annotations; 51 | 52 | /** Returns YES if this cluster contains more than one annotation. */ 53 | - (BOOL)isCluster; 54 | /** Returns YES if all annotations in this cluster have the same location. */ 55 | - (BOOL)isUniqueLocation; 56 | - (BOOL)isOneLocation __deprecated; 57 | 58 | /** The area that includes all annotations. */ 59 | - (MKMapRect)mapRect; 60 | 61 | @end 62 | -------------------------------------------------------------------------------- /CCHMapClusterController/CCHMapClusterControllerDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // CCHMapClusterControllerDelegate.h 3 | // CCHMapClusterController 4 | // 5 | // Copyright (C) 2013 Claus Höfele 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | // 25 | 26 | #import 27 | 28 | @class CCHMapClusterController; 29 | @class CCHMapClusterAnnotation; 30 | 31 | /** 32 | Protocol to configure custom titles and subtitles for cluster annotations. 33 | */ 34 | @protocol CCHMapClusterControllerDelegate 35 | 36 | @optional 37 | 38 | /** 39 | Returns the title for a cluster annotation. 40 | @param mapClusterController The cluster controller sending the message. 41 | @param mapClusterAnnotation The cluster annotation. 42 | */ 43 | - (NSString *)mapClusterController:(CCHMapClusterController *)mapClusterController titleForMapClusterAnnotation:(CCHMapClusterAnnotation *)mapClusterAnnotation; 44 | 45 | /** 46 | Returns the subtitle for a cluster annotation. 47 | @param mapClusterController The cluster controller sending the message. 48 | @param mapClusterAnnotation The cluster annotation. 49 | */ 50 | - (NSString *)mapClusterController:(CCHMapClusterController *)mapClusterController subtitleForMapClusterAnnotation:(CCHMapClusterAnnotation *)mapClusterAnnotation; 51 | 52 | /** 53 | Called before the given cluster annotation is reused for a cell. 54 | @param mapClusterController The cluster controller sending the message. 55 | @param mapClusterAnnotation The cluster annotation that's reused. Its properties are updated to reflect the current state. 56 | */ 57 | - (void)mapClusterController:(CCHMapClusterController *)mapClusterController willReuseMapClusterAnnotation:(CCHMapClusterAnnotation *)mapClusterAnnotation; 58 | 59 | @end 60 | -------------------------------------------------------------------------------- /CCHMapClusterController Tests/KPAnnotationTree/KPAnnotation.m: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2012 Bryan Bonczek 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | #import "KPAnnotation.h" 18 | 19 | @interface KPAnnotation () 20 | 21 | @property (nonatomic, readwrite) NSSet *annotations; 22 | @property (nonatomic, readwrite) float radius; 23 | 24 | @end 25 | 26 | @implementation KPAnnotation 27 | 28 | 29 | - (id)initWithAnnotations:(NSArray *)annotations { 30 | return [self initWithAnnotationSet:[NSSet setWithArray:annotations]]; 31 | } 32 | 33 | - (id)initWithAnnotationSet:(NSSet *)set { 34 | self = [super init]; 35 | 36 | if(self){ 37 | self.annotations = set; 38 | self.title = [NSString stringWithFormat:@"%i things", [self.annotations count]];; 39 | [self calculateValues]; 40 | } 41 | 42 | return self; 43 | } 44 | 45 | - (BOOL)isCluster { 46 | return (self.annotations.count > 1); 47 | } 48 | 49 | #pragma mark - Private 50 | 51 | - (void)calculateValues { 52 | 53 | CLLocationDegrees minLat = INT_MAX; 54 | CLLocationDegrees minLng = INT_MAX; 55 | CLLocationDegrees maxLat = -INT_MAX; 56 | CLLocationDegrees maxLng = -INT_MAX; 57 | 58 | CLLocationDegrees totalLat = 0; 59 | CLLocationDegrees totalLng = 0; 60 | 61 | for(id a in self.annotations){ 62 | 63 | CLLocationDegrees lat = [a coordinate].latitude; 64 | CLLocationDegrees lng = [a coordinate].longitude; 65 | 66 | minLat = MIN(minLat, lat); 67 | minLng = MIN(minLng, lng); 68 | maxLat = MAX(maxLat, lat); 69 | maxLng = MAX(maxLng, lng); 70 | 71 | totalLat += lat; 72 | totalLng += lng; 73 | } 74 | 75 | 76 | self.coordinate = CLLocationCoordinate2DMake(totalLat / self.annotations.count, 77 | totalLng / self.annotations.count); 78 | 79 | self.radius = [[[CLLocation alloc] initWithLatitude:minLat 80 | longitude:minLng] 81 | distanceFromLocation:[[CLLocation alloc] initWithLatitude:maxLat 82 | longitude:maxLng]] / 2.f; 83 | } 84 | 85 | 86 | 87 | @end 88 | -------------------------------------------------------------------------------- /CCHMapClusterController/CCHMapClusterControllerUtils.h: -------------------------------------------------------------------------------- 1 | // 2 | // CCHMapClusterControllerUtils.h 3 | // CCHMapClusterController 4 | // 5 | // Copyright (C) 2013 Claus Höfele 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | // 25 | 26 | #import 27 | #import 28 | 29 | @class CCHMapClusterAnnotation; 30 | @class CCHMapClusterController; 31 | 32 | MKMapRect CCHMapClusterControllerAlignMapRectToCellSize(MKMapRect mapRect, double cellSize); 33 | CCHMapClusterAnnotation *CCHMapClusterControllerFindVisibleAnnotation(NSSet *annotations, NSSet *visibleAnnotations); 34 | #if TARGET_OS_IPHONE 35 | double CCHMapClusterControllerMapLengthForLength(MKMapView *mapView, UIView *view, double length); 36 | #else 37 | double CCHMapClusterControllerMapLengthForLength(MKMapView *mapView, NSView *view, double length); 38 | #endif 39 | double CCHMapClusterControllerAlignMapLengthToWorldWidth(double mapLength); 40 | BOOL CCHMapClusterControllerCoordinateEqualToCoordinate(CLLocationCoordinate2D coordinate0, CLLocationCoordinate2D coordinate1); 41 | CCHMapClusterAnnotation *CCHMapClusterControllerClusterAnnotationForAnnotation(MKMapView *mapView, id annotation, MKMapRect mapRect); 42 | void CCHMapClusterControllerEnumerateCells(MKMapRect mapRect, double cellSize, void (^block)(MKMapRect cellMapRect)); 43 | MKMapRect CCHMapClusterControllerMapRectForCoordinateRegion(MKCoordinateRegion coordinateRegion); 44 | NSSet *CCHMapClusterControllerClusterAnnotationsForAnnotations(NSArray *annotations, CCHMapClusterController *mapClusterController); 45 | double CCHMapClusterControllerZoomLevelForRegion(CLLocationDegrees longitudeCenter, CLLocationDegrees longitudeDelta, CGFloat width); 46 | NSArray *CCHMapClusterControllerAnnotationSetsByUniqueLocations(NSSet *annotations, NSUInteger maxUniqueLocations); 47 | BOOL CCHMapClusterControllerIsUniqueLocation(NSSet *annotations); -------------------------------------------------------------------------------- /CCHMapClusterController/CCHFadeInOutMapAnimator.m: -------------------------------------------------------------------------------- 1 | // 2 | // CCHFadeInOutMapAnimator.m 3 | // CCHMapClusterController 4 | // 5 | // Copyright (C) 2013 Claus Höfele 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | // 25 | 26 | #import "CCHFadeInOutMapAnimator.h" 27 | 28 | #import "CCHMapClusterController.h" 29 | 30 | #import 31 | 32 | @implementation CCHFadeInOutMapAnimator 33 | 34 | - (instancetype)init 35 | { 36 | self = [super init]; 37 | if (self) { 38 | _duration = 0.2; 39 | } 40 | return self; 41 | } 42 | 43 | - (void)mapClusterController:(CCHMapClusterController *)mapClusterController didAddAnnotationViews:(NSArray *)annotationViews 44 | { 45 | // Animate annotations that get added 46 | #if TARGET_OS_IPHONE 47 | for (MKAnnotationView *annotationView in annotationViews) 48 | { 49 | annotationView.alpha = 0.0; 50 | } 51 | 52 | [UIView animateWithDuration:self.duration animations:^{ 53 | for (MKAnnotationView *annotationView in annotationViews) { 54 | annotationView.alpha = 1.0; 55 | } 56 | }]; 57 | #endif 58 | } 59 | 60 | - (void)mapClusterController:(CCHMapClusterController *)mapClusterController willRemoveAnnotations:(NSArray *)annotations withCompletionHandler:(void (^)(void))completionHandler 61 | { 62 | #if TARGET_OS_IPHONE 63 | MKMapView *mapView = mapClusterController.mapView; 64 | [UIView animateWithDuration:self.duration animations:^{ 65 | for (id annotation in annotations) { 66 | MKAnnotationView *annotationView = [mapView viewForAnnotation:annotation]; 67 | annotationView.alpha = 0.0; 68 | } 69 | } completion:^(BOOL finished) { 70 | if (completionHandler) { 71 | completionHandler(); 72 | } 73 | }]; 74 | #else 75 | if (completionHandler) { 76 | completionHandler(); 77 | } 78 | #endif 79 | } 80 | 81 | @end 82 | -------------------------------------------------------------------------------- /CCHMapClusterController/CCHMapClusterAnnotation.m: -------------------------------------------------------------------------------- 1 | // 2 | // CCHMapClusterAnnotation.m 3 | // CCHMapClusterController 4 | // 5 | // Copyright (C) 2013 Claus Höfele 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | // 25 | 26 | #import "CCHMapClusterAnnotation.h" 27 | 28 | #import "CCHMapClusterControllerDelegate.h" 29 | #import "CCHMapClusterControllerUtils.h" 30 | 31 | @implementation CCHMapClusterAnnotation 32 | 33 | - (NSString *)title 34 | { 35 | if (_title == nil && [self.delegate respondsToSelector:@selector(mapClusterController:titleForMapClusterAnnotation:)]) { 36 | _title = [self.delegate mapClusterController:self.mapClusterController titleForMapClusterAnnotation:self]; 37 | } 38 | 39 | return _title; 40 | } 41 | 42 | - (NSString *)subtitle 43 | { 44 | if (_subtitle == nil && [self.delegate respondsToSelector:@selector(mapClusterController:subtitleForMapClusterAnnotation:)]) { 45 | _subtitle = [self.delegate mapClusterController:self.mapClusterController subtitleForMapClusterAnnotation:self]; 46 | } 47 | 48 | return _subtitle; 49 | } 50 | 51 | - (BOOL)isCluster 52 | { 53 | return (self.annotations.count > 1); 54 | } 55 | 56 | - (BOOL)isUniqueLocation 57 | { 58 | return CCHMapClusterControllerIsUniqueLocation(self.annotations); 59 | } 60 | 61 | - (BOOL)isOneLocation 62 | { 63 | return [self isUniqueLocation]; 64 | } 65 | 66 | - (MKMapRect)mapRect 67 | { 68 | MKMapPoint clusterPoint = MKMapPointForCoordinate(self.coordinate); 69 | MKMapRect mapRect = MKMapRectMake(clusterPoint.x, clusterPoint.y, 0.1, 0.1); 70 | for (id annotation in self.annotations) 71 | { 72 | MKMapPoint annotationPoint = MKMapPointForCoordinate(annotation.coordinate); 73 | MKMapRect pointRect = MKMapRectMake(annotationPoint.x, annotationPoint.y, 0.1, 0.1); 74 | mapRect = MKMapRectUnion(mapRect, pointRect); 75 | } 76 | 77 | return mapRect; 78 | } 79 | 80 | @end 81 | -------------------------------------------------------------------------------- /CCHMapClusterController Example OS X/CCHMapClusterController Example OS X/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // CCHMapClusterController Example OS X 4 | // 5 | // Created by Hoefele, Claus(choefele) on 27.11.13. 6 | // Copyright (c) 2013 Claus Höfele. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | #import "DataReader.h" 12 | #import "DataReaderDelegate.h" 13 | 14 | #import "CCHMapClusterAnnotation.h" 15 | #import "CCHMapClusterController.h" 16 | #import "CCHMapClusterControllerDelegate.h" 17 | #import "CCHCenterOfMassMapClusterer.h" 18 | 19 | @interface AppDelegate() 20 | 21 | @property (strong, nonatomic) CCHMapClusterController *mapClusterController; 22 | @property (strong, nonatomic) id mapClusterer; 23 | 24 | @end 25 | 26 | @implementation AppDelegate 27 | 28 | - (void)applicationDidFinishLaunching:(NSNotification *)aNotification 29 | { 30 | // Show Berlin 31 | CLLocationCoordinate2D location = CLLocationCoordinate2DMake(52.516221, 13.377829); 32 | MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(location, 45000, 45000); 33 | self.mapView.region = region; 34 | 35 | // Set up map clustering 36 | self.mapClusterController = [[CCHMapClusterController alloc] initWithMapView:self.mapView]; 37 | self.mapClusterController.delegate = self; 38 | 39 | // Cell size and margin factor 40 | // self.mapClusterController.cellSize = 100; // [points] 41 | // self.mapClusterController.marginFactor = 0; // 0 = no additional margin 42 | // self.mapClusterController.debuggingEnabled = YES; // display grid 43 | 44 | // Positioning cluster representations 45 | // self.mapClusterer = [[CCHCenterOfMassMapClusterer alloc] init]; 46 | // self.mapClusterController.clusterer = self.mapClusterer; // change default clusterer 47 | // self.mapClusterController.reuseExistingClusterAnnotations = NO; // YES to avoid updating positions 48 | 49 | // Read annotations 50 | DataReader *dataReader = [[DataReader alloc] init]; 51 | dataReader.delegate = self; 52 | [dataReader startReadingBerlinData]; 53 | } 54 | 55 | - (void)dataReader:(DataReader *)dataReader addAnnotations:(NSArray *)annotations 56 | { 57 | // [self.mapView addAnnotations:annotations]; 58 | [self.mapClusterController addAnnotations:annotations withCompletionHandler:NULL]; 59 | } 60 | 61 | - (NSString *)mapClusterController:(CCHMapClusterController *)mapClusterController titleForMapClusterAnnotation:(CCHMapClusterAnnotation *)mapClusterAnnotation 62 | { 63 | NSUInteger numAnnotations = mapClusterAnnotation.annotations.count; 64 | NSString *unit = numAnnotations > 1 ? @"annotations" : @"annotation"; 65 | return [NSString stringWithFormat:@"%tu %@", numAnnotations, unit]; 66 | } 67 | 68 | - (NSString *)mapClusterController:(CCHMapClusterController *)mapClusterController subtitleForMapClusterAnnotation:(CCHMapClusterAnnotation *)mapClusterAnnotation 69 | { 70 | NSUInteger numAnnotations = MIN(mapClusterAnnotation.annotations.count, 5); 71 | NSArray *annotations = [mapClusterAnnotation.annotations.allObjects subarrayWithRange:NSMakeRange(0, numAnnotations)]; 72 | NSArray *titles = [annotations valueForKey:@"title"]; 73 | return [titles componentsJoinedByString:@", "]; 74 | } 75 | 76 | @end 77 | -------------------------------------------------------------------------------- /CCHMapClusterController/CCHMapTreeUtils.h: -------------------------------------------------------------------------------- 1 | // 2 | // CCHMapTreeUtils.h 3 | // CCHMapClusterController 4 | // 5 | // Copyright (C) 2013 Theodore Calmes 6 | // Copyright (C) 2013 Claus Höfele 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | // 26 | 27 | #import 28 | 29 | @interface CCHMapTreeUnsafeMutableArray : NSObject 30 | 31 | @property (nonatomic, assign, readonly) id __unsafe_unretained *objects; 32 | @property (nonatomic, readonly) NSUInteger numObjects; 33 | 34 | - (instancetype)initWithCapacity:(NSUInteger)capacity; 35 | - (void)addObject:(__unsafe_unretained id)object; 36 | 37 | @end 38 | 39 | typedef struct CCHMapTreeNodeData { 40 | double x, y; 41 | void *data; 42 | } CCHMapTreeNodeData; 43 | NS_INLINE CCHMapTreeNodeData CCHMapTreeNodeDataMake(double x, double y, void *data) { 44 | return (CCHMapTreeNodeData){x, y, data}; 45 | } 46 | 47 | typedef struct CCHMapTreeBoundingBox { 48 | double x0, y0, xf, yf; 49 | } CCHMapTreeBoundingBox; 50 | NS_INLINE CCHMapTreeBoundingBox CCHMapTreeBoundingBoxMake(double x0, double y0, double xf, double yf) { 51 | return (CCHMapTreeBoundingBox){x0, y0, xf, yf}; 52 | } 53 | 54 | typedef struct CCHMapTreeNode { 55 | CCHMapTreeBoundingBox boundingBox; 56 | struct CCHMapTreeNode *northWest; 57 | struct CCHMapTreeNode *northEast; 58 | struct CCHMapTreeNode *southWest; 59 | struct CCHMapTreeNode *southEast; 60 | CCHMapTreeNodeData *points; 61 | unsigned long count; 62 | } CCHMapTreeNode; 63 | CCHMapTreeNode *CCHMapTreeNodeMake(CCHMapTreeBoundingBox boundary, unsigned long bucketCapacity); 64 | void CCHMapTreeFreeQuadTreeNode(CCHMapTreeNode *node); 65 | 66 | typedef void(^CCHMapTreeTraverseBlock)(CCHMapTreeNode *currentNode); 67 | void CCHMapTreeTraverse(CCHMapTreeNode *node, CCHMapTreeTraverseBlock block); 68 | 69 | typedef void(^TBDataReturnBlock)(CCHMapTreeNodeData data); 70 | void CCHMapTreeGatherDataInRange(CCHMapTreeNode *node, CCHMapTreeBoundingBox range, TBDataReturnBlock block); 71 | void CCHMapTreeGatherDataInRange2(CCHMapTreeNode *node, CCHMapTreeBoundingBox range, __unsafe_unretained NSMutableSet *annotations); 72 | void CCHMapTreeGatherDataInRange3(CCHMapTreeNode *node, CCHMapTreeBoundingBox range, __unsafe_unretained CCHMapTreeUnsafeMutableArray *annotations); 73 | 74 | CCHMapTreeNode *CCHMapTreeBuildWithData(CCHMapTreeNodeData *data, unsigned long count, CCHMapTreeBoundingBox boundingBox, unsigned long bucketCapacity); 75 | bool CCHMapTreeNodeInsertData(CCHMapTreeNode* node, CCHMapTreeNodeData data, unsigned long bucketCapacity); 76 | bool CCHMapTreeNodeRemoveData(CCHMapTreeNode* node, CCHMapTreeNodeData data); // only removes first matching item -------------------------------------------------------------------------------- /CCHMapClusterController Tests/CCHCenterOfMassMapClustererTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // CCHCenterOfMassMapClustererTests.m 3 | // CCHMapClusterController 4 | // 5 | // Copyright (C) 2013 Claus Höfele 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | // 25 | 26 | #import "CCHCenterOfMassMapClusterer.h" 27 | 28 | #import 29 | 30 | @interface CCHCenterOfMassMapClustererTests : XCTestCase 31 | 32 | @property (nonatomic) CCHCenterOfMassMapClusterer *mapClusterer; 33 | 34 | @end 35 | 36 | @implementation CCHCenterOfMassMapClustererTests 37 | 38 | - (void)setUp 39 | { 40 | [super setUp]; 41 | 42 | self.mapClusterer = [[CCHCenterOfMassMapClusterer alloc] init]; 43 | } 44 | 45 | - (void)testCoordinateForAnnotationsNil 46 | { 47 | CLLocationCoordinate2D coordinate = [self.mapClusterer mapClusterController:nil coordinateForAnnotations:nil inMapRect:MKMapRectNull]; 48 | XCTAssertEqual(coordinate.latitude, 0.0); 49 | XCTAssertEqual(coordinate.longitude, 0.0); 50 | } 51 | 52 | - (void)testCoordinateForAnnotationsEmpty 53 | { 54 | NSMutableSet *annotations = [[NSMutableSet alloc] init]; 55 | CLLocationCoordinate2D coordinate = [self.mapClusterer mapClusterController:nil coordinateForAnnotations:annotations inMapRect:MKMapRectNull]; 56 | XCTAssertEqual(coordinate.latitude, 0.0); 57 | XCTAssertEqual(coordinate.longitude, 0.0); 58 | } 59 | 60 | - (void)testCoordinateForAnnotations 61 | { 62 | NSMutableSet *annotations = [[NSMutableSet alloc] initWithCapacity:4]; 63 | MKPointAnnotation *annotation0 = [[MKPointAnnotation alloc] init]; 64 | annotation0.coordinate = CLLocationCoordinate2DMake(10, 0); 65 | [annotations addObject:annotation0]; 66 | MKPointAnnotation *annotation1 = [[MKPointAnnotation alloc] init]; 67 | annotation1.coordinate = CLLocationCoordinate2DMake(10, 10); 68 | [annotations addObject:annotation1]; 69 | MKPointAnnotation *annotation2 = [[MKPointAnnotation alloc] init]; 70 | annotation2.coordinate = CLLocationCoordinate2DMake(10, 20); 71 | [annotations addObject:annotation2]; 72 | MKPointAnnotation *annotation3 = [[MKPointAnnotation alloc] init]; 73 | annotation3.coordinate = CLLocationCoordinate2DMake(10, 30); 74 | [annotations addObject:annotation3]; 75 | 76 | CLLocationCoordinate2D averageCoordinate = CLLocationCoordinate2DMake(40 / 4, 60 / 4); 77 | CLLocationCoordinate2D coordinate = [self.mapClusterer mapClusterController:nil coordinateForAnnotations:annotations inMapRect:MKMapRectNull]; 78 | XCTAssertEqualWithAccuracy(averageCoordinate.latitude, coordinate.latitude, __FLT_EPSILON__); 79 | XCTAssertEqualWithAccuracy(averageCoordinate.longitude, coordinate.longitude, __FLT_EPSILON__); 80 | } 81 | 82 | @end 83 | -------------------------------------------------------------------------------- /CCHMapClusterController Tests/CCHNearCenterMapClustererTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // CCHNearCenterMapClustererTests.m 3 | // CCHMapClusterController 4 | // 5 | // Copyright (C) 2013 Claus Höfele 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | // 25 | 26 | #import "CCHNearCenterMapClusterer.h" 27 | 28 | #import 29 | 30 | @interface CCHNearCenterMapClustererTests : XCTestCase 31 | 32 | @property (nonatomic) CCHNearCenterMapClusterer *mapClusterer; 33 | 34 | @end 35 | 36 | @implementation CCHNearCenterMapClustererTests 37 | 38 | - (void)setUp 39 | { 40 | [super setUp]; 41 | 42 | self.mapClusterer = [[CCHNearCenterMapClusterer alloc] init]; 43 | } 44 | 45 | - (void)testCoordinateForAnnotationsNil 46 | { 47 | CLLocationCoordinate2D coordinate = [self.mapClusterer mapClusterController:nil coordinateForAnnotations:nil inMapRect:MKMapRectNull]; 48 | XCTAssertEqual(coordinate.latitude, 0.0); 49 | XCTAssertEqual(coordinate.longitude, 0.0); 50 | } 51 | 52 | - (void)testCoordinateForAnnotationsEmpty 53 | { 54 | NSMutableSet *annotations = [[NSMutableSet alloc] init]; 55 | CLLocationCoordinate2D coordinate = [self.mapClusterer mapClusterController:nil coordinateForAnnotations:annotations inMapRect:MKMapRectNull]; 56 | XCTAssertEqual(coordinate.latitude, 0.0); 57 | XCTAssertEqual(coordinate.longitude, 0.0); 58 | } 59 | 60 | - (void)testCoordinateForAnnotations 61 | { 62 | MKMapPoint mapPoint = MKMapPointForCoordinate(CLLocationCoordinate2DMake(45, 45)); 63 | MKMapRect mapRect = MKMapRectMake(mapPoint.x, mapPoint.y, 0, 0); 64 | mapRect = MKMapRectInset(mapRect, -10000, -10000); 65 | 66 | NSMutableSet *annotations = [[NSMutableSet alloc] initWithCapacity:4]; 67 | MKPointAnnotation *annotation0 = [[MKPointAnnotation alloc] init]; 68 | annotation0.coordinate = CLLocationCoordinate2DMake(40, 40); 69 | [annotations addObject:annotation0]; 70 | MKPointAnnotation *annotation1 = [[MKPointAnnotation alloc] init]; 71 | annotation1.coordinate = CLLocationCoordinate2DMake(47, 47); 72 | [annotations addObject:annotation1]; 73 | MKPointAnnotation *annotation2 = [[MKPointAnnotation alloc] init]; 74 | annotation2.coordinate = CLLocationCoordinate2DMake(45.1, 44.9); 75 | [annotations addObject:annotation2]; 76 | MKPointAnnotation *annotation3 = [[MKPointAnnotation alloc] init]; 77 | annotation3.coordinate = CLLocationCoordinate2DMake(42.1, 43.7); 78 | [annotations addObject:annotation3]; 79 | 80 | CLLocationCoordinate2D coordinate = [self.mapClusterer mapClusterController:nil coordinateForAnnotations:annotations inMapRect:mapRect]; 81 | XCTAssertEqualWithAccuracy(annotation2.coordinate.latitude, coordinate.latitude, __FLT_EPSILON__); 82 | XCTAssertEqualWithAccuracy(annotation2.coordinate.longitude, coordinate.longitude, __FLT_EPSILON__); 83 | } 84 | 85 | @end 86 | -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/ClusterAnnotationView.m: -------------------------------------------------------------------------------- 1 | // 2 | // ClusterAnnotationView.m 3 | // CCHMapClusterController Example iOS 4 | // 5 | // Created by Hoefele, Claus(choefele) on 09.01.14. 6 | // Copyright (c) 2014 Claus Höfele. All rights reserved. 7 | // 8 | 9 | // Based on https://github.com/thoughtbot/TBAnnotationClustering/blob/master/TBAnnotationClustering/TBClusterAnnotationView.m by Theodore Calmes 10 | 11 | #import "ClusterAnnotationView.h" 12 | 13 | @interface ClusterAnnotationView () 14 | 15 | @property (nonatomic) UILabel *countLabel; 16 | 17 | @end 18 | 19 | @implementation ClusterAnnotationView 20 | 21 | - (instancetype)initWithAnnotation:(id)annotation reuseIdentifier:(NSString *)reuseIdentifier 22 | { 23 | self = [super initWithAnnotation:annotation reuseIdentifier:reuseIdentifier]; 24 | if (self) { 25 | self.backgroundColor = [UIColor clearColor]; 26 | [self setUpLabel]; 27 | [self setCount:1]; 28 | } 29 | return self; 30 | } 31 | 32 | - (void)setUpLabel 33 | { 34 | _countLabel = [[UILabel alloc] initWithFrame:self.bounds]; 35 | _countLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 36 | _countLabel.textAlignment = NSTextAlignmentCenter; 37 | _countLabel.backgroundColor = [UIColor clearColor]; 38 | _countLabel.textColor = [UIColor whiteColor]; 39 | _countLabel.textAlignment = NSTextAlignmentCenter; 40 | _countLabel.adjustsFontSizeToFitWidth = YES; 41 | _countLabel.minimumScaleFactor = 2; 42 | _countLabel.numberOfLines = 1; 43 | _countLabel.font = [UIFont boldSystemFontOfSize:12]; 44 | _countLabel.baselineAdjustment = UIBaselineAdjustmentAlignCenters; 45 | 46 | [self addSubview:_countLabel]; 47 | } 48 | 49 | - (void)setCount:(NSUInteger)count 50 | { 51 | _count = count; 52 | 53 | self.countLabel.text = [@(count) stringValue]; 54 | [self setNeedsLayout]; 55 | } 56 | 57 | - (void)setBlue:(BOOL)blue 58 | { 59 | _blue = blue; 60 | [self setNeedsLayout]; 61 | } 62 | 63 | - (void)setUniqueLocation:(BOOL)uniqueLocation 64 | { 65 | _uniqueLocation = uniqueLocation; 66 | [self setNeedsLayout]; 67 | } 68 | 69 | - (void)layoutSubviews 70 | { 71 | // Images are faster than using drawRect: 72 | UIImage *image; 73 | CGPoint centerOffset; 74 | CGRect countLabelFrame; 75 | if (self.isUniqueLocation) { 76 | NSString *imageName = self.isBlue ? @"SquareBlue" : @"SquareRed"; 77 | image = [UIImage imageNamed:imageName]; 78 | centerOffset = CGPointMake(0, image.size.height * 0.5); 79 | CGRect frame = self.bounds; 80 | frame.origin.y -= 2; 81 | countLabelFrame = frame; 82 | } else { 83 | NSString *suffix; 84 | if (self.count > 1000) { 85 | suffix = @"39"; 86 | } else if (self.count > 500) { 87 | suffix = @"38"; 88 | } else if (self.count > 200) { 89 | suffix = @"36"; 90 | } else if (self.count > 100) { 91 | suffix = @"34"; 92 | } else if (self.count > 50) { 93 | suffix = @"31"; 94 | } else if (self.count > 20) { 95 | suffix = @"28"; 96 | } else if (self.count > 10) { 97 | suffix = @"25"; 98 | } else if (self.count > 5) { 99 | suffix = @"24"; 100 | } else { 101 | suffix = @"21"; 102 | } 103 | 104 | NSString *imageName = [NSString stringWithFormat:@"%@%@", self.isBlue ? @"CircleBlue" : @"CircleRed", suffix]; 105 | image = [UIImage imageNamed:imageName]; 106 | 107 | centerOffset = CGPointZero; 108 | countLabelFrame = self.bounds; 109 | } 110 | 111 | self.countLabel.frame = countLabelFrame; 112 | self.image = image; 113 | self.centerOffset = centerOffset; 114 | } 115 | 116 | @end 117 | -------------------------------------------------------------------------------- /CCHMapClusterController Example OS X/CCHMapClusterController Example OS X.xcodeproj/xcshareddata/xcschemes/CCHMapClusterController Example OS X.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 66 | 72 | 73 | 74 | 75 | 76 | 77 | 83 | 85 | 91 | 92 | 93 | 94 | 96 | 97 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS.xcodeproj/xcshareddata/xcschemes/CCHMapClusterController Example iOS.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 | 67 | 73 | 74 | 75 | 76 | 77 | 78 | 84 | 86 | 92 | 93 | 94 | 95 | 97 | 98 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /CCHMapClusterController/CCHMapClusterController.h: -------------------------------------------------------------------------------- 1 | // 2 | // CCHMapClusterController.h 3 | // CCHMapClusterController 4 | // 5 | // Copyright (C) 2013 Claus Höfele 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | // 25 | 26 | #import 27 | #import 28 | 29 | @protocol CCHMapClusterControllerDelegate; 30 | @protocol CCHMapClusterer; 31 | @protocol CCHMapAnimator; 32 | 33 | /** 34 | Controller to cluster annotations. Automatically updates clustering when user zooms or pans the map. 35 | */ 36 | @interface CCHMapClusterController : NSObject 37 | 38 | /** Clustered annotations. */ 39 | @property (nonatomic, copy, readonly) NSSet *annotations; 40 | /** Map view to display clustered annotations. */ 41 | @property (nonatomic, readonly) MKMapView *mapView; 42 | 43 | /** Multiplier to extend visible area that's included for clustering (default: 0.5). */ 44 | @property (nonatomic) double marginFactor; 45 | /** Cell size in [points] (default: 60). */ 46 | @property (nonatomic) double cellSize; 47 | 48 | /** The current zoom level of the visible map region. A zoom level of 0 means that the entire map fits 49 | the screen width. The value increases while zooming in. */ 50 | @property (nonatomic, readonly) double zoomLevel; 51 | /** The maximum zoom level before clustering will be disabled and each annotation on the map will 52 | have a unique location (default: `DBL_MAX`). */ 53 | @property (nonatomic) double maxZoomLevelForClustering; 54 | 55 | /** The minimum number of unique locations before a cell gets clustered (default: 0). */ 56 | @property (nonatomic) NSUInteger minUniqueLocationsForClustering; 57 | 58 | /** Delegate to configure cluster annotations. */ 59 | @property (nonatomic, weak) id delegate; 60 | 61 | /** Strategy for positioning cluster annotations (default: `CCHCenterOfMassMapClusterer`). */ 62 | @property (nonatomic, weak) id clusterer; 63 | /** Reuse existing cluster annotations for a cell (default: `YES`). */ 64 | @property (nonatomic) BOOL reuseExistingClusterAnnotations; 65 | 66 | /** Strategy for animating cluster annotations in and out (default: `CCHFadeInOutMapAnimator`). */ 67 | @property (nonatomic, weak) id animator; 68 | 69 | /** Displays the grid used for clustering. */ 70 | @property (nonatomic, getter = isDebuggingEnabled) BOOL debuggingEnabled; 71 | 72 | /** 73 | Initializes the cluster controller. 74 | @param mapView `MKMapView` to use to display clusters. 75 | */ 76 | - (instancetype)initWithMapView:(MKMapView *)mapView; 77 | 78 | /** 79 | Adds annotations and immediately updates clustering. 80 | @param annotations Annotations to add. 81 | @param completionHandler Called when the clustering finished updating. 82 | */ 83 | - (void)addAnnotations:(NSArray *)annotations withCompletionHandler:(void (^)(void))completionHandler; 84 | 85 | /** 86 | Removes annotations and immediately updates clustering. 87 | @param annotations Annotations to add. 88 | @param completionHandler Called when the clustering finished updating. 89 | */ 90 | - (void)removeAnnotations:(NSArray *)annotations withCompletionHandler:(void (^)(void))completionHandler; 91 | 92 | /** 93 | Zooms to the position of the cluster that contains the given annotation and selects the cluster's annotation view. 94 | @param annotation The annotation to look for. Uses `isEqual:` to check for a matching annotation previously added with `addAnnotations:withCompletionHandler:`. 95 | @param latitudinalMeters North-to-south distance used for zooming. 96 | @param longitudinalMeters East-to-west distance used for zooming. 97 | */ 98 | - (void)selectAnnotation:(id)annotation andZoomToRegionWithLatitudinalMeters:(CLLocationDistance)latitudinalMeters longitudinalMeters:(CLLocationDistance)longitudinalMeters; 99 | 100 | @end 101 | -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOS/DataReader.m: -------------------------------------------------------------------------------- 1 | // 2 | // DataReader.m 3 | // Macoun 2013 4 | // 5 | // Created by Hoefele, Claus(choefele) on 20.09.13. 6 | // Copyright (c) 2013 Hoefele, Claus(choefele). All rights reserved. 7 | // 8 | 9 | #import "DataReader.h" 10 | 11 | #import "DataReaderDelegate.h" 12 | 13 | #import 14 | 15 | #define BATCH_COUNT 500 16 | #define DELAY_BETWEEN_BATCHES 0.3 17 | 18 | @interface DataReader() 19 | 20 | @property (nonatomic) NSOperationQueue *operationQueue; 21 | 22 | @end 23 | 24 | @implementation DataReader 25 | 26 | - (instancetype)init 27 | { 28 | self = [super init]; 29 | if (self) { 30 | _operationQueue = [[NSOperationQueue alloc] init]; 31 | _operationQueue.maxConcurrentOperationCount = 1; 32 | } 33 | 34 | return self; 35 | } 36 | 37 | - (void)startReadingBerlinData 38 | { 39 | // Parse on background thread 40 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{ 41 | NSString *file = [NSBundle.mainBundle pathForResource:@"Berlin-Data" ofType:@"json"]; 42 | NSInputStream *inputStream = [NSInputStream inputStreamWithFileAtPath:file]; 43 | [inputStream open]; 44 | NSArray *dataAsJson = [NSJSONSerialization JSONObjectWithStream:inputStream options:0 error:nil]; 45 | 46 | NSMutableArray *annotations = [NSMutableArray arrayWithCapacity:BATCH_COUNT]; 47 | for (NSDictionary *annotationAsJSON in dataAsJson) { 48 | // Convert JSON into annotation object 49 | MKPointAnnotation *annotation = [[MKPointAnnotation alloc] init]; 50 | NSString *latitudeAsString = [annotationAsJSON valueForKeyPath:@"location.coordinates.latitude"]; 51 | NSString *longitudeAsString = [annotationAsJSON valueForKeyPath:@"location.coordinates.longitude"]; 52 | annotation.coordinate = CLLocationCoordinate2DMake(latitudeAsString.doubleValue, longitudeAsString.doubleValue); 53 | annotation.title = [annotationAsJSON valueForKeyPath:@"person.lastName"]; 54 | 55 | [annotations addObject:annotation]; 56 | 57 | if (annotations.count == BATCH_COUNT) { 58 | // Dispatch batch of annotations 59 | [self dispatchAnnotations:annotations]; 60 | [annotations removeAllObjects]; 61 | } 62 | } 63 | 64 | // Dispatch remaining annotations 65 | [self dispatchAnnotations:annotations]; 66 | }); 67 | } 68 | 69 | - (void)startReadingUSData 70 | { 71 | // Parse on background thread 72 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{ 73 | NSString *file = [NSBundle.mainBundle pathForResource:@"USA-HotelMotel" ofType:@"csv"]; 74 | NSArray *lines = [[NSString stringWithContentsOfFile:file encoding:NSASCIIStringEncoding error:nil] componentsSeparatedByString:@"\n"]; 75 | 76 | NSMutableArray *annotations = [NSMutableArray arrayWithCapacity:BATCH_COUNT]; 77 | for (NSString *line in lines) { 78 | NSString *trimmedLine = [line stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceAndNewlineCharacterSet]; 79 | if (trimmedLine.length > 0) { 80 | // Convert CSV into annotation object 81 | MKPointAnnotation *annotation = [[MKPointAnnotation alloc] init]; 82 | NSArray *components = [line componentsSeparatedByString:@","]; 83 | annotation.coordinate = CLLocationCoordinate2DMake([components[1] doubleValue], [components[0] doubleValue]); 84 | annotation.title = [components[2] stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceAndNewlineCharacterSet]; 85 | 86 | [annotations addObject:annotation]; 87 | 88 | if (annotations.count == BATCH_COUNT) { 89 | // Dispatch batch of annotations 90 | [self dispatchAnnotations:annotations]; 91 | [annotations removeAllObjects]; 92 | } 93 | } 94 | } 95 | 96 | // Dispatch remaining annotations 97 | [self dispatchAnnotations:annotations]; 98 | }); 99 | } 100 | 101 | - (void)stopReadingData 102 | { 103 | [self.operationQueue cancelAllOperations]; 104 | } 105 | 106 | - (void)dispatchAnnotations:(NSArray *)annotations 107 | { 108 | // Dispatch on main thread with some delay to simulate network requests 109 | NSArray *annotationsToDispatch = [annotations copy]; 110 | [self.operationQueue addOperationWithBlock:^{ 111 | dispatch_async(dispatch_get_main_queue(), ^{ 112 | [self.delegate dataReader:self addAnnotations:annotationsToDispatch]; 113 | }); 114 | [NSThread sleepForTimeInterval:DELAY_BETWEEN_BATCHES]; 115 | }]; 116 | } 117 | 118 | @end 119 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | Changes 2 | ======= 3 | ## 1.7.2 4 | - Fix warnings when building with CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF. Thanks Gareth Redman for the PR. 5 | 6 | ## 1.7.1 7 | - Fix block declaration warnings and update project files to Xcode 9.2. Thanks @damarte for the PR. 8 | - Fix crash due to usage of KVO #136 9 | 10 | ## 1.7.0 11 | - Set minimum deployment target to iOS 7.0 and fix deprecation warnings. 12 | 13 | ## 1.6.6 14 | - Fix implementation of `annotations` property. Thanks to tarbrain for the code. 15 | 16 | ## 1.6.5 17 | - Make `annotations` property thread-safe. Thanks for alxon and nverinaud for reporting this problem. 18 | 19 | ## 1.6.4 20 | - The completion handlers for `addAnnotations:withCompletionHandler:` and `removeAnnotations:withCompletionHandler:` are now guaranteed to be called on the main thread. Thanks to robertjpayne for the pull request 21 | 22 | ## 1.6.3 23 | - `CCHMapClusterController` now builds with Xcode 6/iOS 8 (thanks to detouch for the pull request and rosskimes for letting me know about the problem) 24 | 25 | ## 1.6.2 26 | - Excluded private header files from pod 27 | - Updated documentation 28 | 29 | ## 1.6.1 30 | 31 | - Bug fix for missing `CCHMapClusterController` instance in delegate methods (thanks to Palleas for the pull request) 32 | - Added recipe to describe how `MKMapView` handles taps on annotation views (thanks to thomasouk for the question) 33 | 34 | ## 1.6.0 35 | 36 | - `CCHMapClusterController` now has a new property `maxZoomLevelForClustering`, which disables clustering if the current zoom level exceeds this value. When disabled, all cluster annotations on the map cluster will have one unique location. The current zoom level can be queried with the property `zoomLevel`. Thanks to tspacek for the code and onato and iGriever for suggesting this feature. 37 | - There's also a new property `minUniqueLocationsForClustering` that controls clustering for a cell based on the number of unique locations in a cell. Clustering is disabled if the number of unique locations in a cell is below this value. 38 | - Renamed property `isOneLocation` in `CCHMapClusterAnnotation` to `isUniqueLocation` 39 | - Removed asserts that triggered the exception 'Invalid map length' because my assumption that this could never happen was wrong. Thanks to zeyadsalloum and jas54 for helping me find this issue 40 | - Fixed crash that was happening occasionally because the map view was accessed on a background thread. Thanks to zeyadsalloum, bpoplauschi, igordla, and rosskimes for helping me debug this issue 41 | 42 | ## 1.5.0 43 | 44 | - Multiple `CCHMapClusterController`s can now use the same `MKMapView` instance. This allows you to have multiple groups of clusters where each group has its own settings (thanks to eikebartles for ideas and suggestions) 45 | - `selectAnnotation:andZoomToRegionWithLatitudinalMeters:longitudinalMeters:` will now assert that the right annotation has been passed in. The documentation has also been updated with a better example on how to use this API (thanks to tspacek for the request) 46 | - Added the method `mapRect` to `CCHMapClusterAnnotation` to calculate the area that includes all clustered annotations. Also added a code recipe on how to use this method to zoom in to a cluster. 47 | - Updated code recipe for centering the map without changing the zoom level (thanks to plu for suggesting this) 48 | - Added code recipe for showing callout accessory views dynamically (thanks to SSA111 for suggesting this) 49 | - Added unit tests for animation code (thanks to nferruzzi for the pull request) 50 | 51 | ## 1.4.1 52 | 53 | - Fixed issue where non-clustered `MKAnnotation`s would be removed from the map view (thanks to rosskimes for the pull request) 54 | - Fixed bug that was causing `MKOverlayView`s/`MKOverlayRenderer`s to not show up (thanks to rosskimes for the pull request) 55 | - Added a settings UI in example to configure clustering 56 | - Updated annotation view in example to use pre-rendered images instead of `drawRect:` for best performance 57 | 58 | ## 1.4.0 59 | 60 | - Added `removeAnnotations:withCompletionHandler:` to `CCHMapClusterController` to remove annotations from clustering (thanks to zeyadsalloum for suggesting this feature) 61 | - Fixed bug when rotating map view 62 | 63 | ## 1.3.0 64 | 65 | - Added new delegate method `mapClusterController:willReuseMapClusterAnnotation:` to `CCHMapClusterControllerDelegate` that's called when cluster annotations are reused 66 | - Example updated to demonstrate annotation views which adapt to current cluster size 67 | - Bugfixes and performance improvements 68 | - Thanks to onato for suggesting these changes 69 | 70 | ## 1.2.0 71 | 72 | - Switched to quad tree based on `TBQuadTree` to speed up performance 73 | - Clustering now happens on a background thread for improved responsiveness 74 | - Fixed issues when panning across the 180th meridian 75 | 76 | ## 1.1.0 77 | 78 | - Added option to configure positioning of cluster annotations 79 | - Added option to enable/disable reusing existing cluster annotations 80 | - Added option to configure how cluster annotations are animated 81 | - Added more unit tests and documentation 82 | 83 | ## 1.0.1 84 | 85 | - Initial release 86 | -------------------------------------------------------------------------------- /CCHMapClusterController/CCHMapTree.m: -------------------------------------------------------------------------------- 1 | // 2 | // CCHMapTree.m 3 | // CCHMapClusterController 4 | // 5 | // Copyright (C) 2013 Claus Höfele 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | // 25 | 26 | #import "CCHMapTree.h" 27 | 28 | #import "CCHMapTreeUtils.h" 29 | 30 | @interface CCHMapTree() 31 | 32 | @property (nonatomic) NSMutableSet *mutableAnnotations; 33 | @property (nonatomic) CCHMapTreeNode *root; 34 | @property (nonatomic) NSUInteger nodeCapacity; 35 | 36 | @end 37 | 38 | @implementation CCHMapTree 39 | 40 | - (instancetype)init 41 | { 42 | return [self initWithNodeCapacity:10 minLatitude:-85.0 maxLatitude:85.0 minLongitude:-180.0 maxLongitude:180.0]; 43 | } 44 | 45 | - (instancetype)initWithNodeCapacity:(NSUInteger)nodeCapacity minLatitude:(double)minLatitude maxLatitude:(double)maxLatitude minLongitude:(double)minLongitude maxLongitude:(double)maxLongitude 46 | { 47 | self = [super init]; 48 | if (self) { 49 | _nodeCapacity = nodeCapacity; 50 | _mutableAnnotations = [NSMutableSet set]; 51 | CCHMapTreeBoundingBox world = CCHMapTreeBoundingBoxMake(minLatitude, minLongitude, maxLatitude, maxLongitude); 52 | _root = CCHMapTreeBuildWithData(NULL, 0, world, nodeCapacity); 53 | } 54 | 55 | return self; 56 | } 57 | 58 | - (void)dealloc 59 | { 60 | CCHMapTreeFreeQuadTreeNode(self.root); 61 | } 62 | 63 | - (NSSet *)annotations 64 | { 65 | return [self.mutableAnnotations copy]; 66 | } 67 | 68 | - (BOOL)addAnnotations:(NSArray *)annotations 69 | { 70 | BOOL updated = NO; 71 | 72 | NSMutableSet *mutableAnnotations = self.mutableAnnotations; 73 | for (id annotation in annotations) { 74 | if (![mutableAnnotations containsObject:annotation]) { 75 | CCHMapTreeNodeData data = CCHMapTreeNodeDataMake(annotation.coordinate.latitude, annotation.coordinate.longitude, (__bridge void *)annotation); 76 | if (CCHMapTreeNodeInsertData(_root, data, (int)_nodeCapacity)) { 77 | updated = YES; 78 | [mutableAnnotations addObject:annotation]; 79 | } 80 | } 81 | } 82 | 83 | return updated; 84 | } 85 | 86 | - (BOOL)removeAnnotations:(NSArray *)annotations 87 | { 88 | BOOL updated = NO; 89 | 90 | NSMutableSet *mutableAnnotations = self.mutableAnnotations; 91 | for (id annotation in annotations) { 92 | id member = [mutableAnnotations member:annotation]; 93 | if (member) { 94 | CCHMapTreeNodeData data = CCHMapTreeNodeDataMake(annotation.coordinate.latitude, annotation.coordinate.longitude, (__bridge void *)member); 95 | if (CCHMapTreeNodeRemoveData(_root, data)) { 96 | updated = YES; 97 | [mutableAnnotations removeObject:annotation]; 98 | } 99 | } 100 | } 101 | 102 | return updated; 103 | } 104 | 105 | CCHMapTreeBoundingBox CCHMapTreeBoundingBoxForMapRect(MKMapRect mapRect) 106 | { 107 | CLLocationCoordinate2D topLeft = MKCoordinateForMapPoint(mapRect.origin); 108 | CLLocationCoordinate2D botRight = MKCoordinateForMapPoint(MKMapPointMake(MKMapRectGetMaxX(mapRect), MKMapRectGetMaxY(mapRect))); 109 | 110 | CLLocationDegrees minLat = botRight.latitude; 111 | CLLocationDegrees maxLat = topLeft.latitude; 112 | CLLocationDegrees minLon = topLeft.longitude; 113 | CLLocationDegrees maxLon = botRight.longitude; 114 | 115 | return CCHMapTreeBoundingBoxMake(minLat, minLon, maxLat, maxLon); 116 | } 117 | 118 | - (NSSet *)annotationsInMapRect:(MKMapRect)mapRect 119 | { 120 | CCHMapTreeUnsafeMutableArray *annotations = [[CCHMapTreeUnsafeMutableArray alloc] initWithCapacity:10]; 121 | CCHMapTreeGatherDataInRange3(self.root, CCHMapTreeBoundingBoxForMapRect(mapRect), annotations); 122 | NSSet *annotationsAsSet = [NSSet setWithObjects:annotations.objects count:annotations.numObjects]; 123 | 124 | return annotationsAsSet; 125 | } 126 | @end 127 | -------------------------------------------------------------------------------- /CCHMapClusterController Tests/CCHMapClusterAnnotationTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // CCHMapClusterAnnotationTests.m 3 | // CCHMapClusterController 4 | // 5 | // Copyright (C) 2013 Claus Höfele 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | // 25 | 26 | #import 27 | 28 | #import "CCHMapClusterAnnotation.h" 29 | 30 | @interface CCHMapClusterAnnotationTests : XCTestCase 31 | 32 | @property (nonatomic) CCHMapClusterAnnotation *clusterAnnotation; 33 | 34 | @end 35 | 36 | @implementation CCHMapClusterAnnotationTests 37 | 38 | - (void)setUp 39 | { 40 | [super setUp]; 41 | 42 | self.clusterAnnotation = [[CCHMapClusterAnnotation alloc] init]; 43 | } 44 | 45 | - (void)testIsCluster 46 | { 47 | MKPointAnnotation *annotation0 = [[MKPointAnnotation alloc] init]; 48 | MKPointAnnotation *annotation1 = [[MKPointAnnotation alloc] init]; 49 | self.clusterAnnotation.annotations = [NSSet setWithArray:@[annotation0, annotation1]]; 50 | XCTAssertTrue(self.clusterAnnotation.isCluster); 51 | } 52 | 53 | - (void)testIsNotCluster 54 | { 55 | XCTAssertFalse(self.clusterAnnotation.isCluster); 56 | MKPointAnnotation *annotation = [[MKPointAnnotation alloc] init]; 57 | self.clusterAnnotation.annotations = [NSSet setWithArray:@[annotation]]; 58 | XCTAssertFalse(self.clusterAnnotation.isCluster); 59 | } 60 | 61 | - (void)testIsUniqueLocation 62 | { 63 | MKPointAnnotation *annotation0 = [[MKPointAnnotation alloc] init]; 64 | annotation0.coordinate = CLLocationCoordinate2DMake(50.0, 12.0); 65 | MKPointAnnotation *annotation1 = [[MKPointAnnotation alloc] init]; 66 | annotation1.coordinate = annotation0.coordinate; 67 | 68 | self.clusterAnnotation.annotations = [NSSet setWithArray:@[annotation0]]; 69 | XCTAssertTrue(self.clusterAnnotation.isUniqueLocation); 70 | 71 | self.clusterAnnotation.annotations = [NSSet setWithArray:@[annotation0, annotation1]]; 72 | XCTAssertTrue(self.clusterAnnotation.isUniqueLocation); 73 | } 74 | 75 | - (void)testIsUniqueLocationFalse 76 | { 77 | MKPointAnnotation *annotation0 = [[MKPointAnnotation alloc] init]; 78 | annotation0.coordinate = CLLocationCoordinate2DMake(50.0, 12.0); 79 | MKPointAnnotation *annotation1 = [[MKPointAnnotation alloc] init]; 80 | annotation1.coordinate = CLLocationCoordinate2DMake(50.1, 12.0); 81 | self.clusterAnnotation.annotations = [NSSet setWithArray:@[annotation0, annotation1]]; 82 | XCTAssertFalse(self.clusterAnnotation.isUniqueLocation); 83 | } 84 | 85 | - (void)testMapRectIncludesClusterCoordinate 86 | { 87 | self.clusterAnnotation.coordinate = CLLocationCoordinate2DMake(50.0, 12.0); 88 | MKMapRect mapRect = self.clusterAnnotation.mapRect; 89 | XCTAssertTrue(MKMapRectContainsPoint(mapRect, MKMapPointForCoordinate(self.clusterAnnotation.coordinate))); 90 | } 91 | 92 | - (void)testMapRectSingle 93 | { 94 | MKPointAnnotation *annotation0 = [[MKPointAnnotation alloc] init]; 95 | annotation0.coordinate = CLLocationCoordinate2DMake(50.0, 12.0); 96 | self.clusterAnnotation.annotations = [NSSet setWithArray:@[annotation0]]; 97 | MKMapRect mapRect = self.clusterAnnotation.mapRect; 98 | XCTAssertTrue(MKMapRectContainsPoint(mapRect, MKMapPointForCoordinate(annotation0.coordinate))); 99 | } 100 | 101 | - (void)testMapRectMultiple 102 | { 103 | MKPointAnnotation *annotation0 = [[MKPointAnnotation alloc] init]; 104 | annotation0.coordinate = CLLocationCoordinate2DMake(50.0, 12.0); 105 | MKPointAnnotation *annotation1 = [[MKPointAnnotation alloc] init]; 106 | annotation1.coordinate = CLLocationCoordinate2DMake(10.5, 78.0); 107 | self.clusterAnnotation.annotations = [NSSet setWithArray:@[annotation0, annotation1]]; 108 | MKMapRect mapRect = self.clusterAnnotation.mapRect; 109 | XCTAssertTrue(MKMapRectContainsPoint(mapRect, MKMapPointForCoordinate(annotation0.coordinate))); 110 | XCTAssertTrue(MKMapRectContainsPoint(mapRect, MKMapPointForCoordinate(annotation1.coordinate))); 111 | } 112 | 113 | 114 | @end 115 | -------------------------------------------------------------------------------- /CCHMapClusterController/CCHMapViewDelegateProxy.m: -------------------------------------------------------------------------------- 1 | // 2 | // CCHMapViewDelegateProxy.m 3 | // CCHMapClusterController 4 | // 5 | // Copyright (C) 2013 Claus Höfele 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | // 25 | 26 | #import "CCHMapViewDelegateProxy.h" 27 | 28 | #import "CCHMapClusterControllerDebugPolygon.h" 29 | 30 | @interface CCHMapViewDelegateProxy() 31 | 32 | @property (nonatomic) NSHashTable *delegates; 33 | @property (nonatomic, weak) NSObject *target; 34 | @property (nonatomic) MKMapView *mapView; 35 | 36 | @end 37 | 38 | @implementation CCHMapViewDelegateProxy 39 | 40 | - (instancetype)initWithMapView:(MKMapView *)mapView delegate:(NSObject *)delegate 41 | { 42 | self = [super init]; 43 | if (self) { 44 | _delegates = [[NSHashTable alloc] initWithOptions:NSPointerFunctionsWeakMemory capacity:1]; 45 | [_delegates addObject:delegate]; 46 | _target = mapView.delegate; 47 | _mapView = mapView; 48 | [self swapDelegates]; 49 | } 50 | return self; 51 | } 52 | 53 | - (void)addDelegate:(NSObject *)delegate 54 | { 55 | [self.delegates addObject:delegate]; 56 | } 57 | 58 | - (void)dealloc 59 | { 60 | [self.mapView removeObserver:self forKeyPath:@"delegate"]; 61 | self.mapView.delegate = self.target; 62 | } 63 | 64 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context 65 | { 66 | [self.mapView removeObserver:self forKeyPath:@"delegate"]; 67 | [self swapDelegates]; 68 | } 69 | 70 | - (void)swapDelegates 71 | { 72 | self.target = self.mapView.delegate; 73 | self.mapView.delegate = self; 74 | [self.mapView addObserver:self forKeyPath:@"delegate" options:NSKeyValueObservingOptionNew context:NULL]; 75 | } 76 | 77 | - (BOOL)respondsToSelector:(SEL)selector 78 | { 79 | // Check if selector is implemented in this class 80 | if ([super respondsToSelector:selector]) { 81 | return YES; 82 | } 83 | 84 | // Otherwise, use forwardInvocation: on delegates and target 85 | for (id delegate in self.delegates) { 86 | if ([delegate respondsToSelector:selector]) { 87 | return YES; 88 | } 89 | } 90 | 91 | return [self.target respondsToSelector:selector]; 92 | } 93 | 94 | - (NSMethodSignature *)methodSignatureForSelector:(SEL)selector 95 | { 96 | for (id delegate in self.delegates) { 97 | if ([delegate respondsToSelector:selector]) { 98 | return [delegate methodSignatureForSelector:selector]; 99 | } 100 | } 101 | 102 | return [self.target methodSignatureForSelector:selector]; 103 | } 104 | 105 | - (void)forwardInvocation:(NSInvocation *)invocation 106 | { 107 | for (id delegate in self.delegates) { 108 | if ([delegate respondsToSelector:invocation.selector]) { 109 | [invocation invokeWithTarget:delegate]; 110 | } 111 | } 112 | 113 | if ([self.target respondsToSelector:invocation.selector]) { 114 | [invocation invokeWithTarget:self.target]; 115 | } 116 | } 117 | 118 | #pragma mark - Map view proxied delegate methods 119 | 120 | - (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id)overlay 121 | { 122 | MKOverlayRenderer *renderer; 123 | 124 | // Target can override return value 125 | if ([self.target respondsToSelector:@selector(mapView:rendererForOverlay:)]) { 126 | renderer = [self.target mapView:mapView rendererForOverlay:overlay]; 127 | } 128 | 129 | // Default return value for debug polygons 130 | if (renderer == nil && [overlay isKindOfClass:CCHMapClusterControllerDebugPolygon.class]) { 131 | MKPolygonRenderer *polygonRenderer = [[MKPolygonRenderer alloc] initWithPolygon:(MKPolygon *)overlay]; 132 | #if TARGET_OS_IPHONE 133 | UIColor *color = [UIColor.blueColor colorWithAlphaComponent:0.7]; 134 | #else 135 | NSColor *color = [NSColor.blueColor colorWithAlphaComponent:0.7]; 136 | #endif 137 | polygonRenderer.strokeColor = color; 138 | polygonRenderer.lineWidth = 1; 139 | renderer = polygonRenderer; 140 | } 141 | 142 | return renderer; 143 | } 144 | 145 | @end 146 | -------------------------------------------------------------------------------- /CCHMapClusterController Tests/TBQuadTree/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 | -------------------------------------------------------------------------------- /CCHMapClusterController Tests/KPAnnotationTree/KPAnnotationTree.m: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2012 Bryan Bonczek 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | #import "KPAnnotationTree.h" 18 | #import "KPTreeNode.h" 19 | #import "KPAnnotation.h" 20 | 21 | #if 0 22 | #define BBTreeLog(...) NSLog(__VA_ARGS__) 23 | #else 24 | #define BBTreeLog(...) ((void) 0) 25 | #endif 26 | 27 | @interface KPAnnotationTree () 28 | 29 | @property (nonatomic) KPTreeNode *root; 30 | @property (nonatomic, readwrite) NSSet *annotations; 31 | 32 | @end 33 | 34 | @implementation KPAnnotationTree 35 | 36 | - (id)initWithAnnotations:(NSArray *)annotations { 37 | 38 | self = [super init]; 39 | 40 | if(self){ 41 | self.annotations = [NSSet setWithArray:annotations]; 42 | self.root = [self buildTree:annotations level:0]; 43 | } 44 | 45 | return self; 46 | } 47 | 48 | #pragma mark - Search 49 | 50 | - (NSArray *)annotationsInMapRect:(MKMapRect)rect { 51 | 52 | NSMutableArray *result = [NSMutableArray array]; 53 | 54 | [self doSearchInMapRect:rect 55 | mutableAnnotations:result 56 | curNode:self.root 57 | curLevel:0]; 58 | 59 | return result; 60 | } 61 | 62 | 63 | - (void)doSearchInMapRect:(MKMapRect)mapRect 64 | mutableAnnotations:(NSMutableArray *)annotations 65 | curNode:(KPTreeNode *)curNode 66 | curLevel:(NSInteger)level { 67 | 68 | if(curNode == nil){ 69 | return; 70 | } 71 | 72 | MKMapPoint mapPoint = curNode.mapPoint; 73 | 74 | BBTreeLog(@"Testing (%f, %f)...", [curNode.annotation coordinate].latitude, [curNode.annotation coordinate].longitude); 75 | 76 | if(MKMapRectContainsPoint(mapRect, mapPoint)){ 77 | BBTreeLog(@"YES"); 78 | [annotations addObject:curNode.annotation]; 79 | } 80 | else { 81 | BBTreeLog(@"RECT: NO"); 82 | } 83 | 84 | BOOL useY = (BOOL)(level % 2); 85 | 86 | float val = (useY ? mapPoint.y : mapPoint.x); 87 | float minVal = (useY ? mapRect.origin.y : mapRect.origin.x); 88 | float maxVal = (useY ? (mapRect.origin.y + mapRect.size.height) : (mapRect.origin.x + mapRect.size.width)); 89 | 90 | if(maxVal < val){ 91 | 92 | [self doSearchInMapRect:mapRect 93 | mutableAnnotations:annotations 94 | curNode:curNode.left 95 | curLevel:(level + 1)]; 96 | } 97 | else if(minVal > val){ 98 | 99 | [self doSearchInMapRect:mapRect 100 | mutableAnnotations:annotations 101 | curNode:curNode.right 102 | curLevel:(level + 1)]; 103 | } 104 | else { 105 | 106 | [self doSearchInMapRect:mapRect 107 | mutableAnnotations:annotations 108 | curNode:curNode.left 109 | curLevel:(level + 1)]; 110 | 111 | [self doSearchInMapRect:mapRect 112 | mutableAnnotations:annotations 113 | curNode:curNode.right 114 | curLevel:(level + 1)]; 115 | } 116 | 117 | } 118 | 119 | #pragma mark - MKMapView 120 | 121 | 122 | #pragma mark - Tree Building (Private) 123 | 124 | 125 | - (KPTreeNode *)buildTree:(NSArray *)annotations level:(NSInteger)curLevel { 126 | 127 | NSInteger count = [annotations count]; 128 | 129 | if(count == 0){ 130 | return nil; 131 | } 132 | 133 | KPTreeNode *n = [[KPTreeNode alloc] init]; 134 | 135 | BOOL sortY = (BOOL)(curLevel % 2); 136 | 137 | //TODO: build the tree without sorting at every level 138 | NSArray *sortedAnnotations = [self sortedAnnotations:annotations sortY:sortY]; 139 | 140 | // store median in tree and recurse through left and right sub arrays 141 | NSInteger medianIdx = [sortedAnnotations count] / 2; 142 | 143 | n.annotation = [sortedAnnotations objectAtIndex:medianIdx]; 144 | n.mapPoint = MKMapPointForCoordinate(n.annotation.coordinate); 145 | 146 | n.left = [self buildTree:[sortedAnnotations subarrayWithRange:NSMakeRange(0, medianIdx)] 147 | level:(curLevel + 1)]; 148 | 149 | 150 | n.right = [self buildTree:[sortedAnnotations subarrayWithRange:NSMakeRange((medianIdx + 1), (count - (medianIdx + 1)))] 151 | level:(curLevel + 1)]; 152 | 153 | 154 | return n; 155 | } 156 | 157 | - (NSArray *)sortedAnnotations:(NSArray *)annotations sortY:(BOOL)sortY { 158 | 159 | return [annotations sortedArrayUsingComparator:^NSComparisonResult(id a1, id a2) { 160 | 161 | MKMapPoint p1 = MKMapPointForCoordinate([a1 coordinate]); 162 | MKMapPoint p2 = MKMapPointForCoordinate([a2 coordinate]); 163 | 164 | float val1 = (sortY ? p1.y : p1.x); 165 | float val2 = (sortY ? p2.y : p2.x); 166 | 167 | if(val1 > val2){ 168 | return NSOrderedDescending; 169 | } 170 | else if(val1 < val2){ 171 | return NSOrderedAscending; 172 | } 173 | else { 174 | return NSOrderedSame; 175 | } 176 | 177 | }]; 178 | 179 | } 180 | 181 | @end 182 | -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOSTests/CCHMapViewDelegateProxyViewForOverlayTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // CCHMapViewDelegateProxyReturnValueTests.m 3 | // CCHMapClusterController Example iOS 4 | // 5 | // Created by Hoefele, Claus on 18.02.14. 6 | // Copyright (c) 2014 Claus Höfele. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | #import "CCHMapViewDelegateProxy.h" 13 | #import "CCHMapClusterControllerDebugPolygon.h" 14 | 15 | @interface TestOverlayView0 : MKOverlayView 16 | @end 17 | @implementation TestOverlayView0 18 | @end 19 | 20 | @interface TestOverlayView1 : MKOverlayView 21 | @end 22 | @implementation TestOverlayView1 23 | @end 24 | 25 | #if TARGET_OS_IPHONE 26 | @interface MapViewDelegateReturnsValue : NSObject 27 | @property (nonatomic, assign) Class valueClass; 28 | - (id)initWithValueClass:(Class)valueClass; 29 | - (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id)overlay; 30 | @end 31 | @implementation MapViewDelegateReturnsValue 32 | - (id)initWithValueClass:(Class)valueClass { 33 | self = [super init]; 34 | if (self) { 35 | self.valueClass = valueClass; 36 | } 37 | return self; 38 | } 39 | - (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id)overlay { 40 | return [[self.valueClass alloc] init]; 41 | } 42 | @end 43 | 44 | @interface MapViewDelegateReturnsNil : NSObject 45 | - (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id)overlay; 46 | @end 47 | @implementation MapViewDelegateReturnsNil 48 | - (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id)overlay { 49 | return nil; 50 | } 51 | @end 52 | 53 | @interface MapViewDelegateEmpty : NSObject 54 | @end 55 | @implementation MapViewDelegateEmpty 56 | @end 57 | #endif 58 | 59 | // Remove DEBUG_POLYGON_CLASS 60 | 61 | @interface CCHMapViewDelegateProxyViewForOverlayTests : XCTestCase 62 | 63 | @end 64 | 65 | @implementation CCHMapViewDelegateProxyViewForOverlayTests 66 | 67 | - (MKOverlayView *)viewForOverlay:(id)overlay withMapViewDelegate:(NSObject *)mapViewDelegate proxyDelegate:(NSObject *)proxyDelegate 68 | { 69 | MKMapView *mapView = [[MKMapView alloc] init]; 70 | mapView.delegate = mapViewDelegate; 71 | 72 | CCHMapViewDelegateProxy *delegateProxy = [[CCHMapViewDelegateProxy alloc] initWithMapView:mapView delegate:proxyDelegate]; 73 | (void)delegateProxy; 74 | 75 | MKOverlayView *overlayView = [mapView.delegate mapView:mapView viewForOverlay:overlay]; 76 | return overlayView; 77 | } 78 | 79 | #pragma mark - Map view delegate returns value 80 | 81 | - (void)testMapViewDelegateProxy 82 | { 83 | NSObject *mapViewDelegate = nil; 84 | NSObject *proxyDelegate = nil; 85 | MKOverlayView *view = [self viewForOverlay:[CCHMapClusterControllerDebugPolygon new] withMapViewDelegate:mapViewDelegate proxyDelegate:proxyDelegate]; 86 | XCTAssertTrue([view isMemberOfClass:MKPolygonView.class]); 87 | } 88 | 89 | - (void)testMapViewDelegateProxyWrongClass 90 | { 91 | NSObject *mapViewDelegate = nil; 92 | NSObject *proxyDelegate = nil; 93 | MKOverlayView *view = [self viewForOverlay:[MKCircle new] withMapViewDelegate:mapViewDelegate proxyDelegate:proxyDelegate]; 94 | XCTAssertNil(view); 95 | } 96 | 97 | - (void)testMapViewDelegateHasPriority 98 | { 99 | NSObject *mapViewDelegate = [[MapViewDelegateReturnsValue alloc] initWithValueClass:TestOverlayView0.class]; 100 | NSObject *proxyDelegate = [[MapViewDelegateReturnsValue alloc] initWithValueClass:TestOverlayView1.class]; 101 | MKOverlayView *view = [self viewForOverlay:[CCHMapClusterControllerDebugPolygon new] withMapViewDelegate:mapViewDelegate proxyDelegate:proxyDelegate]; 102 | XCTAssertTrue([view isMemberOfClass:TestOverlayView0.class]); 103 | } 104 | 105 | - (void)testMapViewDelegateProxyIgnoredNil 106 | { 107 | NSObject *mapViewDelegate = nil; 108 | NSObject *proxyDelegate = [[MapViewDelegateReturnsValue alloc] initWithValueClass:TestOverlayView0.class]; 109 | MKOverlayView *view = [self viewForOverlay:[CCHMapClusterControllerDebugPolygon new] withMapViewDelegate:mapViewDelegate proxyDelegate:proxyDelegate]; 110 | XCTAssertTrue([view isMemberOfClass:MKPolygonView.class]); 111 | } 112 | 113 | - (void)testMapViewDelegateProxyIgnoredReturnsNil 114 | { 115 | NSObject *mapViewDelegate = [[MapViewDelegateReturnsNil alloc] init]; 116 | NSObject *proxyDelegate = [[MapViewDelegateReturnsValue alloc] initWithValueClass:TestOverlayView0.class]; 117 | MKOverlayView *view = [self viewForOverlay:[CCHMapClusterControllerDebugPolygon new] withMapViewDelegate:mapViewDelegate proxyDelegate:proxyDelegate]; 118 | XCTAssertTrue([view isMemberOfClass:MKPolygonView.class]); 119 | } 120 | 121 | - (void)testMapViewDelegateProxyIgnoredEmpty 122 | { 123 | NSObject *mapViewDelegate = [[MapViewDelegateEmpty alloc] init]; 124 | NSObject *proxyDelegate = [[MapViewDelegateReturnsValue alloc] initWithValueClass:TestOverlayView0.class]; 125 | MKOverlayView *view = [self viewForOverlay:[CCHMapClusterControllerDebugPolygon new] withMapViewDelegate:mapViewDelegate proxyDelegate:proxyDelegate]; 126 | XCTAssertTrue([view isMemberOfClass:MKPolygonView.class]); 127 | } 128 | 129 | //#pragma mark - Test implementation 130 | // 131 | //- (void)testOverlayClassCorrect 132 | //{ 133 | // NSObject *proxyDelegate = [[MapViewDelegateReturnsValue alloc] init]; 134 | // id overlay = [[DEBUG_POLYGON_CLASS alloc] init]; 135 | // MKOverlayView *view = [self viewForOverlay:overlay withMapViewDelegate:nil proxyDelegate:proxyDelegate]; 136 | // XCTAssertNotNil(view); 137 | //} 138 | // 139 | //- (void)testOverlayClassWrong 140 | //{ 141 | // NSObject *proxyDelegate = [[MapViewDelegateReturnsValue alloc] init]; 142 | // id overlay = [[MKCircle alloc] init]; 143 | // MKOverlayView *view = [self viewForOverlay:overlay withMapViewDelegate:nil proxyDelegate:proxyDelegate]; 144 | // XCTAssertNil(view); 145 | //} 146 | // 147 | @end 148 | -------------------------------------------------------------------------------- /CCHMapClusterController Tests/CCHMapViewDelegateProxyOverlayTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // CCHMapViewDelegateProxyOverlayTests.m 3 | // CCHMapClusterController 4 | // 5 | // Copyright (C) 2014 Claus Höfele 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | // 25 | 26 | #import "CCHMapViewDelegateProxy.h" 27 | #import "CCHMapClusterControllerDebugPolygon.h" 28 | 29 | #import 30 | #import 31 | 32 | @interface TestOverlayRenderer0 : MKOverlayRenderer 33 | @end 34 | @implementation TestOverlayRenderer0 35 | @end 36 | 37 | @interface TestOverlayRenderer1 : MKOverlayRenderer 38 | @end 39 | @implementation TestOverlayRenderer1 40 | @end 41 | 42 | @interface MapViewDelegateReturnsValue : NSObject 43 | @property (nonatomic, assign) Class valueClass; 44 | - (instancetype)initWithValueClass:(Class)valueClass; 45 | - (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id)overlay; 46 | @end 47 | @implementation MapViewDelegateReturnsValue 48 | - (instancetype)initWithValueClass:(Class)valueClass { 49 | self = [super init]; 50 | if (self) { 51 | self.valueClass = valueClass; 52 | } 53 | return self; 54 | } 55 | - (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id)overlay { 56 | return [[self.valueClass alloc] init]; 57 | } 58 | @end 59 | 60 | @interface MapViewDelegateReturnsNil : NSObject 61 | - (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id)overlay; 62 | @end 63 | @implementation MapViewDelegateReturnsNil 64 | - (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id)overlay { 65 | return nil; 66 | } 67 | @end 68 | 69 | @interface MapViewDelegateEmpty : NSObject 70 | @end 71 | @implementation MapViewDelegateEmpty 72 | @end 73 | 74 | @interface CCHMapViewDelegateProxyOverlayTests : XCTestCase 75 | 76 | @end 77 | 78 | @implementation CCHMapViewDelegateProxyOverlayTests 79 | 80 | - (MKOverlayRenderer *)rendererForOverlay:(id)overlay withMapViewDelegate:(NSObject *)mapViewDelegate proxyDelegate:(NSObject *)proxyDelegate 81 | { 82 | MKMapView *mapView = [[MKMapView alloc] init]; 83 | mapView.delegate = mapViewDelegate; 84 | 85 | CCHMapViewDelegateProxy *delegateProxy = [[CCHMapViewDelegateProxy alloc] initWithMapView:mapView delegate:proxyDelegate]; 86 | (void)delegateProxy; 87 | 88 | MKOverlayRenderer *overlayRenderer; 89 | if ([mapView.delegate respondsToSelector:@selector(mapView:rendererForOverlay:)]) { 90 | overlayRenderer = [mapView.delegate mapView:mapView rendererForOverlay:overlay]; 91 | } 92 | return overlayRenderer; 93 | } 94 | 95 | - (void)testMapViewDelegateProxy 96 | { 97 | NSObject *mapViewDelegate = nil; 98 | NSObject *proxyDelegate = nil; 99 | MKOverlayRenderer *view = [self rendererForOverlay:[CCHMapClusterControllerDebugPolygon new] withMapViewDelegate:mapViewDelegate proxyDelegate:proxyDelegate]; 100 | XCTAssertTrue([view isMemberOfClass:MKPolygonRenderer.class]); 101 | } 102 | 103 | - (void)testMapViewDelegateProxyWrongClass 104 | { 105 | NSObject *mapViewDelegate = nil; 106 | NSObject *proxyDelegate = nil; 107 | MKOverlayRenderer *renderer = [self rendererForOverlay:[MKCircle new] withMapViewDelegate:mapViewDelegate proxyDelegate:proxyDelegate]; 108 | XCTAssertNil(renderer); 109 | } 110 | 111 | - (void)testMapViewDelegateHasPriority 112 | { 113 | NSObject *mapViewDelegate = [[MapViewDelegateReturnsValue alloc] initWithValueClass:TestOverlayRenderer0.class]; 114 | NSObject *proxyDelegate = [[MapViewDelegateReturnsValue alloc] initWithValueClass:TestOverlayRenderer1.class]; 115 | MKOverlayRenderer *renderer = [self rendererForOverlay:[CCHMapClusterControllerDebugPolygon new] withMapViewDelegate:mapViewDelegate proxyDelegate:proxyDelegate]; 116 | XCTAssertTrue([renderer isMemberOfClass:TestOverlayRenderer0.class]); 117 | } 118 | 119 | - (void)testMapViewDelegateProxyIgnoredNil 120 | { 121 | NSObject *mapViewDelegate = nil; 122 | NSObject *proxyDelegate = [[MapViewDelegateReturnsValue alloc] initWithValueClass:TestOverlayRenderer0.class]; 123 | MKOverlayRenderer *renderer = [self rendererForOverlay:[CCHMapClusterControllerDebugPolygon new] withMapViewDelegate:mapViewDelegate proxyDelegate:proxyDelegate]; 124 | XCTAssertTrue([renderer isMemberOfClass:MKPolygonRenderer.class]); 125 | } 126 | 127 | - (void)testMapViewDelegateProxyIgnoredReturnsNil 128 | { 129 | NSObject *mapViewDelegate = [[MapViewDelegateReturnsNil alloc] init]; 130 | NSObject *proxyDelegate = [[MapViewDelegateReturnsValue alloc] initWithValueClass:TestOverlayRenderer0.class]; 131 | MKOverlayRenderer *renderer = [self rendererForOverlay:[CCHMapClusterControllerDebugPolygon new] withMapViewDelegate:mapViewDelegate proxyDelegate:proxyDelegate]; 132 | XCTAssertTrue([renderer isMemberOfClass:MKPolygonRenderer.class]); 133 | } 134 | 135 | - (void)testMapViewDelegateProxyIgnoredEmpty 136 | { 137 | NSObject *mapViewDelegate = [[MapViewDelegateEmpty alloc] init]; 138 | NSObject *proxyDelegate = [[MapViewDelegateReturnsValue alloc] initWithValueClass:TestOverlayRenderer0.class]; 139 | MKOverlayRenderer *renderer = [self rendererForOverlay:[CCHMapClusterControllerDebugPolygon new] withMapViewDelegate:mapViewDelegate proxyDelegate:proxyDelegate]; 140 | XCTAssertTrue([renderer isMemberOfClass:MKPolygonRenderer.class]); 141 | } 142 | 143 | @end 144 | -------------------------------------------------------------------------------- /CCHMapClusterController Example iOS/CCHMapClusterController Example iOSTests/CCHMapViewDelegateProxyTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // CCHMapViewDelegateProxyTests.m 3 | // CCHMapClusterController Example iOS 4 | // 5 | // Created by Hoefele, Claus on 17.02.14. 6 | // Copyright (c) 2014 Claus Höfele. All rights reserved. 7 | // 8 | 9 | #import "CCHMapViewDelegateProxy.h" 10 | 11 | #import 12 | #import 13 | 14 | @interface MapViewDelegate : NSObject 15 | @property (nonatomic, assign) BOOL called; 16 | @end 17 | @implementation MapViewDelegate 18 | - (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated { 19 | self.called = YES; 20 | } 21 | @end 22 | 23 | @interface CCHMapViewDelegateProxyTests : XCTestCase 24 | 25 | @property (nonatomic, strong) MKMapView *mapView; 26 | 27 | @end 28 | 29 | @implementation CCHMapViewDelegateProxyTests 30 | 31 | - (void)setUp 32 | { 33 | [super setUp]; 34 | 35 | self.mapView = [[MKMapView alloc] init]; 36 | } 37 | 38 | - (void)testMapViewDelegate 39 | { 40 | MapViewDelegate *mapViewDelegate = [[MapViewDelegate alloc] init]; 41 | self.mapView.delegate = mapViewDelegate; 42 | 43 | MapViewDelegate *proxyDelegate = [[MapViewDelegate alloc] init]; 44 | CCHMapViewDelegateProxy *mapViewDelegateProxy = [[CCHMapViewDelegateProxy alloc] initWithMapView:self.mapView delegate:proxyDelegate]; 45 | XCTAssertEqual(self.mapView.delegate, mapViewDelegateProxy); 46 | XCTAssertEqual(mapViewDelegateProxy.target, mapViewDelegate); 47 | XCTAssertEqual(mapViewDelegateProxy.delegates.count, (NSUInteger)1); 48 | XCTAssertEqual(mapViewDelegateProxy.delegates.anyObject, proxyDelegate); 49 | } 50 | 51 | - (void)testMapViewDelegateChangeToNil 52 | { 53 | MapViewDelegate *mapViewDelegate = [[MapViewDelegate alloc] init]; 54 | self.mapView.delegate = mapViewDelegate; 55 | 56 | MapViewDelegate *proxyDelegate = [[MapViewDelegate alloc] init]; 57 | CCHMapViewDelegateProxy *mapViewDelegateProxy = [[CCHMapViewDelegateProxy alloc] initWithMapView:self.mapView delegate:proxyDelegate]; 58 | 59 | self.mapView.delegate = nil; 60 | 61 | XCTAssertEqual(self.mapView.delegate, mapViewDelegateProxy); 62 | XCTAssertNil(mapViewDelegateProxy.target); 63 | XCTAssertEqual(mapViewDelegateProxy.delegates.count, (NSUInteger)1); 64 | XCTAssertEqual(mapViewDelegateProxy.delegates.anyObject, proxyDelegate); 65 | } 66 | 67 | - (void)testMapViewNilDelegate 68 | { 69 | MapViewDelegate *proxyDelegate = [[MapViewDelegate alloc] init]; 70 | CCHMapViewDelegateProxy *mapViewDelegateProxy = [[CCHMapViewDelegateProxy alloc] initWithMapView:self.mapView delegate:proxyDelegate]; 71 | XCTAssertEqual(self.mapView.delegate, mapViewDelegateProxy); 72 | XCTAssertNil(mapViewDelegateProxy.target); 73 | XCTAssertEqual(mapViewDelegateProxy.delegates.count, (NSUInteger)1); 74 | XCTAssertEqual(mapViewDelegateProxy.delegates.anyObject, proxyDelegate); 75 | } 76 | 77 | - (void)testMapViewNilDelegateChangeToInstance 78 | { 79 | MapViewDelegate *proxyDelegate = [[MapViewDelegate alloc] init]; 80 | CCHMapViewDelegateProxy *mapViewDelegateProxy = [[CCHMapViewDelegateProxy alloc] initWithMapView:self.mapView delegate:proxyDelegate]; 81 | 82 | MapViewDelegate *mapViewDelegate = [[MapViewDelegate alloc] init]; 83 | self.mapView.delegate = mapViewDelegate; 84 | 85 | XCTAssertEqual(self.mapView.delegate, mapViewDelegateProxy); 86 | XCTAssertEqual(mapViewDelegateProxy.target, mapViewDelegate); 87 | XCTAssertEqual(mapViewDelegateProxy.delegates.count, (NSUInteger)1); 88 | XCTAssertEqual(mapViewDelegateProxy.delegates.anyObject, proxyDelegate); 89 | } 90 | 91 | - (void)testDeallocDelegateProxy 92 | { 93 | MapViewDelegate *mapViewDelegate = [[MapViewDelegate alloc] init]; 94 | self.mapView.delegate = mapViewDelegate; 95 | 96 | @autoreleasepool { 97 | MapViewDelegate *proxyDelegate = [[MapViewDelegate alloc] init]; 98 | CCHMapViewDelegateProxy *mapViewDelegateProxy = [[CCHMapViewDelegateProxy alloc] initWithMapView:self.mapView delegate:proxyDelegate]; 99 | XCTAssertEqual(mapViewDelegateProxy.target, mapViewDelegate); 100 | } 101 | 102 | XCTAssertEqual(self.mapView.delegate, mapViewDelegate); 103 | } 104 | 105 | - (void)testDeallocMapView 106 | { 107 | MapViewDelegate *proxyDelegate = [[MapViewDelegate alloc] init]; 108 | CCHMapViewDelegateProxy *mapViewDelegateProxy; 109 | 110 | @autoreleasepool { 111 | MKMapView *mapView = [[MKMapView alloc] init]; 112 | mapViewDelegateProxy = [[CCHMapViewDelegateProxy alloc] initWithMapView:mapView delegate:proxyDelegate]; 113 | } 114 | 115 | XCTAssertNil(mapViewDelegateProxy.target); 116 | XCTAssertEqual(mapViewDelegateProxy.delegates.count, (NSUInteger)1); 117 | } 118 | 119 | - (void)testAddMultipleDelegateProxies 120 | { 121 | MapViewDelegate *mapViewDelegate = [[MapViewDelegate alloc] init]; 122 | self.mapView.delegate = mapViewDelegate; 123 | 124 | MapViewDelegate *proxyDelegate0 = [[MapViewDelegate alloc] init]; 125 | CCHMapViewDelegateProxy *mapViewDelegateProxy = [[CCHMapViewDelegateProxy alloc] initWithMapView:self.mapView delegate:proxyDelegate0]; 126 | MapViewDelegate *proxyDelegate1 = [[MapViewDelegate alloc] init]; 127 | [mapViewDelegateProxy addDelegate:proxyDelegate1]; 128 | 129 | XCTAssertEqual(self.mapView.delegate, mapViewDelegateProxy); 130 | XCTAssertEqual(mapViewDelegateProxy.target, mapViewDelegate); 131 | XCTAssertEqual(mapViewDelegateProxy.delegates.count, (NSUInteger)2); 132 | } 133 | 134 | - (void)testRemoveMultipleDelegateProxies 135 | { 136 | MapViewDelegate *mapViewDelegate = [[MapViewDelegate alloc] init]; 137 | self.mapView.delegate = mapViewDelegate; 138 | 139 | @autoreleasepool { 140 | MapViewDelegate *proxyDelegate0 = [[MapViewDelegate alloc] init]; 141 | CCHMapViewDelegateProxy *mapViewDelegateProxy = [[CCHMapViewDelegateProxy alloc] initWithMapView:self.mapView delegate:proxyDelegate0]; 142 | MapViewDelegate *proxyDelegate1 = [[MapViewDelegate alloc] init]; 143 | [mapViewDelegateProxy addDelegate:proxyDelegate1]; 144 | } 145 | 146 | XCTAssertEqual(self.mapView.delegate, mapViewDelegate); 147 | } 148 | 149 | - (void)testCallDelegates 150 | { 151 | MapViewDelegate *mapViewDelegate = [[MapViewDelegate alloc] init]; 152 | self.mapView.delegate = mapViewDelegate; 153 | MapViewDelegate *proxyDelegate0 = [[MapViewDelegate alloc] init]; 154 | CCHMapViewDelegateProxy *mapViewDelegateProxy = [[CCHMapViewDelegateProxy alloc] initWithMapView:self.mapView delegate:proxyDelegate0]; 155 | MapViewDelegate *proxyDelegate1 = [[MapViewDelegate alloc] init]; 156 | [mapViewDelegateProxy addDelegate:proxyDelegate1]; 157 | 158 | [mapViewDelegateProxy mapView:nil regionDidChangeAnimated:YES]; 159 | 160 | XCTAssertTrue(mapViewDelegate.called); 161 | XCTAssertTrue(proxyDelegate0.called); 162 | XCTAssertTrue(proxyDelegate1.called); 163 | } 164 | 165 | @end 166 | -------------------------------------------------------------------------------- /CCHMapClusterController Tests/CCHMapViewDelegateProxyTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // CCHMapViewDelegateProxyTests.m 3 | // CCHMapClusterController 4 | // 5 | // Copyright (C) 2014 Claus Höfele 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | // 25 | 26 | #import "CCHMapViewDelegateProxy.h" 27 | 28 | #import 29 | #import 30 | 31 | @interface MapViewDelegate : NSObject 32 | @property (nonatomic, assign) BOOL called; 33 | @end 34 | @implementation MapViewDelegate 35 | - (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated { 36 | self.called = YES; 37 | } 38 | @end 39 | 40 | @interface CCHMapViewDelegateProxyTests : XCTestCase 41 | 42 | @property (nonatomic) MKMapView *mapView; 43 | 44 | @end 45 | 46 | @implementation CCHMapViewDelegateProxyTests 47 | 48 | - (void)setUp 49 | { 50 | [super setUp]; 51 | 52 | self.mapView = [[MKMapView alloc] init]; 53 | } 54 | 55 | - (void)testMapViewDelegate 56 | { 57 | MapViewDelegate *mapViewDelegate = [[MapViewDelegate alloc] init]; 58 | self.mapView.delegate = mapViewDelegate; 59 | 60 | MapViewDelegate *proxyDelegate = [[MapViewDelegate alloc] init]; 61 | CCHMapViewDelegateProxy *mapViewDelegateProxy = [[CCHMapViewDelegateProxy alloc] initWithMapView:self.mapView delegate:proxyDelegate]; 62 | XCTAssertEqual(self.mapView.delegate, mapViewDelegateProxy); 63 | XCTAssertEqual(mapViewDelegateProxy.target, mapViewDelegate); 64 | XCTAssertEqual(mapViewDelegateProxy.delegates.count, 1); 65 | XCTAssertEqual(mapViewDelegateProxy.delegates.anyObject, proxyDelegate); 66 | } 67 | 68 | - (void)testMapViewDelegateChangeToNil 69 | { 70 | MapViewDelegate *mapViewDelegate = [[MapViewDelegate alloc] init]; 71 | self.mapView.delegate = mapViewDelegate; 72 | 73 | MapViewDelegate *proxyDelegate = [[MapViewDelegate alloc] init]; 74 | CCHMapViewDelegateProxy *mapViewDelegateProxy = [[CCHMapViewDelegateProxy alloc] initWithMapView:self.mapView delegate:proxyDelegate]; 75 | 76 | self.mapView.delegate = nil; 77 | 78 | XCTAssertEqual(self.mapView.delegate, mapViewDelegateProxy); 79 | XCTAssertNil(mapViewDelegateProxy.target); 80 | XCTAssertEqual(mapViewDelegateProxy.delegates.count, 1); 81 | XCTAssertEqual(mapViewDelegateProxy.delegates.anyObject, proxyDelegate); 82 | } 83 | 84 | - (void)testMapViewNilDelegate 85 | { 86 | MapViewDelegate *proxyDelegate = [[MapViewDelegate alloc] init]; 87 | CCHMapViewDelegateProxy *mapViewDelegateProxy = [[CCHMapViewDelegateProxy alloc] initWithMapView:self.mapView delegate:proxyDelegate]; 88 | XCTAssertEqual(self.mapView.delegate, mapViewDelegateProxy); 89 | XCTAssertNil(mapViewDelegateProxy.target); 90 | XCTAssertEqual(mapViewDelegateProxy.delegates.count, 1); 91 | XCTAssertEqual(mapViewDelegateProxy.delegates.anyObject, proxyDelegate); 92 | } 93 | 94 | - (void)testMapViewNilDelegateChangeToInstance 95 | { 96 | MapViewDelegate *proxyDelegate = [[MapViewDelegate alloc] init]; 97 | CCHMapViewDelegateProxy *mapViewDelegateProxy = [[CCHMapViewDelegateProxy alloc] initWithMapView:self.mapView delegate:proxyDelegate]; 98 | 99 | MapViewDelegate *mapViewDelegate = [[MapViewDelegate alloc] init]; 100 | self.mapView.delegate = mapViewDelegate; 101 | 102 | XCTAssertEqual(self.mapView.delegate, mapViewDelegateProxy); 103 | XCTAssertEqual(mapViewDelegateProxy.target, mapViewDelegate); 104 | XCTAssertEqual(mapViewDelegateProxy.delegates.count, 1); 105 | XCTAssertEqual(mapViewDelegateProxy.delegates.anyObject, proxyDelegate); 106 | } 107 | 108 | - (void)testDeallocDelegateProxy 109 | { 110 | MapViewDelegate *mapViewDelegate = [[MapViewDelegate alloc] init]; 111 | self.mapView.delegate = mapViewDelegate; 112 | 113 | @autoreleasepool { 114 | MapViewDelegate *proxyDelegate = [[MapViewDelegate alloc] init]; 115 | CCHMapViewDelegateProxy *mapViewDelegateProxy = [[CCHMapViewDelegateProxy alloc] initWithMapView:self.mapView delegate:proxyDelegate]; 116 | XCTAssertEqual(mapViewDelegateProxy.target, mapViewDelegate); 117 | } 118 | 119 | XCTAssertEqual(self.mapView.delegate, mapViewDelegate); 120 | } 121 | 122 | - (void)testDeallocMapView 123 | { 124 | MapViewDelegate *proxyDelegate = [[MapViewDelegate alloc] init]; 125 | CCHMapViewDelegateProxy *mapViewDelegateProxy; 126 | 127 | @autoreleasepool { 128 | MKMapView *mapView = [[MKMapView alloc] init]; 129 | mapViewDelegateProxy = [[CCHMapViewDelegateProxy alloc] initWithMapView:mapView delegate:proxyDelegate]; 130 | } 131 | 132 | XCTAssertNil(mapViewDelegateProxy.target); 133 | XCTAssertEqual(mapViewDelegateProxy.delegates.count, 1); 134 | } 135 | 136 | - (void)testAddMultipleDelegateProxies 137 | { 138 | MapViewDelegate *mapViewDelegate = [[MapViewDelegate alloc] init]; 139 | self.mapView.delegate = mapViewDelegate; 140 | 141 | MapViewDelegate *proxyDelegate0 = [[MapViewDelegate alloc] init]; 142 | CCHMapViewDelegateProxy *mapViewDelegateProxy = [[CCHMapViewDelegateProxy alloc] initWithMapView:self.mapView delegate:proxyDelegate0]; 143 | MapViewDelegate *proxyDelegate1 = [[MapViewDelegate alloc] init]; 144 | [mapViewDelegateProxy addDelegate:proxyDelegate1]; 145 | 146 | XCTAssertEqual(self.mapView.delegate, mapViewDelegateProxy); 147 | XCTAssertEqual(mapViewDelegateProxy.target, mapViewDelegate); 148 | XCTAssertEqual(mapViewDelegateProxy.delegates.count, 2); 149 | } 150 | 151 | - (void)testRemoveMultipleDelegateProxies 152 | { 153 | MapViewDelegate *mapViewDelegate = [[MapViewDelegate alloc] init]; 154 | self.mapView.delegate = mapViewDelegate; 155 | 156 | @autoreleasepool { 157 | MapViewDelegate *proxyDelegate0 = [[MapViewDelegate alloc] init]; 158 | CCHMapViewDelegateProxy *mapViewDelegateProxy = [[CCHMapViewDelegateProxy alloc] initWithMapView:self.mapView delegate:proxyDelegate0]; 159 | MapViewDelegate *proxyDelegate1 = [[MapViewDelegate alloc] init]; 160 | [mapViewDelegateProxy addDelegate:proxyDelegate1]; 161 | } 162 | 163 | XCTAssertEqual(self.mapView.delegate, mapViewDelegate); 164 | } 165 | 166 | - (void)testCallDelegates 167 | { 168 | MapViewDelegate *mapViewDelegate = [[MapViewDelegate alloc] init]; 169 | self.mapView.delegate = mapViewDelegate; 170 | MapViewDelegate *proxyDelegate0 = [[MapViewDelegate alloc] init]; 171 | CCHMapViewDelegateProxy *mapViewDelegateProxy = [[CCHMapViewDelegateProxy alloc] initWithMapView:self.mapView delegate:proxyDelegate0]; 172 | MapViewDelegate *proxyDelegate1 = [[MapViewDelegate alloc] init]; 173 | [mapViewDelegateProxy addDelegate:proxyDelegate1]; 174 | 175 | [mapViewDelegateProxy mapView:self.mapView regionDidChangeAnimated:YES]; 176 | 177 | XCTAssertTrue(mapViewDelegate.called); 178 | XCTAssertTrue(proxyDelegate0.called); 179 | XCTAssertTrue(proxyDelegate1.called); 180 | } 181 | 182 | @end 183 | --------------------------------------------------------------------------------