mirror of
https://github.com/FoggedLens/iD.git
synced 2026-05-29 19:29:35 +02:00
Merge branch 'develop' into add_mapilio_data
This commit is contained in:
+16
-3
@@ -37,39 +37,52 @@ _Breaking developer changes, which may affect downstream projects or sites that
|
||||
|
||||
# Unreleased (2.26.0-dev)
|
||||
#### :tada: New Features
|
||||
* Combo fields for tags with `yes/no` values now also display the `no` state and allow to toggle between the two states ([#7427])
|
||||
#### :sparkles: Usability & Accessibility
|
||||
* Make it easier to search for OSM objects by id ([#9520], thanks [@k-yle])
|
||||
#### :scissors: Operations
|
||||
#### :camera: Street-Level
|
||||
#### :white_check_mark: Validation
|
||||
* Offer to connect sidewalk to service road without tagging the connection as a crossing ([#9650], thanks [@1ec5])
|
||||
#### :bug: Bugfixes
|
||||
* Fix `multi/many/semiCombo` options for not being selectable immediately after removing them for fields with predefined options
|
||||
* Fix a bug where the _Add_ input element on comboboxes with a fixed set of allowed options is still hidden after an option of a previously "fully saturated" field is removed
|
||||
* Fix wrongly flagged "incorrect geometry type" warnings for features with lifecycle-prefixed tags ([#9483], thanks [@biswajit-k])
|
||||
* Fix corruption of tag values of fields with referenced strings, but restricted `options`, when an unavailable option is entered manually into the field.
|
||||
* Properly handle case sensitive tag values in taginfo suggestions in raw tag editor ([#9640])
|
||||
* Fix dysfunctional autocomplete of wikidata fields for some languages with country-codes ([#9638])
|
||||
* Prevent certain tag values from corrupting css classes when they contain whitespaces ([#9637], thanks [@k-yle])
|
||||
#### :earth_asia: Localization
|
||||
* Send `Accept-Language` header on Nominatim API calls ([#9501], thanks [@k-yle])
|
||||
* Add Address and Phone Format for India ([#9482], thanks [@biswajit-k])
|
||||
* Add Address and Phone format for India ([#9482], thanks [@biswajit-k])
|
||||
* Add Address format for the Philippines ([#9482], thanks [@bryceco])
|
||||
#### :hourglass: Performance
|
||||
#### :mortar_board: Walkthrough / Help
|
||||
#### :rocket: Presets
|
||||
* Render "oneway" arrows for features with `waterway=pressurized`, `waterway=spillway`, `seamark:type=two-way_route` or `seamark:type=recommended_traffic_lane` ([#9492], thanks [@k-yle])
|
||||
* Render "right-side" arrows for features with lifecycle prefixes ([#9493], thanks [@k-yle])
|
||||
* Take regional variants of parent presets into account when resolving preset fields ([#9524])
|
||||
* Render "right-side" arrows for `man_made=quay` features
|
||||
#### :hammer: Development
|
||||
* Upgrade dependencies: `fortawesome` to v6.4, `which-polygon` to v2.2.1, `glob` to v9.2, `temaki` to v5.3, `marked` to v4.3, `core-js-bundle` to v3.30
|
||||
* Upgrade dependencies: `fortawesome` to v6.4, `which-polygon` to v2.2.1, `glob` to v9.2, `temaki` to v5.4, `marked` to v4.3, `core-js-bundle` to v3.30, `osm-auth` to v2.1
|
||||
* Bundle `package-lock.json` file in repository for faster `clean-install` builds
|
||||
* Build icons from configured presets source and also process field value `icons` in `npm run build:data`
|
||||
|
||||
[#7427]: https://github.com/openstreetmap/iD/issues/7427
|
||||
[#9482]: https://github.com/openstreetmap/iD/pull/9482
|
||||
[#9483]: https://github.com/openstreetmap/iD/pull/9483
|
||||
[#9492]: https://github.com/openstreetmap/iD/pull/9492
|
||||
[#9493]: https://github.com/openstreetmap/iD/pull/9493
|
||||
[#9520]: https://github.com/openstreetmap/iD/pull/9520
|
||||
[#9501]: https://github.com/openstreetmap/iD/pull/9501
|
||||
[#9520]: https://github.com/openstreetmap/iD/pull/9520
|
||||
[#9524]: https://github.com/openstreetmap/iD/issues/9524
|
||||
[#9630]: https://github.com/openstreetmap/iD/pull/9630
|
||||
[#9637]: https://github.com/openstreetmap/iD/pull/9637
|
||||
[#9638]: https://github.com/openstreetmap/iD/pull/9638
|
||||
[#9640]: https://github.com/openstreetmap/iD/issues/9640
|
||||
[#9650]: https://github.com/openstreetmap/iD/pull/9650
|
||||
[@biswajit-k]: https://github.com/biswajit-k
|
||||
[@bryceco]: https://github.com/bryceco
|
||||
|
||||
|
||||
# 2.25.2
|
||||
|
||||
@@ -1676,6 +1676,12 @@ input.date-selector {
|
||||
max-width: 100%;
|
||||
color: #7092ff;
|
||||
}
|
||||
.form-field-input-multicombo li.chip.negated span {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
.form-field-input-multicombo li.chip input {
|
||||
width: 1em;
|
||||
}
|
||||
.ideditor[dir='ltr'] .form-field-input-multicombo li.chip {
|
||||
padding: 2px 0px 2px 5px;
|
||||
}
|
||||
|
||||
@@ -186,5 +186,13 @@
|
||||
["city", "postcode"],
|
||||
["district"]
|
||||
]
|
||||
},
|
||||
{
|
||||
"countryCodes": ["ph"],
|
||||
"format": [
|
||||
["unit", "housenumber", "street"],
|
||||
["district", "city"],
|
||||
["postcode", "province"]
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1931,6 +1931,8 @@ en:
|
||||
title: Connect this feature
|
||||
connect_features:
|
||||
title: Connect the features
|
||||
connect_using_crossing:
|
||||
title: Connect using a crossing
|
||||
connect_using_ford:
|
||||
title: Connect using a ford
|
||||
continue_from_start:
|
||||
|
||||
@@ -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}));
|
||||
};
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
@@ -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') {
|
||||
|
||||
@@ -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 || {});
|
||||
})
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Generated
+669
-662
File diff suppressed because it is too large
Load Diff
+15
-15
@@ -55,14 +55,14 @@
|
||||
"abortcontroller-polyfill": "^1.7.5",
|
||||
"aes-js": "^3.1.2",
|
||||
"alif-toolkit": "^1.2.9",
|
||||
"core-js-bundle": "^3.30.1",
|
||||
"core-js-bundle": "^3.30.2",
|
||||
"diacritics": "1.3.0",
|
||||
"fast-deep-equal": "~3.1.1",
|
||||
"fast-json-stable-stringify": "2.1.0",
|
||||
"lodash-es": "~4.17.15",
|
||||
"marked": "~4.3.0",
|
||||
"marked": "~5.0.2",
|
||||
"node-diff3": "~3.1.0",
|
||||
"osm-auth": "~2.0.1",
|
||||
"osm-auth": "~2.1.0",
|
||||
"pannellum": "2.5.6",
|
||||
"pbf": "^3.2.1",
|
||||
"polygon-clipping": "~0.15.1",
|
||||
@@ -75,22 +75,22 @@
|
||||
"@fortawesome/free-brands-svg-icons": "~6.4.0",
|
||||
"@fortawesome/free-regular-svg-icons": "~6.4.0",
|
||||
"@fortawesome/free-solid-svg-icons": "~6.4.0",
|
||||
"@rapideditor/temaki": "~5.3.0",
|
||||
"@rapideditor/temaki": "~5.4.0",
|
||||
"@mapbox/maki": "^8.0.1",
|
||||
"@openstreetmap/id-tagging-schema": "^6.0.0",
|
||||
"@openstreetmap/id-tagging-schema": "^6.2.0",
|
||||
"@transifex/api": "^5.2.0",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"chai": "^4.3.4",
|
||||
"chai": "^4.3.7",
|
||||
"chalk": "^4.1.2",
|
||||
"cldr-core": "^41.0.0",
|
||||
"cldr-localenames-full": "^41.0.0",
|
||||
"cldr-core": "^43.0.0",
|
||||
"cldr-localenames-full": "^43.0.0",
|
||||
"concat-files": "^0.1.1",
|
||||
"d3": "~7.8.1",
|
||||
"d3": "~7.8.4",
|
||||
"dotenv": "^16.0.3",
|
||||
"editor-layer-index": "github:osmlab/editor-layer-index#gh-pages",
|
||||
"esbuild": "^0.17.18",
|
||||
"esbuild": "^0.17.19",
|
||||
"esbuild-visualizer": "^0.4.0",
|
||||
"eslint": "^8.8.0",
|
||||
"eslint": "^8.41.0",
|
||||
"fetch-mock": "^9.11.0",
|
||||
"gaze": "^1.1.3",
|
||||
"glob": "^9.2.1",
|
||||
@@ -98,19 +98,19 @@
|
||||
"js-yaml": "^4.0.0",
|
||||
"json-stringify-pretty-compact": "^3.0.0",
|
||||
"karma": "^6.4.2",
|
||||
"karma-chrome-launcher": "^3.1.0",
|
||||
"karma-chrome-launcher": "^3.2.0",
|
||||
"karma-coverage": "2.1.1",
|
||||
"karma-mocha": "^2.0.1",
|
||||
"karma-remap-istanbul": "^0.6.0",
|
||||
"mapillary_sprite_source": "^1.8.0",
|
||||
"mapillary-js": "4.1.1",
|
||||
"minimist": "^1.2.8",
|
||||
"mocha": "^10.0.0",
|
||||
"mocha": "^10.2.0",
|
||||
"name-suggestion-index": "~6.0",
|
||||
"node-fetch": "^2.6.9",
|
||||
"node-fetch": "^2.6.11",
|
||||
"npm-run-all": "^4.0.0",
|
||||
"osm-community-index": "~5.5.2",
|
||||
"postcss": "^8.4.21",
|
||||
"postcss": "^8.4.23",
|
||||
"postcss-selector-prepend": "^0.5.0",
|
||||
"shelljs": "^0.8.0",
|
||||
"shx": "^0.3.0",
|
||||
|
||||
@@ -67,7 +67,7 @@ function getLangNamesInNativeLang() {
|
||||
let langDirectoryPaths = fs.readdirSync(cldrMainDir);
|
||||
langDirectoryPaths.forEach(code => {
|
||||
let languagesPath = `${cldrMainDir}${code}/languages.json`;
|
||||
//if (!fs.existsSync(languagesPath)) return;
|
||||
if (!fs.existsSync(languagesPath)) return;
|
||||
let languageObj = JSON.parse(fs.readFileSync(languagesPath, 'utf8')).main[code];
|
||||
let identity = languageObj.identity;
|
||||
|
||||
|
||||
@@ -2,7 +2,12 @@ describe('iD.validations.crossing_ways', function () {
|
||||
var context;
|
||||
|
||||
beforeEach(function() {
|
||||
context = iD.coreContext().assetPath('../dist/').init();
|
||||
const container = d3.select('body').append('div');
|
||||
context = iD.coreContext().assetPath('../dist/').init().container(container);
|
||||
container
|
||||
.append('div')
|
||||
.attr('class', 'main-map')
|
||||
.call(context.map());
|
||||
});
|
||||
|
||||
function createWaysWithOneCrossingPoint(tags1, tags2) {
|
||||
@@ -253,6 +258,17 @@ describe('iD.validations.crossing_ways', function () {
|
||||
verifySingleCrossingIssue(validate(), {});
|
||||
});
|
||||
|
||||
it('flags sidewalk crossing service road', function() {
|
||||
createWaysWithOneCrossingPoint({ highway: 'service' }, { highway: 'footway', footway: 'sidewalk' });
|
||||
const issues = validate();
|
||||
verifySingleCrossingIssue(issues, {});
|
||||
context.enter(iD.modeSelect(context, ['w-1']));
|
||||
const dynamicFixes = issues[0].dynamicFixes(context);
|
||||
expect(dynamicFixes).to.have.lengthOf(5);
|
||||
expect(dynamicFixes[0]._connectionTags).to.eql({});
|
||||
expect(dynamicFixes[1]._connectionTags).to.eql({ highway: 'crossing' });
|
||||
});
|
||||
|
||||
it('flags road crossing railway', function() {
|
||||
createWaysWithOneCrossingPoint({ highway: 'residential' }, { railway: 'rail' });
|
||||
verifySingleCrossingIssue(validate(), { railway: 'level_crossing' });
|
||||
|
||||
Reference in New Issue
Block a user