mirror of
https://github.com/FoggedLens/iD.git
synced 2026-05-15 05:30:35 +02:00
Merge branch 'develop' into unbundled-presets
# Conflicts: # dist/locales/en.json
This commit is contained in:
@@ -15,7 +15,6 @@ type = YAML
|
||||
|
||||
[id-editor.presets]
|
||||
file_filter = .tx/tmp/presets/<lang>.yaml
|
||||
source_file = data/presets.yaml
|
||||
source_lang = en
|
||||
type = YAML
|
||||
|
||||
|
||||
+10
-1
@@ -457,8 +457,17 @@ An issue with the active [MapRules](https://github.com/radiant-maxar/maprules) v
|
||||
A feature's tags indicate it should have a different geometry than it currently does.
|
||||
|
||||
* `area_as_line`: an unclosed way has tags implying it should be a closed area (e.g. `area=yes` or `building=yes`)
|
||||
* `vertex_as_point`: a detached node has tags implying it should be part of a way (e.g. `highway=stop`)
|
||||
* `area_as_point`
|
||||
* `area_as_vertex`
|
||||
* `line_as_area`
|
||||
* `line_as_point`
|
||||
* `line_as_vertex`: a detached node has tags implying it should be a line (e.g. `highway=motorway`)
|
||||
* `point_as_area`
|
||||
* `point_as_line`
|
||||
* `point_as_vertex`: a vertex node has tags implying it should be detached from ways (e.g. `amenity=cafe`)
|
||||
* `vertex_as_area`
|
||||
* `vertex_as_line`
|
||||
* `vertex_as_point`: a detached node has tags implying it should be part of a way (e.g. `highway=stop`)
|
||||
* `unclosed_multipolygon_part`: a relation is tagged as a multipolygon but not all of its member ways form closed rings
|
||||
|
||||
##### `missing_role`
|
||||
|
||||
+14
-3
@@ -1094,6 +1094,14 @@ a.hide-toggle {
|
||||
fill: transparent;
|
||||
}
|
||||
|
||||
.preset-icon-category-border path {
|
||||
stroke: #999;
|
||||
stroke-width: 1px;
|
||||
fill: transparent;
|
||||
backface-visibility: hidden;
|
||||
vector-effect: non-scaling-stroke;
|
||||
}
|
||||
|
||||
.preset-icon-line {
|
||||
margin: auto;
|
||||
position: absolute;
|
||||
@@ -1169,7 +1177,7 @@ a.hide-toggle {
|
||||
.preset-icon.framed .icon {
|
||||
transform: scale(0.4);
|
||||
}
|
||||
.preset-icon.framed.line-geom .icon,
|
||||
.preset-icon.framed.line-geom:not(.category) .icon,
|
||||
.preset-icon.framed.route-geom .icon {
|
||||
top: 20%;
|
||||
transform: translateY(-30%) scale(0.4);
|
||||
@@ -1180,7 +1188,7 @@ a.hide-toggle {
|
||||
.preset-icon-iD.framed .icon {
|
||||
transform: scale(0.74);
|
||||
}
|
||||
.preset-icon-iD.framed.line-geom .icon,
|
||||
.preset-icon-iD.framed.line-geom:not(.category) .icon,
|
||||
.preset-icon-iD.framed.route-geom .icon {
|
||||
transform: translateY(-30%) scale(0.74);
|
||||
}
|
||||
@@ -4382,9 +4390,12 @@ img.tile-debug {
|
||||
justify-content: space-between;
|
||||
align-items: flex-end;
|
||||
z-index: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.attribution-wrap * { pointer-events: all; }
|
||||
.attribution-wrap > * {
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.attribution-wrap .base-layer-attribution,
|
||||
.attribution-wrap .overlay-layer-attribution {
|
||||
|
||||
@@ -580,6 +580,7 @@ en:
|
||||
upload_explanation: "The changes you upload will be visible on all maps that use OpenStreetMap data."
|
||||
upload_explanation_with_user: "The changes you upload as {user} will be visible on all maps that use OpenStreetMap data."
|
||||
request_review: "I would like someone to review my edits."
|
||||
request_review_info: "Unsure about something? Invite an experienced mapper to check your work once it's live."
|
||||
save: Upload
|
||||
cancel: Cancel
|
||||
changes: Changes
|
||||
@@ -1649,6 +1650,8 @@ en:
|
||||
message: "{feature} ends very close to itself but does not reconnect"
|
||||
highway-highway:
|
||||
reference: Intersecting highways should share a junction vertex.
|
||||
area_as_point:
|
||||
message: '{feature} should be an area, not a point'
|
||||
close_nodes:
|
||||
title: "Very Close Points"
|
||||
tip: "Find redundant and crowded points"
|
||||
@@ -1728,9 +1731,14 @@ en:
|
||||
message: '{feature} has an invalid email address'
|
||||
message_multi: '{feature} has multiple invalid email addresses'
|
||||
reference: 'Email addresses must look like "user@example.com".'
|
||||
line_as_area:
|
||||
message: '{feature} should be a line, not an area'
|
||||
line_as_point:
|
||||
message: '{feature} should be a line, not a point'
|
||||
mismatched_geometry:
|
||||
title: Mismatched Geometry
|
||||
tip: "Find features with conflicting tags and geometry"
|
||||
reference: Most features are limited to certain geometry types.
|
||||
missing_role:
|
||||
title: Missing Roles
|
||||
message: "{member} has no role within {relation}"
|
||||
@@ -1762,6 +1770,10 @@ en:
|
||||
message: "{feature} looks like a brand with nonstandard tags"
|
||||
message_incomplete: "{feature} looks like a brand with incomplete tags"
|
||||
reference: "All features of the same brand should be tagged the same way."
|
||||
point_as_area:
|
||||
message: '{feature} should be a point, not an area'
|
||||
point_as_line:
|
||||
message: '{feature} should be a point, not a line'
|
||||
point_as_vertex:
|
||||
message: '{feature} should be a standalone point based on its tags'
|
||||
reference: "Some features shouldn't be part of lines or areas."
|
||||
@@ -1835,6 +1847,9 @@ en:
|
||||
title: Continue drawing from start
|
||||
continue_from_end:
|
||||
title: Continue drawing from end
|
||||
convert_to_line:
|
||||
title: Convert this to a line
|
||||
annotation: Converted an area to a line.
|
||||
delete_feature:
|
||||
title: Delete this feature
|
||||
extract_point:
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
@@ -89,7 +89,8 @@ export function behaviorDraw(context) {
|
||||
var p2 = downPointer.pointerLocGetter(d3_event);
|
||||
var dist = geoVecLength(downPointer.downLoc, p2);
|
||||
|
||||
if (dist < _closeTolerance || (dist < _tolerance && (t2 - downPointer.downTime) < 500)) {
|
||||
if (dist < _closeTolerance ||
|
||||
(dist < _tolerance && (t2 - downPointer.downTime) < 500)) {
|
||||
// Prevent a quick second click
|
||||
d3_select(window).on('click.draw-block', function() {
|
||||
d3_event.stopPropagation();
|
||||
|
||||
@@ -238,8 +238,15 @@ export function behaviorDrawWay(context, wayID, mode, startGraph) {
|
||||
_drawNode = undefined;
|
||||
_didResolveTempEdit = false;
|
||||
_origWay = context.entity(wayID);
|
||||
_headNodeID = typeof _nodeIndex === 'number' ? _origWay.nodes[_nodeIndex] :
|
||||
(_origWay.isClosed() ? _origWay.nodes[_origWay.nodes.length - 2] : _origWay.nodes[_origWay.nodes.length - 1]);
|
||||
|
||||
if (typeof _nodeIndex === 'number') {
|
||||
_headNodeID = _origWay.nodes[_nodeIndex];
|
||||
} else if (_origWay.isClosed()) {
|
||||
_headNodeID = _origWay.nodes[_origWay.nodes.length - 2];
|
||||
} else {
|
||||
_headNodeID = _origWay.nodes[_origWay.nodes.length - 1];
|
||||
}
|
||||
|
||||
_wayGeometry = _origWay.geometry(context.graph());
|
||||
_annotation = t((_origWay.nodes.length === (_origWay.isClosed() ? 2 : 1) ?
|
||||
'operations.start.annotation.' :
|
||||
|
||||
+35
-7
@@ -7,10 +7,12 @@ import { t } from '../core/localizer';
|
||||
import { actionMove } from '../actions/move';
|
||||
import { actionNoop } from '../actions/noop';
|
||||
import { behaviorEdit } from '../behavior/edit';
|
||||
import { geoViewportEdge, geoVecSubtract } from '../geo';
|
||||
import { geoVecLength, geoVecSubtract } from '../geo/vector';
|
||||
import { geoViewportEdge } from '../geo/geom';
|
||||
import { modeBrowse } from './browse';
|
||||
import { modeSelect } from './select';
|
||||
import { utilKeybinding } from '../util';
|
||||
import { utilFastMouse } from '../util/util';
|
||||
|
||||
|
||||
import { operationCircularize } from '../operations/circularize';
|
||||
@@ -21,6 +23,9 @@ import { operationRotate } from '../operations/rotate';
|
||||
|
||||
|
||||
export function modeMove(context, entityIDs, baseGraph) {
|
||||
|
||||
var _tolerancePx = 4; // see also behaviorDrag, behaviorSelect, modeRotate
|
||||
|
||||
var mode = {
|
||||
id: 'move',
|
||||
button: 'browse'
|
||||
@@ -45,6 +50,9 @@ export function modeMove(context, entityIDs, baseGraph) {
|
||||
var _origin;
|
||||
var _nudgeInterval;
|
||||
|
||||
// use pointer events on supported platforms; fallback to mouse events
|
||||
var _pointerPrefix = 'PointerEvent' in window ? 'pointer' : 'mouse';
|
||||
|
||||
|
||||
function doMove(nudge) {
|
||||
nudge = nudge || [0, 0];
|
||||
@@ -129,12 +137,29 @@ export function modeMove(context, entityIDs, baseGraph) {
|
||||
|
||||
behaviors.forEach(context.install);
|
||||
|
||||
var downEvent;
|
||||
|
||||
context.surface()
|
||||
.on('mousemove.move', move)
|
||||
.on('click.move', finish);
|
||||
.on(_pointerPrefix + 'down.modeMove', function(d3_event) {
|
||||
downEvent = d3_event;
|
||||
});
|
||||
|
||||
d3_select(window)
|
||||
.on(_pointerPrefix + 'move.modeMove', move, true)
|
||||
.on(_pointerPrefix + 'up.modeMove', function(d3_event) {
|
||||
if (!downEvent) return;
|
||||
var mapNode = context.container().select('.main-map').node();
|
||||
var pointGetter = utilFastMouse(mapNode);
|
||||
var p1 = pointGetter(downEvent);
|
||||
var p2 = pointGetter(d3_event);
|
||||
var dist = geoVecLength(p1, p2);
|
||||
|
||||
if (dist <= _tolerancePx) finish(d3_event);
|
||||
downEvent = null;
|
||||
}, true);
|
||||
|
||||
context.history()
|
||||
.on('undone.move', undone);
|
||||
.on('undone.modeMove', undone);
|
||||
|
||||
keybinding
|
||||
.on('⎋', cancel)
|
||||
@@ -153,11 +178,14 @@ export function modeMove(context, entityIDs, baseGraph) {
|
||||
});
|
||||
|
||||
context.surface()
|
||||
.on('mousemove.move', null)
|
||||
.on('click.move', null);
|
||||
.on(_pointerPrefix + 'down.modeMove', null);
|
||||
|
||||
d3_select(window)
|
||||
.on(_pointerPrefix + 'move.modeMove', null, true)
|
||||
.on(_pointerPrefix + 'up.modeMove', null, true);
|
||||
|
||||
context.history()
|
||||
.on('undone.move', null);
|
||||
.on('undone.modeMove', null);
|
||||
|
||||
d3_select(document)
|
||||
.call(keybinding.unbind);
|
||||
|
||||
+37
-10
@@ -11,7 +11,7 @@ import { t } from '../core/localizer';
|
||||
import { actionRotate } from '../actions/rotate';
|
||||
import { actionNoop } from '../actions/noop';
|
||||
import { behaviorEdit } from '../behavior/edit';
|
||||
import { geoVecInterp } from '../geo';
|
||||
import { geoVecInterp, geoVecLength } from '../geo/vector';
|
||||
import { modeBrowse } from './browse';
|
||||
import { modeSelect } from './select';
|
||||
|
||||
@@ -21,10 +21,14 @@ import { operationMove } from '../operations/move';
|
||||
import { operationOrthogonalize } from '../operations/orthogonalize';
|
||||
import { operationReflectLong, operationReflectShort } from '../operations/reflect';
|
||||
|
||||
import { utilGetAllNodes, utilKeybinding } from '../util';
|
||||
import { utilKeybinding } from '../util/keybinding';
|
||||
import { utilFastMouse, utilGetAllNodes } from '../util/util';
|
||||
|
||||
|
||||
export function modeRotate(context, entityIDs) {
|
||||
|
||||
var _tolerancePx = 4; // see also behaviorDrag, behaviorSelect, modeMove
|
||||
|
||||
var mode = {
|
||||
id: 'rotate',
|
||||
button: 'browse'
|
||||
@@ -49,8 +53,11 @@ export function modeRotate(context, entityIDs) {
|
||||
var _prevTransform;
|
||||
var _pivot;
|
||||
|
||||
// use pointer events on supported platforms; fallback to mouse events
|
||||
var _pointerPrefix = 'PointerEvent' in window ? 'pointer' : 'mouse';
|
||||
|
||||
function doRotate() {
|
||||
|
||||
function doRotate(d3_event) {
|
||||
var fn;
|
||||
if (context.graph() !== _prevGraph) {
|
||||
fn = context.perform;
|
||||
@@ -73,7 +80,7 @@ export function modeRotate(context, entityIDs) {
|
||||
}
|
||||
|
||||
|
||||
var currMouse = context.map().mouse();
|
||||
var currMouse = context.map().mouse(d3_event);
|
||||
var currAngle = Math.atan2(currMouse[1] - _pivot[1], currMouse[0] - _pivot[0]);
|
||||
|
||||
if (typeof _prevAngle === 'undefined') _prevAngle = currAngle;
|
||||
@@ -127,12 +134,29 @@ export function modeRotate(context, entityIDs) {
|
||||
|
||||
behaviors.forEach(context.install);
|
||||
|
||||
var downEvent;
|
||||
|
||||
context.surface()
|
||||
.on('mousemove.rotate', doRotate)
|
||||
.on('click.rotate', finish);
|
||||
.on(_pointerPrefix + 'down.modeRotate', function(d3_event) {
|
||||
downEvent = d3_event;
|
||||
});
|
||||
|
||||
d3_select(window)
|
||||
.on(_pointerPrefix + 'move.modeRotate', doRotate, true)
|
||||
.on(_pointerPrefix + 'up.modeRotate', function(d3_event) {
|
||||
if (!downEvent) return;
|
||||
var mapNode = context.container().select('.main-map').node();
|
||||
var pointGetter = utilFastMouse(mapNode);
|
||||
var p1 = pointGetter(downEvent);
|
||||
var p2 = pointGetter(d3_event);
|
||||
var dist = geoVecLength(p1, p2);
|
||||
|
||||
if (dist <= _tolerancePx) finish(d3_event);
|
||||
downEvent = null;
|
||||
}, true);
|
||||
|
||||
context.history()
|
||||
.on('undone.rotate', undone);
|
||||
.on('undone.modeRotate', undone);
|
||||
|
||||
keybinding
|
||||
.on('⎋', cancel)
|
||||
@@ -147,11 +171,14 @@ export function modeRotate(context, entityIDs) {
|
||||
behaviors.forEach(context.uninstall);
|
||||
|
||||
context.surface()
|
||||
.on('mousemove.rotate', null)
|
||||
.on('click.rotate', null);
|
||||
.on(_pointerPrefix + 'down.modeRotate', null);
|
||||
|
||||
d3_select(window)
|
||||
.on(_pointerPrefix + 'move.modeRotate', null, true)
|
||||
.on(_pointerPrefix + 'up.modeRotate', null, true);
|
||||
|
||||
context.history()
|
||||
.on('undone.rotate', null);
|
||||
.on('undone.modeRotate', null);
|
||||
|
||||
d3_select(document)
|
||||
.call(keybinding.unbind);
|
||||
|
||||
@@ -17,8 +17,7 @@ export function operationMove(context, selectedIDs) {
|
||||
|
||||
|
||||
operation.available = function() {
|
||||
return selectedIDs.length > 1 ||
|
||||
context.entity(selectedIDs[0]).type !== 'node';
|
||||
return selectedIDs.length > 0;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -710,7 +710,7 @@ export function rendererMap(context) {
|
||||
|
||||
|
||||
map.mouse = function(d3_event) {
|
||||
var event = _lastPointerEvent || d3_event;
|
||||
var event = d3_event || _lastPointerEvent;
|
||||
if (event) {
|
||||
var s;
|
||||
while ((s = event.sourceEvent)) { event = s; }
|
||||
|
||||
@@ -109,7 +109,9 @@ function preventCoincident(loc, bumpUp) {
|
||||
let coincident = false;
|
||||
do {
|
||||
// first time, move marker up. after that, move marker right.
|
||||
let delta = coincident ? [0.00001, 0] : (bumpUp ? [0, 0.00001] : [0, 0]);
|
||||
let delta = coincident ? [0.00001, 0] :
|
||||
bumpUp ? [0, 0.00001] :
|
||||
[0, 0];
|
||||
loc = geoVecAdd(loc, delta);
|
||||
let bbox = geoExtent(loc).bbox();
|
||||
coincident = _cache.rtree.search(bbox).length;
|
||||
|
||||
@@ -153,7 +153,9 @@ export function svgNotes(projection, context, dispatch) {
|
||||
.attr('x', '-3px')
|
||||
.attr('y', '-19px')
|
||||
.attr('xlink:href', function(d) {
|
||||
return '#iD-icon-' + (d.id < 0 ? 'plus' : (d.status === 'open' ? 'close' : 'apply'));
|
||||
if (d.id < 0) return '#iD-icon-plus';
|
||||
if (d.status === 'open') return '#iD-icon-close';
|
||||
return '#iD-icon-apply';
|
||||
});
|
||||
|
||||
// update
|
||||
@@ -196,7 +198,9 @@ export function svgNotes(projection, context, dispatch) {
|
||||
|
||||
|
||||
function sortY(a, b) {
|
||||
return (a.id === selectedID) ? 1 : (b.id === selectedID) ? -1 : b.loc[1] - a.loc[1];
|
||||
if (a.id === selectedID) return 1;
|
||||
if (b.id === selectedID) return -1;
|
||||
return b.loc[1] - a.loc[1];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -323,6 +323,11 @@ export function uiCommit(context) {
|
||||
.append('label')
|
||||
.attr('for', requestReviewDomId);
|
||||
|
||||
if (!labelEnter.empty()) {
|
||||
labelEnter
|
||||
.call(uiTooltip().title(t.html('commit.request_review_info')).placement('top'));
|
||||
}
|
||||
|
||||
labelEnter
|
||||
.append('input')
|
||||
.attr('type', 'checkbox')
|
||||
|
||||
@@ -125,7 +125,7 @@ export function uiFieldLocalized(field, context) {
|
||||
var existingLangs = new Set(existingLangsOrdered.filter(Boolean));
|
||||
|
||||
for (var k in tags) {
|
||||
var m = k.match(/^(.*):(.+)$/);
|
||||
var m = k.match(/^(.*):(.*)$/);
|
||||
if (m && m[1] === field.key && m[2]) {
|
||||
var item = { lang: m[2], value: tags[k] };
|
||||
if (existingLangs.has(item.lang)) {
|
||||
@@ -138,8 +138,12 @@ export function uiFieldLocalized(field, context) {
|
||||
}
|
||||
}
|
||||
|
||||
_multilingual = _multilingual.filter(function(item) {
|
||||
return !item.lang || !existingLangs.has(item.lang);
|
||||
// Don't remove items based on deleted tags, since this makes the UI
|
||||
// disappear unexpectedly when clearing values - #8164
|
||||
_multilingual.forEach(function(item) {
|
||||
if (item.lang && existingLangs.has(item.lang)) {
|
||||
item.value = '';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -512,16 +516,20 @@ export function uiFieldLocalized(field, context) {
|
||||
if (field.locked()) return;
|
||||
d3_event.preventDefault();
|
||||
|
||||
if (!d.lang || !d.value) {
|
||||
_multilingual.splice(index, 1);
|
||||
renderMultilingual(selection);
|
||||
} else {
|
||||
// remove the UI item manually
|
||||
_multilingual.splice(_multilingual.indexOf(d), 1);
|
||||
|
||||
var langKey = d.lang && key(d.lang);
|
||||
if (langKey && langKey in _tags) {
|
||||
delete _tags[langKey];
|
||||
// remove from entity tags
|
||||
var t = {};
|
||||
t[key(d.lang)] = undefined;
|
||||
t[langKey] = undefined;
|
||||
dispatch.call('change', this, t);
|
||||
return;
|
||||
}
|
||||
|
||||
renderMultilingual(selection);
|
||||
})
|
||||
.call(svgIcon('#iD-operation-delete'));
|
||||
|
||||
@@ -562,9 +570,8 @@ export function uiFieldLocalized(field, context) {
|
||||
|
||||
entries.order();
|
||||
|
||||
entries.classed('present', function(d) {
|
||||
return d.lang && d.value;
|
||||
});
|
||||
// allow removing the entry UIs even if there isn't a tag to remove
|
||||
entries.classed('present', true);
|
||||
|
||||
utilGetSetValue(entries.select('.localized-lang'), function(d) {
|
||||
var langItem = _languagesArray.find(function(item) {
|
||||
|
||||
@@ -31,7 +31,14 @@ export function uiNoteHeader() {
|
||||
.call(svgIcon('#iD-icon-note', 'note-fill'));
|
||||
|
||||
iconEnter.each(function(d) {
|
||||
var statusIcon = '#iD-icon-' + (d.id < 0 ? 'plus' : (d.status === 'open' ? 'close' : 'apply'));
|
||||
var statusIcon;
|
||||
if (d.id < 0) {
|
||||
statusIcon = '#iD-icon-plus';
|
||||
} else if (d.status === 'open') {
|
||||
statusIcon = '#iD-icon-close';
|
||||
} else {
|
||||
statusIcon = '#iD-icon-apply';
|
||||
}
|
||||
iconEnter
|
||||
.append('div')
|
||||
.attr('class', 'note-icon-annotation')
|
||||
|
||||
+83
-45
@@ -59,6 +59,30 @@ export function uiPresetIcon() {
|
||||
}
|
||||
|
||||
|
||||
function renderCategoryBorder(container, drawBorder) {
|
||||
let categoryBorder = container.selectAll('.preset-icon-category-border')
|
||||
.data(drawBorder ? [0] : []);
|
||||
|
||||
categoryBorder.exit()
|
||||
.remove();
|
||||
|
||||
let categoryBorderEnter = categoryBorder.enter();
|
||||
|
||||
const d = 60;
|
||||
|
||||
categoryBorderEnter
|
||||
.append('svg')
|
||||
.attr('class', 'preset-icon-fill preset-icon-category-border')
|
||||
.attr('width', d)
|
||||
.attr('height', d)
|
||||
.attr('viewBox', `0 0 ${d} ${d}`)
|
||||
.append('path')
|
||||
.attr('d', 'M9.5,7.5 L25.5,7.5 L28.5,12.5 L49.5,12.5 C51.709139,12.5 53.5,14.290861 53.5,16.5 L53.5,43.5 C53.5,45.709139 51.709139,47.5 49.5,47.5 L10.5,47.5 C8.290861,47.5 6.5,45.709139 6.5,43.5 L6.5,12.5 L9.5,7.5 Z');
|
||||
|
||||
categoryBorder = categoryBorderEnter.merge(categoryBorder);
|
||||
}
|
||||
|
||||
|
||||
function renderCircleFill(container, drawVertex) {
|
||||
let vertexFill = container.selectAll('.preset-icon-fill-vertex')
|
||||
.data(drawVertex ? [0] : []);
|
||||
@@ -271,6 +295,60 @@ export function uiPresetIcon() {
|
||||
}
|
||||
}
|
||||
|
||||
function renderSvgIcon(container, picon, geom, isFramed, category, tagClasses) {
|
||||
const isMaki = picon && /^maki-/.test(picon);
|
||||
const isTemaki = picon && /^temaki-/.test(picon);
|
||||
const isFa = picon && /^fa[srb]-/.test(picon);
|
||||
const isiDIcon = picon && !(isMaki || isTemaki || isFa);
|
||||
|
||||
let icon = container.selectAll('.preset-icon')
|
||||
.data(picon ? [0] : []);
|
||||
|
||||
icon.exit()
|
||||
.remove();
|
||||
|
||||
icon = icon.enter()
|
||||
.append('div')
|
||||
.attr('class', 'preset-icon')
|
||||
.call(svgIcon(''))
|
||||
.merge(icon);
|
||||
|
||||
icon
|
||||
.attr('class', 'preset-icon ' + (geom ? geom + '-geom' : ''))
|
||||
.classed('category', category)
|
||||
.classed('framed', isFramed)
|
||||
.classed('preset-icon-iD', isiDIcon);
|
||||
|
||||
icon.selectAll('svg')
|
||||
.attr('class', 'icon ' + picon + ' ' + (!isiDIcon && geom !== 'line' ? '' : tagClasses));
|
||||
|
||||
var suffix = '';
|
||||
if (isMaki) {
|
||||
suffix = isSmall() && geom === 'point' ? '-11' : '-15';
|
||||
}
|
||||
|
||||
icon.selectAll('use')
|
||||
.attr('href', '#' + picon + suffix);
|
||||
}
|
||||
|
||||
|
||||
function renderImageIcon(container, imageURL) {
|
||||
let imageIcon = container.selectAll('img.image-icon')
|
||||
.data(imageURL ? [0] : []);
|
||||
|
||||
imageIcon.exit()
|
||||
.remove();
|
||||
|
||||
imageIcon = imageIcon.enter()
|
||||
.append('img')
|
||||
.attr('class', 'image-icon')
|
||||
.on('load', () => container.classed('showing-img', true) )
|
||||
.on('error', () => container.classed('showing-img', false) )
|
||||
.merge(imageIcon);
|
||||
|
||||
imageIcon
|
||||
.attr('src', imageURL);
|
||||
}
|
||||
|
||||
// Route icons are drawn with a zigzag annotation underneath:
|
||||
// o o
|
||||
@@ -310,17 +388,13 @@ export function uiPresetIcon() {
|
||||
const isFallback = isSmall() && p.isFallback && p.isFallback();
|
||||
const imageURL = (showThirdPartyIcons === 'true') && p.imageURL;
|
||||
const picon = getIcon(p, geom);
|
||||
const isMaki = picon && /^maki-/.test(picon);
|
||||
const isTemaki = picon && /^temaki-/.test(picon);
|
||||
const isFa = picon && /^fa[srb]-/.test(picon);
|
||||
const isiDIcon = picon && !(isMaki || isTemaki || isFa);
|
||||
const isCategory = !p.setTags;
|
||||
const drawPoint = picon && geom === 'point' && isSmall() && !isFallback;
|
||||
const drawVertex = picon !== null && geom === 'vertex' && (!isSmall() || !isFallback);
|
||||
const drawLine = picon && geom === 'line' && !isFallback && !isCategory;
|
||||
const drawArea = picon && geom === 'area' && !isFallback;
|
||||
const drawArea = picon && geom === 'area' && !isFallback && !isCategory;
|
||||
const drawRoute = picon && geom === 'route';
|
||||
const isFramed = (drawVertex || drawArea || drawLine || drawRoute);
|
||||
const isFramed = drawVertex || drawArea || drawLine || drawRoute || isCategory;
|
||||
|
||||
let tags = !isCategory ? p.setTags({}, geom) : {};
|
||||
for (let k in tags) {
|
||||
@@ -344,50 +418,14 @@ export function uiPresetIcon() {
|
||||
.classed('showing-img', !!imageURL)
|
||||
.classed('fallback', isFallback);
|
||||
|
||||
renderCategoryBorder(container, isCategory);
|
||||
renderPointBorder(container, drawPoint);
|
||||
renderCircleFill(container, drawVertex);
|
||||
renderSquareFill(container, drawArea, tagClasses);
|
||||
renderLine(container, drawLine, tagClasses);
|
||||
renderRoute(container, drawRoute, p);
|
||||
|
||||
let icon = container.selectAll('.preset-icon')
|
||||
.data(picon ? [0] : []);
|
||||
|
||||
icon.exit()
|
||||
.remove();
|
||||
|
||||
icon = icon.enter()
|
||||
.append('div')
|
||||
.attr('class', 'preset-icon')
|
||||
.call(svgIcon(''))
|
||||
.merge(icon);
|
||||
|
||||
icon
|
||||
.attr('class', 'preset-icon ' + (geom ? geom + '-geom' : ''))
|
||||
.classed('framed', isFramed)
|
||||
.classed('preset-icon-iD', isiDIcon);
|
||||
|
||||
icon.selectAll('svg')
|
||||
.attr('class', 'icon ' + picon + ' ' + (!isiDIcon && geom !== 'line' ? '' : tagClasses));
|
||||
|
||||
icon.selectAll('use')
|
||||
.attr('href', '#' + picon + (isMaki ? (isSmall() && geom === 'point' ? '-11' : '-15') : ''));
|
||||
|
||||
let imageIcon = container.selectAll('img.image-icon')
|
||||
.data(imageURL ? [0] : []);
|
||||
|
||||
imageIcon.exit()
|
||||
.remove();
|
||||
|
||||
imageIcon = imageIcon.enter()
|
||||
.append('img')
|
||||
.attr('class', 'image-icon')
|
||||
.on('load', () => container.classed('showing-img', true) )
|
||||
.on('error', () => container.classed('showing-img', false) )
|
||||
.merge(imageIcon);
|
||||
|
||||
imageIcon
|
||||
.attr('src', imageURL);
|
||||
renderSvgIcon(container, picon, geom, isFramed, isCategory, tagClasses);
|
||||
renderImageIcon(container, imageURL);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -447,7 +447,8 @@ export function utilFastMouse(container) {
|
||||
return function(e) {
|
||||
return [
|
||||
e.clientX - rectLeft - clientLeft,
|
||||
e.clientY - rectTop - clientTop];
|
||||
e.clientY - rectTop - clientTop
|
||||
];
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import deepEqual from 'fast-deep-equal';
|
||||
import { actionAddVertex } from '../actions/add_vertex';
|
||||
import { actionChangeTags } from '../actions/change_tags';
|
||||
import { actionMergeNodes } from '../actions/merge_nodes';
|
||||
import { actionExtract } from '../actions/extract';
|
||||
import { modeSelect } from '../modes/select';
|
||||
import { osmJoinWays } from '../osm/multipolygon';
|
||||
import { osmNodeGeometriesForTags } from '../osm/tags';
|
||||
import { osmNodeGeometriesForTags, osmTagSuggestingArea } from '../osm/tags';
|
||||
import { presetManager } from '../presets';
|
||||
import { geoHasSelfIntersections, geoSphericalDistance } from '../geo';
|
||||
import { t } from '../core/localizer';
|
||||
@@ -139,7 +140,7 @@ export function validationMismatchedGeometry() {
|
||||
}
|
||||
}
|
||||
|
||||
function vertexTaggedAsPointIssue(entity, graph) {
|
||||
function vertexPointIssue(entity, graph) {
|
||||
// we only care about nodes
|
||||
if (entity.type !== 'node') return null;
|
||||
|
||||
@@ -196,46 +197,157 @@ export function validationMismatchedGeometry() {
|
||||
.html(t.html('issues.point_as_vertex.reference'));
|
||||
},
|
||||
entityIds: [entity.id],
|
||||
dynamicFixes: function(context) {
|
||||
|
||||
var entityId = this.entityIds[0];
|
||||
|
||||
var extractOnClick = null;
|
||||
if (!context.hasHiddenConnections(entityId)) {
|
||||
|
||||
extractOnClick = function(context) {
|
||||
var entityId = this.issue.entityIds[0];
|
||||
var action = actionExtract(entityId);
|
||||
context.perform(
|
||||
action,
|
||||
t('operations.extract.annotation', { n: 1 })
|
||||
);
|
||||
// re-enter mode to trigger updates
|
||||
context.enter(modeSelect(context, [action.getExtractedNodeID()]));
|
||||
};
|
||||
}
|
||||
|
||||
return [
|
||||
new validationIssueFix({
|
||||
icon: 'iD-operation-extract',
|
||||
title: t.html('issues.fix.extract_point.title'),
|
||||
onClick: extractOnClick
|
||||
})
|
||||
];
|
||||
}
|
||||
dynamicFixes: extractPointDynamicFixes
|
||||
});
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
function otherMismatchIssue(entity, graph) {
|
||||
// ignore boring features
|
||||
if (!entity.hasInterestingTags()) return null;
|
||||
|
||||
if (entity.type !== 'node' && entity.type !== 'way') return null;
|
||||
|
||||
// address lines are special so just ignore them
|
||||
if (entity.type === 'node' && entity.isOnAddressLine(graph)) return null;
|
||||
|
||||
var sourceGeom = entity.geometry(graph);
|
||||
|
||||
var targetGeoms = entity.type === 'way' ? ['point', 'vertex'] : ['line', 'area'];
|
||||
|
||||
if (sourceGeom === 'area') targetGeoms.unshift('line');
|
||||
|
||||
var targetGeom = targetGeoms.find(nodeGeom => {
|
||||
var asSource = presetManager.matchTags(entity.tags, sourceGeom);
|
||||
var asTarget = presetManager.matchTags(entity.tags, nodeGeom);
|
||||
if (!asSource || !asTarget ||
|
||||
asSource === asTarget ||
|
||||
// sometimes there are two presets with the same tags for different geometries
|
||||
deepEqual(asSource.tags, asTarget.tags)) return false;
|
||||
|
||||
if (asTarget.isFallback()) return false;
|
||||
|
||||
var primaryKey = Object.keys(asTarget.tags)[0];
|
||||
|
||||
// special case: buildings-as-points are discouraged by iD, but common in OSM, so ignore them
|
||||
if (primaryKey === 'building') return false;
|
||||
|
||||
if (asTarget.tags[primaryKey] === '*') return false;
|
||||
|
||||
return asSource.isFallback() || asSource.tags[primaryKey] === '*';
|
||||
});
|
||||
|
||||
if (!targetGeom) return null;
|
||||
|
||||
var subtype = targetGeom + '_as_' + sourceGeom;
|
||||
|
||||
if (targetGeom === 'vertex') targetGeom = 'point';
|
||||
if (sourceGeom === 'vertex') sourceGeom = 'point';
|
||||
|
||||
var referenceId = targetGeom + '_as_' + sourceGeom;
|
||||
|
||||
var dynamicFixes;
|
||||
if (targetGeom === 'point') {
|
||||
dynamicFixes = extractPointDynamicFixes;
|
||||
|
||||
} else if (sourceGeom === 'area' && targetGeom === 'line') {
|
||||
dynamicFixes = lineToAreaDynamicFixes;
|
||||
}
|
||||
|
||||
return new validationIssue({
|
||||
type: type,
|
||||
subtype: subtype,
|
||||
severity: 'warning',
|
||||
message: function(context) {
|
||||
var entity = context.hasEntity(this.entityIds[0]);
|
||||
return entity ? t.html('issues.' + referenceId + '.message', {
|
||||
feature: utilDisplayLabel(entity, targetGeom)
|
||||
}) : '';
|
||||
},
|
||||
reference: function showReference(selection) {
|
||||
selection.selectAll('.issue-reference')
|
||||
.data([0])
|
||||
.enter()
|
||||
.append('div')
|
||||
.attr('class', 'issue-reference')
|
||||
.html(t.html('issues.mismatched_geometry.reference'));
|
||||
},
|
||||
entityIds: [entity.id],
|
||||
dynamicFixes: dynamicFixes
|
||||
});
|
||||
}
|
||||
|
||||
function lineToAreaDynamicFixes(context) {
|
||||
|
||||
var convertOnClick;
|
||||
|
||||
var entityId = this.entityIds[0];
|
||||
var entity = context.entity(entityId);
|
||||
var tags = Object.assign({}, entity.tags); // shallow copy
|
||||
delete tags.area;
|
||||
if (!osmTagSuggestingArea(tags)) {
|
||||
// if removing the area tag would make this a line, offer that as a quick fix
|
||||
convertOnClick = function(context) {
|
||||
var entityId = this.issue.entityIds[0];
|
||||
var entity = context.entity(entityId);
|
||||
var tags = Object.assign({}, entity.tags); // shallow copy
|
||||
if (tags.area) {
|
||||
delete tags.area;
|
||||
}
|
||||
context.perform(
|
||||
actionChangeTags(entityId, tags),
|
||||
t('issues.fix.convert_to_line.annotation')
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
return [
|
||||
new validationIssueFix({
|
||||
icon: 'iD-icon-line',
|
||||
title: t.html('issues.fix.convert_to_line.title'),
|
||||
onClick: convertOnClick
|
||||
})
|
||||
];
|
||||
}
|
||||
|
||||
function extractPointDynamicFixes(context) {
|
||||
|
||||
var entityId = this.entityIds[0];
|
||||
|
||||
var extractOnClick = null;
|
||||
if (!context.hasHiddenConnections(entityId)) {
|
||||
|
||||
extractOnClick = function(context) {
|
||||
var entityId = this.issue.entityIds[0];
|
||||
var action = actionExtract(entityId);
|
||||
context.perform(
|
||||
action,
|
||||
t('operations.extract.annotation', { n: 1 })
|
||||
);
|
||||
// re-enter mode to trigger updates
|
||||
context.enter(modeSelect(context, [action.getExtractedNodeID()]));
|
||||
};
|
||||
}
|
||||
|
||||
return [
|
||||
new validationIssueFix({
|
||||
icon: 'iD-operation-extract',
|
||||
title: t.html('issues.fix.extract_point.title'),
|
||||
onClick: extractOnClick
|
||||
})
|
||||
];
|
||||
}
|
||||
|
||||
function unclosedMultipolygonPartIssues(entity, graph) {
|
||||
|
||||
if (entity.type !== 'relation' ||
|
||||
!entity.isMultipolygon() ||
|
||||
entity.isDegenerate() ||
|
||||
// cannot determine issues for incompletely-downloaded relations
|
||||
!entity.isComplete(graph)) return null;
|
||||
!entity.isComplete(graph)) return [];
|
||||
|
||||
var sequences = osmJoinWays(entity.members, graph);
|
||||
|
||||
@@ -285,12 +397,16 @@ export function validationMismatchedGeometry() {
|
||||
}
|
||||
|
||||
var validation = function checkMismatchedGeometry(entity, graph) {
|
||||
var issues = [
|
||||
vertexTaggedAsPointIssue(entity, graph),
|
||||
lineTaggedAsAreaIssue(entity)
|
||||
];
|
||||
issues = issues.concat(unclosedMultipolygonPartIssues(entity, graph));
|
||||
return issues.filter(Boolean);
|
||||
var vertexPoint = vertexPointIssue(entity, graph);
|
||||
if (vertexPoint) return [vertexPoint];
|
||||
|
||||
var lineAsArea = lineTaggedAsAreaIssue(entity);
|
||||
if (lineAsArea) return [lineAsArea];
|
||||
|
||||
var mismatch = otherMismatchIssue(entity, graph);
|
||||
if (mismatch) return [mismatch];
|
||||
|
||||
return unclosedMultipolygonPartIssues(entity, graph);
|
||||
};
|
||||
|
||||
validation.type = type;
|
||||
|
||||
+1
-1
@@ -86,7 +86,7 @@
|
||||
"cldr-localenames-full": "37.0.0",
|
||||
"colors": "^1.1.2",
|
||||
"concat-files": "^0.1.1",
|
||||
"d3": "~6.2.0",
|
||||
"d3": "~6.3.0",
|
||||
"editor-layer-index": "github:osmlab/editor-layer-index#gh-pages",
|
||||
"eslint": "^7.1.0",
|
||||
"gaze": "^1.1.3",
|
||||
|
||||
Reference in New Issue
Block a user