Skip to content

Commit 22bb121

Browse files
RD-1432: Add circle layer
1 parent 92ac87f commit 22bb121

File tree

20 files changed

+1241
-0
lines changed

20 files changed

+1241
-0
lines changed

Examples/ClusteringClassic.kt

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
package com.maptiler.examples
2+
3+
import android.graphics.Color
4+
import android.os.Bundle
5+
import androidx.activity.ComponentActivity
6+
import com.maptiler.maptilersdk.MTConfig
7+
import com.maptiler.maptilersdk.map.MTMapOptions
8+
import com.maptiler.maptilersdk.map.MTMapViewClassic
9+
import com.maptiler.maptilersdk.map.MTMapViewController
10+
import com.maptiler.maptilersdk.map.style.MTMapReferenceStyle
11+
import com.maptiler.maptilersdk.map.style.MTMapStyleVariant
12+
import com.maptiler.maptilersdk.map.style.MTStyle
13+
import com.maptiler.maptilersdk.map.style.dsl.Expression
14+
import com.maptiler.maptilersdk.map.style.dsl.Filter
15+
import com.maptiler.maptilersdk.map.style.dsl.MTFeatureKey
16+
import com.maptiler.maptilersdk.map.style.dsl.PropertyValue
17+
import com.maptiler.maptilersdk.map.style.layer.circle.MTCircleLayer
18+
import com.maptiler.maptilersdk.map.style.layer.circle.colorConst
19+
import com.maptiler.maptilersdk.map.style.layer.circle.colorExpr
20+
import com.maptiler.maptilersdk.map.style.layer.circle.radiusConst
21+
import com.maptiler.maptilersdk.map.style.layer.circle.radiusExpr
22+
import com.maptiler.maptilersdk.map.style.layer.symbol.MTSymbolLayer
23+
import com.maptiler.maptilersdk.map.style.layer.symbol.textAllowOverlap
24+
import com.maptiler.maptilersdk.map.style.layer.symbol.MTTextAnchor
25+
import com.maptiler.maptilersdk.map.style.layer.symbol.textAnchor
26+
import com.maptiler.maptilersdk.map.style.layer.symbol.textColorConst
27+
import com.maptiler.maptilersdk.map.style.layer.symbol.textField
28+
import com.maptiler.maptilersdk.map.style.layer.symbol.textFont
29+
import com.maptiler.maptilersdk.map.style.layer.symbol.textSize
30+
import com.maptiler.maptilersdk.map.style.dsl.MTTextToken
31+
import com.maptiler.maptilersdk.map.style.source.MTGeoJSONSource
32+
import java.net.URL
33+
34+
/**
35+
* View (XML) example building the same clustering demo as Compose.
36+
*/
37+
class ClusteringClassicActivity : ComponentActivity() {
38+
private lateinit var mapView: MTMapViewClassic
39+
private lateinit var controller: MTMapViewController
40+
41+
override fun onCreate(savedInstanceState: Bundle?) {
42+
super.onCreate(savedInstanceState)
43+
MTConfig.apiKey = "YOUR_API_KEY"
44+
45+
controller = MTMapViewController(baseContext)
46+
setContentView(R.layout.activity_basic_classic_map_view)
47+
mapView = findViewById(R.id.classicMapView)
48+
mapView.initialize(
49+
referenceStyle = MTMapReferenceStyle.DATAVIZ,
50+
options = MTMapOptions(),
51+
controller = controller,
52+
styleVariant = MTMapStyleVariant.DARK,
53+
)
54+
55+
// Build once map/bridge is ready
56+
controller.delegate = object : com.maptiler.maptilersdk.map.MTMapViewDelegate {
57+
override fun onMapViewInitialized() {
58+
setupClusters(controller.style!!)
59+
}
60+
}
61+
}
62+
63+
private fun setupClusters(style: MTStyle) {
64+
val src = MTGeoJSONSource.fromUrl(
65+
identifier = "earthquakes",
66+
url = URL("https://docs.maptiler.com/sdk-js/assets/earthquakes.geojson"),
67+
).apply {
68+
isCluster = true
69+
clusterRadius = 50.0
70+
clusterMaxZoom = 14.0
71+
}
72+
style.addSource(src)
73+
74+
val clusters =
75+
MTCircleLayer("clusters", src.identifier)
76+
.apply {
77+
withFilter(Filter.clusters())
78+
colorExpr(
79+
Expression.step(
80+
input = Expression.get(MTFeatureKey.POINT_COUNT),
81+
default = PropertyValue.Color(Color.parseColor("#51bbd6")),
82+
stops = listOf(
83+
100.0 to PropertyValue.Color(Color.parseColor("#f1f075")),
84+
750.0 to PropertyValue.Color(Color.parseColor("#f28cb1")),
85+
),
86+
),
87+
)
88+
radiusExpr(
89+
Expression.step(
90+
input = Expression.get(MTFeatureKey.POINT_COUNT),
91+
default = PropertyValue.Num(20.0),
92+
stops = listOf(
93+
100.0 to PropertyValue.Num(30.0),
94+
750.0 to PropertyValue.Num(40.0),
95+
),
96+
),
97+
)
98+
}
99+
style.addLayer(clusters)
100+
101+
val labels =
102+
MTSymbolLayer("clusterCount", src.identifier)
103+
.apply {
104+
withFilter(Filter.clusters())
105+
textField(MTTextToken.POINT_COUNT_ABBREVIATED)
106+
textSize(12.0)
107+
textAllowOverlap(true)
108+
textAnchor(MTTextAnchor.CENTER)
109+
textFont(listOf("DIN Offc Pro Medium", "Arial Unicode MS Bold"))
110+
textColorConst(Color.WHITE)
111+
}
112+
style.addLayer(labels)
113+
114+
val unclustered =
115+
MTCircleLayer("unclusteredPoint", src.identifier)
116+
.apply {
117+
withFilter(Filter.unclustered())
118+
colorConst(Color.parseColor("#11b4da"))
119+
radiusConst(4.0)
120+
}
121+
style.addLayer(unclustered)
122+
}
123+
}

