mirror of
https://github.com/FoggedLens/iD.git
synced 2026-05-21 15:56:56 +02:00
Add support for nope targets, line sub-segment targeting
This commit is contained in:
@@ -31,6 +31,12 @@
|
||||
stroke: currentColor;
|
||||
}
|
||||
|
||||
/* `.target-nope` objects are explicitly forbidden to join to */
|
||||
.node.target.target-nope,
|
||||
.way.target.target-nope {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* `.active` objects (currently being drawn or dragged) are not interactive */
|
||||
/* This is important to allow the events to drop through to whatever is */
|
||||
/* below them on the map, so you can still hover and connect to other things. */
|
||||
|
||||
@@ -58,7 +58,10 @@ export function behaviorDraw(context) {
|
||||
// When drawing, connect only to things classed as targets..
|
||||
// (this excludes area fills and active drawing elements)
|
||||
var selection = d3_select(element);
|
||||
return (selection.classed('target') && element.__data__) || {};
|
||||
if (selection.classed('target')) return {};
|
||||
|
||||
var d = selection.datum();
|
||||
return (d && d.id && context.hasEntity(d.id)) || {};
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
} from 'd3-selection';
|
||||
|
||||
import { d3keybinding as d3_keybinding } from '../lib/d3.keybinding.js';
|
||||
import { osmEntity } from '../osm/index';
|
||||
import { utilRebind } from '../util/rebind';
|
||||
|
||||
|
||||
@@ -107,19 +106,20 @@ export function behaviorHover(context) {
|
||||
_selection.selectAll('.hover-suppressed')
|
||||
.classed('hover-suppressed', false);
|
||||
|
||||
if (_target instanceof osmEntity && _target.id !== _newId) {
|
||||
var entity = _target && _target.id && context.hasEntity(_target.id);
|
||||
if (entity && entity.id !== _newId) {
|
||||
|
||||
// If drawing a way, don't hover on a node that was just placed. #3974
|
||||
var mode = context.mode() && context.mode().id;
|
||||
if ((mode === 'draw-line' || mode === 'draw-area') && !_newId && _target.type === 'node') {
|
||||
_newId = _target.id;
|
||||
if ((mode === 'draw-line' || mode === 'draw-area') && !_newId && entity.type === 'node') {
|
||||
_newId = entity.id;
|
||||
return;
|
||||
}
|
||||
|
||||
var selector = '.' + _target.id;
|
||||
var selector = '.' + entity.id;
|
||||
|
||||
if (_target.type === 'relation') {
|
||||
_target.members.forEach(function(member) {
|
||||
if (entity.type === 'relation') {
|
||||
entity.members.forEach(function(member) {
|
||||
selector += ', .' + member.id;
|
||||
});
|
||||
}
|
||||
@@ -129,7 +129,7 @@ export function behaviorHover(context) {
|
||||
_selection.selectAll(selector)
|
||||
.classed(suppressed ? 'hover-suppressed' : 'hover', true);
|
||||
|
||||
dispatch.call('hover', this, !suppressed && _target.id);
|
||||
dispatch.call('hover', this, !suppressed && entity.id);
|
||||
|
||||
} else {
|
||||
dispatch.call('hover', this, null);
|
||||
|
||||
+12
-10
@@ -17,10 +17,10 @@ import { osmEntity } from '../osm';
|
||||
|
||||
|
||||
export function behaviorSelect(context) {
|
||||
var lastMouse = null,
|
||||
suppressMenu = true,
|
||||
tolerance = 4,
|
||||
p1 = null;
|
||||
var lastMouse = null;
|
||||
var suppressMenu = true;
|
||||
var tolerance = 4;
|
||||
var p1 = null;
|
||||
|
||||
|
||||
function point() {
|
||||
@@ -102,19 +102,21 @@ export function behaviorSelect(context) {
|
||||
.on('mouseup.select', null, true);
|
||||
|
||||
if (!p1) return;
|
||||
var p2 = point(),
|
||||
dist = geoEuclideanDistance(p1, p2);
|
||||
var p2 = point();
|
||||
var dist = geoEuclideanDistance(p1, p2);
|
||||
|
||||
p1 = null;
|
||||
if (dist > tolerance) {
|
||||
return;
|
||||
}
|
||||
|
||||
var isMultiselect = d3_event.shiftKey || d3_select('#surface .lasso').node(),
|
||||
isShowAlways = +context.storage('edit-menu-show-always') === 1,
|
||||
datum = d3_event.target.__data__ || (lastMouse && lastMouse.target.__data__),
|
||||
mode = context.mode();
|
||||
var isMultiselect = d3_event.shiftKey || d3_select('#surface .lasso').node();
|
||||
var isShowAlways = +context.storage('edit-menu-show-always') === 1;
|
||||
var datum = d3_event.target.__data__ || (lastMouse && lastMouse.target.__data__);
|
||||
var mode = context.mode();
|
||||
|
||||
var entity = datum && datum.id && context.hasEntity(datum.id);
|
||||
if (entity) datum = entity;
|
||||
|
||||
if (datum && datum.type === 'midpoint') {
|
||||
datum = datum.parents[0];
|
||||
|
||||
@@ -127,7 +127,8 @@ export function modeDragNode(context) {
|
||||
if (!event || event.altKey || !d3_select(event.target).classed('target')) {
|
||||
return {};
|
||||
} else {
|
||||
return event.target.__data__ || {};
|
||||
var d = event.target.__data__;
|
||||
return (d && d.id && context.hasEntity(d.id)) || {};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -246,7 +246,10 @@ export function modeSelect(context, selectedIDs) {
|
||||
|
||||
function dblclick() {
|
||||
var target = d3_select(d3_event.target);
|
||||
|
||||
var datum = target.datum();
|
||||
var entity = datum && datum.id && context.hasEntity(datum.id);
|
||||
if (entity) datum = entity;
|
||||
|
||||
if (datum instanceof osmWay && target.classed('target')) {
|
||||
var choice = geoChooseEdge(context.childNodes(datum), context.mouse(), context.projection);
|
||||
|
||||
+155
-8
@@ -4,6 +4,7 @@ import _flatten from 'lodash-es/flatten';
|
||||
import _forOwn from 'lodash-es/forOwn';
|
||||
import _map from 'lodash-es/map';
|
||||
|
||||
import { geoPath as d3_geoPath } from 'd3-geo';
|
||||
import { range as d3_range } from 'd3-array';
|
||||
|
||||
import {
|
||||
@@ -37,16 +38,145 @@ export function svgLines(projection, context) {
|
||||
|
||||
|
||||
function drawTargets(selection, graph, entities, filter) {
|
||||
var fillClass = context.getDebug('target') ? 'pink ' : 'nocolor ';
|
||||
var getPath = svgPath(projection, graph);
|
||||
var passive = entities.filter(function(d) {
|
||||
return true;
|
||||
// return context.activeIDs().indexOf(d.id) === -1;
|
||||
var targetClass = context.getDebug('target') ? 'pink ' : 'nocolor ';
|
||||
var nopeClass = context.getDebug('target') ? 'red ' : 'nocolor ';
|
||||
var getPath = svgPath(projection, graph).geojson;
|
||||
// var getPath = d3_geoPath(projection);
|
||||
|
||||
var activeID = context.activeID();
|
||||
|
||||
// Rather than drawing lines directly, we'll cut out pieces
|
||||
// depending on which parts are active.
|
||||
var data = { targets: [], nopes: [] };
|
||||
|
||||
// Touch targets control which other vertices we can drag a vertex onto.
|
||||
// - the activeID - nope
|
||||
// - next to the activeID - yes (vertices will be merged)
|
||||
// - 2 away from the activeID - nope (would create a self intersecting segment)
|
||||
// - all others on a closed way - nope (would create a self intersecting polygon)
|
||||
//
|
||||
// 0 = active vertex - no touch/connect
|
||||
// 1 = passive vertex - yes touch/connect
|
||||
// 2 = adjacent vertex - special rules
|
||||
function passive(d) {
|
||||
if (!activeID) return 1;
|
||||
if (activeID === d.id) return 0;
|
||||
|
||||
var parents = graph.parentWays(d);
|
||||
var i, j;
|
||||
|
||||
for (i = 0; i < parents.length; i++) {
|
||||
var nodes = parents[i].nodes;
|
||||
var isClosed = parents[i].isClosed();
|
||||
for (j = 0; j < nodes.length; j++) { // find this vertex, look nearby
|
||||
if (nodes[j] === d.id) {
|
||||
var ix1 = j - 2;
|
||||
var ix2 = j - 1;
|
||||
var ix3 = j + 1;
|
||||
var ix4 = j + 2;
|
||||
|
||||
if (isClosed) { // wraparound if needed
|
||||
var max = nodes.length - 1;
|
||||
if (ix1 < 0) ix1 = max + ix1;
|
||||
if (ix2 < 0) ix2 = max + ix2;
|
||||
if (ix3 > max) ix3 = ix3 - max;
|
||||
if (ix4 > max) ix4 = ix4 - max;
|
||||
}
|
||||
|
||||
if (nodes[ix1] === activeID) return 0; // prevent self intersect
|
||||
else if (nodes[ix2] === activeID) return 2; // adjacent - ok!
|
||||
else if (nodes[ix3] === activeID) return 2; // adjacent - ok!
|
||||
else if (nodes[ix4] === activeID) return 0; // prevent self intersect
|
||||
else if (isClosed && nodes.indexOf(activeID) !== -1) return 0; // prevent self intersect
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
entities.forEach(function(way) {
|
||||
var coordGroups = { passive: [], active: [] };
|
||||
var segment = [];
|
||||
var startType = null; // 0 = active, 1 = passive, 2 = adjacent
|
||||
var currType = null;
|
||||
var node;
|
||||
|
||||
for (var i = 0; i < way.nodes.length; i++) {
|
||||
|
||||
if (way.nodes[i] === activeID) { // vertex is the activeID
|
||||
segment = []; // draw no segment here
|
||||
startType = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
node = graph.entity(way.nodes[i]);
|
||||
currType = passive(node);
|
||||
|
||||
if (startType === null) {
|
||||
startType = currType;
|
||||
}
|
||||
|
||||
if (currType !== startType) { // line changes here - try to save a segment
|
||||
|
||||
if (segment.length > 0) { // finish previous segment
|
||||
segment.push(node.loc);
|
||||
|
||||
if (startType === 2 || currType === 2) { // one adjacent vertex
|
||||
coordGroups.active.push(segment);
|
||||
} else if (startType === 0 && currType === 0) { // both active vertices
|
||||
coordGroups.active.push(segment);
|
||||
} else {
|
||||
coordGroups.passive.push(segment);
|
||||
}
|
||||
}
|
||||
|
||||
segment = [];
|
||||
startType = currType;
|
||||
}
|
||||
|
||||
segment.push(node.loc);
|
||||
}
|
||||
|
||||
// complete whatever segment we ended on
|
||||
if (segment.length > 1) {
|
||||
if (startType === 2 || currType === 2) { // one adjacent vertex
|
||||
coordGroups.active.push(segment);
|
||||
} else if (startType === 0 && currType === 0) { // both active vertices
|
||||
coordGroups.active.push(segment);
|
||||
} else {
|
||||
coordGroups.passive.push(segment);
|
||||
}
|
||||
}
|
||||
|
||||
if (coordGroups.passive.length) {
|
||||
data.targets.push({
|
||||
'type': 'Feature',
|
||||
'id': way.id,
|
||||
'geometry': {
|
||||
'type': 'MultiLineString',
|
||||
'coordinates': coordGroups.passive
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (coordGroups.active.length) {
|
||||
data.nopes.push({
|
||||
'type': 'Feature',
|
||||
'id': way.id + '-nope', // break the ids on purpose
|
||||
'geometry': {
|
||||
'type': 'MultiLineString',
|
||||
'coordinates': coordGroups.active
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
var targets = selection.selectAll('.line.target')
|
||||
|
||||
// Places to hover and connect
|
||||
var targets = selection.selectAll('.line.target-allowed')
|
||||
.filter(filter)
|
||||
.data(passive, function key(d) { return d.id; });
|
||||
.data(data.targets, function key(d) { return d.id; });
|
||||
|
||||
// exit
|
||||
targets.exit()
|
||||
@@ -57,7 +187,24 @@ export function svgLines(projection, context) {
|
||||
.append('path')
|
||||
.merge(targets)
|
||||
.attr('d', getPath)
|
||||
.attr('class', function(d) { return 'way line target ' + fillClass + d.id; });
|
||||
.attr('class', function(d) { return 'way line target target-allowed ' + targetClass + d.id; });
|
||||
|
||||
|
||||
// NOPE
|
||||
var nopes = selection.selectAll('.line.target-nope')
|
||||
.data(data.nopes, function key(d) { return d.id; });
|
||||
|
||||
// exit
|
||||
nopes.exit()
|
||||
.remove();
|
||||
|
||||
// enter/update
|
||||
nopes.enter()
|
||||
.append('path')
|
||||
.merge(nopes)
|
||||
.attr('d', getPath)
|
||||
.attr('class', function(d) { return 'way line target target-nope ' + nopeClass + d.id; });
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
+16
-12
@@ -15,23 +15,27 @@ export function svgPath(projection, graph, isArea) {
|
||||
// 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 = {},
|
||||
padding = isArea ? 65 : 5,
|
||||
viewport = projection.clipExtent(),
|
||||
paddedExtent = [
|
||||
[viewport[0][0] - padding, viewport[0][1] - padding],
|
||||
[viewport[1][0] + padding, viewport[1][1] + padding]
|
||||
],
|
||||
clip = d3_geoIdentity().clipExtent(paddedExtent).stream,
|
||||
project = projection.stream,
|
||||
path = d3_geoPath()
|
||||
.projection({stream: function(output) { return project(clip(output)); }});
|
||||
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()
|
||||
.projection({stream: function(output) { return project(clip(output)); }});
|
||||
|
||||
return function(entity) {
|
||||
var svgpath = function(entity) {
|
||||
if (entity.id in cache) {
|
||||
return cache[entity.id];
|
||||
} else {
|
||||
return cache[entity.id] = path(entity.asGeoJSON(graph));
|
||||
}
|
||||
};
|
||||
|
||||
svgpath.geojson = path;
|
||||
|
||||
return svgpath;
|
||||
}
|
||||
|
||||
+87
-9
@@ -182,17 +182,75 @@ export function svgVertices(projection, context) {
|
||||
|
||||
|
||||
function drawTargets(selection, graph, entities, filter) {
|
||||
var fillClass = context.getDebug('target') ? 'pink ' : 'nocolor ';
|
||||
var targetClass = context.getDebug('target') ? 'pink ' : 'nocolor ';
|
||||
var nopeClass = context.getDebug('target') ? 'red ' : 'nocolor ';
|
||||
var activeID = context.activeID();
|
||||
var data = { targets: [], nopes: [] };
|
||||
|
||||
// no targets for entities that are active, or adjacent to active.
|
||||
// Touch targets control which other vertices we can drag a vertex onto.
|
||||
// - the activeID - nope
|
||||
// - next to the activeID - yes (vertices will be merged)
|
||||
// - 2 away from the activeID - nope (would create a self intersecting segment)
|
||||
// - all others on a closed way - nope (would create a self intersecting polygon)
|
||||
//
|
||||
// 0 = active vertex - no touch/connect
|
||||
// 1 = passive vertex - yes touch/connect
|
||||
// 2 = adjacent vertex - special rules
|
||||
function passive(d) {
|
||||
return d.id !== context.activeID();
|
||||
if (!activeID) return 1;
|
||||
if (activeID === d.id) return 0;
|
||||
|
||||
var parents = graph.parentWays(d);
|
||||
var i, j;
|
||||
|
||||
for (i = 0; i < parents.length; i++) {
|
||||
var nodes = parents[i].nodes;
|
||||
var isClosed = parents[i].isClosed();
|
||||
for (j = 0; j < nodes.length; j++) { // find this vertex, look nearby
|
||||
if (nodes[j] === d.id) {
|
||||
var ix1 = j - 2;
|
||||
var ix2 = j - 1;
|
||||
var ix3 = j + 1;
|
||||
var ix4 = j + 2;
|
||||
|
||||
if (isClosed) { // wraparound if needed
|
||||
var max = nodes.length - 1;
|
||||
if (ix1 < 0) ix1 = max + ix1;
|
||||
if (ix2 < 0) ix2 = max + ix2;
|
||||
if (ix3 > max) ix3 = ix3 - max;
|
||||
if (ix4 > max) ix4 = ix4 - max;
|
||||
}
|
||||
|
||||
if (nodes[ix1] === activeID) return 0; // prevent self intersect
|
||||
else if (nodes[ix2] === activeID) return 2; // adjacent - ok!
|
||||
else if (nodes[ix3] === activeID) return 2; // adjacent - ok!
|
||||
else if (nodes[ix4] === activeID) return 0; // prevent self intersect
|
||||
else if (isClosed && nodes.indexOf(activeID) !== -1) return 0; // prevent self intersect
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
var data = entities.filter(passive);
|
||||
var targets = selection.selectAll('.vertex.target')
|
||||
|
||||
entities.forEach(function(node) {
|
||||
if (activeID === node.id) return; // draw no vertex on the activeID
|
||||
|
||||
var currType = passive(node);
|
||||
if (currType !== 0) {
|
||||
data.targets.push(node); // passive or adjacent - allow to connect
|
||||
} else {
|
||||
data.nopes.push({
|
||||
id: node.id + '-nope', // not a real osmNode, break the id on purpose
|
||||
loc: node.loc
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
var targets = selection.selectAll('.vertex.target-allowed')
|
||||
.filter(filter)
|
||||
.data(data, function key(d) { return d.id; });
|
||||
.data(data.targets, function key(d) { return d.id; });
|
||||
|
||||
// exit
|
||||
targets.exit()
|
||||
@@ -201,9 +259,26 @@ export function svgVertices(projection, context) {
|
||||
// enter/update
|
||||
targets.enter()
|
||||
.append('circle')
|
||||
.attr('r', function(d) { return _radii[d.id] || radiuses.shadow[3]; })
|
||||
.attr('r', function(d) { return (_radii[d.id] || radiuses.shadow[3]); })
|
||||
.merge(targets)
|
||||
.attr('class', function(d) { return 'node vertex target ' + fillClass + d.id; })
|
||||
.attr('class', function(d) { return 'node vertex target target-allowed ' + targetClass + d.id; })
|
||||
.attr('transform', svgPointTransform(projection));
|
||||
|
||||
|
||||
// NOPE
|
||||
var nopes = selection.selectAll('.vertex.target-nope')
|
||||
.data(data.nopes, function key(d) { return d.id; });
|
||||
|
||||
// exit
|
||||
nopes.exit()
|
||||
.remove();
|
||||
|
||||
// enter/update
|
||||
nopes.enter()
|
||||
.append('circle')
|
||||
.attr('r', function(d) { return (_radii[d.id.replace('-nope','')] || radiuses.shadow[3]); })
|
||||
.merge(nopes)
|
||||
.attr('class', function(d) { return 'node vertex target target-nope ' + nopeClass + d.id; })
|
||||
.attr('transform', svgPointTransform(projection));
|
||||
}
|
||||
|
||||
@@ -321,8 +396,11 @@ export function svgVertices(projection, context) {
|
||||
.call(draw, graph, currentVisible(all), sets, filterRendered);
|
||||
|
||||
// Draw touch targets..
|
||||
var filterTargets = function(d) {
|
||||
return isMoving ? true : filterRendered(d);
|
||||
};
|
||||
selection.selectAll('.layer-points .layer-points-targets')
|
||||
.call(drawTargets, graph, currentVisible(all), filterRendered);
|
||||
.call(drawTargets, graph, currentVisible(all), filterTargets);
|
||||
|
||||
|
||||
function currentVisible(which) {
|
||||
|
||||
Reference in New Issue
Block a user