Skip to content

Commit

Permalink
Geo: Use more precise map markers in geo activities. (#2940)
Browse files Browse the repository at this point in the history
* Geo: Use more precise map marker icons.

* Geo: Use crossshairs to indicate location in GoogleMapFragment.

* Geo: Fix up polygon drawing in GoogleMapFragment to match OsmMapFragment.

* Fix Robolectric breakage of GoogleMap in tests.

* Remove unnecessary testMode guard in getBitmapDescriptor method.

* Move colour constants used in geo activities into named colour resources.

* Make getBitmap properly handle different kinds of Drawables.

* Fix incorrect colour constants.  Fix clearing of features from GoogleMapFragment.

* Fixed the arrow position in OSM maps

The default marker used by OSM is a person. The location of the person marker's feet is adjusted based on display density. Since we are using crosshairs which are square instead of the person marker, the marker location needs to be adjusted.
  • Loading branch information
zestyping authored and grzesiek2010 committed Mar 27, 2019
1 parent 32d73b8 commit ea35ea2
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 20 deletions.
Expand Up @@ -17,7 +17,6 @@
import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.content.Intent;
import android.graphics.Color;
import android.location.Location;
import android.os.Handler;
import android.provider.Settings;
Expand All @@ -32,6 +31,10 @@
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.BitmapDescriptor;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.Circle;
import com.google.android.gms.maps.model.CircleOptions;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.LatLngBounds;
import com.google.android.gms.maps.model.Marker;
Expand All @@ -42,6 +45,7 @@
import org.odk.collect.android.R;
import org.odk.collect.android.location.client.LocationClient;
import org.odk.collect.android.location.client.LocationClients;
import org.odk.collect.android.utilities.IconUtils;
import org.odk.collect.android.utilities.ToastUtils;

import java.util.ArrayList;
Expand All @@ -61,6 +65,8 @@ public class GoogleMapFragment extends SupportMapFragment implements
public static final float POINT_ZOOM = 16;

protected GoogleMap map;
protected Marker locationCrosshairs;
protected Circle accuracyCircle;
protected List<ReadyListener> gpsLocationReadyListeners = new ArrayList<>();
protected PointListener clickListener;
protected PointListener longPressListener;
Expand Down Expand Up @@ -104,10 +110,8 @@ public class GoogleMapFragment extends SupportMapFragment implements
map.setOnMapLongClickListener(this);
map.setOnMarkerDragListener(this);
map.getUiSettings().setCompassEnabled(true);
// Show the blue dot on the map, but hide the Google-provided
// "go to my location" button; we have our own button for that.
map.setMyLocationEnabled(true);
map.getUiSettings().setMyLocationButtonEnabled(false);
// Don't show the blue dot on the map; we'll draw crosshairs instead.
map.setMyLocationEnabled(false);
map.setMinZoomPreference(1);
map.moveCamera(CameraUpdateFactory.newLatLngZoom(INITIAL_CENTER, INITIAL_ZOOM));
if (listener != null) {
Expand Down Expand Up @@ -267,7 +271,9 @@ protected void moveOrAnimateCamera(CameraUpdate movement, boolean animate) {

@Override public void clearFeatures() {
if (map != null) { // during Robolectric tests, map will be null
map.clear();
for (MapFeature feature : features.values()) {
feature.dispose();
}
}
features.clear();
}
Expand Down Expand Up @@ -321,6 +327,35 @@ protected void moveOrAnimateCamera(CameraUpdate movement, boolean animate) {
if (gpsLocationListener != null) {
gpsLocationListener.onPoint(lastLocationFix);
}
updateLocationIndicator(toLatLng(lastLocationFix), location.getAccuracy());
}

protected void updateLocationIndicator(LatLng loc, double radius) {
if (map == null) {
return;
}
if (locationCrosshairs == null) {
locationCrosshairs = map.addMarker(new MarkerOptions()
.position(loc)
.icon(getBitmapDescriptor(R.drawable.ic_crosshairs))
.anchor(0.5f, 0.5f) // center the crosshairs on the position
);
}
if (accuracyCircle == null) {
int stroke = getResources().getColor(R.color.locationAccuracyCircle);
int fill = getResources().getColor(R.color.locationAccuracyFill);
accuracyCircle = map.addCircle(new CircleOptions()
.center(loc)
.radius(radius)
.strokeWidth(1)
.strokeColor(stroke)
.fillColor(fill)
);
}

locationCrosshairs.setPosition(loc);
accuracyCircle.setCenter(loc);
accuracyCircle.setRadius(radius);
}

@Override public @Nullable MapPoint getGpsLocation() {
Expand All @@ -343,7 +378,12 @@ protected void moveOrAnimateCamera(CameraUpdate movement, boolean animate) {
}
}

@Override public void onMarkerDragStart(Marker marker) { }
@Override public void onMarkerDragStart(Marker marker) {
// When dragging starts, GoogleMap makes the marker jump up to move it
// out from under the user's finger; whenever a marker moves, we have
// to update its corresponding feature.
updateFeature(findFeature(marker));
}

@Override public void onMarkerDrag(Marker marker) {
// When a marker is manually dragged, the position is no longer
Expand Down Expand Up @@ -442,8 +482,8 @@ protected void updateFeature(int featureId) {
return new LatLng(point.lat, point.lon);
}

protected static Marker createMarker(GoogleMap map, MapPoint point, boolean draggable) {
if (map == null) {
protected Marker createMarker(GoogleMap map, MapPoint point, boolean draggable) {
if (map == null) { // during Robolectric tests, map will be null
return null;
}
// A Marker's position is a LatLng with just latitude and longitude
Expand All @@ -453,9 +493,16 @@ protected static Marker createMarker(GoogleMap map, MapPoint point, boolean drag
.position(toLatLng(point))
.snippet(point.alt + ";" + point.sd)
.draggable(draggable)
.icon(getBitmapDescriptor(R.drawable.ic_map_point))
.anchor(0.5f, 0.5f) // center the icon on the position
);
}

protected BitmapDescriptor getBitmapDescriptor(int drawableId) {
return BitmapDescriptorFactory.fromBitmap(
IconUtils.getBitmap(getActivity(), drawableId));
}

@VisibleForTesting public boolean isGpsErrorDialogShowing() {
return gpsErrorDialog != null && gpsErrorDialog.isShowing();
}
Expand All @@ -477,7 +524,7 @@ interface MapFeature {
void dispose();
}

protected static class MarkerFeature implements MapFeature {
protected class MarkerFeature implements MapFeature {
Marker marker;

public MarkerFeature(GoogleMap map, MapPoint point, boolean draggable) {
Expand All @@ -501,11 +548,12 @@ public void dispose() {
}

/** A polyline or polygon that can be manipulated by dragging markers at its vertices. */
protected static class PolyFeature implements MapFeature {
protected class PolyFeature implements MapFeature {
final GoogleMap map;
final List<Marker> markers = new ArrayList<>();
final boolean closedPolygon;
Polyline polyline;
public static final int STROKE_WIDTH = 5;

public PolyFeature(GoogleMap map, Iterable<MapPoint> points, boolean closedPolygon) {
this.map = map;
Expand Down Expand Up @@ -534,11 +582,12 @@ public void update() {
if (markers.isEmpty()) {
clearPolyline();
} else if (polyline == null) {
PolylineOptions options = new PolylineOptions();
options.color(Color.RED);
options.zIndex(1);
options.addAll(latLngs);
polyline = map.addPolyline(options);
polyline = map.addPolyline(new PolylineOptions()
.color(getResources().getColor(R.color.mapLine))
.zIndex(1)
.width(STROKE_WIDTH)
.addAll(latLngs)
);
} else {
polyline.setPoints(latLngs);
}
Expand Down
Expand Up @@ -17,7 +17,7 @@
import android.app.AlertDialog;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.graphics.Bitmap;
import android.graphics.Paint;
import android.location.Location;
import android.location.LocationManager;
Expand All @@ -39,6 +39,7 @@
import org.odk.collect.android.R;
import org.odk.collect.android.location.client.LocationClient;
import org.odk.collect.android.location.client.LocationClients;
import org.odk.collect.android.utilities.IconUtils;
import org.osmdroid.api.IGeoPoint;
import org.osmdroid.events.MapEventsReceiver;
import org.osmdroid.events.MapListener;
Expand Down Expand Up @@ -113,6 +114,11 @@ public MapView getMapView() {
map.getOverlays().add(new MapEventsOverlay(this));
addMapLayoutChangeListener(map);
myLocationOverlay = new MyLocationNewOverlay(map);
myLocationOverlay.setDrawAccuracyEnabled(true);
Bitmap crosshairs = IconUtils.getBitmap(getActivity(), R.drawable.ic_crosshairs);
myLocationOverlay.setDirectionArrow(crosshairs, crosshairs);
myLocationOverlay.setPersonHotspot(crosshairs.getWidth() / 2.0f, crosshairs.getHeight() / 2.0f);

locationClient = LocationClients.clientForContext(getActivity());
locationClient.setListener(this);
if (readyListener != null) {
Expand Down Expand Up @@ -410,8 +416,8 @@ protected Marker createMarker(MapView map, MapPoint point, MapFeature feature) {
marker.setPosition(toGeoPoint(point));
marker.setSubDescription(Double.toString(point.sd));
marker.setDraggable(feature != null);
marker.setIcon(ContextCompat.getDrawable(map.getContext(), R.drawable.ic_place_black));
marker.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM);
marker.setIcon(ContextCompat.getDrawable(map.getContext(), R.drawable.ic_map_point));
marker.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_CENTER);

marker.setOnMarkerDragListener(new Marker.OnMarkerDragListener() {
@Override public void onMarkerDragStart(Marker marker) { }
Expand Down Expand Up @@ -516,7 +522,7 @@ public PolyFeature(MapView map, Iterable<MapPoint> points, boolean closedPolygon
this.map = map;
this.closedPolygon = closedPolygon;
polyline = new Polyline();
polyline.setColor(Color.RED);
polyline.setColor(getResources().getColor(R.color.mapLine));
Paint paint = polyline.getPaint();
paint.setStrokeWidth(STROKE_WIDTH);
map.getOverlays().add(polyline);
Expand Down
Expand Up @@ -16,7 +16,13 @@

package org.odk.collect.android.utilities;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.support.v4.content.ContextCompat;

import org.odk.collect.android.R;

Expand All @@ -29,4 +35,25 @@ public static int getNotificationAppIcon() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ?
R.drawable.ic_notes_white : R.drawable.ic_notes_white_png;
}

/** Renders a Drawable (such as a vector drawable) into a Bitmap. */
public static Bitmap getBitmap(Context context, int drawableId) {
Drawable drawable = ContextCompat.getDrawable(context, drawableId);
if (drawable instanceof BitmapDrawable) { // shortcut if it's already a bitmap
Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
if (bitmap != null) {
return bitmap;
}
}
int width = drawable.getIntrinsicWidth();
int height = drawable.getIntrinsicHeight();
if (width <= 0 || height <= 0) { // negative if Drawable is a solid colour
width = height = 1;
}
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bitmap;
}
}
9 changes: 9 additions & 0 deletions collect_app/src/main/res/drawable/ic_crosshairs.xml
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="48"
android:viewportHeight="48">
<path
android:fillColor="@color/locationCrosshairs"
android:pathData="M18,23v2h-18v-2h18z m12,0h18v2h-18v-2z m-7,-5v-18h2v18h-2z m0,12h2v18h-2v-18z" />
</vector>
8 changes: 8 additions & 0 deletions collect_app/src/main/res/drawable/ic_map_point.xml
@@ -0,0 +1,8 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="36dp"
android:height="36dp"
android:viewportWidth="36"
android:viewportHeight="36">
<path android:fillColor="@color/mapPointCircle" android:pathData="M18,13c-2.77,0 -5,2.24 -5,5s2.24,5 5,5s5,-2.24 5,-5s-2.24,-5 -5,-5z"/>
<path android:fillColor="@color/mapPointFill" android:pathData="M18,21.33c-1.84,0 -3.33,-1.5 -3.33,-3.33s1.5,-3.33 3.33,-3.33s3.33,1.5 3.33,3.33s-1.5,3.33 -3.33,3.33z"/>
</vector>
12 changes: 12 additions & 0 deletions collect_app/src/main/res/values/colors.xml
Expand Up @@ -48,4 +48,16 @@ the License.
<color name="locationStatusSearching">#333333</color>
<color name="locationStatusAcceptable">#337733</color>
<color name="locationStatusUnacceptable">#773333</color>

<!-- Colors for location indicators on the map; chosen to resemble the -->
<!-- bright blue location dot on Google Maps and Apple Maps and the -->
<!-- shaded blue accuracy circle in OSMDroid. -->
<color name="locationCrosshairs">#ff0099ff</color>
<color name="locationAccuracyCircle">#800099ff</color>
<color name="locationAccuracyFill">#200099ff</color>

<!-- Colors for recorded points and lines drawn on the map. -->
<color name="mapPointCircle">#ffff0000</color>
<color name="mapPointFill">#ffffffff</color>
<color name="mapLine">#ffff0000</color>
</resources>

0 comments on commit ea35ea2

Please sign in to comment.