How to click a GoogleMap Polygon below a Marker? - android

My android application uses a GoogleMap with Polygons to show areas of interest to the user. If the user clicks on a Polygon the click event is handled by the OnPolygonClickListener and an info dialog is displayed. My problem lies with a Marker showing the current location of the user which is blocking click events passing to the Polygon below.
Sidenote: The click area of the marker also seems to be larger than visible. This circle actually blocks nearly the whole square below it.
What I tried so far:
Catching the click event and ignore it
getMap().setOnMarkerClickListener(new OnMarkerClickListener() {
public boolean onMarkerClick(Marker marker) {
return true;
}
});
Does not work in this case because we can either return false which triggers the default behavior (zooming in on the marker) or true which ignores the click event altogether since - per documentation - there is only one event per click.
Changing the z-index of the polygon: Does not work because - again per documentation - "Markers are effectively considered to be in a separate z-index group compared to other overlays or shapes, regardless of the z-index of the other overlays" so click events are always passed to potential markers first.
So is there a way to make a Polygon below a Marker clickable or at least reduce the clickable marker area to the visible size?

Following the suggestion of MrUpsidown and using googlemaps PolyUtil I implemented a manual validation to trigger a Polygon targeted click manually. I can not tell about efficiency of containsLocation() when handling a larger amount of polygons but it works for the time being.
mMap.setOnMarkerClickListener { marker ->
if (marker == userLocation) {
mPolygons.forEach { polygon ->
if(PolyUtil.containsLocation(userLocation?.position, polygon.points, false)) {
// Do Polygon stuff here
return#forEach
}
}
true
} else {
// Do optional marker stuff here
true
}
}

Related

Android Mapbox how to capture marker clicks when using CirlceClustering layer

My Current Android application employs the excellent Mapbox SDK
implementation 'com.mapbox.mapboxsdk:mapbox-android-sdk:8.0.0'
implementation 'com.mapbox.mapboxsdk:mapbox-android-plugin-annotation-v7:0.6.0'
implementation 'com.mapbox.mapboxsdk:mapbox-android-plugin-localization-v7:0.9.0'
My application displays approx 50,000 markers and I am using CircleLayer clustering.
The application works as required/expected apart from the fact I cannot see how to detect when my user clicks on any of the low level markers.
All the "Marker" related mapboxMap methods are all deprecated
and direct the developer to employ
use <a href="https://github.com/mapbox/mapbox-plugins-android/tree/master/plugin-annotation">
* Mapbox Annotation Plugin
However I cannot see how to use plugin-annotation to detect clicks on my low level markers.
What am I missing?
To detect any click on your CircleLayer you need first to implement the onMapClick or onMapLongClick methods. Then on every detected click, you need to query your source layer and see if there any features near that location.
If so, then you can get the N nearest features and handle their behaviour.
It should look like this:
#Override
public boolean onMapClick(#NonNull LatLng point) {
// Get the clicked point coordinates
PointF screenPoint = mapboxMap.getProjection().toScreenLocation(point);
// Query the source layer in that location
List<Feature> features = mapboxMap.queryRenderedFeatures(screenPoint, "MY_SOURCE_LAYER_ID");
if (!features.isEmpty()) {
// get the first feature in the list
Feature feature = features.get(0);
// do stuff...
}
return true;
}
This is a very basic way of handling clicks on your layers data. You can find this example I have slightly modified here.

Is it possible to listen to clicks in a small area of a marker in Google Map?

I am using Google Map in an Android app.
Google Map API provides a setOnMarkerClickListener so that we can do what we want when user clicks on a marker.
googleMap.setOnMarkerClickListener(new GoogleMap.OnMarkerClickListener() {
#Override
public boolean onMarkerClick(Marker marker) {
//Do my stuff here
}
});
However, my marker image is pretty large and I only want a small part of it to be clickable.
And it seems there is no method of Marker that returns its X Y position in its fragment.
So, is this actually feasible, assuming that I must use Google Map?

Google Maps Android, detect user interation in a location aware map

