Skip to content

Commit

Permalink
Merge branch 'select_overpass_results'
Browse files Browse the repository at this point in the history
  • Loading branch information
simonpoole committed Sep 3, 2024
2 parents d9379b2 + 6ecadcd commit 98afb80
Show file tree
Hide file tree
Showing 5 changed files with 126 additions and 48 deletions.
16 changes: 12 additions & 4 deletions documentation/docs/help/en/Main map display.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ Select either the transfer icon ![Transfer](../images/menu_transfer.png) or the
* **Review changes...** - review current changes
* **Download current view** - download the area visible on the screen and merge it with existing data *(requires network connectivity)*
* **Clear and download current view** - clear any data in memory and then download the area visible on the screen *(requires network connectivity)*
* **Query Overpass...** - run a query against a Overpass API server *(requires network connectivity)*
* **Query Overpass...** - run a query against a Overpass API server, for more information see _Overpass queries_ below. *(requires network connectivity)*
* **Close current changeset** - manually close the current changeset *(only available if a changeset is open)*
* **Location based auto download** - download an area around the current location automatically *(requires network connectivity)* *(requires GPS)*
* **Pan and zoom auto download** - download the area shown in the current screen automatically *(requires network connectivity)*
Expand Down Expand Up @@ -228,16 +228,24 @@ Show the user preference screens. The settings are split into two sets: the firs
* **Remove EGM** - remove the EGM.
* **Import data style...** - import an additional data style from an XML file or from a ZIP archive containing an XML file and icons. This overwrites existing style files with the same name.
* **Load keys from file...** - load additional keys, for example API keys for background imagery, or other OAuth keys from a file.
* **JS Console** - start the JavaScript console for scripting the application. Note that this needs to be anabled in the "Advaced preferences":
* **JS Console** - start the JavaScript console for scripting the application. Note that this needs to be enabled in the "Advanced preferences".

### ![Find](../images/ic_menu_search_holo_light.png) Find

Search for a location and pan to it with the OpenStreetMap Nominatim or Photon service *(requires network connectivity)*

### Search for objects

