I am trying to display a list of venues on Google Maps in Android, which can be clustered on zoom out and on zoom in unclustered.
WHEN UNCLUSTERED, an individual item info window can be opened to look at that venue details, and clicked to open a separate activity.
I am using this https://developers.google.com/maps/documentation/android-api/utility/marker-clustering?hl=en
I am doing this :
Getting Map Fragment in onResume()
#Override
public void onResume() {
super.onResume();
// Getting map for the map fragment
mapFragment = new SupportMapFragment();
mapFragment.getMapAsync(new VenuesInLocationOnMapReadyCallback(getContext()));
// Adding map fragment to the view using fragment transaction
FragmentManager fragmentManager = getChildFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.add(R.id.venues_in_location_support_map_fragment_container, mapFragment);
fragmentTransaction.commit();
}
MapReadyCallback :
private class VenuesInLocationOnMapReadyCallback implements OnMapReadyCallback {
private static final float ZOOM_LEVEL = 10;
private final Context context;
public VenuesInLocationOnMapReadyCallback(Context context) {
this.context = context;
}
#Override
public void onMapReady(final GoogleMap map) {
// Setting up marker clusters
setUpClusterManager(getContext(), map);
// Allowing user to select My Location
map.setMyLocationEnabled(true);
// My location button handler to check the location setting enable
map.setOnMyLocationButtonClickListener(new GoogleMap.OnMyLocationButtonClickListener() {
#Override
public boolean onMyLocationButtonClick() {
promptForLocationSetting(getContext(), map);
// Returning false ensures camera try to move to user location
return false;
}
});
map.getUiSettings().setMyLocationButtonEnabled(true);
// Disabling map toolbar
map.getUiSettings().setMapToolbarEnabled(false);
}
}
Setting up Cluster Manager
private void setUpClusterManager(final Context context, GoogleMap map) {
// Declare a variable for the cluster manager.
ClusterManager<LocationMarker> mClusterManager;
// Position the map.
LatLng wocLatLng = new LatLng(28.467948, 77.080685);
map.moveCamera(CameraUpdateFactory.newLatLngZoom(wocLatLng, VenuesInLocationOnMapReadyCallback.ZOOM_LEVEL));
// Initialize the manager with the context and the map.
mClusterManager = new ClusterManager<LocationMarker>(context, map);
// Point the map's listeners at the listeners implemented by the cluster
// manager.
map.setOnCameraChangeListener(mClusterManager);
map.setOnMarkerClickListener(mClusterManager);
// Add cluster items (markers) to the cluster manager.
addLocations(mClusterManager);
// Setting custom cluster marker manager for info window adapter
map.setInfoWindowAdapter(mClusterManager.getMarkerManager());
mClusterManager.getMarkerCollection().setOnInfoWindowAdapter(new MyLocationInfoWindowAdapter());
map.setOnInfoWindowClickListener(new MyMarkerInfoWindowClickListener());
}
Adding Cluster items (markers)
private void addLocations(ClusterManager<LocationMarker> mClusterManager) {
for (int i = 0; i < venuesDetailsJsonArray.length(); i++) {
try {
JSONObject thisVenueJson = (JSONObject) venuesDetailsJsonArray.get(i);
JSONObject thisVenueLocationJson = thisVenueJson.getJSONObject("location");
LocationMarker thisVenueMarker = new LocationMarker(thisVenueLocationJson.getDouble("latitude"),
thisVenueLocationJson.getDouble("longitude"), thisVenueJson.getInt("id"));
mClusterManager.addItem(thisVenueMarker);
} catch (JSONException e) {
e.printStackTrace();
}
}
}
MyLocationInfoWIndowAdapter
private class MyLocationInfoWindowAdapter implements GoogleMap.InfoWindowAdapter {
#Override
public View getInfoWindow(Marker marker) {
return null;
}
#Override
public View getInfoContents(Marker marker) {
Log.e("getInfoContent", marker.toString());
View venueInfoWindow = ((LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE))
.inflate(R.layout.venues_map_item, null);
return venueInfoWindow;
}
}
MarkerInfoWindowClickListener
private class MyMarkerInfoWindowClickListener implements GoogleMap.OnInfoWindowClickListener {
#Override
public void onInfoWindowClick(Marker marker) {
// TODO: This is the click listener, that means all the info must be added as Tag to Marker
Intent venueDetailsDisplayIntent = new Intent(getActivity(), VenueDetailsDisplayActivity.class);
startActivity(venueDetailsDisplayIntent);
}
}
Location Marker class
public class LocationMarker implements ClusterItem{
private final LatLng mPosition;
private final int id;
public LocationMarker(double lat, double lng, int id) {
mPosition = new LatLng(lat, lng);
this.id = id;
}
#Override
public LatLng getPosition() {
return mPosition;
}
public int getId() {
return this.id;
}
}
The way that I am understanding the flow is this :
onResume --> fragmentTransaction --> VenuesInLocationOnMapReadyCallback --> setUpClusterManager --> addLocations (This adds Custom markers)
Marker Click --> MyLocationInfoWindowAdapter --> getInfoContents(Marker marker)
Marker Info Window click --> MyMarkerInfoWindowClickListener
According to my Understanding of process (I could be wrong):
I am adding an id to my custom LocationMarker when Adding markers in addLocations function.
I need to display different info in infoWindow for different markers.
InfoWindow is displayed using MyLocationInfoWindowAdapter-->getInfoContents(Marker marker)
But here is the rub, I can't find a way to figure out which marker has been clicked upon so that I can set appropriate info in InfoWindow.
On Click on opened InfoWindow I need to open a separate Activity. A/C to me InfoWindow click is handled using MyMarkerInfoWindowClickListener-->onInfoWindowClick(Marker marker) Here too I am having the same problem (I can't figure out which marker's info window has been clicked).
Related
I am working on the google map project for navigation. and creating and displaying many polygons.
But when I try to click near the marker it always detects the marker point. So I would like to know that is there any property there where I can set the marker clickable radius?
I show that is available in the JavaScript but I could not find any lead regarding Android.
Any help or reference much appreciated.
Anyway you can use a workaround:
disable marker clicks response;
detect touch on map determine nearest marker by yourself.
Of course, you need to store all of the markers in that case.
While the first point is simple:
mGoogleMap.setOnMarkerClickListener(new GoogleMap.OnMarkerClickListener() {
#Override
public boolean onMarkerClick(Marker marker) {
return true;
}
});
the second is not: even if "empty" onMarkerClick() return true markers will always intercept this event.
To get rid of that you can use approach with "touchable wrapper" like in this answer within custom MapFragment that can intercept touch events before they goes to MapFragment. When you get touch event you can get screen coordinates of touch. Then you need to find marker with minimal distance from touch location (you also need to convert marke's 'LatLng' position into screen flat coordinates via Projection.toScreenLocation() method). If founded marker within clickable radius you can process custom onMarkerClick event, if not - process polygon click.
Something like that:
Class TouchableWrapper - the core of approach:
public class TouchableWrapper extends FrameLayout {
private static final int CLICK_RADIUS_IN_PIXELS = 25;
private GoogleMap mGoogleMap;
private List<Marker> mMarkers;
public TouchableWrapper(Context context) {
super(context);
}
public void setGoogleMapAndMarkers(GoogleMap googleMap, List<Marker> markers) {
mGoogleMap = googleMap;
mMarkers = markers;
}
#Override
public boolean dispatchTouchEvent(MotionEvent event) {
if (mGoogleMap == null) return super.dispatchTouchEvent(event);
int screenX = (int) event.getX();
int screenY = (int) event.getY();
if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) {
// find marker nearest to touch position
Projection projection = mGoogleMap.getProjection();
Marker nearestMarker = null;
int minDistanceInPixels = Integer.MAX_VALUE;
for (Marker marker : mMarkers) {
Point markerScreen = projection.toScreenLocation(marker.getPosition());
int distanceToMarker = (int) Math.sqrt((screenX - markerScreen.x) * (screenX - markerScreen.x)
+ (screenY - markerScreen.y) * (screenY - markerScreen.y));
if (distanceToMarker < minDistanceInPixels) {
minDistanceInPixels = distanceToMarker;
nearestMarker = marker;
}
}
// "drop" nearest marker if it is not within radius
if (minDistanceInPixels > CLICK_RADIUS_IN_PIXELS) {
nearestMarker = null;
}
if (nearestMarker != null) {
// decide what to process (marker click or polygon click) here
Toast.makeText(getContext(),
"Clicked on marker " + nearestMarker.getTitle(), Toast.LENGTH_LONG).show();
}
}
return super.dispatchTouchEvent(event);
}
}
You can adjust clickable radius via CLICK_RADIUS_IN_PIXELS constant value.
Customized MapFragmet that uses TouchableWrapper class:
public class TouchableMapFragment extends MapFragment {
public View originalContentView;
public TouchableWrapper touchView;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
originalContentView = super.onCreateView(inflater, parent, savedInstanceState);
touchView = new TouchableWrapper(getActivity());
touchView.addView(originalContentView);
return touchView;
}
#Override
public View getView() {
return originalContentView;
}
}
'MainActivity' that uses TouchableMapFragment:
public class MainActivity extends AppCompatActivity implements OnMapReadyCallback {
private static final String TAG = MainActivity.class.getSimpleName();
static final LatLng MARKER_1 = new LatLng(50.450311, 30.523730);
static final LatLng MARKER_2 = new LatLng(50.4502, 30.52365);
private GoogleMap mGoogleMap;
private TouchableMapFragment mMapFragment;
private List<Marker> mMarkers = new ArrayList<>();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mMapFragment = (TouchableMapFragment) getFragmentManager()
.findFragmentById(R.id.map_fragment);
mMapFragment.getMapAsync(this);
}
#Override
public void onMapReady(GoogleMap googleMap) {
mGoogleMap = googleMap;
// store markers
Marker marker = mGoogleMap.addMarker(new MarkerOptions()
.position(MARKER_1)
.title("Marker 1"));
mMarkers.add(marker);
marker = mGoogleMap.addMarker(new MarkerOptions()
.position(MARKER_2)
.title("Marker 2"));
mMarkers.add(marker);
// pass stored markers to "touchable wrapper"
mMapFragment.touchView.setGoogleMapAndMarkers(mGoogleMap, mMarkers);
// disable marker click processing
mGoogleMap.setOnMarkerClickListener(new GoogleMap.OnMarkerClickListener() {
#Override
public boolean onMarkerClick(Marker marker) {
return true;
}
});
mGoogleMap.animateCamera(CameraUpdateFactory.newLatLngZoom(MARKER_1, 14));
}
}
and 'MainActivity' layout
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".activities.MainActivity">
<fragment
android:id="#+id/map_fragment"
android:name="<your.package.name>.TouchableMapFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</RelativeLayout>
Also, in your case you need to pass List<Polygon> to TouchableWrapper like List<Marker> in example above and process polygon click in its dispatchTouchEvent(MotionEvent event) too.
i want to show the location indicator when the map is loaded and add a marker whenever the map is clicked but none of these seem to work !
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final MapFragment mapFragment = (MapFragment) getSupportFragmentManager().findFragmentById(R.id.mapFragment);
assert mapFragment != null;
mapFragment.getMapAsync(new OnMapInitListener() {
#Override
public void onMapReady(MapView mapView) {
OnlineManager.getInstance().enableOnlineMapStreaming(true);
PositionManager.getInstance().startPositionUpdating();
PositionManager.getInstance().enableRemotePositioningService();
mpView=mapView;
mpView.addMapGestureListener(new MapGestureAdapter() {
#Override
public boolean onMapClicked(final MotionEvent e, final boolean isTwoFingers) {
MapMarker marker = new MapMarker(new GeoCoordinates(PositionManager.getInstance().getLastKnownPosition().getLongitudeAccuracy(),PositionManager.getInstance().getLastKnownPosition().getLatitudeAccuracy()));
mpView.addMapObject(marker);
return true;
}
});
}
#Override
public void onMapError(int error, String info) {}
});
}
You’re trying to create new Marker with getLongitudeAccuracy() and getLatitudeAccuracy(). You need to use geo coordinates!
If you want to add the marker to the position of last known gps signal you can use this code:
MapMarker marker = new MapMarker(PositionManager.getInstance().getLastKnownPosition().getCoordinates())
But as there can be no known position at that time it can result in no marker added. So be sure you have location turned on and strong signal. Based on your example it would make more sense to add marker to the position you clicked on. For that purpose use this code:
mpView.addMapGestureListener(new MapGestureAdapter() {
#Override
public boolean onMapClicked(final MotionEvent e, final boolean isTwoFingers) {
MapMarker marker = new MapMarker(mpView.geoCoordinatesFromPoint(e.getX(), e.getY()));
mpView.addMapObject(marker);
return true;
}
});
In my google maps fragment,
I used this to add my item as clusters
mClusterManager = new ClusterManager<ContactInfo>(getActivity(), googleMap);
mClusterManager.setOnClusterClickListener(this);
mClusterManager.addItem(myItem);
Now, I can manage to set onClusterClickListener by
mClusterManager.setOnClusterClickListener(this);
By using the above code, I can detect when I click those clusters,
However, when I click those seperate markers, this does not work.
How to detect those seperate markers I added to clusterManager?
setOnClusterClickListener is invoked when a Cluster is tapped.
You also need to set setOnClusterItemClickListener which is
Sets a callback that's invoked when an individual ClusterItem is
tapped. Note: For this listener to function, the ClusterManager must
be added as a click listener to the map.
And be sure to implement ClusterManager.OnClusterItemClickListener<T extends ClusterItem>
Try This custom Class.
private class PersonRenderer extends DefaultClusterRenderer<Person> {
public PersonRenderer() {
super(MainActivity.this, mMap, mClusterManager1);
}
#Override
protected void onBeforeClusterItemRendered(Person person, MarkerOptions markerOptions) {
Debug.e("call", "onBeforeClusterItemRendered");
// Draw a single person.
// Set the info window to show their name.
}
#Override
protected boolean shouldRenderAsCluster(Cluster<Person> cluster) {
Debug.e("call", "shouldRenderAsCluster");
return cluster.getSize() > 1;
}
#Override
protected void onClusterItemRendered(Person clusterItem, Marker marker) {
super.onClusterItemRendered(clusterItem, marker);
Debug.e("call", "onClusterItemRendered");
}
}
onClusterClick
#Override
public boolean onClusterClick(Cluster<Person> cluster) {
Debug.e("call", "onClusterClick");
// Show a toast with some info when the cluster is clicked.
String firstName = cluster.getItems().iterator().next().name;
// Toast.makeText(this, cluster.getSize() + " (including " + firstName + ")", Toast.LENGTH_SHORT).show();
// Zoom in the cluster. Need to create LatLngBounds and including all the cluster items
// inside of bounds, then animate to center of the bounds.
// Create the builder to collect all essential cluster items for the bounds.
LatLngBounds.Builder builder = LatLngBounds.builder();
for (ClusterItem item : cluster.getItems()) {
builder.include(item.getPosition());
}
// Get the LatLngBounds
final LatLngBounds bounds = builder.build();
// Animate camera to the bounds
try {
mMap.animateCamera(CameraUpdateFactory.newLatLngBounds(bounds, 100));
} catch (Exception e) {
e.printStackTrace();
}
return true;
}
onClusterInfoWindowClick
#Override
public void onClusterInfoWindowClick(Cluster<Person> cluster) {
Debug.e("call", "onClusterInfoWindowClick");
// Does nothing, but you could go to a list of the users.
// clickedCluster = cluster;
}
onClusterItemClick
#Override
public boolean onClusterItemClick(Person item) {
// Does nothing, but you could go into the user's profile page, for example.
Debug.e("call", "onClusterItemClick");
clickedClusterItem = item;
return false;
}
onClusterItemInfoWindowClick
#Override
public void onClusterItemInfoWindowClick(Person item) {
// Does nothing, but you could go into the user's profile page, for example.
Debug.e("call", "onClusterItemInfoWindowClick");
}
I have two fragments in my application. Ons is a map fragment and the other is a list fragment. Both of these are added to a single activity. The idea is that when a user is selected from the list, the app switches to the map fragment and displays the user. The problem is that I am getting a nullPointer exception on the GoogleMap object.
I know that the map works, because on a button press in the map fragment, I can see my current location, and I do not get 'n NullPointer.
This is the onClickListener in my ListFragment:
listEmployees.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
ChildUser childUser = children.get(position);
Tracker.showMapFragment(childUser);
}
});
This is the function that is called from my ListFragment in my parent Activity:
public static void showMapFragment(ChildUser childUser) {
mViewPager.setCurrentItem(0, true);
MapFragment.showSelectedUser(childUser);
trackingEmployee = true;
}
This is the applicable code in my MapFragment:
Initialisation:
mMapFragment = new SupportMapFragment() {
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Log.d("MAP", "on activity created");
googleMap = mMapFragment.getMap();
googleMap.getUiSettings().setZoomControlsEnabled(false);
}
};
getChildFragmentManager().beginTransaction().add(R.id.map, mMapFragment).commit();
The showSelectedUser function:
public static void showSelectedUser(ChildUser childUser) {
try {
googleMap.clear();
LatLng point = new LatLng(childUser.getChildLatitude(), childUser.getChildLongetude());
CameraUpdate cameraUpdate = CameraUpdateFactory.newLatLngZoom(point, 15);
MarkerOptions marker = new MarkerOptions();
marker.position(point);
googleMap.addMarker(marker);
googleMap.animateCamera(cameraUpdate);
} catch (NullPointerException e) {
e.printStackTrace();
}
}
I get a NullPointerException at googleMap.clear();. Please advise me as to what I can possibly be doing wrong as it works fine when I show my current location from within the MapFragment. Please let me know if you need additional information. Thank you in advance!
UPDATE
When the MapFragment is visible and I press a button, I can view the users current location in the MapFragment with the following code:
public static void showUserLocation() {
googleMap.clear();
if (Tracker.clockedInShift) {
CacheUploads cacheUpload = new CacheUploads(context);
LatLng point = cacheUpload.getMostRecentLocation();
CameraUpdate cameraUpdate = CameraUpdateFactory.newLatLngZoom(point, 15);
MarkerOptions marker = new MarkerOptions();
marker.position(point);
googleMap.addMarker(marker);
googleMap.animateCamera(cameraUpdate);
}
}
The map is linked up correctly because this code gets executed successfully. Why is the googleMap object null when accessed from the ListFragment? Please let me know if you require more information! Thanks
Looks like the answer to this question was fairly obvious. Using an inner class solved the problem. The following code solved everything:
private static GoogleMap googleMap;
public static class MyMapFragment extends SupportMapFragment {
public MyMapFragment() {
super();
}
public static MyMapFragment newInstance() {
MyMapFragment frag = new MyMapFragment();
return frag;
}
#Override
public View onCreateView(LayoutInflater arg0, ViewGroup arg1, Bundle arg2) {
View v = super.onCreateView(arg0, arg1, arg2);
googleMap = getMap();
return v;
}
}
Then just use googleMap to do whatever you want. The above code should be in your Activity/Fragment.
Currently doing a simple app that contain google map. and i would like to link to next activity when the user click on a designed coordinate but it is not working and there is no error, please help me
double latitude = 1.34503109;
double longitude = 103.94008398;
LatLng latLng = new LatLng(latitude, longitude);
gMap.setOnMapClickListener(new GoogleMap.OnMapClickListener() {
#Override
public void onMapClick(LatLng latLng) {
Intent i = new Intent(Activity_Selecting_Bus.this,
Activity_Bus_Selected.class);
startActivity(i);
}
});
If I understand correctly you have markers set up in your map and when user clicks on a marker you start another activity. The following code should work (in SupportMapFragment):
getMap().setOnMarkerClickListener(new GoogleMap.OnMarkerClickListener() {
#Override
public boolean onMarkerClick(Marker marker) {
// do the thing!
return true;
}
});
If you don't have markers and want to listen for a certain location click use this instead:
getMap().setOnMapClickListener(new GoogleMap.OnMapClickListener() {
#Override
public void onMapClick(LatLng latLng) {
// do the thing!
}
});
In this case you probably want to start the activity when the user clicks "close enough" to a certain location. I use this library http://googlemaps.github.io/android-maps-utils/ which contains method
SphericalUtil.computeDistanceBetween(LatLng from, LatLng to)
which returns distance between two LatLngs in meters.
EDIT Example:
First you define where the user has to click and which activity does that particular click launch:
private static final HashMap<LatLng, Class<? extends Activity>> sTargets = new HashMap();
static {
sTargets.put(new LatLng(34.0204989,-118.4117325), LosAngelesActivity.class);
sTargets.put(new LatLng(42.755942,-75.8092041), NewYorkActivity.class);
sTargets.put(new LatLng(42.352711,-83.099205), DetroitActivity.class);
}
private static final int RANGE_METERS = 200 * 1000; // 200 km range
Then when the user clicks on the map, you compute distance to each point. If it fits, you launch the activity.
getMap().setOnMapClickListener(new GoogleMap.OnMapClickListener() {
#Override
public void onMapClick(LatLng input) {
for(Map.Entry<LatLng,<? extends Activity>> entry : sTargets.entrySet()) {
LatLng ll = entry.getKey();
boolean inRange = SphericalUtil.computeDistanceBetween(input, ll) < RANGE_METERS;
if (inRange) {
Class<? extends Activity> cls = entry.getValue();
Intent i = new Intent(getActivity(), cls);
getActivity().startActivity(i);
break; // stop calculating after first result
}
}
}
});