mirror of
https://github.com/FoggedLens/iD.git
synced 2026-05-16 13:59:27 +02:00
Merge remote-tracking branch 'origin/housenumber-labels' into develop
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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,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';
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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,4 +1,4 @@
|
||||
import { isEqual } from 'lodash';
|
||||
import { isEqual } from 'lodash-es';
|
||||
|
||||
import { t } from '../core/localizer';
|
||||
import { osmAreaKeys, osmAreaKeysExceptions } from '../osm/tags';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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});
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,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);
|
||||
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 = [];
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user