├── .swift-version ├── JDRealHeatMap.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ └── JamesDouble.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── xcuserdata │ └── JamesDouble.xcuserdatad │ └── xcschemes │ ├── JDRealHeatMap.xcscheme │ └── xcschememanagement.plist ├── JDRealHeatMap ├── AppDelegate.swift ├── Assets.xcassets │ ├── 13607804_1138031072920408_645704923_n.imageset │ │ ├── 13607804_1138031072920408_645704923_n.png │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ └── Image.imageset │ │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist ├── JDSwiftHeatMap.zip ├── JDSwiftHeatMap │ ├── JDColorMixer.swift │ ├── JDHMMissionController.swift │ ├── JDOverlay.swift │ ├── JDOverlayRender.swift │ ├── JDRealHeatMap.swift │ └── JDRowDataProducer.swift └── ViewController.swift ├── JDSwiftHeatMap.podspec ├── LICENSE ├── README.md └── Readme_img ├── jdheatmapDemo.png └── logo.png /.swift-version: -------------------------------------------------------------------------------- 1 | 3.1 2 | -------------------------------------------------------------------------------- /JDRealHeatMap.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 4F90C0EB1EEE6A39003706F3 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F90C0EA1EEE6A39003706F3 /* AppDelegate.swift */; }; 11 | 4F90C0ED1EEE6A39003706F3 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F90C0EC1EEE6A39003706F3 /* ViewController.swift */; }; 12 | 4F90C0F01EEE6A39003706F3 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4F90C0EE1EEE6A39003706F3 /* Main.storyboard */; }; 13 | 4F90C0F21EEE6A39003706F3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4F90C0F11EEE6A39003706F3 /* Assets.xcassets */; }; 14 | 4F90C0F51EEE6A39003706F3 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4F90C0F31EEE6A39003706F3 /* LaunchScreen.storyboard */; }; 15 | 4FB9F0FA1F167047008F8215 /* JDColorMixer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FB9F0F41F167047008F8215 /* JDColorMixer.swift */; }; 16 | 4FB9F0FB1F167047008F8215 /* JDHMMissionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FB9F0F51F167047008F8215 /* JDHMMissionController.swift */; }; 17 | 4FB9F0FC1F167047008F8215 /* JDOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FB9F0F61F167047008F8215 /* JDOverlay.swift */; }; 18 | 4FB9F0FD1F167047008F8215 /* JDOverlayRender.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FB9F0F71F167047008F8215 /* JDOverlayRender.swift */; }; 19 | 4FB9F0FE1F167047008F8215 /* JDRealHeatMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FB9F0F81F167047008F8215 /* JDRealHeatMap.swift */; }; 20 | 4FB9F0FF1F167047008F8215 /* JDRowDataProducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FB9F0F91F167047008F8215 /* JDRowDataProducer.swift */; }; 21 | /* End PBXBuildFile section */ 22 | 23 | /* Begin PBXFileReference section */ 24 | 4F90C0E71EEE6A39003706F3 /* JDRealHeatMap.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = JDRealHeatMap.app; sourceTree = BUILT_PRODUCTS_DIR; }; 25 | 4F90C0EA1EEE6A39003706F3 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 26 | 4F90C0EC1EEE6A39003706F3 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 27 | 4F90C0EF1EEE6A39003706F3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 28 | 4F90C0F11EEE6A39003706F3 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 29 | 4F90C0F41EEE6A39003706F3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 30 | 4F90C0F61EEE6A39003706F3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 31 | 4FB9F0F41F167047008F8215 /* JDColorMixer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = JDColorMixer.swift; path = JDSwiftHeatMap/JDColorMixer.swift; sourceTree = ""; }; 32 | 4FB9F0F51F167047008F8215 /* JDHMMissionController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = JDHMMissionController.swift; path = JDSwiftHeatMap/JDHMMissionController.swift; sourceTree = ""; }; 33 | 4FB9F0F61F167047008F8215 /* JDOverlay.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = JDOverlay.swift; path = JDSwiftHeatMap/JDOverlay.swift; sourceTree = ""; }; 34 | 4FB9F0F71F167047008F8215 /* JDOverlayRender.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = JDOverlayRender.swift; path = JDSwiftHeatMap/JDOverlayRender.swift; sourceTree = ""; }; 35 | 4FB9F0F81F167047008F8215 /* JDRealHeatMap.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = JDRealHeatMap.swift; path = JDSwiftHeatMap/JDRealHeatMap.swift; sourceTree = ""; }; 36 | 4FB9F0F91F167047008F8215 /* JDRowDataProducer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = JDRowDataProducer.swift; path = JDSwiftHeatMap/JDRowDataProducer.swift; sourceTree = ""; }; 37 | /* End PBXFileReference section */ 38 | 39 | /* Begin PBXFrameworksBuildPhase section */ 40 | 4F90C0E41EEE6A39003706F3 /* Frameworks */ = { 41 | isa = PBXFrameworksBuildPhase; 42 | buildActionMask = 2147483647; 43 | files = ( 44 | ); 45 | runOnlyForDeploymentPostprocessing = 0; 46 | }; 47 | /* End PBXFrameworksBuildPhase section */ 48 | 49 | /* Begin PBXGroup section */ 50 | 4F90C0DE1EEE6A38003706F3 = { 51 | isa = PBXGroup; 52 | children = ( 53 | 4F90C0E91EEE6A39003706F3 /* JDRealHeatMap */, 54 | 4F90C0E81EEE6A39003706F3 /* Products */, 55 | ); 56 | sourceTree = ""; 57 | }; 58 | 4F90C0E81EEE6A39003706F3 /* Products */ = { 59 | isa = PBXGroup; 60 | children = ( 61 | 4F90C0E71EEE6A39003706F3 /* JDRealHeatMap.app */, 62 | ); 63 | name = Products; 64 | sourceTree = ""; 65 | }; 66 | 4F90C0E91EEE6A39003706F3 /* JDRealHeatMap */ = { 67 | isa = PBXGroup; 68 | children = ( 69 | 4F90C0FE1EEE6C69003706F3 /* JDRealHeatMap */, 70 | 4F90C0EA1EEE6A39003706F3 /* AppDelegate.swift */, 71 | 4F90C0EC1EEE6A39003706F3 /* ViewController.swift */, 72 | 4F90C0EE1EEE6A39003706F3 /* Main.storyboard */, 73 | 4F90C0F11EEE6A39003706F3 /* Assets.xcassets */, 74 | 4F90C0F31EEE6A39003706F3 /* LaunchScreen.storyboard */, 75 | 4F90C0F61EEE6A39003706F3 /* Info.plist */, 76 | ); 77 | path = JDRealHeatMap; 78 | sourceTree = ""; 79 | }; 80 | 4F90C0FE1EEE6C69003706F3 /* JDRealHeatMap */ = { 81 | isa = PBXGroup; 82 | children = ( 83 | 4FB9F0F81F167047008F8215 /* JDRealHeatMap.swift */, 84 | 4FB9F0F51F167047008F8215 /* JDHMMissionController.swift */, 85 | 4FB9F0F41F167047008F8215 /* JDColorMixer.swift */, 86 | 4FB9F0F61F167047008F8215 /* JDOverlay.swift */, 87 | 4FB9F0F71F167047008F8215 /* JDOverlayRender.swift */, 88 | 4FB9F0F91F167047008F8215 /* JDRowDataProducer.swift */, 89 | ); 90 | name = JDRealHeatMap; 91 | sourceTree = ""; 92 | }; 93 | /* End PBXGroup section */ 94 | 95 | /* Begin PBXNativeTarget section */ 96 | 4F90C0E61EEE6A39003706F3 /* JDRealHeatMap */ = { 97 | isa = PBXNativeTarget; 98 | buildConfigurationList = 4F90C0F91EEE6A39003706F3 /* Build configuration list for PBXNativeTarget "JDRealHeatMap" */; 99 | buildPhases = ( 100 | 4F90C0E31EEE6A39003706F3 /* Sources */, 101 | 4F90C0E41EEE6A39003706F3 /* Frameworks */, 102 | 4F90C0E51EEE6A39003706F3 /* Resources */, 103 | ); 104 | buildRules = ( 105 | ); 106 | dependencies = ( 107 | ); 108 | name = JDRealHeatMap; 109 | productName = JDRealHeatMap; 110 | productReference = 4F90C0E71EEE6A39003706F3 /* JDRealHeatMap.app */; 111 | productType = "com.apple.product-type.application"; 112 | }; 113 | /* End PBXNativeTarget section */ 114 | 115 | /* Begin PBXProject section */ 116 | 4F90C0DF1EEE6A38003706F3 /* Project object */ = { 117 | isa = PBXProject; 118 | attributes = { 119 | LastSwiftUpdateCheck = 0830; 120 | LastUpgradeCheck = 0830; 121 | ORGANIZATIONNAME = james12345; 122 | TargetAttributes = { 123 | 4F90C0E61EEE6A39003706F3 = { 124 | CreatedOnToolsVersion = 8.3.1; 125 | DevelopmentTeam = 857SMZQ3R8; 126 | LastSwiftMigration = 0900; 127 | ProvisioningStyle = Automatic; 128 | }; 129 | }; 130 | }; 131 | buildConfigurationList = 4F90C0E21EEE6A39003706F3 /* Build configuration list for PBXProject "JDRealHeatMap" */; 132 | compatibilityVersion = "Xcode 3.2"; 133 | developmentRegion = English; 134 | hasScannedForEncodings = 0; 135 | knownRegions = ( 136 | en, 137 | Base, 138 | ); 139 | mainGroup = 4F90C0DE1EEE6A38003706F3; 140 | productRefGroup = 4F90C0E81EEE6A39003706F3 /* Products */; 141 | projectDirPath = ""; 142 | projectRoot = ""; 143 | targets = ( 144 | 4F90C0E61EEE6A39003706F3 /* JDRealHeatMap */, 145 | ); 146 | }; 147 | /* End PBXProject section */ 148 | 149 | /* Begin PBXResourcesBuildPhase section */ 150 | 4F90C0E51EEE6A39003706F3 /* Resources */ = { 151 | isa = PBXResourcesBuildPhase; 152 | buildActionMask = 2147483647; 153 | files = ( 154 | 4F90C0F51EEE6A39003706F3 /* LaunchScreen.storyboard in Resources */, 155 | 4F90C0F21EEE6A39003706F3 /* Assets.xcassets in Resources */, 156 | 4F90C0F01EEE6A39003706F3 /* Main.storyboard in Resources */, 157 | ); 158 | runOnlyForDeploymentPostprocessing = 0; 159 | }; 160 | /* End PBXResourcesBuildPhase section */ 161 | 162 | /* Begin PBXSourcesBuildPhase section */ 163 | 4F90C0E31EEE6A39003706F3 /* Sources */ = { 164 | isa = PBXSourcesBuildPhase; 165 | buildActionMask = 2147483647; 166 | files = ( 167 | 4FB9F0FA1F167047008F8215 /* JDColorMixer.swift in Sources */, 168 | 4FB9F0FD1F167047008F8215 /* JDOverlayRender.swift in Sources */, 169 | 4F90C0ED1EEE6A39003706F3 /* ViewController.swift in Sources */, 170 | 4FB9F0FF1F167047008F8215 /* JDRowDataProducer.swift in Sources */, 171 | 4F90C0EB1EEE6A39003706F3 /* AppDelegate.swift in Sources */, 172 | 4FB9F0FB1F167047008F8215 /* JDHMMissionController.swift in Sources */, 173 | 4FB9F0FC1F167047008F8215 /* JDOverlay.swift in Sources */, 174 | 4FB9F0FE1F167047008F8215 /* JDRealHeatMap.swift in Sources */, 175 | ); 176 | runOnlyForDeploymentPostprocessing = 0; 177 | }; 178 | /* End PBXSourcesBuildPhase section */ 179 | 180 | /* Begin PBXVariantGroup section */ 181 | 4F90C0EE1EEE6A39003706F3 /* Main.storyboard */ = { 182 | isa = PBXVariantGroup; 183 | children = ( 184 | 4F90C0EF1EEE6A39003706F3 /* Base */, 185 | ); 186 | name = Main.storyboard; 187 | sourceTree = ""; 188 | }; 189 | 4F90C0F31EEE6A39003706F3 /* LaunchScreen.storyboard */ = { 190 | isa = PBXVariantGroup; 191 | children = ( 192 | 4F90C0F41EEE6A39003706F3 /* Base */, 193 | ); 194 | name = LaunchScreen.storyboard; 195 | sourceTree = ""; 196 | }; 197 | /* End PBXVariantGroup section */ 198 | 199 | /* Begin XCBuildConfiguration section */ 200 | 4F90C0F71EEE6A39003706F3 /* Debug */ = { 201 | isa = XCBuildConfiguration; 202 | buildSettings = { 203 | ALWAYS_SEARCH_USER_PATHS = NO; 204 | CLANG_ANALYZER_NONNULL = YES; 205 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 206 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 207 | CLANG_CXX_LIBRARY = "libc++"; 208 | CLANG_ENABLE_MODULES = YES; 209 | CLANG_ENABLE_OBJC_ARC = YES; 210 | CLANG_WARN_BOOL_CONVERSION = YES; 211 | CLANG_WARN_CONSTANT_CONVERSION = YES; 212 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 213 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 214 | CLANG_WARN_EMPTY_BODY = YES; 215 | CLANG_WARN_ENUM_CONVERSION = YES; 216 | CLANG_WARN_INFINITE_RECURSION = YES; 217 | CLANG_WARN_INT_CONVERSION = YES; 218 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 219 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 220 | CLANG_WARN_UNREACHABLE_CODE = YES; 221 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 222 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 223 | COPY_PHASE_STRIP = NO; 224 | DEBUG_INFORMATION_FORMAT = dwarf; 225 | ENABLE_STRICT_OBJC_MSGSEND = YES; 226 | ENABLE_TESTABILITY = YES; 227 | GCC_C_LANGUAGE_STANDARD = gnu99; 228 | GCC_DYNAMIC_NO_PIC = NO; 229 | GCC_NO_COMMON_BLOCKS = YES; 230 | GCC_OPTIMIZATION_LEVEL = 0; 231 | GCC_PREPROCESSOR_DEFINITIONS = ( 232 | "DEBUG=1", 233 | "$(inherited)", 234 | ); 235 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 236 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 237 | GCC_WARN_UNDECLARED_SELECTOR = YES; 238 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 239 | GCC_WARN_UNUSED_FUNCTION = YES; 240 | GCC_WARN_UNUSED_VARIABLE = YES; 241 | IPHONEOS_DEPLOYMENT_TARGET = 10.3; 242 | MTL_ENABLE_DEBUG_INFO = YES; 243 | ONLY_ACTIVE_ARCH = YES; 244 | SDKROOT = iphoneos; 245 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 246 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 247 | }; 248 | name = Debug; 249 | }; 250 | 4F90C0F81EEE6A39003706F3 /* Release */ = { 251 | isa = XCBuildConfiguration; 252 | buildSettings = { 253 | ALWAYS_SEARCH_USER_PATHS = NO; 254 | CLANG_ANALYZER_NONNULL = YES; 255 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 256 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 257 | CLANG_CXX_LIBRARY = "libc++"; 258 | CLANG_ENABLE_MODULES = YES; 259 | CLANG_ENABLE_OBJC_ARC = YES; 260 | CLANG_WARN_BOOL_CONVERSION = YES; 261 | CLANG_WARN_CONSTANT_CONVERSION = YES; 262 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 263 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 264 | CLANG_WARN_EMPTY_BODY = YES; 265 | CLANG_WARN_ENUM_CONVERSION = YES; 266 | CLANG_WARN_INFINITE_RECURSION = YES; 267 | CLANG_WARN_INT_CONVERSION = YES; 268 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 269 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 270 | CLANG_WARN_UNREACHABLE_CODE = YES; 271 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 272 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 273 | COPY_PHASE_STRIP = NO; 274 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 275 | ENABLE_NS_ASSERTIONS = NO; 276 | ENABLE_STRICT_OBJC_MSGSEND = YES; 277 | GCC_C_LANGUAGE_STANDARD = gnu99; 278 | GCC_NO_COMMON_BLOCKS = YES; 279 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 280 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 281 | GCC_WARN_UNDECLARED_SELECTOR = YES; 282 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 283 | GCC_WARN_UNUSED_FUNCTION = YES; 284 | GCC_WARN_UNUSED_VARIABLE = YES; 285 | IPHONEOS_DEPLOYMENT_TARGET = 10.3; 286 | MTL_ENABLE_DEBUG_INFO = NO; 287 | SDKROOT = iphoneos; 288 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 289 | VALIDATE_PRODUCT = YES; 290 | }; 291 | name = Release; 292 | }; 293 | 4F90C0FA1EEE6A39003706F3 /* Debug */ = { 294 | isa = XCBuildConfiguration; 295 | buildSettings = { 296 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 297 | DEVELOPMENT_TEAM = 857SMZQ3R8; 298 | INFOPLIST_FILE = JDRealHeatMap/Info.plist; 299 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 300 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 301 | PRODUCT_BUNDLE_IDENTIFIER = com.jameskuo12345.JDRealHeatMap; 302 | PRODUCT_NAME = "$(TARGET_NAME)"; 303 | SWIFT_SWIFT3_OBJC_INFERENCE = On; 304 | SWIFT_VERSION = 4.0; 305 | }; 306 | name = Debug; 307 | }; 308 | 4F90C0FB1EEE6A39003706F3 /* Release */ = { 309 | isa = XCBuildConfiguration; 310 | buildSettings = { 311 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 312 | DEVELOPMENT_TEAM = 857SMZQ3R8; 313 | INFOPLIST_FILE = JDRealHeatMap/Info.plist; 314 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 315 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 316 | PRODUCT_BUNDLE_IDENTIFIER = com.jameskuo12345.JDRealHeatMap; 317 | PRODUCT_NAME = "$(TARGET_NAME)"; 318 | SWIFT_SWIFT3_OBJC_INFERENCE = On; 319 | SWIFT_VERSION = 4.0; 320 | }; 321 | name = Release; 322 | }; 323 | /* End XCBuildConfiguration section */ 324 | 325 | /* Begin XCConfigurationList section */ 326 | 4F90C0E21EEE6A39003706F3 /* Build configuration list for PBXProject "JDRealHeatMap" */ = { 327 | isa = XCConfigurationList; 328 | buildConfigurations = ( 329 | 4F90C0F71EEE6A39003706F3 /* Debug */, 330 | 4F90C0F81EEE6A39003706F3 /* Release */, 331 | ); 332 | defaultConfigurationIsVisible = 0; 333 | defaultConfigurationName = Release; 334 | }; 335 | 4F90C0F91EEE6A39003706F3 /* Build configuration list for PBXNativeTarget "JDRealHeatMap" */ = { 336 | isa = XCConfigurationList; 337 | buildConfigurations = ( 338 | 4F90C0FA1EEE6A39003706F3 /* Debug */, 339 | 4F90C0FB1EEE6A39003706F3 /* Release */, 340 | ); 341 | defaultConfigurationIsVisible = 0; 342 | defaultConfigurationName = Release; 343 | }; 344 | /* End XCConfigurationList section */ 345 | }; 346 | rootObject = 4F90C0DF1EEE6A38003706F3 /* Project object */; 347 | } 348 | -------------------------------------------------------------------------------- /JDRealHeatMap.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /JDRealHeatMap.xcodeproj/project.xcworkspace/xcuserdata/JamesDouble.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesdouble/JDSwiftHeatMap/3e22ab10abd302f5fb57f7de6bcd22223722fa0f/JDRealHeatMap.xcodeproj/project.xcworkspace/xcuserdata/JamesDouble.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /JDRealHeatMap.xcodeproj/xcuserdata/JamesDouble.xcuserdatad/xcschemes/JDRealHeatMap.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 46 | 47 | 48 | 49 | 50 | 51 | 61 | 63 | 69 | 70 | 71 | 72 | 73 | 74 | 80 | 82 | 88 | 89 | 90 | 91 | 93 | 94 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /JDRealHeatMap.xcodeproj/xcuserdata/JamesDouble.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | JDRealHeatMap.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 4F90C0E61EEE6A39003706F3 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /JDRealHeatMap/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // JDSwiftHeatMap 4 | // 5 | // Created by 郭介騵 on 2017/6/12. 6 | // Copyright © 2017年 james12345. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // 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. 24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // 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. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // 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. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /JDRealHeatMap/Assets.xcassets/13607804_1138031072920408_645704923_n.imageset/13607804_1138031072920408_645704923_n.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesdouble/JDSwiftHeatMap/3e22ab10abd302f5fb57f7de6bcd22223722fa0f/JDRealHeatMap/Assets.xcassets/13607804_1138031072920408_645704923_n.imageset/13607804_1138031072920408_645704923_n.png -------------------------------------------------------------------------------- /JDRealHeatMap/Assets.xcassets/13607804_1138031072920408_645704923_n.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "13607804_1138031072920408_645704923_n.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /JDRealHeatMap/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ios-marketing", 45 | "size" : "1024x1024", 46 | "scale" : "1x" 47 | } 48 | ], 49 | "info" : { 50 | "version" : 1, 51 | "author" : "xcode" 52 | } 53 | } -------------------------------------------------------------------------------- /JDRealHeatMap/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /JDRealHeatMap/Assets.xcassets/Image.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "scale" : "3x" 14 | } 15 | ], 16 | "info" : { 17 | "version" : 1, 18 | "author" : "xcode" 19 | } 20 | } -------------------------------------------------------------------------------- /JDRealHeatMap/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /JDRealHeatMap/Base.lproj/Main.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 | 35 | 42 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /JDRealHeatMap/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /JDRealHeatMap/JDSwiftHeatMap.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesdouble/JDSwiftHeatMap/3e22ab10abd302f5fb57f7de6bcd22223722fa0f/JDRealHeatMap/JDSwiftHeatMap.zip -------------------------------------------------------------------------------- /JDRealHeatMap/JDSwiftHeatMap/JDColorMixer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JDColorMixer.swift 3 | // JDSwiftHeatMap 4 | // 5 | // Created by 郭介騵 on 2017/6/30. 6 | // Copyright © 2017年 james12345. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | enum ColorMixerMode 12 | { 13 | case BlurryMode 14 | case DistinctMode 15 | } 16 | 17 | 18 | struct BytesRGB 19 | { 20 | var redRow:UTF8Char = 0 21 | var greenRow:UTF8Char = 0 22 | var BlueRow:UTF8Char = 0 23 | var alpha:UTF8Char = 255 24 | } 25 | 26 | class JDHeatColorMixer:NSObject 27 | { 28 | var colorArray:[UIColor] = [] 29 | var mixerMode:ColorMixerMode = .DistinctMode 30 | let colorMixerThread = DispatchQueue(label: "ColorMixer.Thread") 31 | 32 | init(array:[UIColor],level:Int) 33 | { 34 | super.init() 35 | colorMixerThread.async { 36 | [weak self] in 37 | 38 | let devideLevel = level 39 | if(devideLevel == 0) {fatalError("devide level should not be 0")} 40 | if(devideLevel == 1) { self?.colorArray = array; return} 41 | for index in 0..BytesRGB 66 | { 67 | func getClearify(inDestiny D:Float)->BytesRGB 68 | { 69 | if(D == 0) //Only None Flat Data Type will Have 0 destiny 70 | { 71 | let rgb:BytesRGB = BytesRGB(redRow: 0, 72 | greenRow: 0, 73 | BlueRow: 0, 74 | alpha: 0) 75 | return rgb 76 | } 77 | // 78 | let colorCount = colorArray.count 79 | if(colorCount < 2) 80 | { 81 | colorArray.append(UIColor.clear) 82 | } 83 | 84 | var TargetColor:UIColor = UIColor() 85 | let AverageWeight:Float = 1.0 / Float(colorCount-1) 86 | var counter:Float = 0.0 87 | for color in colorArray 88 | { 89 | let next = counter + AverageWeight 90 | if((counter <= D) && DBytesRGB 115 | { 116 | if(D == 0) 117 | { 118 | let rgb:BytesRGB = BytesRGB(redRow: 0, 119 | greenRow: 0, 120 | BlueRow: 0, 121 | alpha: 0) 122 | return rgb 123 | } 124 | 125 | let colorCount = colorArray.count 126 | if(colorCount < 2) 127 | { 128 | colorArray.append(UIColor.clear) 129 | } 130 | 131 | var TargetColor:[UIColor] = [] 132 | let AverageWeight:Float = 1.0 / Float(colorCount-1) 133 | var counter:Float = 0.0 134 | var RDiff:Float = 0.0 135 | var Index = 0 136 | for color in colorArray 137 | { 138 | if( ((D < (counter + AverageWeight)) && (D > counter))) //The Target is between this two color 139 | { 140 | TargetColor.append(color) 141 | let secondcolor = colorArray[Index+1] 142 | TargetColor.append(secondcolor) 143 | // 144 | RDiff = (D - counter) 145 | break 146 | } 147 | else if(counter == D) 148 | { 149 | TargetColor = [color,color] 150 | break 151 | } 152 | Index += 1 153 | counter += AverageWeight 154 | } 155 | if(RDiff > 1) { fatalError("RDiff Error") } 156 | let LDiff = 1.0 - RDiff 157 | // 158 | func caculateRGB()->BytesRGB 159 | { 160 | if(TargetColor.count != 2) {fatalError("Color Mixer Problem")} 161 | let LCGColor = TargetColor[0].rgb() 162 | let LRed:Float = (LCGColor?.red)! 163 | let LGreen:Float = (LCGColor?.green)! 164 | let LBlue:Float = (LCGColor?.blue)! 165 | 166 | let RCGColor = TargetColor[1].rgb() 167 | let RRed:Float = (RCGColor?.red)! 168 | let RGreen:Float = (RCGColor?.green)! 169 | let RBlue:Float = (RCGColor?.blue)! 170 | 171 | // 172 | let redRow:UTF8Char = UTF8Char(Float(LRed * LDiff + RRed * RDiff) * D) 173 | let GreenRow:UTF8Char = UTF8Char(Float(LGreen * LDiff + RGreen * RDiff) * D) 174 | let BlueRow:UTF8Char = UTF8Char(Float(LBlue * LDiff + RBlue * RDiff) * D) 175 | 176 | return BytesRGB(redRow: redRow, 177 | greenRow: GreenRow, 178 | BlueRow: BlueRow, 179 | alpha: UTF8Char(D * 255)) 180 | } 181 | return caculateRGB() 182 | } 183 | 184 | if(mixerMode == .BlurryMode) 185 | { 186 | return getBlurryRGB(inDestiny: D) 187 | } 188 | else if(mixerMode == .DistinctMode) 189 | { 190 | return getClearify(inDestiny: D) 191 | } 192 | return BytesRGB(redRow: 0, 193 | greenRow: 0, 194 | BlueRow: 0, 195 | alpha: 0) 196 | } 197 | } 198 | 199 | extension UIColor { 200 | 201 | func rgb() -> (red:Float, green:Float, blue:Float, alpha:Float)? { 202 | var fRed : CGFloat = 0 203 | var fGreen : CGFloat = 0 204 | var fBlue : CGFloat = 0 205 | var fAlpha: CGFloat = 0 206 | if self.getRed(&fRed, green: &fGreen, blue: &fBlue, alpha: &fAlpha) { 207 | let iRed = Float(fRed * 255.0) 208 | let iGreen = Float(fGreen * 255.0) 209 | let iBlue = Float(fBlue * 255.0) 210 | let iAlpha = Float(fAlpha * 255.0) 211 | 212 | return (red:iRed, green:iGreen, blue:iBlue, alpha:iAlpha) 213 | } else { 214 | // Could not extract RGBA components: 215 | return nil 216 | } 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /JDRealHeatMap/JDSwiftHeatMap/JDHMMissionController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JDHMMissionController.swift 3 | // JDSwiftHeatMap 4 | // 5 | // Created by 郭介騵 on 2017/6/16. 6 | // Copyright © 2017年 james12345. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import MapKit 11 | 12 | class JDHeatMapMissionController:NSObject 13 | { 14 | typealias ProducerFor = [JDHeatOverlayRender:JDRowDataProducer] 15 | // 16 | var Render_ProducerPair:ProducerFor = [:] 17 | weak var jdswiftheatmap:JDSwiftHeatMap! 18 | var Caculating:Bool = false 19 | var Datatype:DataPointType = .RadiusPoint 20 | let MapWidthInUIView:CGFloat 21 | // 22 | var biggestRegion:MKMapRect = MKMapRect(origin: MKMapPoint(), size: MKMapSize(width: 0, height: 0)) 23 | var MaxHeatLevelinWholeMap:Int = 0 24 | // 25 | let missionThread = DispatchQueue(label: "MissionThread") 26 | 27 | init(JDSwiftHeatMap map:JDSwiftHeatMap,datatype t:DataPointType,mode m:ColorMixerMode) 28 | { 29 | jdswiftheatmap = map 30 | Datatype = t 31 | MapWidthInUIView = map.frame.width 32 | JDRowDataProducer.theColorMixer.mixerMode = m 33 | } 34 | 35 | func renderFor(overlay:JDHeatOverlay)->JDHeatOverlayRender? 36 | { 37 | let render = self.jdswiftheatmap.renderer(for: overlay) 38 | let heartrender = render as? JDHeatOverlayRender 39 | return heartrender 40 | } 41 | 42 | /** 43 | 1.0 The User Change Data,so Call this Refresh to Recollect the Data 44 | **/ 45 | func ExecuteRefreshMission() 46 | { 47 | Render_ProducerPair = [:] 48 | /* 49 | 1.1 Call the Delegate 50 | */ 51 | guard let heatdelegate = jdswiftheatmap.heatmapdelegate else { 52 | return 53 | } 54 | /* 55 | 1.2 Collect New Data 56 | */ 57 | jdswiftheatmap.removeOverlays(jdswiftheatmap.overlays) 58 | let datacount = heatdelegate.heatmap(HeatPointCount: jdswiftheatmap) 59 | var id:Int = 1 60 | for i in 0.. MaxHeatLevelinWholeMap) ? heat : MaxHeatLevelinWholeMap 65 | let raius = heatdelegate.heatmap(RadiusInKMFor: i) 66 | let newHeatPoint:JDHeatPoint = JDHeatPoint(heat: heat, coor: coor, heatradius: raius) 67 | if(Datatype == .FlatPoint) 68 | { 69 | /* 70 | 1.3-1 type = Flat point there will Only be one overlay 71 | */ 72 | func collectToOneOverlay() 73 | { 74 | if(jdswiftheatmap.overlays.count == 1) ///Haved Overlay 75 | { 76 | if let Flatoverlay = jdswiftheatmap.overlays.first as? JDHeatFlatPointOverlay 77 | { 78 | Flatoverlay.insertHeatpoint(input: newHeatPoint) 79 | } 80 | return 81 | } 82 | else if(jdswiftheatmap.overlays.count == 0) ///First Overlay 83 | { 84 | let BigOverlay = JDHeatFlatPointOverlay(first: newHeatPoint) 85 | jdswiftheatmap.add(BigOverlay, level: MKOverlayLevel.aboveLabels) 86 | return 87 | } 88 | } 89 | collectToOneOverlay() 90 | } 91 | else if(Datatype == .RadiusPoint) 92 | { 93 | /* 94 | 1.3-1 type = Radius point classification The Point 95 | */ 96 | func CluseToOverlay() 97 | { 98 | for overlay in jdswiftheatmap.overlays 99 | { 100 | let overlaymaprect = overlay.boundingMapRect 101 | //Cluse in Old Overlay 102 | if(MKMapRectIntersectsRect(overlaymaprect, newHeatPoint.MapRect)) 103 | { 104 | if let heatoverlay = overlay as? JDHeatRadiusPointOverlay 105 | { 106 | heatoverlay.insertHeatpoint(input: newHeatPoint) 107 | return 108 | } 109 | } 110 | } 111 | //Create New Overlay,OverlayRender會一起創造 112 | let heatoverlay = JDHeatRadiusPointOverlay(first: newHeatPoint) 113 | jdswiftheatmap.add(heatoverlay, level: MKOverlayLevel.aboveLabels) 114 | } 115 | CluseToOverlay() 116 | } 117 | } 118 | 119 | if(MaxHeatLevelinWholeMap == 0) { fatalError("Max Heat level should not be 0") } 120 | 121 | /* 122 | 1.3.3 Reduce The Recover Overlay 123 | */ 124 | func ReduceOverlay(){ 125 | var ReduceBool:Bool = false 126 | repeat 127 | { 128 | ReduceBool = false 129 | for overlayX in jdswiftheatmap.overlays 130 | { 131 | guard let heatoverlayX = overlayX as? JDHeatRadiusPointOverlay 132 | else{ 133 | break 134 | } 135 | for overlayY in jdswiftheatmap.overlays 136 | { 137 | if(overlayY.isEqual(overlayX)){continue} 138 | let overlayXmaprect = overlayX.boundingMapRect 139 | let overlayYmaprect = overlayY.boundingMapRect 140 | if(MKMapRectIntersectsRect(overlayXmaprect, overlayYmaprect)) 141 | { 142 | ReduceBool = true 143 | if let heatoverlayY = overlayY as? JDHeatRadiusPointOverlay 144 | { 145 | for point in heatoverlayY.HeatPointsArray 146 | { 147 | heatoverlayX.insertHeatpoint(input: point) 148 | } 149 | } 150 | jdswiftheatmap.remove(overlayY) 151 | break 152 | } 153 | } 154 | if(ReduceBool) {break} 155 | } 156 | }while(ReduceBool) 157 | } 158 | ReduceOverlay() 159 | 160 | /* 161 | 1.4 All Point have Been Classified to Overlay 162 | 1.4.1 Caculate The Region where map should zoom later 163 | */ 164 | for overlay in jdswiftheatmap.overlays 165 | { 166 | if let heatoverlay = overlay as? JDHeatRadiusPointOverlay 167 | { 168 | let heatoverlayRect = heatoverlay.boundingMapRect 169 | let size = heatoverlayRect.size.height * heatoverlayRect.size.width 170 | let biggestize = biggestRegion.size.height * biggestRegion.size.width 171 | biggestRegion = (size > biggestize) ? heatoverlayRect : biggestRegion 172 | // 173 | } 174 | else if let heatoverlay = overlay as? JDHeatFlatPointOverlay 175 | { 176 | biggestRegion = heatoverlay.boundingMapRect 177 | } 178 | } 179 | StartComputRowFormData() 180 | } 181 | /** 182 | 2.0 Caculate HeatPoint data to a CGFormat 183 | **/ 184 | func StartComputRowFormData() 185 | { 186 | print(#function) 187 | LastVisibleMapRect = jdswiftheatmap.visibleMapRect 188 | func computing() 189 | { 190 | func OverlayRender(heatoverlay:JDHeatOverlay) 191 | { 192 | if let render = renderFor(overlay: heatoverlay) 193 | { 194 | if let CaculatedRowFormData = render.caculateRowFormData(maxHeat: MaxHeatLevelinWholeMap) 195 | { 196 | var rawdataproducer:JDRowDataProducer! 197 | let OverlayCGRect = CaculatedRowFormData.rect 198 | let LocalFormData = CaculatedRowFormData.data 199 | if(Datatype == .RadiusPoint) 200 | { 201 | rawdataproducer = JDRadiusPointRowDataProducer(size: (OverlayCGRect.size), rowHeatData: LocalFormData) 202 | } 203 | else if(Datatype == .FlatPoint) 204 | { 205 | rawdataproducer = JDFlatPointRowDataProducer(size: (OverlayCGRect.size), rowHeatData: LocalFormData) 206 | } 207 | Render_ProducerPair[render] = rawdataproducer 208 | // 209 | let visibleMacRect = biggestRegion 210 | 211 | let scaleUIView_MapRect:Double = Double(MapWidthInUIView) / visibleMacRect.size.width 212 | rawdataproducer?.reduceSize(scales: scaleUIView_MapRect) 213 | return 214 | } 215 | } 216 | //Nil in iphone 8 & ios11 217 | // fatalError("RenderPair may be wrong") 218 | } 219 | // 220 | if(Datatype == .RadiusPoint) 221 | { 222 | for overlay in jdswiftheatmap.overlays 223 | { 224 | if let heatoverlay = overlay as? JDHeatRadiusPointOverlay 225 | { 226 | OverlayRender(heatoverlay: heatoverlay) 227 | } 228 | } 229 | } 230 | else if(Datatype == .FlatPoint) 231 | { 232 | if(jdswiftheatmap.overlays.count == 1) 233 | { 234 | if let Flatoverlay = jdswiftheatmap.overlays[0] as? JDHeatFlatPointOverlay 235 | { 236 | OverlayRender(heatoverlay: Flatoverlay) 237 | } 238 | } 239 | } 240 | } 241 | missionThread.async(execute: { 242 | computing() 243 | self.caculateStart() 244 | }) 245 | } 246 | /** 247 | 3.0 Most Take time task 248 | **/ 249 | func caculateStart() 250 | { 251 | print(#function) 252 | self.Caculating = true 253 | func computing() 254 | { 255 | for overlay in jdswiftheatmap.overlays 256 | { 257 | if let heatoverlay = overlay as? JDHeatOverlay 258 | { 259 | if let render = renderFor(overlay: heatoverlay) 260 | { 261 | if let producer = Render_ProducerPair[render] 262 | { 263 | producer.produceRowData() 264 | render.Bitmapsize = producer.FitnessIntSize 265 | render.BytesPerRow = producer.BytesPerRow 266 | render.dataReference.append(contentsOf: producer.RowData) 267 | render.setNeedsDisplay() 268 | producer.rowformdatas = [] 269 | } 270 | } 271 | } 272 | } 273 | self.Caculating = false 274 | DispatchQueue.main.sync { 275 | if(jdswiftheatmap.showindicator) 276 | { 277 | jdswiftheatmap.indicator?.stopAnimating() 278 | } 279 | let ZoomOrigin = MKMapPoint(x: biggestRegion.origin.x - biggestRegion.size.width * 2, y: biggestRegion.origin.y - biggestRegion.size.height * 2) 280 | let zoomoutregion = MKMapRect(origin: ZoomOrigin, size: MKMapSize(width: biggestRegion.size.width * 4, height: biggestRegion.size.height * 4)) 281 | jdswiftheatmap.setVisibleMapRect(zoomoutregion, animated: true) 282 | } 283 | } 284 | computing() 285 | } 286 | 287 | var LastVisibleMapRect:MKMapRect = MKMapRect.init() 288 | } 289 | 290 | extension JDHeatMapMissionController 291 | { 292 | func mapViewWillStartRenderingMap() 293 | { 294 | if(Caculating) {return} //If last time rendering not finishe yet... 295 | let visibleMacRect = jdswiftheatmap.visibleMapRect 296 | if(visibleMacRect.size.width == biggestRegion.size.width && 297 | visibleMacRect.origin.x == biggestRegion.origin.x && 298 | visibleMacRect.origin.y == biggestRegion.origin.y) {return} 299 | let RerenderNessceryCheck = LastVisibleMapRect.size.width / visibleMacRect.size.width 300 | //The map zoom doesn't change significant 301 | if(RerenderNessceryCheck > 0.7 && RerenderNessceryCheck < 1.66 ) {return} 302 | print(#function) 303 | self.Caculating = true 304 | LastVisibleMapRect = visibleMacRect 305 | if(jdswiftheatmap.showindicator) 306 | { 307 | jdswiftheatmap.indicator?.startAnimating() 308 | } 309 | // 310 | func compuing() 311 | { 312 | for overlay in jdswiftheatmap.overlays 313 | { 314 | if let heatoverlay = overlay as? JDHeatOverlay 315 | { 316 | func RecaculateOverlayRender() 317 | { 318 | if let render = renderFor(overlay: heatoverlay) 319 | { 320 | let scaleUIView_MapRect:Double = Double(MapWidthInUIView) / visibleMacRect.size.width 321 | if let rawdataproducer = Render_ProducerPair[render] 322 | { 323 | /* 324 | Exam the cgimage which draw in last time have more pixel? 325 | */ 326 | let newWidth = Int(rawdataproducer.OriginCGSize.width * CGFloat(scaleUIView_MapRect) * 1.5) 327 | if let lastimage = render.Lastimage 328 | { 329 | if(lastimage.width > newWidth) { return } 330 | else { render.Lastimage = nil } //Make it can draw 331 | } 332 | /* 333 | Recaculate new Size new Data to draw a new cgimage 334 | (Probably user zoom in. 335 | */ 336 | rawdataproducer.reduceSize(scales: scaleUIView_MapRect) //Recaculate new FitnessSize 337 | rawdataproducer.produceRowData() 338 | render.Bitmapsize = rawdataproducer.FitnessIntSize 339 | render.BytesPerRow = rawdataproducer.BytesPerRow 340 | render.dataReference.append(contentsOf: rawdataproducer.RowData) 341 | render.setNeedsDisplay() 342 | rawdataproducer.rowformdatas = [] 343 | } 344 | } 345 | } 346 | RecaculateOverlayRender() 347 | } 348 | } 349 | } 350 | // 351 | missionThread.async(execute: { 352 | compuing() 353 | DispatchQueue.main.sync(execute: { 354 | if(self.jdswiftheatmap.showindicator) 355 | { 356 | self.jdswiftheatmap.indicator?.stopAnimating() 357 | } 358 | self.Caculating = false 359 | }) 360 | }) 361 | } 362 | } 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | -------------------------------------------------------------------------------- /JDRealHeatMap/JDSwiftHeatMap/JDOverlay.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JDOverlay.swift 3 | // JDSwiftHeatMap 4 | // 5 | // Created by 郭介騵 on 2017/6/14. 6 | // Copyright © 2017年 james12345. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import MapKit 11 | 12 | /** 13 | 這個類別只需要知道MapRect層面,不需要知道CGRect層面的事 14 | */ 15 | 16 | class JDHeatOverlay:NSObject, MKOverlay 17 | { 18 | var HeatPointsArray:[JDHeatPoint] = [] 19 | var CaculatedMapRect:MKMapRect? 20 | /* Overlay的中心 */ 21 | var coordinate: CLLocationCoordinate2D 22 | { 23 | let midMKPoint = MKMapPoint(x: MKMapRectGetMidX(boundingMapRect), y: MKMapRectGetMidY(boundingMapRect)) 24 | return MKCoordinateForMapPoint(midMKPoint) 25 | } 26 | /* Overlay涵蓋的範圍 */ 27 | var boundingMapRect: MKMapRect 28 | { 29 | guard let BeenCaculatedMapRect = CaculatedMapRect else { 30 | fatalError("boundingMapRect Error") 31 | } 32 | return BeenCaculatedMapRect 33 | } 34 | 35 | init(first Heatpoint:JDHeatPoint) 36 | { 37 | super.init() 38 | caculateMaprect(newPoint: Heatpoint) 39 | HeatPointsArray.append(Heatpoint) 40 | } 41 | 42 | func caculateMaprect(newPoint:JDHeatPoint) 43 | { 44 | fatalError("Should implement this method in child class") 45 | } 46 | 47 | /** 48 | 新的點進來先放在Buffer裡 49 | */ 50 | func insertHeatpoint(input:JDHeatPoint) 51 | { 52 | caculateMaprect(newPoint: input) 53 | HeatPointsArray.append(input) 54 | } 55 | } 56 | 57 | class JDHeatRadiusPointOverlay:JDHeatOverlay 58 | { 59 | var NewHeatPointBuffer:[JDHeatPoint] = [] 60 | /** 61 | 有新的點加進來 -> 62 | 重新計算這個Overlay的涵蓋 63 | */ 64 | override func caculateMaprect(newPoint:JDHeatPoint) 65 | { 66 | var MaxX:Double = -9999999999999 67 | var MaxY:Double = -9999999999999 68 | var MinX:Double = 99999999999999 69 | var MinY:Double = 99999999999999 70 | if let BeenCaculatedMapRect = CaculatedMapRect 71 | { 72 | //Not the First Time 73 | MaxX = MKMapRectGetMaxX(BeenCaculatedMapRect) 74 | MaxY = MKMapRectGetMaxY(BeenCaculatedMapRect) 75 | MinX = MKMapRectGetMinX(BeenCaculatedMapRect) 76 | MinY = MKMapRectGetMinY(BeenCaculatedMapRect) 77 | // 78 | let heatmaprect = newPoint.MapRect 79 | let tMaxX = MKMapRectGetMaxX(heatmaprect) 80 | let tMaxY = MKMapRectGetMaxY(heatmaprect) 81 | let tMinX = MKMapRectGetMinX(heatmaprect) 82 | let tMinY = MKMapRectGetMinY(heatmaprect) 83 | MaxX = (tMaxX > MaxX) ? tMaxX : MaxX 84 | MaxY = (tMaxY > MaxY) ? tMaxY : MaxY 85 | MinX = (tMinX < MinX) ? tMinX : MinX 86 | MinY = (tMinY < MinY) ? tMinY : MinY 87 | } 88 | else 89 | { 90 | //First Time Caculate Fitst Point Only 91 | let heatmaprect = newPoint.MapRect 92 | MaxX = MKMapRectGetMaxX(heatmaprect) 93 | MaxY = MKMapRectGetMaxY(heatmaprect) 94 | MinX = MKMapRectGetMinX(heatmaprect) 95 | MinY = MKMapRectGetMinY(heatmaprect) 96 | } 97 | let rect = MKMapRectMake(MinX, MinY, MaxX - MinX, MaxY - MinY) 98 | CaculatedMapRect = rect 99 | } 100 | 101 | 102 | } 103 | 104 | class JDHeatFlatPointOverlay:JDHeatOverlay 105 | { 106 | 107 | /** 108 | 有新的點加進來 -> 109 | 重新計算這個Overlay的涵蓋 110 | */ 111 | override func caculateMaprect(newPoint:JDHeatPoint) 112 | { 113 | var MaxX:Double = -9999999999999 114 | var MaxY:Double = -9999999999999 115 | var MinX:Double = 99999999999999 116 | var MinY:Double = 99999999999999 117 | if let BeenCaculatedMapRect = CaculatedMapRect 118 | { 119 | //Not the First Time 120 | MaxX = MKMapRectGetMaxX(BeenCaculatedMapRect) 121 | MaxY = MKMapRectGetMaxY(BeenCaculatedMapRect) 122 | MinX = MKMapRectGetMinX(BeenCaculatedMapRect) 123 | MinY = MKMapRectGetMinY(BeenCaculatedMapRect) 124 | // 125 | let heatmaprect = newPoint.MapRect 126 | let tMaxX = MKMapRectGetMaxX(heatmaprect) 127 | let tMaxY = MKMapRectGetMaxY(heatmaprect) 128 | let tMinX = MKMapRectGetMinX(heatmaprect) 129 | let tMinY = MKMapRectGetMinY(heatmaprect) 130 | MaxX = (tMaxX > MaxX) ? tMaxX : MaxX 131 | MaxY = (tMaxY > MaxY) ? tMaxY : MaxY 132 | MinX = (tMinX < MinX) ? tMinX : MinX 133 | MinY = (tMinY < MinY) ? tMinY : MinY 134 | } 135 | else 136 | { 137 | //First Time Caculate Fitst Point Only 138 | let heatmaprect = newPoint.MapRect 139 | MaxX = MKMapRectGetMaxX(heatmaprect) 140 | MaxY = MKMapRectGetMaxY(heatmaprect) 141 | MinX = MKMapRectGetMinX(heatmaprect) 142 | MinY = MKMapRectGetMinY(heatmaprect) 143 | } 144 | let rect = MKMapRectMake(MinX, MinY, MaxX - MinX, MaxY - MinY) 145 | CaculatedMapRect = rect 146 | } 147 | 148 | } 149 | -------------------------------------------------------------------------------- /JDRealHeatMap/JDSwiftHeatMap/JDOverlayRender.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JDOverlayRender.swift 3 | // JDSwiftHeatMap 4 | // 5 | // Created by 郭介騵 on 2017/6/19. 6 | // Copyright © 2017年 james12345. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import MapKit 11 | 12 | /** 13 | 這個類別只需要知道畫圖相關的,不用記住任何點Data 14 | 只要交給Producer製造還給他一個RowData 15 | */ 16 | class JDHeatOverlayRender:MKOverlayRenderer 17 | { 18 | var Lastimage:CGImage? 19 | var Bitmapsize:IntSize = IntSize() 20 | var BitmapMemorySize:Int{ 21 | return Bitmapsize.width * Bitmapsize.height * 4 22 | } 23 | var dataReference:[UTF8Char] = [] 24 | var BytesPerRow:Int = 0 25 | 26 | init(heat overlay: JDHeatOverlay) { 27 | super.init(overlay: overlay) 28 | self.alpha = 0.6 29 | } 30 | 31 | func caculateRowFormData(maxHeat level:Int)->(data:[RowFormHeatData],rect:CGRect)? 32 | { 33 | return nil 34 | } 35 | 36 | /** 37 | drawMapRect is the real meat of this class; it defines how MapKit should render this view when given a specific MKMapRect, MKZoomScale, and the CGContextRef 38 | */ 39 | override func draw(_ mapRect: MKMapRect, zoomScale: MKZoomScale, in context: CGContext) { 40 | 41 | guard let overlay = overlay as? JDHeatOverlay else { 42 | return 43 | } 44 | //Last Time Created image have more resolution, so keep using it 45 | if let lastTimeMoreHighSolutionImgae = Lastimage 46 | { 47 | let mapCGRect = rect(for: overlay.boundingMapRect) 48 | context.draw(lastTimeMoreHighSolutionImgae, in: mapCGRect) 49 | return 50 | } 51 | else if(dataReference.count == 0 ) 52 | { 53 | //The Data is not ready 54 | return 55 | } 56 | // 57 | func getHeatMapContextImage()->CGImage? 58 | { 59 | //More Detail 60 | func CreateContextOldWay()->CGImage? 61 | { 62 | func heatMapCGImage()->CGImage? 63 | { 64 | let tempBuffer = malloc(BitmapMemorySize) 65 | memcpy(tempBuffer, &dataReference, BytesPerRow * Bitmapsize.height) 66 | defer 67 | { 68 | free(tempBuffer) 69 | } 70 | let rgbColorSpace:CGColorSpace = CGColorSpaceCreateDeviceRGB() 71 | let alphabitmapinfo = CGImageAlphaInfo.premultipliedLast.rawValue 72 | if let contextlayer:CGContext = CGContext(data: tempBuffer, width: Bitmapsize.width, height: Bitmapsize.height, bitsPerComponent: 8, bytesPerRow: BytesPerRow, space: rgbColorSpace, bitmapInfo: alphabitmapinfo) 73 | { 74 | return contextlayer.makeImage() 75 | } 76 | return nil 77 | } 78 | 79 | if let cgimage = heatMapCGImage() 80 | { 81 | let cgsize:CGSize = CGSize(width: Bitmapsize.width, height: Bitmapsize.height) 82 | UIGraphicsBeginImageContext(cgsize) 83 | if let contexts = UIGraphicsGetCurrentContext() 84 | { 85 | let rect = CGRect(origin: CGPoint.zero, size: cgsize) 86 | contexts.draw(cgimage, in: rect) 87 | return contexts.makeImage() 88 | } 89 | } 90 | print("Create fail") 91 | return nil 92 | } 93 | let img = CreateContextOldWay() 94 | UIGraphicsEndImageContext() 95 | return img 96 | } 97 | if let tempimage = getHeatMapContextImage() 98 | { 99 | let mapCGRect = rect(for: overlay.boundingMapRect) 100 | Lastimage = tempimage 101 | context.clear(mapCGRect) 102 | self.dataReference.removeAll() 103 | context.draw(Lastimage!, in: mapCGRect) 104 | } 105 | else{ 106 | print("cgcontext error") 107 | } 108 | } 109 | } 110 | 111 | class JDRadiusPointOverlayRender:JDHeatOverlayRender 112 | { 113 | override func caculateRowFormData(maxHeat level:Int)->(data:[RowFormHeatData],rect:CGRect)? 114 | { 115 | guard let overlay = overlay as? JDHeatRadiusPointOverlay else { 116 | return nil 117 | } 118 | var rowformArr:[RowFormHeatData] = [] 119 | // 120 | for heatpoint in overlay.HeatPointsArray 121 | { 122 | let mkmappoint = heatpoint.MidMapPoint 123 | let GlobalCGpoint:CGPoint = self.point(for: mkmappoint) 124 | let OverlayCGRect = rect(for: overlay.boundingMapRect) 125 | let localX = GlobalCGpoint.x - (OverlayCGRect.origin.x) 126 | let localY = GlobalCGpoint.y - (OverlayCGRect.origin.y) 127 | let loaclCGPoint = CGPoint(x: localX, y: localY) 128 | // 129 | let radiusinMKDistanse:Double = heatpoint.radiusInMKDistance 130 | let radiusmaprect = MKMapRect(origin: MKMapPoint.init(), size: MKMapSize(width: radiusinMKDistanse, height: radiusinMKDistanse)) 131 | let radiusCGDistance = rect(for: radiusmaprect).width 132 | // 133 | let newRow:RowFormHeatData = RowFormHeatData(heatlevel: Float(heatpoint.HeatLevel) / Float(level), localCGpoint: loaclCGPoint, radius: radiusCGDistance) 134 | rowformArr.append(newRow) 135 | } 136 | let cgsize = rect(for: overlay.boundingMapRect) 137 | return (rect:cgsize,data:rowformArr) 138 | } 139 | } 140 | 141 | class JDFlatPointOverlayRender:JDHeatOverlayRender 142 | { 143 | override func caculateRowFormData(maxHeat level:Int)->(data:[RowFormHeatData],rect:CGRect)? 144 | { 145 | guard let FlatPointoverlay = overlay as? JDHeatFlatPointOverlay else { 146 | return nil 147 | } 148 | // 149 | var rowformArr:[RowFormHeatData] = [] 150 | let OverlayCGRect:CGRect = rect(for: FlatPointoverlay.boundingMapRect) 151 | for heatpoint in FlatPointoverlay.HeatPointsArray 152 | { 153 | let mkmappoint = heatpoint.MidMapPoint 154 | let GlobalCGpoint:CGPoint = self.point(for: mkmappoint) 155 | // 156 | let localX = GlobalCGpoint.x - (OverlayCGRect.origin.x) 157 | let localY = GlobalCGpoint.y - (OverlayCGRect.origin.y) 158 | let loaclCGPoint = CGPoint(x: localX, y: localY) 159 | // 160 | let radiusinMKDistanse:Double = heatpoint.radiusInMKDistance 161 | let radiusmaprect = MKMapRect(origin: MKMapPoint.init(), size: MKMapSize(width: radiusinMKDistanse, height: radiusinMKDistanse)) 162 | let radiusCGDistance = rect(for: radiusmaprect).width 163 | // 164 | let newRow:RowFormHeatData = RowFormHeatData(heatlevel: Float(heatpoint.HeatLevel) / Float(level), localCGpoint: loaclCGPoint, radius: radiusCGDistance) 165 | rowformArr.append(newRow) 166 | } 167 | let cgsize = rect(for: overlay.boundingMapRect) 168 | return (rect:cgsize,data:rowformArr) 169 | } 170 | } 171 | 172 | 173 | 174 | -------------------------------------------------------------------------------- /JDRealHeatMap/JDSwiftHeatMap/JDRealHeatMap.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JDSwiftHeatMap.swift 3 | // JDSwiftHeatMap 4 | // 5 | // Created by 郭介騵 on 2017/6/12. 6 | // Copyright © 2017年 james12345. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import MapKit 11 | 12 | public enum JDMapType 13 | { 14 | case RadiusDistinct 15 | case FlatDistinct 16 | case RadiusBlurry 17 | } 18 | 19 | enum DataPointType 20 | { 21 | case FlatPoint 22 | case RadiusPoint 23 | } 24 | 25 | public class JDSwiftHeatMap:MKMapView 26 | { 27 | var heatmapdelegate: JDHeatMapDelegate? 28 | var missionController:JDHeatMapMissionController! 29 | var indicator:UIActivityIndicatorView? 30 | // 31 | public var showindicator:Bool = true{ 32 | didSet{ 33 | if(!showindicator) 34 | { 35 | indicator?.stopAnimating() 36 | } 37 | } 38 | } 39 | 40 | public init(frame: CGRect,delegate d:JDHeatMapDelegate,maptype type:JDMapType,BasicColors array:[UIColor] = [UIColor.blue,UIColor.green,UIColor.red],devideLevel:Int = 2) 41 | { 42 | super.init(frame: frame) 43 | self.showsScale = true 44 | self.delegate = self 45 | self.heatmapdelegate = d 46 | 47 | JDRowDataProducer.theColorMixer = JDHeatColorMixer(array: array, level: devideLevel) 48 | 49 | if(type == .RadiusBlurry) 50 | { 51 | missionController = JDHeatMapMissionController(JDSwiftHeatMap: self, datatype: .RadiusPoint,mode: .BlurryMode) 52 | } 53 | else if(type == .FlatDistinct) 54 | { 55 | missionController = JDHeatMapMissionController(JDSwiftHeatMap: self, datatype: .FlatPoint,mode: .DistinctMode) 56 | } 57 | else if(type == .RadiusDistinct) 58 | { 59 | missionController = JDHeatMapMissionController(JDSwiftHeatMap: self, datatype: .RadiusPoint,mode: .DistinctMode) 60 | } 61 | refresh() 62 | InitIndicator() 63 | } 64 | 65 | required public init?(coder aDecoder: NSCoder) { 66 | fatalError("init(coder:) has not been implemented") 67 | } 68 | 69 | public func refresh() 70 | { 71 | if(self.showindicator) 72 | { 73 | self.indicator?.startAnimating() 74 | } 75 | missionController.ExecuteRefreshMission() 76 | } 77 | 78 | public func setType(type:JDMapType) 79 | { 80 | if(type == .RadiusBlurry) 81 | { 82 | missionController = JDHeatMapMissionController(JDSwiftHeatMap: self, datatype: .RadiusPoint,mode: .BlurryMode) 83 | } 84 | else if(type == .FlatDistinct) 85 | { 86 | missionController = JDHeatMapMissionController(JDSwiftHeatMap: self, datatype: .FlatPoint,mode: .DistinctMode) 87 | } 88 | else if(type == .RadiusDistinct) 89 | { 90 | missionController = JDHeatMapMissionController(JDSwiftHeatMap: self, datatype: .RadiusPoint,mode: .DistinctMode) 91 | } 92 | refresh() 93 | } 94 | 95 | func reZoomRegion(biggestRegion:MKMapRect) 96 | { 97 | self.setRegion(MKCoordinateRegionForMapRect(biggestRegion), animated: true) 98 | } 99 | 100 | func InitIndicator() 101 | { 102 | indicator = UIActivityIndicatorView(activityIndicatorStyle: .whiteLarge) 103 | indicator?.translatesAutoresizingMaskIntoConstraints = false 104 | self.addSubview(indicator!) 105 | let sizeWidth = NSLayoutConstraint(item: indicator!, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 0.0, constant: 60) 106 | let sizeHeight = NSLayoutConstraint(item: indicator!, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 0.0, constant: 60) 107 | let CenterX = NSLayoutConstraint(item: indicator!, attribute: .centerX, relatedBy: .equal, toItem: self, attribute: .centerX, multiplier: 1.0, constant: 0) 108 | let CenterY = NSLayoutConstraint(item: indicator!, attribute: .centerY, relatedBy: .equal, toItem: self, attribute: .centerY, multiplier: 1.0, constant: 0) 109 | 110 | indicator?.addConstraints([sizeWidth,sizeHeight]) 111 | self.addConstraints([CenterX,CenterY]) 112 | self.updateConstraints() 113 | } 114 | } 115 | 116 | extension JDSwiftHeatMap:MKMapViewDelegate 117 | { 118 | 119 | public func heatmapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer? 120 | { 121 | if let FlatOverlay = overlay as? JDHeatFlatPointOverlay 122 | { 123 | let onlyRender = JDFlatPointOverlayRender(heat: FlatOverlay) 124 | return onlyRender 125 | } 126 | else if let radiusOverlay = overlay as? JDHeatRadiusPointOverlay 127 | { 128 | let render = JDRadiusPointOverlayRender(heat: radiusOverlay) 129 | return render 130 | } 131 | return MKOverlayRenderer() 132 | } 133 | 134 | public func heatmapViewWillStartRenderingMap(_ mapView: MKMapView) 135 | { 136 | missionController.mapViewWillStartRenderingMap() 137 | } 138 | 139 | public func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer 140 | { 141 | if let heatoverlay = self.heatmapView(mapView, rendererFor: overlay) 142 | { 143 | return heatoverlay 144 | } 145 | else 146 | { 147 | return MKOverlayRenderer() 148 | } 149 | } 150 | 151 | public func mapViewWillStartRenderingMap(_ mapView: MKMapView) { 152 | self.heatmapViewWillStartRenderingMap(mapView) 153 | } 154 | } 155 | 156 | public protocol JDHeatMapDelegate { 157 | func heatmap(HeatPointCount heatmap:JDSwiftHeatMap) -> Int 158 | func heatmap(HeatLevelFor index:Int) -> Int 159 | func heatmap(RadiusInKMFor index:Int) -> Double 160 | func heatmap(CoordinateFor index:Int) -> CLLocationCoordinate2D 161 | } 162 | 163 | extension JDHeatMapDelegate 164 | { 165 | func heatmap(RadiusInKMFor index:Int) -> Double 166 | { 167 | return 100 168 | } 169 | } 170 | 171 | struct JDHeatPoint 172 | { 173 | var HeatLevel:Int = 0 174 | var coordinate:CLLocationCoordinate2D = CLLocationCoordinate2D.init() 175 | 176 | var radiusInKillometer:Double = 100 177 | var MidMapPoint:MKMapPoint 178 | { 179 | return MKMapPointForCoordinate(self.coordinate) 180 | } 181 | var radiusInMKDistance:Double 182 | { 183 | let locationdegree:CLLocationDegrees = coordinate.latitude 184 | let MeterPerMapPointInNowLati:Double = MKMetersPerMapPointAtLatitude(locationdegree) 185 | let KMPerPerMapPoint:Double = MeterPerMapPointInNowLati / 1000 186 | let MapPointPerKM:Double = 1 / KMPerPerMapPoint 187 | return radiusInKillometer * MapPointPerKM 188 | } 189 | 190 | var MapRect:MKMapRect 191 | { 192 | let origin:MKMapPoint = MKMapPoint(x: MidMapPoint.x - radiusInMKDistance, y: MidMapPoint.y - radiusInMKDistance) 193 | let size:MKMapSize = MKMapSize(width: 2 * radiusInMKDistance, height: 2 * radiusInMKDistance) 194 | return MKMapRect(origin: origin, size: size) 195 | } 196 | 197 | init() 198 | { 199 | 200 | } 201 | 202 | init(heat level:Int,coor:CLLocationCoordinate2D,heatradius inKM:Double) 203 | { 204 | radiusInKillometer = inKM 205 | HeatLevel = level 206 | coordinate = coor 207 | } 208 | 209 | func distanceto(anoter point:JDHeatPoint)->CGFloat 210 | { 211 | let latidiff = (point.coordinate.latitude - self.coordinate.latitude) 212 | let longdiff = (point.coordinate.longitude - self.coordinate.longitude) 213 | let sqrts = sqrt((latidiff * latidiff) + (longdiff * longdiff)) 214 | return CGFloat(sqrts) 215 | } 216 | 217 | 218 | } 219 | 220 | 221 | -------------------------------------------------------------------------------- /JDRealHeatMap/JDSwiftHeatMap/JDRowDataProducer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JDRowDataProducer.swift 3 | // JDSwiftHeatMap 4 | // 5 | // Created by 郭介騵 on 2017/6/14. 6 | // Copyright © 2017年 james12345. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import MapKit 11 | 12 | struct RowFormHeatData { 13 | var heatlevel:Float = 0 14 | var localCGpoint:CGPoint = CGPoint.zero 15 | var radius:CGFloat = 0 16 | } 17 | 18 | struct IntSize { 19 | var width:Int = 0 20 | var height:Int = 0 21 | } 22 | 23 | /** 24 | All this class needs to know is relative position & CGSize 25 | And Produce an array of rgba colro 26 | **/ 27 | class JDRowDataProducer:NSObject 28 | { 29 | /* 30 | These two variable should not be modified after 31 | */ 32 | var Originrowformdatas:[RowFormHeatData] = [] 33 | var OriginCGSize:CGSize = CGSize.zero 34 | // 35 | static var theColorMixer:JDHeatColorMixer! 36 | // 37 | var RowData:[UTF8Char] = [] 38 | var rowformdatas:[RowFormHeatData] = [] 39 | var FitnessIntSize:IntSize! 40 | 41 | var BytesPerRow:Int 42 | { 43 | return 4 * FitnessIntSize.width 44 | } 45 | 46 | init(size:CGSize,rowHeatData:[RowFormHeatData]) 47 | { 48 | super.init() 49 | 50 | self.Originrowformdatas = rowHeatData 51 | self.OriginCGSize = size 52 | } 53 | /** 54 | Sould not Miss this or the image size will up to GB 55 | (All beacuse MKMapRect Has a high definetion) 56 | **/ 57 | func reduceSize(scales:Double) 58 | { 59 | let scale:CGFloat = CGFloat(scales) * 1.5 60 | let newWidth = Int(OriginCGSize.width * scale) 61 | let newHeight = Int(OriginCGSize.height * scale) 62 | self.FitnessIntSize = IntSize(width: newWidth, height: newHeight) 63 | 64 | func reduceRowData() 65 | { 66 | rowformdatas.removeAll() 67 | for origindata in Originrowformdatas 68 | { 69 | let newX = origindata.localCGpoint.x * scale 70 | let newY = origindata.localCGpoint.y * scale 71 | let newCGPoint = CGPoint(x: newX, y: newY) 72 | let newRadius = origindata.radius * scale 73 | let modifiRowFormData = RowFormHeatData(heatlevel: origindata.heatlevel, localCGpoint: newCGPoint , radius: newRadius) 74 | rowformdatas.append(modifiRowFormData) 75 | } 76 | } 77 | reduceRowData() 78 | RowData = Array.init(repeating: 0, count: 4 * FitnessIntSize.width * FitnessIntSize.height) 79 | } 80 | 81 | /** 82 | SubClass Should Override thie method 83 | **/ 84 | func produceRowData() 85 | { 86 | } 87 | } 88 | 89 | class JDRadiusPointRowDataProducer:JDRowDataProducer 90 | { 91 | override func produceRowData() 92 | { 93 | //print(#function + "w:\(FitnessIntSize.width),w:\(FitnessIntSize.height)") 94 | var ByteCount:Int = 0 95 | for h in 0.. 0) 106 | { 107 | destiny += ratio * heatpoint.heatlevel 108 | } 109 | } 110 | if(destiny > 1) 111 | { 112 | destiny = 1 113 | } 114 | let rgb = JDRowDataProducer.theColorMixer.getDestinyColorRGB(inDestiny: destiny) 115 | 116 | let redRow:UTF8Char = rgb.redRow 117 | let greenRow:UTF8Char = rgb.greenRow 118 | let BlueRow:UTF8Char = rgb.BlueRow 119 | let alpha:UTF8Char = rgb.alpha 120 | 121 | self.RowData[ByteCount] = redRow 122 | self.RowData[ByteCount+1] = greenRow 123 | self.RowData[ByteCount+2] = BlueRow 124 | self.RowData[ByteCount+3] = alpha 125 | ByteCount += 4 126 | } 127 | } 128 | } 129 | } 130 | 131 | class JDFlatPointRowDataProducer:JDRowDataProducer 132 | { 133 | override func produceRowData() 134 | { 135 | //print(#function + "w:\(FitnessIntSize.width),w:\(FitnessIntSize.height)") 136 | var ByteCount:Int = 0 137 | for h in 0.. 0) 148 | { 149 | destiny += ratio * heatpoint.heatlevel 150 | } 151 | } 152 | if(destiny == 0) 153 | { 154 | destiny += 0.01 155 | } 156 | 157 | if(destiny > 1) 158 | { 159 | destiny = 1 160 | } 161 | 162 | let rgb = JDRowDataProducer.theColorMixer.getDestinyColorRGB(inDestiny: destiny) 163 | 164 | let redRow:UTF8Char = rgb.redRow 165 | let greenRow:UTF8Char = rgb.greenRow 166 | let BlueRow:UTF8Char = rgb.BlueRow 167 | let alpha:UTF8Char = UTF8Char(Int(destiny * 255)) 168 | 169 | self.RowData[ByteCount] = redRow 170 | self.RowData[ByteCount+1] = greenRow 171 | self.RowData[ByteCount+2] = BlueRow 172 | self.RowData[ByteCount+3] = alpha 173 | ByteCount += 4 174 | } 175 | } 176 | } 177 | } 178 | 179 | 180 | 181 | extension CGPoint 182 | { 183 | func distanceTo(anther point:CGPoint)->Float 184 | { 185 | let diffx = (self.x - point.x) * (self.x - point.x) 186 | let diffy = (self.y - point.y) * (self.y - point.y) 187 | return sqrtf(Float(diffx + diffy)) 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /JDRealHeatMap/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // JDSwiftHeatMap 4 | // 5 | // Created by 郭介騵 on 2017/6/12. 6 | // Copyright © 2017年 james12345. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import MapKit 11 | 12 | class ViewController: UIViewController { 13 | 14 | @IBOutlet weak var mapsView: UIView! 15 | var map:JDSwiftHeatMap? 16 | var testpointCoor = [CLLocationCoordinate2D(latitude: 27, longitude: 120),CLLocationCoordinate2D(latitude: 25.3, longitude: 119),CLLocationCoordinate2D(latitude: 27, longitude: 120), 17 | CLLocationCoordinate2D(latitude: 27, longitude: 121) 18 | ] 19 | override func viewDidLoad() { 20 | super.viewDidLoad() 21 | addRandomData() 22 | 23 | map = JDSwiftHeatMap(frame: mapsView.frame, delegate: self, maptype: .FlatDistinct) 24 | //map = JDSwiftHeatMap(frame: mapsView.frame, delegate: self, maptype: .FlatDistinct, BasicColors: [UIColor.yellow,UIColor.red], devideLevel: 2) 25 | map?.delegate = self 26 | mapsView.addSubview(map!) 27 | } 28 | 29 | override func didReceiveMemoryWarning() { 30 | super.didReceiveMemoryWarning() 31 | // Dispose of any resources that can be recreated. 32 | } 33 | 34 | @IBAction func changeToRaidusD(_ sender: Any) { 35 | map?.setType(type: .RadiusDistinct) 36 | } 37 | 38 | @IBAction func ChangeToRadiusB(_ sender: Any) { 39 | map?.setType(type: .RadiusBlurry) 40 | } 41 | 42 | @IBAction func ChangeToFlatD(_ sender: Any) { 43 | map?.setType(type: .FlatDistinct) 44 | } 45 | 46 | 47 | func addRandomData() 48 | { 49 | for _ in 0..<20 50 | { 51 | let loti:Double = Double(119) + Double(Float(arc4random()) / Float(UINT32_MAX)) 52 | let lati:Double = Double(25 + arc4random_uniform(4)) + 2 * Double(Float(arc4random()) / Float(UINT32_MAX)) 53 | testpointCoor.append(CLLocationCoordinate2D(latitude: lati, longitude: loti)) 54 | } 55 | } 56 | } 57 | 58 | extension ViewController:MKMapViewDelegate 59 | { 60 | func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer { 61 | if let heatoverlay = map?.heatmapView(mapView, rendererFor: overlay) 62 | { 63 | return heatoverlay 64 | } 65 | else 66 | { 67 | return MKOverlayRenderer() 68 | } 69 | } 70 | 71 | func mapViewWillStartRenderingMap(_ mapView: MKMapView) { 72 | map?.heatmapViewWillStartRenderingMap(mapView) 73 | } 74 | } 75 | 76 | extension ViewController:JDHeatMapDelegate 77 | { 78 | func heatmap(HeatPointCount heatmap:JDSwiftHeatMap) -> Int 79 | { 80 | return testpointCoor.count 81 | } 82 | 83 | func heatmap(HeatLevelFor index:Int) -> Int 84 | { 85 | return 1 + index 86 | } 87 | 88 | func heatmap(RadiusInKMFor index: Int) -> Double { 89 | return Double(20 + index * 2) 90 | } 91 | 92 | func heatmap(CoordinateFor index:Int) -> CLLocationCoordinate2D 93 | { 94 | return testpointCoor[index] 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /JDSwiftHeatMap.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'JDSwiftHeatMap' 3 | s.version = '1.1.0' 4 | s.summary = 'You can easily make a highly customized HeatMap' 5 | 6 | s.description = <<-DESC 7 | JDSwiftMap is an IOS Native MapKit Library. 8 | You can easily make a highly customized HeatMap. 9 | DESC 10 | 11 | s.homepage = 'https://github.com/jamesdouble/JDSwiftHeatMap' 12 | s.license = { :type => 'MIT', :file => 'LICENSE' } 13 | s.author = { 'JamesDouble' => 'jameskuo12345@gmail.com' } 14 | s.source = { :git => 'https://github.com/jamesdouble/JDSwiftHeatMap.git', :tag => s.version.to_s } 15 | 16 | s.ios.deployment_target = '9.0' 17 | s.source_files = 'JDRealHeatMap/JDSwiftHeatMap/*' 18 | 19 | end 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 JAMES KUO - IOS Developer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Alt text](https://raw.githubusercontent.com/jamesdouble/JDSwiftHeatMap/master/Readme_img/logo.png?token=AJBUU8PbfD_WRNgAB4UEqbt1vDhm2iS3ks5ZbgTowA%3D%3D) 2 | 3 | **JDSwiftMap** is an IOS Native MapKit Library. 4 | 5 | You can easily make a highly customized HeatMap. 6 | 7 | ![Alt text](https://img.shields.io/badge/SwiftVersion-4.0+-red.svg?link=http://left&link=http://right) 8 | ![Alt text](https://img.shields.io/badge/IOSVersion-9.0+-green.svg) 9 | ![Alt text](https://img.shields.io/badge/BuildVersion-1.0.0-green.svg) 10 | ![Alt text](https://img.shields.io/badge/Author-JamesDouble-blue.svg?link=http://https://jamesdouble.github.io/index.html&link=http://https://jamesdouble.github.io/index.html) 11 | 12 | 13 | ![Alt text](https://raw.githubusercontent.com/jamesdouble/JDSwiftHeatMap/master/Readme_img/jdheatmapDemo.png?token=AJBUU1UA_L_wx5f_E3iRsaUGAh_xg3pCks5Zb1yIwA%3D%3D) 14 | 15 | # Installation 16 | * Cocoapods 17 | 18 | ``` 19 | pod 'JDSWiftHeatMap' 20 | ``` 21 | 22 | 23 | # Usage 24 | 25 | JDSwiftHeatMap is based on **IOS native MKMapView**, 26 | 27 | so you must familiar with. 28 | 29 | ## Init 30 | 31 | * Give a frame. 32 | * Follow JDHeatMapDelegate. 33 | * Choose a MapType Below 34 | 35 | ```Swift 36 | map = JDRealHeatMap(frame: self.view.frame, delegate: self, maptype: .FlatDistinct) 37 | self.view.addSubview(map!) 38 | ``` 39 | 40 | ### With ColorSetting 41 | ```Swift 42 | map = JDSwiftHeatMap(frame: mapsView.frame, delegate: self, maptype: .FlatDistinct, BasicColors: [UIColor.yellow,UIColor.red], devideLevel: 2) 43 | self.view.addSubview(map!) 44 | ``` 45 | BasicColors is element color array. 46 | 47 | Devide Level = How Many Middle Colors between Basic Colors. 48 | 49 | ## Delegate - Most Important 50 | 51 | There are two delegate you need to pay close. 52 | 53 | 1. ***MKMapViewDelegate*** - (Optional) 54 | 55 | This is the delegate you familiar,( AnnoationView For.., Render For...) You sure can use this delegate in old way, or not to follow this delegate. 56 | 57 | **But if you do, you may need to follow two essential function.** 58 | 59 | ```Swift 60 | extension ViewController:MKMapViewDelegate 61 | { 62 | func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer 63 | { 64 | if let heatoverlay = map?.heatmapView(mapView, rendererFor: overlay) 65 | { 66 | return heatoverlay 67 | } 68 | else 69 | { 70 | var yourownRender = yourownRenderClass() 71 | return yourownRender 72 | } 73 | } 74 | 75 | func mapViewWillStartRenderingMap(_ mapView: MKMapView) 76 | { 77 | map?.heatmapViewWillStartRenderingMap(mapView) 78 | } 79 | } 80 | map.delegate = self 81 | 82 | ``` 83 | 84 | 2. ***JDHeatMapDelegate*** 85 | 86 | When we talk to Heat Map, the most important thing is ***"Data"*** ! You should provide the data you want to display in this delegate. 87 | 88 | ```Swift 89 | public protocol JDHeatMapDelegate { 90 | func heatmap(HeatPointCount heatmap:JDRealHeatMap) -> Int 91 | func heatmap(HeatLevelFor index:Int) -> Int 92 | @Optional func heatmap(RadiusInKMFor index:Int) -> Double 93 | func heatmap(CoordinateFor index:Int) -> CLLocationCoordinate2D 94 | } 95 | ``` 96 | *The default radius in km is 100KM.* 97 | 98 | ### Notice 99 | 100 | More data will cause **large memory using**, should be notice. 101 | 102 | ## Setting 103 | 104 | 1. public func setType(type:JDMapType) 105 | 106 | change the display type, it will refresh automatically. 107 | 108 | 2. public func refresh() 109 | 110 | Call this function when data changed. 111 | 112 | 3. public var showindicator:Bool 113 | 114 | Set the loading indicator showing or not. 115 | 116 | 117 | -------------------------------------------------------------------------------- /Readme_img/jdheatmapDemo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesdouble/JDSwiftHeatMap/3e22ab10abd302f5fb57f7de6bcd22223722fa0f/Readme_img/jdheatmapDemo.png -------------------------------------------------------------------------------- /Readme_img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesdouble/JDSwiftHeatMap/3e22ab10abd302f5fb57f7de6bcd22223722fa0f/Readme_img/logo.png --------------------------------------------------------------------------------