I use the SymbolManager class to add a marker as SymbolOption on Mapbox.
This is the code I wrote to add the marker and it runs:
fun addMarker(marker : MapboxMarker) {
map?.style?.let {
it.getImage(MARKER_IMAGE_ID) ?: run {
it.addImage(MARKER_IMAGE_ID, BitmapFactory.decodeResource(context.resources, R.drawable.marker), true)
}
}
symbolManager ?: run {
symbolManager = SymbolManager(mapView!!, map!!, map!!.style!!)
symbolManager!!.iconAllowOverlap = true;
symbolManager!!.textAllowOverlap = true;
}
symbolManager!!.create(SymbolOptions().apply {
withLatLng(marker.latLng)
withIconImage(MARKER_IMAGE_ID)
marker.markerOptions?.let {
withIconColor(ColorUtils.colorToRgbaString(it.markerColor))
}
withIconSize(1f)
withSymbolSortKey(10.0f)
})
}
where MapboxMarker is my model with latitude, longitude and marker color
data class MapboxMarker(
var latLng: LatLng,
var markerColor: Int = Color.WHITE
)
Now, I need to remove a marker from map, but
symbolManager?.delete(symbolOption)
doesn't compile, it needs a Symbol object and SymbolOption is not a Symbol.
So... how can I remove a single SymbolObject?
Declare a SymbolManager:
private lateinit var mSymbolManager : SymbolManager
In your Fragment / Activity implement the interface OnMapReadyCallback
class MapFragment : Fragment(), OnMapReadyCallback {
override fun onMapReady(mapboxMap: MapboxMap) {
mapboxMap.setStyle(Style.SATELLITE) { style ->
mSymbolManager = SymbolManager(*YOUR_MAP_VIEW*, mapboxMap, style)
mSymbolManager.iconAllowOverlap = true
mSymbolManager.textAllowOverlap = true
}
}
}
To add a marker:
style.addImage("NAME_OF_MARKER_IMG", BitmapFactory.decodeResource(resources, YOUR_DRAWABLE_ICON))
mSymbolManager.create(
SymbolOptions()
.withLatLng(LatLng(LATITUDE, LONGITUDE))
.withTextAnchor(Property.TEXT_ANCHOR_TOP)
.withTextField("YOUR_TEXT_HERE")
.withIconImage("NAME_OF_MARKER_IMG")
.withTextColor("#COLOR") // #FFFFFF = WHITE
)
To delete all markers:
mSymbolManager.deleteAll()
To delete specific marker:
// Declare reference to that marker
private lateinit var mSymbol: Symbol
// During the creation save that reference
mSymbol = mSymbolManager.create(...)
// Use the SymbolManager to delete it
mSymbolManager.delete(mSymbol)
A special thanks to Aboukinane for saving my life on this.
You are correctly creating the symbol object, but you are not storing a reference to it. You need that reference to delete it. Therefore you can solve your problem by keeping track of the symbols you create.
For that just keep the reference in a variable:
val symbolManager = SymbolManager(mapView, mapboxMap, style)
symbolManager.iconAllowOverlap = true
symbolManager.iconIgnorePlacement = true
// Add symbol at specified lat/lon
val symbol = symbolManager.create(SymbolOptions()
.withLatLng(LatLng(60.169091, 24.939876))
.withIconImage(ICON_ID)
.withIconSize(2.0f))
You now have the reference to that symbol stored inside symbol.
If you want to remove the symbol, you can use the reference:
symbolManager.delete(symbol)
Related
I use MapView inside my recyclerview items to display info about a city which the item is about, and I can't figure out how to fix binding issues.
When current item is at the top of the viewport, the map is correct:
But when another item is at the top of the viewport, that is what happens:
As you can see, map displays Salzburg instead of Edinburgh because Salzburg is at the top of recyclerview viewport.
My ViewHolder class:
inner class WeatherViewHolder(
private val binding: ItemWeatherBinding
) : RecyclerView.ViewHolder(
binding.root
), OnMapReadyCallback {
lateinit var gMap: GoogleMap
init {
binding.apply {
// Other binging here...
with(map) {
onCreate(null)
getMapAsync(this#WeatherViewHolder)
}
}
}
fun bind(weather: Weather) {
binding.apply {
//Other binding here...
setMapLocation()
}
}
override fun onMapReady(googleMap: GoogleMap) {
Log.v("Maps", "OnReady")
MapsInitializer.initialize(superFragment.activity)
gMap = googleMap
with(gMap.uiSettings) {
isZoomControlsEnabled = false
isCompassEnabled = false
isMapToolbarEnabled = false
isMyLocationButtonEnabled = false
isRotateGesturesEnabled = false
isScrollGesturesEnabled = false
isTiltGesturesEnabled = false
isZoomGesturesEnabled = false
}
binding.map.onResume()
setMapLocation()
}
private fun setMapLocation() {
if (!::gMap.isInitialized) return
Log.v("Maps", "setting location")
val weather = getItem(adapterPosition)
with(gMap) {
moveCamera(CameraUpdateFactory.newLatLngZoom(LatLng(weather.latitude, weather.longitude), 10f))
mapType = GoogleMap.MAP_TYPE_NORMAL
}
superFragment.viewLifecycleOwner.lifecycleScope.launch {
repository.getInfo { response ->
gMap.addTileOverlay(/*setting my tiles here*/)
}
}
}
}
I tried calling setMapLocation() only to an item which is currently expanded (as I use expandable layout here), but it only led to more confusing behavior.
The question here is: how can I fix this behavior and display correct map regardless of recyclerview's scroll position?
I am using Kotlin in my Android project. MapView is in fragment.
When user selects polygon I change its stroke color. Problem is that sometimes onPolygonClick method detects different polygon. You can see it in this GIF: https://gyazo.com/167aed90529031df01c07d7f583f790e
onPolygonClick method:
override fun onPolygonClick(polygon: Polygon?) {
polygons.forEach {
it.strokeColor = Color.BLACK
}
polygon?.strokeColor = Color.WHITE
}
At first, I fetch hexagons from server and then I draw it on map. This is method which is called after data are fetched to add polygons to the map:
private fun drawRegion(regions: Array<Kraj>) {
//reset map
googleMap.clear()
polygons = ArrayList()
setMapViewBounds(regions)
for (region in regions) {
val rnd = Random()
val color = Color.argb(255, rnd.nextInt(256), rnd.nextInt(256), rnd.nextInt(256))
for (hexagon in region.hexagons) {
val options = PolygonOptions()
for (point in hexagon) {
options.add(point)
}
options.strokeColor(Color.BLACK)
options.fillColor(color)
options.strokeWidth(2.5.toFloat())
options.clickable(true)
val pol = googleMap.addPolygon(options)
pol.tag = region.id
polygons.add(pol)
}
}
}
As you can see I also save all polygons to polygons array so I can access all of them in onPolygonClick method.
onMapReady method:
override fun onMapReady(map: GoogleMap?) {
map?.let {
googleMap = it
googleMap.setMapStyle(
MapStyleOptions.loadRawResourceStyle(
activity?.applicationContext, R.raw.empty_map_style
)
)
}
map?.setOnMapClickListener(this)
map?.setOnPolygonClickListener(this)
addObservers()
}
I didn't figure out why MapView detects different polygon, but I did a workaround.
In case you have same problem as me, just set your polygon options clickable to false (otherwise onMapClick is not working if you tap on polygon), setup onMapClickListener and detect which polygon was clicked:
override fun onMapClick(coords: LatLng?) {
val polygon = polygons.first {PolyUtil.containsLocation(coords!!, it.points, true)}
val id = polygon?.tag.let { it } as Int
polygons.forEach {
it.strokeColor = Color.BLACK
val itId = it?.tag.let { it } as Int
if (it == polygon) {
it.strokeColor = Color.BLUE
}
}
viewModel.userClickedOnPolygonWithId(id)
polygon?.strokeColor = Color.WHITE
}
Im making a project where i need to add and remove some markers created dynamically. i'm creating my markes this way:
private fun AddMarkerFromArray(name:String, adrs:String,cod:String,imgSrc:String){
val info = MarkerDataNormal(name, adrs,
cod )
val markerOptions = MarkerOptions()
markerOptions.position(LatLng(0.0,0.0))
.title("Location Details")
.snippet("I am custom Location Marker.")
.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_BLUE)
)
val customInfoWindow = CustomInfoWindowGoogleMap(this)
mMap.setInfoWindowAdapter(customInfoWindow)
val spot: Marker = mMap.addMarker(markerOptions)
spot.tag = info
// spot.showInfoWindow()
}
And this is the rest of code:
data class MarkerDataNormal(val mLocatioName: String,
val mLocationAddres: String,
val mLocationCood: String)
class CustomInfoWindowGoogleMap(val context: Context) : GoogleMap.InfoWindowAdapter {
override fun getInfoContents(p0: Marker?): View {
var mInfoView = (context as Activity).layoutInflater.inflate(R.layout.markernormal, null)
var mInfoWindow: MarkerDataNormal? = p0?.tag as MarkerDataNormal?
mInfoView.txtNombre.text = mInfoWindow?.mLocatioName
mInfoView.txtDireccion.text = mInfoWindow?.mLocationAddres
mInfoView.txtCoordenadas.text = mInfoWindow?.mLocationCood
return mInfoView
}
override fun getInfoWindow(p0: Marker): View? {
return null
}
}
so i'm using the function to make apear all the Markers i need, they are displayed correctly, but i can't clear() only one cause every one of them are "spot"
how can i change the name of the variable or assign an id to each one for later access?
I think you should store the references to the marker objects in a List<Marker>. This way you will have access to these references later.
I want to customize line join style as in the picture below:
How can I do it?
My source code:
val lineString = LineString.fromLngLats(tempCoordinateList)
val geoJsonSource = GeoJsonSource(DASHED_LINE_SOURCE_ID, Feature.fromGeometry(lineString))
mapboxMap?.addSource(geoJsonSource)
val lineLayer = LineLayer(DASHED_LINE_LAYER_ID, DASHED_LINE_SOURCE_ID).apply {
setProperties(
PropertyFactory.lineDasharray(arrayOf(2f, 1f)),
PropertyFactory.lineCap(Property.LINE_CAP_SQUARE),
PropertyFactory.lineJoin(Property.LINE_JOIN_ROUND),
PropertyFactory.lineWidth(3f),
PropertyFactory.lineColor(Color.parseColor("#e55e5e")))
}
mapboxMap?.addLayer(lineLayer)
I didn't find a solution to how to achieve this with standard LineLayer's properties. Therefore, I decided to add these points in a separate layer with markers.
val markerCoordinates = arrayListOf<Feature>()
tempCoordinateList
.forEach {
val feature = Feature.fromGeometry(
Point.fromLngLat(it.longitude, it.latitude))
markerCoordinates.add(feature)
}
val geoJsonSource = GeoJsonSource(MARKER_POINTS_SOURCE_ID, FeatureCollection.fromFeatures(markerCoordinates))
mapboxMap?.addSource(geoJsonSource)
mapboxMap?.addImage(MARKER_POINTS_IMAGE_ID, pointIcon)
val markers = SymbolLayer(MARKER_POINTS_LAYER_ID, MARKER_POINTS_SOURCE_ID)
.withProperties(PropertyFactory.iconImage(MARKER_POINTS_IMAGE_ID))
mapboxMap?.addLayer(markers)
On my map I use the Marker Cluster Utility to group the markers. All the markers when first put on the map have the same icon, then, when I move close to one of the markers, its icon must change. I've read other discussions about this, but as far as I've understood, I'd need to remove the marker and generate it again with the new icon.
My markers belong to a cluster, so I should remove the marker from the cluster, generate a new marker and add it to the cluster manager object.
The problem is that the cluster manager object has a renderer attached to it which also defines the marker's icon and it would use the same icon as for the removed marker.
Some code:
the renderer class
class VenueMarkerRender(private val context: Context, map: GoogleMap, clusterManager: ClusterManager<Venue>)
: DefaultClusterRenderer<Venue>(context, map, clusterManager) {
override fun onBeforeClusterItemRendered(item: Venue?, markerOptions: MarkerOptions?) {
super.onBeforeClusterItemRendered(item, markerOptions)
markerOptions!!.icon(bitmapDescriptorFromVector(context, R.drawable.ic_map_black))
}
override fun onBeforeClusterRendered(cluster: Cluster<Venue>?, markerOptions: MarkerOptions?) {
super.onBeforeClusterRendered(cluster, markerOptions)
markerOptions!!.icon(bitmapDescriptorFromVector(context, R.drawable.ic_home_black_24dp))
}
override fun shouldRenderAsCluster(cluster: Cluster<Venue>?): Boolean {
return cluster!!.size > 1
}
/**
* Takes a vector image and make it available to use as a marker's icon
*/
private fun bitmapDescriptorFromVector(context: Context, #DrawableRes vectorDrawableResourceId: Int): BitmapDescriptor {
// ...
return BitmapDescriptorFactory.fromBitmap(bitmap)
}
}
the Venue class
class Venue : ClusterItem {
private var mPosition: LatLng
private var mTitle: String? = null
private var mSnippet: String? = null
constructor(lat: Double, lng: Double, title: String, snippet: String) {
mPosition = LatLng(lat, lng)
mTitle = title
mSnippet = snippet
}
override fun getPosition(): LatLng {
return mPosition
}
override fun getTitle(): String {
return mTitle!!
}
override fun getSnippet(): String? {
return mSnippet
}
}
finally how the cluster manager is created and how a venue is added to it
mClusterManager = ClusterManager(this, map)
val renderer = VenueMarkerRender(this, map, mClusterManager!!)
mClusterManager!!.renderer = renderer
// other code
for (i in 0 until markers.length()) {
val marker = JSONObject(markers.getJSONObject(i).toString())
val venue = Venue(
marker.getDouble("lat"),
marker.getDouble("lng"),
marker.getString("title"),
marker.getString("snippet"),
)
mClusterManager!!.addItem(venue)
}
mClusterManager!!.cluster()
Is it possible to generate a new Venue object with its own icon and to add it to the cluster manager object? Or is there a better way to obtain what I need?
I've just found the solution, I hope this will help someone else.
I've declared the renderer as a class attribute to make it available everywhere inside the activity
private var renderer: VenueMarkerRender? = null
before it was a private variable inside the method which sets up the Cluster Manager. Then it is initialized as already shown in the previous message
renderer = VenueMarkerRender(this, map, mClusterManager!!)
Now to change the marker when I get close to it, it is enough to call this method each time that the location changes
private fun markerProximity() {
// get the venues' list from the cluster
val venues = mClusterManager!!.algorithm.items
// if the cluster was not empty
if (venues.isNotEmpty()) {
// initialize the array which will contain the distance
val distance: FloatArray = floatArrayOf(0f,0f,0f)
// loop through all the venues
for (venue:Venue in venues) {
// get the distance in meters between the current position and the venue location
Location.distanceBetween(
venue.position.latitude,
venue.position.longitude,
lastLocation.latitude,
lastLocation.longitude,
distance)
// if closer than 3 meters
if ( distance[0] < 3 ) {
// change this marker's icon
renderer!!.getMarker(venue)
.setIcon(BitmapDescriptorFactory
.fromResource(R.drawable.my_location))
}
}
}
}