mirror of
https://github.com/FoggedLens/iD.git
synced 2026-05-09 11:35:39 +02:00
Render oneway markers inline in dashed lines (#10849)
This commit is contained in:
@@ -39,6 +39,7 @@ _Breaking developer changes, which may affect downstream projects or sites that
|
||||
|
||||
#### :sparkles: Usability & Accessibility
|
||||
* Allow searching for coordinates in localized number format in search box ([#10805])
|
||||
* Improve visibility of oneway arrows for dashed line styles (such as railway lines, foot paths, etc.): they are now rendered such that the arrows seemlessly integrate into the line dashes ([#10849])
|
||||
#### :scissors: Operations
|
||||
* Fix unexpected behavior of squaring operation on individual vertices ([#10401])
|
||||
#### :camera: Street-Level
|
||||
|
||||
+17
-1
@@ -405,9 +405,14 @@ path.line.stroke.tag-highway-pedestrian,
|
||||
path.line.stroke.tag-pedestrian {
|
||||
stroke: #fff;
|
||||
stroke-width: 3.5;
|
||||
stroke-dasharray: 8, 8;
|
||||
stroke-dasharray: 6, 6;
|
||||
stroke-linecap: butt;
|
||||
}
|
||||
path.line.stroke.tag-highway-pedestrian.tag-oneway,
|
||||
path.line.stroke.tag-pedestrian.tag-oneway {
|
||||
stroke-dasharray: 6, 6, 6, 18;
|
||||
stroke-dashoffset: 9;
|
||||
}
|
||||
.low-zoom path.line.stroke.tag-highway-pedestrian,
|
||||
.low-zoom path.line.stroke.tag-pedestrian {
|
||||
stroke-width: 2;
|
||||
@@ -484,6 +489,13 @@ path.line.stroke.tag-highway-bridleway {
|
||||
stroke-linecap: butt;
|
||||
stroke-dasharray: 6, 6;
|
||||
}
|
||||
path.line.stroke.tag-highway-path.tag-oneway,
|
||||
path.line.stroke.tag-highway-footway.tag-oneway,
|
||||
path.line.stroke.tag-highway-cycleway.tag-oneway,
|
||||
path.line.stroke.tag-highway-bridleway.tag-oneway {
|
||||
stroke-dasharray: 6, 6, 6, 18;
|
||||
stroke-dashoffset: 9;
|
||||
}
|
||||
.low-zoom path.line.stroke.tag-highway-path,
|
||||
.low-zoom path.line.stroke.tag-highway-footway,
|
||||
.low-zoom path.line.stroke.tag-highway-cycleway,
|
||||
@@ -633,6 +645,10 @@ path.line.stroke.tag-highway-ladder,
|
||||
path.line.stroke.tag-highway.tag-crossing-unmarked {
|
||||
stroke-dasharray: 6, 4;
|
||||
}
|
||||
path.line.stroke.tag-highway.tag-crossing-unmarked.tag-oneway {
|
||||
stroke-dasharray: 6, 4, 6, 20;
|
||||
stroke-dashoffset: 8;
|
||||
}
|
||||
.low-zoom path.line.stroke.tag-highway.tag-crossing-unmarked {
|
||||
stroke-dasharray: 3, 2;
|
||||
}
|
||||
|
||||
@@ -60,6 +60,7 @@ path.line.stroke.tag-aeroway-runway {
|
||||
stroke-width: 2;
|
||||
stroke-linecap: butt;
|
||||
stroke-dasharray: 24, 48;
|
||||
stroke-dashoffset: -7;
|
||||
}
|
||||
.low-zoom path.line.shadow.tag-aeroway-runway {
|
||||
stroke-width: 16;
|
||||
|
||||
+5
-1
@@ -20,7 +20,11 @@ path.line.casing.tag-railway {
|
||||
path.line.stroke.tag-railway {
|
||||
stroke-width: 2;
|
||||
stroke-linecap: butt;
|
||||
stroke-dasharray: 12, 12;
|
||||
stroke-dasharray: 10,8;
|
||||
}
|
||||
path.line.stroke.tag-railway.tag-oneway {
|
||||
stroke-dasharray: 10, 26;
|
||||
stroke-dashoffset: 5;
|
||||
}
|
||||
.low-zoom path.line.shadow.tag-railway {
|
||||
stroke-width: 12;
|
||||
|
||||
@@ -15,19 +15,25 @@ import {
|
||||
* Resampling
|
||||
*/
|
||||
export function geoRawMercator() {
|
||||
var project = d3_geoMercatorRaw;
|
||||
var k = 512 / Math.PI; // scale
|
||||
var x = 0;
|
||||
var y = 0; // translate
|
||||
var clipExtent = [[0, 0], [0, 0]];
|
||||
|
||||
const project = d3_geoMercatorRaw;
|
||||
let k = 512 / Math.PI; // scale
|
||||
let x = 0;
|
||||
let y = 0; // translate
|
||||
let clipExtent = [[0, 0], [0, 0]];
|
||||
|
||||
/**
|
||||
* @param {[number, number]} point
|
||||
* @returns {[number, number]}
|
||||
*/
|
||||
function projection(point) {
|
||||
point = project(point[0] * Math.PI / 180, point[1] * Math.PI / 180);
|
||||
return [point[0] * k + x, y - point[1] * k];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param {[number, number]} point
|
||||
* @returns {[number, number]}
|
||||
*/
|
||||
projection.invert = function(point) {
|
||||
point = project.invert((point[0] - x) / k, (y - point[1]) / k);
|
||||
return point && [point[0] * 180 / Math.PI, point[1] * 180 / Math.PI];
|
||||
@@ -67,7 +73,7 @@ export function geoRawMercator() {
|
||||
|
||||
projection.stream = d3_geoTransform({
|
||||
point: function(x, y) {
|
||||
var vec = projection([x, y]);
|
||||
const vec = projection([x, y]);
|
||||
this.stream.point(vec[0], vec[1]);
|
||||
}
|
||||
}).stream;
|
||||
@@ -75,3 +81,6 @@ export function geoRawMercator() {
|
||||
|
||||
return projection;
|
||||
}
|
||||
/**
|
||||
* @typedef {ReturnType<geoRawMercator>} Projection
|
||||
*/
|
||||
|
||||
Vendored
+2
@@ -21,6 +21,8 @@ declare global {
|
||||
|
||||
export type AbstractEntity = InstanceType<typeof iD.osmEntity>;
|
||||
export type OsmEntity = OsmNode | OsmWay | OsmRelation;
|
||||
|
||||
export type Projection = import('./geo/raw_mercator').Projection;
|
||||
}
|
||||
|
||||
declare namespace d3 {
|
||||
|
||||
+6
-6
@@ -28,12 +28,12 @@ export function svgDefs(context) {
|
||||
// positioning for different tags)
|
||||
|
||||
/** @param {string} name @param {string} colour */
|
||||
function addOnewayMarker(name, colour) {
|
||||
function addOnewayMarker(name, colour, opacity) {
|
||||
_defsSelection
|
||||
.append('marker')
|
||||
.attr('id', `ideditor-oneway-marker-${name}`)
|
||||
.attr('viewBox', '0 0 10 5')
|
||||
.attr('refX', 2.5)
|
||||
.attr('refX', 4)
|
||||
.attr('refY', 2.5)
|
||||
.attr('markerWidth', 2)
|
||||
.attr('markerHeight', 2)
|
||||
@@ -41,14 +41,14 @@ export function svgDefs(context) {
|
||||
.attr('orient', 'auto')
|
||||
.append('path')
|
||||
.attr('class', 'oneway-marker-path')
|
||||
.attr('d', 'M 5,3 L 0,3 L 0,2 L 5,2 L 5,0 L 10,2.5 L 5,5 z')
|
||||
.attr('d', 'M 6,3 L 0,3 L 0,2 L 6,2 L 5,0 L 10,2.5 L 5,5 z')
|
||||
.attr('stroke', 'none')
|
||||
.attr('fill', colour)
|
||||
.attr('opacity', '0.75');
|
||||
.attr('opacity', '1');
|
||||
}
|
||||
addOnewayMarker('black', '#000'); // default
|
||||
addOnewayMarker('black', '#333'); // default
|
||||
addOnewayMarker('white', '#fff'); // for dark lines (bridges under construction, railways, etc.)
|
||||
addOnewayMarker('pink', '#eaf'); // for dark lines where white arrows don't work
|
||||
addOnewayMarker('gray', '#eee'); // for railway lines
|
||||
|
||||
|
||||
function addSidedMarker(name, color, offset) {
|
||||
|
||||
+73
-46
@@ -59,20 +59,31 @@ export function svgPassiveVertex(node, graph, activeID) {
|
||||
}
|
||||
|
||||
|
||||
export function svgMarkerSegments(projection, graph, dt,
|
||||
shouldReverse,
|
||||
bothDirections) {
|
||||
/**
|
||||
*
|
||||
* @param {iD.Projection} projection
|
||||
* @param {iD.Graph} graph
|
||||
* @param {Number} dt spacing between segments
|
||||
* @param {Function<Boolean>} [shouldReverse]
|
||||
* @param {Function<Boolean>} [bothDirections]
|
||||
*/
|
||||
export function svgMarkerSegments(projection, graph, dt, shouldReverse = () => false, bothDirections = () => false) {
|
||||
/**
|
||||
* @param {iD.OsmWay} entity
|
||||
* @returns {[{id: String, d: String}]} list of svg path segments corres
|
||||
*/
|
||||
return function(entity) {
|
||||
var i = 0;
|
||||
var offset = dt;
|
||||
var segments = [];
|
||||
var clip = d3_geoIdentity().clipExtent(projection.clipExtent()).stream;
|
||||
var coordinates = graph.childNodes(entity).map(function(n) { return n.loc; });
|
||||
var a, b;
|
||||
let i = 0;
|
||||
let offset = dt / 2;
|
||||
const segments = [];
|
||||
|
||||
if (shouldReverse(entity)) {
|
||||
coordinates.reverse();
|
||||
}
|
||||
const clip = paddedClipExtent(projection);
|
||||
|
||||
const coordinates = graph.childNodes(entity).map(function(n) { return n.loc; });
|
||||
let a, b;
|
||||
|
||||
const _shouldReverse = shouldReverse(entity);
|
||||
const _bothDirections = bothDirections(entity);
|
||||
|
||||
d3_geoStream({
|
||||
type: 'LineString',
|
||||
@@ -84,19 +95,19 @@ export function svgMarkerSegments(projection, graph, dt,
|
||||
b = [x, y];
|
||||
|
||||
if (a) {
|
||||
var span = geoVecLength(a, b) - offset;
|
||||
let span = geoVecLength(a, b) - offset;
|
||||
|
||||
if (span >= 0) {
|
||||
var heading = geoVecAngle(a, b);
|
||||
var dx = dt * Math.cos(heading);
|
||||
var dy = dt * Math.sin(heading);
|
||||
var p = [
|
||||
const heading = geoVecAngle(a, b);
|
||||
const dx = dt * Math.cos(heading);
|
||||
const dy = dt * Math.sin(heading);
|
||||
let p = [
|
||||
a[0] + offset * Math.cos(heading),
|
||||
a[1] + offset * Math.sin(heading)
|
||||
];
|
||||
|
||||
// gather coordinates
|
||||
var coord = [a, p];
|
||||
const coord = [a, p];
|
||||
for (span -= dt; span >= 0; span -= dt) {
|
||||
p = geoVecAdd(p, [dx, dy]);
|
||||
coord.push(p);
|
||||
@@ -104,17 +115,18 @@ export function svgMarkerSegments(projection, graph, dt,
|
||||
coord.push(b);
|
||||
|
||||
// generate svg paths
|
||||
var segment = '';
|
||||
var j;
|
||||
let segment = '';
|
||||
|
||||
for (j = 0; j < coord.length; j++) {
|
||||
segment += (j === 0 ? 'M' : 'L') + coord[j][0] + ',' + coord[j][1];
|
||||
if (!_shouldReverse || _bothDirections) {
|
||||
for (let j = 0; j < coord.length; j++) {
|
||||
segment += (j === 0 ? 'M' : 'L') + coord[j][0] + ',' + coord[j][1];
|
||||
}
|
||||
segments.push({ id: entity.id, index: i++, d: segment });
|
||||
}
|
||||
segments.push({ id: entity.id, index: i++, d: segment });
|
||||
|
||||
if (bothDirections(entity)) {
|
||||
if (_shouldReverse || _bothDirections) {
|
||||
segment = '';
|
||||
for (j = coord.length - 1; j >= 0; j--) {
|
||||
for (let j = coord.length - 1; j >= 0; j--) {
|
||||
segment += (j === coord.length - 1 ? 'M' : 'L') + coord[j][0] + ',' + coord[j][1];
|
||||
}
|
||||
segments.push({ id: entity.id, index: i++, d: segment });
|
||||
@@ -133,30 +145,19 @@ export function svgMarkerSegments(projection, graph, dt,
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param {iD.Projection} projection
|
||||
* @param {iD.Graph} graph
|
||||
* @param {Boolean} isArea
|
||||
*/
|
||||
export function svgPath(projection, graph, isArea) {
|
||||
|
||||
// Explanation of magic numbers:
|
||||
// "padding" here allows space for strokes to extend beyond the viewport,
|
||||
// so that the stroke isn't drawn along the edge of the viewport when
|
||||
// the shape is clipped.
|
||||
//
|
||||
// When drawing lines, pad viewport by 5px.
|
||||
// When drawing areas, pad viewport by 65px in each direction to allow
|
||||
// for 60px area fill stroke (see ".fill-partial path.fill" css rule)
|
||||
|
||||
var cache = {};
|
||||
var padding = isArea ? 65 : 5;
|
||||
var viewport = projection.clipExtent();
|
||||
var paddedExtent = [
|
||||
[viewport[0][0] - padding, viewport[0][1] - padding],
|
||||
[viewport[1][0] + padding, viewport[1][1] + padding]
|
||||
];
|
||||
var clip = d3_geoIdentity().clipExtent(paddedExtent).stream;
|
||||
var project = projection.stream;
|
||||
var path = d3_geoPath()
|
||||
const cache = {};
|
||||
const project = projection.stream;
|
||||
const clip = paddedClipExtent(projection, isArea);
|
||||
const path = d3_geoPath()
|
||||
.projection({stream: function(output) { return project(clip(output)); }});
|
||||
|
||||
var svgpath = function(entity) {
|
||||
const svgpath = function(entity) {
|
||||
if (entity.id in cache) {
|
||||
return cache[entity.id];
|
||||
} else {
|
||||
@@ -283,3 +284,29 @@ export function svgSegmentWay(way, graph, activeID) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a d3 projection stream that clips the given geometries to an
|
||||
* extent that is slightly padded.
|
||||
*
|
||||
* Explanation of magic numbers:
|
||||
* "padding" here allows space for strokes to extend beyond the viewport,
|
||||
* so that the stroke isn't drawn along the edge of the viewport when
|
||||
* the shape is clipped.
|
||||
* When drawing lines, pad viewport by 5px.
|
||||
* When drawing areas, pad viewport by 65px in each direction to allow
|
||||
* for 60px area fill stroke (see ".fill-partial path.fill" css rule)
|
||||
*
|
||||
* @param {import('../geo/raw_mercator').Projection} projection
|
||||
* @param {Boolean} isArea
|
||||
*/
|
||||
function paddedClipExtent(projection, isArea = false) {
|
||||
var padding = isArea ? 65 : 5;
|
||||
var viewport = projection.clipExtent();
|
||||
var paddedExtent = [
|
||||
[viewport[0][0] - padding, viewport[0][1] - padding],
|
||||
[viewport[1][0] + padding, viewport[1][1] + padding]
|
||||
];
|
||||
return d3_geoIdentity().clipExtent(paddedExtent).stream;
|
||||
}
|
||||
|
||||
@@ -14,9 +14,9 @@ import { utilDetect } from '../util/detect';
|
||||
function onewayArrowColour(tags) {
|
||||
// the return value must be defined in ./defs.js
|
||||
if (tags.highway === 'construction' && tags.bridge) return 'white';
|
||||
if (tags.highway === 'pedestrian' && tags.bridge) return 'pink';
|
||||
if (tags.railway) return 'black'; // TODO: use a better colour
|
||||
if (tags.aeroway === 'runway') return 'pink';
|
||||
if (tags.highway === 'pedestrian') return 'gray';
|
||||
if (tags.railway) return 'gray';
|
||||
if (tags.aeroway === 'runway') return 'white';
|
||||
|
||||
return 'black';
|
||||
}
|
||||
@@ -264,7 +264,7 @@ export function svgLines(projection, context) {
|
||||
var v = pathdata[k];
|
||||
var onewayArr = v.filter(function(d) { return d.isOneWay(); });
|
||||
var onewaySegments = svgMarkerSegments(
|
||||
projection, graph, 35,
|
||||
projection, graph, 36,
|
||||
entity => entity.isOneWayBackwards(),
|
||||
entity => entity.isBiDirectional(),
|
||||
);
|
||||
@@ -272,9 +272,7 @@ export function svgLines(projection, context) {
|
||||
|
||||
var sidedArr = v.filter(function(d) { return d.isSided(); });
|
||||
var sidedSegments = svgMarkerSegments(
|
||||
projection, graph, 30,
|
||||
function shouldReverse() { return false; },
|
||||
function bothDirections() { return false; }
|
||||
projection, graph, 30
|
||||
);
|
||||
sideddata[k] = utilArrayFlatten(sidedArr.map(sidedSegments));
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user