Merge branch 'develop' into add_mapilio_data

This commit is contained in:
sezerbozbiyik
2023-05-26 16:17:07 +03:00
21 changed files with 916 additions and 748 deletions
+4 -3
View File
@@ -3,6 +3,7 @@ export function actionChangePreset(entityID, oldPreset, newPreset, skipFieldDefa
var entity = graph.entity(entityID);
var geometry = entity.geometry(graph);
var tags = entity.tags;
const loc = entity.extent(graph).center();
// preserve tags that the new preset might care about, if any
var preserveKeys;
@@ -15,14 +16,14 @@ export function actionChangePreset(entityID, oldPreset, newPreset, skipFieldDefa
// only if old preset is not a sub-preset of the new one:
// preserve tags for which the new preset has a field
// https://github.com/openstreetmap/iD/issues/9372
newPreset.fields().concat(newPreset.moreFields())
newPreset.fields(loc).concat(newPreset.moreFields(loc))
.filter(f => f.matchGeometry(geometry))
.map(f => f.key).filter(Boolean)
.forEach(key => preserveKeys.push(key));
}
}
if (oldPreset) tags = oldPreset.unsetTags(tags, geometry, preserveKeys);
if (newPreset) tags = newPreset.setTags(tags, geometry, skipFieldDefaults);
if (oldPreset) tags = oldPreset.unsetTags(tags, geometry, preserveKeys, loc);
if (newPreset) tags = newPreset.setTags(tags, geometry, skipFieldDefaults, loc);
return graph.replace(entity.update({tags: tags}));
};
+8 -6
View File
@@ -15,9 +15,11 @@ export function modeAddArea(context, mode) {
.on('startFromWay', startFromWay)
.on('startFromNode', startFromNode);
var defaultTags = { area: 'yes' };
if (mode.preset) defaultTags = mode.preset.setTags(defaultTags, 'area');
function defaultTags(loc) {
var defaultTags = { area: 'yes' };
if (mode.preset) defaultTags = mode.preset.setTags(defaultTags, 'area', false, loc);
return defaultTags;
}
function actionClose(wayId) {
return function (graph) {
@@ -29,7 +31,7 @@ export function modeAddArea(context, mode) {
function start(loc) {
var startGraph = context.graph();
var node = osmNode({ loc: loc });
var way = osmWay({ tags: defaultTags });
var way = osmWay({ tags: defaultTags(loc) });
context.perform(
actionAddEntity(node),
@@ -45,7 +47,7 @@ export function modeAddArea(context, mode) {
function startFromWay(loc, edge) {
var startGraph = context.graph();
var node = osmNode({ loc: loc });
var way = osmWay({ tags: defaultTags });
var way = osmWay({ tags: defaultTags(loc) });
context.perform(
actionAddEntity(node),
@@ -61,7 +63,7 @@ export function modeAddArea(context, mode) {
function startFromNode(node) {
var startGraph = context.graph();
var way = osmWay({ tags: defaultTags });
var way = osmWay({ tags: defaultTags(node.loc) });
context.perform(
actionAddEntity(way),
+8 -5
View File
@@ -15,14 +15,17 @@ export function modeAddLine(context, mode) {
.on('startFromWay', startFromWay)
.on('startFromNode', startFromNode);
var defaultTags = {};
if (mode.preset) defaultTags = mode.preset.setTags(defaultTags, 'line');
function defaultTags(loc) {
var defaultTags = {};
if (mode.preset) defaultTags = mode.preset.setTags(defaultTags, 'line', false, loc);
return defaultTags;
}
function start(loc) {
var startGraph = context.graph();
var node = osmNode({ loc: loc });
var way = osmWay({ tags: defaultTags });
var way = osmWay({ tags: defaultTags(loc) });
context.perform(
actionAddEntity(node),
@@ -37,7 +40,7 @@ export function modeAddLine(context, mode) {
function startFromWay(loc, edge) {
var startGraph = context.graph();
var node = osmNode({ loc: loc });
var way = osmWay({ tags: defaultTags });
var way = osmWay({ tags: defaultTags(loc) });
context.perform(
actionAddEntity(node),
@@ -52,7 +55,7 @@ export function modeAddLine(context, mode) {
function startFromNode(node) {
var startGraph = context.graph();
var way = osmWay({ tags: defaultTags });
var way = osmWay({ tags: defaultTags(node.loc) });
context.perform(
actionAddEntity(way),
+11 -7
View File
@@ -19,12 +19,15 @@ export function modeAddPoint(context, mode) {
.on('cancel', cancel)
.on('finish', cancel);
var defaultTags = {};
if (mode.preset) defaultTags = mode.preset.setTags(defaultTags, 'point');
function defaultTags(loc) {
var defaultTags = {};
if (mode.preset) defaultTags = mode.preset.setTags(defaultTags, 'point', false, loc);
return defaultTags;
}
function add(loc) {
var node = osmNode({ loc: loc, tags: defaultTags });
var node = osmNode({ loc: loc, tags: defaultTags(loc) });
context.perform(
actionAddEntity(node),
@@ -36,7 +39,7 @@ export function modeAddPoint(context, mode) {
function addWay(loc, edge) {
var node = osmNode({ tags: defaultTags });
var node = osmNode({ tags: defaultTags(loc) });
context.perform(
actionAddMidpoint({loc: loc, edge: edge}, node),
@@ -54,14 +57,15 @@ export function modeAddPoint(context, mode) {
function addNode(node) {
if (Object.keys(defaultTags).length === 0) {
const _defaultTags = defaultTags(node.loc);
if (Object.keys(_defaultTags).length === 0) {
enterSelectMode(node);
return;
}
var tags = Object.assign({}, node.tags); // shallow copy
for (var key in defaultTags) {
tags[key] = defaultTags[key];
for (var key in _defaultTags) {
tags[key] = _defaultTags[key];
}
context.perform(
-3
View File
@@ -163,9 +163,6 @@ export function presetIndex() {
// Rebuild universal fields array
_universal = Object.values(_fields).filter(field => field.universal);
// Reset all the preset fields - they'll need to be resolved again
Object.values(_presets).forEach(preset => preset.resetFields());
// Rebuild geometry index
_geometryIndex = { point: {}, vertex: {}, line: {}, area: {}, relation: {} };
_this.collection.forEach(preset => {
+25 -15
View File
@@ -1,7 +1,10 @@
import { isEqual } from 'lodash';
import { t } from '../core/localizer';
import { osmAreaKeys, osmAreaKeysExceptions } from '../osm/tags';
import { utilArrayUniq, utilObjectOmit } from '../util';
import { utilSafeClassName } from '../util/util';
import { locationManager } from '../core/LocationManager';
//
@@ -13,8 +16,6 @@ export function presetPreset(presetID, preset, addable, allFields, allPresets) {
allPresets = allPresets || {};
let _this = Object.assign({}, preset); // shallow copy
let _addable = addable || false;
let _resolvedFields; // cache
let _resolvedMoreFields; // cache
let _searchName; // cache
let _searchNameStripped; // cache
let _searchAliases; // cache
@@ -40,11 +41,9 @@ export function presetPreset(presetID, preset, addable, allFields, allPresets) {
_this.originalMoreFields = (_this.moreFields || []);
_this.fields = () => _resolvedFields || (_resolvedFields = resolveFields('fields'));
_this.fields = loc => resolveFields('fields', loc);
_this.moreFields = () => _resolvedMoreFields || (_resolvedMoreFields = resolveFields('moreFields'));
_this.resetFields = () => _resolvedFields = _resolvedMoreFields = null;
_this.moreFields = loc => resolveFields('moreFields', loc);
_this.tags = _this.tags || {};
@@ -219,13 +218,13 @@ export function presetPreset(presetID, preset, addable, allFields, allPresets) {
};
_this.unsetTags = (tags, geometry, ignoringKeys, skipFieldDefaults) => {
_this.unsetTags = (tags, geometry, ignoringKeys, skipFieldDefaults, loc) => {
// allow manually keeping some tags
let removeTags = ignoringKeys ? utilObjectOmit(_this.removeTags, ignoringKeys) : _this.removeTags;
tags = utilObjectOmit(tags, Object.keys(removeTags));
if (geometry && !skipFieldDefaults) {
_this.fields().forEach(field => {
_this.fields(loc).forEach(field => {
if (field.matchGeometry(geometry) && field.key &&
field.default === tags[field.key] &&
(!ignoringKeys || ignoringKeys.indexOf(field.key) === -1)) {
@@ -239,7 +238,7 @@ export function presetPreset(presetID, preset, addable, allFields, allPresets) {
};
_this.setTags = (tags, geometry, skipFieldDefaults) => {
_this.setTags = (tags, geometry, skipFieldDefaults, loc) => {
const addTags = _this.addTags;
tags = Object.assign({}, tags); // shallow copy
@@ -277,7 +276,7 @@ export function presetPreset(presetID, preset, addable, allFields, allPresets) {
}
if (geometry && !skipFieldDefaults) {
_this.fields().forEach(field => {
_this.fields(loc).forEach(field => {
if (field.matchGeometry(geometry) && field.key && !tags[field.key] && field.default) {
tags[field.key] = field.default;
}
@@ -290,14 +289,14 @@ export function presetPreset(presetID, preset, addable, allFields, allPresets) {
// For a preset without fields, use the fields of the parent preset.
// Replace {preset} placeholders with the fields of the specified presets.
function resolveFields(which) {
function resolveFields(which, loc) {
const fieldIDs = (which === 'fields' ? _this.originalFields : _this.originalMoreFields);
let resolved = [];
fieldIDs.forEach(fieldID => {
const match = fieldID.match(referenceRegex);
if (match !== null) { // a presetID wrapped in braces {}
resolved = resolved.concat(inheritFields(match[1], which));
resolved = resolved.concat(inheritFields(allPresets[match[1]], which));
} else if (allFields[fieldID]) { // a normal fieldID
resolved.push(allFields[fieldID]);
} else {
@@ -310,7 +309,19 @@ export function presetPreset(presetID, preset, addable, allFields, allPresets) {
const endIndex = _this.id.lastIndexOf('/');
const parentID = endIndex && _this.id.substring(0, endIndex);
if (parentID) {
resolved = inheritFields(parentID, which);
let parent = allPresets[parentID];
if (loc) {
const validHere = locationManager.locationSetsAt(loc);
if (!validHere[parent.locationSetID]) {
// this is a preset for which a regional variant of the main preset exists
const candidateIDs = Object.keys(allPresets).filter(k => k.startsWith(parentID));
parent = allPresets[candidateIDs.find(candidateID => {
const candidate = allPresets[candidateID];
return validHere[candidate.locationSetID] && isEqual(candidate.tags, parent.tags);
})];
}
}
resolved = inheritFields(parent, which);
}
}
@@ -318,8 +329,7 @@ export function presetPreset(presetID, preset, addable, allFields, allPresets) {
// returns an array of fields to inherit from the given presetID, if found
function inheritFields(presetID, which) {
const parent = allPresets[presetID];
function inheritFields(parent, which) {
if (!parent) return [];
if (which === 'fields') {
+10 -3
View File
@@ -17,7 +17,7 @@ export default {
// Search for Wikidata items matching the query
itemsForSearchQuery: function(query, callback) {
itemsForSearchQuery: function _itemsForSearchQuery(query, callback, language) {
if (!query) {
if (callback) callback('No query', {});
return;
@@ -32,7 +32,7 @@ export default {
search: query,
type: 'item',
// the language to search
language: lang,
language: language || lang,
// the language for the label and description in the result
uselang: lang,
limit: 10,
@@ -42,7 +42,14 @@ export default {
d3_json(url)
.then(function(result) {
if (result && result.error) {
throw new Error(result.error);
if (result.error.code === 'badvalue' &&
result.error.info.includes(lang) &&
!language && lang.includes('-')) {
// retry without "country suffix" region subtag
_itemsForSearchQuery(query, callback, lang.split('-')[0]);
} else {
throw new Error(result.error);
}
}
if (callback) callback(null, result.search || {});
})
+6 -1
View File
@@ -159,7 +159,12 @@ export function svgTagClasses() {
classes.push('tag-wikidata');
}
return classes.join(' ').trim();
// ensure that classes for tags keys/values with special characters like spaces
// are not added to the DOM, because it can cause bizarre issues (#9448)
return classes
.filter(klass => /^[-_a-z0-9]+$/.test(klass))
.join(' ')
.trim();
};
+37 -5
View File
@@ -151,7 +151,7 @@ export function uiFieldCombo(field, context) {
function objectDifference(a, b) {
return a.filter(function(d1) {
return !b.some(function(d2) {
return !d2.isMixed && d1.value === d2.value;
return d1.value === d2.value;
});
});
}
@@ -416,6 +416,17 @@ export function uiFieldCombo(field, context) {
}
function invertMultikey(d3_event, d) {
d3_event.preventDefault();
d3_event.stopPropagation();
var t = {};
if (_isMulti) {
t[d.key] = _tags[d.key] === 'yes' ? 'no' : 'yes';
}
dispatch.call('change', this, t);
}
function combo(selection) {
_container = selection.selectAll('.form-field-input-wrap')
.data([0]);
@@ -455,6 +466,11 @@ export function uiFieldCombo(field, context) {
.attr('class', 'input-wrap')
.merge(_inputWrap);
// Hide 'Add' button if this field uses fixed set of
// options and they're all currently used
var hideAdd = (!_allowCustomValues && !_comboData.length);
_inputWrap.style('display', hideAdd ? 'none' : null);
_input = _inputWrap.selectAll('input')
.data([0]);
} else {
@@ -557,13 +573,13 @@ export function uiFieldCombo(field, context) {
if (!field.key && field.keys.indexOf(k) === -1) continue;
var v = tags[k];
if (!v || (typeof v === 'string' && v.toLowerCase() === 'no')) continue;
var suffix = field.key ? k.slice(field.key.length) : k;
_multiData.push({
key: k,
value: displayValue(suffix),
display: renderValue(suffix),
state: typeof v === 'string' ? v.toLowerCase() : '',
isMixed: Array.isArray(v)
});
}
@@ -623,7 +639,7 @@ export function uiFieldCombo(field, context) {
maxLength = Math.max(0, maxLength);
// Hide 'Add' button if this field is already at its character limit
var hideAdd = maxLength <= 0;
var hideAdd = maxLength <= 0 || (!_allowCustomValues && !_comboData.length);
_container.selectAll('.chiplist .input-wrap')
.style('display', hideAdd ? 'none' : null);
@@ -656,8 +672,24 @@ export function uiFieldCombo(field, context) {
return d.isMixed;
})
.attr('title', function(d) {
return d.isMixed ? t('inspector.unshared_value_tooltip') : null;
});
if (d.isMixed) {
return t('inspector.unshared_value_tooltip');
}
if (!['yes', 'no'].includes(d.state)) {
return d.state;
}
return null;
})
.classed('negated', d => d.state === 'no');
if (!_isSemi) {
chips.selectAll('input[type=checkbox]').remove();
chips.insert('input', 'span')
.attr('type', 'checkbox')
.property('checked', d => d.state === 'yes')
.property('indeterminate', d => d.isMixed || !['yes', 'no'].includes(d.state))
.on('click', invertMultikey);
}
if (allowDragAndDrop) {
registerDragAndDrop(chips);
+35 -7
View File
@@ -384,6 +384,17 @@ export function uiFieldText(field, context) {
}
// returns all values of a (potential) multiselection and/or multi-key field
function getVals(tags) {
if (field.keys) {
return new Set(field.keys.reduce((acc, key) => acc.concat(tags[key]), [])
.filter(Boolean));
} else {
return new Set([].concat(tags[field.key]).filter(Boolean));
}
}
function change(onInput) {
return function() {
var t = {};
@@ -391,7 +402,7 @@ export function uiFieldText(field, context) {
if (!onInput) val = context.cleanTagValue(val);
// don't override multiple values with blank string
if (!val && Array.isArray(_tags[field.key])) return;
if (!val && getVals(_tags).size > 1) return;
if (!onInput) {
if (field.type === 'number' && val) {
@@ -405,7 +416,24 @@ export function uiFieldText(field, context) {
utilGetSetValue(input, val);
}
t[field.key] = val || undefined;
dispatch.call('change', this, t, onInput);
if (field.keys) {
// for multi-key fields with: handle alternative tag keys gracefully
// https://github.com/openstreetmap/id-tagging-schema/issues/905
dispatch.call('change', this, tags => {
if (field.keys.some(key => tags[key])) {
// use exiting key(s)
field.keys.filter(key => tags[key]).forEach(key => {
tags[key] = val || undefined;
});
} else {
// fall back to default key if none of the `keys` is preset
tags[field.key] = val || undefined;
}
return tags;
}, onInput);
} else {
dispatch.call('change', this, t, onInput);
}
};
}
@@ -416,14 +444,14 @@ export function uiFieldText(field, context) {
return i;
};
i.tags = function(tags) {
_tags = tags;
var isMixed = Array.isArray(tags[field.key]);
utilGetSetValue(input, !isMixed && tags[field.key] ? tags[field.key] : '')
.attr('title', isMixed ? tags[field.key].filter(Boolean).join('\n') : undefined)
const vals = getVals(tags);
const isMixed = vals.size > 1;
const val = vals.size === 1 ? [...vals][0] : '';
utilGetSetValue(input, val)
.attr('title', isMixed ? [...vals].join('\n') : undefined)
.attr('placeholder', isMixed ? t('inspector.multiple_values') : (field.placeholder() || t('inspector.unknown')))
.classed('mixed', isMixed);
+1 -1
View File
@@ -96,7 +96,7 @@ export function uiFieldLocalized(field, context) {
var preset = presetManager.match(entity, context.graph());
if (preset) {
var isSuggestion = preset.suggestion;
var fields = preset.fields();
var fields = preset.fields(entity.extent(context.graph()).center());
var showsBrandField = fields.some(function(d) { return d.id === 'brand'; });
var showsOperatorField = fields.some(function(d) { return d.id === 'operator'; });
var setsName = preset.addTags.name;
+8 -2
View File
@@ -4,6 +4,7 @@ import { presetManager } from '../../presets';
import { t, localizer } from '../../core/localizer';
import { utilArrayIdentical } from '../../util/array';
import { utilArrayUnion, utilRebind } from '../../util';
import { geoExtent } from '../../geo/extent';
import { uiField } from '../field';
import { uiFormFields } from '../form_fields';
import { uiSection } from '../section';
@@ -32,6 +33,11 @@ export function uiSectionPresetFields(context) {
return geoms;
}, {}));
const loc = _entityIDs.reduce(function(extent, entityID) {
var entity = context.graph().entity(entityID);
return extent.extend(entity.extent(context.graph()));
}, geoExtent()).center();
var presetsManager = presetManager;
var allFields = [];
@@ -39,8 +45,8 @@ export function uiSectionPresetFields(context) {
var sharedTotalFields;
_presets.forEach(function(preset) {
var fields = preset.fields();
var moreFields = preset.moreFields();
var fields = preset.fields(loc);
var moreFields = preset.moreFields(loc);
allFields = utilArrayUnion(allFields, fields);
allMoreFields = utilArrayUnion(allMoreFields, moreFields);
+29 -8
View File
@@ -1,3 +1,5 @@
import { isEqual } from 'lodash';
import { actionAddMidpoint } from '../actions/add_midpoint';
import { actionChangeTags } from '../actions/change_tags';
import { actionMergeNodes } from '../actions/merge_nodes';
@@ -123,9 +125,8 @@ export function validationCrossingWays(context) {
motorway: true, motorway_link: true, trunk: true, trunk_link: true,
primary: true, primary_link: true, secondary: true, secondary_link: true
};
var nonCrossingHighways = { track: true };
function tagsForConnectionNodeIfAllowed(entity1, entity2, graph) {
function tagsForConnectionNodeIfAllowed(entity1, entity2, graph, lessLikelyTags) {
var featureType1 = getFeatureType(entity1, graph);
var featureType2 = getFeatureType(entity2, graph);
@@ -141,11 +142,18 @@ export function validationCrossingWays(context) {
// one feature is a path but not both
var roadFeature = entity1IsPath ? entity2 : entity1;
if (nonCrossingHighways[roadFeature.tags.highway]) {
// don't mark path connections with certain roads as crossings
var pathFeature = entity1IsPath ? entity1 : entity2;
// don't mark path connections with tracks as crossings
if (roadFeature.tags.highway === 'track') {
return {};
}
// a sidewalk crossing a driveway is unremarkable and unlikely to be interrupted by the driveway
// a sidewalk crossing another kind of service road may be similarly unremarkable
if (!lessLikelyTags &&
roadFeature.tags.highway === 'service' &&
pathFeature.tags.highway === 'footway' && pathFeature.tags.footway === 'sidewalk') {
return {};
}
var pathFeature = entity1IsPath ? entity1 : entity2;
if (['marked', 'unmarked', 'traffic_signals', 'uncontrolled'].indexOf(pathFeature.tags.crossing) !== -1) {
// if the path is a crossing, match the crossing type
return bothLines ? { highway: 'crossing', crossing: pathFeature.tags.crossing } : {};
@@ -435,6 +443,10 @@ export function validationCrossingWays(context) {
if (connectionTags) {
fixes.push(makeConnectWaysFix(this.data.connectionTags));
let lessLikelyConnectionTags = tagsForConnectionNodeIfAllowed(entities[0], entities[1], graph, true);
if (lessLikelyConnectionTags && !isEqual(connectionTags, lessLikelyConnectionTags)) {
fixes.push(makeConnectWaysFix(lessLikelyConnectionTags));
}
}
if (isCrossingIndoors) {
@@ -692,16 +704,23 @@ export function validationCrossingWays(context) {
function makeConnectWaysFix(connectionTags) {
var fixTitleID = 'connect_features';
var fixIcon = 'iD-icon-crossing';
if (connectionTags.highway === 'crossing') {
fixTitleID = 'connect_using_crossing';
fixIcon = 'temaki-pedestrian';
}
if (connectionTags.ford) {
fixTitleID = 'connect_using_ford';
if (connectionTags.highway) {
fixIcon = 'temaki-pedestrian';
}
}
return new validationIssueFix({
icon: 'iD-icon-crossing',
const fix = new validationIssueFix({
icon: fixIcon,
title: t.append('issues.fix.' + fixTitleID + '.title'),
onClick: function(context) {
var loc = this.issue.loc;
var connectionTags = this.issue.data.connectionTags;
var edges = this.issue.data.edges;
context.perform(
@@ -737,6 +756,8 @@ export function validationCrossingWays(context) {
);
}
});
fix._connectionTags = connectionTags;
return fix;
}
function makeChangeLayerFix(higherOrLower) {