I've an Android Application that displays a map.
The map is by default centered on the user position following its movements (the center is updated according to the position updates).
However I want the user to be able to use gestures to navigate throw the map. When the user starts the navigation I want the "following" to stop, and a button is displayed so that it can start again.
How can I know when the user has moved the map center?
On the GoogleMap.OnCameraChangeListener, I don't know if the change is due to a location changed or a user interaction.
I've a a kind of working solution using the OnCameraChangeListener, but its a bit "dirty" and I don't find it very nice:
map.setOnCameraChangeListener(new GoogleMap.OnCameraChangeListener() {
#Override
public void onCameraChange(CameraPosition position) {
// gpsPosition contains the last position obtained
if(isFollowing && gpsPosition!=null && (Math.abs(position.target.latitude - gpsPosition.latitude)>0.000001 || Math.abs(position.target.longitude - gpsPosition.longitude)>0.000001) ){
isFollowing = false;
map.getUiSettings().setMyLocationButtonEnabled(true);
}
}
});
Is there a nice Solution to the problem?
If you want to detect user interacting with the map OnCameraChangeListener is surely too late to do that. It may be called seconds after user started interacting with the map.
What you need to do, is add 2 other listeners:
OnMyLocationButtonClickListener - to know when to start tracking (set your isFollowing to true)
OnMyLocationChangeListener - when your isFollowing is true, call GoogleMap.animateCamera to the new position
So the only thing left is when you set isFollowing to false:
Create an invisible overlay in your layout (like here, but with normal View instead of custom) and assign this overlay (the View) an OnTouchListener (ok, it is 3rd, I lied that you need only 2).
In this listener always return false, but also set isFollowing to false. This when when user starts interacting with the map and you should stop automated camera movements.
I also see you are showing and hiding my location button, so do this where you change the value of isFollowing. Btw.: good idea to do that. I'll try it too.
I have never tried this, but here are some ideas off the top of my (balding) head:
Option #1: Handle the location change updates yourself, by recentering the map yourself when the location change comes in. If a camera-change event occurs that does not appear to be tied to the location change (e.g., 5+ milliseconds later), that was presumably a user modifying the map via gestures.
Option #2: Subclass the Maps V2 MapView and override touch-related methods like onTouchEvent(). In addition to chaining to the superclass, you would know that a camera change that happens very soon from now probably is from the user modifying the map via gestures.
Option #3: Do both of the above. That way, all changes to the map should touch your code, which should increase the reliability of your determining the source of the camera change.
BTW, I filed a feature request for a better solution.
The simpliest way should be saving the center Longitude and Latitude of your Map.
Now create an EventListener and check it in an if-statement:
if(oldLongLat != newLongLat){
//Something has moved
}else {
//Nothing has moved
}

Clickable Polylines Google Maps API Android

I would like to click a spot on a Google maps v2 android map. If the clicked point intersects a point on a polyline path, then display the polyline. I do not see any documented clickable events for polylines in android. I tried to extend the current Polyline object (marked final)
What other options do I have?
You can use library:
https://github.com/googlemaps/android-maps-utils
And detect clicks to polyline using next method (in OnMapClickListener):
PolyUtil.isLocationOnPath(point, polyline.getPoints(), isGeodesic, tolerance);
With the recent update of the maps api, v8.4, introduces clickable Polyline
As mentioned in the doc:
Use the OnPolylineClickListener to listen to click events on a
clickable polyline. To set this listener on the map, call
googleMap.setOnPolylineClickListener(...). When
a user clicks on a polyline, you will receive an
onPolylineClick(Polyline) callback.
gradle-dependency:
compile 'com.google.android.gms:play-services-maps:8.4.0'
implement callback: GoogleMap.OnPolylineClickListener
initialize Polyline:
Polyline polyline = googleMap.addPolyline(options);
polyline.setClickable(true);
...
receive events
#Override
public void onPolylineClick(Polyline polyline) {
....
}
Happy coding :)
Register an OnMapClickListener. Determine if a given click is on your line yourself. If it is, do whatever it was you wanted to do in this case.
I had a similar issue where I could not process click events on polylines. I was using Xamarin for Android which is C# but the functionality is largely the same as the Android Java Libraries in this case.
In the end, I ended up doing what seemed to be the only option.
This involved processing all of the midpoints of my polylines(of which there were around 1300). On every OnMapClick, I took the LatLng of the click event and performed a distance formula between it and the midpoint of all polylines in the static List<PolylineOptions>. I then attached a map marker to the closest polyline.
From a tap on a polyline, it pops up a marker in about a quarter of a second.
I imagine the implemented marker click events from the Google Maps API work in a similar way.
Here is the for loop that handles finding the closest point to a click.
int i = 0;//create an indexer for the loop
double shortestDist = 100;//set an initial very large dist just to be safe
int myIndex = 0;//set variable that will store the running index of the closest point
foreach (PolylineOptions po in myPolylines) {
var thisDist = Distance (point, midPoint (po.Points [0].Latitude, po.Points [0].Longitude, po.Points [1].Latitude, po.Points [1].Longitude));//calculate distance between point and midpoint of polyline
if (thisDist < shortestDist) {
shortestDist = thisDist;//remember current shortest distance
myIndex = i;//set closest polyline index to current loop iteration
}
i++;
}
I know it isn't the prettiest code but it gets the job done. I didn't see a real answer to this anywhere on the internet so here it is. It could probably be made more efficient by calculating the midpoints beforehand and storing them in an equally sized list and then not having to call the midpoint formula for each polyline on every map click but it works really fast already.
EDIT
I do my testing on a galaxy s3 by the way, so I think it's not too inefficient.
If you are using com.google.android.gms:play-services-maps:8.4.0 then it includes polylines click listener
googleMap.setOnPolylineClickListener(new GoogleMap.OnPolylineClickListener()
{
     #Override
     public void onPolylineClick(Polyline polyline)
      {
          //do your work selected polyline
     }
});
PolylineOptions line = new PolylineOptions();
Polyline polyline = googleMap.addPolyline(line);
polyline.setClickable(true);

