Skip to content

Commit 015a871

Browse files
RD-1317: Add heatmap layer (#220)
1 parent 1cbddb0 commit 015a871

File tree

4 files changed

+397
-0
lines changed

4 files changed

+397
-0
lines changed

Sources/MapTilerSDK/Commands/Style/AddLayer.swift

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ package struct AddLayer: MTCommand {
2525
return handleMTHillshadeLayer(layer)
2626
} else if let layer = layer as? MTCircleLayer {
2727
return handleMTCircleLayer(layer)
28+
} else if let layer = layer as? MTHeatmapLayer {
29+
return handleMTHeatmapLayer(layer)
2830
}
2931

3032
return emptyReturnValue
@@ -106,6 +108,19 @@ package struct AddLayer: MTCommand {
106108
}
107109
return js
108110
}
111+
112+
private func handleMTHeatmapLayer(_ layer: MTHeatmapLayer) -> JSString {
113+
guard let layerString: JSString = layer.toJSON() else {
114+
return emptyReturnValue
115+
}
116+
117+
let processed = unquoteExpressions(in: layerString)
118+
var js = "\(MTBridge.mapObject).addLayer(\(processed));"
119+
if let filter = layer.initialFilter {
120+
js.append("\n \(MTBridge.mapObject).setFilter('\(layer.identifier)', \(filter.toJS()));")
121+
}
122+
return js
123+
}
109124
}
110125
/// Replaces string-encoded expressions with raw JSON arrays.
111126
/// Ensures the style parser reads them as expressions (not strings).
@@ -126,6 +141,31 @@ fileprivate func unquoteExpressions(in json: String) -> String {
126141
with: "$1$2",
127142
options: .regularExpression
128143
)
144+
s = s.replacingOccurrences(
145+
of: #"(?s)("heatmap-color"\s*:\s*)"(\[.*?\])""#,
146+
with: "$1$2",
147+
options: .regularExpression
148+
)
149+
s = s.replacingOccurrences(
150+
of: #"(?s)("heatmap-radius"\s*:\s*)"(\[.*?\])""#,
151+
with: "$1$2",
152+
options: .regularExpression
153+
)
154+
s = s.replacingOccurrences(
155+
of: #"(?s)("heatmap-intensity"\s*:\s*)"(\[.*?\])""#,
156+
with: "$1$2",
157+
options: .regularExpression
158+
)
159+
s = s.replacingOccurrences(
160+
of: #"(?s)("heatmap-opacity"\s*:\s*)"(\[.*?\])""#,
161+
with: "$1$2",
162+
options: .regularExpression
163+
)
164+
s = s.replacingOccurrences(
165+
of: #"(?s)("heatmap-weight"\s*:\s*)"(\[.*?\])""#,
166+
with: "$1$2",
167+
options: .regularExpression
168+
)
129169
// Unescape escaped quotes inside expression arrays (e.g., \"step\" -> "step")
130170
s = s.replacingOccurrences(of: "\\\"", with: "\"")
131171
return s

Sources/MapTilerSDK/Commands/Style/AddLayers.swift

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ package struct AddLayers: MTCommand {
3030
jsString.append(handleMTHillshadeLayer(layer))
3131
} else if let layer = layer as? MTCircleLayer {
3232
jsString.append(handleMTCircleLayer(layer))
33+
} else if let layer = layer as? MTHeatmapLayer {
34+
jsString.append(handleMTHeatmapLayer(layer))
3335
}
3436
}
3537

@@ -107,6 +109,18 @@ package struct AddLayers: MTCommand {
107109
}
108110
return js
109111
}
112+
113+
private func handleMTHeatmapLayer(_ layer: MTHeatmapLayer) -> JSString {
114+
guard let layerString: JSString = layer.toJSON() else {
115+
return emptyReturnValue
116+
}
117+
let processed = unquoteExpressions(in: layerString)
118+
var js = "\(MTBridge.mapObject).addLayer(\(processed));"
119+
if let filter = layer.initialFilter {
120+
js.append("\n \(MTBridge.mapObject).setFilter('\(layer.identifier)', \(filter.toJS()));")
121+
}
122+
return js
123+
}
110124
}
111125

112126
/// Replaces string-encoded expressions with raw JSON arrays.
@@ -128,6 +142,31 @@ fileprivate func unquoteExpressions(in json: String) -> String {
128142
with: "$1$2",
129143
options: .regularExpression
130144
)
145+
s = s.replacingOccurrences(
146+
of: #"(?s)("heatmap-color"\s*:\s*)"(\[.*?\])""#,
147+
with: "$1$2",
148+
options: .regularExpression
149+
)
150+
s = s.replacingOccurrences(
151+
of: #"(?s)("heatmap-radius"\s*:\s*)"(\[.*?\])""#,
152+
with: "$1$2",
153+
options: .regularExpression
154+
)
155+
s = s.replacingOccurrences(
156+
of: #"(?s)("heatmap-intensity"\s*:\s*)"(\[.*?\])""#,
157+
with: "$1$2",
158+
options: .regularExpression
159+
)
160+
s = s.replacingOccurrences(
161+
of: #"(?s)("heatmap-opacity"\s*:\s*)"(\[.*?\])""#,
162+
with: "$1$2",
163+
options: .regularExpression
164+
)
165+
s = s.replacingOccurrences(
166+
of: #"(?s)("heatmap-weight"\s*:\s*)"(\[.*?\])""#,
167+
with: "$1$2",
168+
options: .regularExpression
169+
)
131170
s = s.replacingOccurrences(of: "\\\"", with: "\"")
132171
return s
133172
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
//
2+
// Copyright (c) 2026, MapTiler
3+
// All rights reserved.
4+
// SPDX-License-Identifier: BSD 3-Clause
5+
//
6+
// MTHeatmapLayer+DSL.swift
7+
// MapTilerSDK
8+
//
9+
10+
import Foundation
11+
12+
// DSL
13+
extension MTHeatmapLayer {
14+
/// Adds layer to map DSL style.
15+
///
16+
/// Prefer `MTStyle/addLayer(_:)` on MTMapView instead.
17+
public func addToMap(_ mapView: MTMapView) {
18+
Task {
19+
let layer = MTHeatmapLayer(
20+
identifier: self.identifier,
21+
sourceIdentifier: self.sourceIdentifier,
22+
maxZoom: self.maxZoom,
23+
minZoom: self.minZoom,
24+
sourceLayer: self.sourceLayer,
25+
color: self.color,
26+
intensity: self.intensity,
27+
opacity: self.opacity,
28+
radius: self.radius,
29+
weight: self.weight,
30+
visibility: self.visibility
31+
)
32+
layer.filterExpression = self.filterExpression
33+
layer.initialFilter = self.initialFilter
34+
35+
try await mapView.style?.addLayer(layer)
36+
}
37+
}
38+
}

0 commit comments

Comments
 (0)