├── 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 |
--------------------------------------------------------------------------------