GetMeThere
GetMeThere consists in a set of views and tools to help you build offline applications to guide a user to get to a specific location on the globe.
compile 'com.arecmetafora:getMeThere:1.0.0'
https://play.google.com/store/apps/details?id=com.arecmetafora.getmethere
UI Components
GetMeThere provides three out-of-the-box UI components to work with offline navigation and georeference: Map
, Compass
and AugmentedRealityCompass
.
A Map
is a tiny and lightweight offline map of a specific location’s neighborhood. It helps users to find their way home by providing a simple guidance through a small offline map. It is very useful when the user needs to get an overview about his surroundings and can be used in a variety of situations, for instance, finding his hotel, points of interests in a tracking challenge, etc.
You can declare a Map
via XML as shown in the example below:
<com.arecmetafora.getmethere.Map
xmlns:lib="http://schemas.android.com/apk/res-auto"
android:id="@+id/map"
android:layout_width="match_parent"
android:layout_height="300dp"
lib:locationIcon="@drawable/map_location_pin"/>
After declaring your Map
view, you still need to set an offline map which contains the map image that will be drawn. How to do that will be described later, in the Offline Maps section.
A Compass
is like a common compass used for navigation. However, instead of pointing to magnetic north, it points to the location that the user is heading to. Also, in addition to the pointer, it provides the distance between the current user’s location and the point of interest, in meters.
Its declaration in XML can be done in the following way:
<com.arecmetafora.getmethere.Compass
xmlns:lib="http://schemas.android.com/apk/res-auto"
android:id="@+id/compass"
android:layout_width="match_parent"
android:layout_height="300dp"
lib:arcWidth="@dimen/compass_arc_width"
lib:arcColor="@color/compass_arc"
lib:textSize="@dimen/compass_distance_text_size"
lib:textColor="@color/compass_distance"
lib:pointer="@drawable/compass_pointer"
lib:locationIcon="@drawable/compass_location"/>
An AugmentedRealityCompass
is similar to Compass
, but uses the camera to project the location on the screen, simulating an augmented reality scenario.
Its declaration in XML can be done in the following way:
<com.arecmetafora.getmethere.AugmentedRealityCompass
xmlns:lib="http://schemas.android.com/apk/res-auto"
android:id="@+id/ar_compass"
android:layout_width="match_parent"
android:layout_height="300dp"
lib:textSize="@dimen/ar_compass_text_size"
lib:textColor="@color/ar_compass_distance"
lib:pointer="@drawable/ar_compass_pointer"
lib:locationIcon="@drawable/ar_compass_location"/>
The custom styles (under lib
namespace) are optional.
Compass Sensors
To get the UI components working you need to bind them to a CompassSensor
. This class is responsible for tracking a location, providing callbacks with the current user’s location, the distance in meters between the current location and the point of interest, the north azimuth and the azimuth between the user’s orientation and the location, etc.
You can bind a CompassSensor
to any object which class implements the CompassSensor.CompassSensorListener
interface.
The bind of a CompassSensor
to a UI component can be done in the onCreate
method of your Activity, as shown below:
mMap = findViewById(R.id.map);
mCompass = findViewById(R.id.compass);
mAugmentedRealityCompass = findViewById(R.id.ar_compass);
Location location = ...; // Where the user is heading to
mCompassSensor = CompassSensor.from(this, this)
.bindTo(mMap)
.bindTo(mCompass)
.bindTo(mAugmentedRealityCompass)
.track(mLocationToTrack);
You do not need to worry about the Activity lifecycle and the compass sensor, since it is already aware of Activity lifecycle events, thanks to Android architecture components.
Offline Maps
So that your Map
can work entirely offline, you need to provide it with an offline map. An offline map consists in a small image, the center location (in lat/lon degrees) and the scale used to get the map image.
GetMeThere
already provides an out-of-the-box implementation for a GoogleMaps map (which is obtained using the Google Static Maps API). However, you can create your own offine map, by implementing the interface OfflineMap
and MapProjection
(or use the MercatorProjection
instead).
The example below shows how to create an offline GoogleMaps map:
OfflineGoogleMaps.cache(context, location, description);
This method will download a map with the default resolution of 1200x800 pixels (with 2x scale of a 600x400 map) using the zoom level of 15x.
After downloaded, you can load this map and append to a Map
by calling the method setOfflineMap
as show below:
OfflineMap offlineMap = OfflineGoogleMaps.fromLocation(context, location);
mMap.setOfflineMap(offlineMap);
Required Permissions and Depedencies
This library needs some permissions to work. Make sure your app requests this permissions to the user before using their components:
android.permission.ACCESS_COARSE_LOCATION
android.permission.ACCESS_FINE_LOCATION
android.permission.CAMERA
This library also uses Google Play Services to get the user’s location. Make sure you add this dependency in your gradle script:
compile 'com.google.android.gms:play-services-location:11.8.0'
Example
The following example shows how to build a simple UI with the two view components, Map
and Compass
.
public class MainActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks<OfflineMap> {
Map mMap;
Compass mCompass;
Location mLocation;
// Simple loader to get the map from the cloud and cache on disk
private static class MapLoader extends AsyncTaskLoader<OfflineMap> {
Location mLocation;
private MapLoader(Context context, Location location) {
super(context);
mLocation = location;
}
@Override
protected void onStartLoading() {
forceLoad();
}
@Nullable
@Override
public OfflineMap loadInBackground() {
try {
OfflineGoogleMaps.cache(getContext(), mLocation, "TESTE");
return OfflineGoogleMaps.fromLocation(getContext(), mLocation);
} catch (Exception ignored) {
// TODO: Deal with exceptions...
}
return null;
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mCompass = findViewById(R.id.compass);
mMap = findViewById(R.id.map);
// Mock Location. Get yours from intent args, for example
mLocation = new Location("");
mLocation.setLatitude(-23.595498);
mLocation.setLongitude(-46.686404);
CompassSensor.from(this, this)
.bindTo(mMap)
.bindTo(mCompass)
.track(mLocation);
getSupportLoaderManager().initLoader(0, null, this);
// Make sure you request permissions before using the API views
ActivityCompat.requestPermissions(this, new String[] {
android.Manifest.permission.ACCESS_FINE_LOCATION,
android.Manifest.permission.ACCESS_COARSE_LOCATION}, 0);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
mCompassSensor.start();
}
@NonNull
@Override
public Loader<OfflineMap> onCreateLoader(int id, @Nullable Bundle args) {
return new MapLoader(this, mLocation);
}
@Override
public void onLoadFinished(@NonNull Loader<OfflineMap> loader, OfflineMap data) {
mMap.setOfflineMap(data);
}
@Override
public void onLoaderReset(@NonNull Loader<OfflineMap> loader) {
}
}