Skip to content

Commit

Permalink
Allow filtering Mapillary data by date
Browse files Browse the repository at this point in the history
This adds functionality to set a date range for Mapillary data to be
filtered on.
  • Loading branch information
simonpoole committed Oct 25, 2023
1 parent adf173f commit d2d25f0
Show file tree
Hide file tree
Showing 17 changed files with 413 additions and 50 deletions.
4 changes: 2 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -609,7 +609,7 @@ dependencies {
implementation "androidx.appcompat:appcompat-resources:1.6.1"
implementation "androidx.recyclerview:recyclerview:1.1.0"
implementation "androidx.preference:preference:1.1.0"
implementation "com.google.android.material:material:1.3.0"
implementation "com.google.android.material:material:1.8.0"
implementation "androidx.annotation:annotation:1.1.0"
implementation "androidx.core:core:1.7.0"
implementation "androidx.exifinterface:exifinterface:1.3.6"
Expand All @@ -623,7 +623,7 @@ dependencies {

//non-Android google
implementation 'com.google.protobuf:protobuf-java:3.12.2'
implementation "com.google.code.gson:gson:2.8.9"
implementation "com.google.code.gson:gson:2.10.1"
implementation "com.google.protobuf:protobuf-java:$googleProtobufVersion"
implementation 'com.google.openlocationcode:openlocationcode:1.0.4'

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package de.blau.android.layer.mapillary;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeUnit;

import org.junit.After;
Expand All @@ -19,18 +22,23 @@
import android.app.Instrumentation.ActivityMonitor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.rule.ActivityTestRule;
import androidx.test.uiautomator.UiDevice;
import androidx.test.uiautomator.UiObject2;
import de.blau.android.App;
import de.blau.android.LayerUtils;
import de.blau.android.Logic;
import de.blau.android.Main;
import de.blau.android.Map;
import de.blau.android.MockTileServer;
import de.blau.android.R;
import de.blau.android.TestUtils;
import de.blau.android.dialogs.DateRangeDialog;
import de.blau.android.layer.LayerDialogTest;
import de.blau.android.layer.LayerType;
import de.blau.android.photos.MapillaryViewerActivity;
import de.blau.android.prefs.AdvancedPrefDatabase;
Expand Down Expand Up @@ -71,9 +79,9 @@ public void setup() {

assertNotNull(main);
TestUtils.grantPermissons(device);

LayerUtils.removeLayer(main, LayerType.MAPILLARY);
tileServer = MockTileServer.setupTileServer(main, "mapillary.mbt", true, LayerType.MAPILLARY, TileType.MVT,
de.blau.android.layer.mapillary.MapOverlay.MAPILLARY_TILES_ID);
de.blau.android.layer.mapillary.MapillaryOverlay.MAPILLARY_TILES_ID);

mockApiServer = new MockWebServerPlus();
HttpUrl mockApiBaseUrl = mockApiServer.server().url("/");
Expand Down Expand Up @@ -110,14 +118,15 @@ public void teardown() {
} catch (IOException | NullPointerException e) {
// ignore
}
LayerUtils.removeLayer(main, LayerType.MAPILLARY);
try (TileLayerDatabase tlDb = new TileLayerDatabase(main); SQLiteDatabase db = tlDb.getWritableDatabase()) {
TileLayerDatabase.deleteLayerWithId(db, de.blau.android.layer.mapillary.MapOverlay.MAPILLARY_TILES_ID);
TileLayerDatabase.deleteLayerWithId(db, de.blau.android.layer.mapillary.MapillaryOverlay.MAPILLARY_TILES_ID);
}
instrumentation.waitForIdleSync();
}

/**
* Add mapillary layer
* Add mapillary layer and click on one image
*/
@Test
public void mapillaryLayer() {
Expand All @@ -126,7 +135,7 @@ public void mapillaryLayer() {
imageResponse.setResponseCode(200);
imageResponse.setBody("{\"thumb_2048_url\": \"" + mockImagesBaseUrl.toString() + "\",\"computed_geometry\": {\"type\": \"Point\",\"coordinates\": ["
+ "8.407748800863,47.412813485744]" + "},\"id\": \"178993950747668\"}");
de.blau.android.layer.mapillary.MapOverlay layer = (MapOverlay) map.getLayer(LayerType.MAPILLARY);
de.blau.android.layer.mapillary.MapillaryOverlay layer = (MapillaryOverlay) map.getLayer(LayerType.MAPILLARY);
assertNotNull(layer);
layer.flushCaches(main); // forces the layer to retrieve everything

Expand Down Expand Up @@ -183,4 +192,35 @@ public void mapillaryLayer() {
}
}
}

/**
* Add mapillary layer and click on one image that should be filtered away
*
* Unluckily there doesn't seem to be an easy way to drag the sliders
*/
@Test
public void mapillaryLayerFilter() {
TestUtils.unlock(device);
TestUtils.sleep();

UiObject2 menuButton = TestUtils.getLayerButton(device, "Mapillary", LayerDialogTest.MENU_BUTTON);
menuButton.click();
assertTrue(TestUtils.clickText(device, false, main.getString(R.string.layer_set_date_range), true, false));
assertTrue(TestUtils.findText(device, false, main.getString(R.string.date_range_title)));
assertTrue(TestUtils.clickText(device, false, main.getString(R.string.okay), true, false));
assertTrue(TestUtils.clickText(device, false, main.getString(R.string.Done), true, false));
MapillaryOverlay layer = (MapillaryOverlay) map.getLayer(LayerType.MAPILLARY);
layer.setDateRange(0L, 0L);
layer.invalidate();
//
map.getViewBox().moveTo(map, (int) (8.407748800863 * 1E7), (int) (47.412813485744 * 1E7));
map.invalidate();
TestUtils.zoomToLevel(device, main, 22);
TestUtils.clickAtCoordinates(device, map, 8.407748800863, 47.412813485744, true); // nothing should happen
if (TestUtils.clickText(device, false, "OK", true)) {
TestUtils.clickAtCoordinates(device, map, 8.407748800863, 47.412813485744, true);
}
assertFalse(TestUtils.clickMenuButton(device, main.getString(R.string.share), false, true));
}

}
41 changes: 40 additions & 1 deletion src/main/assets/mapillary-style.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,19 @@
"id": "overview",
"type": "line",
"source-layer": "overview",
"filter": [
"all",
[
">=",
"captured_at",
0
],
[
"<=",
"captured_at",
0
]
],
"maxzoom": 13,
"paint": {
"line-opacity": 1,
Expand Down Expand Up @@ -33,6 +46,19 @@
"id": "sequence",
"type": "line",
"source-layer": "sequence",
"filter": [
"all",
[
">=",
"captured_at",
0
],
[
"<=",
"captured_at",
0
]
],
"minzoom": 14,
"paint": {
"line-opacity": 1,
Expand Down Expand Up @@ -60,6 +86,19 @@
"id": "image",
"type": "symbol",
"source-layer": "image",
"filter": [
"all",
[
">=",
"captured_at",
0
],
[
"<=",
"captured_at",
0
]
],
"minzoom": 19,
"layout": {
"icon-image": "arrow",
Expand Down Expand Up @@ -92,7 +131,7 @@
"paint": {
"icon-color": "rgba(200, 100, 0, 1)"
}
}
}
],
"id": "mapillary"
}
2 changes: 1 addition & 1 deletion src/main/java/de/blau/android/DisambiguationMenu.java
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ private Type typeFromObject(@NonNull ClickedObject<?> clicked) {
return Type.GPX;
}
if (object instanceof de.blau.android.util.mvt.VectorTileDecoder.Feature) {
return clicked.getLayer() instanceof de.blau.android.layer.mapillary.MapOverlay ? Type.MAPILLARY : Type.MVT;
return clicked.getLayer() instanceof de.blau.android.layer.mapillary.MapillaryOverlay ? Type.MAPILLARY : Type.MVT;
}
if (object instanceof Photo) {
return Type.IMAGE;
Expand Down
8 changes: 4 additions & 4 deletions src/main/java/de/blau/android/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -1044,15 +1044,15 @@ private void processIntents() {
}
break;
case ACTION_MAPILLARY_SELECT:
final de.blau.android.layer.mapillary.MapOverlay mapillaryLayer = map != null
? (de.blau.android.layer.mapillary.MapOverlay) map.getLayer(LayerType.MAPILLARY)
final de.blau.android.layer.mapillary.MapillaryOverlay mapillaryLayer = map != null
? (de.blau.android.layer.mapillary.MapillaryOverlay) map.getLayer(LayerType.MAPILLARY)
: null;
if (mapillaryLayer != null) {
double[] coords = intent.getDoubleArrayExtra(de.blau.android.layer.mapillary.MapOverlay.COORDINATES_KEY);
double[] coords = intent.getDoubleArrayExtra(de.blau.android.layer.mapillary.MapillaryOverlay.COORDINATES_KEY);
if (coords != null) {
map.getViewBox().moveTo(map, (int) (coords[1] * 1E7), (int) (coords[0] * 1E7));
}
mapillaryLayer.select(intent.getIntExtra(de.blau.android.layer.mapillary.MapOverlay.SET_POSITION_KEY, 0));
mapillaryLayer.select(intent.getIntExtra(de.blau.android.layer.mapillary.MapillaryOverlay.SET_POSITION_KEY, 0));
}
break;
case ACTION_MAP_UPDATE:
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/de/blau/android/Map.java
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ public void setUpLayers(@NonNull Context ctx) {
}
break;
case MAPILLARY:
layer = new de.blau.android.layer.mapillary.MapOverlay(this);
layer = new de.blau.android.layer.mapillary.MapillaryOverlay(this);
break;
case BOOKMARKS:
layer = new de.blau.android.layer.bookmarks.MapOverlay(this);
Expand Down
152 changes: 152 additions & 0 deletions src/main/java/de/blau/android/dialogs/DateRangeDialog.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
package de.blau.android.dialogs;

import android.annotation.SuppressLint;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AlertDialog.Builder;
import androidx.appcompat.app.AppCompatDialog;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;

import com.google.android.material.slider.RangeSlider;

import de.blau.android.App;
import de.blau.android.R;
import de.blau.android.layer.DateRangeInterface;
import de.blau.android.listener.DoNothingListener;
import de.blau.android.util.ACRAHelper;
import de.blau.android.util.ImmersiveDialogFragment;
import de.blau.android.util.ThemeUtils;
import de.blau.android.views.layers.MapTilesLayer;

/**
* Display a dialog allowing the user to change some properties of the current background
*
*/
public class DateRangeDialog extends ImmersiveDialogFragment {

private static final String DEBUG_TAG = DateRangeDialog.class.getSimpleName();

private static final String TAG = "fragment_daterange";

public static final String LABEL_FORMAT = "yyyy MMM dd";

private static final String LAYERINDEX = "layer_index";
private static final String START_DATE = "start_date";
private static final String END_DATE = "end_date";

private static final long FROM_DATE = 1377990000000L;

private SimpleDateFormat labelDate = new SimpleDateFormat(LABEL_FORMAT);

/**
* Display a dialog allowing the user to change some properties of the current background
*
* @param activity the calling Activity
* @param layerIndex the index of the Layer
* @param endDate
* @param startDate
*/
public static void showDialog(@NonNull FragmentActivity activity, int layerIndex, long startDate, long endDate) {
dismissDialog(activity);
try {
FragmentManager fm = activity.getSupportFragmentManager();
DateRangeDialog backgroundPropertiesFragment = newInstance(layerIndex, startDate, endDate);
backgroundPropertiesFragment.show(fm, TAG);
} catch (IllegalStateException isex) {
Log.e(DEBUG_TAG, "showDialog", isex);
}
}

/**
* Dismiss the dialog
*
* @param activity the calling Activity
*/
private static void dismissDialog(@NonNull FragmentActivity activity) {
de.blau.android.dialogs.Util.dismissDialog(activity, TAG);
}

/**
* Get a new DateRange dialog instance
*
* @param layerIndex the index of the Layer
* @param startDate the start date in ms since the epoch
* @param endDate the end date in ms since the epoch
* @return a DateRange instance
*/
@NonNull
private static DateRangeDialog newInstance(int layerIndex, long startDate, long endDate) {
DateRangeDialog f = new DateRangeDialog();
Bundle args = new Bundle();
args.putInt(LAYERINDEX, layerIndex);
args.putLong(START_DATE, startDate);
args.putLong(END_DATE, endDate);
f.setArguments(args);
f.setShowsDialog(true);

return f;
}

@NonNull
@SuppressLint("InflateParams")
@Override
public AppCompatDialog onCreateDialog(Bundle savedInstanceState) {
Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle(R.string.date_range_title);
final LayoutInflater inflater = ThemeUtils.getLayoutInflater(getActivity());
DoNothingListener doNothingListener = new DoNothingListener();
View layout = inflater.inflate(R.layout.daterange, null);
RangeSlider slider = (RangeSlider) layout.findViewById(R.id.range_slider);
slider.setLabelFormatter((float v) -> labelDate.format(new Date(fromDays(v))));
MapTilesLayer<?> layer = (MapTilesLayer<?>) App.getLogic().getMap().getLayer(getArguments().getInt(LAYERINDEX, -1));
if (layer instanceof DateRangeInterface) {
long today = new Date().getTime();
slider.setValues(toDays(Math.max(FROM_DATE, getArguments().getLong(START_DATE, -1L))),
toDays(Math.min(getArguments().getLong(END_DATE, today), today)));
slider.setValueTo(toDays(today));
slider.addOnChangeListener((RangeSlider s, float arg1, boolean arg2) -> {
final List<Float> values = s.getValues();
if (values != null && values.size() == 2) {
((DateRangeInterface) layer).setDateRange(fromDays(values.get(0)), fromDays(values.get(1)));
}
layer.invalidate();
});
} else {
ACRAHelper.nocrashReport(null, "layer null or doesn't implement DateRangeInterface");
}
builder.setView(layout);
builder.setPositiveButton(R.string.okay, doNothingListener);

return builder.create();
}

/**
* Convert from days to ms
*
* @param days the number of days
* @return ms
*/
private static long fromDays(float days) {
return (long) (days * 24 * 3600000L);
}

/**
* Convert from ms to days
*
* @param ms the number of ms
* @return the number of days
*/
private static float toDays(long ms) {
return ms / (24f * 3600000L);
}
}
Loading

0 comments on commit d2d25f0

Please sign in to comment.