Android Maps API v2 - approaches to marker rotation?

We're porting an app from the v1 Maps API to the v2 API, and having trouble with markers.
We need markers that point in a specific geographic direction. In V1, we could build the bitmap pointing in the right direction at draw time, but in V2 the marker bitmap can't be changed.
I'm not sure if the best approach is to destroy and re-build all our markers when the map is rotated (which sounds like a performance problem), or to try drawing them all ourselves. That could be via a TileOverlay or via a view of our own that we sat on top of the map.
I don't really like any of these approaches. Has anyone tried any of them ?
UPDATE:
I've tried drawing via a view of our own, but that was far too laggy when the map was dragged.
I'm now destroying & recreating the markers, but that is (as expected) a performance problem, taking ~2000mS to update 60 markers.
Good news everyone! Google has added rotation to the Maps API, so we don't have to roll our own implementations anymore.
They have also added flat markers, which I guess is more related to the original question. A flattened marker will always stay in the orientation it was originally drawn on the map: https://developers.google.com/maps/documentation/android/marker#flatten_a_marker
The only requirement is that you reference the latest version of Google Play Services.
I'm also rewriting my app (Runbot) for the new API and had to figure out how to create custom markers representing milestones (like 1km, 2km, ...) and how to show or show not all of them depending on the zoom level.
I had a custom drawable that I used for the v1 API and what I do now to render the markers is about this (Position is a class of my own that holds the position and further information; all needed here is its LatLng property):
private void addMarker(Position p, MilestoneDrawable milestone) {
if (mMarkers.containsKey(p)) {
mMarkers.get(p).setVisible(true);
} else {
Marker m = mMap.addMarker(new MarkerOptions()
.position(p.latLng)
.icon(BitmapDescriptorFactory.fromBitmap(Util.drawableToBitmap(milestone)))
.anchor(0.5f, 1.0f) // bottom center
);
mMarkers.put(p, m);
}
}
Besides creating and adding the custom markers, what you see is that I keep the markers in a HashMap so I do not have to destroy and create them all the time. When it comes to zooming and deciding which ones to show, I first set all of the markers to invisible and than call addMarker() for those I want to be shown, and those which I already have in the HashMap I simply make visible again.
I hope this helps you a bit. I have a bit of mixed feelings towards the new API...
I had a similar problem where I had markers that needed to rotate. My solution was to have the object the marker represented be responsible for generating the marker. I have a few methods in the object that look like:
protected Marker getMarker(GoogleMap map) {
if (this.marker == null) {
marker = map.addMarker(new MarkerOptions().position(location).
icon(BitmapDescriptorFactory.fromBitmap(BusMarkerImageFactory.
getMarkerIcon(heading))));
}
return marker;
}
protected void updateMarker(GoogleMap map) {
if (marker != null) {
rotateIcon();
marker.setPosition(location);
} else {
getMarker(map);
}
private void rotateIcon() {
marker.setIcon(BitmapDescriptorFactory.
fromBitmap(BusMarkerImageFactory.getMarkerIcon(heading)));
}
This is from a system that draws buses with the markers pointing in the direction they are heading, so of course, your code will be different, but the concept is very similar. Instead of rebuilding the entire marker you're keeping a reference to it somewhere and then simply resetting the icon.
Of course, drawing all those bitmaps for minor changes is a drain on memory. I used a flyweight pattern in the (incorrectly named) BusMarkerImageFactory to keep 16 images for 16 possible heading ranges. It is a static class that simply takes in the heading and returns the image that I've mapped to that range.
can't you use addMarker(new MarkerOptions()) method ?
If you need a custom marker you can create an implementation of InfoWindowAdapter and use that implementation like mMap.setInfoWindowAdapter(new CustomInfoWindowAdapter());
here is the documentation for InfoWindowAdapter

Categories

Resources