@maydon_tech/react-native-nitro-maps 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +20 -0
- package/NitroMap.podspec +42 -0
- package/README.md +172 -0
- package/android/CMakeLists.txt +27 -0
- package/android/build.gradle +141 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +3 -0
- package/android/src/main/cpp/cpp-adapter.cpp +6 -0
- package/android/src/main/java/com/margelo/nitro/nitromap/ClusterIconGenerator.kt +108 -0
- package/android/src/main/java/com/margelo/nitro/nitromap/ColorUtils.kt +63 -0
- package/android/src/main/java/com/margelo/nitro/nitromap/HybridNitroMap.kt +408 -0
- package/android/src/main/java/com/margelo/nitro/nitromap/HybridNitroMapConfig.kt +68 -0
- package/android/src/main/java/com/margelo/nitro/nitromap/MarkerIconCache.kt +176 -0
- package/android/src/main/java/com/margelo/nitro/nitromap/MarkerIconFactory.kt +252 -0
- package/android/src/main/java/com/margelo/nitro/nitromap/NitroMapPackage.kt +33 -0
- package/android/src/main/java/com/margelo/nitro/nitromap/clustering/NitroClusterEngine.kt +252 -0
- package/android/src/main/java/com/margelo/nitro/nitromap/clustering/QuadTree.kt +195 -0
- package/android/src/main/java/com/margelo/nitro/nitromap/providers/GoogleMapProvider.kt +912 -0
- package/android/src/main/java/com/margelo/nitro/nitromap/providers/MapProviderInterface.kt +70 -0
- package/cpp/ClusterEngine.hpp +411 -0
- package/cpp/KDBush.hpp +238 -0
- package/cpp/QuadTree.hpp +246 -0
- package/ios/Clustering/ClusterEngineWrapper.h +58 -0
- package/ios/Clustering/ClusterEngineWrapper.mm +142 -0
- package/ios/Clustering/ClusterIconRenderer.swift +80 -0
- package/ios/Clustering/NitroClusterEngine.swift +117 -0
- package/ios/Clustering/NitroClusterIconGenerator.swift +35 -0
- package/ios/MarkerRenderer/MarkerIconFactory.swift +322 -0
- package/ios/MarkerRenderer/PriceMarkerRenderer.swift +140 -0
- package/ios/NitroMap.swift +332 -0
- package/ios/NitroMapConfig/HybridNitroMapConfig.swift +33 -0
- package/ios/Providers/GoogleMapDelegate.swift +310 -0
- package/ios/Providers/GoogleMapProvider+Camera.swift +164 -0
- package/ios/Providers/GoogleMapProvider.swift +924 -0
- package/ios/Providers/MapProviderProtocol.swift +78 -0
- package/ios/Shared/ClusterConfig+Factory.swift +58 -0
- package/ios/Shared/ClusteringManager.swift +211 -0
- package/ios/Shared/MapStyleProvider.swift +135 -0
- package/ios/Shared/MarkerSelectionHandler.swift +116 -0
- package/ios/Utils/ColorValueExtension.swift +54 -0
- package/lib/module/components/ImageMarker.js +146 -0
- package/lib/module/components/ImageMarker.js.map +1 -0
- package/lib/module/components/NitroMap.js +320 -0
- package/lib/module/components/NitroMap.js.map +1 -0
- package/lib/module/components/PriceMarker.js +165 -0
- package/lib/module/components/PriceMarker.js.map +1 -0
- package/lib/module/context/NitroMapContext.js +15 -0
- package/lib/module/context/NitroMapContext.js.map +1 -0
- package/lib/module/hooks/useNitroMarker.js +104 -0
- package/lib/module/hooks/useNitroMarker.js.map +1 -0
- package/lib/module/index.js +21 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/modules/index.js +4 -0
- package/lib/module/modules/index.js.map +1 -0
- package/lib/module/modules/module.js +30 -0
- package/lib/module/modules/module.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/specs/NitroMap.nitro.js +4 -0
- package/lib/module/specs/NitroMap.nitro.js.map +1 -0
- package/lib/module/specs/NitroMapConfig.nitro.js +4 -0
- package/lib/module/specs/NitroMapConfig.nitro.js.map +1 -0
- package/lib/module/types/map.js +2 -0
- package/lib/module/types/map.js.map +1 -0
- package/lib/module/types/marker.js +4 -0
- package/lib/module/types/marker.js.map +1 -0
- package/lib/module/utils/colors.js +147 -0
- package/lib/module/utils/colors.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/components/ImageMarker.d.ts +70 -0
- package/lib/typescript/src/components/ImageMarker.d.ts.map +1 -0
- package/lib/typescript/src/components/NitroMap.d.ts +14 -0
- package/lib/typescript/src/components/NitroMap.d.ts.map +1 -0
- package/lib/typescript/src/components/PriceMarker.d.ts +88 -0
- package/lib/typescript/src/components/PriceMarker.d.ts.map +1 -0
- package/lib/typescript/src/context/NitroMapContext.d.ts +16 -0
- package/lib/typescript/src/context/NitroMapContext.d.ts.map +1 -0
- package/lib/typescript/src/hooks/useNitroMarker.d.ts +78 -0
- package/lib/typescript/src/hooks/useNitroMarker.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +12 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/modules/index.d.ts +2 -0
- package/lib/typescript/src/modules/index.d.ts.map +1 -0
- package/lib/typescript/src/modules/module.d.ts +22 -0
- package/lib/typescript/src/modules/module.d.ts.map +1 -0
- package/lib/typescript/src/specs/NitroMap.nitro.d.ts +227 -0
- package/lib/typescript/src/specs/NitroMap.nitro.d.ts.map +1 -0
- package/lib/typescript/src/specs/NitroMapConfig.nitro.d.ts +10 -0
- package/lib/typescript/src/specs/NitroMapConfig.nitro.d.ts.map +1 -0
- package/lib/typescript/src/types/map.d.ts +154 -0
- package/lib/typescript/src/types/map.d.ts.map +1 -0
- package/lib/typescript/src/types/marker.d.ts +248 -0
- package/lib/typescript/src/types/marker.d.ts.map +1 -0
- package/lib/typescript/src/utils/colors.d.ts +82 -0
- package/lib/typescript/src/utils/colors.d.ts.map +1 -0
- package/nitro.json +21 -0
- package/nitrogen/generated/android/c++/JCamera.hpp +74 -0
- package/nitrogen/generated/android/c++/JClusterAnimationStyle.hpp +68 -0
- package/nitrogen/generated/android/c++/JClusterConfig.hpp +121 -0
- package/nitrogen/generated/android/c++/JClusterPressEvent.hpp +86 -0
- package/nitrogen/generated/android/c++/JClusterStrategy.hpp +59 -0
- package/nitrogen/generated/android/c++/JColorValue.cpp +26 -0
- package/nitrogen/generated/android/c++/JColorValue.hpp +70 -0
- package/nitrogen/generated/android/c++/JCoordinate.hpp +61 -0
- package/nitrogen/generated/android/c++/JEdgePadding.hpp +69 -0
- package/nitrogen/generated/android/c++/JFunc_void.hpp +75 -0
- package/nitrogen/generated/android/c++/JFunc_void_ClusterPressEvent.hpp +81 -0
- package/nitrogen/generated/android/c++/JFunc_void_MapError.hpp +78 -0
- package/nitrogen/generated/android/c++/JFunc_void_MapPressEvent.hpp +81 -0
- package/nitrogen/generated/android/c++/JFunc_void_MarkerDragEvent.hpp +80 -0
- package/nitrogen/generated/android/c++/JFunc_void_MarkerPressEvent.hpp +80 -0
- package/nitrogen/generated/android/c++/JFunc_void_RegionChangeEvent.hpp +79 -0
- package/nitrogen/generated/android/c++/JHybridNitroMapConfigSpec.cpp +59 -0
- package/nitrogen/generated/android/c++/JHybridNitroMapConfigSpec.hpp +66 -0
- package/nitrogen/generated/android/c++/JHybridNitroMapSpec.cpp +593 -0
- package/nitrogen/generated/android/c++/JHybridNitroMapSpec.hpp +125 -0
- package/nitrogen/generated/android/c++/JImageMarkerConfig.hpp +86 -0
- package/nitrogen/generated/android/c++/JMapBoundaries.hpp +62 -0
- package/nitrogen/generated/android/c++/JMapError.hpp +61 -0
- package/nitrogen/generated/android/c++/JMapPressEvent.hpp +64 -0
- package/nitrogen/generated/android/c++/JMapProvider.hpp +62 -0
- package/nitrogen/generated/android/c++/JMapStyleElement.hpp +87 -0
- package/nitrogen/generated/android/c++/JMapStyler.hpp +78 -0
- package/nitrogen/generated/android/c++/JMapType.hpp +62 -0
- package/nitrogen/generated/android/c++/JMarkerAnimation.hpp +62 -0
- package/nitrogen/generated/android/c++/JMarkerColor.hpp +69 -0
- package/nitrogen/generated/android/c++/JMarkerConfig.hpp +77 -0
- package/nitrogen/generated/android/c++/JMarkerData.hpp +121 -0
- package/nitrogen/generated/android/c++/JMarkerDragEvent.hpp +63 -0
- package/nitrogen/generated/android/c++/JMarkerPressEvent.hpp +63 -0
- package/nitrogen/generated/android/c++/JMarkerStyle.hpp +62 -0
- package/nitrogen/generated/android/c++/JPoint.hpp +61 -0
- package/nitrogen/generated/android/c++/JPriceMarkerStyle.hpp +102 -0
- package/nitrogen/generated/android/c++/JRegion.hpp +69 -0
- package/nitrogen/generated/android/c++/JRegionChangeEvent.hpp +62 -0
- package/nitrogen/generated/android/c++/JVariant_String_MarkerColor.cpp +26 -0
- package/nitrogen/generated/android/c++/JVariant_String_MarkerColor.hpp +70 -0
- package/nitrogen/generated/android/c++/views/JHybridNitroMapStateUpdater.cpp +144 -0
- package/nitrogen/generated/android/c++/views/JHybridNitroMapStateUpdater.hpp +49 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/Camera.kt +50 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/ClusterAnimationStyle.kt +24 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/ClusterConfig.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/ClusterPressEvent.kt +44 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/ClusterStrategy.kt +21 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/ColorValue.kt +59 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/Coordinate.kt +41 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/EdgePadding.kt +47 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/Func_void.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/Func_void_ClusterPressEvent.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/Func_void_MapError.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/Func_void_MapPressEvent.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/Func_void_MarkerDragEvent.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/Func_void_MarkerPressEvent.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/Func_void_RegionChangeEvent.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/HybridNitroMapConfigSpec.kt +61 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/HybridNitroMapSpec.kt +342 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/ImageMarkerConfig.kt +56 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/MapBoundaries.kt +41 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/MapError.kt +41 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/MapPressEvent.kt +41 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/MapProvider.kt +22 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/MapStyleElement.kt +44 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/MapStyler.kt +53 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/MapType.kt +22 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/MarkerAnimation.kt +22 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/MarkerColor.kt +47 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/MarkerConfig.kt +44 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/MarkerData.kt +71 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/MarkerDragEvent.kt +41 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/MarkerPressEvent.kt +41 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/MarkerStyle.kt +22 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/Point.kt +41 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/PriceMarkerStyle.kt +68 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/Region.kt +47 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/RegionChangeEvent.kt +41 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/Variant_String_MarkerColor.kt +59 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/nitromapOnLoad.kt +35 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/views/HybridNitroMapManager.kt +50 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/views/HybridNitroMapStateUpdater.kt +23 -0
- package/nitrogen/generated/android/nitromap+autolinking.cmake +87 -0
- package/nitrogen/generated/android/nitromap+autolinking.gradle +27 -0
- package/nitrogen/generated/android/nitromapOnLoad.cpp +70 -0
- package/nitrogen/generated/android/nitromapOnLoad.hpp +25 -0
- package/nitrogen/generated/ios/NitroMap+autolinking.rb +60 -0
- package/nitrogen/generated/ios/NitroMap-Swift-Cxx-Bridge.cpp +130 -0
- package/nitrogen/generated/ios/NitroMap-Swift-Cxx-Bridge.hpp +793 -0
- package/nitrogen/generated/ios/NitroMap-Swift-Cxx-Umbrella.hpp +132 -0
- package/nitrogen/generated/ios/NitroMapAutolinking.mm +41 -0
- package/nitrogen/generated/ios/NitroMapAutolinking.swift +40 -0
- package/nitrogen/generated/ios/c++/HybridNitroMapConfigSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridNitroMapConfigSpecSwift.hpp +84 -0
- package/nitrogen/generated/ios/c++/HybridNitroMapSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridNitroMapSpecSwift.hpp +410 -0
- package/nitrogen/generated/ios/c++/views/HybridNitroMapComponent.mm +206 -0
- package/nitrogen/generated/ios/swift/Camera.swift +80 -0
- package/nitrogen/generated/ios/swift/ClusterAnimationStyle.swift +52 -0
- package/nitrogen/generated/ios/swift/ClusterConfig.swift +268 -0
- package/nitrogen/generated/ios/swift/ClusterPressEvent.swift +70 -0
- package/nitrogen/generated/ios/swift/ClusterStrategy.swift +40 -0
- package/nitrogen/generated/ios/swift/ColorValue.swift +18 -0
- package/nitrogen/generated/ios/swift/Coordinate.swift +47 -0
- package/nitrogen/generated/ios/swift/EdgePadding.swift +69 -0
- package/nitrogen/generated/ios/swift/Func_void.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_Camera.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_ClusterPressEvent.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_MapBoundaries.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_MapError.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_MapPressEvent.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_MarkerDragEvent.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_MarkerPressEvent.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_RegionChangeEvent.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +47 -0
- package/nitrogen/generated/ios/swift/HybridNitroMapConfigSpec.swift +57 -0
- package/nitrogen/generated/ios/swift/HybridNitroMapConfigSpec_cxx.swift +142 -0
- package/nitrogen/generated/ios/swift/HybridNitroMapSpec.swift +93 -0
- package/nitrogen/generated/ios/swift/HybridNitroMapSpec_cxx.swift +953 -0
- package/nitrogen/generated/ios/swift/ImageMarkerConfig.swift +166 -0
- package/nitrogen/generated/ios/swift/MapBoundaries.swift +47 -0
- package/nitrogen/generated/ios/swift/MapError.swift +47 -0
- package/nitrogen/generated/ios/swift/MapPressEvent.swift +47 -0
- package/nitrogen/generated/ios/swift/MapProvider.swift +44 -0
- package/nitrogen/generated/ios/swift/MapStyleElement.swift +108 -0
- package/nitrogen/generated/ios/swift/MapStyler.swift +177 -0
- package/nitrogen/generated/ios/swift/MapType.swift +44 -0
- package/nitrogen/generated/ios/swift/MarkerAnimation.swift +44 -0
- package/nitrogen/generated/ios/swift/MarkerColor.swift +69 -0
- package/nitrogen/generated/ios/swift/MarkerConfig.swift +82 -0
- package/nitrogen/generated/ios/swift/MarkerData.swift +195 -0
- package/nitrogen/generated/ios/swift/MarkerDragEvent.swift +47 -0
- package/nitrogen/generated/ios/swift/MarkerPressEvent.swift +47 -0
- package/nitrogen/generated/ios/swift/MarkerStyle.swift +44 -0
- package/nitrogen/generated/ios/swift/Point.swift +47 -0
- package/nitrogen/generated/ios/swift/PriceMarkerStyle.swift +374 -0
- package/nitrogen/generated/ios/swift/Region.swift +69 -0
- package/nitrogen/generated/ios/swift/RegionChangeEvent.swift +47 -0
- package/nitrogen/generated/ios/swift/Variant_String_MarkerColor.swift +18 -0
- package/nitrogen/generated/shared/c++/Camera.hpp +92 -0
- package/nitrogen/generated/shared/c++/ClusterAnimationStyle.hpp +88 -0
- package/nitrogen/generated/shared/c++/ClusterConfig.hpp +140 -0
- package/nitrogen/generated/shared/c++/ClusterPressEvent.hpp +86 -0
- package/nitrogen/generated/shared/c++/ClusterStrategy.hpp +76 -0
- package/nitrogen/generated/shared/c++/Coordinate.hpp +79 -0
- package/nitrogen/generated/shared/c++/EdgePadding.hpp +87 -0
- package/nitrogen/generated/shared/c++/HybridNitroMapConfigSpec.cpp +22 -0
- package/nitrogen/generated/shared/c++/HybridNitroMapConfigSpec.hpp +65 -0
- package/nitrogen/generated/shared/c++/HybridNitroMapSpec.cpp +82 -0
- package/nitrogen/generated/shared/c++/HybridNitroMapSpec.hpp +173 -0
- package/nitrogen/generated/shared/c++/ImageMarkerConfig.hpp +103 -0
- package/nitrogen/generated/shared/c++/MapBoundaries.hpp +80 -0
- package/nitrogen/generated/shared/c++/MapError.hpp +79 -0
- package/nitrogen/generated/shared/c++/MapPressEvent.hpp +83 -0
- package/nitrogen/generated/shared/c++/MapProvider.hpp +80 -0
- package/nitrogen/generated/shared/c++/MapStyleElement.hpp +87 -0
- package/nitrogen/generated/shared/c++/MapStyler.hpp +96 -0
- package/nitrogen/generated/shared/c++/MapType.hpp +80 -0
- package/nitrogen/generated/shared/c++/MarkerAnimation.hpp +80 -0
- package/nitrogen/generated/shared/c++/MarkerColor.hpp +87 -0
- package/nitrogen/generated/shared/c++/MarkerConfig.hpp +91 -0
- package/nitrogen/generated/shared/c++/MarkerData.hpp +131 -0
- package/nitrogen/generated/shared/c++/MarkerDragEvent.hpp +81 -0
- package/nitrogen/generated/shared/c++/MarkerPressEvent.hpp +81 -0
- package/nitrogen/generated/shared/c++/MarkerStyle.hpp +80 -0
- package/nitrogen/generated/shared/c++/Point.hpp +79 -0
- package/nitrogen/generated/shared/c++/PriceMarkerStyle.hpp +119 -0
- package/nitrogen/generated/shared/c++/Region.hpp +87 -0
- package/nitrogen/generated/shared/c++/RegionChangeEvent.hpp +80 -0
- package/nitrogen/generated/shared/c++/views/HybridNitroMapComponent.cpp +351 -0
- package/nitrogen/generated/shared/c++/views/HybridNitroMapComponent.hpp +141 -0
- package/nitrogen/generated/shared/json/NitroMapConfig.json +32 -0
- package/package.json +176 -0
- package/react-native.config.js +16 -0
- package/src/components/ImageMarker.tsx +254 -0
- package/src/components/NitroMap.tsx +433 -0
- package/src/components/PriceMarker.tsx +311 -0
- package/src/context/NitroMapContext.tsx +33 -0
- package/src/hooks/useNitroMarker.ts +198 -0
- package/src/index.tsx +62 -0
- package/src/modules/index.ts +6 -0
- package/src/modules/module.ts +45 -0
- package/src/specs/NitroMap.nitro.ts +292 -0
- package/src/specs/NitroMapConfig.nitro.ts +8 -0
- package/src/types/map.ts +166 -0
- package/src/types/marker.ts +267 -0
- package/src/utils/colors.ts +159 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import UIKit
|
|
2
|
+
|
|
3
|
+
/// Shared cluster icon renderer — single source of truth for cluster icon rendering.
|
|
4
|
+
/// Eliminates duplication across NitroClusterIconGenerator, ClusterAnnotationView,
|
|
5
|
+
/// and NitroClusterAnnotationView (DRY).
|
|
6
|
+
enum ClusterIconRenderer {
|
|
7
|
+
|
|
8
|
+
/// Renders a cluster icon with the given count and optional config.
|
|
9
|
+
///
|
|
10
|
+
/// - Parameters:
|
|
11
|
+
/// - count: Number of markers in the cluster
|
|
12
|
+
/// - config: Optional cluster configuration for colors/border
|
|
13
|
+
/// - size: Override diameter. If nil, auto-selects based on count.
|
|
14
|
+
/// - Returns: Rendered cluster icon image
|
|
15
|
+
static func renderIcon(
|
|
16
|
+
count: Int,
|
|
17
|
+
config: ClusterConfig?,
|
|
18
|
+
size: CGFloat? = nil
|
|
19
|
+
) -> UIImage {
|
|
20
|
+
let diameter = size ?? clusterDiameter(for: count)
|
|
21
|
+
|
|
22
|
+
let bgColor = resolveColor(config?.backgroundColor.toMarkerColor(),
|
|
23
|
+
fallback: MarkerColor(r: 0, g: 122, b: 255, a: 255))
|
|
24
|
+
let textColor = resolveColor(config?.textColor.toMarkerColor(),
|
|
25
|
+
fallback: MarkerColor(r: 255, g: 255, b: 255, a: 255))
|
|
26
|
+
let borderWidth = CGFloat(config?.borderWidth ?? 2)
|
|
27
|
+
let borderUIColor = resolveColor(config?.borderColor.toMarkerColor(),
|
|
28
|
+
fallback: MarkerColor(r: 255, g: 255, b: 255, a: 255))
|
|
29
|
+
|
|
30
|
+
let renderer = UIGraphicsImageRenderer(
|
|
31
|
+
size: CGSize(width: diameter, height: diameter)
|
|
32
|
+
)
|
|
33
|
+
return renderer.image { context in
|
|
34
|
+
let fullRect = CGRect(x: 0, y: 0, width: diameter, height: diameter)
|
|
35
|
+
let insetRect = fullRect.insetBy(dx: borderWidth, dy: borderWidth)
|
|
36
|
+
|
|
37
|
+
// Border circle
|
|
38
|
+
borderUIColor.setFill()
|
|
39
|
+
context.cgContext.fillEllipse(in: fullRect)
|
|
40
|
+
|
|
41
|
+
// Background circle
|
|
42
|
+
bgColor.setFill()
|
|
43
|
+
context.cgContext.fillEllipse(in: insetRect)
|
|
44
|
+
|
|
45
|
+
// Text
|
|
46
|
+
let text = "\(count)"
|
|
47
|
+
let font = UIFont.boldSystemFont(ofSize: diameter * 0.35)
|
|
48
|
+
let attributes: [NSAttributedString.Key: Any] = [
|
|
49
|
+
.font: font,
|
|
50
|
+
.foregroundColor: textColor,
|
|
51
|
+
]
|
|
52
|
+
let textSize = text.size(withAttributes: attributes)
|
|
53
|
+
let textRect = CGRect(
|
|
54
|
+
x: (diameter - textSize.width) / 2,
|
|
55
|
+
y: (diameter - textSize.height) / 2,
|
|
56
|
+
width: textSize.width,
|
|
57
|
+
height: textSize.height
|
|
58
|
+
)
|
|
59
|
+
text.draw(in: textRect, withAttributes: attributes)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/// Standard cluster diameter based on count (Apple HIG: minimum 44pt tap targets).
|
|
64
|
+
static func clusterDiameter(for count: Int) -> CGFloat {
|
|
65
|
+
return count < 10 ? 44 : (count < 100 ? 52 : 60)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// MARK: - Private
|
|
69
|
+
|
|
70
|
+
private static func resolveColor(_ markerColor: MarkerColor?,
|
|
71
|
+
fallback: MarkerColor) -> UIColor {
|
|
72
|
+
let c = markerColor ?? fallback
|
|
73
|
+
return UIColor(
|
|
74
|
+
red: CGFloat(c.r) / 255,
|
|
75
|
+
green: CGFloat(c.g) / 255,
|
|
76
|
+
blue: CGFloat(c.b) / 255,
|
|
77
|
+
alpha: CGFloat(c.a) / 255
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import CoreLocation
|
|
3
|
+
|
|
4
|
+
/// Swift wrapper for C++ ClusterEngine via Objective-C++ bridge.
|
|
5
|
+
/// Pure spatial grouping — no icon dimensions.
|
|
6
|
+
/// Caller (GoogleMapProvider) handles visual collision with real rendered sizes.
|
|
7
|
+
class NitroClusterEngine {
|
|
8
|
+
|
|
9
|
+
private let engine: ClusterEngineWrapper
|
|
10
|
+
|
|
11
|
+
init() {
|
|
12
|
+
engine = ClusterEngineWrapper()
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// MARK: - Configuration
|
|
16
|
+
|
|
17
|
+
func setClusterRadius(_ radius: Double) {
|
|
18
|
+
engine.setClusterRadius(radius)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
func setMinClusterSize(_ size: Int) {
|
|
22
|
+
engine.setMinClusterSize(size)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
func setMaxZoom(_ zoom: Double) {
|
|
26
|
+
engine.setMaxZoom(zoom)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// MARK: - Marker Management
|
|
30
|
+
|
|
31
|
+
/// Add a marker for clustering. Only geographic data is stored;
|
|
32
|
+
/// icon dimensions are handled by the Swift collision resolver.
|
|
33
|
+
func addMarker(_ markerData: MarkerData) {
|
|
34
|
+
engine.addMarker(
|
|
35
|
+
withId: markerData.id,
|
|
36
|
+
latitude: markerData.coordinate.latitude,
|
|
37
|
+
longitude: markerData.coordinate.longitude
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
func removeMarker(_ id: String) {
|
|
42
|
+
engine.removeMarker(withId: id)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
func clearMarkers() {
|
|
46
|
+
engine.clearMarkers()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// MARK: - Result Types
|
|
50
|
+
|
|
51
|
+
/// Single marker returned from clustering (not merged into a cluster).
|
|
52
|
+
struct SingleMarkerResult {
|
|
53
|
+
var markerId: String
|
|
54
|
+
var latitude: Double
|
|
55
|
+
var longitude: Double
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
struct ClusteringResult {
|
|
59
|
+
var clusters: [ClusterDataResult]
|
|
60
|
+
var singleMarkers: [SingleMarkerResult]
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
struct ClusterDataResult {
|
|
64
|
+
var coordinate: CLLocationCoordinate2D
|
|
65
|
+
var markerIds: [String]
|
|
66
|
+
var count: Int
|
|
67
|
+
var iconSize: Double // Hint from C++; collision uses real rendered size
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// MARK: - Clustering
|
|
71
|
+
|
|
72
|
+
/// Provider-agnostic clustering method — returns spatial groups.
|
|
73
|
+
/// Caller resolves IDs to MarkerData and measures real icon sizes.
|
|
74
|
+
func clusterWithBounds(
|
|
75
|
+
minLat: Double,
|
|
76
|
+
maxLat: Double,
|
|
77
|
+
minLon: Double,
|
|
78
|
+
maxLon: Double,
|
|
79
|
+
zoom: Double,
|
|
80
|
+
mapSize: CGSize
|
|
81
|
+
) -> ClusteringResult {
|
|
82
|
+
let objcResult = engine.cluster(
|
|
83
|
+
withMinLat: minLat,
|
|
84
|
+
maxLat: maxLat,
|
|
85
|
+
minLon: minLon,
|
|
86
|
+
maxLon: maxLon,
|
|
87
|
+
zoom: zoom,
|
|
88
|
+
mapWidth: Double(mapSize.width),
|
|
89
|
+
mapHeight: Double(mapSize.height)
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
var clusters: [ClusterDataResult] = []
|
|
93
|
+
var singleMarkers: [SingleMarkerResult] = []
|
|
94
|
+
|
|
95
|
+
for clusterData in objcResult.clusters {
|
|
96
|
+
clusters.append(ClusterDataResult(
|
|
97
|
+
coordinate: CLLocationCoordinate2D(
|
|
98
|
+
latitude: clusterData.latitude,
|
|
99
|
+
longitude: clusterData.longitude
|
|
100
|
+
),
|
|
101
|
+
markerIds: clusterData.markerIds as [String],
|
|
102
|
+
count: clusterData.count,
|
|
103
|
+
iconSize: clusterData.iconSize
|
|
104
|
+
))
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
for point in objcResult.singleMarkers {
|
|
108
|
+
singleMarkers.append(SingleMarkerResult(
|
|
109
|
+
markerId: point.markerId,
|
|
110
|
+
latitude: point.latitude,
|
|
111
|
+
longitude: point.longitude
|
|
112
|
+
))
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return ClusteringResult(clusters: clusters, singleMarkers: singleMarkers)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import UIKit
|
|
3
|
+
|
|
4
|
+
/// Generates and caches cluster icons.
|
|
5
|
+
/// Uses ClusterIconRenderer for the actual rendering.
|
|
6
|
+
///
|
|
7
|
+
/// FIX #6: No longer extends GMUDefaultClusterIconGenerator — the GMU
|
|
8
|
+
/// clustering pipeline is gone. This is now a plain Swift class.
|
|
9
|
+
final class NitroClusterIconGenerator {
|
|
10
|
+
|
|
11
|
+
private var config: ClusterConfig?
|
|
12
|
+
|
|
13
|
+
/// Cache cluster icons to avoid re-rendering identical icons on every cluster() call
|
|
14
|
+
private let iconCache = NSCache<NSNumber, UIImage>()
|
|
15
|
+
|
|
16
|
+
func updateConfig(_ config: ClusterConfig?) {
|
|
17
|
+
self.config = config
|
|
18
|
+
iconCache.removeAllObjects()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
func icon(forSize size: UInt) -> UIImage {
|
|
22
|
+
let cacheKey = NSNumber(value: size)
|
|
23
|
+
if let cached = iconCache.object(forKey: cacheKey) {
|
|
24
|
+
return cached
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let image = ClusterIconRenderer.renderIcon(
|
|
28
|
+
count: Int(size),
|
|
29
|
+
config: config
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
iconCache.setObject(image, forKey: cacheKey)
|
|
33
|
+
return image
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import UIKit
|
|
2
|
+
|
|
3
|
+
/// Global marker icon cache with aggressive texture reuse
|
|
4
|
+
final class MarkerIconCache {
|
|
5
|
+
static let shared = MarkerIconCache()
|
|
6
|
+
|
|
7
|
+
// Use NSCache for automatic memory management
|
|
8
|
+
private let cache = NSCache<NSString, UIImage>()
|
|
9
|
+
|
|
10
|
+
// Separate cache for URL image data to avoid repeated network calls
|
|
11
|
+
private let urlImageCache = NSCache<NSString, UIImage>()
|
|
12
|
+
|
|
13
|
+
// Track unique icon count for debugging
|
|
14
|
+
private var uniqueIconCount = 0
|
|
15
|
+
private var cacheHits = 0
|
|
16
|
+
private var cacheMisses = 0
|
|
17
|
+
|
|
18
|
+
private init() {
|
|
19
|
+
// Increased limits to handle more markers without texture exhaustion
|
|
20
|
+
cache.countLimit = 500 // Max 500 unique icons (was 200)
|
|
21
|
+
cache.totalCostLimit = 100 * 1024 * 1024 // 100MB max (was 50MB)
|
|
22
|
+
|
|
23
|
+
urlImageCache.countLimit = 200
|
|
24
|
+
urlImageCache.totalCostLimit = 50 * 1024 * 1024
|
|
25
|
+
|
|
26
|
+
// Listen for memory warnings to clear caches
|
|
27
|
+
NotificationCenter.default.addObserver(
|
|
28
|
+
self,
|
|
29
|
+
selector: #selector(handleMemoryWarning),
|
|
30
|
+
name: UIApplication.didReceiveMemoryWarningNotification,
|
|
31
|
+
object: nil
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
@objc private func handleMemoryWarning() {
|
|
36
|
+
// Clear half the cache on memory warning instead of all
|
|
37
|
+
// This keeps frequently used icons while freeing memory
|
|
38
|
+
#if DEBUG
|
|
39
|
+
print("[MarkerIconCache] Memory warning received, clearing caches")
|
|
40
|
+
#endif
|
|
41
|
+
cache.removeAllObjects()
|
|
42
|
+
urlImageCache.removeAllObjects()
|
|
43
|
+
uniqueIconCount = 0
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
func getIcon(forKey key: String) -> UIImage? {
|
|
47
|
+
if let cached = cache.object(forKey: key as NSString) {
|
|
48
|
+
cacheHits += 1
|
|
49
|
+
logStatsIfNeeded()
|
|
50
|
+
return cached
|
|
51
|
+
}
|
|
52
|
+
cacheMisses += 1
|
|
53
|
+
logStatsIfNeeded()
|
|
54
|
+
return nil
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
func setIcon(_ image: UIImage, forKey key: String) {
|
|
58
|
+
// Calculate cost based on image size for better memory management
|
|
59
|
+
let cost = Int(image.size.width * image.size.height * image.scale * image.scale * 4)
|
|
60
|
+
cache.setObject(image, forKey: key as NSString, cost: cost)
|
|
61
|
+
uniqueIconCount += 1
|
|
62
|
+
|
|
63
|
+
if uniqueIconCount % 100 == 0 {
|
|
64
|
+
#if DEBUG
|
|
65
|
+
print("[MarkerIconCache] Cached \(uniqueIconCount) unique icons")
|
|
66
|
+
#endif
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
private func logStatsIfNeeded() {
|
|
71
|
+
let total = cacheHits + cacheMisses
|
|
72
|
+
if total > 0 && total % 500 == 0 {
|
|
73
|
+
let hitRate = Double(cacheHits) / Double(total) * 100
|
|
74
|
+
#if DEBUG
|
|
75
|
+
print("[MarkerIconCache] Stats: hits=\(cacheHits), misses=\(cacheMisses), hitRate=\(String(format: "%.1f", hitRate))%, unique=\(uniqueIconCount)")
|
|
76
|
+
#endif
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
func getURLImage(forKey key: String) -> UIImage? {
|
|
81
|
+
return urlImageCache.object(forKey: key as NSString)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
func setURLImage(_ image: UIImage, forKey key: String) {
|
|
85
|
+
let cost = Int(image.size.width * image.size.height * image.scale * image.scale * 4)
|
|
86
|
+
urlImageCache.setObject(image, forKey: key as NSString, cost: cost)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
func clear() {
|
|
90
|
+
cache.removeAllObjects()
|
|
91
|
+
urlImageCache.removeAllObjects()
|
|
92
|
+
uniqueIconCount = 0
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/// Generate a simplified cache key for price markers
|
|
96
|
+
/// Reduces texture diversity by bucketing similar configurations
|
|
97
|
+
static func priceMarkerKey(
|
|
98
|
+
price: String,
|
|
99
|
+
currency: String,
|
|
100
|
+
selected: Bool
|
|
101
|
+
) -> String {
|
|
102
|
+
// Normalize price to reduce unique textures
|
|
103
|
+
return "price_\(price)_\(currency)_\(selected)"
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/// Generate cache key for image markers
|
|
107
|
+
static func imageMarkerKey(
|
|
108
|
+
source: String,
|
|
109
|
+
width: Double,
|
|
110
|
+
height: Double,
|
|
111
|
+
cornerRadius: Double
|
|
112
|
+
) -> String {
|
|
113
|
+
// Round dimensions to reduce unique keys
|
|
114
|
+
let w = Int(width / 5) * 5 // Round to nearest 5
|
|
115
|
+
let h = Int(height / 5) * 5
|
|
116
|
+
let r = Int(cornerRadius / 5) * 5
|
|
117
|
+
// Use stable hash for cache key
|
|
118
|
+
let sourceHash = stableHash(source)
|
|
119
|
+
return "image_\(sourceHash)_\(w)_\(h)_\(r)"
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/// Stable hash that doesn't change between app launches
|
|
123
|
+
private static func stableHash(_ string: String) -> Int {
|
|
124
|
+
var hash = 5381
|
|
125
|
+
for char in string.utf8 {
|
|
126
|
+
hash = ((hash << 5) &+ hash) &+ Int(char)
|
|
127
|
+
}
|
|
128
|
+
return abs(hash)
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
class MarkerIconFactory {
|
|
133
|
+
|
|
134
|
+
static func createIcon(for markerData: MarkerData) -> UIImage? {
|
|
135
|
+
switch markerData.config.style {
|
|
136
|
+
case .image:
|
|
137
|
+
return createImageMarkerIcon(markerData.config.image)
|
|
138
|
+
case .pricemarker:
|
|
139
|
+
return createPriceMarkerIcon(markerData.config.priceMarker)
|
|
140
|
+
case .default:
|
|
141
|
+
return nil
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// MARK: - Price Marker Icon
|
|
146
|
+
private static func createPriceMarkerIcon(_ config: PriceMarkerStyle?) -> UIImage? {
|
|
147
|
+
guard let config = config else { return nil }
|
|
148
|
+
|
|
149
|
+
// Generate normalized cache key
|
|
150
|
+
let cacheKey = MarkerIconCache.priceMarkerKey(
|
|
151
|
+
price: config.price,
|
|
152
|
+
currency: config.currency,
|
|
153
|
+
selected: config.selected
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
// Check cache first
|
|
157
|
+
if let cachedImage = MarkerIconCache.shared.getIcon(forKey: cacheKey) {
|
|
158
|
+
return cachedImage
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Generate new icon
|
|
162
|
+
let image = PriceMarkerRenderer.render(config: config)
|
|
163
|
+
|
|
164
|
+
// Cache the result
|
|
165
|
+
MarkerIconCache.shared.setIcon(image, forKey: cacheKey)
|
|
166
|
+
|
|
167
|
+
return image
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// MARK: - Image Marker Icon
|
|
171
|
+
private static func createImageMarkerIcon(_ config: ImageMarkerConfig?) -> UIImage? {
|
|
172
|
+
guard let config = config else { return nil }
|
|
173
|
+
|
|
174
|
+
let imageSource = config.imageBase64 ?? config.imageUrl ?? ""
|
|
175
|
+
|
|
176
|
+
// Generate normalized cache key
|
|
177
|
+
let cacheKey = MarkerIconCache.imageMarkerKey(
|
|
178
|
+
source: imageSource,
|
|
179
|
+
width: config.width,
|
|
180
|
+
height: config.height,
|
|
181
|
+
cornerRadius: config.cornerRadius
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
// Check cache first
|
|
185
|
+
if let cachedImage = MarkerIconCache.shared.getIcon(forKey: cacheKey) {
|
|
186
|
+
return cachedImage
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
var sourceImage: UIImage?
|
|
190
|
+
|
|
191
|
+
if let base64 = config.imageBase64,
|
|
192
|
+
let data = Data(base64Encoded: base64)
|
|
193
|
+
{
|
|
194
|
+
sourceImage = UIImage(data: data)
|
|
195
|
+
} else if let urlString = config.imageUrl {
|
|
196
|
+
// Check URL image cache first
|
|
197
|
+
if let cachedURLImage = MarkerIconCache.shared.getURLImage(forKey: urlString) {
|
|
198
|
+
sourceImage = cachedURLImage
|
|
199
|
+
} else {
|
|
200
|
+
// Load URL image on background thread to avoid blocking
|
|
201
|
+
// Return placeholder and trigger async load
|
|
202
|
+
loadURLImageAsync(urlString: urlString, cacheKey: cacheKey, config: config)
|
|
203
|
+
// Return a placeholder image for now
|
|
204
|
+
sourceImage = createPlaceholderImage(
|
|
205
|
+
width: CGFloat(config.width),
|
|
206
|
+
height: CGFloat(config.height)
|
|
207
|
+
)
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
let image = renderImageMarker(sourceImage: sourceImage, config: config)
|
|
212
|
+
|
|
213
|
+
// Cache the result (will be replaced when async image loads)
|
|
214
|
+
MarkerIconCache.shared.setIcon(image, forKey: cacheKey)
|
|
215
|
+
|
|
216
|
+
return image
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/// Load URL image asynchronously using URLSession (proper HTTP handling)
|
|
220
|
+
private static func loadURLImageAsync(urlString: String, cacheKey: String, config: ImageMarkerConfig) {
|
|
221
|
+
guard let url = URL(string: urlString) else { return }
|
|
222
|
+
|
|
223
|
+
var request = URLRequest(url: url)
|
|
224
|
+
request.timeoutInterval = 10 // 10 second timeout
|
|
225
|
+
request.cachePolicy = .returnCacheDataElseLoad
|
|
226
|
+
|
|
227
|
+
URLSession.shared.dataTask(with: request) { data, response, error in
|
|
228
|
+
if let error = error {
|
|
229
|
+
#if DEBUG
|
|
230
|
+
print("[MarkerIconFactory] Failed to load image from \(urlString): \(error.localizedDescription)")
|
|
231
|
+
#endif
|
|
232
|
+
return
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Validate HTTP response
|
|
236
|
+
if let httpResponse = response as? HTTPURLResponse,
|
|
237
|
+
!(200...299).contains(httpResponse.statusCode) {
|
|
238
|
+
#if DEBUG
|
|
239
|
+
print("[MarkerIconFactory] HTTP \(httpResponse.statusCode) for image \(urlString)")
|
|
240
|
+
#endif
|
|
241
|
+
return
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
guard let data = data, let image = UIImage(data: data) else {
|
|
245
|
+
return
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Cache and render on main thread
|
|
249
|
+
DispatchQueue.main.async {
|
|
250
|
+
MarkerIconCache.shared.setURLImage(image, forKey: urlString)
|
|
251
|
+
|
|
252
|
+
// Render the final marker icon with the loaded image
|
|
253
|
+
let finalIcon = renderImageMarker(sourceImage: image, config: config)
|
|
254
|
+
MarkerIconCache.shared.setIcon(finalIcon, forKey: cacheKey)
|
|
255
|
+
|
|
256
|
+
// Post notification for markers to refresh their icons
|
|
257
|
+
NotificationCenter.default.post(
|
|
258
|
+
name: Notification.Name("MarkerIconLoaded"),
|
|
259
|
+
object: nil,
|
|
260
|
+
userInfo: ["cacheKey": cacheKey, "icon": finalIcon]
|
|
261
|
+
)
|
|
262
|
+
}
|
|
263
|
+
}.resume()
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/// Create a placeholder image while URL image loads
|
|
267
|
+
private static func createPlaceholderImage(width: CGFloat, height: CGFloat) -> UIImage {
|
|
268
|
+
let renderer = UIGraphicsImageRenderer(size: CGSize(width: width, height: height))
|
|
269
|
+
return renderer.image { context in
|
|
270
|
+
let rect = CGRect(x: 0, y: 0, width: width, height: height)
|
|
271
|
+
UIColor.lightGray.withAlphaComponent(0.3).setFill()
|
|
272
|
+
UIBezierPath(roundedRect: rect, cornerRadius: width * 0.1).fill()
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/// Render image marker with given source image
|
|
277
|
+
private static func renderImageMarker(sourceImage: UIImage?, config: ImageMarkerConfig) -> UIImage {
|
|
278
|
+
let width = CGFloat(config.width)
|
|
279
|
+
let height = CGFloat(config.height)
|
|
280
|
+
let cornerRadius = CGFloat(config.cornerRadius)
|
|
281
|
+
let borderWidth = CGFloat(config.borderWidth)
|
|
282
|
+
|
|
283
|
+
let borderColorValue = config.borderColor.toMarkerColor()
|
|
284
|
+
let borderColor = UIColor(
|
|
285
|
+
red: CGFloat(borderColorValue.r) / 255,
|
|
286
|
+
green: CGFloat(borderColorValue.g) / 255,
|
|
287
|
+
blue: CGFloat(borderColorValue.b) / 255,
|
|
288
|
+
alpha: CGFloat(borderColorValue.a) / 255
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
let renderer = UIGraphicsImageRenderer(
|
|
292
|
+
size: CGSize(width: width, height: height)
|
|
293
|
+
)
|
|
294
|
+
return renderer.image { context in
|
|
295
|
+
let rect = CGRect(x: 0, y: 0, width: width, height: height)
|
|
296
|
+
let path = UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius)
|
|
297
|
+
|
|
298
|
+
// Clip to rounded rect
|
|
299
|
+
path.addClip()
|
|
300
|
+
|
|
301
|
+
// Draw image
|
|
302
|
+
if let image = sourceImage {
|
|
303
|
+
image.draw(in: rect)
|
|
304
|
+
} else {
|
|
305
|
+
UIColor.lightGray.setFill()
|
|
306
|
+
UIRectFill(rect)
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Draw border
|
|
310
|
+
if borderWidth > 0 {
|
|
311
|
+
borderColor.setStroke()
|
|
312
|
+
path.lineWidth = borderWidth
|
|
313
|
+
path.stroke()
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// MARK: - Cache Management
|
|
319
|
+
static func clearCache() {
|
|
320
|
+
MarkerIconCache.shared.clear()
|
|
321
|
+
}
|
|
322
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
// ios/MarkerRenderer/PriceMarkerRenderer.swift
|
|
2
|
+
import UIKit
|
|
3
|
+
|
|
4
|
+
class PriceMarkerRenderer {
|
|
5
|
+
|
|
6
|
+
struct Style {
|
|
7
|
+
var price: String
|
|
8
|
+
var currency: String
|
|
9
|
+
var selected: Bool
|
|
10
|
+
var backgroundColor: UIColor
|
|
11
|
+
var selectedBackgroundColor: UIColor
|
|
12
|
+
var textColor: UIColor
|
|
13
|
+
var selectedTextColor: UIColor
|
|
14
|
+
var fontSize: CGFloat
|
|
15
|
+
var paddingHorizontal: CGFloat
|
|
16
|
+
var paddingVertical: CGFloat
|
|
17
|
+
var shadowOpacity: Float
|
|
18
|
+
|
|
19
|
+
static func fromConfig(_ config: PriceMarkerStyle?) -> Style {
|
|
20
|
+
guard let config = config else {
|
|
21
|
+
return Style(
|
|
22
|
+
price: "0",
|
|
23
|
+
currency: "UZS",
|
|
24
|
+
selected: false,
|
|
25
|
+
backgroundColor: .white,
|
|
26
|
+
selectedBackgroundColor: UIColor(red: 255/255, green: 59/255, blue: 48/255, alpha: 1),
|
|
27
|
+
textColor: UIColor(red: 51/255, green: 51/255, blue: 51/255, alpha: 1),
|
|
28
|
+
selectedTextColor: .white,
|
|
29
|
+
fontSize: 10,
|
|
30
|
+
paddingHorizontal: 8,
|
|
31
|
+
paddingVertical: 6,
|
|
32
|
+
shadowOpacity: 0.12
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return Style(
|
|
37
|
+
price: config.price,
|
|
38
|
+
currency: config.currency,
|
|
39
|
+
selected: config.selected,
|
|
40
|
+
backgroundColor: PriceMarkerRenderer.variantToUIColor(config.backgroundColor) ?? .white,
|
|
41
|
+
selectedBackgroundColor: PriceMarkerRenderer.variantToUIColor(config.selectedBackgroundColor)
|
|
42
|
+
?? UIColor(red: 255/255, green: 59/255, blue: 48/255, alpha: 1),
|
|
43
|
+
textColor: PriceMarkerRenderer.variantToUIColor(config.textColor)
|
|
44
|
+
?? UIColor(red: 51/255, green: 51/255, blue: 51/255, alpha: 1),
|
|
45
|
+
selectedTextColor: PriceMarkerRenderer.variantToUIColor(config.selectedTextColor) ?? .white,
|
|
46
|
+
fontSize: CGFloat(config.fontSize ?? 10),
|
|
47
|
+
paddingHorizontal: CGFloat(config.paddingHorizontal ?? 8),
|
|
48
|
+
paddingVertical: CGFloat(config.paddingVertical ?? 6),
|
|
49
|
+
shadowOpacity: Float(config.shadowOpacity ?? 0.12)
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/// Converts a Variant_String_MarkerColor (Nitro generated type) to UIColor.
|
|
55
|
+
private static func variantToUIColor(_ value: Variant_String_MarkerColor?) -> UIColor? {
|
|
56
|
+
guard let value = value else { return nil }
|
|
57
|
+
let mc: MarkerColor
|
|
58
|
+
switch value {
|
|
59
|
+
case .first(let hex):
|
|
60
|
+
mc = ColorValue.first(hex).toMarkerColor()
|
|
61
|
+
case .second(let markerColor):
|
|
62
|
+
mc = markerColor
|
|
63
|
+
}
|
|
64
|
+
return UIColor(
|
|
65
|
+
red: CGFloat(mc.r) / 255,
|
|
66
|
+
green: CGFloat(mc.g) / 255,
|
|
67
|
+
blue: CGFloat(mc.b) / 255,
|
|
68
|
+
alpha: CGFloat(mc.a) / 255
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
static func render(config: PriceMarkerStyle?) -> UIImage {
|
|
73
|
+
let style = Style.fromConfig(config)
|
|
74
|
+
|
|
75
|
+
// Build text: "9M UZS"
|
|
76
|
+
let text = "\(style.price) \(style.currency)"
|
|
77
|
+
|
|
78
|
+
// Font
|
|
79
|
+
let font = UIFont.systemFont(ofSize: style.fontSize, weight: .light)
|
|
80
|
+
let attributes: [NSAttributedString.Key: Any] = [.font: font]
|
|
81
|
+
let textSize = text.size(withAttributes: attributes)
|
|
82
|
+
|
|
83
|
+
// Calculate size with padding
|
|
84
|
+
let width = textSize.width + style.paddingHorizontal * 2
|
|
85
|
+
let height = textSize.height + style.paddingVertical * 2
|
|
86
|
+
|
|
87
|
+
// Reduced shadow size for smaller textures (was 20, now 8)
|
|
88
|
+
let shadowBlur: CGFloat = 8
|
|
89
|
+
let shadowOffset: CGFloat = 2
|
|
90
|
+
let canvasWidth = width + shadowBlur * 2
|
|
91
|
+
let canvasHeight = height + shadowBlur + shadowOffset + shadowBlur / 2
|
|
92
|
+
|
|
93
|
+
let renderer = UIGraphicsImageRenderer(size: CGSize(width: canvasWidth, height: canvasHeight))
|
|
94
|
+
|
|
95
|
+
return renderer.image { context in
|
|
96
|
+
let ctx = context.cgContext
|
|
97
|
+
|
|
98
|
+
// Pill rect (centered with shadow space)
|
|
99
|
+
let pillRect = CGRect(
|
|
100
|
+
x: shadowBlur,
|
|
101
|
+
y: shadowBlur / 2,
|
|
102
|
+
width: width,
|
|
103
|
+
height: height
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
// Corner radius = half height for pill shape
|
|
107
|
+
let cornerRadius = height / 2
|
|
108
|
+
let path = UIBezierPath(roundedRect: pillRect, cornerRadius: cornerRadius)
|
|
109
|
+
|
|
110
|
+
// Shadow
|
|
111
|
+
ctx.saveGState()
|
|
112
|
+
ctx.setShadow(
|
|
113
|
+
offset: CGSize(width: 0, height: shadowOffset),
|
|
114
|
+
blur: shadowBlur,
|
|
115
|
+
color: UIColor.black.withAlphaComponent(CGFloat(style.shadowOpacity)).cgColor
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
// Background
|
|
119
|
+
let bgColor = style.selected ? style.selectedBackgroundColor : style.backgroundColor
|
|
120
|
+
bgColor.setFill()
|
|
121
|
+
path.fill()
|
|
122
|
+
|
|
123
|
+
ctx.restoreGState()
|
|
124
|
+
|
|
125
|
+
// Text
|
|
126
|
+
let textColor = style.selected ? style.selectedTextColor : style.textColor
|
|
127
|
+
let textRect = CGRect(
|
|
128
|
+
x: pillRect.origin.x + style.paddingHorizontal,
|
|
129
|
+
y: pillRect.origin.y + style.paddingVertical,
|
|
130
|
+
width: textSize.width,
|
|
131
|
+
height: textSize.height
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
text.draw(in: textRect, withAttributes: [
|
|
135
|
+
.font: font,
|
|
136
|
+
.foregroundColor: textColor
|
|
137
|
+
])
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|