Update presetIndex to resolve and index locationSets

Added a deferred work queue to do this with requestIdleCallback in the background
This commit is contained in:
Bryan Housel
2021-01-06 16:26:12 -05:00
parent b0800c10ed
commit 9eb6f8781f
2 changed files with 126 additions and 5 deletions
+11 -4
View File
@@ -586,12 +586,19 @@ export function coreContext() {
function loadNSIPresets() {
return fileFetcher.get('nsi_presets')
.then(d => {
return Promise.all([
fileFetcher.get('nsi_presets'),
fileFetcher.get('nsi_features')
])
.then(vals => {
// Add `suggestion=true` to all the nsi presets
// The preset json schema doesn't include it, but the iD code still uses it
Object.values(d.presets).forEach(preset => preset.suggestion = true);
presetManager.merge({ presets: d.presets });
Object.values(vals[0].presets).forEach(preset => preset.suggestion = true);
presetManager.merge({
presets: vals[0].presets,
featureCollection: vals[1]
});
})
.catch(() => { /* ignore */ });
}
+115 -1
View File
@@ -1,5 +1,8 @@
import { dispatch as d3_dispatch } from 'd3-dispatch';
import LocationConflation from '@ideditor/location-conflation';
import whichPolygon from 'which-polygon';
import { prefs } from '../core/preferences';
import { fileFetcher } from '../core/file_fetcher';
import { osmNodeGeometriesForTags, osmSetAreaKeys, osmSetPointTags, osmSetVertexTags } from '../osm/tags';
@@ -7,7 +10,7 @@ import { presetCategory } from './category';
import { presetCollection } from './collection';
import { presetField } from './field';
import { presetPreset } from './preset';
import { utilArrayUniq, utilRebind } from '../util';
import { utilArrayChunk, utilArrayUniq, utilRebind } from '../util';
export { presetCategory };
export { presetCollection };
@@ -45,13 +48,20 @@ export function presetIndex() {
let _fields = {};
let _categories = {};
let _universal = [];
let _customFeatures = {};
let _resolvedFeatures = {}; // cache of all locationSet Features
let _addablePresetIDs = null; // Set of preset IDs that the user can add
let _recents;
let _favorites;
let _deferred = new Set();
let _queue = [];
// Index of presets by (geometry, tag key).
let _geometryIndex = { point: {}, vertex: {}, line: {}, area: {}, relation: {} };
let _loco;
let _featureIndex;
let _loadPromise;
_this.ensureLoaded = () => {
@@ -77,13 +87,33 @@ export function presetIndex() {
};
// `merge` accepts an object containing new preset data (all properties optional):
// {
// fields: {},
// presets: {},
// categories: {},
// defaults: {},
// featureCollection: {}
//}
_this.merge = (d) => {
// Cancel any existing deferred work - we'll end up redoing it after this merge
_queue = [];
Array.from(_deferred).forEach(handle => {
window.cancelIdleCallback(handle);
_deferred.delete(handle);
});
// Merge Fields
if (d.fields) {
Object.keys(d.fields).forEach(fieldID => {
const f = d.fields[fieldID];
if (f) { // add or replace
_fields[fieldID] = presetField(fieldID, f);
if (!_fields[fieldID].locationSet) {
_fields[fieldID].locationSet = { include: ['Q2'] }; // default worldwide
_fields[fieldID].locationSetID = '+[Q2]';
}
} else { // remove
delete _fields[fieldID];
}
@@ -97,6 +127,10 @@ export function presetIndex() {
if (p) { // add or replace
const isAddable = !_addablePresetIDs || _addablePresetIDs.has(presetID);
_presets[presetID] = presetPreset(presetID, p, isAddable, _fields, _presets);
if (!_presets[presetID].locationSet) {
_presets[presetID].locationSet = { include: ['Q2'] }; // default worldwide
_presets[presetID].locationSetID = '+[Q2]';
}
} else { // remove (but not if it's a fallback)
const existing = _presets[presetID];
if (existing && !existing.isFallback()) {
@@ -155,7 +189,87 @@ export function presetIndex() {
});
});
// Merge Custom Features
if (d.featureCollection && Array.isArray(d.featureCollection.features)) {
d.featureCollection.features.forEach(feature => {
const featureID = feature.id || (feature.properties && feature.properties.id);
if (featureID) { // add or replace
_customFeatures[featureID] = feature;
}
});
}
// Replace LocationConflation resolver if new customFeatures have been added
// (Would be nice in a future version to be able to add new custom features to it, rather than replacing entirely)
if (!_loco || d.featureCollection) {
_loco = new LocationConflation({ type: 'FeatureCollection', features: Object.values(_customFeatures) });
resolveLocationSet({ include: ['Q2'] }); // resolve the default "world" feature
}
// Resolve all features -> geojson, processing data in chunks
let toResolve = Object.values(_presets).concat(Object.values(_fields))
.filter(d => !d.locationSetID);
_queue = utilArrayChunk(toResolve, 250);
// Everything after here will be deferred.
processQueue()
.then(() => { // Rebuild feature index
_featureIndex = whichPolygon({ type: 'FeatureCollection', features: Object.values(_resolvedFeatures) });
});
return _this;
function processQueue() {
if (!_queue.length) return Promise.resolve();
const chunk = _queue.pop();
return new Promise(resolvePromise => {
const handle = window.requestIdleCallback(() => {
_deferred.delete(handle);
resolveLocationSets(chunk);
resolvePromise();
});
_deferred.add(handle);
})
.then(() => processQueue());
}
function resolveLocationSets(items) {
if (!Array.isArray(items)) return;
items.forEach(item => {
let locationSet = item.locationSet || { include: ['Q2'] }; // fallback to world
let locationSetID;
try {
locationSetID = resolveLocationSet(locationSet);
} catch (err) {
locationSet = { include: ['Q2'] }; // fallback to world
locationSetID = '+[Q2]';
}
// store this info with the preset/field
item.locationSet = locationSet;
item.locationSetID = locationSetID;
});
}
function resolveLocationSet(locationSet) {
const resolved = _loco.resolveLocationSet(locationSet);
const locationSetID = resolved.id;
if (!resolved.feature.geometry.coordinates.length || !resolved.feature.properties.area) {
throw new Error(`locationSet ${locationSetID} resolves to an empty feature.`);
}
if (!_resolvedFeatures[locationSetID]) { // First time seeing this locationSet feature
let feature = JSON.parse(JSON.stringify(resolved.feature)); // deep clone
feature.id = locationSetID; // Important: always use the locationSet `id` (`+[Q30]`), not the feature `id` (`Q30`)
feature.properties.id = locationSetID;
_resolvedFeatures[locationSetID] = feature; // insert into cache
}
return locationSetID;
}
};