Examples/ClusteringCompose.kt

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
package com.maptiler.examples
2+
3+
import android.graphics.Color
4+
import android.os.Bundle
5+
import androidx.activity.ComponentActivity
6+
import androidx.activity.compose.setContent
7+
import androidx.compose.foundation.layout.fillMaxSize
8+
import androidx.compose.runtime.Composable
9+
import androidx.compose.runtime.DisposableEffect
10+
import androidx.compose.runtime.LaunchedEffect
11+
import androidx.compose.runtime.remember
12+
import androidx.compose.ui.Modifier
13+
import com.maptiler.maptilersdk.MTConfig
14+
import com.maptiler.maptilersdk.events.MTEvent
15+
import com.maptiler.maptilersdk.map.MTMapOptions
16+
import com.maptiler.maptilersdk.map.MTMapView
17+
import com.maptiler.maptilersdk.map.MTMapViewController
18+
import com.maptiler.maptilersdk.map.MTMapViewDelegate
19+
import com.maptiler.maptilersdk.map.style.MTMapReferenceStyle
20+
import com.maptiler.maptilersdk.map.style.MTMapStyleVariant
21+
import com.maptiler.maptilersdk.map.style.MTStyle
22+
import com.maptiler.maptilersdk.map.style.dsl.Expression
23+
import com.maptiler.maptilersdk.map.style.dsl.Filter
24+
import com.maptiler.maptilersdk.map.style.dsl.MTFeatureKey
25+
import com.maptiler.maptilersdk.map.style.dsl.PropertyValue
26+
import com.maptiler.maptilersdk.map.style.layer.circle.MTCircleLayer
27+
import com.maptiler.maptilersdk.map.style.layer.circle.colorConst
28+
import com.maptiler.maptilersdk.map.style.layer.circle.colorExpr
29+
import com.maptiler.maptilersdk.map.style.layer.circle.radiusConst
30+
import com.maptiler.maptilersdk.map.style.layer.circle.radiusExpr
31+
import com.maptiler.maptilersdk.map.style.layer.symbol.MTSymbolLayer
32+
import com.maptiler.maptilersdk.map.style.layer.symbol.textAllowOverlap
33+
import com.maptiler.maptilersdk.map.style.layer.symbol.MTTextAnchor
34+
import com.maptiler.maptilersdk.map.style.layer.symbol.textAnchor
35+
import com.maptiler.maptilersdk.map.style.layer.symbol.textColorConst
36+
import com.maptiler.maptilersdk.map.style.layer.symbol.textField
37+
import com.maptiler.maptilersdk.map.style.layer.symbol.textFont
38+
import com.maptiler.maptilersdk.map.style.layer.symbol.textSize
39+
import com.maptiler.maptilersdk.map.style.dsl.MTTextToken
40+
import com.maptiler.maptilersdk.map.style.source.MTGeoJSONSource
41+
import com.maptiler.maptilersdk.map.types.MTData
42+
import java.net.URL
43+
44+
/**
45+
* Compose example demonstrating cluster circles + count labels + unclustered circles
46+
* using the typed style DSL and helpers.
47+
*/
48+
@Composable
49+
fun ClusteringCompose() {
50+
val controller = remember { MTMapViewController(baseContext) }
51+
52+
LaunchedEffect(controller) {
53+
controller.delegate = object : MTMapViewDelegate {
54+
override fun onMapViewInitialized() {
55+
setupClusters(controller.style!!)
56+
}
57+
58+
override fun onEventTriggered(event: MTEvent, data: MTData?) {
59+
// no-op
60+
}
61+
}
62+
}
63+
64+
DisposableEffect(controller) { onDispose { controller.delegate = null } }
65+
66+
MTMapView(
67+
referenceStyle = MTMapReferenceStyle.DATAVIZ,
68+
options = MTMapOptions(),
69+
controller = controller,
70+
modifier = Modifier.fillMaxSize(),
71+
styleVariant = MTMapStyleVariant.DARK,
72+
)
73+
}
74+
75+
private fun setupClusters(style: MTStyle) {
76+
// 1) Source with clustering
77+
val src = MTGeoJSONSource.fromUrl(
78+
identifier = "earthquakes",
79+
url = URL("https://docs.maptiler.com/sdk-js/assets/earthquakes.geojson"),
80+
).apply {
81+
isCluster = true
82+
clusterRadius = 50.0
83+
clusterMaxZoom = 14.0
84+
}
85+
style.addSource(src)
86+
87+
// 2) Cluster circles (inline config)
88+
val clusters =
89+
MTCircleLayer(identifier = "clusters", sourceIdentifier = src.identifier)
90+
.apply {
91+
withFilter(Filter.clusters())
92+
colorExpr(
93+
Expression.step(
94+
input = Expression.get(MTFeatureKey.POINT_COUNT),
95+
default = PropertyValue.Color(Color.parseColor("#51bbd6")),
96+
stops = listOf(
97+
100.0 to PropertyValue.Color(Color.parseColor("#f1f075")),
98+
750.0 to PropertyValue.Color(Color.parseColor("#f28cb1")),
99+
),
100+
),
101+
)
102+
radiusExpr(
103+
Expression.step(
104+
input = Expression.get(MTFeatureKey.POINT_COUNT),
105+
default = PropertyValue.Num(20.0),
106+
stops = listOf(
107+
100.0 to PropertyValue.Num(30.0),
108+
750.0 to PropertyValue.Num(40.0),
109+
),
110+
),
111+
)
112+
}
113+
style.addLayer(clusters)
114+
115+
// 3) Cluster count labels (inline config)
116+
val labels =
117+
MTSymbolLayer(identifier = "clusterCount", sourceIdentifier = src.identifier)
118+
.apply {
119+
withFilter(Filter.clusters())
120+
textField(MTTextToken.POINT_COUNT_ABBREVIATED)
121+
textSize(12.0)
122+
textAllowOverlap(true)
123+
textAnchor(MTTextAnchor.CENTER)
124+
textFont(listOf("DIN Offc Pro Medium", "Arial Unicode MS Bold"))
125+
textColorConst(Color.WHITE)
126+
}
127+
style.addLayer(labels)
128+
129+
// 4) Unclustered points (inline config)
130+
val unclustered =
131+
MTCircleLayer(identifier = "unclusteredPoint", sourceIdentifier = src.identifier)
132+
.apply {
133+
withFilter(Filter.unclustered())
134+
colorConst(Color.parseColor("#11b4da"))
135+
radiusConst(4.0)
136+
}
137+
style.addLayer(unclustered)
138+
}
139+
140+
/** Optional Activity wrapper to run the composable. */
141+
class ClusteringComposeActivity : ComponentActivity() {
142+
override fun onCreate(savedInstanceState: Bundle?) {
143+
super.onCreate(savedInstanceState)
144+
MTConfig.apiKey = "YOUR_API_KEY"
145+
setContent { ClusteringCompose() }
146+
}
147+
}

