@@ -10,33 +10,54 @@ import androidx.compose.ui.platform.LocalLifecycleOwner
1010import androidx.compose.ui.viewinterop.AndroidView
1111import androidx.lifecycle.Lifecycle
1212import androidx.lifecycle.LifecycleEventObserver
13- import org.maplibre.android.annotations.MarkerOptions
13+ import com.google.gson.JsonArray
14+ import com.google.gson.JsonObject
1415import org.maplibre.android.camera.CameraPosition
1516import org.maplibre.android.camera.CameraUpdateFactory
1617import org.maplibre.android.geometry.LatLng
1718import org.maplibre.android.geometry.LatLngBounds
19+ import org.maplibre.android.location.LocationComponentActivationOptions
20+ import org.maplibre.android.location.LocationComponentOptions
1821import org.maplibre.android.maps.MapView
22+ import org.maplibre.android.style.expressions.Expression
23+ import org.maplibre.android.style.layers.CircleLayer
24+ import org.maplibre.android.style.layers.PropertyFactory
25+ import org.maplibre.android.style.sources.GeoJsonSource
1926
27+ @androidx.annotation.RequiresPermission (allOf = [android.Manifest .permission.ACCESS_FINE_LOCATION , android.Manifest .permission.ACCESS_COARSE_LOCATION ])
2028@Composable
2129fun LibreMap (
2230 markers : List <MapMarker >,
2331 selectedMarkerId : String? ,
2432 modifier : Modifier = Modifier ,
25- styleUrl : String = "https : // demotiles.maplibre.org/style.json"
33+ styleUrl : String = "https : // demotiles.maplibre.org/style.json",
34+ onMarkerClick : (String ) -> Unit = {}
2635) {
2736 val context = LocalContext .current
2837 val lifecycleOwner = LocalLifecycleOwner .current
2938
3039 val mapView = remember {
3140 MapView (context).apply {
3241 getMapAsync { mapLibreMap ->
33- mapLibreMap.setStyle(styleUrl) {
42+ mapLibreMap.setStyle(styleUrl) { style ->
3443 // Set initial camera position (centered on equator with moderate zoom)
3544 val initialPosition = CameraPosition .Builder ()
3645 .target(LatLng (0.0 , 0.0 ))
3746 .zoom(2.0 )
3847 .build()
3948 mapLibreMap.cameraPosition = initialPosition
49+ val locationComponent = mapLibreMap.locationComponent
50+ locationComponent.activateLocationComponent(
51+ LocationComponentActivationOptions
52+ .builder(context, style)
53+ .locationComponentOptions(
54+ LocationComponentOptions .builder(context)
55+ .pulseEnabled(true )
56+ .build()
57+ )
58+ .build()
59+ )
60+ locationComponent.isLocationComponentEnabled = true
4061 }
4162 }
4263 }
@@ -67,18 +88,79 @@ fun LibreMap(
6788 update = { view ->
6889 view.getMapAsync { mapLibreMap ->
6990 mapLibreMap.getStyle { style ->
70- // Clear existing markers
71- mapLibreMap.clear()
91+ // Remove existing source and layer if they exist
92+ style.getLayer(" tree-markers-layer" )?.let { style.removeLayer(it) }
93+ style.getSource(" tree-markers-source" )?.let { style.removeSource(it) }
7294
73- // Add markers in bulk
95+ // Add markers in bulk as GeoJSON
7496 if (markers.isNotEmpty()) {
75- val markerOptions = markers.map { marker ->
76- MarkerOptions ()
77- .position(LatLng (marker.latitude, marker.longitude))
97+ // Create GeoJSON FeatureCollection
98+ val features = JsonArray ()
99+ markers.forEach { marker ->
100+ val feature = JsonObject ().apply {
101+ addProperty(" type" , " Feature" )
102+ add(" geometry" , JsonObject ().apply {
103+ addProperty(" type" , " Point" )
104+ add(" coordinates" , JsonArray ().apply {
105+ add(marker.longitude)
106+ add(marker.latitude)
107+ })
108+ })
109+ add(" properties" , JsonObject ().apply {
110+ addProperty(" id" , marker.id)
111+ })
112+ }
113+ features.add(feature)
114+ }
115+
116+ val featureCollection = JsonObject ().apply {
117+ addProperty(" type" , " FeatureCollection" )
118+ add(" features" , features)
78119 }
79120
80- // Add all markers at once
81- mapLibreMap.addMarkers(markerOptions)
121+ // Add GeoJSON source
122+ val source = GeoJsonSource (" tree-markers-source" , featureCollection.toString())
123+ style.addSource(source)
124+
125+ // Add circle layer with green dots
126+ val radiusExpression = if (selectedMarkerId != null ) {
127+ Expression .switchCase(
128+ Expression .eq(Expression .get(" id" ), Expression .literal(selectedMarkerId)),
129+ Expression .literal(12f ), // Selected marker radius
130+ Expression .literal(8f ) // Default marker radius
131+ )
132+ } else {
133+ Expression .literal(8f )
134+ }
135+
136+ val circleLayer = CircleLayer (" tree-markers-layer" , " tree-markers-source" ).apply {
137+ setProperties(
138+ PropertyFactory .circleRadius(radiusExpression),
139+ PropertyFactory .circleColor(" #4CAF50" ),
140+ PropertyFactory .circleStrokeWidth(2f ),
141+ PropertyFactory .circleStrokeColor(" #FFFFFF" )
142+ )
143+ }
144+ style.addLayer(circleLayer)
145+
146+ // Add click listener for markers
147+ mapLibreMap.addOnMapClickListener { latLng ->
148+ // Convert map coordinates to screen point
149+ val screenPoint = mapLibreMap.projection.toScreenLocation(latLng)
150+
151+ // Query features at click point from marker layer
152+ val features = mapLibreMap.queryRenderedFeatures(screenPoint, " tree-markers-layer" )
153+
154+ // If marker clicked, extract ID and notify
155+ if (features.isNotEmpty()) {
156+ val markerId = features.first().getStringProperty(" id" )
157+ if (markerId != null ) {
158+ onMarkerClick(markerId)
159+ return @addOnMapClickListener true // Consume event
160+ }
161+ }
162+ false // Don't consume - allow map pan/zoom
163+ }
82164
83165 // Handle selected marker zoom
84166 if (selectedMarkerId != null ) {
0 commit comments