The Google Maps Android API consists of a core set of classes that combine to provide mapping capabilities in Android applications. The key elements of a map are as follows:
GoogleMap
. The main class of the Google Maps Android API. This class is responsible for downloading and displaying map tiles and for displaying and responding to map controls. The GoogleMap
object is not created directly by the application but is instead created when MapView
or MapFragment
instances are created. A reference to the GoogleMap
object can be obtained within application code via a call to the getMap()
method of a MapView
, MapFragment
or SupportMapFragment
instance.MapView
. A subclass of the View
class, this class provides the view canvas onto which the map is drawn by the GoogleMap
object, allowing a map to be placed in the user interface layout of an activity.SupportFragmentMap
. A subclass of the Fragment
class, this class allows a map to be placed within a Fragment
in an Android layout.Marker
. The purpose of the Marker
class is to allow locations to be marked on a map. Markers are added to a map by obtaining a reference to the GoogleMap
object associated with a map and then making a call to the addMarker()
method of that object instance. The position of a marker is defined via Longitude and Latitude. Markers can be configured in a number of ways, including specifying a title, text and an icon. Markers may also be made to be draggable, allowing the user to move the marker to different positions on a map.Shapes
. The drawing of lines and shapes on a map is achieved through the use of the Polyline
, Polygon
and Circle
classes.UiSettings
. The UiSettings
class provides a level of control from within an application of which user interface controls appear on a map. Using this class, for example, the application can control whether or not the zoom, current location and compass controls appear on a map. This class can also be used to configure which touch screen gestures are recognized by the map.To use Google Maps you need to create a valid Google Maps API key. The key is free, you can use it with any of your applications that call the Maps API, and it supports an unlimited number of users. You can register your application on the Google Developer Console and enable the API. To do this, go to Get API Key, click GET A KEY and create/enable API for your project.
Next, to test your app you need Android emulator with Google Play services or a compatible Android device that runs Android 2.3 or higher and includes Google Play Store.
If you have Android emulator without Google Play services you will get
W/GooglePlayServicesUtil: Google Play Store is missing
To fix it, create Android emulator in Android Studio with Google APIs, for example, Android 6.0 (with Google APIs).
After that you can get :)
Google Play services out of date. Requires 9683000 but found 8489270
It means that you have installed new version in build.gradle, but device has old one. I fix it by downgrading version in build.gradle to 8+.
So, you have the key and Android emulator with Google Play services.
Place the following lines into the dependencies node of the build.gradle file.
// for real device // compile 'com.google.android.gms:play-services:9.8.0' // for emulator compile 'com.google.android.gms:play-services:8+' compile 'com.google.android.gms:play-services-location:8+'
Once you have your libraries imported, you can close build.gradle and open your AndroidManifest.xml file. Above the application node, you need to declare that the application uses OpenGL ES 2.0 and define the permissions needed by your application.
<uses-feature android:glEsVersion="0x00020000" android:required="true" /> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
INTERNET
– if we are connected to Internet or not.ACCESS_FINE_LOCATION
- to determine user’s location using GPS. It will give us precise location.ACCESS_COARSE_LOCATION
– to determine user’s location using WiFi and mobile. It will give us an approximate location.Within the application node, you need to add metadata Maps API key.
<meta-data android:name="com.google.android.geo.API_KEY" android:value="API_KEY"/>
Next, you need to extend MainActivity
by implementing OnMapReadyCallback, GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener, GoogleMap.OnMarkerClickListener, GoogleMap.OnMapLongClickListener. SupportMapFragment
is used here rather than com.google.android.gms.maps.MapFragment in order to add backwards compatibility before API 12.
ConnectionCallbacks
and OnConnectionFailedListener
are designed to monitor the state of the GoogleApiClient
, which is used in this application for getting the user's current location.OnInfoWindowClickListener
is triggered when the user clicks on the info window that pops up over a marker on the map.OnMapLongClickListener
and OnMapClickListener
are triggered when the user either taps or holds down on a portion of the map.OnMarkerClickListener
is called when the user clicks on a marker on the map, which typically also displays the info window for that marker.MapFragment
is a map component in an app. This fragment is the simplest way to place a map in an application. It's a wrapper around a MapView of a map to automatically handle the necessary life cycle needs. Being a fragment, this component can be added to an activity's layout file simply with the XML below. Open activity_main.xml from your resources folder and change it so that it includes the fragment as a view.
<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=".MainActivity"> <fragment android:id="@+id/map" android:name="com.google.android.gms.maps.SupportMapFragment" android:layout_width="match_parent" android:layout_height="match_parent"/> </RelativeLayout>
Returning to our MainActivity
class, you need to define some global values at the top of the class for use in your application.
private GoogleMap map; private GoogleApiClient googleApiClient; private Location currentLocation;
Here googleApiClient
and currentLocation
are used for getting the user's location for initializing the map camera. Next, you need to create your GoogleApiClient
and initiate LocationServices
in order to get your user's current location.
In the moveToMyLocation
method, you initialize the camera and some basic map properties. You start by creating a CameraPosition
object through the CameraPosition.Builder
, with a target set for the latitude and longitude of your user and a set zoom level.
setMyLocationEnabled
adds a button to the top right corner of the MapFragment
that automatically moves the camera to your user's location when pressed.
setZoomControlsEnabled
adds + and - buttons in the lower right corner, allowing the user to change the map zoom level without having to use gestures. There's a few more interesting things that you can set using UiSettings
, such as adding a compass or disabling gestures, which you can find in the UiSettings reference.
Alternatively, the map:uiZoomControls
property may be set within the map element of the XML resource file: map:uiZoomControls="false"
.
The compass may be displayed either via a call to the setCompassEnabled()
method of the UiSettings
instance, or through XML resources using the map:uiCompass
property.
onMarkerClick
method creates a generic red marker where the user has tapped. Additional options, such as setting a marker as draggable, can be set through the MarkerOptions
object. You can find additional attributes in the official MarkerOptions reference.
Full code of MainActivity
public class MainActivity extends FragmentActivity implements OnMapReadyCallback, GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener, GoogleMap.OnMarkerClickListener, GoogleMap.OnMapLongClickListener { private GoogleMap map; private GoogleApiClient googleApiClient; private Location currentLocation; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager() .findFragmentById(R.id.map); mapFragment.getMapAsync(this); googleApiClient = new GoogleApiClient.Builder(this) .addConnectionCallbacks(this) .addOnConnectionFailedListener(this) .addApi(LocationServices.API) .build(); } @Override public void onConnected(@Nullable Bundle bundle) { if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_COARSE_LOCATION ) != PackageManager.PERMISSION_GRANTED) { Toast.makeText(MainActivity.this, "Please allow ACCESS_COARSE_LOCATION persmission.", Toast.LENGTH_LONG).show(); return; } currentLocation = LocationServices.FusedLocationApi.getLastLocation(googleApiClient); map.setMyLocationEnabled(true); map.getUiSettings().setZoomControlsEnabled(true); map.setOnMyLocationButtonClickListener(new GoogleMap.OnMyLocationButtonClickListener() { @Override public boolean onMyLocationButtonClick() { moveToMyLocation(); return false; } }); moveToMyLocation(); } @Override public void onConnectionSuspended(int i) {} @Override public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {} @Override public void onMapLongClick(LatLng latLng) { MarkerOptions options = new MarkerOptions().position(latLng); options.title(getAddressFromLatLng(latLng)); options.icon(BitmapDescriptorFactory.defaultMarker()); map.addMarker(options); } @Override public void onMapReady(GoogleMap googleMap) { map = googleMap; LatLng latLng = new LatLng(49.2400, 28.4811); MarkerOptions marker = new MarkerOptions() .position(latLng) .title("Vinnytsia") .snippet("My hometown!"); map.addMarker(marker); map.moveCamera(CameraUpdateFactory.newLatLngZoom(latLng, 18)); map.setOnMarkerClickListener(this); map.setOnMapLongClickListener(this); } private String getAddressFromLatLng(LatLng latLng) { Geocoder geocoder = new Geocoder(MainActivity.this); String address = ""; try { address = geocoder.getFromLocation(latLng.latitude, latLng.longitude, 1) .get(0).getAddressLine(0); } catch (IOException e ) { } return address; } @Override public boolean onMarkerClick(Marker marker) { marker.showInfoWindow(); return true; } @Override protected void onStart() { super.onStart(); googleApiClient.connect(); } @Override protected void onStop() { super.onStop(); if( googleApiClient != null && googleApiClient.isConnected() ) { googleApiClient.disconnect(); } } public void moveToMyLocation() { if (currentLocation != null) { CameraPosition position = CameraPosition.builder() .target(new LatLng(currentLocation.getLatitude(), currentLocation.getLongitude())) .zoom(16) .build(); map.animateCamera(CameraUpdateFactory.newCameraPosition(position), null); } else { Toast.makeText(this, "Can not get user location!", Toast.LENGTH_LONG).show(); } } }
Don’t like the default Android pins? You can also create a marker with a custom icon as the pin. Go back to onMapLongClick()
and add the following line of code after the MarkerOptions
instantiation:
options.icon(BitmapDescriptorFactory.fromBitmap(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_user_location)));
Download custom pins named ic_user_location
from this link and unzip it to res/mipmap.
You can also change the type of the MAP dynamically by making a call to the setMapType()
method of the corresponding GoogleMap
object, passing through one of the following values:
GoogleMap.MAP_TYPE_NONE
. An empty grid with no mapping tiles displayed.GoogleMap.MAP_TYPE_NORMAL
. The standard view consisting of the classic road map.GoogleMap.MAP_TYPE_SATELLITE
. Displays the satellite imagery of the map region.GoogleMap.MAP_TYPE_HYBRID
. Displays satellite imagery with the road maps superimposed.GoogleMap.MAP_TYPE_TERRAIN
. Displays topographical information such as contour lines and colors.Handling map gesture interaction
The Google Maps Android API is capable of responding to a number of different user interactions. These interactions can be used to change the area of the map displayed, the zoom level and even the angle of view (such that a 3D representation of the map area is displayed for certain cities).
Map zooming gestures
Support for gestures relating to zooming in and out of a map may be enabled or disabled using the setZoomControlsEnabled()
method of the UiSettings
object associated with the GoogleMap
instance. For example, the following code enables zoom gestures for our example map:
import com.google.android.gms.maps.UiSettings; ... UiSettings mapSettings; mapSettings = mMap.getUiSettings();
The same result can be achieved within an XML resource file by setting the map:uiZoomGestures
property to true
or false
. When enabled, zooming will occur when the user makes pinching gestures on the screen. Similarly, a double tap will zoom in whilst a two finger tap will zoom out. One finger zooming gestures, on the other hand, are performed by tapping twice but not releasing the second tap and then sliding the finger up and down on the screen to zoom in and out respectively.
Map scrolling/panning gestures
A scrolling, or panning gesture allows the user to move around the map by dragging the map around the screen with a single finger motion. Scrolling gestures may be enabled within code via a call to the setScrollGesturesEnabled()
method of the UiSettings
instance:
UiSettings mapSettings; mapSettings = mMap.getUiSettings(); mapSettings.setScrollGesturesEnabled(true);
Alternatively, scrolling on a map instance may be enabled in an XML resource layout file using the map:uiScrollGestures
property.
Map tilt gestures
Tilt gestures allow the user to tilt the angle of projection of the map by placing two fingers on the screen and moving them up and down to adjust the tilt angle. Tilt gestures may be enabled or disabled via a call to the setTiltGesturesEnabled()
method of the UiSettings
instance, for example:
UiSettings mapSettings; mapSettings = mMap.getUiSettings(); mapSettings.setTiltGesturesEnabled(true);
Tilt gestures may also be enabled and disabled using the map:uiTiltGestures
property in an XML layout resource file.
Map rotation gestures
By placing two fingers on the screen and rotating then in a circular motion, the user may rotate the orientation of a map when map rotation gestures are enabled. This gesture support is enabled and disabled in code via a call to the setRotateGesturesEnabled()
method of the UiSettings
instance, for example:
UiSettings mapSettings; mapSettings = mMap.getUiSettings(); mapSettings.setRotateGesturesEnabled(true);
Rotation gestures may also be enabled or disabled using the map:uiRotateGestures
property in an XML layout resource file.
My location button
My location button will be used to move map to your current location. This button can be shown / hidden by calling setMyLocationButtonEnabled(boolean)
function.
map.getUiSettings().setMyLocationButtonEnabled(true);
Toolbar buttons
By default, a toolbar appears at the bottom right of the map when a user taps a marker. The toolbar gives the user quick access to the Google Maps mobile app. You can enable and disable the toolbar by calling setMapToolbarEnabled(boolean)
.
map.getUiSettings().setMapToolbarEnabled(true);
Drawing on the map
The GoogleMap
object has a set of methods that make it easy to draw shapes and place images onto the map. To draw a simple circle, you only need to create a CircleOptions
object, set a radius and center location, and define the stroke/fill colors and size.
Once you have a CircleOptions
object, you can call addCircle
to draw the defined circle on top of the map. Just like when placing markers, objects that are drawn on the map return an object of the drawn item type so it can be referenced later if needed.
private void drawCircle(LatLng loc) { CircleOptions options = new CircleOptions(); options.center(loc); // radius in meters options.radius(10); options.fillColor(R.color.colorPrimary); options.strokeColor(R.color.colorPrimaryDark); options.strokeWidth(10); map.addCircle(options); }
I added drawCircle()
to onMapReady()
method after map.setOnMapLongClickListener(this);
.
To draw a different closed-off shape, you can take multiple LatLng points and create a PolygonOptions
object. As you can see below, PolygonOptions
are created in a similar fashion to CircleOptions
. Instead of using a center and radius method, you use add with a list of points. You can then call addPolygon
to draw the shape. For this example, you simply draw a triangle onto the map.
private void drawPolygon(LatLng startPoint) { LatLng point2 = new LatLng(startPoint.latitude + .001, startPoint.longitude); LatLng point3 = new LatLng(startPoint.latitude, startPoint.longitude + .001); PolygonOptions options = new PolygonOptions(); options.add(startPoint, point2, point3); options.fillColor(R.color.colorPrimary); options.strokeColor(R.color.colorPrimaryDark); options.strokeWidth(10); map.addPolygon(options); }
Marker Clustering
By clustering your markers, you can put a large number of markers on a map without making the map hard to read.
Clustering on Google Maps is really simple because there is an awesome library that helps us do this. The Google Maps Android API Utility Library.
Add the following dependency to your app's Gradle build file:
dependencies { compile 'com.google.maps.android:android-maps-utils:0.5+' }
The ClusterManager
helps you manage multiple markers at different zoom levels. This means you can put a large number of markers on a map without making the map hard to read. When a user views the map at a high zoom level, the individual markers show on the map. When the user zooms out to a lower zoom level, the markers gather together into clusters, to make viewing the map easier.
The first thing you need to do is create a new model object that implements the ClusterItem
interface. This model needs to implement the getPosition
, getTitle
, getSnippet
methods from the ClusterItem
.
public class PinCluster implements ClusterItem { private LatLng position; private String title; private String snippet; public PinCluster(LatLng latLng, String title, String snippet) { this.position = latLng; this.title = title; this.snippet = snippet; } @Override public LatLng getPosition() { return position; } @Override public String getTitle() { return title; } @Override public String getSnippet() { return snippet; } }
When you initialize your map, you need to create a ClusterManager
, associate it with your GoogleMap
, and add your LatLng
positions as ClusterMarkerLocations
to the ClusterManager
for the utility to know what to cluster. Take a look at the following implementation to better understand how this works.
public class MainActivity extends AppCompatActivity implements OnMapReadyCallback, GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener { private GoogleMap map; private GoogleApiClient googleApiClient; private List<PinCluster> pins; private ClusterManager<PinCluster> clusterManager; private Activity activity = MainActivity.this; @Override public void onMapReady(GoogleMap googleMap) { map = googleMap; pins = new ArrayList<>(); clusterManager = new ClusterManager<PinCluster>(activity, map); map.setOnMapLoadedCallback(new GoogleMap.OnMapLoadedCallback() { @Override public void onMapLoaded() { LatLng centerLatLng = new LatLng(49.2400, 28.4811); map.moveCamera(CameraUpdateFactory.newLatLngZoom(centerLatLng, 18)); pins.add(new PinCluster(centerLatLng, "My town", "Vinnica")); LatLngBounds.Builder builder = new LatLngBounds.Builder(); builder.include(centerLatLng); for (int i = 0; i < 20; i++) { LatLng rndLatLng = randomMarker(centerLatLng); builder.include(rndLatLng); pins.add(new PinCluster(rndLatLng, "Title " + i, "Snippet " + i)); } LatLngBounds bounds = builder.build(); int width = getResources().getDisplayMetrics().widthPixels; int padding = (int) (width * 0.05); CameraUpdate cu = CameraUpdateFactory.newLatLngBounds(bounds, padding); map.moveCamera(cu); map.setOnCameraIdleListener(clusterManager); map.setOnMarkerClickListener(clusterManager); clusterManager.addItems(pins); } }); } }
At this point, you should be able to render default markers that cluster.
In order use customized markers you will need to extend DefaultClusterRenderer
and build your own renderer class:
public class PinRenderer extends DefaultClusterRenderer<PinCluster> { Context context; int pinWith = 100; int pinHeight = 110; public PinRenderer(Context context, ClusterManager<PinCluster> clusterManager, GoogleMap map) { super(context, map, clusterManager); this.context = context; } @Override protected void onBeforeClusterItemRendered(PinCluster myItem, MarkerOptions markerOptions) { // Customize the marker here markerOptions.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_ORANGE)); } @Override protected void onBeforeClusterRendered(Cluster<PinCluster> cluster, MarkerOptions markerOptions) { // Customize the cluster here IconGenerator iconGenerator = new IconGenerator(context.getApplicationContext()); iconGenerator.setBackground(ContextCompat.getDrawable(context, R.drawable.pin)); LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); View iconView = inflater.inflate(R.layout.map_cluster_view, null, false); iconGenerator.setContentView(iconView); Bitmap bg = iconGenerator.makeIcon(String.valueOf(cluster.getSize())); Bitmap resizedBitmap = Bitmap.createScaledBitmap(bg, pinWith, pinHeight, false); markerOptions.icon(BitmapDescriptorFactory.fromBitmap(resizedBitmap)); } }
Following is map_cluster_view.xml.
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_centerHorizontal="true"> <TextView android:id="@+id/amu_text" android:layout_width="65dp" android:layout_height="60dp" android:textSize="23sp" android:text="0" android:textColor="#FF3C6D" android:gravity="center" /> </LinearLayout>
Result
According to google-maps-clustering Google Maps Android API Utility Library is very slow for large amounts of markers, which causes skipping frames and ANRs.
How to disable gestures and events in map
public void onMapReady(GoogleMap googleMap) { map = googleMap; map.getUiSettings().setAllGesturesEnabled(false); map.setOnMarkerClickListener(new GoogleMap.OnMarkerClickListener() { @Override public boolean onMarkerClick(Marker marker) { return true; } }); }
How to set bounds in map
public void onMapReady(GoogleMap googleMap) { map = googleMap; map.setOnMapLoadedCallback(new GoogleMap.OnMapLoadedCallback() { @Override public void onMapLoaded() { final LatLngBounds.Builder builder = new LatLngBounds.Builder(); LatLng latLng1 = new LatLng(49.442403, 28.147860); builder.include(latLng1); map.addMarker(new MarkerOptions().position(latLng1)); LatLng latLng2 = new LatLng(49.677570, 27.683688); builder.include(latLng2); map.addMarker(new MarkerOptions().position(latLng2)); LatLng latLng3 = new LatLng(49.996429, 28.741122); builder.include(latLng3); map.addMarker(new MarkerOptions().position(latLng3)); LatLngBounds bounds = builder.build(); int width = getResources().getDisplayMetrics().widthPixels; int padding = (int) (width * 0.05); CameraUpdate cu = CameraUpdateFactory.newLatLngBounds(bounds, padding); map.moveCamera(cu); } }); }
How to generate random marker nearby another marker
private void randomMarker(LatLng startLatLng) { Random rand = new Random(); float minF = 0.0010f; float maxF = 0.0099f; float incLat = rand.nextFloat() * (maxF - minF) + minF; float incLng = rand.nextFloat() * (maxF - minF) + minF; int minI = -1; int maxI = 1; int range = maxI - minI + 1; int signLat = rand.nextInt(range) + minI; int signLng = rand.nextInt(range) + minI; signLat = signLat == 0 ? 1 : signLat; signLng = signLng == 0 ? 1 : signLng; incLat = incLat * signLat; incLng = incLng * signLng; LatLng ll = new LatLng(startLatLng.latitude + incLat, startLatLng.longitude + incLng); MarkerOptions options = new MarkerOptions() .position(ll) .icon(BitmapDescriptorFactory.defaultMarker()) .title("Marker"); map.addMarker(options); }
How to add marker on Map click
@Override public void onMapReady(GoogleMap googleMap) { map = googleMap; LatLng latLng = new LatLng(49.2349067, 28.399594); map.moveCamera(CameraUpdateFactory.newLatLngZoom(latLng, 13)); map.setOnMapClickListener(new GoogleMap.OnMapClickListener() { @Override public void onMapClick(LatLng latLng) { Geocoder geocoder = new Geocoder(activity); List<Address> list; try { list = geocoder.getFromLocation(latLng.latitude, latLng.longitude, 1); } catch (IOException e) { return; } Address address = list.get(0); if (marker != null) { marker.remove(); } MarkerOptions options = new MarkerOptions() .title(address.getLocality()) .position(new LatLng(latLng.latitude, latLng.longitude)); marker = map.addMarker(options); } }); }
How to sort list of LatLng objects by distance to some location
Sort list of Person
items
ArrayList<Person> items = getPersons(); LatLng startLocation = new LatLng(49.2400, 28.4811); Collections.sort(items, new Comparator<Person>(){ public int compare(Person o1, Person o2){ float distance1 = o1.getDistance(startLocation); float distance2 = o2.getDistance(startLocation); if(distance1 == distance2) return 0; return distance1 < distance2 ? -1 : 1; } });
Snippet of getDistance
method of Person
object
public float getDistance(LatLng to) { float distance = 0; if (to != null) { Location loc1 = new Location(""); loc1.setLatitude(lat); loc1.setLongitude(lng); Location loc2 = new Location(""); loc2.setLatitude(to.latitude); loc2.setLongitude(to.longitude); distance = loc1.distanceTo(loc2); } return distance; }
How to change the position of a marker
MarkerOptions markerOptions = new MarkerOptions().position(new LatLng(50,6))); Marker marker = map.addMarker(markerOptions); marker.setPosition(new LatLng(50,5));
How to create custom marker
By default map marker color will be red. Google maps provides some set of predefined colored icons for the marker.
marker.setIcon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_CYAN)); marker.setIcon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_BLUE)); marker.setIcon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_GREEN));
Apart from maps native marker icons, you can use own image to show as a marker. You can load the icon from any kind of supported sources.
fromAsset(String assetName)
- loading from assets folder.fromBitmap(Bitmap image)
- loading bitmap image.fromFile(String path)
- loading from file.fromResource(int resourceId)
- loading from drawable resource.public BitmapDescriptor getMapIcon(int resID, int widthPx, int heightPx) { //int resID = getResources().getIdentifier(iconName, "drawable", getPackageName()); Bitmap imageBitmap = BitmapFactory.decodeResource(getResources(), resID); Bitmap resizedBitmap = Bitmap.createScaledBitmap(imageBitmap, widthPx, heightPx, false); return BitmapDescriptorFactory.fromBitmap(resizedBitmap); } MarkerOptions markerOptions = new MarkerOptions() .position(new LatLng(50,5)) .title("Marker") .icon(getMapIcon(R.drawable.girl1, 150, 150)); map.addMarker(markerOptions);
How to create custom marker from view
map.addMarker(new MarkerOptions() .position(latLng) .icon(BitmapDescriptorFactory .fromBitmap(getMarkerBitmapFromView(R.drawable.avatar)))); ... private Bitmap getMarkerBitmapFromView(@DrawableRes int resId) { View v = ((LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE)) .inflate(R.layout.view_custom_marker, null); ImageView imageView = (ImageView) v.findViewById(R.id.profile_image); imageView.setImageResource(resId); v.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); v.layout(0, 0, v.getMeasuredWidth(), v.getMeasuredHeight()); v.buildDrawingCache(); Bitmap bmp = Bitmap.createBitmap(v.getMeasuredWidth(), v.getMeasuredHeight(), Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bmp); canvas.drawColor(Color.WHITE, PorterDuff.Mode.SRC_IN); Drawable drawable = v.getBackground(); if (drawable != null) drawable.draw(canvas); v.draw(canvas); return bmp; }
How to rotate map
Bearing - The orientation of the map in degrees measured in a clockwise direction from North.
CameraPosition cameraPosition = map.getCameraPosition(); float bearing = cameraPosition.bearing; bearing += 90; map.moveCamera( CameraUpdateFactory.newCameraPosition(new CameraPosition.Builder() .target(target) .zoom(zoom) .bearing(bearing) .build()) );
How to check if two markers intersect
Projection projection = map.getProjection(); float scale = getResources().getDisplayMetrics().density; // calc size of first marker Point screenPositionMarker1 = projection.toScreenLocation(markerLatLng1); int widthMarker1 = (int) (210 * scale); int heightMarker1 = (int) (112 * scale); int halfMarker1 = widthMarker1 / 2; int leftMarker1 = screenPositionMarker1.x - halfMarker1; int topMarker1 = screenPositionMarker1.y - heightMarker1; int rightMarker1 = screenPositionMarker1.x + halfMarker1; int bottomMarker1 = screenPositionMarker1.y; Rect rect1 = new Rect(leftMarker1, topMarker1, rightMarker1, bottomMarker1); // calc size of second marker Point screenPositionMarker2 = projection.toScreenLocation(markerLatLng2); int pinMarkerSize = getResources().getInteger(R.integer.pinMarkerSize); int halfMarker2 = pinMarkerSize / 2; int leftMarker2 = screenPositionMarker2.x - halfMarker2; int topMarker2 = screenPositionMarker2.y - pinMarkerSize; int rightMarker2 = screenPositionMarker2.x + halfMarker2; int bottomMarker2 = screenPositionMarker2.y; Rect rect2 = new Rect(leftMarker2, topMarker2, rightMarker2, bottomMarker2); boolean isIntersect = rect1.intersect(rect2); // visualize intersection // ImageView imgRect = new ImageView(activity); // imgRect.setBackgroundColor(Color.YELLOW); // RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(pinMarkerSize, // pinMarkerSize); // params.leftMargin = leftMarker2; // params.topMargin = topMarker2; // rlContent.addView(imgRect, params); // // ImageView imgCenter = new ImageView(activity); // imgCenter.setBackgroundColor(Color.RED); // RelativeLayout.LayoutParams params2 = new RelativeLayout.LayoutParams(20, 20); // params2.leftMargin = screenPositionMarker2.x; // params2.topMargin = screenPositionMarker2.y; // rlContent.addView(imgCenter, params2);
Moving Camera to a Location with Animation
You may want to move camera to a particular position. Google maps provides set of functions to achieve this.
map.animateCamera(CameraUpdateFactory.newLatLngZoom(latLng, 17));
How to calculate distance between two location
First approach
Location loc1 = new Location("One"); loc1.setLatitude(lat1); loc1.setLongitude(lon1); Location loc2 = new Location("Two"); loc2.setLatitude(lat2); loc2.setLongitude(lon2); float distanceInMeters = loc1.distanceTo(loc2);
Second approach
float[] distanceInMeters = new float[1]; Location.distanceBetween(lat1, lon1, lat2, lon2, distanceInMeters);
Both approaches return the same result.
How to create custom InfoWindow
First, you should define new InfoWindowAdapter
map.setInfoWindowAdapter(new GoogleMap.InfoWindowAdapter() { @Override public View getInfoWindow(Marker arg0) { return null; } @Override public View getInfoContents(Marker arg0) { View v = null; try { v = getLayoutInflater().inflate(R.layout.custom_infowindow, null); TextView tvTitle = (TextView) v.findViewById(R.id.tvTitle); tvTitle.setText(arg0.getTitle()); } catch (Exception e) { e.printStackTrace(); } return v; } });
Second, if you want some action on click you should define OnInfoWindowClickListener
map.setOnInfoWindowClickListener(new GoogleMap.OnInfoWindowClickListener() { @Override public void onInfoWindowClick(Marker marker) { Toast.makeText(activity, "Selected", Toast.LENGTH_SHORT).show(); } });
Third, define custom layout
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/tvTitle" android:layout_width="match_parent" android:layout_height="wrap_content" /> <ImageView android:src="@mipmap/ic_launcher" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout>
If you want InfoWindow
with custom action you can use InteractiveInfoWindowAndroid.
How to load image to InfoWindow via Glide 4
map.setInfoWindowAdapter(new GoogleMap.InfoWindowAdapter() { Marker marker; @Override public View getInfoWindow(Marker arg0) { marker = arg0; return null; } @Override public View getInfoContents(Marker arg0) { View v = null; try { v = getLayoutInflater().inflate(R.layout.infowindow, null); TextView tvTitle = (TextView) v.findViewById(R.id.tvTitle); ImageView iv = (ImageView) v.findViewById(R.id.iv); tvTitle.setText(arg0.getTitle()); SimpleTarget bs = new SimpleTarget<Bitmap>(){ @Override public void onResourceReady(Bitmap resource, Transition<? super Bitmap> transition) { iv.setImageBitmap(resource); if (marker != null && marker.isInfoWindowShown()) { marker.hideInfoWindow(); marker.showInfoWindow(); } } }; Glide.with(activity.getApplicationContext()).asBitmap() .load(URL) .into(bs); } catch (Exception e) { e.printStackTrace(); } return v; } });
Google Maps Directions API
The Google Maps Directions API is a service that calculates directions between locations using an HTTP request.
With the Directions API, you can:
The API returns the most efficient routes when calculating directions.
Note: This service is not designed to respond in real time to user input.
Before you start developing with the Directions API, review the authentication requirements (you need an API key) and the API usage limits.
You can look at response from Directions API using following url
https://maps.googleapis.com/maps/api/directions/json ?origin=49.2400,28.4811 &destination=49.22833,28.39866 &key=API_KEY
So, the Google Maps Directions API is a web service that Google provides us to get information about a route. A route is getting from an initial location to a specific destination. This web service provides us with information for different transport modes, waypoints and traveling times.
For Android we can use the Java client library.
To get this library into our app, we need to add the following to our build.gradle file.
implementation 'com.google.maps:google-maps-services:0.2.6' implementation 'com.google.maps.android:android-maps-utils:0.5+'
Before we start looking at some code, we also need to get a Google Directions API Key.
Now that we have all the configuration out the way, we can start implementing this API in our Android app.
Firstly, we will need to create a GeoApiContext
object. This object is where we will set our API key.
GeoApiContext geoApiContext = new GeoApiContext.Builder() .apiKey(KEY) .build();
Once we have our GeoApiContext
, we can now use the DirectionsAPI
class (which has a static method called newRequest
) to request direction information. This newRequest
method takes a GeoApiContext
as an argument, which then returns us a DirectionsApiRequest
.
On the DirectionsApiRequest
object we have some methods that we need to set before we make this request. First we need to set our TravelMode
, which can be Driving, Bicycling, Walking or Transit.
We then need to set our origin and our destination points.
Lastly, we can call the departureTime
method, so that we know the travel times that are returned are based on the departure time we provided. This method takes a simple DateTime
object.
Now we can call the await method on the DirectionsApiRequest
. This will make a synchronous call to the web service and return us a DirectionsResult
object.
public class MainActivity extends AppCompatActivity implements OnMapReadyCallback, GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener { private AppCompatActivity activity = MainActivity.this; private String TAG = MainActivity.class.getSimpleName(); private GoogleMap map; private GoogleApiClient googleApiClient; Polyline polyline; TextView tvDuration; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager() .findFragmentById(R.id.map); mapFragment.getMapAsync(this); googleApiClient = new GoogleApiClient.Builder(this) .addConnectionCallbacks(this) .addOnConnectionFailedListener(this) .addApi(LocationServices.API) .build(); tvDuration = findViewById(R.id.tvDuration); } @Override public void onConnected(@Nullable Bundle bundle) { if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_COARSE_LOCATION ) != PackageManager.PERMISSION_GRANTED) { Toast.makeText(MainActivity.this, "Please allow ACCESS_COARSE_LOCATION persmission.", Toast.LENGTH_LONG).show(); return; } map.getUiSettings().setZoomControlsEnabled(true); } @Override public void onConnectionSuspended(int i) {} @Override public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {} @Override public void onMapReady(GoogleMap googleMap) { map = googleMap; transportMode(TravelMode.DRIVING); } public void transportDriving(View v) { transportMode(TravelMode.DRIVING); } public void transportWalking(View v) { transportMode(TravelMode.WALKING); } public void transportBicycling(View v) { transportMode(TravelMode.BICYCLING); } public void transportPublic(View v) { transportMode(TravelMode.TRANSIT); } private void transportMode(TravelMode mode) { GeoApiContext geoApiContext = new GeoApiContext.Builder() .apiKey(KEY) .build(); String ll1 = "49.2400,28.4811"; String ll2 = "49.22833,28.39866"; DateTime now = new DateTime(); try { DirectionsResult result = DirectionsApi.newRequest(geoApiContext) .mode(mode).origin(ll1) .destination(ll2) .departureTime(now) .await(); if (result.routes.length > 0) { String distance = result.routes[0].legs[0].distance.humanReadable; String duration = result.routes[0].legs[0].duration.humanReadable; tvDuration.setText(String.format("%s / %s", distance, duration)); if (polyline != null) polyline.remove(); List<LatLng> decodedPath = PolyUtil.decode(result.routes[0].overviewPolyline.getEncodedPath()); PolylineOptions polylineOptions = new PolylineOptions().width(8) .color(ContextCompat.getColor(getApplicationContext(), R.color.route)).addAll(decodedPath); polyline = map.addPolyline(polylineOptions); BitmapDescriptor iconStart = BitmapDescriptorFactory .fromResource(R.drawable.nav_start_icon); BitmapDescriptor iconDestination = BitmapDescriptorFactory .fromResource(R.drawable.nav_destination_icon); DirectionsLeg firstLeg = result.routes[0].legs[0]; map.addMarker(new MarkerOptions().icon(iconStart) .anchor(0.5f, 0.5f).position(new LatLng(firstLeg.startLocation.lat, firstLeg.startLocation.lng)) .title(firstLeg.startAddress)); map.addMarker(new MarkerOptions().icon(iconDestination) .anchor(0.5f, 0.9f).position(new LatLng(firstLeg.endLocation.lat, firstLeg.endLocation.lng)) .title(firstLeg.startAddress)); LatLngBounds.Builder builder = new LatLngBounds.Builder(); LatLng b1 = new LatLng(result.routes[0].bounds.northeast.lat, result.routes[0].bounds.northeast.lng); builder.include(b1); LatLng b2 = new LatLng(result.routes[0].bounds.southwest.lat, result.routes[0].bounds.southwest.lng); builder.include(b2); LatLngBounds bounds = builder.build(); int width = getResources().getDisplayMetrics().widthPixels; int padding = (int) (width * 0.05); CameraUpdate cu = CameraUpdateFactory.newLatLngBounds(bounds, padding); map.moveCamera(cu); } else { Toast.makeText(activity, "Google direction can't build route for your direction!", Toast.LENGTH_SHORT).show(); } } catch (ApiException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } @Override protected void onStart() { super.onStart(); googleApiClient.connect(); } @Override protected void onStop() { super.onStop(); if( googleApiClient != null && googleApiClient.isConnected() ) { googleApiClient.disconnect(); } } }
Result
How to pass navigation data to Google Map
Using Maps URLs, you can build a universal, cross-platform URL to launch Google Maps and perform searches, get directions and navigation, and display map views and panoramic images. The URL syntax is the same regardless of the platform in use.
See example in browser.
try { String origin = "49.2346493,28.3295511"; String destination = "50.4021368,30.2525068"; String travelMode = "driving"; String uri = String.format("https://www.google.com/maps/dir/? api=1&origin=%s&destination=%s&travelmode=%s&dir_action=navigate", origin, destination, travelMode); Intent intent = new Intent(android.content.Intent.ACTION_VIEW, Uri.parse(uri)); intent.setClassName("com.google.android.apps.maps", "com.google.android.maps.MapsActivity"); startActivity(intent); } catch (ActivityNotFoundException e) { try { activity.startActivity(new Intent( Intent.ACTION_VIEW, Uri.parse("market://details?id=com.google.android.apps.maps"))); } catch (android.content.ActivityNotFoundException anfe) { activity.startActivity(new Intent( Intent.ACTION_VIEW, Uri.parse("http://play.google.com/store/apps/details?id=com.google.android.apps.maps"))); } e.printStackTrace(); }
Google maps example in Kotlin
class MainActivity : AppCompatActivity(), OnMapReadyCallback{ private var map: GoogleMap? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val mapFragment = supportFragmentManager.findFragmentById(R.id.map) as SupportMapFragment mapFragment.getMapAsync(this) } override fun onMapReady(googleMap: GoogleMap) { map = googleMap val ll = LatLng(23.777176, 90.399452) map?.let { it.addMarker(MarkerOptions().position(ll).title("Marker")) it.moveCamera(CameraUpdateFactory.newLatLng(ll)) } } }
Useful links