onPolygonClick detects different polygon - android

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
}

Related

Custom markers not visible in mapbox-android

I'm using mapbox-sdk android for location tracking. I want to add few custom markers to the map in specified location. But the below code doesn't works for me.
MarkerOptions options = new MarkerOptions();
options.title("pos");
IconFactory iconFactory = IconFactory.getInstance(MainActivity.this);
Icon icon = iconFactory.fromResource(R.drawable.home);
options.icon(icon);
options.position(new LatLng(80.27, 13.09));
mapboxMap.addMarker(options);
I use mapox-sdk:6.0.1
use this:
private fun addMarkerIconsToMap(loadedMapStyle: Style) {
BitmapUtils.getBitmapFromDrawable(getDrawable(R.drawable.red_marker))?.let {
loadedMapStyle.addImage("icon-id", it)
}
if(!locationList.isNullOrEmpty()){
val feature: ArrayList<Feature> = ArrayList()
for (x in 0 until locationList.size){
feature.add(Feature.fromGeometry(Point.fromLngLat(locationList[x].long, locationList[x].lat)))
}
loadedMapStyle.addSource(GeoJsonSource("source-id",
FeatureCollection.fromFeatures(feature)
)
)
loadedMapStyle.addLayer(SymbolLayer("layer-id", "source-id").withProperties(
iconImage("icon-id"),
iconOffset(arrayOf(0f, -8f))
)
)
}
}
inside locationList is Array of long: Double and lat: Double
YourActivity :
class YourActivity : AppCompatActivity(), OnMapReadyCallback {
private lateinit var mapboxMap: MapboxMap
...
}
and then override :
override fun onMapReady(mapboxMap: MapboxMap) {
mapboxMap.setStyle(Style.MAPBOX_STREETS){
addMarkerIconsToMap(it)
}
this.mapboxMap = mapboxMap
}
This is how I would add a custom marker (from a Compose perspective).
(You can either do it from the viewModel depending on your program structure)
AndroidView(
factory = { mapView }, modifier = Modifier.fillMaxWidth()
) { mView ->
val bitmap = bitmapFromDrawableRes(context, R.drawable.chicken_marker)!!
val annotation = mView.annotations
val pointManager = annotation.createPointAnnotationManager()
list.forEach { point ->
val pointOptions = PointAnnotationOptions()
.withPoint(point)
.withIconImage(bitmap)
pointManager.create(pointOptions.apply { iconSize = 0.8 })
}
Here's the functions used to get the drawable(".svg",".png",".jpeg")
private fun bitmapFromDrawableRes(context: Context, #DrawableRes resourceId: Int): Bitmap? =
convertDrawableToBitmap(AppCompatResources.getDrawable(context, resourceId))
private fun convertDrawableToBitmap(sourceDrawable: Drawable?): Bitmap? {
if (sourceDrawable == null) {
return null
}
return if (sourceDrawable is BitmapDrawable) {
sourceDrawable.bitmap
} else {
// copying drawable object to not manipulate on the same reference
val constantState = sourceDrawable.constantState ?: return null
val drawable = constantState.newDrawable().mutate()
val bitmap: Bitmap = Bitmap.createBitmap(
drawable.intrinsicWidth, drawable.intrinsicHeight,
Bitmap.Config.ARGB_8888
)
val canvas = Canvas(bitmap)
drawable.setBounds(0, 0, canvas.width, canvas.height)
drawable.draw(canvas)
bitmap
}
}

How to properly detect markerClick in Sygic Map

I used MapGestureListener to compare each time the coords of the clicked area and the coords of the marker and if they're at the same coords then I'm good to go but it just won't work because of the relative altitude change that doesn't assure the accuracy of getting the clicked position.
mpView.addMapGestureListener(object : MapGestureAdapter() {
override fun onMapClicked(e: MotionEvent?, isTwoFingers: Boolean): Boolean {
val clickedArea=mpView.geoCoordinatesFromPoint(Math.round(e!!.getX()), Math.round(e.getY()))
for (marker : MapMarker in markerList )
{
val dist=clickedArea!!.distanceTo(marker.position)
if (dist< 2)
{
val positionMarker = markerList.indexOf(marker)
val positionLastMarker = markerList.indexOf(mSelectedMarker!!)
val markerNumber = positionMarker +1
val lastMarkerNumber = positionLastMarker + 1
travelStep = travelStepList.get(markerNumber -1)
configTeaser(travelStep)
}
}
return false
}
})
I've managed to do it , i just had to call the "requestObjectsAtPoint" inside the MapGesture listener and do some workaround ,here's the code :
mpView.addMapGestureListener(object : MapGestureAdapter() {
override fun onMapClicked(e: MotionEvent?, isTwoFingers: Boolean): Boolean {
mpView.requestObjectsAtPoint(e!!.getX(),e.getY(), RequestObjectCallback { objects, x, y ->
for (marker : ViewObject in objects )
{
if (marker.objectType==1)
{
if ((marker as MapObject).mapObjectType==1)
{
val positionMarker = markerList.indexOf(marker)
val positionLastMarker = markerList.indexOf(mSelectedMarker!!)
val markerNumber = positionMarker +1
val lastMarkerNumber = positionLastMarker + 1
mSelectedMarker = marker as MapMarker
travelStep = travelStepList.get(markerNumber -1)
configTeaser(travelStep)
}
}
}
})
return true
}
})

How to select and deselect a marker in google maps?

I have a list of places which are marked in google maps using Markers. I want to select a Marker so that it will highlight with a different color. When I click on the same marker or any other marker I want remove the selection made in the first marker and set it back to the default color.
This is my onClusterItemClick method
override fun onClusterItemClick(p0: Station?): Boolean {
dragView.visibility = View.VISIBLE
viewModel.loadStation(p0?.id!!)
val marker = renderer.getMarker(p0)
//save previous merker here
marker?.setIcon(BitmapDescriptorFactory.fromResource(R.drawable.ic_map_pin_selected))
return true
}
This is my Station Renderer
/**
* Class to design the pin point into the map
*/
inner class StationRenderer(context: Context, map: GoogleMap,
clusterManager: ClusterManager<Station>) : DefaultClusterRenderer<Station>(context, map, clusterManager) {
override fun onBeforeClusterRendered(cluster: Cluster<Station>?, markerOptions: MarkerOptions?) {
markerOptions?.icon(BitmapDescriptorFactory.fromBitmap(createStoreMarker(cluster?.size.toString())))
}
override fun onBeforeClusterItemRendered(item: Station?, markerOptions: MarkerOptions?) {
markerOptions?.icon(BitmapDescriptorFactory.fromResource(R.drawable.ic_map_pin))
}
private fun createStoreMarker(stationsCount:String): Bitmap {
val markerLayout = layoutInflater.inflate(R.layout.marker_item, null)
val markerImage = markerLayout.findViewById(R.id.marker_image) as ImageView
val markerRating = markerLayout.findViewById(R.id.marker_text) as TextView
markerImage.setImageResource(R.drawable.ic_map_pin)
markerRating.text = stationsCount
markerLayout.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED))
markerLayout.layout(0, 0, markerLayout.getMeasuredWidth(), markerLayout.getMeasuredHeight())
val bitmap = Bitmap.createBitmap(markerLayout.getMeasuredWidth(), markerLayout.getMeasuredHeight(), Bitmap.Config.ARGB_8888)
val canvas = Canvas(bitmap)
markerLayout.draw(canvas)
return bitmap
}
override fun shouldRenderAsCluster(cluster: Cluster<Station>?): Boolean {
return cluster?.size !!> 1
}
}
In googleMaps these is no such thing as selected or deselected or some kind of listeners specifically for that but you have onMarkerClick(); you can use this Listener and add some logic to achieve that thing.
googleMap.setOnMarkerClickListener(new GoogleMap.OnMarkerClickListener(){
#Override
public boolean onMarkerClick(Marker marker){
return false;
}
});
You can get the Idea from this: How to select and deselect a marker in google maps in android?

