summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarcus Lundblad <ml@dfupdate.se>2023-01-18 23:26:10 +0100
committerMarcus Lundblad <ml@dfupdate.se>2023-04-25 22:17:29 +0200
commite1d534ea308c07366b5a7d9801c0f44b46512a47 (patch)
treecce2769eba449e61ae8a331f5204549c8f79a766
parent1e53c87a4a0a6c59acd84e02a3b250f7735817f9 (diff)
downloadgnome-maps-e1d534ea308c07366b5a7d9801c0f44b46512a47.tar.gz
WIP: overpass: Implement searching for POIs...
-rw-r--r--src/overpass.js225
1 files changed, 202 insertions, 23 deletions
diff --git a/src/overpass.js b/src/overpass.js
index 4a4b857a..6220e157 100644
--- a/src/overpass.js
+++ b/src/overpass.js
@@ -22,6 +22,7 @@ import GLib from 'gi://GLib';
import GObject from 'gi://GObject';
import Soup from 'gi://Soup';
+import {Application} from './application.js';
import * as OSMNames from './osmNames.js';
import * as PhotonUtils from './photonUtils.js';
import {Place} from './place.js';
@@ -30,10 +31,17 @@ import * as Utils from './utils.js';
const _DEFAULT_TIMEOUT = 180;
const _DEFAULT_MAXSIZE = 536870912;
const _DEFAULT_OUTPUT_FORMAT = 'json';
-const _DEFAULT_OUTPUT_COUNT = 1e6;
+const _DEFAULT_OUTPUT_COUNT = 1000;
const _DEFAULT_OUTPUT_INFO = 'body';
const _DEFAULT_OUTPUT_SORT_ORDER = 'qt';
+// default initial search radius to use for POIs when category doesn't specify one
+const _DEFAULT_INITIAL_POI_SEARCH_RADIUS = 1000;
+// multiplying factor for extended search radius
+const _SEARCH_RADIUS_MULTIPLIER = 10;
+// max distance to remove duplicate (name and type) POIs
+const _POI_DEDUPLICATION_DISTANCE = 500;
+
const BASE_URL = 'https://overpass-api.de/api/interpreter';
export class Overpass extends GObject.Object {
@@ -70,6 +78,8 @@ export class Overpass extends GObject.Object {
place.osm_id);
let request = Soup.Message.new('GET', url);
+ log(`url: ${url}`);
+
this._session.send_and_read_async(request, GLib.PRIORITY_DEFAULT, null,
(source, res) => {
if (request.get_status() !== Soup.Status.OK) {
@@ -80,11 +90,17 @@ export class Overpass extends GObject.Object {
try {
let buffer = this._session.send_and_read_finish(res).get_data();
let jsonObj = JSON.parse(Utils.getBufferText(buffer));
- this._populatePlace(place, jsonObj);
- this.place = place;
- this.notify('place');
+ let element = jsonObj?.elements?.[0];
+
+ if (element) {
+ this._populatePlace(place, element);
+ this.place = place;
+ this.notify('place');
+ } else {
+ Utils.debug('No element in Overpass result');
+ }
} catch(e) {
- Utils.debug('Failed to parse Overpass result');
+ Utils.debug('Failed to parse Overpass result ' + e.stack);
}
});
}
@@ -104,19 +120,161 @@ export class Overpass extends GObject.Object {
try {
let buffer = this._session.send_and_read_finish(res).get_data();
let jsonObj = JSON.parse(Utils.getBufferText(buffer));
- let place = this._createPlace(jsonObj, osmType, osmId);
- callback(place);
+ let element = jsonObj?.elements?.[0];
+
+ if (element) {
+ let place = this._createPlace(element, osmType, osmId);
+
+ callback(place);
+ } else {
+ callback(null);
+ }
} catch(e) {
- Utils.debug('Failed to parse Overpass result');
+ Utils.debug('Failed to parse Overpass result' + e.stack);
callback(null);
}
- })
+ });
}
- _createPlace(overpassData, osmType, osmId) {
- let element = overpassData.elements[0];
+ searchPois(lat, lon, category, cancellable, callback) {
+ let maxResults = Application.settings.get('max-search-results');
+ let initialSearchRadius =
+ category.initialSearchRadius ?? _DEFAULT_INITIAL_POI_SEARCH_RADIUS;
+
+ log('keyValues: ' + category.keyValues);
+
+ this._internalSearchPois(lat, lon, initialSearchRadius,
+ category, cancellable, (firstResults) => {
+ if (!firstResults) {
+ callback(null);
+ return;
+ }
+
+ this._orderPois(firstResults, lat, lon);
+
+ if (category.deduplicate)
+ this._removeNearbyDuplicates(firstResults);
+
+ if (firstResults.length >= maxResults) {
+ callback(firstResults.slice(0, maxResults));
+ } else {
+ // try to widen the search radius to include more results
+ if (cancellable.is_cancelled())
+ return;
+
+ this._internalSearchPois(lat, lon,
+ initialSearchRadius *
+ _SEARCH_RADIUS_MULTIPLIER,
+ category, cancellable,
+ (moreResults) => {
+ if (!moreResults) {
+ /* not able to retrieve more results, go with existing
+ * results
+ */
+ callback(firstResults);
+ return;
+ }
+
+ this._orderPois(moreResults, lat, lon);
+
+ log('first results: ' + firstResults.length);
+ log('more results: ' + moreResults.length);
+
+ // filter out results within the initial radius
+ moreResults =
+ moreResults.filter(p =>
+ p.dist > initialSearchRadius);
+
+ log('more results after filter: ' + moreResults.length);
+
+ let allResults = firstResults.concat(moreResults);
+
+ if (category.deduplicate)
+ this._removeNearbyDuplicates(allResults);
+
+ callback(allResults.slice(0, maxResults));
+ });
+ }
+ });
+ }
+
+ _removeNearbyDuplicates(results) {
+ let i = 0;
+ while (i < results.length - 1) {
+ let p1 = results[i];
+ let p2 = results[i + 1];
+ let distance = p1.location.get_distance_from(p2.location);
+
+ if (distance < _POI_DEDUPLICATION_DISTANCE && p1.name === p2.name)
+ results.splice(i + 1, 1);
+ else
+ i++;
+ }
+ }
+
+ _orderPois(pois, lat, lon) {
+ // order by distance to the search location
+ let origin = new Geocode.Location({ latitude: lat,
+ longitude: lon,
+ accuracy: 0.0 });
+
+ /* pre-compute distance from search origin for each
+ * result to avoid repeated distance calculations in
+ * when searching the array
+ */
+ for (let place of pois) {
+ place.dist = place.location.get_distance_from(origin) * 1000;
+ }
+
+ pois.sort((a, b) => { return a.dist - b.dist });
+ }
+
+ _internalSearchPois(lat, lon, radius, category, cancellable, callback) {
+ let url = this._getPoiQueryUrl(lat, lon, radius, category);
+ let request = Soup.Message.new('GET', url);
+
+ log(`url: ${url}`);
+ log('cancellable: ' + cancellable);
+
- if (!(element && element.tags))
+ this._session.send_and_read_async(request, GLib.PRIORITY_DEFAULT,
+ cancellable, (source, res) => {
+ if (cancellable.is_cancelled())
+ return;
+
+ if (request.get_status() !== Soup.Status.OK) {
+ Utils.debug('Failed to fetch Overpass result: ' +
+ request.get_status());
+ callback(null);
+ return;
+ }
+ try {
+ let buffer = this._session.send_and_read_finish(res).get_data();
+ let jsonObj = JSON.parse(Utils.getBufferText(buffer));
+ let elements = jsonObj?.elements;
+
+ if (!elements) {
+ callback(null);
+ return;
+ }
+
+ let results = [];
+
+ for (let element of elements) {
+ results.push(this._createPlace(element, element.type,
+ element.id));
+ }
+
+ callback(results);
+ } catch (e) {
+ Utils.debug('Failed to parse Overpass result' + e.stack);
+ callback(null);
+ }
+ });
+ }
+
+ _createPlace(element, osmType, osmId) {
+ if (!element.tags)
return null;
let [lat, lon] = this._getCoordsFromElement(element);
@@ -124,7 +282,7 @@ export class Overpass extends GObject.Object {
this._getPhotonProperties(element.tags, osmType, osmId);
let place = PhotonUtils.parsePlace(lat, lon, photonProperties);
- this._populatePlace(place, overpassData);
+ this._populatePlace(place, element);
place.prefilled = true;
return place;
@@ -193,12 +351,7 @@ export class Overpass extends GObject.Object {
properties.osm_value = tags[tag];
}
- _populatePlace(place, overpassData) {
- let element = overpassData.elements[0];
-
- if (!(element && element.tags))
- return;
-
+ _populatePlace(place, element) {
let name = this._getLocalizedName(element.tags, place);
if (name)
place.name = name;
@@ -217,14 +370,21 @@ export class Overpass extends GObject.Object {
}
_getQueryUrl(osmType, osmId) {
- return `${BASE_URL}?data=${this._generateOverpassQuery(osmType, osmId)}`;
+ let data = this._getOsmObjectData(osmType, osmId);
+
+ return `${BASE_URL}?data=${this._generateOverpassQuery(data)}`;
+ }
+
+ _getPoiQueryUrl(lat, lon, radius, category) {
+ let data = this._getPoiData(lat, lon, radius, category);
+
+ return `${BASE_URL}?data=${this._generateOverpassQuery(data)}`;
}
- _generateOverpassQuery(osmType, osmId) {
+ _generateOverpassQuery(data) {
let timeout = this._getKeyValue('timeout', this.timeout);
let out = this._getKeyValue('out', this.outputFormat);
let maxSize = this._getKeyValue('maxsize', this.maxsize);
- let data = this._getData(osmType, osmId);
let output = this._getOutput();
return `${timeout}${out}${maxSize};${data};${output};`;
@@ -234,10 +394,29 @@ export class Overpass extends GObject.Object {
return `[${key}:${value}]`;
}
- _getData(osmType, osmId) {
+ _getOsmObjectData(osmType, osmId) {
return `${osmType}(${osmId})`;
}
+ _getPoiData(lat, lon, radius, category) {
+ let data = '(';
+
+ for (let disjunction of category.keyValues) {
+ for (let type of ['node', 'way', 'relation']) {
+ data += `${type}(around:${radius},${lat},${lon})`;
+
+ for (let kv of disjunction) {
+ data += `[${kv}]`;
+ }
+
+ data += ';';
+ }
+ }
+
+ data += ')';
+ return data;
+ }
+
_getOutput() {
return `out center ${this.outputInfo} ${this.outputSortOrder} ${this.outputCount}`;
}