Merge remote-tracking branch 'origin/housenumber-labels' into develop

This commit is contained in:
Martin Raifer
2025-05-12 18:02:47 +02:00
21 changed files with 255 additions and 189 deletions
+3
View File
@@ -38,6 +38,7 @@ _Breaking developer changes, which may affect downstream projects or sites that
# unreleased (v2.35.0-dev)
#### :sparkles: Usability & Accessibility
* Render housenumbers (or housenames) of address points or buildings as dedicated labels on the map ([#10970])
#### :scissors: Operations
#### :camera: Street-Level
#### :white_check_mark: Validation
@@ -47,6 +48,8 @@ _Breaking developer changes, which may affect downstream projects or sites that
#### :mortar_board: Walkthrough / Help
#### :hammer: Development
[#10970]: https://github.com/openstreetmap/iD/pull/10970
# v2.34.0
##### 2025-05-12
+3
View File
@@ -914,6 +914,9 @@ en:
points:
description: Points
tooltip: "Points of Interest"
address_points:
description: Address Points
tooltip: "Addresses Mapped as Individual Points"
traffic_roads:
description: Traffic Roads
tooltip: "Highways, Streets, etc."
+1 -1
View File
@@ -1,6 +1,6 @@
import deepEqual from 'fast-deep-equal';
import { diff3Merge } from 'node-diff3';
import { escape } from 'lodash';
import { escape } from 'lodash-es';
import { t } from '../core/localizer';
import { actionDeleteMultiple } from './delete_multiple';
+1 -1
View File
@@ -80,12 +80,12 @@ export function coreHistory(context) {
// internal _overwrite with eased time
function _overwrite(args, t) {
var previous = _stack[_index].graph;
var actionResult = _act(args, t);
if (_index > 0) {
_index--;
_stack.pop();
}
_stack = _stack.slice(0, _index + 1);
var actionResult = _act(args, t);
_stack.push(actionResult);
_index++;
return change(previous);
+7 -7
View File
@@ -47,7 +47,7 @@ export function modeMove(context, entityIDs, baseGraph) {
var _prevGraph;
var _cache;
var _origin;
var _prevMouse;
var _nudgeInterval;
// use pointer events on supported platforms; fallback to mouse events
@@ -57,18 +57,18 @@ export function modeMove(context, entityIDs, baseGraph) {
function doMove(nudge) {
nudge = nudge || [0, 0];
var fn;
let fn;
if (_prevGraph !== context.graph()) {
_cache = {};
_origin = context.map().mouseCoordinates();
_prevMouse = context.map().mouse();
fn = context.perform;
} else {
fn = context.overwrite;
}
var currMouse = context.map().mouse();
var origMouse = context.projection(_origin);
var delta = geoVecSubtract(geoVecSubtract(currMouse, origMouse), nudge);
const currMouse = context.map().mouse();
const delta = geoVecSubtract(geoVecSubtract(currMouse, _prevMouse), nudge);
_prevMouse = currMouse;
fn(actionMove(entityIDs, delta, context.projection, _cache));
_prevGraph = context.graph();
@@ -129,7 +129,7 @@ export function modeMove(context, entityIDs, baseGraph) {
mode.enter = function() {
_origin = context.map().mouseCoordinates();
_prevMouse = context.map().mouse();
_prevGraph = null;
_cache = {};
+32 -8
View File
@@ -1,13 +1,37 @@
import { merge } from 'lodash-es';
const uninterestingKeys = new Set([
'attribution',
'created_by',
'import_uuid',
'geobase:datasetName',
'geobase:uuid',
'KSJ2:curve_id',
'KSJ2:lat',
'KSJ2:long',
'lat',
'latitude',
'lon',
'longitude',
'source',
'source_ref',
'odbl',
'odbl:note'
]);
const uninterestingKeyRegex = /^(source(_ref)?|tiger):/;
/**
* Returns whether the given OSM tag key is potentially "interesting".
* For example, some tags are deemed not interesting because the respective tag is
* considered "discardable".
*
* @param {string} key the key to test
* @returns {boolean}
*/
export function osmIsInterestingTag(key) {
return key !== 'attribution' &&
key !== 'created_by' &&
key !== 'source' &&
key !== 'odbl' &&
key.indexOf('source:') !== 0 &&
key.indexOf('source_ref') !== 0 && // purposely exclude colon
key.indexOf('tiger:') !== 0;
if (uninterestingKeys.has(key)) return false;
if (uninterestingKeyRegex.test(key)) return false;
return true;
}
export const osmLifecyclePrefixes = {
@@ -296,7 +320,7 @@ export function isColourValid(value) {
}
// https://wiki.openstreetmap.org/wiki/Special:WhatLinksHere/Property:P44
export var osmMutuallyExclusiveTagPairs = [
export const osmMutuallyExclusiveTagPairs = [
['noname', 'name'],
['noref', 'ref'],
['nohousenumber', 'addr:housenumber'],
+1 -1
View File
@@ -1,4 +1,4 @@
import { isEqual } from 'lodash';
import { isEqual } from 'lodash-es';
import { t } from '../core/localizer';
import { osmAreaKeys, osmAreaKeysExceptions } from '../osm/tags';
+1 -1
View File
@@ -1,6 +1,6 @@
import { geoArea as d3_geoArea, geoMercatorRaw as d3_geoMercatorRaw } from 'd3-geo';
import { json as d3_json } from 'd3-fetch';
import { escape } from 'lodash';
import { escape } from 'lodash-es';
import { t, localizer } from '../core/localizer';
import { geoExtent, geoSphericalDistance } from '../geo';
+11 -2
View File
@@ -1,7 +1,7 @@
import { dispatch as d3_dispatch } from 'd3-dispatch';
import { prefs } from '../core/preferences';
import { osmEntity, osmLifecyclePrefixes } from '../osm';
import { osmEntity, osmIsInterestingTag, osmLifecyclePrefixes } from '../osm';
import { utilRebind } from '../util/rebind';
import { utilArrayGroupBy, utilArrayUnion, utilQsString, utilStringQs } from '../util';
@@ -101,9 +101,18 @@ export function rendererFeatures(context) {
};
}
function isAddressPoint(tags, geometry) {
const keys = Object.keys(tags);
return geometry === 'point' &&
keys.length > 0 &&
keys.every(key =>
key.startsWith('addr:') || !osmIsInterestingTag(key)
);
}
defineRule('address_points', isAddressPoint, 100);
defineRule('points', function isPoint(tags, geometry) {
return geometry === 'point';
return geometry === 'point' && !isAddressPoint(tags, geometry);
}, 200);
defineRule('traffic_roads', function isTrafficRoad(tags) {
+3 -7
View File
@@ -17,7 +17,7 @@ import { utilGetDimensions } from '../util/dimensions';
import { utilRebind } from '../util/rebind';
import { utilZoomPan } from '../util/zoom_pan';
import { utilDoubleUp } from '../util/double_up';
import { isArray } from 'lodash-es';
import { isArray, clamp } from 'lodash-es';
// constants
var TILESIZE = 256;
@@ -26,10 +26,6 @@ var maxZoom = 24;
var kMin = geoZoomToScale(minZoom, TILESIZE);
var kMax = geoZoomToScale(maxZoom, TILESIZE);
function clamp(num, min, max) {
return Math.max(min, Math.min(num, max));
}
export function rendererMap(context) {
var dispatch = d3_dispatch(
@@ -393,8 +389,8 @@ export function rendererMap(context) {
.call(drawLines, graph, data, filter)
.call(drawAreas, graph, data, filter)
.call(drawMidpoints, graph, data, filter, map.trimmedExtent())
.call(drawLabels, graph, data, filter, _dimensions, fullRedraw)
.call(drawPoints, graph, data, filter);
.call(drawPoints, graph, data, filter)
.call(drawLabels, graph, data, filter, _dimensions, fullRedraw);
dispatch.call('drawn', this, {full: true});
}
+1 -1
View File
@@ -5,7 +5,7 @@ import { zoom as d3_zoom, zoomIdentity as d3_zoomIdentity } from 'd3-zoom';
import Protobuf from 'pbf';
import RBush from 'rbush';
import { VectorTile } from '@mapbox/vector-tile';
import { isEqual } from 'lodash';
import { isEqual } from 'lodash-es';
import { utilRebind, utilTiler, utilQsString, utilStringQs, utilSetTransform } from '../util';
import {geoExtent, geoScaleToZoom} from '../geo';
+147 -123
View File
@@ -9,9 +9,9 @@ import {
geoScaleToZoom, geoVecInterp, geoVecLength
} from '../geo';
import { presetManager } from '../presets';
import { osmEntity } from '../osm';
import { osmEntity, osmIsInterestingTag } from '../osm';
import { utilDetect } from '../util/detect';
import { utilDisplayName, utilDisplayNameForPath, utilEntitySelector } from '../util';
import { utilArrayDifference, utilDisplayName, utilDisplayNameForPath, utilEntitySelector } from '../util';
@@ -23,11 +23,10 @@ export function svgLabels(projection, context) {
(detected.browser.toLowerCase() === 'firefox' && detected.version >= 70));
var _rdrawn = new RBush();
var _rskipped = new RBush();
var _textWidthCache = {};
var _entitybboxes = {};
// Listed from highest to lowest priority
var labelStack = [
const labelStack = [
['line', 'aeroway', '*', 12],
['line', 'highway', 'motorway', 12],
['line', 'highway', 'trunk', 12],
@@ -62,7 +61,9 @@ export function svgLabels(projection, context) {
['point', 'ref', '*', 10],
['line', 'name', '*', 12],
['area', 'name', '*', 12],
['point', 'name', '*', 10]
['point', 'name', '*', 10],
['point', 'addr:housenumber', '*', 10],
['point', 'addr:housename', '*', 10]
];
@@ -74,37 +75,10 @@ export function svgLabels(projection, context) {
}
function get(array, prop) {
return function(d, i) { return array[i][prop]; };
}
function textWidth(text, size, elem) {
var c = _textWidthCache[size];
if (!c) c = _textWidthCache[size] = {};
if (c[text]) {
return c[text];
} else if (elem) {
c[text] = elem.getComputedTextLength();
return c[text];
} else {
var str = encodeURIComponent(text).match(/%[CDEFcdef]/g);
if (str === null) {
return size / 3 * 2 * text.length;
} else {
return size / 3 * (2 * text.length + str.length);
}
}
}
function drawLinePaths(selection, entities, filter, classes, labels) {
var paths = selection.selectAll('path')
.filter(filter)
.data(entities, osmEntity.key);
function drawLinePaths(selection, labels, filter, classes) {
var paths = selection.selectAll('path:not(.debug)')
.filter(d => filter(d.entity))
.data(labels, d => osmEntity.key(d.entity));
// exit
paths.exit()
@@ -113,18 +87,18 @@ export function svgLabels(projection, context) {
// enter/update
paths.enter()
.append('path')
.style('stroke-width', get(labels, 'font-size'))
.attr('id', function(d) { return 'ideditor-labelpath-' + d.id; })
.style('stroke-width', d => d.position['font-size'])
.attr('id', d => 'ideditor-labelpath-' + d.entity.id)
.attr('class', classes)
.merge(paths)
.attr('d', get(labels, 'lineString'));
.attr('d', d => d.position.lineString);
}
function drawLineLabels(selection, entities, filter, classes, labels) {
function drawLineLabels(selection, labels, filter, classes) {
var texts = selection.selectAll('text.' + classes)
.filter(filter)
.data(entities, osmEntity.key);
.filter(d => filter(d.entity))
.data(labels, d => osmEntity.key(d.entity));
// exit
texts.exit()
@@ -133,25 +107,28 @@ export function svgLabels(projection, context) {
// enter
texts.enter()
.append('text')
.attr('class', function(d, i) { return classes + ' ' + labels[i].classes + ' ' + d.id; })
.attr('class', d => classes + ' ' + d.position.classes + ' ' + d.entity.id)
.attr('dy', baselineHack ? '0.35em' : null)
.append('textPath')
.attr('class', 'textpath');
// update
selection.selectAll('text.' + classes).selectAll('.textpath')
.filter(filter)
.data(entities, osmEntity.key)
.filter(d => filter(d.entity))
.data(labels, d => osmEntity.key(d.entity))
.attr('startOffset', '50%')
.attr('xlink:href', function(d) { return '#ideditor-labelpath-' + d.id; })
.text(utilDisplayNameForPath);
.attr('xlink:href', function(d) { return '#ideditor-labelpath-' + d.entity.id; })
.text(d => d.name);
}
function drawPointLabels(selection, entities, filter, classes, labels) {
function drawPointLabels(selection, labels, filter, classes) {
if (classes.includes('pointlabel-halo')) {
labels = labels.filter(d => !d.position.isAddr);
}
var texts = selection.selectAll('text.' + classes)
.filter(filter)
.data(entities, osmEntity.key);
.filter(d => filter(d.entity))
.data(labels, d => osmEntity.key(d.entity));
// exit
texts.exit()
@@ -160,35 +137,29 @@ export function svgLabels(projection, context) {
// enter/update
texts.enter()
.append('text')
.attr('class', function(d, i) {
return classes + ' ' + labels[i].classes + ' ' + d.id;
})
.attr('class', d => classes + ' ' + d.position.classes + ' ' + d.entity.id)
.style('text-anchor', d => d.position.textAnchor)
.text(d => d.name)
.merge(texts)
.attr('x', get(labels, 'x'))
.attr('y', get(labels, 'y'))
.style('text-anchor', get(labels, 'textAnchor'))
.text(utilDisplayName)
.each(function(d, i) {
textWidth(utilDisplayName(d), labels[i].height, this);
});
.attr('x', d => d.position.x)
.attr('y', d => d.position.y);
}
function drawAreaLabels(selection, entities, filter, classes, labels) {
entities = entities.filter(hasText);
function drawAreaLabels(selection, labels, filter, classes) {
labels = labels.filter(hasText);
drawPointLabels(selection, entities, filter, classes, labels);
drawPointLabels(selection, labels, filter, classes);
function hasText(d, i) {
return labels[i].hasOwnProperty('x') && labels[i].hasOwnProperty('y');
function hasText(d) {
return d.position.hasOwnProperty('x') && d.position.hasOwnProperty('y');
}
}
function drawAreaIcons(selection, entities, filter, classes, labels) {
function drawAreaIcons(selection, labels, filter, classes) {
var icons = selection.selectAll('use.' + classes)
.filter(filter)
.data(entities, osmEntity.key);
.filter(d => filter(d.entity))
.data(labels, d => osmEntity.key(d.entity));
// exit
icons.exit()
@@ -201,9 +172,9 @@ export function svgLabels(projection, context) {
.attr('width', '17px')
.attr('height', '17px')
.merge(icons)
.attr('transform', get(labels, 'transform'))
.attr('transform', d => d.position.transform)
.attr('xlink:href', function(d) {
var preset = presetManager.match(d, context.graph());
var preset = presetManager.match(d.entity, context.graph());
var picon = preset && preset.icon;
return picon ? '#' + picon : '';
});
@@ -280,27 +251,34 @@ export function svgLabels(projection, context) {
// Insert collision boxes around interesting points/vertices
if (geometry === 'point' || (geometry === 'vertex' && isInterestingVertex(entity))) {
const isAddr = isAddressPoint(entity.tags);
var hasDirections = entity.directions(graph, projection).length;
var markerPadding;
var markerPadding = 0;
if (!wireframe && geometry === 'point' && !(zoom >= 18 && hasDirections)) {
renderNodeAs[entity.id] = 'point';
markerPadding = 20; // extra y for marker height
if (wireframe) {
renderNodeAs[entity.id] = { geometry: 'vertex', isAddr };
} else if (geometry === 'vertex') {
renderNodeAs[entity.id] = { geometry: 'vertex', isAddr };
} else if (zoom >= 18 && hasDirections) {
renderNodeAs[entity.id] = { geometry: 'vertex', isAddr };
} else {
renderNodeAs[entity.id] = 'vertex';
markerPadding = 0;
renderNodeAs[entity.id] = { geometry: 'point', isAddr };
markerPadding = 20; // extra y for marker height
}
var coord = projection(entity.loc);
var nodePadding = 10;
var bbox = {
minX: coord[0] - nodePadding,
minY: coord[1] - nodePadding - markerPadding,
maxX: coord[0] + nodePadding,
maxY: coord[1] + nodePadding
};
doInsert(bbox, entity.id + 'P');
if (isAddr) {
undoInsert(entity.id + 'P');
} else {
var coord = projection(entity.loc);
var nodePadding = 10;
var bbox = {
minX: coord[0] - nodePadding,
minY: coord[1] - nodePadding - markerPadding,
maxX: coord[0] + nodePadding,
maxY: coord[1] + nodePadding
};
doInsert(bbox, entity.id + 'P');
}
}
// From here on, treat vertices like points
@@ -327,12 +305,6 @@ export function svgLabels(projection, context) {
}
}
var positions = {
point: [],
line: [],
area: []
};
var labelled = {
point: [],
line: [],
@@ -349,7 +321,7 @@ export function svgLabels(projection, context) {
var getName = (geometry === 'line') ? utilDisplayNameForPath : utilDisplayName;
var name = getName(entity);
var width = name && textWidth(name, fontSize);
var width = name && textWidth(name, fontSize, selection.select('g.layer-osm.labels').node());
var p = null;
if (geometry === 'point' || geometry === 'vertex') {
@@ -357,10 +329,13 @@ export function svgLabels(projection, context) {
// no vertex labels at low zooms (vertices have no icons)
if (wireframe) continue;
var renderAs = renderNodeAs[entity.id];
if (renderAs === 'vertex' && zoom < 17) continue;
if (renderAs.geometry === 'vertex' && zoom < 17) continue;
while (renderAs.isAddr && width > 36) {
name = `${name.substring(0, name.replace(/…$/, '').length - 1)}`;
width = textWidth(name, fontSize, selection.select('g.layer-osm.labels').node());
}
p = getPointLabel(entity, width, fontSize, renderAs);
} else if (geometry === 'line') {
p = getLineLabel(entity, width, fontSize);
@@ -371,8 +346,11 @@ export function svgLabels(projection, context) {
if (p) {
if (geometry === 'vertex') { geometry = 'point'; } // treat vertex like point
p.classes = geometry + ' tag-' + labelStack[k][1];
positions[geometry].push(p);
labelled[geometry].push(entity);
labelled[geometry].push({
entity,
name,
position: p
});
}
}
}
@@ -391,29 +369,39 @@ export function svgLabels(projection, context) {
}
function getPointLabel(entity, width, height, geometry) {
var y = (geometry === 'point' ? -12 : 0);
function getPointLabel(entity, width, height, style) {
var y = (style.geometry === 'point' ? -12 : 0);
var pointOffsets = {
ltr: [15, y, 'start'],
rtl: [-15, y, 'end']
};
const isAddr = style.isAddr;
var textDirection = localizer.textDirection();
var coord = projection(entity.loc);
var textPadding = 2;
var offset = pointOffsets[textDirection];
if (isAddr) offset = [0, 1, 'middle'];
var p = {
height: height,
width: width,
x: coord[0] + offset[0],
y: coord[1] + offset[1],
textAnchor: offset[2]
textAnchor: offset[2],
isAddr
};
// insert a collision box for the text label..
var bbox;
if (textDirection === 'rtl') {
let bbox;
if (isAddr) {
bbox = {
minX: p.x - (width / 2) - textPadding,
minY: p.y - (height / 2) - textPadding,
maxX: p.x + (width / 2) + textPadding,
maxY: p.y + (height / 2) + textPadding
};
} else if (textDirection === 'rtl') {
bbox = {
minX: p.x - width - textPadding,
minY: p.y - (height / 2) - textPadding,
@@ -559,7 +547,7 @@ export function svgLabels(projection, context) {
var padding = 2;
var p = {};
if (picon) { // icon and label..
if (picon && !shouldSkipIcon(preset)) { // icon and label..
if (addIcon()) {
addLabel(iconSize + padding);
return p;
@@ -625,6 +613,13 @@ export function svgLabels(projection, context) {
_rdrawn.insert(bbox);
}
function undoInsert(id) {
var oldbox = _entitybboxes[id];
if (oldbox) {
_rdrawn.remove(oldbox);
}
delete _entitybboxes[id];
}
function tryInsert(bboxes, id, saveSkipped) {
var skipped = false;
@@ -670,19 +665,19 @@ export function svgLabels(projection, context) {
var debug = layer.selectAll('.labels-group.debug');
// points
drawPointLabels(label, labelled.point, filter, 'pointlabel', positions.point);
drawPointLabels(halo, labelled.point, filter, 'pointlabel-halo', positions.point);
drawPointLabels(label, labelled.point, filter, 'pointlabel');
drawPointLabels(halo, labelled.point, filter, 'pointlabel-halo');
// lines
drawLinePaths(layer, labelled.line, filter, '', positions.line);
drawLineLabels(label, labelled.line, filter, 'linelabel', positions.line);
drawLineLabels(halo, labelled.line, filter, 'linelabel-halo', positions.line);
drawLinePaths(layer, labelled.line, filter, '');
drawLineLabels(label, labelled.line, filter, 'linelabel');
drawLineLabels(halo, labelled.line, filter, 'linelabel-halo');
// areas
drawAreaLabels(label, labelled.area, filter, 'arealabel', positions.area);
drawAreaLabels(halo, labelled.area, filter, 'arealabel-halo', positions.area);
drawAreaIcons(label, labelled.area, filter, 'areaicon', positions.area);
drawAreaIcons(halo, labelled.area, filter, 'areaicon-halo', positions.area);
drawAreaLabels(label, labelled.area, filter, 'arealabel');
drawAreaLabels(halo, labelled.area, filter, 'arealabel-halo');
drawAreaIcons(label, labelled.area, filter, 'areaicon');
drawAreaIcons(halo, labelled.area, filter, 'areaicon-halo');
// debug
drawCollisionBoxes(debug, _rskipped, 'debug-skipped');
@@ -700,26 +695,17 @@ export function svgLabels(projection, context) {
.classed('nolabel', false);
var mouse = context.map().mouse();
var graph = context.graph();
var selectedIDs = context.selectedIDs();
var ids = [];
var pad, bbox;
// hide labels near the mouse
if (mouse) {
if (mouse && context.mode().id !== 'browse' && context.mode().id !== 'select') {
pad = 20;
bbox = { minX: mouse[0] - pad, minY: mouse[1] - pad, maxX: mouse[0] + pad, maxY: mouse[1] + pad };
var nearMouse = _rdrawn.search(bbox).map(function(entity) { return entity.id; });
ids.push.apply(ids, nearMouse);
}
// hide labels on selected nodes (they look weird when dragging / haloed)
for (var i = 0; i < selectedIDs.length; i++) {
var entity = graph.hasEntity(selectedIDs[i]);
if (entity && entity.type === 'node') {
ids.push(selectedIDs[i]);
}
}
ids = utilArrayDifference(ids, context.mode()?.selectedIDs?.() || []);
layers.selectAll(utilEntitySelector(ids))
.classed('nolabel', true);
@@ -776,3 +762,41 @@ export function svgLabels(projection, context) {
return drawLabels;
}
const _textWidthCache = {};
export function textWidth(text, size, container) {
let c = _textWidthCache[size];
if (!c) c = _textWidthCache[size] = {};
if (c[text]) {
return c[text];
}
const elem = document.createElementNS('http://www.w3.org/2000/svg', 'text');
elem.style.fontSize = `${size}px`;
elem.textContent = text;
container.appendChild(elem);
c[text] = elem.getComputedTextLength();
elem.remove();
return c[text];
}
const nonPrimaryKeys = new Set([
'check_date',
'fixme',
'layer',
'level',
'level:ref',
'note'
]);
const nonPrimaryKeysRegex = /^(ref|survey|note):/;
export function isAddressPoint(tags) {
const keys = Object.keys(tags);
return keys.length > 0 && keys.every(key =>
key.startsWith('addr:') ||
!osmIsInterestingTag(key) ||
nonPrimaryKeys.has(key) ||
nonPrimaryKeysRegex.test(key)
);
}
+28 -12
View File
@@ -1,29 +1,44 @@
import deepEqual from 'fast-deep-equal';
import { clamp } from 'lodash-es';
import { geoScaleToZoom } from '../geo';
import { osmEntity } from '../osm';
import { svgPointTransform } from './helpers';
import { svgTagClasses } from './tag_classes';
import { presetManager } from '../presets';
import { textWidth, isAddressPoint } from './labels';
export function svgPoints(projection, context) {
function markerPath(selection, klass) {
selection
.attr('class', klass)
.attr('transform', 'translate(-8, -23)')
.attr('d', 'M 17,8 C 17,13 11,21 8.5,23.5 C 6,21 0,13 0,8 C 0,4 4,-0.5 8.5,-0.5 C 13,-0.5 17,4 17,8 z');
.attr('transform', d => isAddressPoint(d.tags)
? `translate(-${addressShieldWidth(d, selection)/2}, -8)`
: 'translate(-8, -23)')
.attr('d', d => {
if (!isAddressPoint(d.tags)) {
return 'M 17,8 C 17,13 11,21 8.5,23.5 C 6,21 0,13 0,8 C 0,4 4,-0.5 8.5,-0.5 C 13,-0.5 17,4 17,8 z';
}
const shieldWidth = addressShieldWidth(d, selection);
return `M ${shieldWidth},8 C ${shieldWidth},15 ${shieldWidth-2},16 ${shieldWidth-8},16 L 8,16 C 2,16 0,15 0,8 C 0,2 2,0 8,0 L ${shieldWidth-8},0 C ${shieldWidth-2},0 ${shieldWidth},2 ${shieldWidth},8 z`;
});
}
function sortY(a, b) {
return b.loc[1] - a.loc[1];
}
function addressShieldWidth(d, selection) {
const width = textWidth(d.tags['addr:housenumber'] || d.tags['addr:housename'] || '', 10, selection.node().parentElement);
return clamp(width, 10, 34) + 8;
};
// Avoid exit/enter if we're just moving stuff around.
// The node will get a new version but we only need to run the update selection.
function fastEntityKey(d) {
var mode = context.mode();
var isMoving = mode && /^(add|draw|drag|move|rotate)/.test(mode.id);
const mode = context.mode();
const isMoving = mode && /^(add|draw|drag|move|rotate)/.test(mode.id);
return isMoving ? d.id : osmEntity.key(d);
}
@@ -42,15 +57,16 @@ export function svgPoints(projection, context) {
id: node.id,
properties: {
target: true,
entity: node
entity: node,
isAddr: isAddressPoint(node.tags)
},
geometry: node.asGeoJSON()
});
});
var targets = selection.selectAll('.point.target')
.filter(function(d) { return filter(d.properties.entity); })
.data(data, function key(d) { return d.id; });
.filter(d => filter(d.properties.entity))
.data(data, d => fastEntityKey(d.properties.entity));
// exit
targets.exit()
@@ -59,12 +75,12 @@ export function svgPoints(projection, context) {
// enter/update
targets.enter()
.append('rect')
.attr('x', -10)
.attr('y', -26)
.attr('width', 20)
.attr('height', 30)
.merge(targets)
.attr('x', d => d.properties.isAddr ? -addressShieldWidth(d.properties.entity, selection) / 2 : -10)
.attr('y', d => d.properties.isAddr ? -8 : -26)
.attr('width', d => d.properties.isAddr ? addressShieldWidth(d.properties.entity, selection) : 20)
.attr('height', d => d.properties.isAddr ? 16 : 30)
.attr('class', function(d) { return 'node point target ' + fillClass + d.id; })
.merge(targets)
.attr('transform', getTransform);
}
+1 -2
View File
@@ -202,8 +202,8 @@ export function uiEntityEditor(context) {
context.overwrite(combinedAction, annotation);
} else {
context.perform(combinedAction, annotation);
_coalesceChanges = !!onInput;
}
_coalesceChanges = !!onInput;
}
// if leaving field (blur event), rerun validation
@@ -259,7 +259,6 @@ export function uiEntityEditor(context) {
context.overwrite(combinedAction, annotation);
} else {
context.perform(combinedAction, annotation);
_coalesceChanges = false;
}
}
+1 -3
View File
@@ -339,12 +339,10 @@ export function uiFieldAddress(field, context) {
}
_wrap.selectAll('input')
.on('input', change(true))
.on('blur', change())
.on('change', change());
_wrap.selectAll('input:not(.combobox-input)')
.on('input', change(true));
if (_tags) updateTags(_tags);
}
+1 -4
View File
@@ -1,6 +1,7 @@
import {
select as d3_select
} from 'd3-selection';
import { clamp } from 'lodash-es';
import { t } from '../core/localizer';
import { dispatch as d3_dispatch } from 'd3-dispatch';
@@ -230,10 +231,6 @@ export function uiPhotoviewer(context) {
dispatch.call(eventName, target, subtractPadding(utilGetDimensions(target, true), target));
}
function clamp(num, min, max) {
return Math.max(min, Math.min(num, max));
}
function stopResize(d3_event) {
if (pointerId !== (d3_event.pointerId || 'mouse')) return;
@@ -1,6 +1,7 @@
import {
select as d3_select
} from 'd3-selection';
import { clamp } from 'lodash-es';
import { prefs } from '../../core/preferences';
import { t, localizer } from '../../core/localizer';
@@ -27,10 +28,6 @@ export function uiSectionBackgroundDisplayOptions(context) {
sharpness: 1
};
function clamp(x, min, max) {
return Math.max(min, Math.min(x, max));
}
function updateValue(d, val) {
val = clamp(val, _minVal, _maxVal);
+2 -5
View File
@@ -1,4 +1,6 @@
import { range as d3_range } from 'd3-array';
import { clamp } from 'lodash-es';
import { geoExtent, geoScaleToZoom } from '../geo';
@@ -12,11 +14,6 @@ export function utilTiler() {
var _skipNullIsland = false;
function clamp(num, min, max) {
return Math.max(min, Math.min(num, max));
}
function nearNullIsland(tile) {
var x = tile[0];
var y = tile[1];
+2 -4
View File
@@ -1,3 +1,5 @@
import { clamp } from 'lodash-es';
import { t, localizer } from '../core/localizer';
var OSM_PRECISION = 7;
@@ -102,10 +104,6 @@ function wrap(x, min, max) {
return ((x - min) % d + d) % d + min;
}
function clamp(x, min, max) {
return Math.max(min, Math.min(x, max));
}
function roundToDecimal (target, decimalPlace) {
target = Number(target);
decimalPlace = Number(decimalPlace);
+5
View File
@@ -191,6 +191,7 @@ export function utilDisplayName(entity, hideNetwork) {
var name = entity.tags[localizedNameKey] || entity.tags.name || '';
var tags = {
addr: entity.tags['addr:housenumber'] || entity.tags['addr:housename'],
direction: entity.tags.direction,
from: entity.tags.from,
name,
@@ -210,6 +211,10 @@ export function utilDisplayName(entity, hideNetwork) {
if (!entity.tags.route && name) {
return name;
}
// unnamed buildings or address nodes: show housenumber/housename
if (tags.addr) {
return tags.addr;
}
var keyComponents = [];
+3 -3
View File
@@ -1,4 +1,4 @@
import { isEqual } from 'lodash';
import { isEqual } from 'lodash-es';
import { actionAddMidpoint } from '../actions/add_midpoint';
import { actionChangeTags } from '../actions/change_tags';
@@ -447,8 +447,8 @@ export function validationCrossingWays(context) {
var entity1 = graph.hasEntity(this.entityIds[0]),
entity2 = graph.hasEntity(this.entityIds[1]);
return (entity1 && entity2) ? t.append('issues.crossing_ways.message', {
feature: utilDisplayLabel(entity1, graph),
feature2: utilDisplayLabel(entity2, graph)
feature: utilDisplayLabel(entity1, graph, featureType1 === 'building'),
feature2: utilDisplayLabel(entity2, graph, featureType2 === 'building')
}) : '';
},
reference: showReference,