diff options
author | Marcus Lundblad <ml@dfupdate.se> | 2023-01-18 23:26:10 +0100 |
---|---|---|
committer | Marcus Lundblad <ml@dfupdate.se> | 2023-04-25 22:17:29 +0200 |
commit | e1d534ea308c07366b5a7d9801c0f44b46512a47 (patch) | |
tree | cce2769eba449e61ae8a331f5204549c8f79a766 | |
parent | 1e53c87a4a0a6c59acd84e02a3b250f7735817f9 (diff) | |
download | gnome-maps-e1d534ea308c07366b5a7d9801c0f44b46512a47.tar.gz |
WIP: overpass: Implement searching for POIs...
-rw-r--r-- | src/overpass.js | 225 |
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}`; } |