diff --git a/modules/renderer/tile_layer.js b/modules/renderer/tile_layer.js index 843083e33..243314770 100644 --- a/modules/renderer/tile_layer.js +++ b/modules/renderer/tile_layer.js @@ -1,6 +1,7 @@ import * as d3 from 'd3'; import { d3geoTile } from '../lib/d3.geo.tile'; -import { prefixCSSProperty, functor } from '../util/index'; +import { prefixCSSProperty } from '../util/index'; +import { BackgroundSource } from './background_source.js'; export function TileLayer(context) { var tileSize = 256, @@ -10,7 +11,7 @@ export function TileLayer(context) { tileOrigin, z, transformProp = prefixCSSProperty('Transform'), - source = functor(''); + source = BackgroundSource.None(); // blacklist overlay tiles around Null Island.. diff --git a/modules/svg/layers.js b/modules/svg/layers.js index b2ff09e65..e43cc53f7 100644 --- a/modules/svg/layers.js +++ b/modules/svg/layers.js @@ -24,23 +24,25 @@ export function Layers(projection, context) { svg = selection.selectAll('.surface') .data([0]); - svg.enter() + svg = svg.enter() .append('svg') .attr('class', 'surface') + .merge(svg); + + svg .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(); + + groups.enter() + .append('g') + .attr('class', function(d) { return 'data-layer data-layer-' + d.id; }) + .merge(groups) + .each(function(d) { d3.select(this).call(d.layer); }); } drawLayers.all = function() { diff --git a/modules/svg/midpoints.js b/modules/svg/midpoints.js index 9eadb1037..f134d15c7 100644 --- a/modules/svg/midpoints.js +++ b/modules/svg/midpoints.js @@ -64,13 +64,16 @@ export function Midpoints(projection, context) { if (midpoints[d.id]) return true; - for (var i = 0; i < d.parents.length; i++) - if (filter(d.parents[i])) + for (var i = 0; i < d.parents.length; i++) { + if (filter(d.parents[i])) { return true; + } + } return false; } + var layer = selection.selectAll('.layer-hit'); var groups = layer @@ -78,6 +81,9 @@ export function Midpoints(projection, context) { .filter(midpointFilter) .data(_.values(midpoints), function(d) { return d.id; }); + groups.exit() + .remove(); + var enter = groups.enter() .insert('g', ':first-child') .attr('class', 'midpoint'); @@ -90,11 +96,12 @@ export function Midpoints(projection, context) { .attr('points', '-3,4 5,0 -3,-4') .attr('class', 'fill'); - groups + groups = groups + .merge(enter) .attr('transform', function(d) { var translate = PointTransform(projection), - a = context.entity(d.edge[0]), - b = context.entity(d.edge[1]), + a = graph.entity(d.edge[0]), + b = graph.entity(d.edge[1]), angleVal = Math.round(angle(a, b, projection) * (180 / Math.PI)); return translate(d) + ' rotate(' + angleVal + ')'; }) @@ -106,7 +113,5 @@ export function Midpoints(projection, context) { groups.select('polygon.shadow'); groups.select('polygon.fill'); - groups.exit() - .remove(); }; } diff --git a/modules/svg/points.js b/modules/svg/points.js index 700faa5c3..31f016028 100644 --- a/modules/svg/points.js +++ b/modules/svg/points.js @@ -60,7 +60,7 @@ export function Points(projection, context) { groups.select('.icon') .attr('xlink:href', function(entity) { var preset = context.presets().match(entity, graph); - return preset.icon ? '#' + preset.icon + '-12' : ''; + return (preset && preset.icon) ? '#' + preset.icon + '-12' : ''; }); }; } diff --git a/test/index.html b/test/index.html index b8c2be03a..f425f2ccf 100644 --- a/test/index.html +++ b/test/index.html @@ -104,8 +104,10 @@ + + diff --git a/test/spec/svg/areas.js b/test/spec/svg/areas.js index 2d9b49b69..36cb84901 100644 --- a/test/spec/svg/areas.js +++ b/test/spec/svg/areas.js @@ -1,13 +1,19 @@ describe('iD.svg.Areas', function () { - var surface, - projection = d3.geoProjection(function(x, y) { return [x, y]; }) + var context, surface, + projection = d3.geoProjection(function(x, y) { return [x, -y]; }) + .translate([0, 0]) + .scale(180 / Math.PI) .clipExtent([[0, 0], [Infinity, Infinity]]), all = function() { return true; }, none = function() { return false; }; beforeEach(function () { - surface = d3.select(document.createElementNS('http://www.w3.org/2000/svg', 'svg')) - .call(iD.svg.Layers(projection, iD.Context(window))); + context = iD.Context(window); + d3.select(document.createElement('div')) + .attr('id', 'map') + .call(context.map()); + surface = context.surface(); + iD.setAreaKeys({ building: {}, landuse: {}, @@ -24,7 +30,7 @@ describe('iD.svg.Areas', function () { iD.Way({id: 'w', tags: {building: 'yes'}, nodes: ['a', 'b', 'c', 'a']}) ]); - surface.call(iD.svg.Areas(projection), graph, [graph.entity('w')], none); + surface.call(iD.svg.Areas(projection, context), graph, [graph.entity('w')], none); expect(surface.select('path.way')).to.be.classed('way'); expect(surface.select('path.area')).to.be.classed('area'); @@ -39,7 +45,7 @@ describe('iD.svg.Areas', function () { iD.Way({id: 'w', tags: {building: 'yes'}, nodes: ['a', 'b', 'c', 'a']}) ]); - surface.call(iD.svg.Areas(projection), graph, [graph.entity('w')], none); + surface.call(iD.svg.Areas(projection, context), graph, [graph.entity('w')], none); expect(surface.select('.area')).to.be.classed('tag-building'); expect(surface.select('.area')).to.be.classed('tag-building-yes'); @@ -55,10 +61,10 @@ describe('iD.svg.Areas', function () { iD.Way({id: 'x', tags: {area: 'yes'}, nodes: ['a', 'b', 'd', 'a']}) ]); - surface.call(iD.svg.Areas(projection), graph, [graph.entity('x')], all); + surface.call(iD.svg.Areas(projection, context), graph, [graph.entity('x')], all); graph = graph.remove(graph.entity('x')).remove(graph.entity('d')); - surface.call(iD.svg.Areas(projection), graph, [graph.entity('w')], all); + surface.call(iD.svg.Areas(projection, context), graph, [graph.entity('w')], all); expect(surface.select('.area').size()).to.equal(1); }); @@ -77,30 +83,30 @@ describe('iD.svg.Areas', function () { ]); it('stacks smaller areas above larger ones in a single render', function () { - surface.call(iD.svg.Areas(projection), graph, [graph.entity('s'), graph.entity('l')], none); + surface.call(iD.svg.Areas(projection, context), graph, [graph.entity('s'), graph.entity('l')], none); expect(surface.select('.area:nth-child(1)')).to.be.classed('tag-landuse-park'); expect(surface.select('.area:nth-child(2)')).to.be.classed('tag-building-yes'); }); it('stacks smaller areas above larger ones in a single render (reverse)', function () { - surface.call(iD.svg.Areas(projection), graph, [graph.entity('l'), graph.entity('s')], none); + surface.call(iD.svg.Areas(projection, context), graph, [graph.entity('l'), graph.entity('s')], none); expect(surface.select('.area:nth-child(1)')).to.be.classed('tag-landuse-park'); expect(surface.select('.area:nth-child(2)')).to.be.classed('tag-building-yes'); }); it('stacks smaller areas above larger ones in separate renders', function () { - surface.call(iD.svg.Areas(projection), graph, [graph.entity('s')], none); - surface.call(iD.svg.Areas(projection), graph, [graph.entity('l')], none); + surface.call(iD.svg.Areas(projection, context), graph, [graph.entity('s')], none); + surface.call(iD.svg.Areas(projection, context), graph, [graph.entity('l')], none); expect(surface.select('.area:nth-child(1)')).to.be.classed('tag-landuse-park'); expect(surface.select('.area:nth-child(2)')).to.be.classed('tag-building-yes'); }); it('stacks smaller areas above larger ones in separate renders (reverse)', function () { - surface.call(iD.svg.Areas(projection), graph, [graph.entity('l')], none); - surface.call(iD.svg.Areas(projection), graph, [graph.entity('s')], none); + surface.call(iD.svg.Areas(projection, context), graph, [graph.entity('l')], none); + surface.call(iD.svg.Areas(projection, context), graph, [graph.entity('s')], none); expect(surface.select('.area:nth-child(1)')).to.be.classed('tag-landuse-park'); expect(surface.select('.area:nth-child(2)')).to.be.classed('tag-building-yes'); @@ -116,7 +122,7 @@ describe('iD.svg.Areas', function () { graph = iD.Graph([a, b, c, w, r]), areas = [w, r]; - surface.call(iD.svg.Areas(projection), graph, areas, none); + surface.call(iD.svg.Areas(projection, context), graph, areas, none); expect(surface.select('.fill')).to.be.classed('relation'); }); @@ -130,7 +136,7 @@ describe('iD.svg.Areas', function () { graph = iD.Graph([a, b, c, w, r]), areas = [w, r]; - surface.call(iD.svg.Areas(projection), graph, areas, none); + surface.call(iD.svg.Areas(projection, context), graph, areas, none); expect(surface.selectAll('.stroke').size()).to.equal(0); }); @@ -143,7 +149,7 @@ describe('iD.svg.Areas', function () { r = iD.Relation({members: [{id: w.id, type: 'way'}], tags: {type: 'multipolygon'}}), graph = iD.Graph([a, b, c, w, r]); - surface.call(iD.svg.Areas(projection), graph, [w, r], none); + surface.call(iD.svg.Areas(projection, context), graph, [w, r], none); expect(surface.selectAll('.way.fill').size()).to.equal(0); expect(surface.selectAll('.relation.fill').size()).to.equal(1); @@ -158,7 +164,7 @@ describe('iD.svg.Areas', function () { r = iD.Relation({members: [{id: w.id, type: 'way'}], tags: {type: 'multipolygon'}}), graph = iD.Graph([a, b, c, w, r]); - surface.call(iD.svg.Areas(projection), graph, [w, r], none); + surface.call(iD.svg.Areas(projection, context), graph, [w, r], none); expect(surface.selectAll('.stroke').size()).to.equal(0); }); diff --git a/test/spec/svg/layers.js b/test/spec/svg/layers.js new file mode 100644 index 000000000..ca79b45a2 --- /dev/null +++ b/test/spec/svg/layers.js @@ -0,0 +1,30 @@ +describe('iD.svg.Layers', function () { + var context, container, + projection = d3.geoProjection(function(x, y) { return [x, -y]; }) + .translate([0, 0]) + .scale(180 / Math.PI) + .clipExtent([[0, 0], [Infinity, Infinity]]); + + beforeEach(function () { + context = iD.Context(window); + container = d3.select(document.createElement('div')); + }); + + + it('creates a surface', function () { + container.call(iD.svg.Layers(projection, context)); + expect(container.selectAll('svg')).to.be.classed('surface'); + }); + + it('creates default data layers', function () { + container.call(iD.svg.Layers(projection, context)); + var nodes = container.selectAll('svg .data-layer').nodes(); + expect(nodes.length).to.eql(5); + expect(d3.select(nodes[0])).to.be.classed('data-layer-osm'); + expect(d3.select(nodes[1])).to.be.classed('data-layer-gpx'); + expect(d3.select(nodes[2])).to.be.classed('data-layer-mapillary-images'); + expect(d3.select(nodes[3])).to.be.classed('data-layer-mapillary-signs'); + expect(d3.select(nodes[4])).to.be.classed('data-layer-debug'); + }); + +}); diff --git a/test/spec/svg/lines.js b/test/spec/svg/lines.js index bf848f1d4..65228d87a 100644 --- a/test/spec/svg/lines.js +++ b/test/spec/svg/lines.js @@ -1,15 +1,21 @@ describe('iD.svg.Lines', function () { - var surface, - projection = d3.geoProjection(function(x, y) { return [x, y]; }) + var context, surface, + projection = d3.geoProjection(function(x, y) { return [x, -y]; }) + .translate([0, 0]) + .scale(180 / Math.PI) .clipExtent([[0, 0], [Infinity, Infinity]]), all = function() { return true; }, none = function() { return false; }; beforeEach(function () { - surface = d3.select(document.createElementNS('http://www.w3.org/2000/svg', 'svg')) - .call(iD.svg.Layers(projection, iD.Context(window))); + context = iD.Context(window); + d3.select(document.createElement('div')) + .attr('id', 'map') + .call(context.map()); + surface = context.surface(); }); + it('adds way and line classes', function () { var a = iD.Node({loc: [0, 0]}), b = iD.Node({loc: [1, 1]}), diff --git a/test/spec/svg/midpoints.js b/test/spec/svg/midpoints.js index 3b164c7da..dfb80778c 100644 --- a/test/spec/svg/midpoints.js +++ b/test/spec/svg/midpoints.js @@ -1,15 +1,20 @@ describe('iD.svg.Midpoints', function () { - var surface, - projection = Object, - filter = function() { return true; }, - context; + var context, surface, + projection = d3.geoProjection(function(x, y) { return [x, -y]; }) + .translate([0, 0]) + .scale(180 / Math.PI) + .clipExtent([[0, 0], [Infinity, Infinity]]), + filter = function() { return true; }; beforeEach(function () { context = iD.Context(window); - surface = d3.select(document.createElementNS('http://www.w3.org/2000/svg', 'svg')) - .call(iD.svg.Layers(projection, context)); + d3.select(document.createElement('div')) + .attr('id', 'map') + .call(context.map()); + surface = context.surface(); }); + it('creates midpoint on segment completely within the extent', function () { var a = iD.Node({loc: [0, 0]}), b = iD.Node({loc: [50, 0]}), @@ -21,7 +26,7 @@ describe('iD.svg.Midpoints', function () { context.entity = function(id) { return graph.entity(id); }; surface.call(iD.svg.Midpoints(projection, context), graph, [line], filter, extent); - expect(surface.select('.midpoint').datum().loc).to.eql([25, 0]); + expect(surface.selectAll('.midpoint').datum().loc).to.eql([25, 0]); }); it('doesn\'t create midpoint on segment with pixel length less than 40', function () { @@ -61,7 +66,7 @@ describe('iD.svg.Midpoints', function () { context.entity = function(id) { return graph.entity(id); }; surface.call(iD.svg.Midpoints(projection, context), graph, [line], filter, extent); - expect(surface.select('.midpoint').datum().loc).to.eql([100, 0]); + expect(surface.selectAll('.midpoint').datum().loc).to.eql([100, 0]); }); it('doesn\'t create midpoint on extent edge for segment with pixel length less than 20', function () { diff --git a/test/spec/svg/osm.js b/test/spec/svg/osm.js new file mode 100644 index 000000000..5979d31de --- /dev/null +++ b/test/spec/svg/osm.js @@ -0,0 +1,19 @@ +describe('iD.svg.Osm', function () { + var container; + + beforeEach(function () { + container = d3.select(document.createElementNS('http://www.w3.org/2000/svg', 'svg')); + }); + + it('creates default osm layers', function () { + container.call(iD.svg.Osm()); + var nodes = container.selectAll('.layer-osm').nodes(); + expect(nodes.length).to.eql(5); + expect(d3.select(nodes[0])).to.be.classed('layer-areas'); + expect(d3.select(nodes[1])).to.be.classed('layer-lines'); + expect(d3.select(nodes[2])).to.be.classed('layer-hit'); + expect(d3.select(nodes[3])).to.be.classed('layer-halo'); + expect(d3.select(nodes[4])).to.be.classed('layer-label'); + }); + +}); diff --git a/test/spec/svg/points.js b/test/spec/svg/points.js index 6ea1a9259..59379fdb6 100644 --- a/test/spec/svg/points.js +++ b/test/spec/svg/points.js @@ -1,14 +1,19 @@ describe('iD.svg.Points', function () { - var surface, - projection = Object, - context; + var context, surface, + projection = d3.geoProjection(function(x, y) { return [x, -y]; }) + .translate([0, 0]) + .scale(180 / Math.PI) + .clipExtent([[0, 0], [Infinity, Infinity]]); beforeEach(function () { - context = iD.Context(window).presets(iD.data.presets); - surface = d3.select(document.createElementNS('http://www.w3.org/2000/svg', 'svg')) - .call(iD.svg.Layers(projection, context)); + context = iD.Context(window); + d3.select(document.createElement('div')) + .attr('id', 'map') + .call(context.map()); + surface = context.surface(); }); + it('adds tag classes', function () { var point = iD.Node({tags: {amenity: 'cafe'}, loc: [0, 0]}), graph = iD.Graph([point]); diff --git a/test/spec/svg/vertices.js b/test/spec/svg/vertices.js index 9d48be59e..39e6ee695 100644 --- a/test/spec/svg/vertices.js +++ b/test/spec/svg/vertices.js @@ -1,14 +1,19 @@ describe('iD.svg.Vertices', function () { - var surface, - projection = Object, - context; + var context, surface, + projection = d3.geoProjection(function(x, y) { return [x, -y]; }) + .translate([0, 0]) + .scale(180 / Math.PI) + .clipExtent([[0, 0], [Infinity, Infinity]]); beforeEach(function () { context = iD.Context(window); - surface = d3.select(document.createElementNS('http://www.w3.org/2000/svg', 'svg')) - .call(iD.svg.Layers(projection, context)); + d3.select(document.createElement('div')) + .attr('id', 'map') + .call(context.map()); + surface = context.surface(); }); + it('adds the .shared class to vertices that are members of two or more ways', function () { var node = iD.Node({loc: [0, 0]}), way1 = iD.Way({nodes: [node.id], tags: {highway: 'residential'}}),