Search for OSM objects in the loaded data using JOSMs search/filter expressions. See [JOSM filter documentation](http://vespucci.io/tutorials/object_search/) for more information. Besides searching in the loaded data alternatively you can create a
Overpass API query and use that to download data.
Search for OSM objects in the loaded data using JOSMs search/filter expressions. See [JOSM filter documentation](http://vespucci.io/tutorials/object_search/) for more information. Besides searching in the loaded data alternatively you can create a Overpass API query and use that to download data.

#### Overpass queries

The queries will be run against the Overpass server set in the "Advanced preferences". The standard behaviour is to replace the current data with the results of the query,
if you have unsaved changes the query will not run except if you select the _Merge result_ check box (this will merge the results of the query with the existing data).
If _Select result_ is checked, any results (with the exception of way nodes) will be selected and the app will zoom to the data.

Note: if you are generating the query from the JOSM query language you need to add an _inview_ term otherwise the query will be executed for the whole world, if you are manually
creating a query you should add _({{bbox}})_ terms for the same reason.

### Modes...

Expand Down
7 changes: 5 additions & 2 deletions src/main/assets/help/en/Main map display.html
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ <h3><img src="../images/menu_transfer.png" alt="Transfer" /> Transfer</h3>
<li><strong>Review changes...</strong> - review current changes</li>
<li><strong>Download current view</strong> - download the area visible on the screen and merge it with existing data <em>(requires network connectivity)</em></li>
<li><strong>Clear and download current view</strong> - clear any data in memory and then download the area visible on the screen <em>(requires network connectivity)</em></li>
<li><strong>Query Overpass...</strong> - run a query against a Overpass API server <em>(requires network connectivity)</em></li>
<li><strong>Query Overpass...</strong> - run a query against a Overpass API server, for more information see <em>Overpass queries</em> below. <em>(requires network connectivity)</em></li>
<li><strong>Close current changeset</strong> - manually close the current changeset <em>(only available if a changeset is open)</em></li>
<li><strong>Location based auto download</strong> - download an area around the current location automatically <em>(requires network connectivity)</em> <em>(requires GPS)</em></li>
<li><strong>Pan and zoom auto download</strong> - download the area shown in the current screen automatically <em>(requires network connectivity)</em></li>
Expand Down Expand Up @@ -247,12 +247,15 @@ <h3><img src="../images/menu_tools.png" alt="Tools" /> Tools…</h3>
<li><strong>Remove EGM</strong> - remove the EGM.</li>
<li><strong>Import data style...</strong> - import an additional data style from an XML file or from a ZIP archive containing an XML file and icons. This overwrites existing style files with the same name.</li>
<li><strong>Load keys from file...</strong> - load additional keys, for example API keys for background imagery, or other OAuth keys from a file.</li>
<li><strong>JS Console</strong> - start the JavaScript console for scripting the application. Note that this needs to be anabled in the &quot;Advaced preferences&quot;:</li>
<li><strong>JS Console</strong> - start the JavaScript console for scripting the application. Note that this needs to be enabled in the &quot;Advanced preferences&quot;.</li>
</ul>
<h3><img src="../images/ic_menu_search_holo_light.png" alt="Find" /> Find</h3>
<p>Search for a location and pan to it with the OpenStreetMap Nominatim or Photon service <em>(requires network connectivity)</em></p>
<h3>Search for objects</h3>
<p>Search for OSM objects in the loaded data using JOSMs search/filter expressions. See <a href="http://vespucci.io/tutorials/object_search/">JOSM filter documentation</a> for more information. Besides searching in the loaded data alternatively you can create a Overpass API query and use that to download data.</p>
<h4>Overpass queries</h4>
<p>The queries will be run against the Overpass server set in the &quot;Advanced preferences&quot;. The standard behaviour is to replace the current data with the results of the query, if you have unsaved changes the query will not run except if you select the <em>Merge result</em> check box (this will merge the results of the query with the existing data). If <em>Select result</em> is checked, any results (with the exception of way nodes) will be selected and the app will zoom to the data.</p>
<p>Note: if you are generating the query from the JOSM query language you need to add an <em>inview</em> term otherwise the query will be executed for the whole world, if you are manually creating a query you should add <em>({{bbox}})</em> terms for the same reason.</p>
<h3>Modes...</h3>
<p>This menu allows mode selection as via the <em>lock button</em> and enabling/disabling of the <em>simple mode</em>.</p>
<h3>Tag-Filter <em>(checkbox)</em></h3>
Expand Down
45 changes: 27 additions & 18 deletions src/main/java/de/blau/android/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -2631,24 +2631,33 @@ protected void onPostExecute(Void result) {
* @param text initial overpass query
*/
public static void showOverpassConsole(@NonNull final FragmentActivity activity, @Nullable String text) {
ConsoleDialog.showDialog(activity, R.string.overpass_console, R.string.merge_result, -1, text, null, (context, input, merge, flag2) -> {
Logic logic = App.getLogic();
if (!merge && logic != null && logic.hasChanges()) {
return Util.withHtmlColor(context, R.attr.errorTextColor, context.getString(R.string.overpass_query_would_overwrite));
}
AsyncResult result = de.blau.android.overpass.Server.query(context, de.blau.android.overpass.Server.replacePlaceholders(context, input), merge);
if (ErrorCodes.OK == result.getCode()) {
if (context instanceof Main) {
((Main) context).invalidateMap();
}
Storage storage = App.getDelegator().getCurrentStorage();
return context.getString(R.string.overpass_result, storage.getNodeCount(), storage.getWayCount(), storage.getRelationCount());
} else if (ErrorCodes.NOT_FOUND == result.getCode()) {
return context.getString(R.string.toast_nothing_found);
} else {
return Util.withHtmlColor(context, R.attr.errorTextColor, result.getMessage());
}
}, false);
ConsoleDialog.showDialog(activity, R.string.overpass_console, R.string.merge_result, R.string.select_result, text, null,
(context, input, merge, select) -> {
Logic logic = App.getLogic();
if (!merge && logic != null && logic.hasChanges()) {
return Util.withHtmlColor(context, R.attr.errorTextColor, context.getString(R.string.overpass_query_would_overwrite));
}
AsyncResult result = de.blau.android.overpass.Server.query(context, de.blau.android.overpass.Server.replacePlaceholders(context, input),
merge, select);
if (ErrorCodes.OK == result.getCode()) {
if (context instanceof Main) {
Main main = (Main) context;
main.mapLayout.post(() -> {
if (select) {
main.getEasyEditManager().editElements();
main.zoomTo(logic.getSelectedElements());
}
main.invalidateMap();
});
}
Storage storage = App.getDelegator().getCurrentStorage();
return context.getString(R.string.overpass_result, storage.getNodeCount(), storage.getWayCount(), storage.getRelationCount());
} else if (ErrorCodes.NOT_FOUND == result.getCode()) {
return context.getString(R.string.toast_nothing_found);
} else {
return Util.withHtmlColor(context, R.attr.errorTextColor, result.getMessage());
}
}, false);
}

/**
Expand Down
84 changes: 61 additions & 23 deletions src/main/java/de/blau/android/overpass/Server.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package de.blau.android.overpass;

import static de.blau.android.contract.Constants.LOG_TAG_LEN;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;
Expand All @@ -17,18 +19,23 @@
import de.blau.android.App;
import de.blau.android.AsyncResult;
import de.blau.android.ErrorCodes;
import de.blau.android.Logic;
import de.blau.android.Main;
import de.blau.android.exception.DataConflictException;
import de.blau.android.exception.OsmException;
import de.blau.android.exception.OsmServerException;
import de.blau.android.exception.StorageException;
import de.blau.android.geocode.QueryNominatim;
import de.blau.android.geocode.Search.SearchResult;
import de.blau.android.osm.BoundingBox;
import de.blau.android.osm.Node;
import de.blau.android.osm.OsmElement;
import de.blau.android.osm.OsmParser;
import de.blau.android.osm.Relation;
import de.blau.android.osm.Storage;
import de.blau.android.osm.StorageDelegator;
import de.blau.android.osm.ViewBox;
import de.blau.android.osm.Way;
import okhttp3.FormBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
Expand All @@ -38,21 +45,25 @@

public final class Server {

private static final String DEBUG_TAG = Server.class.getSimpleName().substring(0, Math.min(23, Server.class.getSimpleName().length()));
private static final int TAG_LEN = Math.min(LOG_TAG_LEN, Main.class.getSimpleName().length());
private static final String DEBUG_TAG = Server.class.getSimpleName().substring(0, TAG_LEN);

private static final long TIMEOUT = 2000;
private static final String BODY_DATA = "data";

private static final long TIMEOUT = 2000;
private static final int BASE_STATE = 0;
private static final int CURLY_1_STATE = 1;
private static final int CURLY_2_STATE = 2;
private static final int CURLY_FINISH_STATE = 3;
private static final int ARGUMENT_STATE = 4;
private static final int BASE_STATE = 0;
private static final int CURLY_1_STATE = 1;
private static final int CURLY_2_STATE = 2;
private static final int CURLY_FINISH_STATE = 3;
private static final int ARGUMENT_STATE = 4;

private static final String TURBO_GEOCODE_AREA = "geocodeArea";
private static final String TURBO_CENTER = "center";
private static final String TURBO_BBOX = "bbox";

private static final char CLOSE_BRACKET = '}';
private static final char OPEN_BRACKET = '{';
private static final char DOUBLE_COLON = ':';

private static final long OVERPASS_AREA_ID_OFFSET = 3600000000L;

Expand Down Expand Up @@ -102,7 +113,7 @@ public static String replacePlaceholders(@NonNull Context context, @NonNull Stri
case CURLY_2_STATE:
if (c == CLOSE_BRACKET) {
state = CURLY_FINISH_STATE;
} else if (c == ':') {
} else if (c == DOUBLE_COLON) {
state = ARGUMENT_STATE;
argument.setLength(0); // reset
} else {
Expand Down Expand Up @@ -195,27 +206,32 @@ private static String coordToStr(long coord) {
* @param context an Android Context
* @param query the query
* @param merge merge the received data instead of replacing existing data
* @param select if true select results
* @param handler a listener to call when we are done
*/
@NonNull
public static AsyncResult query(@NonNull final Context context, @NonNull String query, boolean merge) {
public static AsyncResult query(@NonNull final Context context, @NonNull String query, boolean merge, boolean select) {
final String url = App.getPreferences(context).getOverpassServer();
Log.d(DEBUG_TAG, "querying " + url + " for " + query);
try {
Storage storage = execQuery(url, query);
if (!storage.isEmpty()) {
final StorageDelegator delegator = App.getDelegator();
final BoundingBox box = storage.calcBoundingBoxFromData();
if (merge) {
delegator.mergeData(storage, (OsmElement e) -> e.hasProblem(context, App.getDefaultValidator(context)));
delegator.mergeBoundingBox(box);
} else {
delegator.reset(false);
delegator.setCurrentStorage(storage); // this sets dirty flag
delegator.setOriginalBox(box);
}
return new AsyncResult(ErrorCodes.OK);
if (storage.isEmpty()) {
return new AsyncResult(ErrorCodes.NOT_FOUND);
}
final StorageDelegator delegator = App.getDelegator();
final BoundingBox box = storage.calcBoundingBoxFromData();
if (merge) {
delegator.mergeData(storage, (OsmElement e) -> e.hasProblem(context, App.getDefaultValidator(context)));
delegator.mergeBoundingBox(box);
} else {
delegator.reset(false);
delegator.setCurrentStorage(storage); // this sets dirty flag
delegator.setOriginalBox(box);
}
if (select) {
selectResult(storage, delegator);
}
return new AsyncResult(ErrorCodes.OK);
} catch (StorageException sex) {
return new AsyncResult(ErrorCodes.OUT_OF_MEMORY);
} catch (OsmServerException e) {
Expand All @@ -231,7 +247,29 @@ public static AsyncResult query(@NonNull final Context context, @NonNull String
} catch (IOException e) {
return new AsyncResult(ErrorCodes.NO_CONNECTION, e.getMessage());
}
return new AsyncResult(ErrorCodes.NOT_FOUND);
}

/**
* Select the elements in storage, trying to avoid way nodes
*
* @param storage the original results of the query
* @param delegator the current StorageDelegator instance containing the merged results
*/
private static void selectResult(@NonNull final Storage storage, @NonNull final StorageDelegator delegator) {
Logic logic = App.getLogic();
logic.deselectAll();
List<Node> wayNodes = storage.getWayNodes();
for (Node n : storage.getNodes()) {
if (n.hasTags() || !wayNodes.contains(n)) {
logic.addSelectedNode((Node) delegator.getOsmElement(Node.NAME, n.getOsmId()));
}
}
for (Way w : storage.getWays()) {
logic.addSelectedWay((Way) delegator.getOsmElement(Way.NAME, w.getOsmId()));
}
for (Relation r : storage.getRelations()) {
logic.addSelectedRelation((Relation) delegator.getOsmElement(Relation.NAME, r.getOsmId()));
}
}

/**
Expand All @@ -247,7 +285,7 @@ public static AsyncResult query(@NonNull final Context context, @NonNull String
private static Storage execQuery(@NonNull String url, @NonNull String query) throws IOException, SAXException {
Request.Builder requestBuilder = new Request.Builder().url(url);

RequestBody body = new FormBody.Builder().add("data", query).build();
RequestBody body = new FormBody.Builder().add(BODY_DATA, query).build();
Request request = requestBuilder.post(body).build();

OkHttpClient client = App.getHttpClient().newBuilder().connectTimeout(TIMEOUT, TimeUnit.SECONDS).readTimeout(TIMEOUT, TimeUnit.SECONDS).build();
Expand Down
22 changes: 21 additions & 1 deletion src/test/java/de/blau/android/osm/OverpassApiTest.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package de.blau.android.osm;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
Expand Down Expand Up @@ -95,12 +96,31 @@ public void overpassQuery() {
String query = "[out:xml][timeout:90];" + "(" + "node[highway=residential](47.3795692,8.378648,47.3822631,8.3813708);"
+ "way[highway=residential](47.3795692,8.378648,47.3822631,8.3813708);"
+ "relation[highway=residential](47.3795692,8.378648,47.3822631,8.3813708);" + ");" + "(._;>;);" + "out meta;";
de.blau.android.overpass.Server.query(main, query, false);
de.blau.android.overpass.Server.query(main, query, false, false);
runLooper();
Way way = (Way) App.getDelegator().getOsmElement(Way.NAME, 47977728L);
assertNotNull(way);
assertEquals(12, way.getOsmVersion());
}

/**
* Simple overpass query with selection of results
*/
@Test
public void overpassQueryWithSelect() {
mockServer.enqueue("overpass");
String query = "[out:xml][timeout:90];" + "(" + "node[highway=residential](47.3795692,8.378648,47.3822631,8.3813708);"
+ "way[highway=residential](47.3795692,8.378648,47.3822631,8.3813708);"
+ "relation[highway=residential](47.3795692,8.378648,47.3822631,8.3813708);" + ");" + "(._;>;);" + "out meta;";
de.blau.android.overpass.Server.query(main, query, false, true);
runLooper();
Way way = (Way) App.getDelegator().getOsmElement(Way.NAME, 47977728L);
assertNotNull(way);
assertTrue(App.getLogic().getSelectedElements().contains(way));
Node wayNode = (Node) App.getDelegator().getOsmElement(Node.NAME,289981009L);
assertNotNull(wayNode);
assertFalse(App.getLogic().getSelectedElements().contains(wayNode));
}

/**
* Super ugly hack to get the looper to run
Expand Down

0 comments on commit 98afb80

Please sign in to comment.