diff --git a/collect_app/src/main/AndroidManifest.xml b/collect_app/src/main/AndroidManifest.xml
index 73a820ddf9f..3e5677e0b14 100644
--- a/collect_app/src/main/AndroidManifest.xml
+++ b/collect_app/src/main/AndroidManifest.xml
@@ -191,6 +191,9 @@ the specific language governing permissions and limitations under the License.
+
diff --git a/collect_app/src/main/java/org/odk/collect/android/activities/GeoShapeGoogleMapActivity.java b/collect_app/src/main/java/org/odk/collect/android/activities/GeoShapeGoogleMapActivity.java
index b1140f34d39..7620ead5f19 100644
--- a/collect_app/src/main/java/org/odk/collect/android/activities/GeoShapeGoogleMapActivity.java
+++ b/collect_app/src/main/java/org/odk/collect/android/activities/GeoShapeGoogleMapActivity.java
@@ -53,7 +53,6 @@ public class GeoShapeGoogleMapActivity extends CollectAbstractActivity {
private View zoomDialogView;
private Button zoomPointButton;
private Button zoomLocationButton;
- private boolean foundFirstLocation;
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -68,7 +67,7 @@ public void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.geoshape_layout);
// TODO(ping): Remove when we're ready to use this class.
- ((TextView) findViewById(R.id.top_text)).setText("new GeoShapeActivity");
+ ((TextView) findViewById(R.id.top_text)).setText("new Google GeoShapeActivity");
createMapFragment().addTo(this, R.id.map_container, this::setupMap);
}
@@ -99,11 +98,11 @@ private void setupMap(MapFragment newMapFragment) {
}
map = newMapFragment;
+ map.runOnGpsLocationReady(this::onGpsLocationReady);
map.setGpsLocationEnabled(true);
- map.setGpsLocationListener(this::onLocationFix);
map.setLongPressListener(this::addVertex);
- helper = new MapHelper(this, newMapFragment);
+ helper = new MapHelper(this, map);
gpsButton = findViewById(R.id.gps);
gpsButton.setOnClickListener(v -> showZoomDialog());
@@ -142,7 +141,7 @@ private void setupMap(MapFragment newMapFragment) {
// If there is a last know location go there
if (hasWindowFocus() && map.getGpsLocation() != null) {
- foundFirstLocation = true;
+ // foundFirstLocation = true;
gpsButton.setEnabled(true);
showZoomDialog();
}
@@ -209,10 +208,9 @@ private String formatPoints(List points) {
return result;
}
- private void onLocationFix(MapPoint point) {
+ private void onGpsLocationReady(MapFragment map) {
gpsButton.setEnabled(true);
- if (hasWindowFocus() && !foundFirstLocation) {
- foundFirstLocation = true;
+ if (hasWindowFocus()) {
showZoomDialog();
}
}
@@ -225,7 +223,6 @@ private void addVertex(MapPoint point) {
private void clear() {
map.clearFeatures();
shapeId = map.addDraggableShape(new ArrayList<>());
- map.setLongPressListener(this::addVertex);
clearButton.setEnabled(false);
}
diff --git a/collect_app/src/main/java/org/odk/collect/android/activities/GeoShapeOldGoogleMapActivity.java b/collect_app/src/main/java/org/odk/collect/android/activities/GeoShapeOldGoogleMapActivity.java
index b673e6f0e4b..9cad81050ea 100644
--- a/collect_app/src/main/java/org/odk/collect/android/activities/GeoShapeOldGoogleMapActivity.java
+++ b/collect_app/src/main/java/org/odk/collect/android/activities/GeoShapeOldGoogleMapActivity.java
@@ -98,7 +98,7 @@ public void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.geoshape_layout);
// TODO(ping): Remove when we're ready to use the new class.
- ((TextView) findViewById(R.id.top_text)).setText("old GeoShapeActivity");
+ ((TextView) findViewById(R.id.top_text)).setText("old Google GeoShapeActivity");
SupportMapFragment mapFragment = new SupportMapFragment();
getSupportFragmentManager().beginTransaction()
diff --git a/collect_app/src/main/java/org/odk/collect/android/activities/GeoShapeOldOsmMapActivity.java b/collect_app/src/main/java/org/odk/collect/android/activities/GeoShapeOldOsmMapActivity.java
new file mode 100644
index 00000000000..25bc1c7d41c
--- /dev/null
+++ b/collect_app/src/main/java/org/odk/collect/android/activities/GeoShapeOldOsmMapActivity.java
@@ -0,0 +1,596 @@
+/*
+ * Copyright (C) 2016 GeoODK
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.odk.collect.android.activities;
+
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.location.LocationManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.provider.Settings;
+import android.support.v4.content.ContextCompat;
+import android.view.View;
+import android.view.Window;
+import android.widget.Button;
+import android.widget.ImageButton;
+import android.widget.TextView;
+
+import org.odk.collect.android.R;
+import org.odk.collect.android.fragments.OsmMapFragment;
+import org.odk.collect.android.spatial.MapHelper;
+import org.odk.collect.android.utilities.ToastUtils;
+import org.odk.collect.android.widgets.GeoShapeWidget;
+import org.osmdroid.events.MapEventsReceiver;
+import org.osmdroid.events.MapListener;
+import org.osmdroid.events.ScrollEvent;
+import org.osmdroid.events.ZoomEvent;
+import org.osmdroid.tileprovider.IRegisterReceiver;
+import org.osmdroid.util.BoundingBox;
+import org.osmdroid.util.GeoPoint;
+import org.osmdroid.views.MapView;
+import org.osmdroid.views.overlay.MapEventsOverlay;
+import org.osmdroid.views.overlay.Marker;
+import org.osmdroid.views.overlay.Polyline;
+import org.osmdroid.views.overlay.mylocation.GpsMyLocationProvider;
+import org.osmdroid.views.overlay.mylocation.MyLocationNewOverlay;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import static org.odk.collect.android.utilities.PermissionUtils
+ .checkIfLocationPermissionsGranted;
+
+/**
+ * Version of the GeoPointMapActivity that uses the new Maps v2 API and Fragments to enable
+ * specifying a location via placing a tracker on a map.
+ *
+ * @author jonnordling@gmail.com
+ */
+
+public class GeoShapeOldOsmMapActivity extends CollectAbstractActivity implements IRegisterReceiver {
+ private MapView map;
+ private final ArrayList mapMarkers = new ArrayList();
+ private Polyline polyline;
+ public int zoomLevel = 3;
+ public static final int STROKE_WIDTH = 5;
+ public String finalReturnString;
+ private MapEventsOverlay overlayEvents;
+ private boolean clearButtonTest;
+ private ImageButton clearButton;
+ public boolean gpsStatus = true;
+ private ImageButton locationButton;
+ public MyLocationNewOverlay myLocationOverlay;
+ public boolean dataLoaded;
+
+ private MapHelper helper;
+
+ private AlertDialog zoomDialog;
+ private View zoomDialogView;
+
+ private Button zoomPointButton;
+ private Button zoomLocationButton;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ if (!checkIfLocationPermissionsGranted(this)) {
+ finish();
+ return;
+ }
+
+ requestWindowFeature(Window.FEATURE_NO_TITLE);
+ setTitle(getString(R.string.geoshape_title));
+ setContentView(R.layout.geoshape_layout);
+
+ // TODO(ping): Remove when we're ready to use this class.
+ ((TextView) findViewById(R.id.top_text)).setText("old OSM GeoShapeActivity");
+
+ OsmMapFragment mapFragment = new OsmMapFragment();
+ getSupportFragmentManager().beginTransaction()
+ .add(R.id.map_container, mapFragment).commit();
+ mapFragment.getMapAsync(this::setupMap);
+ }
+
+ private void setupMap(MapView map) {
+ this.map = map;
+ helper = new MapHelper(this, map, this);
+ map.setMultiTouchControls(true);
+ map.setBuiltInZoomControls(true);
+ map.setTilesScaledToDpi(true);
+ map.setMapListener(mapViewListener);
+ overlayPointPathListener();
+ ImageButton saveButton = findViewById(R.id.save);
+ clearButton = findViewById(R.id.clear);
+ saveButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ returnLocation();
+ }
+ });
+ clearButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (!mapMarkers.isEmpty()) {
+ showClearDialog();
+ }
+ }
+ });
+ ImageButton layersButton = findViewById(R.id.layers);
+ layersButton.setOnClickListener(new View.OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ helper.showLayersDialog();
+
+ }
+ });
+ locationButton = findViewById(R.id.gps);
+ locationButton.setEnabled(false);
+ locationButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(final View v) {
+ showZoomDialog();
+ }
+ });
+
+ GpsMyLocationProvider imlp = new GpsMyLocationProvider(this.getBaseContext());
+ imlp.setLocationUpdateMinDistance(1000);
+ imlp.setLocationUpdateMinTime(60000);
+ myLocationOverlay = new MyLocationNewOverlay(map);
+
+ Intent intent = getIntent();
+ if (intent != null && intent.getExtras() != null) {
+ if (intent.hasExtra(GeoShapeWidget.SHAPE_LOCATION)) {
+ clearButton.setEnabled(true);
+ dataLoaded = true;
+ String s = intent.getStringExtra(GeoShapeWidget.SHAPE_LOCATION);
+ overlayIntentPolygon(s);
+ //zoomToCentroid();
+ locationButton.setEnabled(true);
+ zoomToBounds();
+ }
+ } else {
+ myLocationOverlay.runOnFirstFix(centerAroundFix);
+ clearButton.setEnabled(false);
+ final Handler handler = new Handler();
+ handler.postDelayed(new Runnable() {
+ public void run() {
+ GeoPoint point = new GeoPoint(34.08145, -39.85007);
+ map.getController().setZoom(3);
+ map.getController().setCenter(point);
+ }
+ }, 100);
+
+ }
+
+ map.invalidate();
+
+ zoomDialogView = getLayoutInflater().inflate(R.layout.geo_zoom_dialog, null);
+
+ zoomLocationButton = zoomDialogView.findViewById(R.id.zoom_location);
+ zoomLocationButton.setOnClickListener(new View.OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ zoomToMyLocation();
+ map.invalidate();
+ zoomDialog.dismiss();
+ }
+ });
+
+ zoomPointButton = zoomDialogView.findViewById(R.id.zoom_saved_location);
+ zoomPointButton.setOnClickListener(new View.OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ //zoomToCentroid();
+ zoomToBounds();
+ map.invalidate();
+ zoomDialog.dismiss();
+ }
+ });
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ if (map != null) {
+ helper.setBasemap();
+ }
+
+ upMyLocationOverlayLayers();
+ }
+
+ @Override
+ public void onBackPressed() {
+ if (!mapMarkers.isEmpty()) {
+ showBackDialog();
+ } else {
+ finish();
+ }
+ }
+
+ @Override
+ protected void onPause() {
+ disableMyLocation();
+ super.onPause();
+ }
+
+ @Override
+ protected void onStop() {
+ disableMyLocation();
+ super.onStop();
+ }
+
+ private void overlayIntentPolygon(String str) {
+ clearButton.setEnabled(true);
+ clearButtonTest = true;
+ String s = str.replace("; ", ";");
+ String[] sa = s.split(";");
+ for (int i = 0; i < (sa.length - 1); i++) {
+ String[] sp = sa[i].split(" ");
+ double[] gp = new double[4];
+ String lat = sp[0].replace(" ", "");
+ String lng = sp[1].replace(" ", "");
+ gp[0] = Double.parseDouble(lat);
+ gp[1] = Double.parseDouble(lng);
+ Marker marker = new Marker(map);
+ marker.setPosition(new GeoPoint(gp[0], gp[1]));
+ marker.setDraggable(true);
+ marker.setIcon(ContextCompat.getDrawable(this, R.drawable.ic_place_black));
+ marker.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM);
+ marker.setOnMarkerClickListener(nullMarkerListener);
+ mapMarkers.add(marker);
+ // pathOverlay.addPoint(marker.getPosition());
+ marker.setDraggable(true);
+ marker.setOnMarkerDragListener(dragListener);
+ map.getOverlays().add(marker);
+ }
+ update_polygon();
+ map.getOverlays().remove(overlayEvents);
+ }
+
+ private final Handler handler = new Handler(Looper.getMainLooper());
+
+ private final Runnable centerAroundFix = new Runnable() {
+ public void run() {
+ handler.post(new Runnable() {
+ public void run() {
+ locationButton.setEnabled(true);
+ showZoomDialog();
+ }
+ });
+ }
+ };
+
+ private void showGPSDisabledAlertToUser() {
+ AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this);
+ alertDialogBuilder.setMessage(getString(R.string.gps_enable_message))
+ .setCancelable(false)
+ .setPositiveButton(getString(R.string.enable_gps),
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ startActivityForResult(
+ new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS), 0);
+ }
+ });
+ alertDialogBuilder.setNegativeButton(getString(R.string.cancel),
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ dialog.cancel();
+ }
+ });
+ AlertDialog alert = alertDialogBuilder.create();
+ alert.show();
+ }
+
+ private void upMyLocationOverlayLayers() {
+ LocationManager locationManager = (LocationManager) getSystemService(LOCATION_SERVICE);
+ if (locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
+ overlayMyLocationLayers();
+ } else {
+ showGPSDisabledAlertToUser();
+ }
+
+ }
+
+ private void overlayMyLocationLayers() {
+ map.getOverlays().add(myLocationOverlay);
+ myLocationOverlay.setEnabled(true);
+ myLocationOverlay.enableMyLocation();
+ }
+
+ private void zoomToMyLocation() {
+ if (myLocationOverlay.getMyLocation() != null) {
+ map.getController().setZoom(15);
+ map.getController().setCenter(myLocationOverlay.getMyLocation());
+ }
+ }
+
+ private void disableMyLocation() {
+ LocationManager locationManager = (LocationManager) getSystemService(LOCATION_SERVICE);
+ if (locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
+ myLocationOverlay.setEnabled(false);
+ myLocationOverlay.disableFollowLocation();
+ myLocationOverlay.disableMyLocation();
+ gpsStatus = false;
+ }
+ }
+
+ private void overlayPointPathListener() {
+ overlayEvents = new MapEventsOverlay(receive);
+ polyline = new Polyline();
+ polyline.setColor(Color.RED);
+ Paint paint = polyline.getPaint();
+ paint.setStrokeWidth(STROKE_WIDTH);
+ map.getOverlays().add(polyline);
+ map.getOverlays().add(overlayEvents);
+ map.invalidate();
+ }
+
+ private void clearFeatures() {
+ clearButtonTest = false;
+ mapMarkers.clear();
+ polyline.setPoints(new ArrayList());
+ map.getOverlays().clear();
+ clearButton.setEnabled(false);
+ //saveButton.setEnabled(false);
+ overlayPointPathListener();
+ overlayMyLocationLayers();
+ map.invalidate();
+
+ }
+
+ private void showClearDialog() {
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setMessage(getString(R.string.geo_clear_warning))
+ .setPositiveButton(getString(R.string.clear),
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ clearFeatures();
+ }
+ })
+ .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+
+ }
+ }).show();
+
+ }
+
+ private void showBackDialog() {
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setMessage(getString(R.string.geo_exit_warning))
+ .setPositiveButton(getString(R.string.discard),
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ finish();
+ }
+ })
+ .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+
+ }
+ }).show();
+
+ }
+
+ private String generateReturnString() {
+ String tempString = "";
+ if (mapMarkers.size() > 1) {
+ if (Collections.frequency(mapMarkers, mapMarkers.get(0)) < 2) {
+ mapMarkers.add(mapMarkers.get(0));
+ }
+ for (int i = 0; i < mapMarkers.size(); i++) {
+ String lat = Double.toString(mapMarkers.get(i).getPosition().getLatitude());
+ String lng = Double.toString(mapMarkers.get(i).getPosition().getLongitude());
+ String alt = "0.0";
+ String acu = "0.0";
+ tempString = tempString + lat + " " + lng + " " + alt + " " + acu + ";";
+ }
+ }
+ return tempString;
+ }
+
+ private void returnLocation() {
+ finalReturnString = generateReturnString();
+ Intent i = new Intent();
+ i.putExtra(
+ FormEntryActivity.GEOSHAPE_RESULTS,
+ finalReturnString);
+ setResult(RESULT_OK, i);
+ if (mapMarkers.size() < 4) {
+ ToastUtils.showShortToastInMiddle(getString(R.string.polygon_validator));
+ } else {
+ finish();
+ }
+ }
+
+ private void update_polygon() {
+ List points = new ArrayList<>();
+ for (int i = 0; i < mapMarkers.size(); i++) {
+ points.add(mapMarkers.get(i).getPosition());
+ }
+ points.add(mapMarkers.get(0).getPosition());
+
+ polyline.setPoints(points);
+ map.invalidate();
+ }
+
+ private final MapEventsReceiver receive = new MapEventsReceiver() {
+ @Override
+ public boolean longPressHelper(GeoPoint point) {
+ if (!clearButtonTest) {
+ clearButton.setEnabled(true);
+ clearButtonTest = true;
+ }
+ Marker marker = new Marker(map);
+ marker.setPosition(point);
+ marker.setDraggable(true);
+ marker.setIcon(ContextCompat.getDrawable(GeoShapeOldOsmMapActivity.this, R.drawable.ic_place_black));
+ marker.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM);
+ marker.setOnMarkerClickListener(nullMarkerListener);
+ mapMarkers.add(marker);
+ marker.setDraggable(true);
+ marker.setOnMarkerDragListener(dragListener);
+ map.getOverlays().add(marker);
+ List points = polyline.getPoints();
+ points.add(marker.getPosition());
+ polyline.setPoints(points);
+ update_polygon();
+ map.invalidate();
+ return false;
+ }
+
+ @Override
+ public boolean singleTapConfirmedHelper(GeoPoint arg0) {
+ return false;
+ }
+ };
+
+ private final MapListener mapViewListener = new MapListener() {
+ @Override
+ public boolean onZoom(ZoomEvent zoomLev) {
+ zoomLevel = zoomLev.getZoomLevel();
+ return false;
+ }
+
+ @Override
+ public boolean onScroll(ScrollEvent arg0) {
+ return false;
+ }
+
+ };
+
+ private final Marker.OnMarkerDragListener dragListener = new Marker.OnMarkerDragListener() {
+ @Override
+ public void onMarkerDragStart(Marker marker) {
+
+ }
+
+ @Override
+ public void onMarkerDragEnd(Marker marker) {
+ update_polygon();
+
+ }
+
+ @Override
+ public void onMarkerDrag(Marker marker) {
+ update_polygon();
+
+ }
+ };
+
+ private final Marker.OnMarkerClickListener nullMarkerListener = new Marker.OnMarkerClickListener() {
+
+ @Override
+ public boolean onMarkerClick(Marker arg0, MapView arg1) {
+ return false;
+ }
+ };
+
+ /*
+ This functions should be added to the mapHelper Class
+
+ */
+ private void zoomToBounds() {
+ map.getController().setZoom(4);
+ map.invalidate();
+ Handler handler = new Handler();
+ handler.postDelayed(new Runnable() {
+ public void run() {
+ double minLat = Double.MAX_VALUE;
+ double maxLat = Double.MIN_VALUE;
+ double minLong = Double.MAX_VALUE;
+ double maxLong = Double.MIN_VALUE;
+ Integer size = mapMarkers.size();
+ for (int i = 0; i < size; i++) {
+ GeoPoint tempMarker = mapMarkers.get(i).getPosition();
+ if (tempMarker.getLatitude() < minLat) {
+ minLat = tempMarker.getLatitude();
+ }
+ if (tempMarker.getLatitude() > maxLat) {
+ maxLat = tempMarker.getLatitude();
+ }
+ if (tempMarker.getLongitude() < minLong) {
+ minLong = tempMarker.getLongitude();
+ }
+ if (tempMarker.getLongitude() > maxLong) {
+ maxLong = tempMarker.getLongitude();
+ }
+ }
+ BoundingBox boundingBox = new BoundingBox(maxLat, maxLong, minLat, minLong);
+ map.zoomToBoundingBox(boundingBox, false);
+ map.invalidate();
+ }
+ }, 100);
+ map.invalidate();
+
+ }
+
+ public void showZoomDialog() {
+
+ if (zoomDialog == null) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle(getString(R.string.zoom_to_where));
+ builder.setView(zoomDialogView)
+ .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ dialog.cancel();
+ }
+ })
+ .setOnCancelListener(new DialogInterface.OnCancelListener() {
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ dialog.cancel();
+ zoomDialog.dismiss();
+ }
+ });
+ zoomDialog = builder.create();
+ }
+ //If feature enable zoom to button else disable
+ if (myLocationOverlay.getMyLocation() != null) {
+ zoomLocationButton.setEnabled(true);
+ zoomLocationButton.setBackgroundColor(Color.parseColor("#50cccccc"));
+ zoomLocationButton.setTextColor(themeUtils.getPrimaryTextColor());
+ } else {
+ zoomLocationButton.setEnabled(false);
+ zoomLocationButton.setBackgroundColor(Color.parseColor("#50e2e2e2"));
+ zoomLocationButton.setTextColor(Color.parseColor("#FF979797"));
+ }
+
+ if (!mapMarkers.isEmpty()) {
+ zoomPointButton.setEnabled(true);
+ zoomPointButton.setBackgroundColor(Color.parseColor("#50cccccc"));
+ zoomPointButton.setTextColor(themeUtils.getPrimaryTextColor());
+ } else {
+ zoomPointButton.setEnabled(false);
+ zoomPointButton.setBackgroundColor(Color.parseColor("#50e2e2e2"));
+ zoomPointButton.setTextColor(Color.parseColor("#FF979797"));
+ }
+ zoomDialog.show();
+ }
+
+ @Override
+ public void destroy() {
+
+ }
+}
diff --git a/collect_app/src/main/java/org/odk/collect/android/activities/GeoShapeOsmMapActivity.java b/collect_app/src/main/java/org/odk/collect/android/activities/GeoShapeOsmMapActivity.java
index e2daf1bbcef..2aa9995f486 100644
--- a/collect_app/src/main/java/org/odk/collect/android/activities/GeoShapeOsmMapActivity.java
+++ b/collect_app/src/main/java/org/odk/collect/android/activities/GeoShapeOsmMapActivity.java
@@ -18,40 +18,25 @@
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Color;
-import android.graphics.Paint;
-import android.location.LocationManager;
import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.provider.Settings;
-import android.support.v4.content.ContextCompat;
import android.view.View;
import android.view.Window;
import android.widget.Button;
import android.widget.ImageButton;
+import android.widget.TextView;
import org.odk.collect.android.R;
-import org.odk.collect.android.fragments.OsmMapFragment;
+import org.odk.collect.android.map.MapFragment;
+import org.odk.collect.android.map.MapPoint;
+import org.odk.collect.android.map.OsmMapFragment;
import org.odk.collect.android.spatial.MapHelper;
import org.odk.collect.android.utilities.ToastUtils;
import org.odk.collect.android.widgets.GeoShapeWidget;
-import org.osmdroid.events.MapEventsReceiver;
-import org.osmdroid.events.MapListener;
-import org.osmdroid.events.ScrollEvent;
-import org.osmdroid.events.ZoomEvent;
import org.osmdroid.tileprovider.IRegisterReceiver;
-import org.osmdroid.util.BoundingBox;
-import org.osmdroid.util.GeoPoint;
-import org.osmdroid.views.MapView;
-import org.osmdroid.views.overlay.MapEventsOverlay;
-import org.osmdroid.views.overlay.Marker;
-import org.osmdroid.views.overlay.Polyline;
-import org.osmdroid.views.overlay.mylocation.GpsMyLocationProvider;
-import org.osmdroid.views.overlay.mylocation.MyLocationNewOverlay;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
+import java.util.Locale;
import static org.odk.collect.android.utilities.PermissionUtils.checkIfLocationPermissionsGranted;
@@ -63,25 +48,14 @@
*/
public class GeoShapeOsmMapActivity extends CollectAbstractActivity implements IRegisterReceiver {
- private MapView map;
- private final ArrayList mapMarkers = new ArrayList();
- private Polyline polyline;
- public int zoomLevel = 3;
- public static final int STROKE_WIDTH = 5;
- public String finalReturnString;
- private MapEventsOverlay overlayEvents;
- private boolean clearButtonTest;
+ private MapFragment map;
+ private int shapeId = -1; // will be a positive featureId once map is ready
private ImageButton clearButton;
- public boolean gpsStatus = true;
- private ImageButton locationButton;
- public MyLocationNewOverlay myLocationOverlay;
- public boolean dataLoaded;
+ private ImageButton gpsButton;
private MapHelper helper;
-
private AlertDialog zoomDialog;
private View zoomDialogView;
-
private Button zoomPointButton;
private Button zoomLocationButton;
@@ -97,125 +71,75 @@ protected void onCreate(Bundle savedInstanceState) {
requestWindowFeature(Window.FEATURE_NO_TITLE);
setTitle(getString(R.string.geoshape_title));
setContentView(R.layout.geoshape_layout);
- OsmMapFragment mapFragment = new OsmMapFragment();
- getSupportFragmentManager().beginTransaction()
- .add(R.id.map_container, mapFragment).commit();
- mapFragment.getMapAsync(this::setupMap);
+
+ // TODO(ping): Remove when we're ready to use this class.
+ ((TextView) findViewById(R.id.top_text)).setText("new OSM GeoShapeActivity");
+
+ createMapFragment().addTo(this, R.id.map_container, this::setupMap);
}
- private void setupMap(MapView map) {
- this.map = map;
- helper = new MapHelper(this, map, this);
- map.setMultiTouchControls(true);
- map.setBuiltInZoomControls(true);
- map.setTilesScaledToDpi(true);
- map.setMapListener(mapViewListener);
- overlayPointPathListener();
- ImageButton saveButton = findViewById(R.id.save);
+ // TODO(ping): Select the appropriate MapFragment implementation (Google or OSM).
+ public static MapFragment createMapFragment() {
+ return new OsmMapFragment();
+ }
+
+ private void setupMap(MapFragment newMapFragment) {
+ map = newMapFragment;
+
+ map.setGpsLocationEnabled(true);
+ map.setLongPressListener(this::addVertex);
+
+ helper = new MapHelper(this, map);
+
clearButton = findViewById(R.id.clear);
- saveButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- returnLocation();
- }
- });
- clearButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- if (!mapMarkers.isEmpty()) {
- showClearDialog();
- }
- }
- });
- ImageButton layersButton = findViewById(R.id.layers);
- layersButton.setOnClickListener(new View.OnClickListener() {
+ clearButton.setOnClickListener(v -> showClearDialog());
- @Override
- public void onClick(View v) {
- helper.showLayersDialog();
+ gpsButton = findViewById(R.id.gps);
+ gpsButton.setOnClickListener(v -> showZoomDialog());
- }
- });
- locationButton = findViewById(R.id.gps);
- locationButton.setEnabled(false);
- locationButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(final View v) {
- showZoomDialog();
- }
- });
+ ImageButton saveButton = findViewById(R.id.save);
+ saveButton.setOnClickListener(v -> finishWithResult());
- GpsMyLocationProvider imlp = new GpsMyLocationProvider(this.getBaseContext());
- imlp.setLocationUpdateMinDistance(1000);
- imlp.setLocationUpdateMinTime(60000);
- myLocationOverlay = new MyLocationNewOverlay(map);
+ ImageButton layersButton = findViewById(R.id.layers);
+ layersButton.setOnClickListener(v -> helper.showLayersDialog());
+ List points = new ArrayList<>();
Intent intent = getIntent();
- if (intent != null && intent.getExtras() != null) {
- if (intent.hasExtra(GeoShapeWidget.SHAPE_LOCATION)) {
- clearButton.setEnabled(true);
- dataLoaded = true;
- String s = intent.getStringExtra(GeoShapeWidget.SHAPE_LOCATION);
- overlayIntentPolygon(s);
- //zoomToCentroid();
- locationButton.setEnabled(true);
- zoomToBounds();
- }
- } else {
- myLocationOverlay.runOnFirstFix(centerAroundFix);
- clearButton.setEnabled(false);
- final Handler handler = new Handler();
- handler.postDelayed(new Runnable() {
- public void run() {
- GeoPoint point = new GeoPoint(34.08145, -39.85007);
- map.getController().setZoom(3);
- map.getController().setCenter(point);
- }
- }, 100);
-
+ if (intent != null && intent.hasExtra(GeoShapeWidget.SHAPE_LOCATION)) {
+ points = parsePoints(intent.getStringExtra(GeoShapeWidget.SHAPE_LOCATION));
}
+ shapeId = map.addDraggableShape(points);
+ gpsButton.setEnabled(!points.isEmpty());
+ clearButton.setEnabled(!points.isEmpty());
- map.invalidate();
+ if (!points.isEmpty()) {
+ map.zoomToBoundingBox(points, 0.8);
+ } else {
+ map.runOnGpsLocationReady(this::onGpsLocationReady);
+ }
zoomDialogView = getLayoutInflater().inflate(R.layout.geo_zoom_dialog, null);
zoomLocationButton = zoomDialogView.findViewById(R.id.zoom_location);
- zoomLocationButton.setOnClickListener(new View.OnClickListener() {
-
- @Override
- public void onClick(View v) {
- zoomToMyLocation();
- map.invalidate();
- zoomDialog.dismiss();
+ zoomLocationButton.setOnClickListener(v -> {
+ MapPoint location = map.getGpsLocation();
+ if (location != null) {
+ map.setCenter(location);
+ map.setZoom(15);
}
+ zoomDialog.dismiss();
});
zoomPointButton = zoomDialogView.findViewById(R.id.zoom_saved_location);
- zoomPointButton.setOnClickListener(new View.OnClickListener() {
-
- @Override
- public void onClick(View v) {
- //zoomToCentroid();
- zoomToBounds();
- map.invalidate();
- zoomDialog.dismiss();
- }
+ zoomPointButton.setOnClickListener(v -> {
+ map.zoomToBoundingBox(map.getPointsOfShape(shapeId), 0.8);
+ zoomDialog.dismiss();
});
}
- @Override
- protected void onResume() {
- super.onResume();
- if (map != null) {
- helper.setBasemap();
- }
-
- upMyLocationOverlayLayers();
- }
-
@Override
public void onBackPressed() {
- if (!mapMarkers.isEmpty()) {
+ if (!map.getPointsOfShape(shapeId).isEmpty()) {
showBackDialog();
} else {
finish();
@@ -223,325 +147,114 @@ public void onBackPressed() {
}
@Override
- protected void onPause() {
- disableMyLocation();
- super.onPause();
+ protected void onStart() {
+ super.onStart();
+ if (map != null) map.setGpsLocationEnabled(true);
}
@Override
protected void onStop() {
- disableMyLocation();
+ map.setGpsLocationEnabled(false);
super.onStop();
}
- private void overlayIntentPolygon(String str) {
- clearButton.setEnabled(true);
- clearButtonTest = true;
- String s = str.replace("; ", ";");
- String[] sa = s.split(";");
- for (int i = 0; i < (sa.length - 1); i++) {
- String[] sp = sa[i].split(" ");
- double[] gp = new double[4];
- String lat = sp[0].replace(" ", "");
- String lng = sp[1].replace(" ", "");
- gp[0] = Double.parseDouble(lat);
- gp[1] = Double.parseDouble(lng);
- Marker marker = new Marker(map);
- marker.setPosition(new GeoPoint(gp[0], gp[1]));
- marker.setDraggable(true);
- marker.setIcon(ContextCompat.getDrawable(this, R.drawable.ic_place_black));
- marker.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM);
- marker.setOnMarkerClickListener(nullMarkerListener);
- mapMarkers.add(marker);
- // pathOverlay.addPoint(marker.getPosition());
- marker.setDraggable(true);
- marker.setOnMarkerDragListener(dragListener);
- map.getOverlays().add(marker);
+ private void onGpsLocationReady(MapFragment map) {
+ gpsButton.setEnabled(true);
+ if (hasWindowFocus()) {
+ showZoomDialog();
}
- update_polygon();
- map.getOverlays().remove(overlayEvents);
}
- private final Handler handler = new Handler(Looper.getMainLooper());
-
- private final Runnable centerAroundFix = new Runnable() {
- public void run() {
- handler.post(new Runnable() {
- public void run() {
- locationButton.setEnabled(true);
- showZoomDialog();
- }
- });
- }
- };
-
- private void showGPSDisabledAlertToUser() {
- AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this);
- alertDialogBuilder.setMessage(getString(R.string.gps_enable_message))
- .setCancelable(false)
- .setPositiveButton(getString(R.string.enable_gps),
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int id) {
- startActivityForResult(
- new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS), 0);
- }
- });
- alertDialogBuilder.setNegativeButton(getString(R.string.cancel),
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int id) {
- dialog.cancel();
- }
- });
- AlertDialog alert = alertDialogBuilder.create();
- alert.show();
- }
-
- private void upMyLocationOverlayLayers() {
- LocationManager locationManager = (LocationManager) getSystemService(LOCATION_SERVICE);
- if (locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
- overlayMyLocationLayers();
- } else {
- showGPSDisabledAlertToUser();
- }
-
- }
-
- private void overlayMyLocationLayers() {
- map.getOverlays().add(myLocationOverlay);
- myLocationOverlay.setEnabled(true);
- myLocationOverlay.enableMyLocation();
- }
-
- private void zoomToMyLocation() {
- if (myLocationOverlay.getMyLocation() != null) {
- map.getController().setZoom(15);
- map.getController().setCenter(myLocationOverlay.getMyLocation());
- }
- }
-
- private void disableMyLocation() {
- LocationManager locationManager = (LocationManager) getSystemService(LOCATION_SERVICE);
- if (locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
- myLocationOverlay.setEnabled(false);
- myLocationOverlay.disableFollowLocation();
- myLocationOverlay.disableMyLocation();
- gpsStatus = false;
- }
- }
-
- private void overlayPointPathListener() {
- overlayEvents = new MapEventsOverlay(receive);
- polyline = new Polyline();
- polyline.setColor(Color.RED);
- Paint paint = polyline.getPaint();
- paint.setStrokeWidth(STROKE_WIDTH);
- map.getOverlays().add(polyline);
- map.getOverlays().add(overlayEvents);
- map.invalidate();
+ private void addVertex(MapPoint point) {
+ map.appendPointToShape(shapeId, point);
+ clearButton.setEnabled(true);
}
- private void clearFeatures() {
- clearButtonTest = false;
- mapMarkers.clear();
- polyline.setPoints(new ArrayList());
- map.getOverlays().clear();
+ private void clear() {
+ map.clearFeatures();
+ shapeId = map.addDraggableShape(new ArrayList<>());
clearButton.setEnabled(false);
- //saveButton.setEnabled(false);
- overlayPointPathListener();
- overlayMyLocationLayers();
- map.invalidate();
-
}
private void showClearDialog() {
- AlertDialog.Builder builder = new AlertDialog.Builder(this);
- builder.setMessage(getString(R.string.geo_clear_warning))
- .setPositiveButton(getString(R.string.clear),
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int id) {
- clearFeatures();
- }
- })
- .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int id) {
-
- }
- }).show();
-
+ if (map.getPointsOfShape(shapeId).isEmpty()) {
+ new AlertDialog.Builder(this)
+ .setMessage(R.string.geo_clear_warning)
+ .setPositiveButton(R.string.clear, (dialog, id) -> clear())
+ .setNegativeButton(R.string.cancel, null)
+ .show();
+ }
}
private void showBackDialog() {
- AlertDialog.Builder builder = new AlertDialog.Builder(this);
- builder.setMessage(getString(R.string.geo_exit_warning))
- .setPositiveButton(getString(R.string.discard),
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int id) {
- finish();
- }
- })
- .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int id) {
-
- }
- }).show();
+ new AlertDialog.Builder(this)
+ .setMessage(getString(R.string.geo_exit_warning))
+ .setPositiveButton(R.string.discard, (dialog, id) -> finish())
+ .setNegativeButton(R.string.cancel, null)
+ .show();
}
- private String generateReturnString() {
- String tempString = "";
- if (mapMarkers.size() > 1) {
- if (Collections.frequency(mapMarkers, mapMarkers.get(0)) < 2) {
- mapMarkers.add(mapMarkers.get(0));
- }
- for (int i = 0; i < mapMarkers.size(); i++) {
- String lat = Double.toString(mapMarkers.get(i).getPosition().getLatitude());
- String lng = Double.toString(mapMarkers.get(i).getPosition().getLongitude());
- String alt = "0.0";
- String acu = "0.0";
- tempString = tempString + lat + " " + lng + " " + alt + " " + acu + ";";
- }
- }
- return tempString;
- }
-
- private void returnLocation() {
- finalReturnString = generateReturnString();
- Intent i = new Intent();
- i.putExtra(
- FormEntryActivity.GEOSHAPE_RESULTS,
- finalReturnString);
- setResult(RESULT_OK, i);
- if (mapMarkers.size() < 4) {
+ private void finishWithResult() {
+ List points = map.getPointsOfShape(shapeId);
+ if (points.size() < 3) {
ToastUtils.showShortToastInMiddle(getString(R.string.polygon_validator));
- } else {
- finish();
- }
- }
-
- private void update_polygon() {
- List points = new ArrayList<>();
- for (int i = 0; i < mapMarkers.size(); i++) {
- points.add(mapMarkers.get(i).getPosition());
+ return;
}
- points.add(mapMarkers.get(0).getPosition());
- polyline.setPoints(points);
- map.invalidate();
+ setResult(RESULT_OK, new Intent().putExtra(
+ FormEntryActivity.GEOSHAPE_RESULTS, formatPoints(points)));
+ finish();
}
- private final MapEventsReceiver receive = new MapEventsReceiver() {
- @Override
- public boolean longPressHelper(GeoPoint point) {
- if (!clearButtonTest) {
- clearButton.setEnabled(true);
- clearButtonTest = true;
+ /**
+ * Parses a form result string, as previously formatted by formatPoints,
+ * into a list of polygon vertices.
+ */
+ private List parsePoints(String coords) {
+ List points = new ArrayList<>();
+ for (String vertex : (coords == null ? "" : coords).split(";")) {
+ String[] words = vertex.trim().split(" ");
+ if (words.length < 2) continue;
+ double lat, lon;
+ try {
+ lat = Double.parseDouble(words[0]);
+ lon = Double.parseDouble(words[1]);
+ } catch (NumberFormatException e) {
+ continue;
}
- Marker marker = new Marker(map);
- marker.setPosition(point);
- marker.setDraggable(true);
- marker.setIcon(ContextCompat.getDrawable(GeoShapeOsmMapActivity.this, R.drawable.ic_place_black));
- marker.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM);
- marker.setOnMarkerClickListener(nullMarkerListener);
- mapMarkers.add(marker);
- marker.setDraggable(true);
- marker.setOnMarkerDragListener(dragListener);
- map.getOverlays().add(marker);
- List points = polyline.getPoints();
- points.add(marker.getPosition());
- polyline.setPoints(points);
- update_polygon();
- map.invalidate();
- return false;
+ points.add(new MapPoint(lat, lon));
}
-
- @Override
- public boolean singleTapConfirmedHelper(GeoPoint arg0) {
- return false;
+ // Polygons are stored with a last point that duplicates the first
+ // point. To prepare the polygon for display and editing, we need
+ // to remove this duplicate point.
+ int count = points.size();
+ if (count > 1 && points.get(0).equals(points.get(count - 1))) {
+ points.remove(count - 1);
}
- };
-
- private final MapListener mapViewListener = new MapListener() {
- @Override
- public boolean onZoom(ZoomEvent zoomLev) {
- zoomLevel = zoomLev.getZoomLevel();
- return false;
- }
-
- @Override
- public boolean onScroll(ScrollEvent arg0) {
- return false;
- }
-
- };
-
- private final Marker.OnMarkerDragListener dragListener = new Marker.OnMarkerDragListener() {
- @Override
- public void onMarkerDragStart(Marker marker) {
-
- }
-
- @Override
- public void onMarkerDragEnd(Marker marker) {
- update_polygon();
-
- }
-
- @Override
- public void onMarkerDrag(Marker marker) {
- update_polygon();
-
- }
- };
-
- private final Marker.OnMarkerClickListener nullMarkerListener = new Marker.OnMarkerClickListener() {
-
- @Override
- public boolean onMarkerClick(Marker arg0, MapView arg1) {
- return false;
- }
- };
-
- /*
- This functions should be added to the mapHelper Class
+ return points;
+ }
+ /**
+ * Serializes a list of polygon vertices into a string, in the format
+ * appropriate for storing as the result of this form question.
*/
- private void zoomToBounds() {
- map.getController().setZoom(4);
- map.invalidate();
- Handler handler = new Handler();
- handler.postDelayed(new Runnable() {
- public void run() {
- double minLat = Double.MAX_VALUE;
- double maxLat = Double.MIN_VALUE;
- double minLong = Double.MAX_VALUE;
- double maxLong = Double.MIN_VALUE;
- Integer size = mapMarkers.size();
- for (int i = 0; i < size; i++) {
- GeoPoint tempMarker = mapMarkers.get(i).getPosition();
- if (tempMarker.getLatitude() < minLat) {
- minLat = tempMarker.getLatitude();
- }
- if (tempMarker.getLatitude() > maxLat) {
- maxLat = tempMarker.getLatitude();
- }
- if (tempMarker.getLongitude() < minLong) {
- minLong = tempMarker.getLongitude();
- }
- if (tempMarker.getLongitude() > maxLong) {
- maxLong = tempMarker.getLongitude();
- }
- }
- BoundingBox boundingBox = new BoundingBox(maxLat, maxLong, minLat, minLong);
- map.zoomToBoundingBox(boundingBox, false);
- map.invalidate();
+ private String formatPoints(List points) {
+ String result = "";
+ if (points.size() > 1) {
+ // Polygons are stored with a last point that duplicates the
+ // first point. Add this extra point if it's not already present.
+ if (!points.get(0).equals(points.get(points.size() - 1))) {
+ points.add(points.get(0));
}
- }, 100);
- map.invalidate();
-
+ for (MapPoint point : points) {
+ result += String.format(Locale.US, "%.6f %.6f 0.0 0.0;", point.lat, point.lon);
+ }
+ }
+ return result;
}
public void showZoomDialog() {
-
if (zoomDialog == null) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(getString(R.string.zoom_to_where));
@@ -561,7 +274,7 @@ public void onCancel(DialogInterface dialog) {
zoomDialog = builder.create();
}
//If feature enable zoom to button else disable
- if (myLocationOverlay.getMyLocation() != null) {
+ if (map.getGpsLocation() != null) {
zoomLocationButton.setEnabled(true);
zoomLocationButton.setBackgroundColor(Color.parseColor("#50cccccc"));
zoomLocationButton.setTextColor(themeUtils.getPrimaryTextColor());
@@ -571,7 +284,7 @@ public void onCancel(DialogInterface dialog) {
zoomLocationButton.setTextColor(Color.parseColor("#FF979797"));
}
- if (!mapMarkers.isEmpty()) {
+ if (!map.getPointsOfShape(shapeId).isEmpty()) {
zoomPointButton.setEnabled(true);
zoomPointButton.setBackgroundColor(Color.parseColor("#50cccccc"));
zoomPointButton.setTextColor(themeUtils.getPrimaryTextColor());
diff --git a/collect_app/src/main/java/org/odk/collect/android/map/GoogleMapFragment.java b/collect_app/src/main/java/org/odk/collect/android/map/GoogleMapFragment.java
index 7a888a71dfd..44734461ec6 100644
--- a/collect_app/src/main/java/org/odk/collect/android/map/GoogleMapFragment.java
+++ b/collect_app/src/main/java/org/odk/collect/android/map/GoogleMapFragment.java
@@ -1,7 +1,6 @@
package org.odk.collect.android.map;
import android.app.AlertDialog;
-import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.location.Location;
@@ -34,9 +33,8 @@
import java.util.Map;
public class GoogleMapFragment extends SupportMapFragment implements MapFragment, GoogleMap.OnMapClickListener, GoogleMap.OnMarkerDragListener, GoogleMap.OnMapLongClickListener, LocationListener, LocationClient.LocationClientListener{
- protected Context context;
protected GoogleMap map;
- protected MapFragment.PointListener gpsLocationListener;
+ protected List gpsLocationReadyListeners = new ArrayList<>();
protected MapFragment.PointListener clickListener;
protected MapFragment.PointListener longPressListener;
protected LocationClient locationClient;
@@ -47,7 +45,6 @@ public class GoogleMapFragment extends SupportMapFragment implements MapFragment
@VisibleForTesting protected AlertDialog gpsErrorDialog;
@Override public void addTo(@NonNull FragmentActivity activity, int containerId, @Nullable ReadyListener listener) {
- context = activity;
activity.getSupportFragmentManager()
.beginTransaction().add(containerId, this).commit();
getMapAsync((GoogleMap map) -> {
@@ -82,7 +79,7 @@ public GoogleMap getGoogleMap() {
}
@Override public double setZoom(double requestedZoom) {
- float actualZoom = (float) requestedZoom;
+ float actualZoom = (float) clamp(requestedZoom, map.getMinZoomLevel(), map.getMaxZoomLevel());
map.animateCamera(CameraUpdateFactory.zoomTo(actualZoom));
return actualZoom;
}
@@ -99,7 +96,7 @@ public GoogleMap getGoogleMap() {
}
}
- @Override public void zoomToBoundingBox(Iterable points, double paddingFactor) {
+ @Override public void zoomToBoundingBox(Iterable points, double scaleFactor) {
if (points == null) return;
LatLngBounds.Builder builder = new LatLngBounds.Builder();
@@ -109,7 +106,7 @@ public GoogleMap getGoogleMap() {
count++;
}
if (count > 0) {
- final LatLngBounds bounds = expandBounds(builder.build(), 1/paddingFactor);
+ final LatLngBounds bounds = expandBounds(builder.build(), 1/scaleFactor);
new Handler().postDelayed(() -> {
map.animateCamera(CameraUpdateFactory.newLatLngBounds(bounds, 0));
}, 100);
@@ -166,17 +163,17 @@ protected LatLngBounds expandBounds(LatLngBounds bounds, double factor) {
features.clear();
}
- @Override public void setClickListener(PointListener listener) {
+ @Override public void setClickListener(@Nullable PointListener listener) {
clickListener = listener;
}
- @Override public void setLongPressListener(PointListener listener) {
+ @Override public void setLongPressListener(@Nullable PointListener listener) {
longPressListener = listener;
}
@Override public void setGpsLocationEnabled(boolean enabled) {
if (enabled) {
- locationClient = LocationClients.clientForContext(context);
+ locationClient = LocationClients.clientForContext(getActivity());
locationClient.setListener(this);
locationClient.start();
} else {
@@ -184,18 +181,23 @@ protected LatLngBounds expandBounds(LatLngBounds bounds, double factor) {
}
}
- @Override public void setGpsLocationListener(PointListener listener) {
- gpsLocationListener = listener;
+ @Override public void runOnGpsLocationReady(@NonNull ReadyListener listener) {
+ if (lastLocationFix != null) {
+ listener.onReady(this);
+ } else {
+ gpsLocationReadyListeners.add(listener);
+ }
}
@Override public void onLocationChanged(Location location) {
- if (gpsLocationListener != null) {
- lastLocationFix = new MapPoint(location.getLatitude(), location.getLongitude());
- gpsLocationListener.onPoint(lastLocationFix);
+ lastLocationFix = new MapPoint(location.getLatitude(), location.getLongitude());
+ for (ReadyListener listener : gpsLocationReadyListeners) {
+ listener.onReady(this);
}
+ gpsLocationReadyListeners.clear();
}
- @Override public MapPoint getGpsLocation() {
+ @Override public @Nullable MapPoint getGpsLocation() {
return lastLocationFix;
}
@@ -237,7 +239,7 @@ protected LatLngBounds expandBounds(LatLngBounds bounds, double factor) {
@Override public void onClientStop() { }
protected void showGpsDisabledAlert() {
- gpsErrorDialog = new AlertDialog.Builder(context)
+ gpsErrorDialog = new AlertDialog.Builder(getActivity())
.setMessage(getString(R.string.gps_enable_message))
.setCancelable(false)
.setPositiveButton(getString(R.string.enable_gps),
@@ -259,11 +261,15 @@ protected void updateFeatures() {
}
}
- protected static MapPoint fromLatLng(LatLng latLng) {
+ protected static double clamp(double x, double min, double max) {
+ return x < min ? min : x > max ? max : x;
+ }
+
+ protected static @NonNull MapPoint fromLatLng(@NonNull LatLng latLng) {
return new MapPoint(latLng.latitude, latLng.longitude);
}
- protected static LatLng toLatLng(MapPoint point) {
+ protected static @NonNull LatLng toLatLng(@NonNull MapPoint point) {
return new LatLng(point.lat, point.lon);
}
diff --git a/collect_app/src/main/java/org/odk/collect/android/map/MapFragment.java b/collect_app/src/main/java/org/odk/collect/android/map/MapFragment.java
index 3474e50ec9d..6a1e91b3a67 100644
--- a/collect_app/src/main/java/org/odk/collect/android/map/MapFragment.java
+++ b/collect_app/src/main/java/org/odk/collect/android/map/MapFragment.java
@@ -10,6 +10,22 @@
/**
* Interface for Fragments that render a map view. The plan is to have one
* implementation for each map SDK, e.g. GoogleMapFragment, OsmMapFragment, etc.
+ *
+ * This is intended to be a single map API that provides all functionality needed
+ * for the three geo widgets (collecting or editing a point, a trace, or a shape):
+ * - Basic control of the viewport (panning, zooming)
+ * - Displaying and getting the current GPS location
+ * - Requesting a callback on the first GPS location fix
+ * - Requesting callbacks for short clicks and long presses on the map
+ * - (to do) Adding editable points to the map
+ * - (to do) Adding editable traces (polylines) to the map
+ * - Adding editable shapes (closed polygons) to the map
+ *
+ * Editable points, traces, and shapes are called "map features" in this API.
+ * To keep the API small, features are not exposed as objects; instead, they are
+ * identified by integer feature IDs. To keep the API unified (instead of behaving
+ * like three different APIs), the map always supports all three kinds of features,
+ * even though the geo widgets only use one kind of feature at a time.
*/
public interface MapFragment {
/**
@@ -84,8 +100,12 @@ public interface MapFragment {
/** Gets the last GPS location fix, or null if there hasn't been one. */
@Nullable MapPoint getGpsLocation();
- /** Registers a callback for GPS location fixes. */
- void setGpsLocationListener(@Nullable PointListener listener);
+ /**
+ * Queues a callback to be invoked as soon as a GPS location fix is
+ * available. If there already is a location fix, the callback is invoked
+ * immediately; otherwise, when a fix is obtained, it will be invoked once.
+ */
+ void runOnGpsLocationReady(@NonNull ReadyListener listener);
/** Registers a callback for a click on the map. */
void setClickListener(@Nullable PointListener listener);
diff --git a/collect_app/src/main/java/org/odk/collect/android/map/OsmMapFragment.java b/collect_app/src/main/java/org/odk/collect/android/map/OsmMapFragment.java
new file mode 100644
index 00000000000..f93d0dc4510
--- /dev/null
+++ b/collect_app/src/main/java/org/odk/collect/android/map/OsmMapFragment.java
@@ -0,0 +1,317 @@
+package org.odk.collect.android.map;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.location.LocationManager;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.VisibleForTesting;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.content.ContextCompat;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import org.odk.collect.android.R;
+import org.odk.collect.android.location.client.LocationClient;
+import org.osmdroid.api.IGeoPoint;
+import org.osmdroid.events.MapEventsReceiver;
+import org.osmdroid.util.BoundingBox;
+import org.osmdroid.util.GeoPoint;
+import org.osmdroid.views.MapView;
+import org.osmdroid.views.overlay.MapEventsOverlay;
+import org.osmdroid.views.overlay.Marker;
+import org.osmdroid.views.overlay.Polyline;
+import org.osmdroid.views.overlay.mylocation.MyLocationNewOverlay;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class OsmMapFragment extends Fragment implements MapFragment, MapEventsReceiver {
+ protected MapView map;
+ protected ReadyListener readyListener;
+ protected List gpsLocationReadyListeners = new ArrayList<>();
+ protected MapFragment.PointListener clickListener;
+ protected MapFragment.PointListener longPressListener;
+ protected MyLocationNewOverlay myLocationOverlay;
+
+ protected LocationClient locationClient;
+ protected MapPoint lastLocationFix = null;
+ protected int nextFeatureId = 1;
+ protected Map features = new HashMap<>();
+
+ @VisibleForTesting protected AlertDialog gpsErrorDialog;
+
+ @Override public void addTo(@NonNull FragmentActivity activity, int containerId, @Nullable ReadyListener listener) {
+ readyListener = listener;
+ activity.getSupportFragmentManager()
+ .beginTransaction().add(containerId, this).commit();
+ }
+
+ @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.osm_map_layout, container, false);
+ map = view.findViewById(R.id.osm_map_view);
+ map.setMultiTouchControls(true);
+ map.setBuiltInZoomControls(true);
+ map.setTilesScaledToDpi(true);
+ map.getOverlays().add(new MapEventsOverlay(this));
+ myLocationOverlay = new MyLocationNewOverlay(map);
+ if (readyListener != null) readyListener.onReady(this);
+ return view;
+ }
+
+ @Override public boolean singleTapConfirmedHelper(GeoPoint geoPoint) {
+ if (clickListener != null) {
+ clickListener.onPoint(fromGeoPoint(geoPoint));
+ return true;
+ }
+ return false;
+ }
+
+ @Override public boolean longPressHelper(GeoPoint geoPoint) {
+ if (longPressListener != null) {
+ longPressListener.onPoint(fromGeoPoint(geoPoint));
+ return true;
+ }
+ return false;
+ }
+
+ @Override public double getZoom() {
+ return map.getZoomLevel();
+ }
+
+ @Override public double setZoom(double requestedZoom) {
+ int actualZoom = (int) Math.round(
+ clamp(requestedZoom, map.getMinZoomLevel(), map.getMaxZoomLevel()));
+ map.getController().setZoom(actualZoom);
+ return actualZoom;
+ }
+
+ @Override public @NonNull MapPoint getCenter() {
+ return fromGeoPoint(map.getMapCenter());
+ }
+
+ @Override public void setCenter(@Nullable MapPoint center) {
+ if (center != null) {
+ map.getController().setCenter(toGeoPoint(center));
+ }
+ }
+
+ @Override public void zoomToBoundingBox(Iterable points, double scaleFactor) {
+ if (points == null) return;
+ int count = 0;
+ List geoPoints = new ArrayList<>();
+ for (MapPoint point : points) {
+ geoPoints.add(toGeoPoint(point));
+ count++;
+ }
+ if (count > 0) {
+ map.zoomToBoundingBox(BoundingBox.fromGeoPoints(
+ geoPoints).increaseByScale((float) (1/scaleFactor)), true);
+ }
+ }
+
+ @Override public int addDraggableShape(Iterable points) {
+ int featureId = nextFeatureId++;
+ features.put(featureId, new DraggableShape(map, points));
+ return featureId;
+ }
+
+ @Override public void appendPointToShape(int featureId, @NonNull MapPoint point) {
+ MapFeature feature = features.get(featureId);
+ if (feature != null && feature instanceof DraggableShape) {
+ ((DraggableShape) feature).addPoint(point);
+ }
+ }
+
+ @Override public @NonNull List getPointsOfShape(int featureId) {
+ MapFeature feature = features.get(featureId);
+ if (feature != null && feature instanceof DraggableShape) {
+ return ((DraggableShape) feature).getPoints();
+ }
+ return new ArrayList<>();
+ }
+
+ @Override public void removeFeature(int featureId) {
+ MapFeature feature = features.get(featureId);
+ if (feature != null) feature.dispose();
+ }
+
+ @Override public void clearFeatures() {
+ map.getOverlays().clear();
+ map.getOverlays().add(new MapEventsOverlay(this));
+ map.getOverlays().add(myLocationOverlay);
+ map.invalidate();
+ features.clear();
+ }
+
+ @Override public void setClickListener(@Nullable PointListener listener) {
+ clickListener = listener;
+ }
+
+ @Override public void setLongPressListener(@Nullable PointListener listener) {
+ longPressListener = listener;
+ }
+
+ @Override public void runOnGpsLocationReady(@NonNull ReadyListener listener) {
+ myLocationOverlay.runOnFirstFix(() -> { listener.onReady(this); });
+ }
+
+ @Override public void setGpsLocationEnabled(boolean enabled) {
+ if (enabled) {
+ LocationManager locationManager = (LocationManager) getContext().getSystemService(Context.LOCATION_SERVICE);
+ if (locationManager != null && locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
+ map.getOverlays().add(myLocationOverlay);
+ myLocationOverlay.setEnabled(true);
+ myLocationOverlay.enableMyLocation();
+ } else {
+ showGpsDisabledAlert();
+ }
+ } else {
+ myLocationOverlay.setEnabled(false);
+ myLocationOverlay.disableFollowLocation();
+ myLocationOverlay.disableMyLocation();
+ }
+ }
+
+ @Override public @Nullable MapPoint getGpsLocation() {
+ IGeoPoint geoPoint = myLocationOverlay.getMyLocation();
+ return geoPoint == null ? null : fromGeoPoint(geoPoint);
+ }
+
+ protected void showGpsDisabledAlert() {
+ gpsErrorDialog = new AlertDialog.Builder(getContext())
+ .setMessage(getString(R.string.gps_enable_message))
+ .setCancelable(false)
+ .setPositiveButton(getString(R.string.enable_gps),
+ (dialog, id) -> startActivityForResult(
+ new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS), 0))
+ .setNegativeButton(getString(R.string.cancel),
+ (dialog, id) -> dialog.cancel())
+ .create();
+ gpsErrorDialog.show();
+ }
+
+ @VisibleForTesting public AlertDialog getGpsErrorDialog() {
+ return gpsErrorDialog;
+ }
+
+ protected void updateFeatures() {
+ for (MapFeature feature : features.values()) {
+ feature.update();
+ }
+ }
+
+ protected static double clamp(double x, double min, double max) {
+ return x < min ? min : x > max ? max : x;
+ }
+
+ protected static @NonNull MapPoint fromGeoPoint(@NonNull IGeoPoint geoPoint) {
+ return new MapPoint(geoPoint.getLatitude(), geoPoint.getLongitude());
+ }
+
+ protected static @NonNull GeoPoint toGeoPoint(@NonNull MapPoint point) {
+ return new GeoPoint(point.lat, point.lon);
+ }
+
+ /**
+ * A MapFeature is a physical feature on a map, such as a point, a road,
+ * a building, a region, etc. It is presented to the user as one editable
+ * object, though its appearance may be constructed from multiple overlays
+ * (e.g. geometric elements, handles for manipulation, etc.).
+ */
+ interface MapFeature {
+ /** Updates the feature's geometry after any UI handles have moved. */
+ void update();
+
+ /** Removes the feature from the map, leaving it no longer usable. */
+ void dispose();
+ }
+
+ /** A polygon that can be manipulated by dragging markers at its vertices. */
+ protected static class DraggableShape implements MapFeature, Marker.OnMarkerClickListener, Marker.OnMarkerDragListener {
+ final MapView map;
+ final List markers = new ArrayList<>();
+ final Polyline polyline;
+ public static final int STROKE_WIDTH = 5;
+
+ public DraggableShape(MapView map, Iterable points) {
+ this.map = map;
+ polyline = new Polyline();
+ polyline.setColor(Color.RED);
+ Paint paint = polyline.getPaint();
+ paint.setStrokeWidth(STROKE_WIDTH);
+ map.getOverlays().add(polyline);
+ for (MapPoint point : points) {
+ addMarker(point);
+ }
+ update();
+ }
+
+ public void update() {
+ List geoPoints = new ArrayList<>();
+ for (Marker marker : markers) {
+ geoPoints.add(marker.getPosition());
+ }
+ polyline.setPoints(geoPoints);
+ map.invalidate();
+ }
+
+ public void dispose() {
+ for (Marker marker : markers) {
+ map.getOverlays().remove(marker);
+ }
+ markers.clear();
+ update();
+ }
+
+ public List getPoints() {
+ List points = new ArrayList<>();
+ for (Marker marker : markers) {
+ points.add(fromGeoPoint(marker.getPosition()));
+ }
+ return points;
+ }
+
+ public void addPoint(MapPoint point) {
+ addMarker(point);
+ update();
+ }
+
+ protected void addMarker(MapPoint point) {
+ Marker marker = new Marker(map);
+ marker.setPosition(toGeoPoint(point));
+ marker.setDraggable(true);
+ marker.setIcon(ContextCompat.getDrawable(map.getContext(), R.drawable.ic_place_black));
+ marker.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM);
+ marker.setOnMarkerClickListener(this);
+ marker.setOnMarkerDragListener(this);
+ map.getOverlays().add(marker);
+ markers.add(marker);
+ }
+
+ @Override public void onMarkerDragStart(Marker marker) {
+ }
+
+ @Override public void onMarkerDragEnd(Marker marker) {
+ update();
+ }
+
+ @Override public void onMarkerDrag(Marker marker) {
+ update();
+ }
+
+ @Override public boolean onMarkerClick(Marker marker, MapView map) {
+ // Prevent the text bubble from appearing when a marker is clicked.
+ return false;
+ }
+ }
+}
diff --git a/collect_app/src/main/java/org/odk/collect/android/widgets/GeoShapeWidget.java b/collect_app/src/main/java/org/odk/collect/android/widgets/GeoShapeWidget.java
index ff1680f2d67..bb6dfbe37ee 100644
--- a/collect_app/src/main/java/org/odk/collect/android/widgets/GeoShapeWidget.java
+++ b/collect_app/src/main/java/org/odk/collect/android/widgets/GeoShapeWidget.java
@@ -31,14 +31,17 @@
import org.odk.collect.android.activities.FormEntryActivity;
import org.odk.collect.android.activities.GeoShapeGoogleMapActivity;
import org.odk.collect.android.activities.GeoShapeOldGoogleMapActivity;
+import org.odk.collect.android.activities.GeoShapeOldOsmMapActivity;
import org.odk.collect.android.activities.GeoShapeOsmMapActivity;
import org.odk.collect.android.listeners.PermissionListener;
import org.odk.collect.android.preferences.PreferenceKeys;
import org.odk.collect.android.utilities.PlayServicesUtil;
import org.odk.collect.android.widgets.interfaces.BinaryWidget;
-import static org.odk.collect.android.utilities.ApplicationConstants.RequestCodes;
-import static org.odk.collect.android.utilities.PermissionUtils.requestLocationPermissions;
+import static org.odk.collect.android.utilities.ApplicationConstants
+ .RequestCodes;
+import static org.odk.collect.android.utilities.PermissionUtils
+ .requestLocationPermissions;
/**
* GeoShapeWidget is the widget that allows the user to get Collect multiple GPS points.
@@ -94,17 +97,15 @@ private void startGeoShapeActivity() {
Intent i;
if (mapSDK.equals(GOOGLE_MAP_KEY)) {
if (PlayServicesUtil.isGooglePlayServicesAvailable(getContext())) {
- if (isRingerOn()) {
- i = new Intent(getContext(), GeoShapeOldGoogleMapActivity.class);
- } else {
- i = new Intent(getContext(), GeoShapeGoogleMapActivity.class);
- }
+ i = new Intent(getContext(), isRingerOn() ?
+ GeoShapeOldGoogleMapActivity.class : GeoShapeGoogleMapActivity.class);
} else {
PlayServicesUtil.showGooglePlayServicesAvailabilityErrorDialog(getContext());
return;
}
} else {
- i = new Intent(getContext(), GeoShapeOsmMapActivity.class);
+ i = new Intent(getContext(), isRingerOn() ?
+ GeoShapeOldOsmMapActivity.class : GeoShapeOsmMapActivity.class);
}
String s = answerDisplay.getText().toString();
if (s.length() != 0) {