From 4254e67ca7e10349798ded6d35a36d197f26a060 Mon Sep 17 00:00:00 2001 From: Martin Raifer Date: Fri, 4 Apr 2025 18:30:18 +0200 Subject: [PATCH 1/9] render addresses (housenumber/housename) * points with a dedicated marker * text inside of areas --- data/core.yaml | 3 ++ modules/core/history.js | 2 +- modules/renderer/features.js | 13 ++++- modules/renderer/map.js | 4 +- modules/svg/labels.js | 81 +++++++++++++++++----------- modules/svg/points.js | 28 ++++++++-- modules/ui/entity_editor.js | 3 +- modules/ui/fields/address.js | 4 +- modules/util/util.js | 5 ++ modules/validations/crossing_ways.js | 4 +- 10 files changed, 99 insertions(+), 48 deletions(-) diff --git a/data/core.yaml b/data/core.yaml index 2a8ef60d7..679aaa118 100644 --- a/data/core.yaml +++ b/data/core.yaml @@ -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." diff --git a/modules/core/history.js b/modules/core/history.js index 1d019922b..e3ea7b324 100644 --- a/modules/core/history.js +++ b/modules/core/history.js @@ -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); diff --git a/modules/renderer/features.js b/modules/renderer/features.js index 0263f3ad2..eeae822db 100644 --- a/modules/renderer/features.js +++ b/modules/renderer/features.js @@ -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'; @@ -103,9 +103,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) { diff --git a/modules/renderer/map.js b/modules/renderer/map.js index b5d71a369..6b940bfb2 100644 --- a/modules/renderer/map.js +++ b/modules/renderer/map.js @@ -393,8 +393,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}); } diff --git a/modules/svg/labels.js b/modules/svg/labels.js index fa0bcabd8..d9ffd5d4b 100644 --- a/modules/svg/labels.js +++ b/modules/svg/labels.js @@ -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'; @@ -27,7 +27,7 @@ export function svgLabels(projection, context) { 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 +62,9 @@ export function svgLabels(projection, context) { ['point', 'ref', '*', 10], ['line', 'name', '*', 12], ['area', 'name', '*', 12], - ['point', 'name', '*', 10] + ['point', 'name', '*', 10], + ['point', 'addr:housenumber', '*', 8], + ['point', 'addr:housename', '*', 8] ]; @@ -163,14 +165,14 @@ export function svgLabels(projection, context) { .attr('class', function(d, i) { return classes + ' ' + labels[i].classes + ' ' + d.id; }) - .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); - }); + }) + .merge(texts) + .attr('x', get(labels, 'x')) + .attr('y', get(labels, 'y')); } @@ -291,16 +293,20 @@ export function svgLabels(projection, context) { markerPadding = 0; } - 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 - }; + if (!isAddressPoint(entity.tags)) { + 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'); + doInsert(bbox, entity.id + 'P'); + } else { + undoInsert(entity.id + 'P'); + } } // From here on, treat vertices like points @@ -391,18 +397,26 @@ export function svgLabels(projection, context) { } + function isAddressPoint(tags) { + const keys = Object.keys(tags); + return keys.length > 0 && keys.every(key => + key.startsWith('addr:') || !osmIsInterestingTag(key)); + } + function getPointLabel(entity, width, height, geometry) { var y = (geometry === 'point' ? -12 : 0); var pointOffsets = { ltr: [15, y, 'start'], rtl: [-15, y, 'end'] }; + const isAddr = isAddressPoint(entity.tags); 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, @@ -412,8 +426,15 @@ export function svgLabels(projection, context) { }; // 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 +580,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 +646,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; @@ -700,26 +728,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); diff --git a/modules/svg/points.js b/modules/svg/points.js index 03ba5b738..1e4056a86 100644 --- a/modules/svg/points.js +++ b/modules/svg/points.js @@ -1,6 +1,6 @@ import deepEqual from 'fast-deep-equal'; import { geoScaleToZoom } from '../geo'; -import { osmEntity } from '../osm'; +import { osmEntity, osmIsInterestingTag } from '../osm'; import { svgPointTransform } from './helpers'; import { svgTagClasses } from './tag_classes'; import { presetManager } from '../presets'; @@ -8,10 +8,28 @@ import { presetManager } from '../presets'; export function svgPoints(projection, context) { function markerPath(selection, klass) { + const isHousenumber = d => { + const tagKeys = Object.keys(d.tags); + if (tagKeys.length === 0) return false; + //return d.tags['addr:housenumber'] && + return Object.keys(d.tags).every(key => + key.startsWith('addr:') || !osmIsInterestingTag(key)); + }; + const addressShieldWidth = d => { + return Math.min(6, Math.max(2, (d.tags['addr:housenumber'] || d.tags['addr:housename'] || '').length)) * 6 + 6; + }; 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 => isHousenumber(d) + ? `translate(-${addressShieldWidth(d)/2}, -8)` + : 'translate(-8, -23)') + .attr('d', d => { + if (!isHousenumber(d)) { + 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); + 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) { @@ -22,8 +40,8 @@ export function svgPoints(projection, context) { // 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); } diff --git a/modules/ui/entity_editor.js b/modules/ui/entity_editor.js index ba2956ab3..f47c2de22 100644 --- a/modules/ui/entity_editor.js +++ b/modules/ui/entity_editor.js @@ -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; } } diff --git a/modules/ui/fields/address.js b/modules/ui/fields/address.js index 2b7851215..2add9d713 100644 --- a/modules/ui/fields/address.js +++ b/modules/ui/fields/address.js @@ -338,12 +338,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); } diff --git a/modules/util/util.js b/modules/util/util.js index 58a223385..b7b928019 100644 --- a/modules/util/util.js +++ b/modules/util/util.js @@ -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 = []; diff --git a/modules/validations/crossing_ways.js b/modules/validations/crossing_ways.js index f4085f941..4158d24da 100644 --- a/modules/validations/crossing_ways.js +++ b/modules/validations/crossing_ways.js @@ -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, From aa96a67c65ec0c2f774115dfacdc308008b85c8a Mon Sep 17 00:00:00 2001 From: Martin Raifer Date: Mon, 21 Apr 2025 14:43:35 +0200 Subject: [PATCH 2/9] use lodash-es everywhere, replace custom clamp methods with lodash --- modules/actions/merge_remote_changes.js | 2 +- modules/osm/tags.js | 2 +- modules/presets/preset.js | 2 +- modules/renderer/background_source.js | 2 +- modules/renderer/map.js | 6 +----- modules/services/mapilio.js | 2 +- modules/svg/points.js | 2 ++ modules/ui/photoviewer.js | 5 +---- modules/ui/sections/background_display_options.js | 5 +---- modules/util/tiler.js | 7 ++----- modules/util/units.js | 6 ++---- modules/validations/crossing_ways.js | 2 +- 12 files changed, 15 insertions(+), 28 deletions(-) diff --git a/modules/actions/merge_remote_changes.js b/modules/actions/merge_remote_changes.js index 358f6a66b..01e940686 100644 --- a/modules/actions/merge_remote_changes.js +++ b/modules/actions/merge_remote_changes.js @@ -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'; diff --git a/modules/osm/tags.js b/modules/osm/tags.js index 959618dce..265f1e8b6 100644 --- a/modules/osm/tags.js +++ b/modules/osm/tags.js @@ -296,7 +296,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'], diff --git a/modules/presets/preset.js b/modules/presets/preset.js index 4da3ccefd..8e6f68d2e 100644 --- a/modules/presets/preset.js +++ b/modules/presets/preset.js @@ -1,4 +1,4 @@ -import { isEqual } from 'lodash'; +import { isEqual } from 'lodash-es'; import { t } from '../core/localizer'; import { osmAreaKeys, osmAreaKeysExceptions } from '../osm/tags'; diff --git a/modules/renderer/background_source.js b/modules/renderer/background_source.js index 57bf26d8d..9d29779c3 100644 --- a/modules/renderer/background_source.js +++ b/modules/renderer/background_source.js @@ -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'; diff --git a/modules/renderer/map.js b/modules/renderer/map.js index 6b940bfb2..e57b03e21 100644 --- a/modules/renderer/map.js +++ b/modules/renderer/map.js @@ -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( diff --git a/modules/services/mapilio.js b/modules/services/mapilio.js index 10582aff3..28a831f48 100644 --- a/modules/services/mapilio.js +++ b/modules/services/mapilio.js @@ -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'; diff --git a/modules/svg/points.js b/modules/svg/points.js index 1e4056a86..123d0f566 100644 --- a/modules/svg/points.js +++ b/modules/svg/points.js @@ -1,4 +1,6 @@ import deepEqual from 'fast-deep-equal'; +import { clamp } from 'lodash-es'; + import { geoScaleToZoom } from '../geo'; import { osmEntity, osmIsInterestingTag } from '../osm'; import { svgPointTransform } from './helpers'; diff --git a/modules/ui/photoviewer.js b/modules/ui/photoviewer.js index 0756fee8f..811bea910 100644 --- a/modules/ui/photoviewer.js +++ b/modules/ui/photoviewer.js @@ -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'; @@ -247,10 +248,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; diff --git a/modules/ui/sections/background_display_options.js b/modules/ui/sections/background_display_options.js index 19bd24d7c..8b98a7cc7 100644 --- a/modules/ui/sections/background_display_options.js +++ b/modules/ui/sections/background_display_options.js @@ -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); diff --git a/modules/util/tiler.js b/modules/util/tiler.js index ead3a75b7..6b1410098 100644 --- a/modules/util/tiler.js +++ b/modules/util/tiler.js @@ -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]; diff --git a/modules/util/units.js b/modules/util/units.js index fb745830a..0483b1a90 100644 --- a/modules/util/units.js +++ b/modules/util/units.js @@ -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); diff --git a/modules/validations/crossing_ways.js b/modules/validations/crossing_ways.js index 4158d24da..3f2e27123 100644 --- a/modules/validations/crossing_ways.js +++ b/modules/validations/crossing_ways.js @@ -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'; From 5afe43c700b9268e8ab5c06b3567388d597c6ac0 Mon Sep 17 00:00:00 2001 From: Martin Raifer Date: Mon, 21 Apr 2025 14:45:16 +0200 Subject: [PATCH 3/9] show ellipsis for long housenumbers/housenames --- modules/svg/labels.js | 188 +++++++++++++++++++++--------------------- modules/svg/points.js | 5 +- 2 files changed, 95 insertions(+), 98 deletions(-) diff --git a/modules/svg/labels.js b/modules/svg/labels.js index d9ffd5d4b..083e2b3fb 100644 --- a/modules/svg/labels.js +++ b/modules/svg/labels.js @@ -23,7 +23,6 @@ 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 @@ -63,8 +62,8 @@ export function svgLabels(projection, context) { ['line', 'name', '*', 12], ['area', 'name', '*', 12], ['point', 'name', '*', 10], - ['point', 'addr:housenumber', '*', 8], - ['point', 'addr:housename', '*', 8] + ['point', 'addr:housenumber', '*', 10], + ['point', 'addr:housename', '*', 10] ]; @@ -81,32 +80,10 @@ export function svgLabels(projection, context) { } - 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) { + function drawLinePaths(selection, labels, filter, classes) { var paths = selection.selectAll('path') - .filter(filter) - .data(entities, osmEntity.key); + .filter(d => filter(d.entity)) + .data(labels, d => osmEntity.key(d.entity)); // exit paths.exit() @@ -115,18 +92,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() @@ -135,25 +112,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() @@ -162,35 +142,29 @@ export function svgLabels(projection, context) { // enter/update texts.enter() .append('text') - .attr('class', function(d, i) { - return classes + ' ' + labels[i].classes + ' ' + d.id; - }) - .style('text-anchor', get(labels, 'textAnchor')) - .text(utilDisplayName) - .each(function(d, i) { - textWidth(utilDisplayName(d), labels[i].height, this); - }) + .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')); + .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() @@ -203,9 +177,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 : ''; }); @@ -282,18 +256,24 @@ 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 } - if (!isAddressPoint(entity.tags)) { + if (isAddr) { + undoInsert(entity.id + 'P'); + } else { var coord = projection(entity.loc); var nodePadding = 10; var bbox = { @@ -302,10 +282,7 @@ export function svgLabels(projection, context) { maxX: coord[0] + nodePadding, maxY: coord[1] + nodePadding }; - doInsert(bbox, entity.id + 'P'); - } else { - undoInsert(entity.id + 'P'); } } @@ -333,12 +310,6 @@ export function svgLabels(projection, context) { } } - var positions = { - point: [], - line: [], - area: [] - }; - var labelled = { point: [], line: [], @@ -355,7 +326,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 g.label').node()); var p = null; if (geometry === 'point' || geometry === 'vertex') { @@ -363,10 +334,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 g.label').node()); + } p = getPointLabel(entity, width, fontSize, renderAs); - } else if (geometry === 'line') { p = getLineLabel(entity, width, fontSize); @@ -377,8 +351,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 + }); } } } @@ -403,13 +380,13 @@ export function svgLabels(projection, context) { key.startsWith('addr:') || !osmIsInterestingTag(key)); } - 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 = isAddressPoint(entity.tags); + const isAddr = style.isAddr; var textDirection = localizer.textDirection(); @@ -422,7 +399,8 @@ export function svgLabels(projection, context) { 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.. @@ -698,19 +676,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'); @@ -795,3 +773,21 @@ export function svgLabels(projection, context) { return drawLabels; } + + +var _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]; +} diff --git a/modules/svg/points.js b/modules/svg/points.js index 123d0f566..ed6b71c25 100644 --- a/modules/svg/points.js +++ b/modules/svg/points.js @@ -6,6 +6,7 @@ import { osmEntity, osmIsInterestingTag } from '../osm'; import { svgPointTransform } from './helpers'; import { svgTagClasses } from './tag_classes'; import { presetManager } from '../presets'; +import { textWidth } from './labels'; export function svgPoints(projection, context) { @@ -13,12 +14,12 @@ export function svgPoints(projection, context) { const isHousenumber = d => { const tagKeys = Object.keys(d.tags); if (tagKeys.length === 0) return false; - //return d.tags['addr:housenumber'] && return Object.keys(d.tags).every(key => key.startsWith('addr:') || !osmIsInterestingTag(key)); }; const addressShieldWidth = d => { - return Math.min(6, Math.max(2, (d.tags['addr:housenumber'] || d.tags['addr:housename'] || '').length)) * 6 + 6; + const width = textWidth(d.tags['addr:housenumber'] || d.tags['addr:housename'] || '', 10, selection.node().parentElement); + return clamp(width, 10, 34) + 8; }; selection .attr('class', klass) From e2cb7663b00803b133943bd19729a8f3b8ea2e62 Mon Sep 17 00:00:00 2001 From: Martin Raifer Date: Mon, 21 Apr 2025 15:56:26 +0200 Subject: [PATCH 4/9] fix move mode as the current graph has now the most recent version of the modified entities, each individual change only needs to take into account the delta of the mouse movement between the current position and the previous one (instead of the original mouse position when the operation was started) --- modules/modes/move.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/modules/modes/move.js b/modules/modes/move.js index a03728783..7a10961fe 100644 --- a/modules/modes/move.js +++ b/modules/modes/move.js @@ -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 = {}; From bcdf6b9e71f39a1b8469e864df6696609fbcf5ea Mon Sep 17 00:00:00 2001 From: Martin Raifer Date: Wed, 23 Apr 2025 11:55:13 +0200 Subject: [PATCH 5/9] add some additional tag keys to list of not-interesting tags --- modules/osm/tags.js | 38 +++++++++++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/modules/osm/tags.js b/modules/osm/tags.js index 265f1e8b6..a7d0448a2 100644 --- a/modules/osm/tags.js +++ b/modules/osm/tags.js @@ -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 = { From 112c9264b2443f32d5c46812cfb0d1984ce3d6ed Mon Sep 17 00:00:00 2001 From: Martin Raifer Date: Wed, 23 Apr 2025 11:56:56 +0200 Subject: [PATCH 6/9] fix crash when collision boxes are drawn in debug mode --- modules/svg/labels.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/svg/labels.js b/modules/svg/labels.js index 083e2b3fb..52c0f2284 100644 --- a/modules/svg/labels.js +++ b/modules/svg/labels.js @@ -81,7 +81,7 @@ export function svgLabels(projection, context) { function drawLinePaths(selection, labels, filter, classes) { - var paths = selection.selectAll('path') + var paths = selection.selectAll('path:not(.debug)') .filter(d => filter(d.entity)) .data(labels, d => osmEntity.key(d.entity)); From 1a5bc723277c63ed4ee0b31d8b477dd12010b95e Mon Sep 17 00:00:00 2001 From: Martin Raifer Date: Wed, 23 Apr 2025 11:57:47 +0200 Subject: [PATCH 7/9] be a bit more lenient when determining if a node is an address node MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit consider some additional tags as "not interesting"/non-POI-like: * check_date/fixme/note/note:*/survey:* — mapping related tags * layer/level/level:ref — can be considered attributes of the address * ref:* – often used to indicate a source ID on imported data --- modules/svg/labels.js | 33 +++++++++++++++++++++------------ modules/svg/points.js | 14 ++++---------- 2 files changed, 25 insertions(+), 22 deletions(-) diff --git a/modules/svg/labels.js b/modules/svg/labels.js index 52c0f2284..bc48b1fde 100644 --- a/modules/svg/labels.js +++ b/modules/svg/labels.js @@ -75,11 +75,6 @@ export function svgLabels(projection, context) { } - function get(array, prop) { - return function(d, i) { return array[i][prop]; }; - } - - function drawLinePaths(selection, labels, filter, classes) { var paths = selection.selectAll('path:not(.debug)') .filter(d => filter(d.entity)) @@ -374,12 +369,6 @@ export function svgLabels(projection, context) { } - function isAddressPoint(tags) { - const keys = Object.keys(tags); - return keys.length > 0 && keys.every(key => - key.startsWith('addr:') || !osmIsInterestingTag(key)); - } - function getPointLabel(entity, width, height, style) { var y = (style.geometry === 'point' ? -12 : 0); var pointOffsets = { @@ -775,7 +764,7 @@ export function svgLabels(projection, context) { } -var _textWidthCache = {}; +const _textWidthCache = {}; export function textWidth(text, size, container) { let c = _textWidthCache[size]; if (!c) c = _textWidthCache[size] = {}; @@ -791,3 +780,23 @@ export function textWidth(text, size, container) { 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) + ); +} diff --git a/modules/svg/points.js b/modules/svg/points.js index ed6b71c25..286cd109a 100644 --- a/modules/svg/points.js +++ b/modules/svg/points.js @@ -2,32 +2,26 @@ import deepEqual from 'fast-deep-equal'; import { clamp } from 'lodash-es'; import { geoScaleToZoom } from '../geo'; -import { osmEntity, osmIsInterestingTag } from '../osm'; +import { osmEntity } from '../osm'; import { svgPointTransform } from './helpers'; import { svgTagClasses } from './tag_classes'; import { presetManager } from '../presets'; -import { textWidth } from './labels'; +import { textWidth, isAddressPoint } from './labels'; export function svgPoints(projection, context) { function markerPath(selection, klass) { - const isHousenumber = d => { - const tagKeys = Object.keys(d.tags); - if (tagKeys.length === 0) return false; - return Object.keys(d.tags).every(key => - key.startsWith('addr:') || !osmIsInterestingTag(key)); - }; const addressShieldWidth = d => { const width = textWidth(d.tags['addr:housenumber'] || d.tags['addr:housename'] || '', 10, selection.node().parentElement); return clamp(width, 10, 34) + 8; }; selection .attr('class', klass) - .attr('transform', d => isHousenumber(d) + .attr('transform', d => isAddressPoint(d.tags) ? `translate(-${addressShieldWidth(d)/2}, -8)` : 'translate(-8, -23)') .attr('d', d => { - if (!isHousenumber(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); From 2fccd798dc63c09fc6590286580177d1dfa438ef Mon Sep 17 00:00:00 2001 From: Martin Raifer Date: Wed, 23 Apr 2025 12:29:35 +0200 Subject: [PATCH 8/9] fix size of touch targets for address shields --- modules/svg/points.js | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/modules/svg/points.js b/modules/svg/points.js index 286cd109a..618c6c453 100644 --- a/modules/svg/points.js +++ b/modules/svg/points.js @@ -11,20 +11,16 @@ import { textWidth, isAddressPoint } from './labels'; export function svgPoints(projection, context) { function markerPath(selection, klass) { - const addressShieldWidth = d => { - const width = textWidth(d.tags['addr:housenumber'] || d.tags['addr:housename'] || '', 10, selection.node().parentElement); - return clamp(width, 10, 34) + 8; - }; selection .attr('class', klass) .attr('transform', d => isAddressPoint(d.tags) - ? `translate(-${addressShieldWidth(d)/2}, -8)` + ? `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); + 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`; }); } @@ -33,6 +29,10 @@ export function svgPoints(projection, context) { 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. @@ -57,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() @@ -74,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); } From 009daf7ef6ff72e3e47bc4a2ce40d516b6c77ee2 Mon Sep 17 00:00:00 2001 From: Martin Raifer Date: Sat, 26 Apr 2025 19:44:49 +0200 Subject: [PATCH 9/9] fix crash on low zoom address rendering --- modules/svg/labels.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/svg/labels.js b/modules/svg/labels.js index bc48b1fde..a80d29c9b 100644 --- a/modules/svg/labels.js +++ b/modules/svg/labels.js @@ -321,7 +321,7 @@ export function svgLabels(projection, context) { var getName = (geometry === 'line') ? utilDisplayNameForPath : utilDisplayName; var name = getName(entity); - var width = name && textWidth(name, fontSize, selection.select('g.layer-osm.labels g.label').node()); + var width = name && textWidth(name, fontSize, selection.select('g.layer-osm.labels').node()); var p = null; if (geometry === 'point' || geometry === 'vertex') { @@ -332,7 +332,7 @@ export function svgLabels(projection, context) { 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 g.label').node()); + width = textWidth(name, fontSize, selection.select('g.layer-osm.labels').node()); } p = getPointLabel(entity, width, fontSize, renderAs);