MapTilerSDK/src/main/java/com/maptiler/maptilersdk/commands/style/AddLayer.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import com.maptiler.maptilersdk.bridge.MTCommand
1212
import com.maptiler.maptilersdk.helpers.ImageHelper
1313
import com.maptiler.maptilersdk.helpers.JsonConfig
1414
import com.maptiler.maptilersdk.map.style.layer.MTLayer
15+
import com.maptiler.maptilersdk.map.style.layer.circle.MTCircleLayer
1516
import com.maptiler.maptilersdk.map.style.layer.fill.MTFillLayer
1617
import com.maptiler.maptilersdk.map.style.layer.line.MTLineLayer
1718
import com.maptiler.maptilersdk.map.style.layer.raster.MTRasterLayer
@@ -31,6 +32,8 @@ internal data class AddLayer(
3132
handleLineLayer(layer)
3233
} else if (layer is MTRasterLayer) {
3334
handleRasterLayer(layer)
35+
} else if (layer is MTCircleLayer) {
36+
handleCircleLayer(layer)
3437
} else {
3538
// Fallback to a generic addLayer for any future-supported layer types
3639
val layerString: JSString = JsonConfig.json.encodeToString(layer)
@@ -79,4 +82,10 @@ internal data class AddLayer(
7982

8083
return "${MTBridge.MAP_OBJECT}.addLayer($layerString);"
8184
}
85+
86+
private fun handleCircleLayer(layer: MTCircleLayer): JSString {
87+
val layerString: JSString = JsonConfig.json.encodeToString(layer)
88+
89+
return "${MTBridge.MAP_OBJECT}.addLayer($layerString);"
90+
}
8291
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
* Copyright (c) 2025, MapTiler
3+
* All rights reserved.
4+
* SPDX-License-Identifier: BSD 3-Clause
5+
*/
6+
7+
package com.maptiler.maptilersdk.commands.style
8+
9+
import com.maptiler.maptilersdk.bridge.MTBridge
10+
import com.maptiler.maptilersdk.bridge.MTCommand
11+
import com.maptiler.maptilersdk.map.style.dsl.PropertyValue
12+
13+
internal data class SetFilter(
14+
val layerId: String,
15+
val filter: PropertyValue,
16+
) : MTCommand {
17+
override val isPrimitiveReturnType: Boolean = false
18+
19+
override fun toJS(): String = "${MTBridge.MAP_OBJECT}.setFilter(\"$layerId\", ${filter.asCode()});"
20+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
* Copyright (c) 2025, MapTiler
3+
* All rights reserved.
4+
* SPDX-License-Identifier: BSD 3-Clause
5+
*/
6+
7+
package com.maptiler.maptilersdk.commands.style
8+
9+
import com.maptiler.maptilersdk.bridge.MTBridge
10+
import com.maptiler.maptilersdk.bridge.MTCommand
11+
import com.maptiler.maptilersdk.map.style.dsl.PropertyValue
12+
13+
internal data class SetLayoutProperty(
14+
val layerId: String,
15+
val name: String,
16+
val value: PropertyValue,
17+
) : MTCommand {
18+
override val isPrimitiveReturnType: Boolean = false
19+
20+
override fun toJS(): String = "${MTBridge.MAP_OBJECT}.setLayoutProperty(\"$layerId\", \"$name\", ${value.asCode()});"
21+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
* Copyright (c) 2025, MapTiler
3+
* All rights reserved.
4+
* SPDX-License-Identifier: BSD 3-Clause
5+
*/
6+
7+
package com.maptiler.maptilersdk.commands.style
8+
9+
import com.maptiler.maptilersdk.bridge.MTBridge
10+
import com.maptiler.maptilersdk.bridge.MTCommand
11+
import com.maptiler.maptilersdk.map.style.dsl.PropertyValue
12+
13+
internal data class SetPaintProperty(
14+
val layerId: String,
15+
val name: String,
16+
val value: PropertyValue,
17+
) : MTCommand {
18+
override val isPrimitiveReturnType: Boolean = false
19+
20+
override fun toJS(): String = "${MTBridge.MAP_OBJECT}.setPaintProperty(\"$layerId\", \"$name\", ${value.asCode()});"
21+
}

0 commit comments

Comments
 (0)