Map not clickable around marker in Google Map SDK for Android

I am building some app like image below, I want to force markers not to be clickable, but there is no setClickable(false) for Marker or MarkerOptions.
Currently area around marker (see attachment) is not clickable ( click is passed to marker, not map)
You have to use Overlay instead of marker in the Map to get exactly what you desire. You could follow this link, similar is done in JavaScript here.
I found a way to manually handle clicks for markers.
Add a touchable wrapper as described in this stackoverflow answer: https://stackoverflow.com/a/58039285/1499750
Add a gesture detector to your fragment and listen to single taps, then find the closest marker based on lat lng:
private var gestureDetector: GestureDetector? = null
...
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
gestureDetector = GestureDetector(context, GoogleMapsGestureListener { e -> onMapSingleTap(e) })
//id of touchable wrapper - can use findViewById here instead if not using kotlin synthetics
googleMapsTouchableWrapper?.onTouch = {
gestureDetector?.onTouchEvent(it)
}
}
private fun onMapSingleTap(e: MotionEvent) {
val latLng = map?.projection?.fromScreenLocation(Point(e.x.toInt(), e.y.toInt())) ?: return
//this assumes you are maintaining a set of the latlngs for your markers
val closestNearbyLatLng = markerLatLngs?.findClosestNearbyLatLng(latLng)
//assuming you have a map of latlng to marker you can now find that marker based on latlng and do whatever you need to with it
}
private fun Set<LatLng>.findClosestNearbyLatLng(latLng: LatLng): LatLng? {
val map = map ?: return null
val screenDistance = map.projection.visibleRegion.latLngBounds.northeast.distanceBetweenInKm(map.projection.visibleRegion.latLngBounds.southwest)
val closestLatLng = this.minBy { latLng.distanceBetweenInKm(it) } ?: return null
if (latLng.distanceBetweenInKm(closestLatLng) < screenDistance/40) {
return closestLatLng
}
return null
}
fun LatLong.distanceBetweenInKm(latLng: LatLng): Double {
if (this == latLng) {
return 0.0
}
val earthRadius = 6371.0 //km value;
//converting to radians
val latPoint1Radians = Math.toRadians(latitude)
val lngPoint1Radians = Math.toRadians(longitude)
val latPoint2Radians = Math.toRadians(latLng.latitude)
val lngPoint2Radians = Math.toRadians(latLng.longitude)
var distance = sin((latPoint2Radians - latPoint1Radians) / 2.0).pow(2.0) + (cos(latPoint1Radians) * cos(latPoint2Radians)
* sin((lngPoint2Radians - lngPoint1Radians) / 2.0).pow(2.0))
distance = 2.0 * earthRadius * asin(sqrt(distance))
return abs(distance) //km value
}
class GoogleMapsGestureListener(private val onSingleTap: (MotionEvent) -> Unit) : GestureDetector.SimpleOnGestureListener() {
override fun onSingleTapConfirmed(e: MotionEvent?): Boolean {
super.onSingleTapConfirmed(e)
e?.let { onSingleTap(it) }
return true
}
}
I recently was able to create a formula to create an area surrounding a certain position on a Google Map, that is also scalable with zoom level.
Here I converted the LatLng coordinates from the marker to actual coordinates on the phone:
//array that holds all locations of every marker
//after a marker is created add the position in here
val positionList = mutableListOf<LatLng>()
//map is variable type GoogleMap
map.setOnMapClickListener {
var inRange = false
for(i in positionList.indices) {
//establish corners of boundaries surrounding markers
val points = positionList.toCoordinates(map)
//check if clicked position falls in one of the positions' bounds
val isInRangeLng = (points[i][2]..points[i][3]).contains(it.longitude)
val isInRangeLat = (points[i][0]..points[i][1]).contains(it.latitude)
//if click lands in of the positions' bounds, stop loop and return inRange
//true
if(isInRangeLat && isInRangeLng) {
inRange = true
break
}
}
if(!inRange) {
//APPLY YOUR LOGIC IF CLICK WAS NOT IN AREA
} else {
//APPLY YOUR LOGIC IF CLICK WAS IN AREA
}
}
//Extension function used to simplify logic
/** Convert LatLng to coordinates on phone **/
fun List<LatLng>.toCoordinates(map: GoogleMap): List<List<Double>> {
val proj: Projection = map.projection
val coordinateList = mutableListOf<List<Double>>()
//create bounds for each position in list
this.forEach {
//get screen coordinates at the current LatLng
val point = proj.toScreenLocation(it)
val left = point.x - 100
val right = point.x + 100
val top = point.y - 100
val bottom = point.y + 100
//convert bounds into two points diagonal of each other
val topRight = Point(right, top)
val bottomLeft = Point(left, bottom)
//convert the two points into LatLng points and get the bounds in north,
//south, west, and east
val northEast = proj.fromScreenLocation(topRight)
val north = northEast.latitude
val east = northEast.longitude
val southWest = proj.fromScreenLocation(bottomLeft)
val south = southWest.latitude
val west = southWest.longitude
//add the bounds to be returned in a list which corresponds to a certain
//position
coordinateList.add(listOf(
south,
north,
west,
east
))
}
return coordinateList
}
This can be used for a lot more than markers too.

Change cluster manager's item icon programmatically

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))
}
}
}
}

Categories

Resources