From 6d7659b3bbffe506a54a668c20e5b099fe86adc9 Mon Sep 17 00:00:00 2001 From: Bryan Housel Date: Thu, 21 Dec 2017 10:36:02 -0500 Subject: [PATCH] Refactor common helper code to svg/helpers.js, add area nopes --- modules/svg/areas.js | 43 ++++- modules/svg/debug.js | 12 +- modules/svg/helpers.js | 256 ++++++++++++++++++++++++++++ modules/svg/index.js | 10 +- modules/svg/lines.js | 135 +-------------- modules/svg/mapillary_images.js | 35 ++-- modules/svg/mapillary_signs.js | 8 +- modules/svg/one_way_segments.js | 67 -------- modules/svg/openstreetcam_images.js | 35 ++-- modules/svg/path.js | 41 ----- modules/svg/point_transform.js | 7 - modules/svg/relation_member_tags.js | 15 -- modules/svg/vertices.js | 59 +------ 13 files changed, 339 insertions(+), 384 deletions(-) create mode 100644 modules/svg/helpers.js delete mode 100644 modules/svg/one_way_segments.js delete mode 100644 modules/svg/path.js delete mode 100644 modules/svg/point_transform.js delete mode 100644 modules/svg/relation_member_tags.js diff --git a/modules/svg/areas.js b/modules/svg/areas.js index c6ff49cbb..3fb7c3723 100644 --- a/modules/svg/areas.js +++ b/modules/svg/areas.js @@ -4,7 +4,7 @@ import _values from 'lodash-es/values'; import { bisector as d3_bisector } from 'd3-array'; import { osmEntity, osmIsSimpleMultipolygonOuterMember } from '../osm'; -import { svgPath, svgTagClasses } from './index'; +import { svgPath, svgSegmentWay, svgTagClasses } from './index'; export function svgAreas(projection, context) { @@ -42,16 +42,25 @@ export function svgAreas(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).geojson; + var activeID = context.activeID(); + + // The targets and nopes will be MultiLineString sub-segments of the ways + var data = { targets: [], nopes: [] }; + + entities.forEach(function(way) { + var features = svgSegmentWay(way, graph, activeID); + data.targets.push.apply(data.targets, features.passive); + data.nopes.push.apply(data.nopes, features.active); }); - var targets = selection.selectAll('.area.target') + + // Targets allow hover and vertex snapping + var targets = selection.selectAll('.area.target-allowed') .filter(filter) - .data(passive, function key(d) { return d.id; }); + .data(data.targets, function key(d) { return d.id; }); // exit targets.exit() @@ -62,7 +71,23 @@ export function svgAreas(projection, context) { .append('path') .merge(targets) .attr('d', getPath) - .attr('class', function(d) { return 'way area target ' + fillClass + d.id; }); + .attr('class', function(d) { return 'way area target target-allowed ' + targetClass + d.id; }); + + + // NOPE + var nopes = selection.selectAll('.area.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 area target target-nope ' + nopeClass + d.id; }); } diff --git a/modules/svg/debug.js b/modules/svg/debug.js index 8ab5617d4..71f007417 100644 --- a/modules/svg/debug.js +++ b/modules/svg/debug.js @@ -1,12 +1,8 @@ -import { geoPath as d3_geoPath } from 'd3-geo'; import { select as d3_select } from 'd3-selection'; import { geoPolygonIntersectsPolygon } from '../geo'; -import { - data, - dataImperial, - dataDriveLeft -} from '../../data'; +import { data, dataImperial, dataDriveLeft } from '../../data'; +import { svgPath } from './index'; export function svgDebug(projection, context) { @@ -27,8 +23,6 @@ export function svgDebug(projection, context) { var showsImperial = context.getDebug('imperial'); var showsDriveLeft = context.getDebug('driveLeft'); var showsTouchTargets = context.getDebug('target'); - var path = d3_geoPath(projection); - var debugData = []; if (showsTile) { @@ -134,7 +128,7 @@ export function svgDebug(projection, context) { // update layer.selectAll('path') - .attr('d', path); + .attr('d', svgPath(projection).geojson); } diff --git a/modules/svg/helpers.js b/modules/svg/helpers.js new file mode 100644 index 000000000..88f04f578 --- /dev/null +++ b/modules/svg/helpers.js @@ -0,0 +1,256 @@ +import _extend from 'lodash-es/extend'; + +import { + geoIdentity as d3_geoIdentity, + geoPath as d3_geoPath, + geoStream as d3_geoStream +} from 'd3-geo'; + +import { geoEuclideanDistance } from '../geo'; + + +// Touch targets control which other vertices we can drag a vertex onto. +// +// - the activeID - nope +// - 1 away (adjacent) to the activeID - yes (vertices will be merged) +// - 2 away from the activeID - nope (would create a self intersecting segment) +// - all others on a linear way - yes +// - all others on a closed way - nope (would create a self intersecting polygon) +// +// returns +// 0 = active vertex - no touch/connect +// 1 = passive vertex - yes touch/connect +// 2 = adjacent vertex - yes but pay attention segmenting a line here +// +export function svgPassiveVertex(node, graph, activeID) { + if (!activeID) return 1; + if (activeID === node.id) return 0; + + var parents = graph.parentWays(node); + + for (var i = 0; i < parents.length; i++) { + var nodes = parents[i].nodes; + var isClosed = parents[i].isClosed(); + for (var j = 0; j < nodes.length; j++) { // find this vertex, look nearby + if (nodes[j] === node.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; // no - prevent self intersect + else if (nodes[ix2] === activeID) return 2; // ok - adjacent + else if (nodes[ix3] === activeID) return 2; // ok - adjacent + else if (nodes[ix4] === activeID) return 0; // no - prevent self intersect + else if (isClosed && nodes.indexOf(activeID) !== -1) return 0; // no - prevent self intersect + } + } + } + + return 1; // ok +} + + +export function svgOneWaySegments(projection, graph, dt) { + 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; + + if (entity.tags.oneway === '-1') { + coordinates.reverse(); + } + + d3_geoStream({ + type: 'LineString', + coordinates: coordinates + }, projection.stream(clip({ + lineStart: function() {}, + lineEnd: function() { a = null; }, + point: function(x, y) { + b = [x, y]; + + if (a) { + var span = geoEuclideanDistance(a, b) - offset; + + if (span >= 0) { + var angle = Math.atan2(b[1] - a[1], b[0] - a[0]); + var dx = dt * Math.cos(angle); + var dy = dt * Math.sin(angle); + var p = [ + a[0] + offset * Math.cos(angle), + a[1] + offset * Math.sin(angle) + ]; + var segment = 'M' + a[0] + ',' + a[1] + 'L' + p[0] + ',' + p[1]; + + for (span -= dt; span >= 0; span -= dt) { + p[0] += dx; + p[1] += dy; + segment += 'L' + p[0] + ',' + p[1]; + } + + segment += 'L' + b[0] + ',' + b[1]; + segments.push({id: entity.id, index: i, d: segment}); + } + + offset = -span; + i++; + } + + a = b; + } + }))); + + return segments; + }; +} + + +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() + .projection({stream: function(output) { return project(clip(output)); }}); + + 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; +} + + +export function svgPointTransform(projection) { + return function(entity) { + // http://jsperf.com/short-array-join + var pt = projection(entity.loc); + return 'translate(' + pt[0] + ',' + pt[1] + ')'; + }; +} + + +export function svgRelationMemberTags(graph) { + return function(entity) { + var tags = entity.tags; + graph.parentRelations(entity).forEach(function(relation) { + var type = relation.tags.type; + if (type === 'multipolygon' || type === 'boundary') { + tags = _extend({}, relation.tags, tags); + } + }); + return tags; + }; +} + + +export function svgSegmentWay(way, graph, activeID) { + var features = { passive: [], active: [] }; + 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 = svgPassiveVertex(node, graph, activeID); + + 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) { + features.passive.push({ + 'type': 'Feature', + 'id': way.id, + 'geometry': { + 'type': 'MultiLineString', + 'coordinates': coordGroups.passive + } + }); + } + + if (coordGroups.active.length) { + features.active.push({ + 'type': 'Feature', + 'id': way.id + '-nope', // break the ids on purpose + 'geometry': { + 'type': 'MultiLineString', + 'coordinates': coordGroups.active + } + }); + } + + return features; +} diff --git a/modules/svg/index.js b/modules/svg/index.js index 8f54f5a5e..68e2f2f28 100644 --- a/modules/svg/index.js +++ b/modules/svg/index.js @@ -9,13 +9,15 @@ export { svgLines } from './lines.js'; export { svgMapillaryImages } from './mapillary_images.js'; export { svgMapillarySigns } from './mapillary_signs.js'; export { svgMidpoints } from './midpoints.js'; -export { svgOneWaySegments } from './one_way_segments.js'; +export { svgOneWaySegments } from './helpers.js'; export { svgOpenstreetcamImages } from './openstreetcam_images.js'; export { svgOsm } from './osm.js'; -export { svgPath } from './path.js'; -export { svgPointTransform } from './point_transform.js'; +export { svgPassiveVertex } from './helpers.js'; +export { svgPath } from './helpers.js'; +export { svgPointTransform } from './helpers.js'; export { svgPoints } from './points.js'; -export { svgRelationMemberTags } from './relation_member_tags.js'; +export { svgRelationMemberTags } from './helpers.js'; +export { svgSegmentWay } from './helpers.js'; export { svgTagClasses } from './tag_classes.js'; export { svgTurns } from './turns.js'; export { svgVertices } from './vertices.js'; diff --git a/modules/svg/lines.js b/modules/svg/lines.js index b9012f0d9..fbecbed7b 100644 --- a/modules/svg/lines.js +++ b/modules/svg/lines.js @@ -4,13 +4,13 @@ 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 { svgOneWaySegments, svgPath, svgRelationMemberTags, + svgSegmentWay, svgTagClasses } from './index'; @@ -40,140 +40,20 @@ export function svgLines(projection, context) { function drawTargets(selection, graph, entities, filter) { 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 getPath = svgPath(projection).geojson; var activeID = context.activeID(); - // Rather than drawing lines directly, we'll cut out pieces - // depending on which parts are active. + // The targets and nopes will be MultiLineString sub-segments of the ways 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 features = svgSegmentWay(way, graph, activeID); + data.targets.push.apply(data.targets, features.passive); + data.nopes.push.apply(data.nopes, features.active); }); - // Places to hover and connect + // Targets allow hover and vertex snapping var targets = selection.selectAll('.line.target-allowed') .filter(filter) .data(data.targets, function key(d) { return d.id; }); @@ -204,7 +84,6 @@ export function svgLines(projection, context) { .merge(nopes) .attr('d', getPath) .attr('class', function(d) { return 'way line target target-nope ' + nopeClass + d.id; }); - } diff --git a/modules/svg/mapillary_images.js b/modules/svg/mapillary_images.js index 1bae3436c..fb8a4b4b8 100644 --- a/modules/svg/mapillary_images.js +++ b/modules/svg/mapillary_images.js @@ -1,23 +1,16 @@ import _throttle from 'lodash-es/throttle'; - -import { - geoIdentity as d3_geoIdentity, - geoPath as d3_geoPath -} from 'd3-geo'; - import { select as d3_select } from 'd3-selection'; - -import { svgPointTransform } from './point_transform'; +import { svgPath, svgPointTransform } from './index'; import { services } from '../services'; export function svgMapillaryImages(projection, context, dispatch) { - var throttledRedraw = _throttle(function () { dispatch.call('change'); }, 1000), - minZoom = 12, - minMarkerZoom = 16, - minViewfieldZoom = 18, - layer = d3_select(null), - _mapillary; + var throttledRedraw = _throttle(function () { dispatch.call('change'); }, 1000); + var minZoom = 12; + var minMarkerZoom = 16; + var minViewfieldZoom = 18; + var layer = d3_select(null); + var _mapillary; function init() { @@ -128,25 +121,19 @@ export function svgMapillaryImages(projection, context, dispatch) { var sequences = (service ? service.sequences(projection) : []); var images = (service && showMarkers ? service.images(projection) : []); - var clip = d3_geoIdentity().clipExtent(projection.clipExtent()).stream; - var project = projection.stream; - var makePath = d3_geoPath().projection({ stream: function(output) { - return project(clip(output)); - }}); - var traces = layer.selectAll('.sequences').selectAll('.sequence') .data(sequences, function(d) { return d.properties.key; }); + // exit traces.exit() .remove(); + // enter/update traces = traces.enter() .append('path') .attr('class', 'sequence') - .merge(traces); - - traces - .attr('d', makePath); + .merge(traces) + .attr('d', svgPath(projection).geojson); var groups = layer.selectAll('.markers').selectAll('.viewfield-group') diff --git a/modules/svg/mapillary_signs.js b/modules/svg/mapillary_signs.js index a327c108d..4b6095cd2 100644 --- a/modules/svg/mapillary_signs.js +++ b/modules/svg/mapillary_signs.js @@ -5,10 +5,10 @@ import { services } from '../services'; export function svgMapillarySigns(projection, context, dispatch) { - var throttledRedraw = _throttle(function () { dispatch.call('change'); }, 1000), - minZoom = 12, - layer = d3_select(null), - _mapillary; + var throttledRedraw = _throttle(function () { dispatch.call('change'); }, 1000); + var minZoom = 12; + var layer = d3_select(null); + var _mapillary; function init() { diff --git a/modules/svg/one_way_segments.js b/modules/svg/one_way_segments.js deleted file mode 100644 index 66759d66d..000000000 --- a/modules/svg/one_way_segments.js +++ /dev/null @@ -1,67 +0,0 @@ -import { - geoIdentity as d3_geoIdentity, - geoStream as d3_geoStream -} from 'd3-geo'; - -import { geoEuclideanDistance } from '../geo'; - - -export function svgOneWaySegments(projection, graph, dt) { - return function(entity) { - var a, - b, - i = 0, - offset = dt, - segments = [], - clip = d3_geoIdentity().clipExtent(projection.clipExtent()).stream, - coordinates = graph.childNodes(entity).map(function(n) { - return n.loc; - }); - - if (entity.tags.oneway === '-1') coordinates.reverse(); - - d3_geoStream({ - type: 'LineString', - coordinates: coordinates - }, projection.stream(clip({ - lineStart: function() {}, - lineEnd: function() { - a = null; - }, - point: function(x, y) { - b = [x, y]; - - if (a) { - var span = geoEuclideanDistance(a, b) - offset; - - if (span >= 0) { - var angle = Math.atan2(b[1] - a[1], b[0] - a[0]), - dx = dt * Math.cos(angle), - dy = dt * Math.sin(angle), - p = [a[0] + offset * Math.cos(angle), - a[1] + offset * Math.sin(angle)]; - - var segment = 'M' + a[0] + ',' + a[1] + - 'L' + p[0] + ',' + p[1]; - - for (span -= dt; span >= 0; span -= dt) { - p[0] += dx; - p[1] += dy; - segment += 'L' + p[0] + ',' + p[1]; - } - - segment += 'L' + b[0] + ',' + b[1]; - segments.push({id: entity.id, index: i, d: segment}); - } - - offset = -span; - i++; - } - - a = b; - } - }))); - - return segments; - }; -} diff --git a/modules/svg/openstreetcam_images.js b/modules/svg/openstreetcam_images.js index 3184a7bc2..35fc76cc0 100644 --- a/modules/svg/openstreetcam_images.js +++ b/modules/svg/openstreetcam_images.js @@ -1,23 +1,16 @@ import _throttle from 'lodash-es/throttle'; - -import { - geoIdentity as d3_geoIdentity, - geoPath as d3_geoPath -} from 'd3-geo'; - import { select as d3_select } from 'd3-selection'; - -import { svgPointTransform } from './point_transform'; +import { svgPath, svgPointTransform } from './index'; import { services } from '../services'; export function svgOpenstreetcamImages(projection, context, dispatch) { - var throttledRedraw = _throttle(function () { dispatch.call('change'); }, 1000), - minZoom = 12, - minMarkerZoom = 16, - minViewfieldZoom = 18, - layer = d3_select(null), - _openstreetcam; + var throttledRedraw = _throttle(function () { dispatch.call('change'); }, 1000); + var minZoom = 12; + var minMarkerZoom = 16; + var minViewfieldZoom = 18; + var layer = d3_select(null); + var _openstreetcam; function init() { @@ -128,25 +121,19 @@ export function svgOpenstreetcamImages(projection, context, dispatch) { var sequences = (service ? service.sequences(projection) : []); var images = (service && showMarkers ? service.images(projection) : []); - var clip = d3_geoIdentity().clipExtent(projection.clipExtent()).stream; - var project = projection.stream; - var makePath = d3_geoPath().projection({ stream: function(output) { - return project(clip(output)); - }}); - var traces = layer.selectAll('.sequences').selectAll('.sequence') .data(sequences, function(d) { return d.properties.key; }); + // exit traces.exit() .remove(); + // enter/update traces = traces.enter() .append('path') .attr('class', 'sequence') - .merge(traces); - - traces - .attr('d', makePath); + .merge(traces) + .attr('d', svgPath(projection).geojson); var groups = layer.selectAll('.markers').selectAll('.viewfield-group') diff --git a/modules/svg/path.js b/modules/svg/path.js deleted file mode 100644 index cf9f09b44..000000000 --- a/modules/svg/path.js +++ /dev/null @@ -1,41 +0,0 @@ -import { - geoIdentity as d3_geoIdentity, - geoPath as d3_geoPath -} from 'd3-geo'; - - -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() - .projection({stream: function(output) { return project(clip(output)); }}); - - 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; -} diff --git a/modules/svg/point_transform.js b/modules/svg/point_transform.js deleted file mode 100644 index cf80f845d..000000000 --- a/modules/svg/point_transform.js +++ /dev/null @@ -1,7 +0,0 @@ -export function svgPointTransform(projection) { - return function(entity) { - // http://jsperf.com/short-array-join - var pt = projection(entity.loc); - return 'translate(' + pt[0] + ',' + pt[1] + ')'; - }; -} diff --git a/modules/svg/relation_member_tags.js b/modules/svg/relation_member_tags.js deleted file mode 100644 index 824d56fe0..000000000 --- a/modules/svg/relation_member_tags.js +++ /dev/null @@ -1,15 +0,0 @@ -import _extend from 'lodash-es/extend'; - - -export function svgRelationMemberTags(graph) { - return function(entity) { - var tags = entity.tags; - graph.parentRelations(entity).forEach(function(relation) { - var type = relation.tags.type; - if (type === 'multipolygon' || type === 'boundary') { - tags = _extend({}, relation.tags, tags); - } - }); - return tags; - }; -} diff --git a/modules/svg/vertices.js b/modules/svg/vertices.js index 14b0ab369..77aad0a1d 100644 --- a/modules/svg/vertices.js +++ b/modules/svg/vertices.js @@ -6,7 +6,7 @@ import { select as d3_select } from 'd3-selection'; import { dataFeatureIcons } from '../../data'; import { geoScaleToZoom } from '../geo'; import { osmEntity } from '../osm'; -import { svgPointTransform } from './index'; +import { svgPassiveVertex, svgPointTransform } from './index'; export function svgVertices(projection, context) { @@ -187,67 +187,22 @@ export function svgVertices(projection, context) { var activeID = context.activeID(); 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(node) { - if (activeID === node.id) return; // draw no vertex on the activeID + if (activeID === node.id) return; // draw no target on the activeID - var currType = passive(node); + var currType = svgPassiveVertex(node, graph, activeID); if (currType !== 0) { - data.targets.push(node); // passive or adjacent - allow to connect + 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 + id: node.id + '-nope', // not a real osmNode, break the id on purpose loc: node.loc }); } }); + + // Targets allow hover and vertex snapping var targets = selection.selectAll('.vertex.target-allowed') .filter(filter) .data(data.targets, function key(d) { return d.id; });