From 2b4cb618b2b63dc7ba58908b547d3a09ce208624 Mon Sep 17 00:00:00 2001
From: Kushan Joshi <0o3ko0@gmail.com>
Date: Wed, 22 Jun 2016 12:29:44 +0530
Subject: [PATCH] Remove svg and deal with it separately with ui
---
Makefile | 5 +
index.html | 1 +
js/lib/id/index.js | 3813 ++++++++++++++------------------------------
test/index.html | 1 +
4 files changed, 1174 insertions(+), 2646 deletions(-)
diff --git a/Makefile b/Makefile
index c0a139e2a..1eb1dac07 100644
--- a/Makefile
+++ b/Makefile
@@ -46,6 +46,7 @@ MODULE_TARGETS = \
js/lib/id/index.js \
js/lib/id/services.js \
js/lib/id/ui/index.js \
+ js/lib/id/svg.js \
js/lib/id/ui/core.js \
js/lib/id/ui/intro.js \
js/lib/id/ui/preset.js
@@ -58,6 +59,10 @@ js/lib/id/services.js: $(shell find modules/services -type f)
@rm -f $@
node_modules/.bin/rollup -f umd -n iD.services modules/services/index.js --no-strict -o $@
+js/lib/id/svg.js: $(shell find modules/svg -type f)
+ @rm -f $@
+ node_modules/.bin/rollup -f umd -n iD.svg modules/svg/index.js --no-strict -o $@
+
js/lib/id/ui/index.js: $(shell find modules/ui -type f)
@rm -f $@
node_modules/.bin/rollup -f umd -n iD modules/ui/ui.js --no-strict -o $@
diff --git a/index.html b/index.html
index 4b35d4120..88b53dfe9 100644
--- a/index.html
+++ b/index.html
@@ -37,6 +37,7 @@
+
diff --git a/js/lib/id/index.js b/js/lib/id/index.js
index 72ee1fda8..d73a15bd4 100644
--- a/js/lib/id/index.js
+++ b/js/lib/id/index.js
@@ -1571,17 +1571,12 @@
return difference;
}
-<<<<<<< HEAD
-<<<<<<< HEAD
-=======
-=======
function tagText(entity) {
return d3.entries(entity.tags).map(function(e) {
return e.key + '=' + e.value;
}).join(', ');
}
->>>>>>> 42ce4cf... external modules for util
function entitySelector(ids) {
return ids.length ? '.' + ids.join(',.') : 'nothing';
}
@@ -1692,9 +1687,6 @@
}
}
-<<<<<<< HEAD
->>>>>>> ef619c2... external modules for behavior
-=======
function editDistance(a, b) {
if (a.length === 0) return b.length;
if (b.length === 0) return a.length;
@@ -1731,7 +1723,6 @@
};
}
->>>>>>> 42ce4cf... external modules for util
/* eslint-disable no-proto */
var getPrototypeOf = Object.getPrototypeOf || function(obj) { return obj.__proto__; };
/* eslint-enable no-proto */
@@ -6505,2626 +6496,6 @@
UnrestrictTurn: UnrestrictTurn
});
-<<<<<<< HEAD
- exports.actions = actions;
- exports.geo = geo;
-=======
- function Areas(projection) {
- // Patterns only work in Firefox when set directly on element.
- // (This is not a bug: https://bugzilla.mozilla.org/show_bug.cgi?id=750632)
- var patterns = {
- wetland: 'wetland',
- beach: 'beach',
- scrub: 'scrub',
- construction: 'construction',
- military: 'construction',
- cemetery: 'cemetery',
- grave_yard: 'cemetery',
- meadow: 'meadow',
- farm: 'farmland',
- farmland: 'farmland',
- orchard: 'orchard'
- };
-
- var patternKeys = ['landuse', 'natural', 'amenity'];
-
- function setPattern(d) {
- for (var i = 0; i < patternKeys.length; i++) {
- if (patterns.hasOwnProperty(d.tags[patternKeys[i]])) {
- this.style.fill = this.style.stroke = 'url("#pattern-' + patterns[d.tags[patternKeys[i]]] + '")';
- return;
- }
- }
- this.style.fill = this.style.stroke = '';
- }
-
- return function drawAreas(surface, graph, entities, filter) {
- var path = Path(projection, graph, true),
- areas = {},
- multipolygon;
-
- for (var i = 0; i < entities.length; i++) {
- var entity = entities[i];
- if (entity.geometry(graph) !== 'area') continue;
-
- multipolygon = isSimpleMultipolygonOuterMember(entity, graph);
- if (multipolygon) {
- areas[multipolygon.id] = {
- entity: multipolygon.mergeTags(entity.tags),
- area: Math.abs(entity.area(graph))
- };
- } else if (!areas[entity.id]) {
- areas[entity.id] = {
- entity: entity,
- area: Math.abs(entity.area(graph))
- };
- }
- }
-
- areas = d3.values(areas).filter(function hasPath(a) { return path(a.entity); });
- areas.sort(function areaSort(a, b) { return b.area - a.area; });
- areas = _.map(areas, 'entity');
-
- var strokes = areas.filter(function(area) {
- return area.type === 'way';
- });
-
- var data = {
- clip: areas,
- shadow: strokes,
- stroke: strokes,
- fill: areas
- };
-
- var clipPaths = surface.selectAll('defs').selectAll('.clipPath')
- .filter(filter)
- .data(data.clip, Entity.key);
-
- clipPaths.enter()
- .append('clipPath')
- .attr('class', 'clipPath')
- .attr('id', function(entity) { return entity.id + '-clippath'; })
- .append('path');
-
- clipPaths.selectAll('path')
- .attr('d', path);
-
- clipPaths.exit()
- .remove();
-
- var areagroup = surface
- .selectAll('.layer-areas')
- .selectAll('g.areagroup')
- .data(['fill', 'shadow', 'stroke']);
-
- areagroup.enter()
- .append('g')
- .attr('class', function(d) { return 'layer areagroup area-' + d; });
-
- var paths = areagroup
- .selectAll('path')
- .filter(filter)
- .data(function(layer) { return data[layer]; }, Entity.key);
-
- // Remove exiting areas first, so they aren't included in the `fills`
- // array used for sorting below (https://github.com/openstreetmap/iD/issues/1903).
- paths.exit()
- .remove();
-
- var fills = surface.selectAll('.area-fill path.area')[0];
-
- var bisect = d3.bisector(function(node) {
- return -node.__data__.area(graph);
- }).left;
-
- function sortedByArea(entity) {
- if (this.__data__ === 'fill') {
- return fills[bisect(fills, -entity.area(graph))];
- }
- }
-
- paths.enter()
- .insert('path', sortedByArea)
- .each(function(entity) {
- var layer = this.parentNode.__data__;
-
- this.setAttribute('class', entity.type + ' area ' + layer + ' ' + entity.id);
-
- if (layer === 'fill') {
- this.setAttribute('clip-path', 'url(#' + entity.id + '-clippath)');
- setPattern.apply(this, arguments);
- }
- })
- .call(TagClasses());
-
- paths
- .attr('d', path);
- };
- }
-
- function Debug(projection, context) {
-
- function multipolygons(imagery) {
- return imagery.map(function(data) {
- return {
- type: 'MultiPolygon',
- coordinates: [ data.polygon ]
- };
- });
- }
-
- function drawDebug(surface) {
- var showsTile = context.getDebug('tile'),
- showsCollision = context.getDebug('collision'),
- showsImagery = context.getDebug('imagery'),
- showsImperial = context.getDebug('imperial'),
- showsDriveLeft = context.getDebug('driveLeft'),
- path = d3.geo.path().projection(projection);
-
-
- var debugData = [];
- if (showsTile) {
- debugData.push({ class: 'red', label: 'tile' });
- }
- if (showsCollision) {
- debugData.push({ class: 'yellow', label: 'collision' });
- }
- if (showsImagery) {
- debugData.push({ class: 'orange', label: 'imagery' });
- }
- if (showsImperial) {
- debugData.push({ class: 'cyan', label: 'imperial' });
- }
- if (showsDriveLeft) {
- debugData.push({ class: 'green', label: 'driveLeft' });
- }
-
-
- var legend = d3.select('#content')
- .selectAll('.debug-legend')
- .data(debugData.length ? [0] : []);
-
- legend.enter()
- .append('div')
- .attr('class', 'fillD debug-legend');
-
- legend.exit()
- .remove();
-
-
- var legendItems = legend.selectAll('.debug-legend-item')
- .data(debugData, function(d) { return d.label; });
-
- legendItems.enter()
- .append('span')
- .attr('class', function(d) { return 'debug-legend-item ' + d.class; })
- .text(function(d) { return d.label; });
-
- legendItems.exit()
- .remove();
-
-
- var layer = surface.selectAll('.layer-debug')
- .data(showsImagery || showsImperial || showsDriveLeft ? [0] : []);
-
- layer.enter()
- .append('g')
- .attr('class', 'layer-debug');
-
- layer.exit()
- .remove();
-
-
- var extent = context.map().extent(),
- availableImagery = showsImagery && multipolygons(iD.data.imagery.filter(function(source) {
- if (!source.polygon) return false;
- return source.polygon.some(function(polygon) {
- return polygonIntersectsPolygon(polygon, extent, true);
- });
- }));
-
- var imagery = layer.selectAll('path.debug-imagery')
- .data(showsImagery ? availableImagery : []);
-
- imagery.enter()
- .append('path')
- .attr('class', 'debug-imagery debug orange');
-
- imagery.exit()
- .remove();
-
-
- var imperial = layer
- .selectAll('path.debug-imperial')
- .data(showsImperial ? [iD.data.imperial] : []);
-
- imperial.enter()
- .append('path')
- .attr('class', 'debug-imperial debug cyan');
-
- imperial.exit()
- .remove();
-
-
- var driveLeft = layer
- .selectAll('path.debug-drive-left')
- .data(showsDriveLeft ? [iD.data.driveLeft] : []);
-
- driveLeft.enter()
- .append('path')
- .attr('class', 'debug-drive-left debug green');
-
- driveLeft.exit()
- .remove();
-
-
- // update
- layer.selectAll('path')
- .attr('d', path);
- }
-
- // This looks strange because `enabled` methods on other layers are
- // chainable getter/setters, and this one is just a getter.
- drawDebug.enabled = function() {
- if (!arguments.length) {
- return context.getDebug('tile') ||
- context.getDebug('collision') ||
- context.getDebug('imagery') ||
- context.getDebug('imperial') ||
- context.getDebug('driveLeft');
- } else {
- return this;
- }
- };
-
- return drawDebug;
- }
-
- /*
- A standalone SVG element that contains only a `defs` sub-element. To be
- used once globally, since defs IDs must be unique within a document.
- */
- function Defs(context) {
-
- function SVGSpriteDefinition(id, href) {
- return function(defs) {
- d3.xml(href, 'image/svg+xml', function(err, svg) {
- if (err) return;
- defs.node().appendChild(
- d3.select(svg.documentElement).attr('id', id).node()
- );
- });
- };
- }
-
- return function drawDefs(selection) {
- var defs = selection.append('defs');
-
- // marker
- defs.append('marker')
- .attr({
- id: 'oneway-marker',
- viewBox: '0 0 10 10',
- refY: 2.5,
- refX: 5,
- markerWidth: 2,
- markerHeight: 2,
- markerUnits: 'strokeWidth',
- orient: 'auto'
- })
- .append('path')
- .attr('class', 'oneway')
- .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('stroke', 'none')
- .attr('fill', '#000')
- .attr('opacity', '0.5');
-
- // patterns
- var patterns = defs.selectAll('pattern')
- .data([
- // pattern name, pattern image name
- ['wetland', 'wetland'],
- ['construction', 'construction'],
- ['cemetery', 'cemetery'],
- ['orchard', 'orchard'],
- ['farmland', 'farmland'],
- ['beach', 'dots'],
- ['scrub', 'dots'],
- ['meadow', 'dots']
- ])
- .enter()
- .append('pattern')
- .attr({
- id: function (d) {
- return 'pattern-' + d[0];
- },
- width: 32,
- height: 32,
- patternUnits: 'userSpaceOnUse'
- });
-
- patterns.append('rect')
- .attr({
- x: 0,
- y: 0,
- width: 32,
- height: 32,
- 'class': function (d) {
- return 'pattern-color-' + d[0];
- }
- });
-
- patterns.append('image')
- .attr({
- x: 0,
- y: 0,
- width: 32,
- height: 32
- })
- .attr('xlink:href', function (d) {
- return context.imagePath('pattern/' + d[1] + '.png');
- });
-
- // clip paths
- defs.selectAll()
- .data([12, 18, 20, 32, 45])
- .enter().append('clipPath')
- .attr('id', function (d) {
- return 'clip-square-' + d;
- })
- .append('rect')
- .attr('x', 0)
- .attr('y', 0)
- .attr('width', function (d) {
- return d;
- })
- .attr('height', function (d) {
- return d;
- });
-
- defs.call(SVGSpriteDefinition(
- 'iD-sprite',
- context.imagePath('iD-sprite.svg')));
-
- defs.call(SVGSpriteDefinition(
- 'maki-sprite',
- context.imagePath('maki-sprite.svg')));
- };
- }
-
- function Gpx(projection, context, dispatch) {
- var showLabels = true,
- layer;
-
- function init() {
- if (Gpx.initialized) return; // run once
-
- Gpx.geojson = {};
- Gpx.enabled = true;
-
- function over() {
- d3.event.stopPropagation();
- d3.event.preventDefault();
- d3.event.dataTransfer.dropEffect = 'copy';
- }
-
- d3.select('body')
- .attr('dropzone', 'copy')
- .on('drop.localgpx', function() {
- d3.event.stopPropagation();
- d3.event.preventDefault();
- if (!iD.detect().filedrop) return;
- drawGpx.files(d3.event.dataTransfer.files);
- })
- .on('dragenter.localgpx', over)
- .on('dragexit.localgpx', over)
- .on('dragover.localgpx', over);
-
- Gpx.initialized = true;
- }
-
-
- function drawGpx(surface) {
- var geojson = Gpx.geojson,
- enabled = Gpx.enabled;
-
- layer = surface.selectAll('.layer-gpx')
- .data(enabled ? [0] : []);
-
- layer.enter()
- .append('g')
- .attr('class', 'layer-gpx');
-
- layer.exit()
- .remove();
-
-
- var paths = layer
- .selectAll('path')
- .data([geojson]);
-
- paths.enter()
- .append('path')
- .attr('class', 'gpx');
-
- paths.exit()
- .remove();
-
- var path = d3.geo.path()
- .projection(projection);
-
- paths
- .attr('d', path);
-
-
- var labels = layer.selectAll('text')
- .data(showLabels && geojson.features ? geojson.features : []);
-
- labels.enter()
- .append('text')
- .attr('class', 'gpx');
-
- labels.exit()
- .remove();
-
- labels
- .text(function(d) {
- return d.properties.desc || d.properties.name;
- })
- .attr('x', function(d) {
- var centroid = path.centroid(d);
- return centroid[0] + 7;
- })
- .attr('y', function(d) {
- var centroid = path.centroid(d);
- return centroid[1];
- });
-
- }
-
- function toDom(x) {
- return (new DOMParser()).parseFromString(x, 'text/xml');
- }
-
- drawGpx.showLabels = function(_) {
- if (!arguments.length) return showLabels;
- showLabels = _;
- return this;
- };
-
- drawGpx.enabled = function(_) {
- if (!arguments.length) return Gpx.enabled;
- Gpx.enabled = _;
- dispatch.change();
- return this;
- };
-
- drawGpx.hasGpx = function() {
- var geojson = Gpx.geojson;
- return (!(_.isEmpty(geojson) || _.isEmpty(geojson.features)));
- };
-
- drawGpx.geojson = function(gj) {
- if (!arguments.length) return Gpx.geojson;
- if (_.isEmpty(gj) || _.isEmpty(gj.features)) return this;
- Gpx.geojson = gj;
- dispatch.change();
- return this;
- };
-
- drawGpx.url = function(url) {
- d3.text(url, function(err, data) {
- if (!err) {
- drawGpx.geojson(toGeoJSON.gpx(toDom(data)));
- }
- });
- return this;
- };
-
- drawGpx.files = function(fileList) {
- if (!fileList.length) return this;
- var f = fileList[0],
- reader = new FileReader();
-
- reader.onload = function(e) {
- drawGpx.geojson(toGeoJSON.gpx(toDom(e.target.result))).fitZoom();
- };
-
- reader.readAsText(f);
- return this;
- };
-
- drawGpx.fitZoom = function() {
- if (!this.hasGpx()) return this;
- var geojson = Gpx.geojson;
-
- var map = context.map(),
- viewport = map.trimmedExtent().polygon(),
- coords = _.reduce(geojson.features, function(coords, feature) {
- var c = feature.geometry.coordinates;
- return _.union(coords, feature.geometry.type === 'Point' ? [c] : c);
- }, []);
-
- if (!polygonIntersectsPolygon(viewport, coords, true)) {
- var extent = Extent(d3.geo.bounds(geojson));
- map.centerZoom(extent.center(), map.trimmedExtentZoom(extent));
- }
-
- return this;
- };
-
- init();
- return drawGpx;
- }
-
- function Icon(name, svgklass, useklass) {
- return function drawIcon(selection) {
- selection.selectAll('svg')
- .data([0])
- .enter()
- .append('svg')
- .attr('class', 'icon ' + (svgklass || ''))
- .append('use')
- .attr('xlink:href', name)
- .attr('class', useklass);
- };
- }
-
- function Labels(projection, context) {
- var path = d3.geo.path().projection(projection);
-
- // Replace with dict and iterate over entities tags instead?
- var label_stack = [
- ['line', 'aeroway'],
- ['line', 'highway'],
- ['line', 'railway'],
- ['line', 'waterway'],
- ['area', 'aeroway'],
- ['area', 'amenity'],
- ['area', 'building'],
- ['area', 'historic'],
- ['area', 'leisure'],
- ['area', 'man_made'],
- ['area', 'natural'],
- ['area', 'shop'],
- ['area', 'tourism'],
- ['point', 'aeroway'],
- ['point', 'amenity'],
- ['point', 'building'],
- ['point', 'historic'],
- ['point', 'leisure'],
- ['point', 'man_made'],
- ['point', 'natural'],
- ['point', 'shop'],
- ['point', 'tourism'],
- ['line', 'name'],
- ['area', 'name'],
- ['point', 'name']
- ];
-
- var default_size = 12;
-
- var font_sizes = label_stack.map(function(d) {
- var style = getStyle('text.' + d[0] + '.tag-' + d[1]),
- m = style && style.cssText.match('font-size: ([0-9]{1,2})px;');
- if (m) return parseInt(m[1], 10);
-
- style = getStyle('text.' + d[0]);
- m = style && style.cssText.match('font-size: ([0-9]{1,2})px;');
- if (m) return parseInt(m[1], 10);
-
- return default_size;
- });
-
- var iconSize = 18;
-
- var pointOffsets = [
- [15, -11, 'start'], // right
- [10, -11, 'start'], // unused right now
- [-15, -11, 'end']
- ];
-
- var lineOffsets = [50, 45, 55, 40, 60, 35, 65, 30, 70, 25,
- 75, 20, 80, 15, 95, 10, 90, 5, 95];
-
-
- var noIcons = ['building', 'landuse', 'natural'];
- function blacklisted(preset) {
- return _.some(noIcons, function(s) {
- return preset.id.indexOf(s) >= 0;
- });
- }
-
- function get(array, prop) {
- return function(d, i) { return array[i][prop]; };
- }
-
- var textWidthCache = {};
-
- 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 drawLineLabels(group, entities, filter, classes, labels) {
- var texts = group.selectAll('text.' + classes)
- .filter(filter)
- .data(entities, Entity.key);
-
- texts.enter()
- .append('text')
- .attr('class', function(d, i) { return classes + ' ' + labels[i].classes + ' ' + d.id; })
- .append('textPath')
- .attr('class', 'textpath');
-
-
- texts.selectAll('.textpath')
- .filter(filter)
- .data(entities, Entity.key)
- .attr({
- 'startOffset': '50%',
- 'xlink:href': function(d) { return '#labelpath-' + d.id; }
- })
- .text(displayName);
-
- texts.exit().remove();
- }
-
- function drawLinePaths(group, entities, filter, classes, labels) {
- var halos = group.selectAll('path')
- .filter(filter)
- .data(entities, Entity.key);
-
- halos.enter()
- .append('path')
- .style('stroke-width', get(labels, 'font-size'))
- .attr('id', function(d) { return 'labelpath-' + d.id; })
- .attr('class', classes);
-
- halos.attr('d', get(labels, 'lineString'));
-
- halos.exit().remove();
- }
-
- function drawPointLabels(group, entities, filter, classes, labels) {
- var texts = group.selectAll('text.' + classes)
- .filter(filter)
- .data(entities, Entity.key);
-
- texts.enter()
- .append('text')
- .attr('class', function(d, i) { return classes + ' ' + labels[i].classes + ' ' + d.id; });
-
- texts.attr('x', get(labels, 'x'))
- .attr('y', get(labels, 'y'))
- .style('text-anchor', get(labels, 'textAnchor'))
- .text(displayName)
- .each(function(d, i) { textWidth(displayName(d), labels[i].height, this); });
-
- texts.exit().remove();
- return texts;
- }
-
- function drawAreaLabels(group, entities, filter, classes, labels) {
- entities = entities.filter(hasText);
- labels = labels.filter(hasText);
- return drawPointLabels(group, entities, filter, classes, labels);
-
- function hasText(d, i) {
- return labels[i].hasOwnProperty('x') && labels[i].hasOwnProperty('y');
- }
- }
-
- function drawAreaIcons(group, entities, filter, classes, labels) {
- var icons = group.selectAll('use')
- .filter(filter)
- .data(entities, Entity.key);
-
- icons.enter()
- .append('use')
- .attr('class', 'icon areaicon')
- .attr('width', '18px')
- .attr('height', '18px');
-
- icons.attr('transform', get(labels, 'transform'))
- .attr('xlink:href', function(d) {
- var icon = context.presets().match(d, context.graph()).icon;
- return '#' + icon + (icon === 'hairdresser' ? '-24': '-18'); // workaround: maki hairdresser-18 broken?
- });
-
-
- icons.exit().remove();
- }
-
- function reverse(p) {
- var angle = Math.atan2(p[1][1] - p[0][1], p[1][0] - p[0][0]);
- return !(p[0][0] < p[p.length - 1][0] && angle < Math.PI/2 && angle > -Math.PI/2);
- }
-
- function lineString(nodes) {
- return 'M' + nodes.join('L');
- }
-
- function subpath(nodes, from, to) {
- function segmentLength(i) {
- var dx = nodes[i][0] - nodes[i + 1][0];
- var dy = nodes[i][1] - nodes[i + 1][1];
- return Math.sqrt(dx * dx + dy * dy);
- }
-
- var sofar = 0,
- start, end, i0, i1;
- for (var i = 0; i < nodes.length - 1; i++) {
- var current = segmentLength(i);
- var portion;
- if (!start && sofar + current >= from) {
- portion = (from - sofar) / current;
- start = [
- nodes[i][0] + portion * (nodes[i + 1][0] - nodes[i][0]),
- nodes[i][1] + portion * (nodes[i + 1][1] - nodes[i][1])
- ];
- i0 = i + 1;
- }
- if (!end && sofar + current >= to) {
- portion = (to - sofar) / current;
- end = [
- nodes[i][0] + portion * (nodes[i + 1][0] - nodes[i][0]),
- nodes[i][1] + portion * (nodes[i + 1][1] - nodes[i][1])
- ];
- i1 = i + 1;
- }
- sofar += current;
-
- }
- var ret = nodes.slice(i0, i1);
- ret.unshift(start);
- ret.push(end);
- return ret;
-
- }
-
- function hideOnMouseover() {
- var layers = d3.select(this)
- .selectAll('.layer-label, .layer-halo');
-
- layers.selectAll('.proximate')
- .classed('proximate', false);
-
- var mouse = context.mouse(),
- pad = 50,
- rect = [mouse[0] - pad, mouse[1] - pad, mouse[0] + pad, mouse[1] + pad],
- ids = _.map(rtree.search(rect), 'id');
-
- if (!ids.length) return;
- layers.selectAll('.' + ids.join(', .'))
- .classed('proximate', true);
- }
-
- var rtree = rbush(),
- rectangles = {};
-
- function drawLabels(surface, graph, entities, filter, dimensions, fullRedraw) {
- var hidePoints = !surface.selectAll('.node.point').node();
-
- var labelable = [], i, k, entity;
- for (i = 0; i < label_stack.length; i++) labelable.push([]);
-
- if (fullRedraw) {
- rtree.clear();
- rectangles = {};
- } else {
- for (i = 0; i < entities.length; i++) {
- rtree.remove(rectangles[entities[i].id]);
- }
- }
-
- // Split entities into groups specified by label_stack
- for (i = 0; i < entities.length; i++) {
- entity = entities[i];
- var geometry = entity.geometry(graph);
-
- if (geometry === 'vertex')
- continue;
- if (hidePoints && geometry === 'point')
- continue;
-
- var preset = geometry === 'area' && context.presets().match(entity, graph),
- icon = preset && !blacklisted(preset) && preset.icon;
-
- if (!icon && !displayName(entity))
- continue;
-
- for (k = 0; k < label_stack.length; k++) {
- if (geometry === label_stack[k][0] && entity.tags[label_stack[k][1]]) {
- labelable[k].push(entity);
- break;
- }
- }
- }
-
- var positions = {
- point: [],
- line: [],
- area: []
- };
-
- var labelled = {
- point: [],
- line: [],
- area: []
- };
-
- // Try and find a valid label for labellable entities
- for (k = 0; k < labelable.length; k++) {
- var font_size = font_sizes[k];
- for (i = 0; i < labelable[k].length; i++) {
- entity = labelable[k][i];
- var name = displayName(entity),
- width = name && textWidth(name, font_size),
- p;
- if (entity.geometry(graph) === 'point') {
- p = getPointLabel(entity, width, font_size);
- } else if (entity.geometry(graph) === 'line') {
- p = getLineLabel(entity, width, font_size);
- } else if (entity.geometry(graph) === 'area') {
- p = getAreaLabel(entity, width, font_size);
- }
- if (p) {
- p.classes = entity.geometry(graph) + ' tag-' + label_stack[k][1];
- positions[entity.geometry(graph)].push(p);
- labelled[entity.geometry(graph)].push(entity);
- }
- }
- }
-
- function getPointLabel(entity, width, height) {
- var coord = projection(entity.loc),
- m = 5, // margin
- offset = pointOffsets[0],
- p = {
- height: height,
- width: width,
- x: coord[0] + offset[0],
- y: coord[1] + offset[1],
- textAnchor: offset[2]
- };
- var rect = [p.x - m, p.y - m, p.x + width + m, p.y + height + m];
- if (tryInsert(rect, entity.id)) return p;
- }
-
-
- function getLineLabel(entity, width, height) {
- var nodes = _.map(graph.childNodes(entity), 'loc').map(projection),
- length = pathLength(nodes);
- if (length < width + 20) return;
-
- for (var i = 0; i < lineOffsets.length; i++) {
- var offset = lineOffsets[i],
- middle = offset / 100 * length,
- start = middle - width/2;
- if (start < 0 || start + width > length) continue;
- var sub = subpath(nodes, start, start + width),
- rev = reverse(sub),
- rect = [
- Math.min(sub[0][0], sub[sub.length - 1][0]) - 10,
- Math.min(sub[0][1], sub[sub.length - 1][1]) - 10,
- Math.max(sub[0][0], sub[sub.length - 1][0]) + 20,
- Math.max(sub[0][1], sub[sub.length - 1][1]) + 30
- ];
- if (rev) sub = sub.reverse();
- if (tryInsert(rect, entity.id)) return {
- 'font-size': height + 2,
- lineString: lineString(sub),
- startOffset: offset + '%'
- };
- }
- }
-
- function getAreaLabel(entity, width, height) {
- var centroid = path.centroid(entity.asGeoJSON(graph, true)),
- extent = entity.extent(graph),
- entitywidth = projection(extent[1])[0] - projection(extent[0])[0],
- rect;
-
- if (isNaN(centroid[0]) || entitywidth < 20) return;
-
- var iconX = centroid[0] - (iconSize/2),
- iconY = centroid[1] - (iconSize/2),
- textOffset = iconSize + 5;
-
- var p = {
- transform: 'translate(' + iconX + ',' + iconY + ')'
- };
-
- if (width && entitywidth >= width + 20) {
- p.x = centroid[0];
- p.y = centroid[1] + textOffset;
- p.textAnchor = 'middle';
- p.height = height;
- rect = [p.x - width/2, p.y, p.x + width/2, p.y + height + textOffset];
- } else {
- rect = [iconX, iconY, iconX + iconSize, iconY + iconSize];
- }
-
- if (tryInsert(rect, entity.id)) return p;
-
- }
-
- function tryInsert(rect, id) {
- // Check that label is visible
- if (rect[0] < 0 || rect[1] < 0 || rect[2] > dimensions[0] ||
- rect[3] > dimensions[1]) return false;
- var v = rtree.search(rect).length === 0;
- if (v) {
- rect.id = id;
- rtree.insert(rect);
- rectangles[id] = rect;
- }
- return v;
- }
-
- var label = surface.selectAll('.layer-label'),
- halo = surface.selectAll('.layer-halo');
-
- // points
- drawPointLabels(label, labelled.point, filter, 'pointlabel', positions.point);
- drawPointLabels(halo, labelled.point, filter, 'pointlabel-halo', positions.point);
-
- // lines
- drawLinePaths(halo, labelled.line, filter, '', positions.line);
- drawLineLabels(label, labelled.line, filter, 'linelabel', positions.line);
- drawLineLabels(halo, labelled.line, filter, 'linelabel-halo', positions.line);
-
- // areas
- drawAreaLabels(label, labelled.area, filter, 'arealabel', positions.area);
- drawAreaLabels(halo, labelled.area, filter, 'arealabel-halo', positions.area);
- drawAreaIcons(label, labelled.area, filter, 'arealabel-icon', positions.area);
-
- // debug
- var showDebug = context.getDebug('collision');
- var debug = label.selectAll('.layer-label-debug')
- .data(showDebug ? [true] : []);
-
- debug.enter()
- .append('g')
- .attr('class', 'layer-label-debug');
-
- debug.exit()
- .remove();
-
- if (showDebug) {
- var gj = rtree.all().map(function(d) {
- return { type: 'Polygon', coordinates: [[
- [d[0], d[1]],
- [d[2], d[1]],
- [d[2], d[3]],
- [d[0], d[3]],
- [d[0], d[1]]
- ]]};
- });
-
- var debugboxes = debug.selectAll('.debug').data(gj);
-
- debugboxes.enter()
- .append('path')
- .attr('class', 'debug yellow');
-
- debugboxes.exit()
- .remove();
-
- debugboxes
- .attr('d', d3.geo.path().projection(null));
- }
- }
-
- drawLabels.supersurface = function(supersurface) {
- supersurface
- .on('mousemove.hidelabels', hideOnMouseover)
- .on('mousedown.hidelabels', function () {
- supersurface.on('mousemove.hidelabels', null);
- })
- .on('mouseup.hidelabels', function () {
- supersurface.on('mousemove.hidelabels', hideOnMouseover);
- });
- };
-
- return drawLabels;
- }
-
- function Layers(projection, context) {
- var dispatch = d3.dispatch('change'),
- svg = d3.select(null),
- layers = [
- { id: 'osm', layer: Osm(projection, context, dispatch) },
- { id: 'gpx', layer: Gpx(projection, context, dispatch) },
- { id: 'mapillary-images', layer: MapillaryImages(projection, context, dispatch) },
- { id: 'mapillary-signs', layer: MapillarySigns(projection, context, dispatch) },
- { id: 'debug', layer: Debug(projection, context, dispatch) }
- ];
-
-
- function drawLayers(selection) {
- svg = selection.selectAll('.surface')
- .data([0]);
-
- svg.enter()
- .append('svg')
- .attr('class', 'surface')
- .append('defs');
-
- var groups = svg.selectAll('.data-layer')
- .data(layers);
-
- groups.enter()
- .append('g')
- .attr('class', function(d) { return 'data-layer data-layer-' + d.id; });
-
- groups
- .each(function(d) { d3.select(this).call(d.layer); });
-
- groups.exit()
- .remove();
- }
-
- drawLayers.all = function() {
- return layers;
- };
-
- drawLayers.layer = function(id) {
- var obj = _.find(layers, function(o) {return o.id === id;});
- return obj && obj.layer;
- };
-
- drawLayers.only = function(what) {
- var arr = [].concat(what);
- drawLayers.remove(_.difference(_.map(layers, 'id'), arr));
- return this;
- };
-
- drawLayers.remove = function(what) {
- var arr = [].concat(what);
- arr.forEach(function(id) {
- layers = _.reject(layers, function(o) {return o.id === id;});
- });
- dispatch.change();
- return this;
- };
-
- drawLayers.add = function(what) {
- var arr = [].concat(what);
- arr.forEach(function(obj) {
- if ('id' in obj && 'layer' in obj) {
- layers.push(obj);
- }
- });
- dispatch.change();
- return this;
- };
-
- drawLayers.dimensions = function(_) {
- if (!arguments.length) return svg.dimensions();
- svg.dimensions(_);
- layers.forEach(function(obj) {
- if (obj.layer.dimensions) {
- obj.layer.dimensions(_);
- }
- });
- return this;
- };
-
-
- return d3.rebind(drawLayers, dispatch, 'on');
- }
-
- function Lines(projection) {
-
- var highway_stack = {
- motorway: 0,
- motorway_link: 1,
- trunk: 2,
- trunk_link: 3,
- primary: 4,
- primary_link: 5,
- secondary: 6,
- tertiary: 7,
- unclassified: 8,
- residential: 9,
- service: 10,
- footway: 11
- };
-
- function waystack(a, b) {
- var as = 0, bs = 0;
-
- if (a.tags.highway) { as -= highway_stack[a.tags.highway]; }
- if (b.tags.highway) { bs -= highway_stack[b.tags.highway]; }
- return as - bs;
- }
-
- return function drawLines(surface, graph, entities, filter) {
- var ways = [], pathdata = {}, onewaydata = {},
- getPath = Path(projection, graph);
-
- for (var i = 0; i < entities.length; i++) {
- var entity = entities[i],
- outer = simpleMultipolygonOuterMember(entity, graph);
- if (outer) {
- ways.push(entity.mergeTags(outer.tags));
- } else if (entity.geometry(graph) === 'line') {
- ways.push(entity);
- }
- }
-
- ways = ways.filter(getPath);
-
- pathdata = _.groupBy(ways, function(way) { return way.layer(); });
-
- _.forOwn(pathdata, function(v, k) {
- onewaydata[k] = _(v)
- .filter(function(d) { return d.isOneWay(); })
- .map(OneWaySegments(projection, graph, 35))
- .flatten()
- .valueOf();
- });
-
- var layergroup = surface
- .selectAll('.layer-lines')
- .selectAll('g.layergroup')
- .data(d3.range(-10, 11));
-
- layergroup.enter()
- .append('g')
- .attr('class', function(d) { return 'layer layergroup layer' + String(d); });
-
-
- var linegroup = layergroup
- .selectAll('g.linegroup')
- .data(['shadow', 'casing', 'stroke']);
-
- linegroup.enter()
- .append('g')
- .attr('class', function(d) { return 'layer linegroup line-' + d; });
-
-
- var lines = linegroup
- .selectAll('path')
- .filter(filter)
- .data(
- function() { return pathdata[this.parentNode.parentNode.__data__] || []; },
- Entity.key
- );
-
- // Optimization: call simple TagClasses only on enter selection. This
- // works because Entity.key is defined to include the entity v attribute.
- lines.enter()
- .append('path')
- .attr('class', function(d) { return 'way line ' + this.parentNode.__data__ + ' ' + d.id; })
- .call(TagClasses());
-
- lines
- .sort(waystack)
- .attr('d', getPath)
- .call(TagClasses().tags(RelationMemberTags(graph)));
-
- lines.exit()
- .remove();
-
-
- var onewaygroup = layergroup
- .selectAll('g.onewaygroup')
- .data(['oneway']);
-
- onewaygroup.enter()
- .append('g')
- .attr('class', 'layer onewaygroup');
-
-
- var oneways = onewaygroup
- .selectAll('path')
- .filter(filter)
- .data(
- function() { return onewaydata[this.parentNode.parentNode.__data__] || []; },
- function(d) { return [d.id, d.index]; }
- );
-
- oneways.enter()
- .append('path')
- .attr('class', 'oneway')
- .attr('marker-mid', 'url(#oneway-marker)');
-
- oneways
- .attr('d', function(d) { return d.d; });
-
- if (iD.detect().ie) {
- oneways.each(function() { this.parentNode.insertBefore(this, this); });
- }
-
- oneways.exit()
- .remove();
-
- };
- }
-
- function mapillary() {
- var mapillary = {},
- apibase = 'https://a.mapillary.com/v2/',
- viewercss = 'https://npmcdn.com/mapillary-js@1.3.0/dist/mapillary-js.min.css',
- viewerjs = 'https://npmcdn.com/mapillary-js@1.3.0/dist/mapillary-js.min.js',
- clientId = 'NzNRM2otQkR2SHJzaXJmNmdQWVQ0dzo1ZWYyMmYwNjdmNDdlNmVi',
- maxResults = 1000,
- maxPages = 10,
- tileZoom = 14,
- dispatch = d3.dispatch('loadedImages', 'loadedSigns');
-
-
- function loadSignStyles(context) {
- d3.select('head').selectAll('#traffico')
- .data([0])
- .enter()
- .append('link')
- .attr('id', 'traffico')
- .attr('rel', 'stylesheet')
- .attr('href', context.asset('traffico/stylesheets/traffico.css'));
- }
-
- function loadSignDefs(context) {
- if (iD.services.mapillary.sign_defs) return;
- iD.services.mapillary.sign_defs = {};
-
- _.each(['au', 'br', 'ca', 'de', 'us'], function(region) {
- d3.json(context.asset('traffico/string-maps/' + region + '-map.json'), function(err, data) {
- if (err) return;
- if (region === 'de') region = 'eu';
- iD.services.mapillary.sign_defs[region] = data;
- });
- });
- }
-
- function loadViewer() {
- // mapillary-wrap
- var wrap = d3.select('#content').selectAll('.mapillary-wrap')
- .data([0]);
-
- var enter = wrap.enter().append('div')
- .attr('class', 'mapillary-wrap')
- .classed('al', true) // 'al'=left, 'ar'=right
- .classed('hidden', true);
-
- enter.append('button')
- .attr('class', 'thumb-hide')
- .on('click', function () { mapillary.hideViewer(); })
- .append('div')
- .call(iD.svg.Icon('#icon-close'));
-
- enter.append('div')
- .attr('id', 'mly')
- .attr('class', 'mly-wrapper')
- .classed('active', false);
-
- // mapillary-viewercss
- d3.select('head').selectAll('#mapillary-viewercss')
- .data([0])
- .enter()
- .append('link')
- .attr('id', 'mapillary-viewercss')
- .attr('rel', 'stylesheet')
- .attr('href', viewercss);
-
- // mapillary-viewerjs
- d3.select('head').selectAll('#mapillary-viewerjs')
- .data([0])
- .enter()
- .append('script')
- .attr('id', 'mapillary-viewerjs')
- .attr('src', viewerjs);
- }
-
- function initViewer(imageKey, context) {
-
- function nodeChanged(d) {
- var clicks = iD.services.mapillary.clicks;
- var index = clicks.indexOf(d.key);
- if (index > -1) { // nodechange initiated from clicking on a marker..
- clicks.splice(index, 1);
- } else { // nodechange initiated from the Mapillary viewer controls..
- var loc = d.apiNavImIm ? [d.apiNavImIm.lon, d.apiNavImIm.lat] : [d.latLon.lon, d.latLon.lat];
- context.map().centerEase(loc);
- mapillary.setSelectedImage(d.key, false);
- }
- }
-
- if (Mapillary && imageKey) {
- var opts = {
- baseImageSize: 320,
- cover: false,
- cache: true,
- debug: false,
- imagePlane: true,
- loading: true,
- sequence: true
- };
-
- var viewer = new Mapillary.Viewer('mly', clientId, imageKey, opts);
- viewer.on('nodechanged', nodeChanged);
- viewer.on('loadingchanged', mapillary.setViewerLoading);
- iD.services.mapillary.viewer = viewer;
- }
- }
-
- function abortRequest(i) {
- i.abort();
- }
-
- function nearNullIsland(x, y, z) {
- if (z >= 7) {
- var center = Math.pow(2, z - 1),
- width = Math.pow(2, z - 6),
- min = center - (width / 2),
- max = center + (width / 2) - 1;
- return x >= min && x <= max && y >= min && y <= max;
- }
- return false;
- }
-
- function getTiles(projection, dimensions) {
- var s = projection.scale() * 2 * Math.PI,
- z = Math.max(Math.log(s) / Math.log(2) - 8, 0),
- ts = 256 * Math.pow(2, z - tileZoom),
- origin = [
- s / 2 - projection.translate()[0],
- s / 2 - projection.translate()[1]];
-
- return d3.geo.tile()
- .scaleExtent([tileZoom, tileZoom])
- .scale(s)
- .size(dimensions)
- .translate(projection.translate())()
- .map(function(tile) {
- var x = tile[0] * ts - origin[0],
- y = tile[1] * ts - origin[1];
-
- return {
- id: tile.toString(),
- extent: iD.geo.Extent(
- projection.invert([x, y + ts]),
- projection.invert([x + ts, y]))
- };
- });
- }
-
-
- function loadTiles(which, url, projection, dimensions) {
- var tiles = getTiles(projection, dimensions).filter(function(t) {
- var xyz = t.id.split(',');
- return !nearNullIsland(xyz[0], xyz[1], xyz[2]);
- });
-
- _.filter(which.inflight, function(v, k) {
- var wanted = _.find(tiles, function(tile) { return k === (tile.id + ',0'); });
- if (!wanted) delete which.inflight[k];
- return !wanted;
- }).map(abortRequest);
-
- tiles.forEach(function(tile) {
- loadTilePage(which, url, tile, 0);
- });
- }
-
- function loadTilePage(which, url, tile, page) {
- var cache = iD.services.mapillary.cache[which],
- id = tile.id + ',' + String(page),
- rect = tile.extent.rectangle();
-
- if (cache.loaded[id] || cache.inflight[id]) return;
-
- cache.inflight[id] = d3.json(url +
- iD.util.qsString({
- geojson: 'true',
- limit: maxResults,
- page: page,
- client_id: clientId,
- min_lon: rect[0],
- min_lat: rect[1],
- max_lon: rect[2],
- max_lat: rect[3]
- }), function(err, data) {
- cache.loaded[id] = true;
- delete cache.inflight[id];
- if (err || !data.features || !data.features.length) return;
-
- var features = [],
- nextPage = page + 1,
- feature, loc, d;
-
- for (var i = 0; i < data.features.length; i++) {
- feature = data.features[i];
- loc = feature.geometry.coordinates;
- d = { key: feature.properties.key, loc: loc };
- if (which === 'images') d.ca = feature.properties.ca;
- if (which === 'signs') d.signs = feature.properties.rects;
-
- features.push([loc[0], loc[1], loc[0], loc[1], d]);
- }
-
- cache.rtree.load(features);
-
- if (which === 'images') dispatch.loadedImages();
- if (which === 'signs') dispatch.loadedSigns();
-
- if (data.features.length === maxResults && nextPage < maxPages) {
- loadTilePage(which, url, tile, nextPage);
- }
- }
- );
- }
-
- mapillary.loadImages = function(projection, dimensions) {
- var url = apibase + 'search/im/geojson?';
- loadTiles('images', url, projection, dimensions);
- };
-
- mapillary.loadSigns = function(context, projection, dimensions) {
- var url = apibase + 'search/im/geojson/or?';
- loadSignStyles(context);
- loadSignDefs(context);
- loadTiles('signs', url, projection, dimensions);
- };
-
- mapillary.loadViewer = function() {
- loadViewer();
- };
-
-
- // partition viewport into `psize` x `psize` regions
- function partitionViewport(psize, projection, dimensions) {
- psize = psize || 16;
- var cols = d3.range(0, dimensions[0], psize),
- rows = d3.range(0, dimensions[1], psize),
- partitions = [];
-
- rows.forEach(function(y) {
- cols.forEach(function(x) {
- var min = [x, y + psize],
- max = [x + psize, y];
- partitions.push(
- iD.geo.Extent(projection.invert(min), projection.invert(max)));
- });
- });
-
- return partitions;
- }
-
- // no more than `limit` results per partition.
- function searchLimited(psize, limit, projection, dimensions, rtree) {
- limit = limit || 3;
-
- var partitions = partitionViewport(psize, projection, dimensions);
- return _.flatten(_.compact(_.map(partitions, function(extent) {
- return rtree.search(extent.rectangle())
- .slice(0, limit)
- .map(function(d) { return d[4]; });
- })));
- }
-
- mapillary.images = function(projection, dimensions) {
- var psize = 16, limit = 3;
- return searchLimited(psize, limit, projection, dimensions, iD.services.mapillary.cache.images.rtree);
- };
-
- mapillary.signs = function(projection, dimensions) {
- var psize = 32, limit = 3;
- return searchLimited(psize, limit, projection, dimensions, iD.services.mapillary.cache.signs.rtree);
- };
-
- mapillary.signsSupported = function() {
- var detected = iD.detect();
- return (!(detected.ie || detected.browser.toLowerCase() === 'safari'));
- };
-
- mapillary.signHTML = function(d) {
- if (!iD.services.mapillary.sign_defs) return;
-
- var detectionPackage = d.signs[0].package,
- type = d.signs[0].type,
- country = detectionPackage.split('_')[1];
-
- return iD.services.mapillary.sign_defs[country][type];
- };
-
- mapillary.showViewer = function() {
- d3.select('#content')
- .selectAll('.mapillary-wrap')
- .classed('hidden', false)
- .selectAll('.mly-wrapper')
- .classed('active', true);
-
- return mapillary;
- };
-
- mapillary.hideViewer = function() {
- d3.select('#content')
- .selectAll('.mapillary-wrap')
- .classed('hidden', true)
- .selectAll('.mly-wrapper')
- .classed('active', false);
-
- d3.selectAll('.layer-mapillary-images .viewfield-group, .layer-mapillary-signs .icon-sign')
- .classed('selected', false);
-
- iD.services.mapillary.image = null;
-
- return mapillary;
- };
-
- mapillary.setViewerLoading = function(loading) {
- var canvas = d3.select('#content')
- .selectAll('.mly-wrapper canvas');
-
- if (canvas.empty()) return; // viewer not loaded yet
-
- var cover = d3.select('#content')
- .selectAll('.mly-wrapper .Cover');
-
- cover.classed('CoverDone', !loading);
-
- var button = cover.selectAll('.CoverButton')
- .data(loading ? [0] : []);
-
- button.enter()
- .append('div')
- .attr('class', 'CoverButton')
- .append('div')
- .attr('class', 'uil-ripple-css')
- .append('div');
-
- button.exit()
- .remove();
-
- return mapillary;
- };
-
- mapillary.updateViewer = function(imageKey, context) {
- if (!iD.services.mapillary) return;
- if (!imageKey) return;
-
- if (!iD.services.mapillary.viewer) {
- initViewer(imageKey, context);
- } else {
- iD.services.mapillary.viewer.moveToKey(imageKey);
- }
-
- return mapillary;
- };
-
- mapillary.getSelectedImage = function() {
- if (!iD.services.mapillary) return null;
- return iD.services.mapillary.image;
- };
-
- mapillary.setSelectedImage = function(imageKey, fromClick) {
- if (!iD.services.mapillary) return null;
-
- iD.services.mapillary.image = imageKey;
- if (fromClick) {
- iD.services.mapillary.clicks.push(imageKey);
- }
-
- d3.selectAll('.layer-mapillary-images .viewfield-group, .layer-mapillary-signs .icon-sign')
- .classed('selected', function(d) { return d.key === imageKey; });
-
- return mapillary;
- };
-
- mapillary.reset = function() {
- var cache = iD.services.mapillary.cache;
-
- if (cache) {
- _.forEach(cache.images.inflight, abortRequest);
- _.forEach(cache.signs.inflight, abortRequest);
- }
-
- iD.services.mapillary.cache = {
- images: { inflight: {}, loaded: {}, rtree: rbush() },
- signs: { inflight: {}, loaded: {}, rtree: rbush() }
- };
-
- iD.services.mapillary.image = null;
- iD.services.mapillary.clicks = [];
-
- return mapillary;
- };
-
-
- if (!iD.services.mapillary.cache) {
- mapillary.reset();
- }
-
- return d3.rebind(mapillary, dispatch, 'on');
- }
-
- function MapillaryImages(projection, context, dispatch) {
- var debouncedRedraw = _.debounce(function () { dispatch.change(); }, 1000),
- minZoom = 12,
- layer = d3.select(null),
- _mapillary;
-
-
- function init() {
- if (MapillaryImages.initialized) return; // run once
- MapillaryImages.enabled = false;
- MapillaryImages.initialized = true;
- }
-
- function getMapillary() {
- if (mapillary && !_mapillary) {
- _mapillary = mapillary();
- _mapillary.on('loadedImages', debouncedRedraw);
- } else if (!mapillary && _mapillary) {
- _mapillary = null;
- }
-
- return _mapillary;
- }
-
- function showLayer() {
- var mapillary = getMapillary();
- if (!mapillary) return;
-
- mapillary.loadViewer();
- editOn();
-
- layer
- .style('opacity', 0)
- .transition()
- .duration(500)
- .style('opacity', 1)
- .each('end', debouncedRedraw);
- }
-
- function hideLayer() {
- var mapillary = getMapillary();
- if (mapillary) {
- mapillary.hideViewer();
- }
-
- debouncedRedraw.cancel();
-
- layer
- .transition()
- .duration(500)
- .style('opacity', 0)
- .each('end', editOff);
- }
-
- function editOn() {
- layer.style('display', 'block');
- }
-
- function editOff() {
- layer.selectAll('.viewfield-group').remove();
- layer.style('display', 'none');
- }
-
- function click(d) {
- var mapillary = getMapillary();
- if (!mapillary) return;
-
- context.map().centerEase(d.loc);
-
- mapillary
- .setSelectedImage(d.key, true)
- .updateViewer(d.key, context)
- .showViewer();
- }
-
- function transform(d) {
- var t = PointTransform(projection)(d);
- if (d.ca) t += ' rotate(' + Math.floor(d.ca) + ',0,0)';
- return t;
- }
-
- function update() {
- var mapillary = getMapillary(),
- data = (mapillary ? mapillary.images(projection, layer.dimensions()) : []),
- imageKey = mapillary ? mapillary.getSelectedImage() : null;
-
- var markers = layer.selectAll('.viewfield-group')
- .data(data, function(d) { return d.key; });
-
- // Enter
- var enter = markers.enter()
- .append('g')
- .attr('class', 'viewfield-group')
- .classed('selected', function(d) { return d.key === imageKey; })
- .on('click', click);
-
- enter.append('path')
- .attr('class', 'viewfield')
- .attr('transform', 'scale(1.5,1.5),translate(-8, -13)')
- .attr('d', 'M 6,9 C 8,8.4 8,8.4 10,9 L 16,-2 C 12,-5 4,-5 0,-2 z');
-
- enter.append('circle')
- .attr('dx', '0')
- .attr('dy', '0')
- .attr('r', '6');
-
- // Exit
- markers.exit()
- .remove();
-
- // Update
- markers
- .attr('transform', transform);
- }
-
- function drawImages(selection) {
- var enabled = MapillaryImages.enabled,
- mapillary = getMapillary();
-
- layer = selection.selectAll('.layer-mapillary-images')
- .data(mapillary ? [0] : []);
-
- layer.enter()
- .append('g')
- .attr('class', 'layer-mapillary-images')
- .style('display', enabled ? 'block' : 'none');
-
- layer.exit()
- .remove();
-
- if (enabled) {
- if (mapillary && ~~context.map().zoom() >= minZoom) {
- editOn();
- update();
- mapillary.loadImages(projection, layer.dimensions());
- } else {
- editOff();
- }
- }
- }
-
- drawImages.enabled = function(_) {
- if (!arguments.length) return MapillaryImages.enabled;
- MapillaryImages.enabled = _;
- if (MapillaryImages.enabled) {
- showLayer();
- } else {
- hideLayer();
- }
- dispatch.change();
- return this;
- };
-
- drawImages.supported = function() {
- return !!getMapillary();
- };
-
- drawImages.dimensions = function(_) {
- if (!arguments.length) return layer.dimensions();
- layer.dimensions(_);
- return this;
- };
-
- init();
- return drawImages;
- }
-
- function MapillarySigns(projection, context, dispatch) {
- var debouncedRedraw = _.debounce(function () { dispatch.change(); }, 1000),
- minZoom = 12,
- layer = d3.select(null),
- _mapillary;
-
-
- function init() {
- if (MapillarySigns.initialized) return; // run once
- MapillarySigns.enabled = false;
- MapillarySigns.initialized = true;
- }
-
- function getMapillary() {
- if (mapillary && !_mapillary) {
- _mapillary = mapillary().on('loadedSigns', debouncedRedraw);
- } else if (!mapillary && _mapillary) {
- _mapillary = null;
- }
- return _mapillary;
- }
-
- function showLayer() {
- editOn();
- debouncedRedraw();
- }
-
- function hideLayer() {
- debouncedRedraw.cancel();
- editOff();
- }
-
- function editOn() {
- layer.style('display', 'block');
- }
-
- function editOff() {
- layer.selectAll('.icon-sign').remove();
- layer.style('display', 'none');
- }
-
- function click(d) {
- var mapillary = getMapillary();
- if (!mapillary) return;
-
- context.map().centerEase(d.loc);
-
- mapillary
- .setSelectedImage(d.key, true)
- .updateViewer(d.key, context)
- .showViewer();
- }
-
- function update() {
- var mapillary = getMapillary(),
- data = (mapillary ? mapillary.signs(projection, layer.dimensions()) : []),
- imageKey = mapillary ? mapillary.getSelectedImage() : null;
-
- var signs = layer.selectAll('.icon-sign')
- .data(data, function(d) { return d.key; });
-
- // Enter
- var enter = signs.enter()
- .append('foreignObject')
- .attr('class', 'icon-sign')
- .attr('width', '32px') // for Firefox
- .attr('height', '32px') // for Firefox
- .classed('selected', function(d) { return d.key === imageKey; })
- .on('click', click);
-
- enter
- .append('xhtml:body')
- .html(mapillary.signHTML);
-
- // Exit
- signs.exit()
- .remove();
-
- // Update
- signs
- .attr('transform', PointTransform(projection));
- }
-
- function drawSigns(selection) {
- var enabled = MapillarySigns.enabled,
- mapillary = getMapillary();
-
- layer = selection.selectAll('.layer-mapillary-signs')
- .data(mapillary ? [0] : []);
-
- layer.enter()
- .append('g')
- .attr('class', 'layer-mapillary-signs')
- .style('display', enabled ? 'block' : 'none')
- .attr('transform', 'translate(-16, -16)'); // center signs on loc
-
- layer.exit()
- .remove();
-
- if (enabled) {
- if (mapillary && ~~context.map().zoom() >= minZoom) {
- editOn();
- update();
- mapillary.loadSigns(context, projection, layer.dimensions());
- } else {
- editOff();
- }
- }
- }
-
- drawSigns.enabled = function(_) {
- if (!arguments.length) return MapillarySigns.enabled;
- MapillarySigns.enabled = _;
- if (MapillarySigns.enabled) {
- showLayer();
- } else {
- hideLayer();
- }
- dispatch.change();
- return this;
- };
-
- drawSigns.supported = function() {
- var mapillary = getMapillary();
- return (mapillary && mapillary.signsSupported());
- };
-
- drawSigns.dimensions = function(_) {
- if (!arguments.length) return layer.dimensions();
- layer.dimensions(_);
- return this;
- };
-
- init();
- return drawSigns;
- }
-
- function Midpoints(projection, context) {
- return function drawMidpoints(surface, graph, entities, filter, extent) {
- var poly = extent.polygon(),
- midpoints = {};
-
- for (var i = 0; i < entities.length; i++) {
- var entity = entities[i];
-
- if (entity.type !== 'way')
- continue;
- if (!filter(entity))
- continue;
- if (context.selectedIDs().indexOf(entity.id) < 0)
- continue;
-
- var nodes = graph.childNodes(entity);
- for (var j = 0; j < nodes.length - 1; j++) {
-
- var a = nodes[j],
- b = nodes[j + 1],
- id = [a.id, b.id].sort().join('-');
-
- if (midpoints[id]) {
- midpoints[id].parents.push(entity);
- } else {
- if (euclideanDistance(projection(a.loc), projection(b.loc)) > 40) {
- var point = interp(a.loc, b.loc, 0.5),
- loc = null;
-
- if (extent.intersects(point)) {
- loc = point;
- } else {
- for (var k = 0; k < 4; k++) {
- point = lineIntersection([a.loc, b.loc], [poly[k], poly[k+1]]);
- if (point &&
- euclideanDistance(projection(a.loc), projection(point)) > 20 &&
- euclideanDistance(projection(b.loc), projection(point)) > 20)
- {
- loc = point;
- break;
- }
- }
- }
-
- if (loc) {
- midpoints[id] = {
- type: 'midpoint',
- id: id,
- loc: loc,
- edge: [a.id, b.id],
- parents: [entity]
- };
- }
- }
- }
- }
- }
-
- function midpointFilter(d) {
- if (midpoints[d.id])
- return true;
-
- for (var i = 0; i < d.parents.length; i++)
- if (filter(d.parents[i]))
- return true;
-
- return false;
- }
-
- var groups = surface.selectAll('.layer-hit').selectAll('g.midpoint')
- .filter(midpointFilter)
- .data(_.values(midpoints), function(d) { return d.id; });
-
- var enter = groups.enter()
- .insert('g', ':first-child')
- .attr('class', 'midpoint');
-
- enter.append('polygon')
- .attr('points', '-6,8 10,0 -6,-8')
- .attr('class', 'shadow');
-
- enter.append('polygon')
- .attr('points', '-3,4 5,0 -3,-4')
- .attr('class', 'fill');
-
- groups
- .attr('transform', function(d) {
- var translate = PointTransform(projection),
- a = context.entity(d.edge[0]),
- b = context.entity(d.edge[1]),
- angle$$ = Math.round(angle(a, b, projection) * (180 / Math.PI));
- return translate(d) + ' rotate(' + angle$$ + ')';
- })
- .call(TagClasses().tags(
- function(d) { return d.parents[0].tags; }
- ));
-
- // Propagate data bindings.
- groups.select('polygon.shadow');
- groups.select('polygon.fill');
-
- groups.exit()
- .remove();
- };
- }
-
- function OneWaySegments(projection, graph, dt) {
- return function(entity) {
- var a,
- b,
- i = 0,
- offset = dt,
- segments = [],
- clip = d3.geo.clipExtent().extent(projection.clipExtent()).stream,
- coordinates = graph.childNodes(entity).map(function(n) {
- return n.loc;
- });
-
- if (entity.tags.oneway === '-1') coordinates.reverse();
-
- d3.geo.stream({
- type: 'LineString',
- coordinates: coordinates
- }, projection.stream(clip({
- lineStart: function() {},
- lineEnd: function() {
- a = null;
- },
- point: function(x, y) {
- b = [x, y];
-
- if (a) {
- var span = euclideanDistance(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;
- };
- }
-
- function Osm() {
- return function drawOsm(selection) {
- var layers = selection.selectAll('.layer-osm')
- .data(['areas', 'lines', 'hit', 'halo', 'label']);
-
- layers.enter().append('g')
- .attr('class', function(d) { return 'layer-osm layer-' + d; });
- };
- }
-
- function Path(projection, graph, polygon) {
- var cache = {},
- clip = d3.geo.clipExtent().extent(projection.clipExtent()).stream,
- project = projection.stream,
- path = d3.geo.path()
- .projection({stream: function(output) { return polygon ? project(output) : project(clip(output)); }});
-
- return function(entity) {
- if (entity.id in cache) {
- return cache[entity.id];
- } else {
- return cache[entity.id] = path(entity.asGeoJSON(graph));
- }
- };
- }
-
- function PointTransform(projection) {
- return function(entity) {
- // http://jsperf.com/short-array-join
- var pt = projection(entity.loc);
- return 'translate(' + pt[0] + ',' + pt[1] + ')';
- };
- }
-
- function Points(projection, context) {
- function markerPath(selection, klass) {
- selection
- .attr('class', klass)
- .attr('transform', 'translate(-8, -23)')
- .attr('d', 'M 17,8 C 17,13 11,21 8.5,23.5 C 6,21 0,13 0,8 C 0,4 4,-0.5 8.5,-0.5 C 13,-0.5 17,4 17,8 z');
- }
-
- function sortY(a, b) {
- return b.loc[1] - a.loc[1];
- }
-
- return function drawPoints(surface, graph, entities, filter) {
- var wireframe = surface.classed('fill-wireframe'),
- points = wireframe ? [] : _.filter(entities, function(e) {
- return e.geometry(graph) === 'point';
- });
-
- points.sort(sortY);
-
- var groups = surface.selectAll('.layer-hit').selectAll('g.point')
- .filter(filter)
- .data(points, Entity.key);
-
- var group = groups.enter()
- .append('g')
- .attr('class', function(d) { return 'node point ' + d.id; })
- .order();
-
- group.append('path')
- .call(markerPath, 'shadow');
-
- group.append('path')
- .call(markerPath, 'stroke');
-
- group.append('use')
- .attr('transform', 'translate(-6, -20)')
- .attr('class', 'icon')
- .attr('width', '12px')
- .attr('height', '12px');
-
- groups.attr('transform', PointTransform(projection))
- .call(TagClasses());
-
- // Selecting the following implicitly
- // sets the data (point entity) on the element
- groups.select('.shadow');
- groups.select('.stroke');
- groups.select('.icon')
- .attr('xlink:href', function(entity) {
- var preset = context.presets().match(entity, graph);
- return preset.icon ? '#' + preset.icon + '-12' : '';
- });
-
- groups.exit()
- .remove();
- };
- }
-
- function RelationMemberTags(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;
- };
- }
-
- function TagClasses() {
- var primaries = [
- 'building', 'highway', 'railway', 'waterway', 'aeroway',
- 'motorway', 'boundary', 'power', 'amenity', 'natural', 'landuse',
- 'leisure', 'place'
- ],
- statuses = [
- 'proposed', 'construction', 'disused', 'abandoned', 'dismantled',
- 'razed', 'demolished', 'obliterated'
- ],
- secondaries = [
- 'oneway', 'bridge', 'tunnel', 'embankment', 'cutting', 'barrier',
- 'surface', 'tracktype', 'crossing'
- ],
- tagClassRe = /^tag-/,
- tags = function(entity) { return entity.tags; };
-
-
- var tagClasses = function(selection) {
- selection.each(function tagClassesEach(entity) {
- var value = this.className,
- classes, primary, status;
-
- if (value.baseVal !== undefined) value = value.baseVal;
-
- classes = value.trim().split(/\s+/).filter(function(name) {
- return name.length && !tagClassRe.test(name);
- }).join(' ');
-
- var t = tags(entity), i, k, v;
-
- // pick at most one primary classification tag..
- for (i = 0; i < primaries.length; i++) {
- k = primaries[i];
- v = t[k];
- if (!v || v === 'no') continue;
-
- primary = k;
- if (statuses.indexOf(v) !== -1) { // e.g. `railway=abandoned`
- status = v;
- classes += ' tag-' + k;
- } else {
- classes += ' tag-' + k + ' tag-' + k + '-' + v;
- }
-
- break;
- }
-
- // add at most one status tag, only if relates to primary tag..
- if (!status) {
- for (i = 0; i < statuses.length; i++) {
- k = statuses[i];
- v = t[k];
- if (!v || v === 'no') continue;
-
- if (v === 'yes') { // e.g. `railway=rail + abandoned=yes`
- status = k;
- }
- else if (primary && primary === v) { // e.g. `railway=rail + abandoned=railway`
- status = k;
- } else if (!primary && primaries.indexOf(v) !== -1) { // e.g. `abandoned=railway`
- status = k;
- primary = v;
- classes += ' tag-' + v;
- } // else ignore e.g. `highway=path + abandoned=railway`
-
- if (status) break;
- }
- }
-
- if (status) {
- classes += ' tag-status tag-status-' + status;
- }
-
- // add any secondary (structure) tags
- for (i = 0; i < secondaries.length; i++) {
- k = secondaries[i];
- v = t[k];
- if (!v || v === 'no') continue;
- classes += ' tag-' + k + ' tag-' + k + '-' + v;
- }
-
- // For highways, look for surface tagging..
- if (primary === 'highway') {
- var paved = (t.highway !== 'track');
- for (k in t) {
- v = t[k];
- if (k in pavedTags) {
- paved = !!pavedTags[k][v];
- break;
- }
- }
- if (!paved) {
- classes += ' tag-unpaved';
- }
- }
-
- classes = classes.trim();
-
- if (classes !== value) {
- d3.select(this).attr('class', classes);
- }
- });
- };
-
- tagClasses.tags = function(_) {
- if (!arguments.length) return tags;
- tags = _;
- return tagClasses;
- };
-
- return tagClasses;
- }
-
- function Turns(projection) {
- return function drawTurns(surface, graph, turns) {
- function key(turn) {
- return [turn.from.node + turn.via.node + turn.to.node].join('-');
- }
-
- function icon(turn) {
- var u = turn.u ? '-u' : '';
- if (!turn.restriction)
- return '#turn-yes' + u;
- var restriction = graph.entity(turn.restriction).tags.restriction;
- return '#turn-' +
- (!turn.indirect_restriction && /^only_/.test(restriction) ? 'only' : 'no') + u;
- }
-
- var groups = surface.selectAll('.layer-hit').selectAll('g.turn')
- .data(turns, key);
-
- // Enter
- var enter = groups.enter().append('g')
- .attr('class', 'turn');
-
- var nEnter = enter.filter(function (turn) { return !turn.u; });
-
- nEnter.append('rect')
- .attr('transform', 'translate(-22, -12)')
- .attr('width', '44')
- .attr('height', '24');
-
- nEnter.append('use')
- .attr('transform', 'translate(-22, -12)')
- .attr('width', '44')
- .attr('height', '24');
-
-
- var uEnter = enter.filter(function (turn) { return turn.u; });
-
- uEnter.append('circle')
- .attr('r', '16');
-
- uEnter.append('use')
- .attr('transform', 'translate(-16, -16)')
- .attr('width', '32')
- .attr('height', '32');
-
-
- // Update
- groups
- .attr('transform', function (turn) {
- var v = graph.entity(turn.via.node),
- t = graph.entity(turn.to.node),
- a = angle(v, t, projection),
- p = projection(v.loc),
- r = turn.u ? 0 : 60;
-
- return 'translate(' + (r * Math.cos(a) + p[0]) + ',' + (r * Math.sin(a) + p[1]) + ') ' +
- 'rotate(' + a * 180 / Math.PI + ')';
- });
-
- groups.select('use')
- .attr('xlink:href', icon);
-
- groups.select('rect');
- groups.select('circle');
-
-
- // Exit
- groups.exit()
- .remove();
-
- return this;
- };
- }
-
- function Vertices(projection, context) {
- var radiuses = {
- // z16-, z17, z18+, tagged
- shadow: [6, 7.5, 7.5, 11.5],
- stroke: [2.5, 3.5, 3.5, 7],
- fill: [1, 1.5, 1.5, 1.5]
- };
-
- var hover;
-
- function siblingAndChildVertices(ids, graph, extent) {
- var vertices = {};
-
- function addChildVertices(entity) {
- if (!context.features().isHiddenFeature(entity, graph, entity.geometry(graph))) {
- var i;
- if (entity.type === 'way') {
- for (i = 0; i < entity.nodes.length; i++) {
- addChildVertices(graph.entity(entity.nodes[i]));
- }
- } else if (entity.type === 'relation') {
- for (i = 0; i < entity.members.length; i++) {
- var member = context.hasEntity(entity.members[i].id);
- if (member) {
- addChildVertices(member);
- }
- }
- } else if (entity.intersects(extent, graph)) {
- vertices[entity.id] = entity;
- }
- }
- }
-
- ids.forEach(function(id) {
- var entity = context.hasEntity(id);
- if (entity && entity.type === 'node') {
- vertices[entity.id] = entity;
- context.graph().parentWays(entity).forEach(function(entity) {
- addChildVertices(entity);
- });
- } else if (entity) {
- addChildVertices(entity);
- }
- });
-
- return vertices;
- }
-
- function draw(selection, vertices, klass, graph, zoom) {
- var icons = {},
- z = (zoom < 17 ? 0 : zoom < 18 ? 1 : 2);
-
- var groups = selection
- .data(vertices, Entity.key);
-
- function icon(entity) {
- if (entity.id in icons) return icons[entity.id];
- icons[entity.id] =
- entity.hasInterestingTags() &&
- context.presets().match(entity, graph).icon;
- return icons[entity.id];
- }
-
- function setClass(klass) {
- return function(entity) {
- this.setAttribute('class', 'node vertex ' + klass + ' ' + entity.id);
- };
- }
-
- function setAttributes(selection) {
- ['shadow','stroke','fill'].forEach(function(klass) {
- var rads = radiuses[klass];
- selection.selectAll('.' + klass)
- .each(function(entity) {
- var i = z && icon(entity),
- c = i ? 0.5 : 0,
- r = rads[i ? 3 : z];
- this.setAttribute('cx', c);
- this.setAttribute('cy', -c);
- this.setAttribute('r', r);
- if (i && klass === 'fill') {
- this.setAttribute('visibility', 'hidden');
- } else {
- this.removeAttribute('visibility');
- }
- });
- });
-
- selection.selectAll('use')
- .each(function() {
- if (z) {
- this.removeAttribute('visibility');
- } else {
- this.setAttribute('visibility', 'hidden');
- }
- });
- }
-
- var enter = groups.enter()
- .append('g')
- .attr('class', function(d) { return 'node vertex ' + klass + ' ' + d.id; });
-
- enter.append('circle')
- .each(setClass('shadow'));
-
- enter.append('circle')
- .each(setClass('stroke'));
-
- // Vertices with icons get a `use`.
- enter.filter(function(d) { return icon(d); })
- .append('use')
- .attr('transform', 'translate(-6, -6)')
- .attr('xlink:href', function(d) { return '#' + icon(d) + '-12'; })
- .attr('width', '12px')
- .attr('height', '12px')
- .each(setClass('icon'));
-
- // Vertices with tags get a fill.
- enter.filter(function(d) { return d.hasInterestingTags(); })
- .append('circle')
- .each(setClass('fill'));
-
- groups
- .attr('transform', PointTransform(projection))
- .classed('shared', function(entity) { return graph.isShared(entity); })
- .call(setAttributes);
-
- groups.exit()
- .remove();
- }
-
- function drawVertices(surface, graph, entities, filter, extent, zoom) {
- var selected = siblingAndChildVertices(context.selectedIDs(), graph, extent),
- wireframe = surface.classed('fill-wireframe'),
- vertices = [];
-
- for (var i = 0; i < entities.length; i++) {
- var entity = entities[i],
- geometry = entity.geometry(graph);
-
- if (wireframe && geometry === 'point') {
- vertices.push(entity);
- continue;
- }
-
- if (geometry !== 'vertex')
- continue;
-
- if (entity.id in selected ||
- entity.hasInterestingTags() ||
- entity.isIntersection(graph)) {
- vertices.push(entity);
- }
- }
-
- surface.selectAll('.layer-hit').selectAll('g.vertex.vertex-persistent')
- .filter(filter)
- .call(draw, vertices, 'vertex-persistent', graph, zoom);
-
- drawHover(surface, graph, extent, zoom);
- }
-
- function drawHover(surface, graph, extent, zoom) {
- var hovered = hover ? siblingAndChildVertices([hover.id], graph, extent) : {};
-
- surface.selectAll('.layer-hit').selectAll('g.vertex.vertex-hover')
- .call(draw, d3.values(hovered), 'vertex-hover', graph, zoom);
- }
-
- drawVertices.drawHover = function(surface, graph, target, extent, zoom) {
- if (target === hover) return;
- hover = target;
- drawHover(surface, graph, extent, zoom);
- };
-
- return drawVertices;
- }
-
-
-
- var svg = Object.freeze({
- Areas: Areas,
- Debug: Debug,
- Defs: Defs,
- Gpx: Gpx,
- Icon: Icon,
- Labels: Labels,
- Layers: Layers,
- Lines: Lines,
- MapillaryImages: MapillaryImages,
- MapillarySigns: MapillarySigns,
- Midpoints: Midpoints,
- OneWaySegments: OneWaySegments,
- Osm: Osm,
- Path: Path,
- PointTransform: PointTransform,
- Points: Points,
- RelationMemberTags: RelationMemberTags,
- TagClasses: TagClasses,
- Turns: Turns,
- Vertices: Vertices
- });
-
function AddArea(context) {
var mode = {
id: 'add-area',
@@ -14016,6 +11387,1173 @@
return d3.rebind(features, dispatch, 'on');
}
+ function Areas(projection) {
+ // Patterns only work in Firefox when set directly on element.
+ // (This is not a bug: https://bugzilla.mozilla.org/show_bug.cgi?id=750632)
+ var patterns = {
+ wetland: 'wetland',
+ beach: 'beach',
+ scrub: 'scrub',
+ construction: 'construction',
+ military: 'construction',
+ cemetery: 'cemetery',
+ grave_yard: 'cemetery',
+ meadow: 'meadow',
+ farm: 'farmland',
+ farmland: 'farmland',
+ orchard: 'orchard'
+ };
+
+ var patternKeys = ['landuse', 'natural', 'amenity'];
+
+ function setPattern(d) {
+ for (var i = 0; i < patternKeys.length; i++) {
+ if (patterns.hasOwnProperty(d.tags[patternKeys[i]])) {
+ this.style.fill = this.style.stroke = 'url("#pattern-' + patterns[d.tags[patternKeys[i]]] + '")';
+ return;
+ }
+ }
+ this.style.fill = this.style.stroke = '';
+ }
+
+ return function drawAreas(surface, graph, entities, filter) {
+ var path = iD.svg.Path(projection, graph, true),
+ areas = {},
+ multipolygon;
+
+ for (var i = 0; i < entities.length; i++) {
+ var entity = entities[i];
+ if (entity.geometry(graph) !== 'area') continue;
+
+ multipolygon = iD.geo.isSimpleMultipolygonOuterMember(entity, graph);
+ if (multipolygon) {
+ areas[multipolygon.id] = {
+ entity: multipolygon.mergeTags(entity.tags),
+ area: Math.abs(entity.area(graph))
+ };
+ } else if (!areas[entity.id]) {
+ areas[entity.id] = {
+ entity: entity,
+ area: Math.abs(entity.area(graph))
+ };
+ }
+ }
+
+ areas = d3.values(areas).filter(function hasPath(a) { return path(a.entity); });
+ areas.sort(function areaSort(a, b) { return b.area - a.area; });
+ areas = _.map(areas, 'entity');
+
+ var strokes = areas.filter(function(area) {
+ return area.type === 'way';
+ });
+
+ var data = {
+ clip: areas,
+ shadow: strokes,
+ stroke: strokes,
+ fill: areas
+ };
+
+ var clipPaths = surface.selectAll('defs').selectAll('.clipPath')
+ .filter(filter)
+ .data(data.clip, iD.Entity.key);
+
+ clipPaths.enter()
+ .append('clipPath')
+ .attr('class', 'clipPath')
+ .attr('id', function(entity) { return entity.id + '-clippath'; })
+ .append('path');
+
+ clipPaths.selectAll('path')
+ .attr('d', path);
+
+ clipPaths.exit()
+ .remove();
+
+ var areagroup = surface
+ .selectAll('.layer-areas')
+ .selectAll('g.areagroup')
+ .data(['fill', 'shadow', 'stroke']);
+
+ areagroup.enter()
+ .append('g')
+ .attr('class', function(d) { return 'layer areagroup area-' + d; });
+
+ var paths = areagroup
+ .selectAll('path')
+ .filter(filter)
+ .data(function(layer) { return data[layer]; }, iD.Entity.key);
+
+ // Remove exiting areas first, so they aren't included in the `fills`
+ // array used for sorting below (https://github.com/openstreetmap/iD/issues/1903).
+ paths.exit()
+ .remove();
+
+ var fills = surface.selectAll('.area-fill path.area')[0];
+
+ var bisect = d3.bisector(function(node) {
+ return -node.__data__.area(graph);
+ }).left;
+
+ function sortedByArea(entity) {
+ if (this.__data__ === 'fill') {
+ return fills[bisect(fills, -entity.area(graph))];
+ }
+ }
+
+ paths.enter()
+ .insert('path', sortedByArea)
+ .each(function(entity) {
+ var layer = this.parentNode.__data__;
+
+ this.setAttribute('class', entity.type + ' area ' + layer + ' ' + entity.id);
+
+ if (layer === 'fill') {
+ this.setAttribute('clip-path', 'url(#' + entity.id + '-clippath)');
+ setPattern.apply(this, arguments);
+ }
+ })
+ .call(iD.svg.TagClasses());
+
+ paths
+ .attr('d', path);
+ };
+ }
+
+ function Labels(projection, context) {
+ var path = d3.geo.path().projection(projection);
+
+ // Replace with dict and iterate over entities tags instead?
+ var label_stack = [
+ ['line', 'aeroway'],
+ ['line', 'highway'],
+ ['line', 'railway'],
+ ['line', 'waterway'],
+ ['area', 'aeroway'],
+ ['area', 'amenity'],
+ ['area', 'building'],
+ ['area', 'historic'],
+ ['area', 'leisure'],
+ ['area', 'man_made'],
+ ['area', 'natural'],
+ ['area', 'shop'],
+ ['area', 'tourism'],
+ ['point', 'aeroway'],
+ ['point', 'amenity'],
+ ['point', 'building'],
+ ['point', 'historic'],
+ ['point', 'leisure'],
+ ['point', 'man_made'],
+ ['point', 'natural'],
+ ['point', 'shop'],
+ ['point', 'tourism'],
+ ['line', 'name'],
+ ['area', 'name'],
+ ['point', 'name']
+ ];
+
+ var default_size = 12;
+
+ var font_sizes = label_stack.map(function(d) {
+ var style = iD.util.getStyle('text.' + d[0] + '.tag-' + d[1]),
+ m = style && style.cssText.match('font-size: ([0-9]{1,2})px;');
+ if (m) return parseInt(m[1], 10);
+
+ style = iD.util.getStyle('text.' + d[0]);
+ m = style && style.cssText.match('font-size: ([0-9]{1,2})px;');
+ if (m) return parseInt(m[1], 10);
+
+ return default_size;
+ });
+
+ var iconSize = 18;
+
+ var pointOffsets = [
+ [15, -11, 'start'], // right
+ [10, -11, 'start'], // unused right now
+ [-15, -11, 'end']
+ ];
+
+ var lineOffsets = [50, 45, 55, 40, 60, 35, 65, 30, 70, 25,
+ 75, 20, 80, 15, 95, 10, 90, 5, 95];
+
+
+ var noIcons = ['building', 'landuse', 'natural'];
+ function blacklisted(preset) {
+ return _.some(noIcons, function(s) {
+ return preset.id.indexOf(s) >= 0;
+ });
+ }
+
+ function get(array, prop) {
+ return function(d, i) { return array[i][prop]; };
+ }
+
+ var textWidthCache = {};
+
+ 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 drawLineLabels(group, entities, filter, classes, labels) {
+ var texts = group.selectAll('text.' + classes)
+ .filter(filter)
+ .data(entities, iD.Entity.key);
+
+ texts.enter()
+ .append('text')
+ .attr('class', function(d, i) { return classes + ' ' + labels[i].classes + ' ' + d.id; })
+ .append('textPath')
+ .attr('class', 'textpath');
+
+
+ texts.selectAll('.textpath')
+ .filter(filter)
+ .data(entities, iD.Entity.key)
+ .attr({
+ 'startOffset': '50%',
+ 'xlink:href': function(d) { return '#labelpath-' + d.id; }
+ })
+ .text(iD.util.displayName);
+
+ texts.exit().remove();
+ }
+
+ function drawLinePaths(group, entities, filter, classes, labels) {
+ var halos = group.selectAll('path')
+ .filter(filter)
+ .data(entities, iD.Entity.key);
+
+ halos.enter()
+ .append('path')
+ .style('stroke-width', get(labels, 'font-size'))
+ .attr('id', function(d) { return 'labelpath-' + d.id; })
+ .attr('class', classes);
+
+ halos.attr('d', get(labels, 'lineString'));
+
+ halos.exit().remove();
+ }
+
+ function drawPointLabels(group, entities, filter, classes, labels) {
+ var texts = group.selectAll('text.' + classes)
+ .filter(filter)
+ .data(entities, iD.Entity.key);
+
+ texts.enter()
+ .append('text')
+ .attr('class', function(d, i) { return classes + ' ' + labels[i].classes + ' ' + d.id; });
+
+ texts.attr('x', get(labels, 'x'))
+ .attr('y', get(labels, 'y'))
+ .style('text-anchor', get(labels, 'textAnchor'))
+ .text(iD.util.displayName)
+ .each(function(d, i) { textWidth(iD.util.displayName(d), labels[i].height, this); });
+
+ texts.exit().remove();
+ return texts;
+ }
+
+ function drawAreaLabels(group, entities, filter, classes, labels) {
+ entities = entities.filter(hasText);
+ labels = labels.filter(hasText);
+ return drawPointLabels(group, entities, filter, classes, labels);
+
+ function hasText(d, i) {
+ return labels[i].hasOwnProperty('x') && labels[i].hasOwnProperty('y');
+ }
+ }
+
+ function drawAreaIcons(group, entities, filter, classes, labels) {
+ var icons = group.selectAll('use')
+ .filter(filter)
+ .data(entities, iD.Entity.key);
+
+ icons.enter()
+ .append('use')
+ .attr('class', 'icon areaicon')
+ .attr('width', '18px')
+ .attr('height', '18px');
+
+ icons.attr('transform', get(labels, 'transform'))
+ .attr('xlink:href', function(d) {
+ var icon = context.presets().match(d, context.graph()).icon;
+ return '#' + icon + (icon === 'hairdresser' ? '-24': '-18'); // workaround: maki hairdresser-18 broken?
+ });
+
+
+ icons.exit().remove();
+ }
+
+ function reverse(p) {
+ var angle = Math.atan2(p[1][1] - p[0][1], p[1][0] - p[0][0]);
+ return !(p[0][0] < p[p.length - 1][0] && angle < Math.PI/2 && angle > -Math.PI/2);
+ }
+
+ function lineString(nodes) {
+ return 'M' + nodes.join('L');
+ }
+
+ function subpath(nodes, from, to) {
+ function segmentLength(i) {
+ var dx = nodes[i][0] - nodes[i + 1][0];
+ var dy = nodes[i][1] - nodes[i + 1][1];
+ return Math.sqrt(dx * dx + dy * dy);
+ }
+
+ var sofar = 0,
+ start, end, i0, i1;
+ for (var i = 0; i < nodes.length - 1; i++) {
+ var current = segmentLength(i);
+ var portion;
+ if (!start && sofar + current >= from) {
+ portion = (from - sofar) / current;
+ start = [
+ nodes[i][0] + portion * (nodes[i + 1][0] - nodes[i][0]),
+ nodes[i][1] + portion * (nodes[i + 1][1] - nodes[i][1])
+ ];
+ i0 = i + 1;
+ }
+ if (!end && sofar + current >= to) {
+ portion = (to - sofar) / current;
+ end = [
+ nodes[i][0] + portion * (nodes[i + 1][0] - nodes[i][0]),
+ nodes[i][1] + portion * (nodes[i + 1][1] - nodes[i][1])
+ ];
+ i1 = i + 1;
+ }
+ sofar += current;
+
+ }
+ var ret = nodes.slice(i0, i1);
+ ret.unshift(start);
+ ret.push(end);
+ return ret;
+
+ }
+
+ function hideOnMouseover() {
+ var layers = d3.select(this)
+ .selectAll('.layer-label, .layer-halo');
+
+ layers.selectAll('.proximate')
+ .classed('proximate', false);
+
+ var mouse = context.mouse(),
+ pad = 50,
+ rect = [mouse[0] - pad, mouse[1] - pad, mouse[0] + pad, mouse[1] + pad],
+ ids = _.map(rtree.search(rect), 'id');
+
+ if (!ids.length) return;
+ layers.selectAll('.' + ids.join(', .'))
+ .classed('proximate', true);
+ }
+
+ var rtree = rbush(),
+ rectangles = {};
+
+ function drawLabels(surface, graph, entities, filter, dimensions, fullRedraw) {
+ var hidePoints = !surface.selectAll('.node.point').node();
+
+ var labelable = [], i, k, entity;
+ for (i = 0; i < label_stack.length; i++) labelable.push([]);
+
+ if (fullRedraw) {
+ rtree.clear();
+ rectangles = {};
+ } else {
+ for (i = 0; i < entities.length; i++) {
+ rtree.remove(rectangles[entities[i].id]);
+ }
+ }
+
+ // Split entities into groups specified by label_stack
+ for (i = 0; i < entities.length; i++) {
+ entity = entities[i];
+ var geometry = entity.geometry(graph);
+
+ if (geometry === 'vertex')
+ continue;
+ if (hidePoints && geometry === 'point')
+ continue;
+
+ var preset = geometry === 'area' && context.presets().match(entity, graph),
+ icon = preset && !blacklisted(preset) && preset.icon;
+
+ if (!icon && !iD.util.displayName(entity))
+ continue;
+
+ for (k = 0; k < label_stack.length; k++) {
+ if (geometry === label_stack[k][0] && entity.tags[label_stack[k][1]]) {
+ labelable[k].push(entity);
+ break;
+ }
+ }
+ }
+
+ var positions = {
+ point: [],
+ line: [],
+ area: []
+ };
+
+ var labelled = {
+ point: [],
+ line: [],
+ area: []
+ };
+
+ // Try and find a valid label for labellable entities
+ for (k = 0; k < labelable.length; k++) {
+ var font_size = font_sizes[k];
+ for (i = 0; i < labelable[k].length; i++) {
+ entity = labelable[k][i];
+ var name = iD.util.displayName(entity),
+ width = name && textWidth(name, font_size),
+ p;
+ if (entity.geometry(graph) === 'point') {
+ p = getPointLabel(entity, width, font_size);
+ } else if (entity.geometry(graph) === 'line') {
+ p = getLineLabel(entity, width, font_size);
+ } else if (entity.geometry(graph) === 'area') {
+ p = getAreaLabel(entity, width, font_size);
+ }
+ if (p) {
+ p.classes = entity.geometry(graph) + ' tag-' + label_stack[k][1];
+ positions[entity.geometry(graph)].push(p);
+ labelled[entity.geometry(graph)].push(entity);
+ }
+ }
+ }
+
+ function getPointLabel(entity, width, height) {
+ var coord = projection(entity.loc),
+ m = 5, // margin
+ offset = pointOffsets[0],
+ p = {
+ height: height,
+ width: width,
+ x: coord[0] + offset[0],
+ y: coord[1] + offset[1],
+ textAnchor: offset[2]
+ };
+ var rect = [p.x - m, p.y - m, p.x + width + m, p.y + height + m];
+ if (tryInsert(rect, entity.id)) return p;
+ }
+
+
+ function getLineLabel(entity, width, height) {
+ var nodes = _.map(graph.childNodes(entity), 'loc').map(projection),
+ length = iD.geo.pathLength(nodes);
+ if (length < width + 20) return;
+
+ for (var i = 0; i < lineOffsets.length; i++) {
+ var offset = lineOffsets[i],
+ middle = offset / 100 * length,
+ start = middle - width/2;
+ if (start < 0 || start + width > length) continue;
+ var sub = subpath(nodes, start, start + width),
+ rev = reverse(sub),
+ rect = [
+ Math.min(sub[0][0], sub[sub.length - 1][0]) - 10,
+ Math.min(sub[0][1], sub[sub.length - 1][1]) - 10,
+ Math.max(sub[0][0], sub[sub.length - 1][0]) + 20,
+ Math.max(sub[0][1], sub[sub.length - 1][1]) + 30
+ ];
+ if (rev) sub = sub.reverse();
+ if (tryInsert(rect, entity.id)) return {
+ 'font-size': height + 2,
+ lineString: lineString(sub),
+ startOffset: offset + '%'
+ };
+ }
+ }
+
+ function getAreaLabel(entity, width, height) {
+ var centroid = path.centroid(entity.asGeoJSON(graph, true)),
+ extent = entity.extent(graph),
+ entitywidth = projection(extent[1])[0] - projection(extent[0])[0],
+ rect;
+
+ if (isNaN(centroid[0]) || entitywidth < 20) return;
+
+ var iconX = centroid[0] - (iconSize/2),
+ iconY = centroid[1] - (iconSize/2),
+ textOffset = iconSize + 5;
+
+ var p = {
+ transform: 'translate(' + iconX + ',' + iconY + ')'
+ };
+
+ if (width && entitywidth >= width + 20) {
+ p.x = centroid[0];
+ p.y = centroid[1] + textOffset;
+ p.textAnchor = 'middle';
+ p.height = height;
+ rect = [p.x - width/2, p.y, p.x + width/2, p.y + height + textOffset];
+ } else {
+ rect = [iconX, iconY, iconX + iconSize, iconY + iconSize];
+ }
+
+ if (tryInsert(rect, entity.id)) return p;
+
+ }
+
+ function tryInsert(rect, id) {
+ // Check that label is visible
+ if (rect[0] < 0 || rect[1] < 0 || rect[2] > dimensions[0] ||
+ rect[3] > dimensions[1]) return false;
+ var v = rtree.search(rect).length === 0;
+ if (v) {
+ rect.id = id;
+ rtree.insert(rect);
+ rectangles[id] = rect;
+ }
+ return v;
+ }
+
+ var label = surface.selectAll('.layer-label'),
+ halo = surface.selectAll('.layer-halo');
+
+ // points
+ drawPointLabels(label, labelled.point, filter, 'pointlabel', positions.point);
+ drawPointLabels(halo, labelled.point, filter, 'pointlabel-halo', positions.point);
+
+ // lines
+ drawLinePaths(halo, labelled.line, filter, '', positions.line);
+ drawLineLabels(label, labelled.line, filter, 'linelabel', positions.line);
+ drawLineLabels(halo, labelled.line, filter, 'linelabel-halo', positions.line);
+
+ // areas
+ drawAreaLabels(label, labelled.area, filter, 'arealabel', positions.area);
+ drawAreaLabels(halo, labelled.area, filter, 'arealabel-halo', positions.area);
+ drawAreaIcons(label, labelled.area, filter, 'arealabel-icon', positions.area);
+
+ // debug
+ var showDebug = context.getDebug('collision');
+ var debug = label.selectAll('.layer-label-debug')
+ .data(showDebug ? [true] : []);
+
+ debug.enter()
+ .append('g')
+ .attr('class', 'layer-label-debug');
+
+ debug.exit()
+ .remove();
+
+ if (showDebug) {
+ var gj = rtree.all().map(function(d) {
+ return { type: 'Polygon', coordinates: [[
+ [d[0], d[1]],
+ [d[2], d[1]],
+ [d[2], d[3]],
+ [d[0], d[3]],
+ [d[0], d[1]]
+ ]]};
+ });
+
+ var debugboxes = debug.selectAll('.debug').data(gj);
+
+ debugboxes.enter()
+ .append('path')
+ .attr('class', 'debug yellow');
+
+ debugboxes.exit()
+ .remove();
+
+ debugboxes
+ .attr('d', d3.geo.path().projection(null));
+ }
+ }
+
+ drawLabels.supersurface = function(supersurface) {
+ supersurface
+ .on('mousemove.hidelabels', hideOnMouseover)
+ .on('mousedown.hidelabels', function () {
+ supersurface.on('mousemove.hidelabels', null);
+ })
+ .on('mouseup.hidelabels', function () {
+ supersurface.on('mousemove.hidelabels', hideOnMouseover);
+ });
+ };
+
+ return drawLabels;
+ }
+
+ function Layers(projection, context) {
+ var dispatch = d3.dispatch('change'),
+ svg = d3.select(null),
+ layers = [
+ { id: 'osm', layer: iD.svg.Osm(projection, context, dispatch) },
+ { id: 'gpx', layer: iD.svg.Gpx(projection, context, dispatch) },
+ { id: 'mapillary-images', layer: iD.svg.MapillaryImages(projection, context, dispatch) },
+ { id: 'mapillary-signs', layer: iD.svg.MapillarySigns(projection, context, dispatch) },
+ { id: 'debug', layer: iD.svg.Debug(projection, context, dispatch) }
+ ];
+
+
+ function drawLayers(selection) {
+ svg = selection.selectAll('.surface')
+ .data([0]);
+
+ svg.enter()
+ .append('svg')
+ .attr('class', 'surface')
+ .append('defs');
+
+ var groups = svg.selectAll('.data-layer')
+ .data(layers);
+
+ groups.enter()
+ .append('g')
+ .attr('class', function(d) { return 'data-layer data-layer-' + d.id; });
+
+ groups
+ .each(function(d) { d3.select(this).call(d.layer); });
+
+ groups.exit()
+ .remove();
+ }
+
+ drawLayers.all = function() {
+ return layers;
+ };
+
+ drawLayers.layer = function(id) {
+ var obj = _.find(layers, function(o) {return o.id === id;});
+ return obj && obj.layer;
+ };
+
+ drawLayers.only = function(what) {
+ var arr = [].concat(what);
+ drawLayers.remove(_.difference(_.map(layers, 'id'), arr));
+ return this;
+ };
+
+ drawLayers.remove = function(what) {
+ var arr = [].concat(what);
+ arr.forEach(function(id) {
+ layers = _.reject(layers, function(o) {return o.id === id;});
+ });
+ dispatch.change();
+ return this;
+ };
+
+ drawLayers.add = function(what) {
+ var arr = [].concat(what);
+ arr.forEach(function(obj) {
+ if ('id' in obj && 'layer' in obj) {
+ layers.push(obj);
+ }
+ });
+ dispatch.change();
+ return this;
+ };
+
+ drawLayers.dimensions = function(_) {
+ if (!arguments.length) return svg.dimensions();
+ svg.dimensions(_);
+ layers.forEach(function(obj) {
+ if (obj.layer.dimensions) {
+ obj.layer.dimensions(_);
+ }
+ });
+ return this;
+ };
+
+
+ return d3.rebind(drawLayers, dispatch, 'on');
+ }
+
+ function Lines(projection) {
+
+ var highway_stack = {
+ motorway: 0,
+ motorway_link: 1,
+ trunk: 2,
+ trunk_link: 3,
+ primary: 4,
+ primary_link: 5,
+ secondary: 6,
+ tertiary: 7,
+ unclassified: 8,
+ residential: 9,
+ service: 10,
+ footway: 11
+ };
+
+ function waystack(a, b) {
+ var as = 0, bs = 0;
+
+ if (a.tags.highway) { as -= highway_stack[a.tags.highway]; }
+ if (b.tags.highway) { bs -= highway_stack[b.tags.highway]; }
+ return as - bs;
+ }
+
+ return function drawLines(surface, graph, entities, filter) {
+ var ways = [], pathdata = {}, onewaydata = {},
+ getPath = iD.svg.Path(projection, graph);
+
+ for (var i = 0; i < entities.length; i++) {
+ var entity = entities[i],
+ outer = iD.geo.simpleMultipolygonOuterMember(entity, graph);
+ if (outer) {
+ ways.push(entity.mergeTags(outer.tags));
+ } else if (entity.geometry(graph) === 'line') {
+ ways.push(entity);
+ }
+ }
+
+ ways = ways.filter(getPath);
+
+ pathdata = _.groupBy(ways, function(way) { return way.layer(); });
+
+ _.forOwn(pathdata, function(v, k) {
+ onewaydata[k] = _(v)
+ .filter(function(d) { return d.isOneWay(); })
+ .map(iD.svg.OneWaySegments(projection, graph, 35))
+ .flatten()
+ .valueOf();
+ });
+
+ var layergroup = surface
+ .selectAll('.layer-lines')
+ .selectAll('g.layergroup')
+ .data(d3.range(-10, 11));
+
+ layergroup.enter()
+ .append('g')
+ .attr('class', function(d) { return 'layer layergroup layer' + String(d); });
+
+
+ var linegroup = layergroup
+ .selectAll('g.linegroup')
+ .data(['shadow', 'casing', 'stroke']);
+
+ linegroup.enter()
+ .append('g')
+ .attr('class', function(d) { return 'layer linegroup line-' + d; });
+
+
+ var lines = linegroup
+ .selectAll('path')
+ .filter(filter)
+ .data(
+ function() { return pathdata[this.parentNode.parentNode.__data__] || []; },
+ iD.Entity.key
+ );
+
+ // Optimization: call simple TagClasses only on enter selection. This
+ // works because iD.Entity.key is defined to include the entity v attribute.
+ lines.enter()
+ .append('path')
+ .attr('class', function(d) { return 'way line ' + this.parentNode.__data__ + ' ' + d.id; })
+ .call(iD.svg.TagClasses());
+
+ lines
+ .sort(waystack)
+ .attr('d', getPath)
+ .call(iD.svg.TagClasses().tags(iD.svg.RelationMemberTags(graph)));
+
+ lines.exit()
+ .remove();
+
+
+ var onewaygroup = layergroup
+ .selectAll('g.onewaygroup')
+ .data(['oneway']);
+
+ onewaygroup.enter()
+ .append('g')
+ .attr('class', 'layer onewaygroup');
+
+
+ var oneways = onewaygroup
+ .selectAll('path')
+ .filter(filter)
+ .data(
+ function() { return onewaydata[this.parentNode.parentNode.__data__] || []; },
+ function(d) { return [d.id, d.index]; }
+ );
+
+ oneways.enter()
+ .append('path')
+ .attr('class', 'oneway')
+ .attr('marker-mid', 'url(#oneway-marker)');
+
+ oneways
+ .attr('d', function(d) { return d.d; });
+
+ if (iD.detect().ie) {
+ oneways.each(function() { this.parentNode.insertBefore(this, this); });
+ }
+
+ oneways.exit()
+ .remove();
+
+ };
+ }
+
+ function Midpoints(projection, context) {
+ return function drawMidpoints(surface, graph, entities, filter, extent) {
+ var poly = extent.polygon(),
+ midpoints = {};
+
+ for (var i = 0; i < entities.length; i++) {
+ var entity = entities[i];
+
+ if (entity.type !== 'way')
+ continue;
+ if (!filter(entity))
+ continue;
+ if (context.selectedIDs().indexOf(entity.id) < 0)
+ continue;
+
+ var nodes = graph.childNodes(entity);
+ for (var j = 0; j < nodes.length - 1; j++) {
+
+ var a = nodes[j],
+ b = nodes[j + 1],
+ id = [a.id, b.id].sort().join('-');
+
+ if (midpoints[id]) {
+ midpoints[id].parents.push(entity);
+ } else {
+ if (iD.geo.euclideanDistance(projection(a.loc), projection(b.loc)) > 40) {
+ var point = iD.geo.interp(a.loc, b.loc, 0.5),
+ loc = null;
+
+ if (extent.intersects(point)) {
+ loc = point;
+ } else {
+ for (var k = 0; k < 4; k++) {
+ point = iD.geo.lineIntersection([a.loc, b.loc], [poly[k], poly[k+1]]);
+ if (point &&
+ iD.geo.euclideanDistance(projection(a.loc), projection(point)) > 20 &&
+ iD.geo.euclideanDistance(projection(b.loc), projection(point)) > 20)
+ {
+ loc = point;
+ break;
+ }
+ }
+ }
+
+ if (loc) {
+ midpoints[id] = {
+ type: 'midpoint',
+ id: id,
+ loc: loc,
+ edge: [a.id, b.id],
+ parents: [entity]
+ };
+ }
+ }
+ }
+ }
+ }
+
+ function midpointFilter(d) {
+ if (midpoints[d.id])
+ return true;
+
+ for (var i = 0; i < d.parents.length; i++)
+ if (filter(d.parents[i]))
+ return true;
+
+ return false;
+ }
+
+ var groups = surface.selectAll('.layer-hit').selectAll('g.midpoint')
+ .filter(midpointFilter)
+ .data(_.values(midpoints), function(d) { return d.id; });
+
+ var enter = groups.enter()
+ .insert('g', ':first-child')
+ .attr('class', 'midpoint');
+
+ enter.append('polygon')
+ .attr('points', '-6,8 10,0 -6,-8')
+ .attr('class', 'shadow');
+
+ enter.append('polygon')
+ .attr('points', '-3,4 5,0 -3,-4')
+ .attr('class', 'fill');
+
+ groups
+ .attr('transform', function(d) {
+ var translate = iD.svg.PointTransform(projection),
+ a = context.entity(d.edge[0]),
+ b = context.entity(d.edge[1]),
+ angle = Math.round(iD.geo.angle(a, b, projection) * (180 / Math.PI));
+ return translate(d) + ' rotate(' + angle + ')';
+ })
+ .call(iD.svg.TagClasses().tags(
+ function(d) { return d.parents[0].tags; }
+ ));
+
+ // Propagate data bindings.
+ groups.select('polygon.shadow');
+ groups.select('polygon.fill');
+
+ groups.exit()
+ .remove();
+ };
+ }
+
+ function Points(projection, context) {
+ function markerPath(selection, klass) {
+ selection
+ .attr('class', klass)
+ .attr('transform', 'translate(-8, -23)')
+ .attr('d', 'M 17,8 C 17,13 11,21 8.5,23.5 C 6,21 0,13 0,8 C 0,4 4,-0.5 8.5,-0.5 C 13,-0.5 17,4 17,8 z');
+ }
+
+ function sortY(a, b) {
+ return b.loc[1] - a.loc[1];
+ }
+
+ return function drawPoints(surface, graph, entities, filter) {
+ var wireframe = surface.classed('fill-wireframe'),
+ points = wireframe ? [] : _.filter(entities, function(e) {
+ return e.geometry(graph) === 'point';
+ });
+
+ points.sort(sortY);
+
+ var groups = surface.selectAll('.layer-hit').selectAll('g.point')
+ .filter(filter)
+ .data(points, iD.Entity.key);
+
+ var group = groups.enter()
+ .append('g')
+ .attr('class', function(d) { return 'node point ' + d.id; })
+ .order();
+
+ group.append('path')
+ .call(markerPath, 'shadow');
+
+ group.append('path')
+ .call(markerPath, 'stroke');
+
+ group.append('use')
+ .attr('transform', 'translate(-6, -20)')
+ .attr('class', 'icon')
+ .attr('width', '12px')
+ .attr('height', '12px');
+
+ groups.attr('transform', iD.svg.PointTransform(projection))
+ .call(iD.svg.TagClasses());
+
+ // Selecting the following implicitly
+ // sets the data (point entity) on the element
+ groups.select('.shadow');
+ groups.select('.stroke');
+ groups.select('.icon')
+ .attr('xlink:href', function(entity) {
+ var preset = context.presets().match(entity, graph);
+ return preset.icon ? '#' + preset.icon + '-12' : '';
+ });
+
+ groups.exit()
+ .remove();
+ };
+ }
+
+ function Vertices(projection, context) {
+ var radiuses = {
+ // z16-, z17, z18+, tagged
+ shadow: [6, 7.5, 7.5, 11.5],
+ stroke: [2.5, 3.5, 3.5, 7],
+ fill: [1, 1.5, 1.5, 1.5]
+ };
+
+ var hover;
+
+ function siblingAndChildVertices(ids, graph, extent) {
+ var vertices = {};
+
+ function addChildVertices(entity) {
+ if (!context.features().isHiddenFeature(entity, graph, entity.geometry(graph))) {
+ var i;
+ if (entity.type === 'way') {
+ for (i = 0; i < entity.nodes.length; i++) {
+ addChildVertices(graph.entity(entity.nodes[i]));
+ }
+ } else if (entity.type === 'relation') {
+ for (i = 0; i < entity.members.length; i++) {
+ var member = context.hasEntity(entity.members[i].id);
+ if (member) {
+ addChildVertices(member);
+ }
+ }
+ } else if (entity.intersects(extent, graph)) {
+ vertices[entity.id] = entity;
+ }
+ }
+ }
+
+ ids.forEach(function(id) {
+ var entity = context.hasEntity(id);
+ if (entity && entity.type === 'node') {
+ vertices[entity.id] = entity;
+ context.graph().parentWays(entity).forEach(function(entity) {
+ addChildVertices(entity);
+ });
+ } else if (entity) {
+ addChildVertices(entity);
+ }
+ });
+
+ return vertices;
+ }
+
+ function draw(selection, vertices, klass, graph, zoom) {
+ var icons = {},
+ z = (zoom < 17 ? 0 : zoom < 18 ? 1 : 2);
+
+ var groups = selection
+ .data(vertices, iD.Entity.key);
+
+ function icon(entity) {
+ if (entity.id in icons) return icons[entity.id];
+ icons[entity.id] =
+ entity.hasInterestingTags() &&
+ context.presets().match(entity, graph).icon;
+ return icons[entity.id];
+ }
+
+ function setClass(klass) {
+ return function(entity) {
+ this.setAttribute('class', 'node vertex ' + klass + ' ' + entity.id);
+ };
+ }
+
+ function setAttributes(selection) {
+ ['shadow','stroke','fill'].forEach(function(klass) {
+ var rads = radiuses[klass];
+ selection.selectAll('.' + klass)
+ .each(function(entity) {
+ var i = z && icon(entity),
+ c = i ? 0.5 : 0,
+ r = rads[i ? 3 : z];
+ this.setAttribute('cx', c);
+ this.setAttribute('cy', -c);
+ this.setAttribute('r', r);
+ if (i && klass === 'fill') {
+ this.setAttribute('visibility', 'hidden');
+ } else {
+ this.removeAttribute('visibility');
+ }
+ });
+ });
+
+ selection.selectAll('use')
+ .each(function() {
+ if (z) {
+ this.removeAttribute('visibility');
+ } else {
+ this.setAttribute('visibility', 'hidden');
+ }
+ });
+ }
+
+ var enter = groups.enter()
+ .append('g')
+ .attr('class', function(d) { return 'node vertex ' + klass + ' ' + d.id; });
+
+ enter.append('circle')
+ .each(setClass('shadow'));
+
+ enter.append('circle')
+ .each(setClass('stroke'));
+
+ // Vertices with icons get a `use`.
+ enter.filter(function(d) { return icon(d); })
+ .append('use')
+ .attr('transform', 'translate(-6, -6)')
+ .attr('xlink:href', function(d) { return '#' + icon(d) + '-12'; })
+ .attr('width', '12px')
+ .attr('height', '12px')
+ .each(setClass('icon'));
+
+ // Vertices with tags get a fill.
+ enter.filter(function(d) { return d.hasInterestingTags(); })
+ .append('circle')
+ .each(setClass('fill'));
+
+ groups
+ .attr('transform', iD.svg.PointTransform(projection))
+ .classed('shared', function(entity) { return graph.isShared(entity); })
+ .call(setAttributes);
+
+ groups.exit()
+ .remove();
+ }
+
+ function drawVertices(surface, graph, entities, filter, extent, zoom) {
+ var selected = siblingAndChildVertices(context.selectedIDs(), graph, extent),
+ wireframe = surface.classed('fill-wireframe'),
+ vertices = [];
+
+ for (var i = 0; i < entities.length; i++) {
+ var entity = entities[i],
+ geometry = entity.geometry(graph);
+
+ if (wireframe && geometry === 'point') {
+ vertices.push(entity);
+ continue;
+ }
+
+ if (geometry !== 'vertex')
+ continue;
+
+ if (entity.id in selected ||
+ entity.hasInterestingTags() ||
+ entity.isIntersection(graph)) {
+ vertices.push(entity);
+ }
+ }
+
+ surface.selectAll('.layer-hit').selectAll('g.vertex.vertex-persistent')
+ .filter(filter)
+ .call(draw, vertices, 'vertex-persistent', graph, zoom);
+
+ drawHover(surface, graph, extent, zoom);
+ }
+
+ function drawHover(surface, graph, extent, zoom) {
+ var hovered = hover ? siblingAndChildVertices([hover.id], graph, extent) : {};
+
+ surface.selectAll('.layer-hit').selectAll('g.vertex.vertex-hover')
+ .call(draw, d3.values(hovered), 'vertex-hover', graph, zoom);
+ }
+
+ drawVertices.drawHover = function(surface, graph, target, extent, zoom) {
+ if (target === hover) return;
+ hover = target;
+ drawHover(surface, graph, extent, zoom);
+ };
+
+ return drawVertices;
+ }
+
function Map(context) {
var dimensions = [1, 1],
dispatch = d3.dispatch('move', 'drawn'),
@@ -14550,29 +13088,12 @@
exports.actions = actions;
exports.geo = geo;
- exports.svg = svg;
exports.behavior = behavior;
-<<<<<<< HEAD
->>>>>>> ef619c2... external modules for behavior
-=======
exports.modes = modes;
-<<<<<<< HEAD
-<<<<<<< HEAD
->>>>>>> 75901f6... external modules for modes
-=======
-=======
exports.operations = Operations;
-<<<<<<< HEAD
->>>>>>> 422ffee... external modules for operations
-=======
exports.presets = presets;
->>>>>>> ed34eb3... external modules for presets
exports.util = util;
-<<<<<<< HEAD
->>>>>>> 42ce4cf... external modules for util
-=======
exports.validations = validations;
->>>>>>> 44cf7f1... Add external modules to validations
exports.Connection = Connection;
exports.Difference = Difference;
exports.Entity = Entity;
diff --git a/test/index.html b/test/index.html
index 700f35af5..7b0240de3 100644
--- a/test/index.html
+++ b/test/index.html
@@ -42,6 +42,7 @@
+