mirror of
https://github.com/FoggedLens/iD.git
synced 2026-05-23 00:29:50 +02:00
WIP on external presets
- preset data is no longer bundled into iD.js - some code pathways commented out re: external presets - many changes so that tests can run without presets at start, or async - still need to make sure fallbacks are always there (point, line, area, etc)
This commit is contained in:
+24
-20
@@ -549,26 +549,30 @@ export function coreContext() {
|
||||
_features.init();
|
||||
_photos.init();
|
||||
|
||||
let presetsParameter = hash.presets;
|
||||
if (presetsParameter && presetsParameter.indexOf('://') !== -1) {
|
||||
// a URL of external presets file
|
||||
_presets.fromExternal(external, (externalPresets) => {
|
||||
context.presets = () => externalPresets; // default + external presets...
|
||||
osmSetAreaKeys(_presets.areaKeys());
|
||||
osmSetPointTags(_presets.pointTags());
|
||||
osmSetVertexTags(_presets.vertexTags());
|
||||
});
|
||||
} else {
|
||||
let addablePresetIDs;
|
||||
if (presetsParameter) {
|
||||
// a list of allowed preset IDs
|
||||
addablePresetIDs = presetsParameter.split(',');
|
||||
}
|
||||
_presets.init(addablePresetIDs);
|
||||
osmSetAreaKeys(_presets.areaKeys());
|
||||
osmSetPointTags(_presets.pointTags());
|
||||
osmSetVertexTags(_presets.vertexTags());
|
||||
}
|
||||
// let presetsParameter = hash.presets;
|
||||
// if (presetsParameter && presetsParameter.indexOf('://') !== -1) {
|
||||
// // a URL of external presets file
|
||||
// _presets.fromExternal(external, (externalPresets) => {
|
||||
// context.presets = () => externalPresets; // default + external presets...
|
||||
// osmSetAreaKeys(_presets.areaKeys());
|
||||
// osmSetPointTags(_presets.pointTags());
|
||||
// osmSetVertexTags(_presets.vertexTags());
|
||||
// });
|
||||
// } else {
|
||||
// let addablePresetIDs;
|
||||
// if (presetsParameter) {
|
||||
// // a list of allowed preset IDs
|
||||
// addablePresetIDs = presetsParameter.split(',');
|
||||
// }
|
||||
// _presets.init(addablePresetIDs);
|
||||
_presets.init()
|
||||
.then(() => {
|
||||
osmSetAreaKeys(_presets.areaKeys());
|
||||
osmSetPointTags(_presets.pointTags());
|
||||
osmSetVertexTags(_presets.vertexTags());
|
||||
});
|
||||
// }
|
||||
|
||||
return context;
|
||||
};
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import { data as _data } from '../../data'; // prebundled data
|
||||
// The coreData module fetches data from JSON files
|
||||
//
|
||||
export function coreData(context) {
|
||||
let _module = {};
|
||||
let _this = {};
|
||||
let _inflight = {};
|
||||
let _fileMap = {
|
||||
'address_formats': 'data/address_formats.min.json',
|
||||
@@ -20,6 +20,10 @@ export function coreData(context) {
|
||||
'nsi_filters': 'https://cdn.jsdelivr.net/npm/name-suggestion-index@3/dist/filters.min.json',
|
||||
'oci_features': 'https://cdn.jsdelivr.net/npm/osm-community-index@2/dist/features.min.json',
|
||||
'oci_resources': 'https://cdn.jsdelivr.net/npm/osm-community-index@2/dist/resources.min.json',
|
||||
'preset_categories': 'data/preset_categories.min.json',
|
||||
'preset_defaults': 'data/preset_defaults.min.json',
|
||||
'preset_fields': 'data/fields.min.json',
|
||||
'preset_presets': 'data/presets.min.json',
|
||||
'phone_formats': 'data/phone_formats.min.json',
|
||||
'shortcuts': 'data/shortcuts.min.json',
|
||||
'territory_languages': 'data/territory_languages.min.json',
|
||||
@@ -29,7 +33,7 @@ export function coreData(context) {
|
||||
|
||||
// Returns a Promise to fetch data
|
||||
// (resolved with the data if we have it already)
|
||||
_module.get = (which) => {
|
||||
_this.get = (which) => {
|
||||
if (_data[which]) {
|
||||
return Promise.resolve(_data[which]);
|
||||
}
|
||||
@@ -62,12 +66,12 @@ export function coreData(context) {
|
||||
|
||||
|
||||
// Accessor for the file map
|
||||
_module.fileMap = function(val) {
|
||||
_this.fileMap = function(val) {
|
||||
if (!arguments.length) return _fileMap;
|
||||
_fileMap = val;
|
||||
return _module;
|
||||
return _this;
|
||||
};
|
||||
|
||||
|
||||
return _module;
|
||||
return _this;
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ export function operationDowngrade(selectedIDs, context) {
|
||||
var entity = graph.entity(entityID);
|
||||
var preset = context.presets().match(entity, graph);
|
||||
|
||||
if (preset.isFallback()) return null;
|
||||
if (!preset || preset.isFallback()) return null;
|
||||
|
||||
if (entity.type === 'node' &&
|
||||
preset.id !== 'address' &&
|
||||
|
||||
+21
-39
@@ -2,53 +2,35 @@ import { t } from '../util/locale';
|
||||
import { presetCollection } from './collection';
|
||||
|
||||
|
||||
export function presetCategory(id, category, all) {
|
||||
category = Object.assign({}, category); // shallow copy
|
||||
export function presetCategory(categoryID, category, all) {
|
||||
let _this = Object.assign({}, category); // shallow copy
|
||||
|
||||
category.id = id;
|
||||
_this.id = categoryID;
|
||||
|
||||
_this.members = presetCollection(_this.members.map(presetID => all.item(presetID)));
|
||||
|
||||
category.members = presetCollection(category.members.map(function(id) {
|
||||
return all.item(id);
|
||||
}));
|
||||
|
||||
|
||||
category.geometry = category.members.collection.reduce(function(geometries, preset) {
|
||||
for (var index in preset.geometry) {
|
||||
var geometry = preset.geometry[index];
|
||||
if (geometries.indexOf(geometry) === -1) {
|
||||
geometries.push(geometry);
|
||||
}
|
||||
_this.geometry = _this.members.collection
|
||||
.reduce((acc, preset) => {
|
||||
for (let i in preset.geometry) {
|
||||
const geometry = preset.geometry[i];
|
||||
if (acc.indexOf(geometry) === -1) {
|
||||
acc.push(geometry);
|
||||
}
|
||||
return geometries;
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
_this.matchGeometry = (geom) => _this.geometry.indexOf(geom) >= 0;
|
||||
|
||||
category.matchGeometry = function(geometry) {
|
||||
return category.geometry.indexOf(geometry) >= 0;
|
||||
};
|
||||
_this.matchAllGeometry = (geometries) => _this.members.collection
|
||||
.some(preset => preset.matchAllGeometry(geometries));
|
||||
|
||||
category.matchAllGeometry = function(geometries) {
|
||||
return category.members.collection.some(function(preset) {
|
||||
return preset.matchAllGeometry(geometries);
|
||||
});
|
||||
};
|
||||
_this.matchScore = () => -1;
|
||||
|
||||
_this.name = () => t(`presets.categories.${categoryID}.name`, { 'default': categoryID });
|
||||
|
||||
_this.terms = () => [];
|
||||
|
||||
|
||||
category.matchScore = function() {
|
||||
return -1;
|
||||
};
|
||||
|
||||
|
||||
category.name = function() {
|
||||
return t('presets.categories.' + id + '.name', {'default': id});
|
||||
};
|
||||
|
||||
|
||||
category.terms = function() {
|
||||
return [];
|
||||
};
|
||||
|
||||
|
||||
return category;
|
||||
return _this;
|
||||
}
|
||||
|
||||
+141
-179
@@ -2,187 +2,149 @@ import { utilArrayUniq, utilEditDistance } from '../util';
|
||||
|
||||
|
||||
export function presetCollection(collection) {
|
||||
var maxSearchResults = 50;
|
||||
const MAXRESULTS = 50;
|
||||
|
||||
var presets = {
|
||||
let _this = {};
|
||||
|
||||
collection: collection,
|
||||
_this.collection = collection;
|
||||
|
||||
_this.item = (id) => _this.collection.find(d => d.id === id);
|
||||
|
||||
_this.index = (id) => _this.collection.findIndex(d => d.id === id);
|
||||
|
||||
_this.matchGeometry = (geometry) => {
|
||||
return presetCollection(
|
||||
_this.collection.filter(d => d.matchGeometry(geometry))
|
||||
);
|
||||
};
|
||||
|
||||
_this.matchAllGeometry = (geometries) => {
|
||||
return presetCollection(_this.collection.filter(d => {
|
||||
if (!d) return false;
|
||||
return d.matchAllGeometry(geometries);
|
||||
}));
|
||||
};
|
||||
|
||||
_this.matchAnyGeometry = (geometries) => {
|
||||
return presetCollection(_this.collection.filter(d => {
|
||||
return geometries.some(geom => d.matchGeometry(geom));
|
||||
}));
|
||||
};
|
||||
|
||||
_this.fallback = (geometry) => {
|
||||
let id = geometry;
|
||||
if (id === 'vertex') id = 'point';
|
||||
return _this.item(id);
|
||||
};
|
||||
|
||||
_this.search = (value, geometry, countryCode) => {
|
||||
if (!value) return this;
|
||||
|
||||
value = value.toLowerCase().trim();
|
||||
|
||||
// match at name beginning or just after a space (e.g. "office" -> match "Law Office")
|
||||
function leading(a) {
|
||||
const index = a.indexOf(value);
|
||||
return index === 0 || a[index - 1] === ' ';
|
||||
}
|
||||
|
||||
// match at name beginning only
|
||||
function leadingStrict(a) {
|
||||
const index = a.indexOf(value);
|
||||
return index === 0;
|
||||
}
|
||||
|
||||
function sortNames(a, b) {
|
||||
let aCompare = (a.suggestion ? a.originalName : a.name()).toLowerCase();
|
||||
let bCompare = (b.suggestion ? b.originalName : b.name()).toLowerCase();
|
||||
|
||||
// priority if search string matches preset name exactly - #4325
|
||||
if (value === aCompare) return -1;
|
||||
if (value === bCompare) return 1;
|
||||
|
||||
// priority for higher matchScore
|
||||
let i = b.originalScore - a.originalScore;
|
||||
if (i !== 0) return i;
|
||||
|
||||
// priority if search string appears earlier in preset name
|
||||
i = aCompare.indexOf(value) - bCompare.indexOf(value);
|
||||
if (i !== 0) return i;
|
||||
|
||||
// priority for shorter preset names
|
||||
return aCompare.length - bCompare.length;
|
||||
}
|
||||
|
||||
let pool = _this.collection;
|
||||
if (countryCode) {
|
||||
pool = pool.filter(a => {
|
||||
if (a.countryCodes && a.countryCodes.indexOf(countryCode) === -1) return false;
|
||||
if (a.notCountryCodes && a.notCountryCodes.indexOf(countryCode) !== -1) return false;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
const searchable = pool.filter(a => a.searchable !== false && a.suggestion !== true);
|
||||
const suggestions = pool.filter(a => a.suggestion === true);
|
||||
|
||||
// matches value to preset.name
|
||||
const leading_name = searchable
|
||||
.filter(a => leading(a.name().toLowerCase()))
|
||||
.sort(sortNames);
|
||||
|
||||
// matches value to preset suggestion name (original name is unhyphenated)
|
||||
const leading_suggestions = suggestions
|
||||
.filter(a => leadingStrict(a.originalName.toLowerCase()))
|
||||
.sort(sortNames);
|
||||
|
||||
// matches value to preset.terms values
|
||||
const leading_terms = searchable
|
||||
.filter(a => (a.terms() || []).some(leading));
|
||||
|
||||
// matches value to preset.tags values
|
||||
const leading_tag_values = searchable
|
||||
.filter(a => Object.values(a.tags || {}).filter(val => val !== '*').some(leading));
|
||||
|
||||
// finds close matches to value in preset.name
|
||||
const similar_name = searchable
|
||||
.map(a => ({ preset: a, dist: utilEditDistance(value, a.name()) }))
|
||||
.filter(a => a.dist + Math.min(value.length - a.preset.name().length, 0) < 3)
|
||||
.sort((a, b) => a.dist - b.dist)
|
||||
.map(a => a.preset);
|
||||
|
||||
// finds close matches to value to preset suggestion name (original name is unhyphenated)
|
||||
const similar_suggestions = suggestions
|
||||
.map(a => ({ preset: a, dist: utilEditDistance(value, a.originalName.toLowerCase()) }))
|
||||
.filter(a => a.dist + Math.min(value.length - a.preset.originalName.length, 0) < 1)
|
||||
.sort((a, b) => a.dist - b.dist)
|
||||
.map(a => a.preset);
|
||||
|
||||
// finds close matches to value in preset.terms
|
||||
const similar_terms = searchable
|
||||
.filter(a => {
|
||||
return (a.terms() || []).some(b => {
|
||||
return utilEditDistance(value, b) + Math.min(value.length - b.length, 0) < 3;
|
||||
});
|
||||
});
|
||||
|
||||
let results = leading_name.concat(
|
||||
leading_suggestions,
|
||||
leading_terms,
|
||||
leading_tag_values,
|
||||
similar_name,
|
||||
similar_suggestions,
|
||||
similar_terms
|
||||
).slice(0, MAXRESULTS - 1);
|
||||
|
||||
if (geometry) {
|
||||
if (typeof geometry === 'string') {
|
||||
results.push(_this.fallback(geometry));
|
||||
} else {
|
||||
geometry.forEach(geom => results.push(_this.fallback(geom)));
|
||||
}
|
||||
}
|
||||
|
||||
return presetCollection(utilArrayUniq(results));
|
||||
};
|
||||
|
||||
|
||||
item: function(id) {
|
||||
return this.collection.find(function(d) {
|
||||
return d.id === id;
|
||||
});
|
||||
},
|
||||
|
||||
index: function(id) {
|
||||
return this.collection.findIndex(function(d) {
|
||||
return d.id === id;
|
||||
});
|
||||
},
|
||||
|
||||
matchGeometry: function(geometry) {
|
||||
return presetCollection(this.collection.filter(function(d) {
|
||||
return d.matchGeometry(geometry);
|
||||
}));
|
||||
},
|
||||
|
||||
matchAllGeometry: function(geometries) {
|
||||
return presetCollection(this.collection.filter(function(d) {
|
||||
if (!d) return false;
|
||||
return d.matchAllGeometry(geometries);
|
||||
}));
|
||||
},
|
||||
|
||||
matchAnyGeometry: function(geometries) {
|
||||
return presetCollection(this.collection.filter(function(d) {
|
||||
return geometries.some(function(geometry) {
|
||||
return d.matchGeometry(geometry);
|
||||
});
|
||||
}));
|
||||
},
|
||||
|
||||
fallback: function(geometry) {
|
||||
var id = geometry;
|
||||
if (id === 'vertex') id = 'point';
|
||||
return this.item(id);
|
||||
},
|
||||
|
||||
search: function(value, geometry, countryCode) {
|
||||
if (!value) return this;
|
||||
|
||||
value = value.toLowerCase().trim();
|
||||
|
||||
// match at name beginning or just after a space (e.g. "office" -> match "Law Office")
|
||||
function leading(a) {
|
||||
var index = a.indexOf(value);
|
||||
return index === 0 || a[index - 1] === ' ';
|
||||
}
|
||||
|
||||
// match at name beginning only
|
||||
function leadingStrict(a) {
|
||||
var index = a.indexOf(value);
|
||||
return index === 0;
|
||||
}
|
||||
|
||||
function sortNames(a, b) {
|
||||
var aCompare = (a.suggestion ? a.originalName : a.name()).toLowerCase();
|
||||
var bCompare = (b.suggestion ? b.originalName : b.name()).toLowerCase();
|
||||
|
||||
// priority if search string matches preset name exactly - #4325
|
||||
if (value === aCompare) return -1;
|
||||
if (value === bCompare) return 1;
|
||||
|
||||
// priority for higher matchScore
|
||||
var i = b.originalScore - a.originalScore;
|
||||
if (i !== 0) return i;
|
||||
|
||||
// priority if search string appears earlier in preset name
|
||||
i = aCompare.indexOf(value) - bCompare.indexOf(value);
|
||||
if (i !== 0) return i;
|
||||
|
||||
// priority for shorter preset names
|
||||
return aCompare.length - bCompare.length;
|
||||
}
|
||||
|
||||
var pool = this.collection;
|
||||
if (countryCode) {
|
||||
pool = pool.filter(function(a) {
|
||||
if (a.countryCodes && a.countryCodes.indexOf(countryCode) === -1) {
|
||||
return false;
|
||||
}
|
||||
if (a.notCountryCodes && a.notCountryCodes.indexOf(countryCode) !== -1) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
var searchable = pool.filter(function(a) {
|
||||
return a.searchable !== false && a.suggestion !== true;
|
||||
});
|
||||
var suggestions = pool.filter(function(a) {
|
||||
return a.suggestion === true;
|
||||
});
|
||||
|
||||
// matches value to preset.name
|
||||
var leading_name = searchable
|
||||
.filter(function(a) {
|
||||
return leading(a.name().toLowerCase());
|
||||
}).sort(sortNames);
|
||||
|
||||
// matches value to preset.terms values
|
||||
var leading_terms = searchable
|
||||
.filter(function(a) {
|
||||
return (a.terms() || []).some(leading);
|
||||
});
|
||||
|
||||
// matches value to preset.tags values
|
||||
var leading_tag_values = searchable
|
||||
.filter(function(a) {
|
||||
return Object.values(a.tags || {})
|
||||
.filter(function(val) { return val !== '*'; })
|
||||
.some(leading);
|
||||
});
|
||||
|
||||
var leading_suggestions = suggestions
|
||||
.filter(function(a) {
|
||||
return leadingStrict(a.originalName.toLowerCase());
|
||||
}).sort(sortNames);
|
||||
|
||||
// finds close matches to value in preset.name
|
||||
var similar_name = searchable
|
||||
.map(function(a) {
|
||||
return { preset: a, dist: utilEditDistance(value, a.name()) };
|
||||
}).filter(function(a) {
|
||||
return a.dist + Math.min(value.length - a.preset.name().length, 0) < 3;
|
||||
}).sort(function(a, b) {
|
||||
return a.dist - b.dist;
|
||||
}).map(function(a) {
|
||||
return a.preset;
|
||||
});
|
||||
|
||||
// finds close matches to value in preset.terms
|
||||
var similar_terms = searchable
|
||||
.filter(function(a) {
|
||||
return (a.terms() || []).some(function(b) {
|
||||
return utilEditDistance(value, b) + Math.min(value.length - b.length, 0) < 3;
|
||||
});
|
||||
});
|
||||
|
||||
var similar_suggestions = suggestions
|
||||
.map(function(a) {
|
||||
return { preset: a, dist: utilEditDistance(value, a.originalName.toLowerCase()) };
|
||||
}).filter(function(a) {
|
||||
return a.dist + Math.min(value.length - a.preset.originalName.length, 0) < 1;
|
||||
}).sort(function(a, b) {
|
||||
return a.dist - b.dist;
|
||||
}).map(function(a) {
|
||||
return a.preset;
|
||||
});
|
||||
|
||||
var results = leading_name.concat(
|
||||
leading_suggestions,
|
||||
leading_terms,
|
||||
leading_tag_values,
|
||||
similar_name,
|
||||
similar_suggestions,
|
||||
similar_terms
|
||||
).slice(0, maxSearchResults - 1);
|
||||
|
||||
if (geometry) {
|
||||
if (typeof geometry === 'string') {
|
||||
results.push(presets.fallback(geometry));
|
||||
} else {
|
||||
geometry.forEach(function(geom) {
|
||||
results.push(presets.fallback(geom));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return presetCollection(utilArrayUniq(results));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
return presets;
|
||||
return _this;
|
||||
}
|
||||
|
||||
+23
-37
@@ -1,47 +1,33 @@
|
||||
import { t } from '../util/locale';
|
||||
import { utilSafeClassName } from '../util/util';
|
||||
|
||||
export function presetField(id, field) {
|
||||
field = Object.assign({}, field); // shallow copy
|
||||
|
||||
field.id = id;
|
||||
export function presetField(fieldID, field) {
|
||||
let _this = Object.assign({}, field); // shallow copy
|
||||
|
||||
// for use in classes, element ids, css selectors
|
||||
field.safeid = utilSafeClassName(id);
|
||||
_this.id = fieldID;
|
||||
|
||||
field.matchGeometry = function(geometry) {
|
||||
return !field.geometry || field.geometry === geometry;
|
||||
};
|
||||
// for use in classes, element ids, css selectors
|
||||
_this.safeid = utilSafeClassName(fieldID);
|
||||
|
||||
field.matchAllGeometry = function(geometries) {
|
||||
return !field.geometry || geometries.every(function(geometry) {
|
||||
return field.geometry.indexOf(geometry) !== -1;
|
||||
});
|
||||
};
|
||||
_this.matchGeometry = (geom) => !_this.geometry || _this.geometry === geom;
|
||||
|
||||
_this.matchAllGeometry = (geometries) => {
|
||||
return !_this.geometry || geometries.every(geom => _this.geometry.indexOf(geom) !== -1);
|
||||
};
|
||||
|
||||
_this.t = (scope, options) => t(`presets.fields.${fieldID}.${scope}`, options);
|
||||
|
||||
_this.label = () => _this.overrideLabel || _this.t('label', { 'default': fieldID });
|
||||
|
||||
const _placeholder = _this.placeholder;
|
||||
_this.placeholder = () => _this.t('placeholder', { 'default': _placeholder });
|
||||
|
||||
_this.originalTerms = (_this.terms || []).join();
|
||||
|
||||
_this.terms = () => _this.t('terms', { 'default': _this.originalTerms })
|
||||
.toLowerCase().trim().split(/\s*,+\s*/);
|
||||
|
||||
|
||||
field.t = function(scope, options) {
|
||||
return t('presets.fields.' + id + '.' + scope, options);
|
||||
};
|
||||
|
||||
|
||||
field.label = function() {
|
||||
return field.overrideLabel || field.t('label', {'default': id});
|
||||
};
|
||||
|
||||
|
||||
var placeholder = field.placeholder;
|
||||
field.placeholder = function() {
|
||||
return field.t('placeholder', {'default': placeholder});
|
||||
};
|
||||
|
||||
|
||||
field.originalTerms = (field.terms || []).join();
|
||||
|
||||
field.terms = function() {
|
||||
return field.t('terms', { 'default': field.originalTerms }).toLowerCase().trim().split(/\s*,+\s*/);
|
||||
};
|
||||
|
||||
|
||||
return field;
|
||||
return _this;
|
||||
}
|
||||
|
||||
+443
-415
@@ -15,440 +15,468 @@ export { presetField };
|
||||
export { presetPreset };
|
||||
|
||||
|
||||
// wraps a presetCollection with methods for
|
||||
// loading new data and returning defaults
|
||||
export function presetIndex(context) {
|
||||
// a presetCollection with methods for
|
||||
// loading new data and returning defaults
|
||||
const dispatch = d3_dispatch('recentsChange');
|
||||
const MAXRECENTS = 30;
|
||||
let _presetData;
|
||||
|
||||
var dispatch = d3_dispatch('recentsChange');
|
||||
let _this = presetCollection([]); // collection of all presets
|
||||
let _defaults = {
|
||||
point: presetCollection([]),
|
||||
vertex: presetCollection([]),
|
||||
line: presetCollection([]),
|
||||
area: presetCollection([]),
|
||||
relation: presetCollection([])
|
||||
};
|
||||
|
||||
var all = presetCollection([]);
|
||||
var _defaults = { area: all, line: all, point: all, vertex: all, relation: all };
|
||||
var _fields = {};
|
||||
var _universal = [];
|
||||
var _recents;
|
||||
// presets that the user can add
|
||||
var _addablePresetIDs;
|
||||
let _fields = {};
|
||||
let _universal = [];
|
||||
let _recents;
|
||||
// let _addablePresetIDs; // presets that the user can add
|
||||
|
||||
// Index of presets by (geometry, tag key).
|
||||
var _index = {
|
||||
point: {},
|
||||
vertex: {},
|
||||
line: {},
|
||||
area: {},
|
||||
relation: {}
|
||||
};
|
||||
|
||||
all.match = function(entity, resolver) {
|
||||
return resolver.transient(entity, 'presetMatch', function() {
|
||||
var geometry = entity.geometry(resolver);
|
||||
|
||||
// Treat entities on addr:interpolation lines as points, not vertices - #3241
|
||||
if (geometry === 'vertex' && entity.isOnAddressLine(resolver)) {
|
||||
geometry = 'point';
|
||||
}
|
||||
|
||||
return all.matchTags(entity.tags, geometry);
|
||||
});
|
||||
};
|
||||
|
||||
all.matchTags = function(tags, geometry) {
|
||||
|
||||
var address;
|
||||
var geometryMatches = _index[geometry];
|
||||
var best = -1;
|
||||
var match;
|
||||
|
||||
for (var k in tags) {
|
||||
// If any part of an address is present,
|
||||
// allow fallback to "Address" preset - #4353
|
||||
if (/^addr:/.test(k) && geometryMatches['addr:*']) {
|
||||
address = geometryMatches['addr:*'][0];
|
||||
}
|
||||
|
||||
var keyMatches = geometryMatches[k];
|
||||
if (!keyMatches) continue;
|
||||
|
||||
for (var i = 0; i < keyMatches.length; i++) {
|
||||
var score = keyMatches[i].matchScore(tags);
|
||||
if (score > best) {
|
||||
best = score;
|
||||
match = keyMatches[i];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (address && (!match || match.isFallback())) {
|
||||
match = address;
|
||||
}
|
||||
return match || all.fallback(geometry);
|
||||
};
|
||||
|
||||
all.allowsVertex = function(entity, resolver) {
|
||||
if (entity.type !== 'node') return false;
|
||||
if (Object.keys(entity.tags).length === 0) return true;
|
||||
|
||||
return resolver.transient(entity, 'vertexMatch', function() {
|
||||
// address lines allow vertices to act as standalone points
|
||||
if (entity.isOnAddressLine(resolver)) return true;
|
||||
|
||||
var geometries = osmNodeGeometriesForTags(entity.tags);
|
||||
if (geometries.vertex) return true;
|
||||
if (geometries.point) return false;
|
||||
// allow vertices for unspecified points
|
||||
return true;
|
||||
});
|
||||
};
|
||||
// Index of presets by (geometry, tag key).
|
||||
let _index = { point: {}, vertex: {}, line: {}, area: {}, relation: {} };
|
||||
|
||||
|
||||
// Because of the open nature of tagging, iD will never have a complete
|
||||
// list of tags used in OSM, so we want it to have logic like "assume
|
||||
// that a closed way with an amenity tag is an area, unless the amenity
|
||||
// is one of these specific types". This function computes a structure
|
||||
// that allows testing of such conditions, based on the presets designated
|
||||
// as as supporting (or not supporting) the area geometry.
|
||||
//
|
||||
// The returned object L is a whitelist/blacklist of tags. A closed way
|
||||
// with a tag (k, v) is considered to be an area if `k in L && !(v in L[k])`
|
||||
// (see `Way#isArea()`). In other words, the keys of L form the whitelist,
|
||||
// and the subkeys form the blacklist.
|
||||
all.areaKeys = function() {
|
||||
var areaKeys = {};
|
||||
var ignore = ['barrier', 'highway', 'footway', 'railway', 'junction', 'type']; // probably a line..
|
||||
function ensurePresetData() {
|
||||
const data = context.data();
|
||||
return Promise.all([
|
||||
data.get('preset_categories'),
|
||||
data.get('preset_defaults'),
|
||||
data.get('preset_presets'),
|
||||
data.get('preset_fields')
|
||||
])
|
||||
.then(vals => {
|
||||
if (_presetData) return _presetData;
|
||||
|
||||
// ignore name-suggestion-index and deprecated presets
|
||||
var presets = all.collection.filter(function(p) {
|
||||
return !p.suggestion && !p.replacement;
|
||||
});
|
||||
|
||||
// whitelist
|
||||
presets.forEach(function(d) {
|
||||
for (var key in d.tags) break;
|
||||
if (!key) return;
|
||||
if (ignore.indexOf(key) !== -1) return;
|
||||
|
||||
if (d.geometry.indexOf('area') !== -1) { // probably an area..
|
||||
areaKeys[key] = areaKeys[key] || {};
|
||||
}
|
||||
});
|
||||
|
||||
// blacklist
|
||||
presets.forEach(function(d) {
|
||||
for (var key in d.addTags) {
|
||||
// examine all addTags to get a better sense of what can be tagged on lines - #6800
|
||||
var value = d.addTags[key];
|
||||
if (key in areaKeys && // probably an area...
|
||||
d.geometry.indexOf('line') !== -1 && // but sometimes a line
|
||||
value !== '*') {
|
||||
areaKeys[key][value] = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return areaKeys;
|
||||
};
|
||||
|
||||
all.pointTags = function() {
|
||||
return all.collection.reduce(function(pointTags, d) {
|
||||
// ignore name-suggestion-index, deprecated, and generic presets
|
||||
if (d.suggestion || d.replacement || d.searchable === false) return pointTags;
|
||||
|
||||
// only care about the primary tag
|
||||
for (var key in d.tags) break;
|
||||
if (!key) return pointTags;
|
||||
|
||||
// if this can be a point
|
||||
if (d.geometry.indexOf('point') !== -1) {
|
||||
pointTags[key] = pointTags[key] || {};
|
||||
pointTags[key][d.tags[key]] = true;
|
||||
}
|
||||
return pointTags;
|
||||
}, {});
|
||||
};
|
||||
|
||||
all.vertexTags = function() {
|
||||
return all.collection.reduce(function(vertexTags, d) {
|
||||
// ignore name-suggestion-index, deprecated, and generic presets
|
||||
if (d.suggestion || d.replacement || d.searchable === false) return vertexTags;
|
||||
|
||||
// only care about the primary tag
|
||||
for (var key in d.tags) break;
|
||||
if (!key) return vertexTags;
|
||||
|
||||
// if this can be a vertex
|
||||
if (d.geometry.indexOf('vertex') !== -1) {
|
||||
vertexTags[key] = vertexTags[key] || {};
|
||||
vertexTags[key][d.tags[key]] = true;
|
||||
}
|
||||
return vertexTags;
|
||||
}, {});
|
||||
};
|
||||
|
||||
all.build = function(d, addable) {
|
||||
if (d.fields) {
|
||||
Object.keys(d.fields).forEach(function(id) {
|
||||
var f = d.fields[id];
|
||||
_fields[id] = presetField(id, f);
|
||||
if (f.universal) {
|
||||
_universal.push(_fields[id]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (d.presets) {
|
||||
var rawPresets = d.presets;
|
||||
Object.keys(d.presets).forEach(function(id) {
|
||||
var p = d.presets[id];
|
||||
var existing = all.index(id);
|
||||
var isAddable = typeof addable === 'function' ? addable(id, p) : addable;
|
||||
if (existing !== -1) {
|
||||
all.collection[existing] = presetPreset(id, p, _fields, isAddable, rawPresets);
|
||||
} else {
|
||||
all.collection.push(presetPreset(id, p, _fields, isAddable, rawPresets));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (d.categories) {
|
||||
Object.keys(d.categories).forEach(function(id) {
|
||||
var c = d.categories[id];
|
||||
var existing = all.index(id);
|
||||
if (existing !== -1) {
|
||||
all.collection[existing] = presetCategory(id, c, all);
|
||||
} else {
|
||||
all.collection.push(presetCategory(id, c, all));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var getItem = (all.item).bind(all);
|
||||
if (_addablePresetIDs) {
|
||||
['area', 'line', 'point', 'vertex', 'relation'].forEach(function(geometry) {
|
||||
_defaults[geometry] = presetCollection(_addablePresetIDs.map(getItem).filter(function(preset) {
|
||||
return preset.geometry.indexOf(geometry) !== -1;
|
||||
}));
|
||||
});
|
||||
} else if (d.defaults) {
|
||||
_defaults = {
|
||||
area: presetCollection(d.defaults.area.map(getItem)),
|
||||
line: presetCollection(d.defaults.line.map(getItem)),
|
||||
point: presetCollection(d.defaults.point.map(getItem)),
|
||||
vertex: presetCollection(d.defaults.vertex.map(getItem)),
|
||||
relation: presetCollection(d.defaults.relation.map(getItem))
|
||||
};
|
||||
}
|
||||
|
||||
for (var i = 0; i < all.collection.length; i++) {
|
||||
var preset = all.collection[i];
|
||||
var geometry = preset.geometry;
|
||||
|
||||
for (var j = 0; j < geometry.length; j++) {
|
||||
var g = _index[geometry[j]];
|
||||
for (var k in preset.tags) {
|
||||
(g[k] = g[k] || []).push(preset);
|
||||
}
|
||||
}
|
||||
}
|
||||
return all;
|
||||
};
|
||||
|
||||
all.init = function(addablePresetIDs) {
|
||||
all.collection = [];
|
||||
_recents = null;
|
||||
_addablePresetIDs = addablePresetIDs;
|
||||
_fields = {};
|
||||
_universal = [];
|
||||
_index = { point: {}, vertex: {}, line: {}, area: {}, relation: {} };
|
||||
|
||||
var addable = true;
|
||||
if (addablePresetIDs) {
|
||||
addable = function(presetID) {
|
||||
return addablePresetIDs.indexOf(presetID) !== -1;
|
||||
};
|
||||
}
|
||||
|
||||
return all.build(data.presets, addable);
|
||||
};
|
||||
|
||||
|
||||
all.reset = function() {
|
||||
all.collection = [];
|
||||
_defaults = { area: all, line: all, point: all, vertex: all, relation: all };
|
||||
_fields = {};
|
||||
_universal = [];
|
||||
_recents = null;
|
||||
|
||||
// Index of presets by (geometry, tag key).
|
||||
_index = {
|
||||
point: {},
|
||||
vertex: {},
|
||||
line: {},
|
||||
area: {},
|
||||
relation: {}
|
||||
return _presetData = {
|
||||
categories: vals[0],
|
||||
defaults: vals[1],
|
||||
presets: vals[2],
|
||||
fields: vals[3]
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
return all;
|
||||
|
||||
// _this.init = (addablePresetIDs) => {
|
||||
_this.init = () => {
|
||||
// _addablePresetIDs = addablePresetIDs;
|
||||
_this.collection = [];
|
||||
_recents = null;
|
||||
_fields = {};
|
||||
_universal = [];
|
||||
_index = { point: {}, vertex: {}, line: {}, area: {}, relation: {} };
|
||||
|
||||
// let addable = true;
|
||||
// if (addablePresetIDs) {
|
||||
// addable = (presetID) => addablePresetIDs.indexOf(presetID) !== -1;
|
||||
// }
|
||||
// return _this.build(d, addable);
|
||||
|
||||
return ensurePresetData()
|
||||
.then(d => _this.build(d));
|
||||
};
|
||||
|
||||
|
||||
_this.reset = () => {
|
||||
_defaults = {
|
||||
point: presetCollection([]),
|
||||
vertex: presetCollection([]),
|
||||
line: presetCollection([]),
|
||||
area: presetCollection([]),
|
||||
relation: presetCollection([])
|
||||
};
|
||||
|
||||
all.fromExternal = function(external, done) {
|
||||
all.reset();
|
||||
d3_json(external)
|
||||
.then(function(externalPresets) {
|
||||
all.build(data.presets, false); // load the default presets as non-addable to start
|
||||
_this.collection = [];
|
||||
_recents = null;
|
||||
_fields = {};
|
||||
_universal = [];
|
||||
_index = { point: {}, vertex: {}, line: {}, area: {}, relation: {} };
|
||||
|
||||
_addablePresetIDs = externalPresets.presets && Object.keys(externalPresets.presets);
|
||||
return _this;
|
||||
};
|
||||
|
||||
all.build(externalPresets, true); // then load the external presets as addable
|
||||
})
|
||||
.catch(function() {
|
||||
all.init();
|
||||
})
|
||||
.finally(function() {
|
||||
done(all);
|
||||
});
|
||||
};
|
||||
|
||||
all.field = function(id) {
|
||||
return _fields[id];
|
||||
};
|
||||
// _this.fromExternal = (external, done) => {
|
||||
// _this.reset();
|
||||
// d3_json(external)
|
||||
// .then(externalPresets => {
|
||||
// _this.build(data.presets, false); // load the default presets as non-addable to start
|
||||
// _addablePresetIDs = externalPresets.presets && Object.keys(externalPresets.presets);
|
||||
// _this.build(externalPresets, true); // then load the external presets as addable
|
||||
// })
|
||||
// .catch(() => _this.init())
|
||||
// .finally(() => done(_this));
|
||||
// };
|
||||
|
||||
all.universal = function() {
|
||||
return _universal;
|
||||
};
|
||||
|
||||
all.defaults = function(geometry, n) {
|
||||
var rec = [];
|
||||
if (!context.inIntro()) {
|
||||
rec = all.recent().matchGeometry(geometry).collection.slice(0, 4);
|
||||
// _this.build = (d, addable) => {
|
||||
_this.build = (d) => {
|
||||
if (d.fields && Object.keys(d.fields).length) {
|
||||
Object.keys(d.fields).forEach(fieldID => {
|
||||
const f = d.fields[fieldID];
|
||||
_fields[fieldID] = presetField(fieldID, f);
|
||||
if (f.universal) {
|
||||
_universal.push(_fields[fieldID]);
|
||||
}
|
||||
var def = utilArrayUniq(rec.concat(_defaults[geometry].collection)).slice(0, n - 1);
|
||||
return presetCollection(utilArrayUniq(rec.concat(def).concat(all.fallback(geometry))));
|
||||
};
|
||||
|
||||
all.recent = function() {
|
||||
return presetCollection(utilArrayUniq(all.getRecents().map(function(d) {
|
||||
return d.preset;
|
||||
})));
|
||||
};
|
||||
|
||||
function RibbonItem(preset, geometry, source) {
|
||||
var item = {};
|
||||
item.preset = preset;
|
||||
item.geometry = geometry;
|
||||
item.source = source;
|
||||
|
||||
item.isRecent = function() {
|
||||
return item.source === 'recent';
|
||||
};
|
||||
item.matches = function(preset, geometry) {
|
||||
return item.preset.id === preset.id && item.geometry === geometry;
|
||||
};
|
||||
item.minified = function() {
|
||||
return {
|
||||
pID: item.preset.id,
|
||||
geom: item.geometry
|
||||
};
|
||||
};
|
||||
return item;
|
||||
});
|
||||
}
|
||||
|
||||
function ribbonItemForMinified(d, source) {
|
||||
if (d && d.pID && d.geom) {
|
||||
var preset = all.item(d.pID);
|
||||
if (!preset) return null;
|
||||
|
||||
var geom = d.geom;
|
||||
// treat point and vertex features as one geometry
|
||||
if (geom === 'vertex') geom = 'point';
|
||||
|
||||
// iD's presets could have changed since this was saved,
|
||||
// so make sure it's still valid.
|
||||
if (preset.matchGeometry(geom) || (geom === 'point' && preset.matchGeometry('vertex'))) {
|
||||
return RibbonItem(preset, geom, source);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function setRecents(items) {
|
||||
_recents = items;
|
||||
var minifiedItems = items.map(function(d) { return d.minified(); });
|
||||
context.storage('preset_recents', JSON.stringify(minifiedItems));
|
||||
|
||||
dispatch.call('recentsChange');
|
||||
}
|
||||
|
||||
all.getRecents = function() {
|
||||
if (!_recents) {
|
||||
// fetch from local storage
|
||||
_recents = (JSON.parse(context.storage('preset_recents')) || [])
|
||||
.reduce(function(output, d) {
|
||||
var item = ribbonItemForMinified(d, 'recent');
|
||||
if (item && item.preset.addable()) output.push(item);
|
||||
return output;
|
||||
}, []);
|
||||
}
|
||||
return _recents;
|
||||
};
|
||||
|
||||
all.removeRecent = function(preset, geometry) {
|
||||
var item = all.recentMatching(preset, geometry);
|
||||
if (item) {
|
||||
var items = all.getRecents();
|
||||
items.splice(items.indexOf(item), 1);
|
||||
setRecents(items);
|
||||
}
|
||||
};
|
||||
|
||||
all.recentMatching = function(preset, geometry) {
|
||||
geometry = all.fallback(geometry).id;
|
||||
var items = all.getRecents();
|
||||
for (var index in items) {
|
||||
if (items[index].matches(preset, geometry)) {
|
||||
return items[index];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
all.moveItem = function(items, fromIndex, toIndex) {
|
||||
if (fromIndex === toIndex ||
|
||||
fromIndex < 0 || toIndex < 0 ||
|
||||
fromIndex >= items.length || toIndex >= items.length) return null;
|
||||
items.splice(toIndex, 0, items.splice(fromIndex, 1)[0]);
|
||||
return items;
|
||||
};
|
||||
|
||||
all.moveRecent = function(item, beforeItem) {
|
||||
var recents = all.getRecents();
|
||||
var fromIndex = recents.indexOf(item);
|
||||
var toIndex = recents.indexOf(beforeItem);
|
||||
var items = all.moveItem(recents, fromIndex, toIndex);
|
||||
if (items) setRecents(items);
|
||||
};
|
||||
|
||||
all.setMostRecent = function(preset, geometry) {
|
||||
if (context.inIntro()) return;
|
||||
if (preset.searchable === false) return;
|
||||
|
||||
geometry = all.fallback(geometry).id;
|
||||
|
||||
var items = all.getRecents();
|
||||
var item = all.recentMatching(preset, geometry);
|
||||
if (item) {
|
||||
items.splice(items.indexOf(item), 1);
|
||||
if (d.presets && Object.keys(d.presets).length) {
|
||||
const rawPresets = d.presets;
|
||||
Object.keys(d.presets).forEach(presetID => {
|
||||
const p = d.presets[presetID];
|
||||
const existing = _this.index(presetID);
|
||||
// const isAddable = typeof addable === 'function' ? addable(presetID, p) : addable;
|
||||
const isAddable = true;
|
||||
if (existing !== -1) {
|
||||
_this.collection[existing] = presetPreset(presetID, p, _fields, isAddable, rawPresets);
|
||||
} else {
|
||||
item = RibbonItem(preset, geometry, 'recent');
|
||||
_this.collection.push(presetPreset(presetID, p, _fields, isAddable, rawPresets));
|
||||
}
|
||||
// allow 30 recents
|
||||
if (items.length === 30) {
|
||||
// remove the last recent (first in, first out)
|
||||
items.pop();
|
||||
}
|
||||
// prepend array
|
||||
items.unshift(item);
|
||||
setRecents(items);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
return utilRebind(all, dispatch, 'on');
|
||||
if (d.categories && Object.keys(d.categories).length) {
|
||||
Object.keys(d.categories).forEach(categoryID => {
|
||||
const c = d.categories[categoryID];
|
||||
const existing = _this.index(categoryID);
|
||||
if (existing !== -1) {
|
||||
_this.collection[existing] = presetCategory(categoryID, c, _this);
|
||||
} else {
|
||||
_this.collection.push(presetCategory(categoryID, c, _this));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const getItem = (_this.item).bind(_this);
|
||||
// if (_addablePresetIDs) {
|
||||
// ['area', 'line', 'point', 'vertex', 'relation'].forEach(geometry => {
|
||||
// _defaults[geometry] = presetCollection(
|
||||
// _addablePresetIDs.map(getItem).filter(preset => preset.geometry.indexOf(geometry) !== -1)
|
||||
// );
|
||||
// });
|
||||
// } else if (d.defaults) {
|
||||
if (d.defaults && Object.keys(d.defaults).length) {
|
||||
_defaults = {
|
||||
point: presetCollection(d.defaults.point.map(getItem)),
|
||||
vertex: presetCollection(d.defaults.vertex.map(getItem)),
|
||||
line: presetCollection(d.defaults.line.map(getItem)),
|
||||
area: presetCollection(d.defaults.area.map(getItem)),
|
||||
relation: presetCollection(d.defaults.relation.map(getItem))
|
||||
};
|
||||
}
|
||||
|
||||
for (let i = 0; i < _this.collection.length; i++) {
|
||||
const preset = _this.collection[i];
|
||||
const geometry = preset.geometry;
|
||||
|
||||
for (let j = 0; j < geometry.length; j++) {
|
||||
let g = _index[geometry[j]];
|
||||
for (let k in preset.tags) {
|
||||
(g[k] = g[k] || []).push(preset);
|
||||
}
|
||||
}
|
||||
}
|
||||
return _this;
|
||||
};
|
||||
|
||||
|
||||
_this.match = (entity, resolver) => {
|
||||
return resolver.transient(entity, 'presetMatch', () => {
|
||||
let geometry = entity.geometry(resolver);
|
||||
// Treat entities on addr:interpolation lines as points, not vertices - #3241
|
||||
if (geometry === 'vertex' && entity.isOnAddressLine(resolver)) {
|
||||
geometry = 'point';
|
||||
}
|
||||
return _this.matchTags(entity.tags, geometry);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
_this.matchTags = (tags, geometry) => {
|
||||
const geometryMatches = _index[geometry];
|
||||
let address;
|
||||
let best = -1;
|
||||
let match;
|
||||
|
||||
for (let k in tags) {
|
||||
// If any part of an address is present, allow fallback to "Address" preset - #4353
|
||||
if (/^addr:/.test(k) && geometryMatches['addr:*']) {
|
||||
address = geometryMatches['addr:*'][0];
|
||||
}
|
||||
|
||||
const keyMatches = geometryMatches[k];
|
||||
if (!keyMatches) continue;
|
||||
|
||||
for (let i = 0; i < keyMatches.length; i++) {
|
||||
const score = keyMatches[i].matchScore(tags);
|
||||
if (score > best) {
|
||||
best = score;
|
||||
match = keyMatches[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (address && (!match || match.isFallback())) {
|
||||
match = address;
|
||||
}
|
||||
return match || _this.fallback(geometry);
|
||||
};
|
||||
|
||||
|
||||
_this.allowsVertex = (entity, resolver) => {
|
||||
if (entity.type !== 'node') return false;
|
||||
if (Object.keys(entity.tags).length === 0) return true;
|
||||
|
||||
return resolver.transient(entity, 'vertexMatch', () => {
|
||||
// address lines allow vertices to act as standalone points
|
||||
if (entity.isOnAddressLine(resolver)) return true;
|
||||
|
||||
const geometries = osmNodeGeometriesForTags(entity.tags);
|
||||
if (geometries.vertex) return true;
|
||||
if (geometries.point) return false;
|
||||
// allow vertices for unspecified points
|
||||
return true;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// Because of the open nature of tagging, iD will never have a complete
|
||||
// list of tags used in OSM, so we want it to have logic like "assume
|
||||
// that a closed way with an amenity tag is an area, unless the amenity
|
||||
// is one of these specific types". This function computes a structure
|
||||
// that allows testing of such conditions, based on the presets designated
|
||||
// as as supporting (or not supporting) the area geometry.
|
||||
//
|
||||
// The returned object L is a keeplist/discardlist of tags. A closed way
|
||||
// with a tag (k, v) is considered to be an area if `k in L && !(v in L[k])`
|
||||
// (see `Way#isArea()`). In other words, the keys of L form the keeplist,
|
||||
// and the subkeys form the discardlist.
|
||||
_this.areaKeys = () => {
|
||||
// The ignore list is for keys that imply lines. (We always add `area=yes` for exceptions)
|
||||
const ignore = ['barrier', 'highway', 'footway', 'railway', 'junction', 'type'];
|
||||
let areaKeys = {};
|
||||
|
||||
// ignore name-suggestion-index and deprecated presets
|
||||
const presets = _this.collection.filter(p => !p.suggestion && !p.replacement);
|
||||
|
||||
// keeplist
|
||||
presets.forEach(p => {
|
||||
let key;
|
||||
for (key in p.tags) break; // pick the first tag
|
||||
if (!key) return;
|
||||
if (ignore.indexOf(key) !== -1) return;
|
||||
|
||||
if (p.geometry.indexOf('area') !== -1) { // probably an area..
|
||||
areaKeys[key] = areaKeys[key] || {};
|
||||
}
|
||||
});
|
||||
|
||||
// discardlist
|
||||
presets.forEach(p => {
|
||||
let key;
|
||||
for (key in p.addTags) {
|
||||
// examine all addTags to get a better sense of what can be tagged on lines - #6800
|
||||
const value = p.addTags[key];
|
||||
if (key in areaKeys && // probably an area...
|
||||
p.geometry.indexOf('line') !== -1 && // but sometimes a line
|
||||
value !== '*') {
|
||||
areaKeys[key][value] = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return areaKeys;
|
||||
};
|
||||
|
||||
|
||||
_this.pointTags = () => {
|
||||
return _this.collection.reduce((pointTags, d) => {
|
||||
// ignore name-suggestion-index, deprecated, and generic presets
|
||||
if (d.suggestion || d.replacement || d.searchable === false) return pointTags;
|
||||
|
||||
// only care about the primary tag
|
||||
let key;
|
||||
for (key in d.tags) break; // pick the first tag
|
||||
if (!key) return pointTags;
|
||||
|
||||
// if this can be a point
|
||||
if (d.geometry.indexOf('point') !== -1) {
|
||||
pointTags[key] = pointTags[key] || {};
|
||||
pointTags[key][d.tags[key]] = true;
|
||||
}
|
||||
return pointTags;
|
||||
}, {});
|
||||
};
|
||||
|
||||
|
||||
_this.vertexTags = () => {
|
||||
return _this.collection.reduce((vertexTags, d) => {
|
||||
// ignore name-suggestion-index, deprecated, and generic presets
|
||||
if (d.suggestion || d.replacement || d.searchable === false) return vertexTags;
|
||||
|
||||
// only care about the primary tag
|
||||
let key;
|
||||
for (key in d.tags) break; // pick the first tag
|
||||
if (!key) return vertexTags;
|
||||
|
||||
// if this can be a vertex
|
||||
if (d.geometry.indexOf('vertex') !== -1) {
|
||||
vertexTags[key] = vertexTags[key] || {};
|
||||
vertexTags[key][d.tags[key]] = true;
|
||||
}
|
||||
return vertexTags;
|
||||
}, {});
|
||||
};
|
||||
|
||||
|
||||
_this.field = (id) => _fields[id];
|
||||
|
||||
_this.universal = () => _universal;
|
||||
|
||||
|
||||
_this.defaults = (geometry, n) => {
|
||||
let rec = [];
|
||||
if (!context.inIntro()) {
|
||||
rec = _this.recent().matchGeometry(geometry).collection.slice(0, 4);
|
||||
}
|
||||
const def = utilArrayUniq(rec.concat(_defaults[geometry].collection)).slice(0, n - 1);
|
||||
return presetCollection(
|
||||
utilArrayUniq(rec.concat(def).concat(_this.fallback(geometry)))
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
_this.recent = () => {
|
||||
return presetCollection(
|
||||
utilArrayUniq(_this.getRecents().map(d => d.preset))
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
function RibbonItem(preset, geometry, source) {
|
||||
let item = {};
|
||||
item.preset = preset;
|
||||
item.geometry = geometry;
|
||||
item.source = source;
|
||||
|
||||
item.isRecent = () => item.source === 'recent';
|
||||
item.matches = (preset, geometry) => item.preset.id === preset.id && item.geometry === geometry;
|
||||
item.minified = () => ({ pID: item.preset.id, geom: item.geometry });
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
|
||||
function ribbonItemForMinified(d, source) {
|
||||
if (d && d.pID && d.geom) {
|
||||
const preset = _this.item(d.pID);
|
||||
if (!preset) return null;
|
||||
|
||||
let geom = d.geom;
|
||||
// treat point and vertex features as one geometry
|
||||
if (geom === 'vertex') geom = 'point';
|
||||
|
||||
// iD's presets could have changed since this was saved,
|
||||
// so make sure it's still valid.
|
||||
if (preset.matchGeometry(geom) || (geom === 'point' && preset.matchGeometry('vertex'))) {
|
||||
return RibbonItem(preset, geom, source);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
function setRecents(items) {
|
||||
_recents = items;
|
||||
const minifiedItems = items.map(d => d.minified());
|
||||
context.storage('preset_recents', JSON.stringify(minifiedItems));
|
||||
dispatch.call('recentsChange');
|
||||
}
|
||||
|
||||
|
||||
_this.getRecents = () => {
|
||||
if (!_recents) {
|
||||
// fetch from local storage
|
||||
_recents = (JSON.parse(context.storage('preset_recents')) || [])
|
||||
.reduce((acc, d) => {
|
||||
let item = ribbonItemForMinified(d, 'recent');
|
||||
if (item && item.preset.addable()) acc.push(item);
|
||||
return acc;
|
||||
}, []);
|
||||
}
|
||||
return _recents;
|
||||
};
|
||||
|
||||
|
||||
_this.removeRecent = (preset, geometry) => {
|
||||
const item = _this.recentMatching(preset, geometry);
|
||||
if (item) {
|
||||
let items = _this.getRecents();
|
||||
items.splice(items.indexOf(item), 1);
|
||||
setRecents(items);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
_this.recentMatching = (preset, geometry) => {
|
||||
geometry = _this.fallback(geometry).id;
|
||||
const items = _this.getRecents();
|
||||
for (let i in items) {
|
||||
if (items[i].matches(preset, geometry)) {
|
||||
return items[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
_this.moveItem = (items, fromIndex, toIndex) => {
|
||||
if (fromIndex === toIndex ||
|
||||
fromIndex < 0 || toIndex < 0 ||
|
||||
fromIndex >= items.length || toIndex >= items.length
|
||||
) return null;
|
||||
|
||||
items.splice(toIndex, 0, items.splice(fromIndex, 1)[0]);
|
||||
return items;
|
||||
};
|
||||
|
||||
|
||||
_this.moveRecent = (item, beforeItem) => {
|
||||
const recents = _this.getRecents();
|
||||
const fromIndex = recents.indexOf(item);
|
||||
const toIndex = recents.indexOf(beforeItem);
|
||||
const items = _this.moveItem(recents, fromIndex, toIndex);
|
||||
if (items) setRecents(items);
|
||||
};
|
||||
|
||||
|
||||
_this.setMostRecent = (preset, geometry) => {
|
||||
if (context.inIntro()) return;
|
||||
if (preset.searchable === false) return;
|
||||
|
||||
geometry = _this.fallback(geometry).id;
|
||||
|
||||
let items = _this.getRecents();
|
||||
let item = _this.recentMatching(preset, geometry);
|
||||
if (item) {
|
||||
items.splice(items.indexOf(item), 1);
|
||||
} else {
|
||||
item = RibbonItem(preset, geometry, 'recent');
|
||||
}
|
||||
|
||||
// remove the last recent (first in, first out)
|
||||
while (items.length >= MAXRECENTS) {
|
||||
items.pop();
|
||||
}
|
||||
|
||||
// prepend array
|
||||
items.unshift(item);
|
||||
setRecents(items);
|
||||
};
|
||||
|
||||
return utilRebind(_this, dispatch, 'on');
|
||||
}
|
||||
|
||||
+233
-257
@@ -4,291 +4,267 @@ import { utilArrayUniq, utilObjectOmit } from '../util';
|
||||
import { utilSafeClassName } from '../util/util';
|
||||
|
||||
|
||||
export function presetPreset(id, preset, fields, addable, rawPresets) {
|
||||
preset = Object.assign({}, preset); // shallow copy
|
||||
export function presetPreset(presetID, preset, fields, addable, rawPresets) {
|
||||
let _this = Object.assign({}, preset); // shallow copy
|
||||
|
||||
preset.id = id;
|
||||
_this.id = presetID;
|
||||
|
||||
// for use in classes, element ids, css selectors
|
||||
preset.safeid = utilSafeClassName(id);
|
||||
// for use in classes, element ids, css selectors
|
||||
_this.safeid = utilSafeClassName(presetID);
|
||||
|
||||
preset.parentPresetID = function() {
|
||||
var endIndex = preset.id.lastIndexOf('/');
|
||||
if (endIndex < 0) return null;
|
||||
|
||||
return preset.id.substring(0, endIndex);
|
||||
};
|
||||
_this.parentPresetID = () => {
|
||||
const endIndex = _this.id.lastIndexOf('/');
|
||||
if (endIndex < 0) return null;
|
||||
return _this.id.substring(0, endIndex);
|
||||
};
|
||||
|
||||
|
||||
// For a preset without fields, use the fields of the parent preset.
|
||||
// Replace {preset} placeholders with the fields of the specified presets.
|
||||
function resolveFieldInheritance() {
|
||||
// For a preset without fields, use the fields of the parent _this.
|
||||
// Replace {preset} placeholders with the fields of the specified presets.
|
||||
function resolveFieldInheritance() {
|
||||
|
||||
// Skip `fields` for the keys which define the preset.
|
||||
// These are usually `typeCombo` fields like `shop=*`
|
||||
function shouldInheritFieldWithID(fieldID) {
|
||||
var f = fields[fieldID];
|
||||
if (f.key) {
|
||||
if (preset.tags[f.key] !== undefined &&
|
||||
// inherit anyway if multiple values are allowed or just a checkbox
|
||||
f.type !== 'multiCombo' && f.type !== 'semiCombo' && f.type !== 'check') {
|
||||
return false;
|
||||
}
|
||||
// Skip `fields` for the keys which define the _this.
|
||||
// These are usually `typeCombo` fields like `shop=*`
|
||||
function shouldInheritFieldWithID(fieldID) {
|
||||
const f = fields[fieldID];
|
||||
if (f.key) {
|
||||
if (_this.tags[f.key] !== undefined &&
|
||||
// inherit anyway if multiple values are allowed or just a checkbox
|
||||
f.type !== 'multiCombo' && f.type !== 'semiCombo' && f.type !== 'check'
|
||||
) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// returns an array of field IDs to inherit from the given presetID, if found
|
||||
function inheritedFieldIDs(presetID, prop) {
|
||||
if (!presetID) return null;
|
||||
|
||||
const inheritPreset = rawPresets[presetID];
|
||||
if (!inheritPreset) return null;
|
||||
|
||||
let inheritFieldIDs = inheritPreset[prop] || [];
|
||||
if (prop === 'fields') {
|
||||
inheritFieldIDs = inheritFieldIDs.filter(shouldInheritFieldWithID);
|
||||
}
|
||||
|
||||
return inheritFieldIDs;
|
||||
}
|
||||
|
||||
|
||||
['fields', 'moreFields'].forEach(prop => {
|
||||
let fieldIDs = [];
|
||||
if (preset[prop] && preset[prop].length) { // fields were defined
|
||||
preset[prop].forEach(fieldID => {
|
||||
const match = fieldID.match(/\{(.*)\}/);
|
||||
if (match !== null) { // presetID wrapped in braces {}
|
||||
const inheritIDs = inheritedFieldIDs(match[1], prop);
|
||||
if (inheritIDs !== null) {
|
||||
fieldIDs = fieldIDs.concat(inheritIDs);
|
||||
} else {
|
||||
/* eslint-disable no-console */
|
||||
console.log(`Cannot resolve presetID ${match[0]} found in ${_this.id} ${prop}`);
|
||||
/* eslint-enable no-console */
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// returns an array of field IDs to inherit from the given presetID, if found
|
||||
function inheritedFieldIDs(presetID, prop) {
|
||||
if (!presetID) return null;
|
||||
|
||||
var inheritPreset = rawPresets[presetID];
|
||||
if (!inheritPreset) return null;
|
||||
|
||||
var inheritFieldIDs = inheritPreset[prop] || [];
|
||||
|
||||
if (prop === 'fields') {
|
||||
inheritFieldIDs = inheritFieldIDs.filter(shouldInheritFieldWithID);
|
||||
}
|
||||
|
||||
return inheritFieldIDs;
|
||||
}
|
||||
|
||||
|
||||
['fields', 'moreFields'].forEach(function(prop) {
|
||||
var fieldIDs = [];
|
||||
if (preset[prop] && preset[prop].length) { // fields were defined
|
||||
preset[prop].forEach(function(fieldID) {
|
||||
var match = fieldID.match(/\{(.*)\}/);
|
||||
if (match !== null) { // presetID wrapped in braces {}
|
||||
var inheritIDs = inheritedFieldIDs(match[1], prop);
|
||||
if (inheritIDs !== null) {
|
||||
fieldIDs = fieldIDs.concat(inheritIDs);
|
||||
} else {
|
||||
/* eslint-disable no-console */
|
||||
console.log('Cannot resolve presetID ' + match[0] +
|
||||
' found in ' + preset.id + ' ' + prop);
|
||||
/* eslint-enable no-console */
|
||||
}
|
||||
} else {
|
||||
fieldIDs.push(fieldID); // no braces - just a normal field
|
||||
}
|
||||
});
|
||||
|
||||
} else { // no fields defined, so use the parent's if possible
|
||||
fieldIDs = inheritedFieldIDs(preset.parentPresetID(), prop);
|
||||
}
|
||||
// resolve duplicate fields
|
||||
fieldIDs = utilArrayUniq(fieldIDs);
|
||||
|
||||
// update this preset with the results
|
||||
preset[prop] = fieldIDs;
|
||||
|
||||
// update the raw object to allow for multiple levels of inheritance
|
||||
rawPresets[preset.id][prop] = fieldIDs;
|
||||
} else {
|
||||
fieldIDs.push(fieldID); // no braces - just a normal field
|
||||
}
|
||||
});
|
||||
|
||||
} else { // no fields defined, so use the parent's if possible
|
||||
fieldIDs = inheritedFieldIDs(_this.parentPresetID(), prop);
|
||||
}
|
||||
fieldIDs = utilArrayUniq(fieldIDs);
|
||||
preset[prop] = fieldIDs;
|
||||
rawPresets[_this.id][prop] = fieldIDs;
|
||||
});
|
||||
}
|
||||
|
||||
if (rawPresets) {
|
||||
resolveFieldInheritance();
|
||||
}
|
||||
|
||||
_this.fields = (_this.fields || []).map(getFields);
|
||||
_this.moreFields = (_this.moreFields || []).map(getFields);
|
||||
_this.geometry = (_this.geometry || []);
|
||||
|
||||
function getFields(f) {
|
||||
return fields[f];
|
||||
}
|
||||
|
||||
|
||||
_this.matchGeometry = (geom) => _this.geometry.indexOf(geom) >= 0;
|
||||
|
||||
_this.matchAllGeometry = (geometries) => {
|
||||
return geometries.every(geom => _this.geometry.indexOf(geom) >= 0);
|
||||
};
|
||||
|
||||
_this.originalScore = _this.matchScore || 1;
|
||||
|
||||
_this.matchScore = (entityTags) => {
|
||||
const tags = _this.tags;
|
||||
let seen = {};
|
||||
let score = 0;
|
||||
|
||||
// match on tags
|
||||
for (let k in tags) {
|
||||
seen[k] = true;
|
||||
if (entityTags[k] === tags[k]) {
|
||||
score += _this.originalScore;
|
||||
} else if (tags[k] === '*' && k in entityTags) {
|
||||
score += _this.originalScore / 2;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (rawPresets) {
|
||||
resolveFieldInheritance();
|
||||
// boost score for additional matches in addTags - #6802
|
||||
const addTags = _this.addTags;
|
||||
for (let k in addTags) {
|
||||
if (!seen[k] && entityTags[k] === addTags[k]) {
|
||||
score += _this.originalScore;
|
||||
}
|
||||
}
|
||||
|
||||
preset.fields = (preset.fields || []).map(getFields);
|
||||
preset.moreFields = (preset.moreFields || []).map(getFields);
|
||||
preset.geometry = (preset.geometry || []);
|
||||
return score;
|
||||
};
|
||||
|
||||
addable = addable || false;
|
||||
|
||||
function getFields(f) {
|
||||
return fields[f];
|
||||
let _textCache = {};
|
||||
_this.t = (scope, options) => {
|
||||
const textID = `presets.presets.${presetID}.${scope}`;
|
||||
if (_textCache[textID]) return _textCache[textID];
|
||||
return _textCache[textID] = t(textID, options);
|
||||
};
|
||||
|
||||
|
||||
_this.originalName = _this.name || '';
|
||||
|
||||
|
||||
_this.name = () => {
|
||||
if (_this.suggestion) {
|
||||
let path = presetID.split('/');
|
||||
path.pop(); // remove brand name
|
||||
// NOTE: insert an en-dash, not a hypen (to avoid conflict with fr - nl names in Brussels etc)
|
||||
return _this.originalName + ' – ' + t('presets.presets.' + path.join('/') + '.name');
|
||||
}
|
||||
return _this.t('name', { 'default': _this.originalName });
|
||||
};
|
||||
|
||||
|
||||
_this.originalTerms = (_this.terms || []).join();
|
||||
|
||||
_this.terms = () => _this.t('terms', { 'default': _this.originalTerms })
|
||||
.toLowerCase().trim().split(/\s*,+\s*/);
|
||||
|
||||
_this.isFallback = () => {
|
||||
const tagCount = Object.keys(_this.tags).length;
|
||||
return tagCount === 0 || (tagCount === 1 && _this.tags.hasOwnProperty('area'));
|
||||
};
|
||||
|
||||
|
||||
addable = addable || false;
|
||||
|
||||
_this.addable = function(val) {
|
||||
if (!arguments.length) return addable;
|
||||
addable = val;
|
||||
return addable;
|
||||
};
|
||||
|
||||
|
||||
const _reference = _this.reference || {};
|
||||
_this.reference = (geom) => {
|
||||
// Lookup documentation on Wikidata...
|
||||
const qid = _this.tags.wikidata || _this.tags['brand:wikidata'] || _this.tags['operator:wikidata'];
|
||||
if (qid) {
|
||||
return { qid: qid };
|
||||
}
|
||||
|
||||
// Lookup documentation on OSM Wikibase...
|
||||
let key = _reference.key || Object.keys(utilObjectOmit(_this.tags, 'name'))[0];
|
||||
let value = _reference.value || _this.tags[key];
|
||||
|
||||
preset.matchGeometry = function(geometry) {
|
||||
return preset.geometry.indexOf(geometry) >= 0;
|
||||
};
|
||||
if (geom === 'relation' && key === 'type') {
|
||||
if (value in _this.tags) {
|
||||
key = value;
|
||||
value = _this.tags[key];
|
||||
} else {
|
||||
return { rtype: value };
|
||||
}
|
||||
}
|
||||
|
||||
if (value === '*') {
|
||||
return { key: key };
|
||||
} else {
|
||||
return { key: key, value: value };
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
preset.matchAllGeometry = function(geometries) {
|
||||
return geometries.every(function(geometry) {
|
||||
return preset.geometry.indexOf(geometry) >= 0;
|
||||
});
|
||||
};
|
||||
_this.removeTags = _this.removeTags || _this.addTags || _this.tags || {};
|
||||
|
||||
_this.unsetTags = (tags, geometry) => {
|
||||
tags = utilObjectOmit(tags, Object.keys(_this.removeTags));
|
||||
|
||||
for (let f in _this.fields) {
|
||||
const field = _this.fields[f];
|
||||
if (field.matchGeometry(geometry) && field.default === tags[field.key]) {
|
||||
delete tags[field.key];
|
||||
}
|
||||
}
|
||||
|
||||
delete tags.area;
|
||||
return tags;
|
||||
};
|
||||
|
||||
|
||||
preset.originalScore = preset.matchScore || 1;
|
||||
_this.addTags = _this.addTags || _this.tags || {};
|
||||
|
||||
_this.setTags = (tags, geometry, skipFieldDefaults) => {
|
||||
const addTags = _this.addTags;
|
||||
tags = Object.assign({}, tags); // shallow copy
|
||||
|
||||
preset.matchScore = function(entityTags) {
|
||||
var tags = preset.tags;
|
||||
var seen = {};
|
||||
var score = 0;
|
||||
var k;
|
||||
for (let k in addTags) {
|
||||
if (addTags[k] === '*') {
|
||||
tags[k] = 'yes';
|
||||
} else {
|
||||
tags[k] = addTags[k];
|
||||
}
|
||||
}
|
||||
|
||||
// match on tags
|
||||
for (k in tags) {
|
||||
seen[k] = true;
|
||||
if (entityTags[k] === tags[k]) {
|
||||
score += preset.originalScore;
|
||||
} else if (tags[k] === '*' && k in entityTags) {
|
||||
score += preset.originalScore / 2;
|
||||
} else {
|
||||
return -1;
|
||||
// Add area=yes if necessary.
|
||||
// This is necessary if the geometry is already an area (e.g. user drew an area) AND any of:
|
||||
// 1. chosen preset could be either an area or a line (`barrier=city_wall`)
|
||||
// 2. chosen preset doesn't have a key in osmAreaKeys (`railway=station`)
|
||||
if (!addTags.hasOwnProperty('area')) {
|
||||
delete tags.area;
|
||||
if (geometry === 'area') {
|
||||
let needsAreaTag = true;
|
||||
if (_this.geometry.indexOf('line') === -1) {
|
||||
for (let k in addTags) {
|
||||
if (k in osmAreaKeys) {
|
||||
needsAreaTag = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// boost score for additional matches in addTags - #6802
|
||||
var addTags = preset.addTags;
|
||||
for (k in addTags) {
|
||||
if (!seen[k] && entityTags[k] === addTags[k]) {
|
||||
score += preset.originalScore;
|
||||
}
|
||||
if (needsAreaTag) {
|
||||
tags.area = 'yes';
|
||||
}
|
||||
|
||||
return score;
|
||||
};
|
||||
|
||||
|
||||
var _textCache = {};
|
||||
|
||||
preset.t = function(scope, options) {
|
||||
var textID = 'presets.presets.' + id + '.' + scope;
|
||||
|
||||
if (_textCache[textID]) return _textCache[textID];
|
||||
|
||||
var text = t(textID, options);
|
||||
_textCache[textID] = text;
|
||||
return text;
|
||||
};
|
||||
|
||||
|
||||
preset.originalName = preset.name || '';
|
||||
|
||||
|
||||
preset.name = function() {
|
||||
if (preset.suggestion) {
|
||||
var path = id.split('/');
|
||||
path.pop(); // remove brand name
|
||||
// NOTE: insert an en-dash, not a hypen (to avoid conflict with fr - nl names in Brussels etc)
|
||||
return preset.originalName + ' – ' + t('presets.presets.' + path.join('/') + '.name');
|
||||
}
|
||||
}
|
||||
if (geometry && !skipFieldDefaults) {
|
||||
for (let f in _this.fields) {
|
||||
const field = _this.fields[f];
|
||||
if (field.matchGeometry(geometry) && field.key && !tags[field.key] && field.default) {
|
||||
tags[field.key] = field.default;
|
||||
}
|
||||
return preset.t('name', { 'default': preset.originalName });
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return tags;
|
||||
};
|
||||
|
||||
|
||||
preset.originalTerms = (preset.terms || []).join();
|
||||
|
||||
|
||||
preset.terms = function() {
|
||||
return preset.t('terms', { 'default': preset.originalTerms }).toLowerCase().trim().split(/\s*,+\s*/);
|
||||
};
|
||||
|
||||
|
||||
preset.isFallback = function() {
|
||||
var tagCount = Object.keys(preset.tags).length;
|
||||
return tagCount === 0 || (tagCount === 1 && preset.tags.hasOwnProperty('area'));
|
||||
};
|
||||
|
||||
preset.addable = function(val) {
|
||||
if (!arguments.length) return addable;
|
||||
addable = val;
|
||||
return addable;
|
||||
};
|
||||
|
||||
|
||||
var reference = preset.reference || {};
|
||||
preset.reference = function(geometry) {
|
||||
// Lookup documentation on Wikidata...
|
||||
var qid = preset.tags.wikidata || preset.tags['brand:wikidata'] || preset.tags['operator:wikidata'];
|
||||
if (qid) {
|
||||
return { qid: qid };
|
||||
}
|
||||
|
||||
// Lookup documentation on OSM Wikibase...
|
||||
var key = reference.key || Object.keys(utilObjectOmit(preset.tags, 'name'))[0];
|
||||
var value = reference.value || preset.tags[key];
|
||||
|
||||
if (geometry === 'relation' && key === 'type') {
|
||||
if (value in preset.tags) {
|
||||
key = value;
|
||||
value = preset.tags[key];
|
||||
} else {
|
||||
return { rtype: value };
|
||||
}
|
||||
}
|
||||
|
||||
if (value === '*') {
|
||||
return { key: key };
|
||||
} else {
|
||||
return { key: key, value: value };
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
preset.removeTags = preset.removeTags || preset.addTags || preset.tags || {};
|
||||
preset.unsetTags = function(tags, geometry) {
|
||||
tags = utilObjectOmit(tags, Object.keys(preset.removeTags));
|
||||
|
||||
for (var f in preset.fields) {
|
||||
var field = preset.fields[f];
|
||||
if (field.matchGeometry(geometry) && field.default === tags[field.key]) {
|
||||
delete tags[field.key];
|
||||
}
|
||||
}
|
||||
|
||||
delete tags.area;
|
||||
return tags;
|
||||
};
|
||||
|
||||
|
||||
preset.addTags = preset.addTags || preset.tags || {};
|
||||
preset.setTags = function(tags, geometry, skipFieldDefaults) {
|
||||
var addTags = preset.addTags;
|
||||
var k;
|
||||
|
||||
tags = Object.assign({}, tags); // shallow copy
|
||||
|
||||
for (k in addTags) {
|
||||
if (addTags[k] === '*') {
|
||||
tags[k] = 'yes';
|
||||
} else {
|
||||
tags[k] = addTags[k];
|
||||
}
|
||||
}
|
||||
|
||||
// Add area=yes if necessary.
|
||||
// This is necessary if the geometry is already an area (e.g. user drew an area) AND any of:
|
||||
// 1. chosen preset could be either an area or a line (`barrier=city_wall`)
|
||||
// 2. chosen preset doesn't have a key in osmAreaKeys (`railway=station`)
|
||||
if (!addTags.hasOwnProperty('area')) {
|
||||
delete tags.area;
|
||||
if (geometry === 'area') {
|
||||
var needsAreaTag = true;
|
||||
if (preset.geometry.indexOf('line') === -1) {
|
||||
for (k in addTags) {
|
||||
if (k in osmAreaKeys) {
|
||||
needsAreaTag = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (needsAreaTag) {
|
||||
tags.area = 'yes';
|
||||
}
|
||||
}
|
||||
}
|
||||
if (geometry && !skipFieldDefaults) {
|
||||
for (var f in preset.fields) {
|
||||
var field = preset.fields[f];
|
||||
if (field.matchGeometry(geometry) && field.key && !tags[field.key] && field.default) {
|
||||
tags[field.key] = field.default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tags;
|
||||
};
|
||||
|
||||
|
||||
return preset;
|
||||
return _this;
|
||||
}
|
||||
|
||||
@@ -22,8 +22,9 @@ export function validationMismatchedGeometry(context) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (context.presets().matchTags(tagSuggestingArea, 'line') ===
|
||||
context.presets().matchTags(tagSuggestingArea, 'area')) {
|
||||
var asLine = context.presets().matchTags(tagSuggestingArea, 'line');
|
||||
var asArea = context.presets().matchTags(tagSuggestingArea, 'area');
|
||||
if (asLine && asArea && (asLine === asArea)) {
|
||||
// these tags also allow lines and making this an area wouldn't matter
|
||||
return null;
|
||||
}
|
||||
@@ -31,6 +32,7 @@ export function validationMismatchedGeometry(context) {
|
||||
return tagSuggestingArea;
|
||||
}
|
||||
|
||||
|
||||
function makeConnectEndpointsFixOnClick(way, graph) {
|
||||
// must have at least three nodes to close this automatically
|
||||
if (way.nodes.length < 3) return null;
|
||||
|
||||
@@ -56,6 +56,7 @@ export function validationOutdatedTags(context) {
|
||||
const oldTags = Object.assign({}, entity.tags); // shallow copy
|
||||
let preset = context.presets().match(entity, graph);
|
||||
let subtype = 'deprecated_tags';
|
||||
if (!preset) return [];
|
||||
|
||||
// upgrade preset..
|
||||
if (preset.replacement) {
|
||||
|
||||
Reference in New Issue
Block a user