diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index b230a443a..51c8e1802 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -296,7 +296,7 @@ correspondence with entities: two vertices. * `iD.svg.Labels` renders the textual [labels](http://mapbox.com/osmdev/2013/02/12/labeling-id/). -* `iD.svg.Surface` sets up a number of layers that ensure that map elements +* `iD.svg.Layers` sets up a number of layers that ensure that map elements appear in an appropriate z-order. ## Other UI diff --git a/css/app.css b/css/app.css index 243b1c43c..52122e1d2 100644 --- a/css/app.css +++ b/css/app.css @@ -2132,7 +2132,7 @@ div.full-screen > button:hover { user-select: none; } -#supersurface, .layer-layer { +#supersurface, .layer { position: absolute; top: 0; left: 0; @@ -2208,7 +2208,7 @@ div.full-screen > button:hover { border-bottom: 1px solid black; } -.infobox .selection-heading { +.infobox .infobox-heading { display: block; border-radius: 4px 0 0 0; padding: 5px 10px; diff --git a/css/map.css b/css/map.css index 88fcb6003..33379001a 100644 --- a/css/map.css +++ b/css/map.css @@ -24,11 +24,11 @@ img.tile-removing { use { pointer-events: none; } /* base styles */ -.layer path:not(.oneway) { fill: none; } /* IE needs :not(.oneway) */ +.layer-osm path:not(.oneway) { fill: none; } /* IE needs :not(.oneway) */ /* the above fill: none rule affects paths in shadow dom only in Firefox */ -.layer use.icon path { fill: #333; } /* FF svg Maki icons */ -.layer .turn use path { fill: #000; } /* FF turn restriction icons */ +.layer-osm use.icon path { fill: #333; } /* FF svg Maki icons */ +.layer-osm .turn use path { fill: #000; } /* FF turn restriction icons */ #turn-only-shape2, #turn-only-u-shape2 { fill: #7092FF; } /* FF turn-only, turn-only-u */ #turn-no-shape2, #turn-no-u-shape2 { fill: #E06D5F; } /* FF turn-no, turn-no-u */ #turn-yes-shape2, #turn-yes-u-shape2 { fill: #8CD05F; } /* FF turn-yes, turn-yes-u */ @@ -1268,7 +1268,7 @@ path.shadow.tag-cutting { /* Surface - unpaved */ path.casing.tag-unpaved { - stroke: #eaeaea; + stroke: #ccc; stroke-linecap: butt; stroke-dasharray: 4, 3; } @@ -1500,7 +1500,7 @@ g.turn circle { } /* GPX Paths */ -div.layer-gpx { +.layer-gpx { pointer-events: none; } diff --git a/index.html b/index.html index f45f10d6c..9f81877df 100644 --- a/index.html +++ b/index.html @@ -56,21 +56,22 @@ - - - - + + + + + - + diff --git a/js/id/renderer/background.js b/js/id/renderer/background.js index cfc8d37e0..dd5e427a6 100644 --- a/js/id/renderer/background.js +++ b/js/id/renderer/background.js @@ -1,11 +1,6 @@ iD.Background = function(context) { var dispatch = d3.dispatch('change'), - baseLayer = iD.TileLayer() - .projection(context.projection), - gpxLayer = iD.GpxLayer(context, dispatch) - .projection(context.projection), - mapillaryImageLayer, - mapillarySignLayer, + baseLayer = iD.TileLayer().projection(context.projection), overlayLayers = []; var backgroundSources; @@ -49,7 +44,8 @@ iD.Background = function(context) { } }); - if (background.showsGpxLayer()) { + var gpx = context.layers().layer('gpx'); + if (gpx && gpx.enabled() && gpx.hasGpx()) { imageryUsed.push('Local GPX'); } @@ -57,19 +53,21 @@ iD.Background = function(context) { } function background(selection) { - var base = selection.selectAll('.background-layer') + var base = selection.selectAll('.layer-background') .data([0]); - base.enter().insert('div', '.layer-data') - .attr('class', 'layer-layer background-layer'); + base.enter() + .insert('div', '.layer-data') + .attr('class', 'layer layer-background'); base.call(baseLayer); var overlays = selection.selectAll('.layer-overlay') .data(overlayLayers, function(d) { return d.source().name(); }); - overlays.enter().insert('div', '.layer-data') - .attr('class', 'layer-layer layer-overlay'); + overlays.enter() + .insert('div', '.layer-data') + .attr('class', 'layer layer-overlay'); overlays.each(function(layer) { d3.select(this).call(layer); @@ -77,52 +75,6 @@ iD.Background = function(context) { overlays.exit() .remove(); - - var gpx = selection.selectAll('.layer-gpx') - .data([0]); - - gpx.enter().insert('div') - .attr('class', 'layer-layer layer-gpx'); - - gpx.call(gpxLayer); - - - var mapillary = iD.services.mapillary, - supportsMapillaryImages = !!mapillary, - supportsMapillarySigns = !!mapillary && mapillary().signsSupported(); - - var mapillaryImages = selection.selectAll('.layer-mapillary-images') - .data(supportsMapillaryImages ? [0] : []); - - mapillaryImages.enter().insert('div') - .attr('class', 'layer-layer layer-mapillary-images'); - - if (supportsMapillaryImages) { - if (!mapillaryImageLayer) { mapillaryImageLayer = iD.MapillaryImageLayer(context); } - mapillaryImages.call(mapillaryImageLayer); - } else { - mapillaryImageLayer = null; - } - - mapillaryImages.exit() - .remove(); - - - var mapillarySigns = selection.selectAll('.layer-mapillary-signs') - .data(supportsMapillarySigns ? [0] : []); - - mapillarySigns.enter().insert('div') - .attr('class', 'layer-layer layer-mapillary-signs'); - - if (supportsMapillarySigns) { - if (!mapillarySignLayer) { mapillarySignLayer = iD.MapillarySignLayer(context); } - mapillarySigns.call(mapillarySignLayer); - } else { - mapillarySignLayer = null; - } - - mapillarySigns.exit() - .remove(); } background.sources = function(extent) { @@ -133,9 +85,6 @@ iD.Background = function(context) { background.dimensions = function(_) { baseLayer.dimensions(_); - gpxLayer.dimensions(_); - if (mapillaryImageLayer) mapillaryImageLayer.dimensions(_); - if (mapillarySignLayer) mapillarySignLayer.dimensions(_); overlayLayers.forEach(function(layer) { layer.dimensions(_); @@ -156,74 +105,6 @@ iD.Background = function(context) { background.baseLayerSource(findSource('Bing')); }; - background.hasGpxLayer = function() { - return !_.isEmpty(gpxLayer.geojson()); - }; - - background.showsGpxLayer = function() { - return background.hasGpxLayer() && gpxLayer.enable(); - }; - - function toDom(x) { - return (new DOMParser()).parseFromString(x, 'text/xml'); - } - - background.gpxLayerFiles = function(fileList) { - var f = fileList[0], - reader = new FileReader(); - - reader.onload = function(e) { - gpxLayer.geojson(toGeoJSON.gpx(toDom(e.target.result))); - iD.ui.MapInMap.gpxLayer.geojson(toGeoJSON.gpx(toDom(e.target.result))); - background.zoomToGpxLayer(); - dispatch.change(); - }; - - reader.readAsText(f); - }; - - background.zoomToGpxLayer = function() { - if (background.hasGpxLayer()) { - var map = context.map(), - viewport = map.trimmedExtent().polygon(), - coords = _.reduce(gpxLayer.geojson().features, function(coords, feature) { - var c = feature.geometry.coordinates; - return _.union(coords, feature.geometry.type === 'Point' ? [c] : c); - }, []); - - if (!iD.geo.polygonIntersectsPolygon(viewport, coords, true)) { - var extent = iD.geo.Extent(d3.geo.bounds(gpxLayer.geojson())); - map.centerZoom(extent.center(), map.trimmedExtentZoom(extent)); - } - } - }; - - background.toggleGpxLayer = function() { - gpxLayer.enable(!gpxLayer.enable()); - iD.ui.MapInMap.gpxLayer.enable(!iD.ui.MapInMap.gpxLayer.enable()); - dispatch.change(); - }; - - background.showsMapillaryImageLayer = function() { - return mapillaryImageLayer && mapillaryImageLayer.enable(); - }; - - background.showsMapillarySignLayer = function() { - return mapillarySignLayer && mapillarySignLayer.enable(); - }; - - background.toggleMapillaryImageLayer = function() { - if (!mapillaryImageLayer) return; - mapillaryImageLayer.enable(!mapillaryImageLayer.enable()); - dispatch.change(); - }; - - background.toggleMapillarySignLayer = function() { - if (!mapillarySignLayer) return; - mapillarySignLayer.enable(!mapillarySignLayer.enable()); - dispatch.change(); - }; - background.showsLayer = function(d) { return d === baseLayer.source() || (d.id === 'custom' && baseLayer.source().id === 'custom') || @@ -317,15 +198,9 @@ iD.Background = function(context) { if (overlay) background.toggleOverlayLayer(overlay); }); - var gpx = q.gpx; - if (gpx) { - d3.text(gpx, function(err, gpxTxt) { - if (!err) { - gpxLayer.geojson(toGeoJSON.gpx(toDom(gpxTxt))); - iD.ui.MapInMap.gpxLayer.geojson(toGeoJSON.gpx(toDom(gpxTxt))); - dispatch.change(); - } - }); + if (q.gpx) { + var gpx = context.layers().layer('gpx'); + if (gpx) { gpx.url(q.gpx); } } }; diff --git a/js/id/renderer/gpx_layer.js b/js/id/renderer/gpx_layer.js deleted file mode 100644 index 9557aa1a4..000000000 --- a/js/id/renderer/gpx_layer.js +++ /dev/null @@ -1,101 +0,0 @@ -iD.GpxLayer = function(context) { - var projection, - gj = {}, - enable = true, - svg; - - function render(selection) { - svg = selection.selectAll('svg') - .data([render]); - - svg.enter() - .append('svg'); - - svg.style('display', enable ? 'block' : 'none'); - - var paths = svg - .selectAll('path') - .data([gj]); - - paths - .enter() - .append('path') - .attr('class', 'gpx'); - - var path = d3.geo.path() - .projection(projection); - - paths - .attr('d', path); - - if (typeof gj.features !== 'undefined') { - svg - .selectAll('text') - .remove(); - - svg - .selectAll('path') - .data(gj.features) - .enter() - .append('text') - .attr('class', 'gpx') - .text(function(d) { - return d.properties.desc || d.properties.name; - }) - .attr('x', function(d) { - var centroid = path.centroid(d); - return centroid[0] + 5; - }) - .attr('y', function(d) { - var centroid = path.centroid(d); - return centroid[1]; - }); - } - } - - render.projection = function(_) { - if (!arguments.length) return projection; - projection = _; - return render; - }; - - render.enable = function(_) { - if (!arguments.length) return enable; - enable = _; - return render; - }; - - render.geojson = function(_) { - if (!arguments.length) return gj; - gj = _; - return render; - }; - - render.dimensions = function(_) { - if (!arguments.length) return svg.dimensions(); - svg.dimensions(_); - return render; - }; - - render.id = 'layer-gpx'; - - 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; - context.background().gpxLayerFiles(d3.event.dataTransfer.files); - }) - .on('dragenter.localgpx', over) - .on('dragexit.localgpx', over) - .on('dragover.localgpx', over); - - return render; -}; diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index a8d70826d..f2fa3242f 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -12,13 +12,16 @@ iD.Map = function(context) { transformStart, transformed = false, minzoom = 0, - points = iD.svg.Points(projection, context), - vertices = iD.svg.Vertices(projection, context), - lines = iD.svg.Lines(projection), - areas = iD.svg.Areas(projection), - midpoints = iD.svg.Midpoints(projection, context), - labels = iD.svg.Labels(projection, context), - supersurface, surface, + drawLayers = iD.svg.Layers(projection, context), + drawPoints = iD.svg.Points(projection, context), + drawVertices = iD.svg.Vertices(projection, context), + drawLines = iD.svg.Lines(projection), + drawAreas = iD.svg.Areas(projection), + drawMidpoints = iD.svg.Midpoints(projection, context), + drawLabels = iD.svg.Labels(projection, context), + supersurface, + wrapper, + surface, mouse, mousemove; @@ -35,18 +38,21 @@ iD.Map = function(context) { .call(zoom); supersurface = selection.append('div') - .attr('id', 'supersurface'); - - // Mapillary streetsigns require supersurface transform to have - // a value in order to do correct foreignObject positioning in Chrome - iD.util.setTransform(supersurface, 0, 0); + .attr('id', 'supersurface') + .call(iD.util.setTransform, 0, 0); // Need a wrapper div because Opera can't cope with an absolutely positioned // SVG element: http://bl.ocks.org/jfirebaugh/6fbfbd922552bf776c16 - var dataLayer = supersurface.append('div') - .attr('class', 'layer-layer layer-data'); + wrapper = supersurface + .append('div') + .attr('class', 'layer layer-data'); - map.surface = surface = dataLayer.append('svg') + map.surface = surface = wrapper + .call(drawLayers) + .selectAll('.surface') + .attr('id', 'surface'); + + surface .on('mousedown.zoom', function() { if (d3.event.button === 2) { d3.event.stopPropagation(); @@ -55,30 +61,28 @@ iD.Map = function(context) { .on('mouseup.zoom', function() { if (resetTransform()) redraw(); }) - .attr('id', 'surface') - .call(iD.svg.Surface(context)); + .on('mousemove.map', function() { + mousemove = d3.event; + }) + .on('mouseover.vertices', function() { + if (map.editable() && !transformed) { + var hover = d3.event.target.__data__; + surface.call(drawVertices.drawHover, context.graph(), hover, map.extent(), map.zoom()); + dispatch.drawn({full: false}); + } + }) + .on('mouseout.vertices', function() { + if (map.editable() && !transformed) { + var hover = d3.event.relatedTarget && d3.event.relatedTarget.__data__; + surface.call(drawVertices.drawHover, context.graph(), hover, map.extent(), map.zoom()); + dispatch.drawn({full: false}); + } + }); - supersurface.call(context.background()); - surface.on('mousemove.map', function() { - mousemove = d3.event; - }); + supersurface + .call(context.background()); - surface.on('mouseover.vertices', function() { - if (map.editable() && !transformed) { - var hover = d3.event.target.__data__; - surface.call(vertices.drawHover, context.graph(), hover, map.extent(), map.zoom()); - dispatch.drawn({full: false}); - } - }); - - surface.on('mouseout.vertices', function() { - if (map.editable() && !transformed) { - var hover = d3.event.relatedTarget && d3.event.relatedTarget.__data__; - surface.call(vertices.drawHover, context.graph(), hover, map.extent(), map.zoom()); - dispatch.drawn({full: false}); - } - }); context.on('enter.map', function() { if (map.editable() && !transformed) { @@ -87,15 +91,16 @@ iD.Map = function(context) { graph = context.graph(); all = context.features().filter(all, graph); - surface.call(vertices, graph, all, filter, map.extent(), map.zoom()); - surface.call(midpoints, graph, all, filter, map.trimmedExtent()); + surface + .call(drawVertices, graph, all, filter, map.extent(), map.zoom()) + .call(drawMidpoints, graph, all, filter, map.trimmedExtent()); dispatch.drawn({full: false}); } }); map.dimensions(selection.dimensions()); - labels.supersurface(supersurface); + drawLabels.supersurface(supersurface); } function pxCenter() { return [dimensions[0] / 2, dimensions[1] / 2]; } @@ -133,19 +138,19 @@ iD.Map = function(context) { data = features.filter(data, graph); surface - .call(vertices, graph, data, filter, map.extent(), map.zoom()) - .call(lines, graph, data, filter) - .call(areas, graph, data, filter) - .call(midpoints, graph, data, filter, map.trimmedExtent()) - .call(labels, graph, data, filter, dimensions, !difference && !extent) - .call(points, data, filter); + .call(drawVertices, graph, data, filter, map.extent(), map.zoom()) + .call(drawLines, graph, data, filter) + .call(drawAreas, graph, data, filter) + .call(drawMidpoints, graph, data, filter, map.trimmedExtent()) + .call(drawLabels, graph, data, filter, dimensions, !difference && !extent) + .call(drawPoints, graph, data, filter); dispatch.drawn({full: true}); } function editOff() { context.features().resetStats(); - surface.selectAll('.layer *').remove(); + surface.selectAll('.layer-osm *').remove(); dispatch.drawn({full: true}); } @@ -185,6 +190,8 @@ iD.Map = function(context) { function resetTransform() { if (!transformed) return false; + + surface.selectAll('.radial-menu').interrupt().remove(); iD.util.setTransform(supersurface, 0, 0); transformed = false; return true; @@ -212,6 +219,7 @@ iD.Map = function(context) { supersurface.call(context.background()); } + // OSM if (map.editable()) { context.loadTiles(projection, dimensions); drawVector(difference, extent); @@ -219,6 +227,9 @@ iD.Map = function(context) { editOff(); } + wrapper + .call(drawLayers); + transformStart = [ projection.scale() * 2 * Math.PI, projection.translate().slice()]; @@ -328,7 +339,7 @@ iD.Map = function(context) { if (!arguments.length) return dimensions; var center = map.center(); dimensions = _; - surface.dimensions(dimensions); + drawLayers.dimensions(dimensions); context.background().dimensions(dimensions); projection.clipExtent([[0, 0], dimensions]); mouse = iD.util.fastMouse(supersurface.node()); @@ -474,5 +485,7 @@ iD.Map = function(context) { return map; }; + map.layers = drawLayers; + return d3.rebind(map, dispatch, 'on'); }; diff --git a/js/id/svg/areas.js b/js/id/svg/areas.js index ebc77101f..e4121e33d 100644 --- a/js/id/svg/areas.js +++ b/js/id/svg/areas.js @@ -82,7 +82,7 @@ iD.svg.Areas = function(projection) { .remove(); var areagroup = surface - .select('.layer-areas') + .selectAll('.layer-areas') .selectAll('g.areagroup') .data(['fill', 'shadow', 'stroke']); diff --git a/js/id/svg/defs.js b/js/id/svg/defs.js index 03054e377..cc6f8ad48 100644 --- a/js/id/svg/defs.js +++ b/js/id/svg/defs.js @@ -15,7 +15,7 @@ iD.svg.Defs = function(context) { }; } - return function (selection) { + return function drawDefs(selection) { var defs = selection.append('defs'); // marker diff --git a/js/id/svg/gpx.js b/js/id/svg/gpx.js new file mode 100644 index 000000000..b0700ccbc --- /dev/null +++ b/js/id/svg/gpx.js @@ -0,0 +1,167 @@ +iD.svg.Gpx = function(projection, context) { + var showLabels = true, + layer; + + function init() { + if (iD.svg.Gpx.initialized) return; // run once + + iD.svg.Gpx.geojson = {}; + iD.svg.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); + + iD.svg.Gpx.initialized = true; + } + + + function drawGpx(surface) { + var geojson = iD.svg.Gpx.geojson, + enabled = iD.svg.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') + .text(function(d) { + return d.properties.desc || d.properties.name; + }); + + labels.exit() + .remove(); + + labels + .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'); + } + + function redraw() { + context.pan([0,0]); + } + + drawGpx.showLabels = function(_) { + if (!arguments.length) return showLabels; + showLabels = _; + return this; + }; + + drawGpx.enabled = function(_) { + if (!arguments.length) return iD.svg.Gpx.enabled; + iD.svg.Gpx.enabled = _; + return this; + }; + + drawGpx.hasGpx = function() { + var geojson = iD.svg.Gpx.geojson; + return (!(_.isEmpty(geojson) || _.isEmpty(geojson.features))); + }; + + drawGpx.geojson = function(gj) { + if (!arguments.length) return iD.svg.Gpx.geojson; + if (_.isEmpty(gj) || _.isEmpty(gj.features)) return this; + iD.svg.Gpx.geojson = gj; + return this; + }; + + drawGpx.url = function(url) { + d3.text(url, function(err, data) { + if (!err) { + this.geojson(toGeoJSON.gpx(toDom(data))); + redraw(); + } + }); + return this; + }; + + drawGpx.files = function(fileList) { + var f = fileList[0], + reader = new FileReader(); + + reader.onload = function(e) { + this.geojson(toGeoJSON.gpx(toDom(e.target.result))).fitZoom(); + redraw(); + }; + + reader.readAsText(f); + return this; + }; + + drawGpx.fitZoom = function() { + if (!this.hasGpx()) return this; + var geojson = iD.svg.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 (!iD.geo.polygonIntersectsPolygon(viewport, coords, true)) { + var extent = iD.geo.Extent(d3.geo.bounds(geojson)); + map.centerZoom(extent.center(), map.trimmedExtentZoom(extent)); + } + + return this; + }; + + init(); + return drawGpx; +}; diff --git a/js/id/svg/icon.js b/js/id/svg/icon.js index 3c8d2f02a..61a432d5a 100644 --- a/js/id/svg/icon.js +++ b/js/id/svg/icon.js @@ -1,5 +1,5 @@ iD.svg.Icon = function(name, svgklass, useklass) { - return function (selection) { + return function drawIcon(selection) { selection.selectAll('svg') .data([0]) .enter() diff --git a/js/id/svg/labels.js b/js/id/svg/labels.js index e85a9df9c..d4639a71b 100644 --- a/js/id/svg/labels.js +++ b/js/id/svg/labels.js @@ -131,7 +131,6 @@ iD.svg.Labels = function(projection, context) { } function drawPointLabels(group, entities, filter, classes, labels) { - var texts = group.selectAll('text.' + classes) .filter(filter) .data(entities, iD.Entity.key); @@ -248,9 +247,8 @@ iD.svg.Labels = function(projection, context) { var rtree = rbush(), rectangles = {}; - function labels(surface, graph, entities, filter, dimensions, fullRedraw) { - - var hidePoints = !surface.select('.node.point').node(); + 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([]); @@ -409,8 +407,8 @@ iD.svg.Labels = function(projection, context) { return v; } - var label = surface.select('.layer-label'), - halo = surface.select('.layer-halo'); + var label = surface.selectAll('.layer-label'), + halo = surface.selectAll('.layer-halo'); // points drawPointLabels(label, labelled.point, filter, 'pointlabel', positions.point); @@ -427,7 +425,7 @@ iD.svg.Labels = function(projection, context) { drawAreaIcons(label, labelled.area, filter, 'arealabel-icon', positions.area); } - labels.supersurface = function(supersurface) { + drawLabels.supersurface = function(supersurface) { supersurface .on('mousemove.hidelabels', hideOnMouseover) .on('mousedown.hidelabels', function () { @@ -438,5 +436,5 @@ iD.svg.Labels = function(projection, context) { }); }; - return labels; + return drawLabels; }; diff --git a/js/id/svg/layers.js b/js/id/svg/layers.js new file mode 100644 index 000000000..55733d14f --- /dev/null +++ b/js/id/svg/layers.js @@ -0,0 +1,80 @@ +iD.svg.Layers = function(projection, context) { + var svg = d3.select(null), + layers = [ + { id: 'osm', layer: iD.svg.Osm(projection, context) }, + { id: 'gpx', layer: iD.svg.Gpx(projection, context) }, + { id: 'mapillary-images', layer: iD.svg.MapillaryImages(projection, context) }, + { id: 'mapillary-signs', layer: iD.svg.MapillarySigns(projection, context) } + ]; + + + 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, 'id', id); + return obj && obj.layer; + }; + + drawLayers.only = function(what) { + var arr = [].concat(what); + drawLayers.remove(_.difference(_.pluck(layers, 'id'), arr)); + return this; + }; + + drawLayers.remove = function(what) { + var arr = [].concat(what); + arr.forEach(function(id) { + layers = _.reject(layers, 'id', id); + }); + return this; + }; + + drawLayers.add = function(what) { + var arr = [].concat(what); + arr.forEach(function(obj) { + if ('id' in obj && 'layer' in obj) { + layers.push(obj); + } + }); + 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 drawLayers; +}; diff --git a/js/id/svg/lines.js b/js/id/svg/lines.js index d62d0c74e..216612416 100644 --- a/js/id/svg/lines.js +++ b/js/id/svg/lines.js @@ -50,7 +50,7 @@ iD.svg.Lines = function(projection) { }); var layergroup = surface - .select('.layer-lines') + .selectAll('.layer-lines') .selectAll('g.layergroup') .data(d3.range(-10, 11)); diff --git a/js/id/renderer/mapillary_image_layer.js b/js/id/svg/mapillary_images.js similarity index 78% rename from js/id/renderer/mapillary_image_layer.js rename to js/id/svg/mapillary_images.js index 544ef011b..bc8d0d6a6 100644 --- a/js/id/renderer/mapillary_image_layer.js +++ b/js/id/svg/mapillary_images.js @@ -1,10 +1,16 @@ -iD.MapillaryImageLayer = function(context) { +iD.svg.MapillaryImages = function(projection, context) { var debouncedRedraw = _.debounce(function () { context.pan([0,0]); }, 1000), - enabled = false, minZoom = 12, layer = d3.select(null), _mapillary; + + function init() { + if (iD.svg.MapillaryImages.initialized) return; // run once + iD.svg.MapillaryImages.enabled = false; + iD.svg.MapillaryImages.initialized = true; + } + function getMapillary() { if (iD.services.mapillary && !_mapillary) { _mapillary = iD.services.mapillary().on('loadedImages', debouncedRedraw); @@ -19,7 +25,7 @@ iD.MapillaryImageLayer = function(context) { if (!mapillary) return; var thumb = mapillary.selectedThumbnail(), - posX = context.projection(image.loc)[0], + posX = projection(image.loc)[0], width = layer.dimensions()[0], position = (posX < width / 2) ? 'right' : 'left'; @@ -71,14 +77,14 @@ iD.MapillaryImageLayer = function(context) { } function transform(d) { - var t = iD.svg.PointTransform(context.projection)(d); + var t = iD.svg.PointTransform(projection)(d); if (d.ca) t += ' rotate(' + Math.floor(d.ca) + ',0,0)'; return t; } - function drawMarkers() { + function update() { var mapillary = getMapillary(), - data = (mapillary ? mapillary.images(context.projection, layer.dimensions()) : []); + data = (mapillary ? mapillary.images(projection, layer.dimensions()) : []); var markers = layer.selectAll('.viewfield-group') .data(data, function(d) { return d.key; }); @@ -107,16 +113,17 @@ iD.MapillaryImageLayer = function(context) { .attr('transform', transform); } - function render(selection) { - var mapillary = getMapillary(); + function drawImages(selection) { + var enabled = iD.svg.MapillaryImages.enabled, + mapillary = getMapillary(); - layer = selection.selectAll('svg') + layer = selection.selectAll('.layer-mapillary-images') .data(mapillary ? [0] : []); layer.enter() - .append('svg') + .append('g') + .attr('class', 'layer-mapillary-images') .style('display', enabled ? 'block' : 'none') - .dimensions(context.map().dimensions()) .on('click', function() { // deselect/select var mapillary = getMapillary(); if (!mapillary) return; @@ -152,31 +159,35 @@ iD.MapillaryImageLayer = function(context) { if (enabled) { if (mapillary && ~~context.map().zoom() >= minZoom) { editOn(); - drawMarkers(); - mapillary.loadImages(context.projection, layer.dimensions()); + update(); + mapillary.loadImages(projection, layer.dimensions()); } else { editOff(); } } } - render.enable = function(_) { - if (!arguments.length) return enabled; - enabled = _; - if (enabled) { + drawImages.enabled = function(_) { + if (!arguments.length) return iD.svg.MapillaryImages.enabled; + iD.svg.MapillaryImages.enabled = _; + if (iD.svg.MapillaryImages.enabled) { showLayer(); } else { hideLayer(); } - return render; + return this; }; - render.dimensions = function(_) { - if (layer.empty()) return null; + drawImages.supported = function() { + return !!getMapillary(); + }; + + drawImages.dimensions = function(_) { if (!arguments.length) return layer.dimensions(); layer.dimensions(_); - return render; + return this; }; - return render; + init(); + return drawImages; }; diff --git a/js/id/renderer/mapillary_sign_layer.js b/js/id/svg/mapillary_signs.js similarity index 74% rename from js/id/renderer/mapillary_sign_layer.js rename to js/id/svg/mapillary_signs.js index 88dea7292..57b0ca443 100644 --- a/js/id/renderer/mapillary_sign_layer.js +++ b/js/id/svg/mapillary_signs.js @@ -1,10 +1,16 @@ -iD.MapillarySignLayer = function(context) { +iD.svg.MapillarySigns = function(projection, context) { var debouncedRedraw = _.debounce(function () { context.pan([0,0]); }, 1000), - enabled = false, minZoom = 12, layer = d3.select(null), _mapillary; + + function init() { + if (iD.svg.MapillarySigns.initialized) return; // run once + iD.svg.MapillarySigns.enabled = false; + iD.svg.MapillarySigns.initialized = true; + } + function getMapillary() { if (iD.services.mapillary && !_mapillary) { _mapillary = iD.services.mapillary().on('loadedSigns', debouncedRedraw); @@ -19,7 +25,7 @@ iD.MapillarySignLayer = function(context) { if (!mapillary) return; var thumb = mapillary.selectedThumbnail(), - posX = context.projection(image.loc)[0], + posX = projection(image.loc)[0], width = layer.dimensions()[0], position = (posX < width / 2) ? 'right' : 'left'; @@ -61,12 +67,11 @@ iD.MapillarySignLayer = function(context) { layer.style('display', 'none'); } - function drawSigns() { + function update() { var mapillary = getMapillary(), - data = (mapillary ? mapillary.signs(context.projection, layer.dimensions()) : []); + data = (mapillary ? mapillary.signs(projection, layer.dimensions()) : []); - var signs = layer.select('.mapillary-sign-offset') - .selectAll('.icon-sign') + var signs = layer.selectAll('.icon-sign') .data(data, function(d) { return d.key; }); // Enter @@ -111,21 +116,20 @@ iD.MapillarySignLayer = function(context) { // Update signs - .attr('transform', iD.svg.PointTransform(context.projection)); + .attr('transform', iD.svg.PointTransform(projection)); } - function render(selection) { - var mapillary = getMapillary(); + function drawSigns(selection) { + var enabled = iD.svg.MapillarySigns.enabled, + mapillary = getMapillary(); - layer = selection.selectAll('svg') + layer = selection.selectAll('.layer-mapillary-signs') .data(mapillary ? [0] : []); layer.enter() - .append('svg') - .style('display', enabled ? 'block' : 'none') - .dimensions(context.map().dimensions()) .append('g') - .attr('class', 'mapillary-sign-offset') + .attr('class', 'layer-mapillary-signs') + .style('display', enabled ? 'block' : 'none') .attr('transform', 'translate(-16, -16)'); // center signs on loc layer.exit() @@ -134,31 +138,36 @@ iD.MapillarySignLayer = function(context) { if (enabled) { if (mapillary && ~~context.map().zoom() >= minZoom) { editOn(); - drawSigns(); - mapillary.loadSigns(context, context.projection, layer.dimensions()); + update(); + mapillary.loadSigns(context, projection, layer.dimensions()); } else { editOff(); } } } - render.enable = function(_) { - if (!arguments.length) return enabled; - enabled = _; - if (enabled) { + drawSigns.enabled = function(_) { + if (!arguments.length) return iD.svg.MapillarySigns.enabled; + iD.svg.MapillarySigns.enabled = _; + if (iD.svg.MapillarySigns.enabled) { showLayer(); } else { hideLayer(); } - return render; + return this; }; - render.dimensions = function(_) { - if (layer.empty()) return null; + drawSigns.supported = function() { + var mapillary = getMapillary(); + return (mapillary && mapillary.signsSupported()); + }; + + drawSigns.dimensions = function(_) { if (!arguments.length) return layer.dimensions(); layer.dimensions(_); - return render; + return this; }; - return render; + init(); + return drawSigns; }; diff --git a/js/id/svg/midpoints.js b/js/id/svg/midpoints.js index 86a6a12a1..3daf82e0e 100644 --- a/js/id/svg/midpoints.js +++ b/js/id/svg/midpoints.js @@ -67,7 +67,7 @@ iD.svg.Midpoints = function(projection, context) { return false; } - var groups = surface.select('.layer-hit').selectAll('g.midpoint') + var groups = surface.selectAll('.layer-hit').selectAll('g.midpoint') .filter(midpointFilter) .data(_.values(midpoints), function(d) { return d.id; }); diff --git a/js/id/svg/osm.js b/js/id/svg/osm.js new file mode 100644 index 000000000..ac6387bdb --- /dev/null +++ b/js/id/svg/osm.js @@ -0,0 +1,9 @@ +iD.svg.Osm = function() { + 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; }); + }; +}; diff --git a/js/id/svg/points.js b/js/id/svg/points.js index 378b52d49..8bcf86fd9 100644 --- a/js/id/svg/points.js +++ b/js/id/svg/points.js @@ -10,16 +10,15 @@ iD.svg.Points = function(projection, context) { return b.loc[1] - a.loc[1]; } - return function drawPoints(surface, entities, filter) { - var graph = context.graph(), - wireframe = surface.classed('fill-wireframe'), + 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.select('.layer-hit').selectAll('g.point') + var groups = surface.selectAll('.layer-hit').selectAll('g.point') .filter(filter) .data(points, iD.Entity.key); @@ -49,7 +48,7 @@ iD.svg.Points = function(projection, context) { groups.select('.stroke'); groups.select('.icon') .attr('xlink:href', function(entity) { - var preset = context.presets().match(entity, context.graph()); + var preset = context.presets().match(entity, graph); return preset.icon ? '#' + preset.icon + '-12' : ''; }); diff --git a/js/id/svg/surface.js b/js/id/svg/surface.js deleted file mode 100644 index fbd7ba99d..000000000 --- a/js/id/svg/surface.js +++ /dev/null @@ -1,14 +0,0 @@ -iD.svg.Surface = function() { - return function (selection) { - selection.selectAll('defs') - .data([0]) - .enter() - .append('defs'); - - var layers = selection.selectAll('.layer') - .data(['areas', 'lines', 'hit', 'halo', 'label']); - - layers.enter().append('g') - .attr('class', function(d) { return 'layer layer-' + d; }); - }; -}; diff --git a/js/id/svg/turns.js b/js/id/svg/turns.js index 98955ec98..c5007a745 100644 --- a/js/id/svg/turns.js +++ b/js/id/svg/turns.js @@ -1,5 +1,5 @@ iD.svg.Turns = function(projection) { - return function(surface, graph, turns) { + return function drawTurns(surface, graph, turns) { function key(turn) { return [turn.from.node + turn.via.node + turn.to.node].join('-'); } @@ -13,7 +13,7 @@ iD.svg.Turns = function(projection) { (!turn.indirect_restriction && /^only_/.test(restriction) ? 'only' : 'no') + u; } - var groups = surface.select('.layer-hit').selectAll('g.turn') + var groups = surface.selectAll('.layer-hit').selectAll('g.turn') .data(turns, key); // Enter diff --git a/js/id/svg/vertices.js b/js/id/svg/vertices.js index 73b971880..c838c7daa 100644 --- a/js/id/svg/vertices.js +++ b/js/id/svg/vertices.js @@ -48,19 +48,10 @@ iD.svg.Vertices = function(projection, context) { function draw(selection, vertices, klass, graph, zoom) { var icons = {}, - z; + z = (zoom < 17 ? 0 : zoom < 18 ? 1 : 2); - if (zoom < 17) { - z = 0; - } else if (zoom < 18) { - z = 1; - } else { - z = 2; - } - - var groups = selection.data(vertices, function(entity) { - return iD.Entity.key(entity); - }); + var groups = selection + .data(vertices, iD.Entity.key); function icon(entity) { if (entity.id in icons) return icons[entity.id]; @@ -162,7 +153,7 @@ iD.svg.Vertices = function(projection, context) { } } - surface.select('.layer-hit').selectAll('g.vertex.vertex-persistent') + surface.selectAll('.layer-hit').selectAll('g.vertex.vertex-persistent') .filter(filter) .call(draw, vertices, 'vertex-persistent', graph, zoom); @@ -172,15 +163,14 @@ iD.svg.Vertices = function(projection, context) { function drawHover(surface, graph, extent, zoom) { var hovered = hover ? siblingAndChildVertices([hover.id], graph, extent) : {}; - surface.select('.layer-hit').selectAll('g.vertex.vertex-hover') + surface.selectAll('.layer-hit').selectAll('g.vertex.vertex-hover') .call(draw, d3.values(hovered), 'vertex-hover', graph, zoom); } - drawVertices.drawHover = function(surface, graph, _, extent, zoom) { - if (hover !== _) { - hover = _; - drawHover(surface, graph, extent, zoom); - } + drawVertices.drawHover = function(surface, graph, target, extent, zoom) { + if (target === hover) return; + hover = target; + drawHover(surface, graph, extent, zoom); }; return drawVertices; diff --git a/js/id/ui.js b/js/id/ui.js index ddcd31649..3c7a51b90 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -28,18 +28,14 @@ iD.ui = function(context) { .attr('id', 'bar') .attr('class', 'fillD'); - var m = content.append('div') + content.append('div') .attr('id', 'map') .call(map); - content.append('div') - .attr('class', 'map-in-map') - .style('display', 'none') + content .call(iD.ui.MapInMap(context)); content.append('div') - .attr('class', 'infobox fillD2') - .style('display', 'none') .call(iD.ui.Info(context)); bar.append('div') @@ -169,8 +165,8 @@ iD.ui = function(context) { var mapDimensions = map.dimensions(); d3.select(window).on('resize.editor', function() { - mapDimensions = m.dimensions(); - map.dimensions(m.dimensions()); + mapDimensions = content.dimensions(null); + map.dimensions(mapDimensions); }); function pan(d) { diff --git a/js/id/ui/background.js b/js/id/ui/background.js index adb0a893c..3a2abf730 100644 --- a/js/id/ui/background.js +++ b/js/id/ui/background.js @@ -24,7 +24,7 @@ iD.ui.Background = function(context) { } function setOpacity(d) { - var bg = context.container().selectAll('.background-layer') + var bg = context.container().selectAll('.layer-background') .transition() .style('opacity', d) .attr('data-opacity', d); diff --git a/js/id/ui/contributors.js b/js/id/ui/contributors.js index 86a38c067..8bf709e71 100644 --- a/js/id/ui/contributors.js +++ b/js/id/ui/contributors.js @@ -1,7 +1,11 @@ iD.ui.Contributors = function(context) { - function update(selection) { + var debouncedUpdate = _.debounce(function() { update(); }, 1000), + limit = 4, + hidden = false, + wrap = d3.select(null); + + function update() { var users = {}, - limit = 4, entities = context.intersects(context.map().extent()); entities.forEach(function(entity) { @@ -11,7 +15,7 @@ iD.ui.Contributors = function(context) { var u = Object.keys(users), subset = u.slice(0, u.length > limit ? limit - 1 : limit); - selection.html('') + wrap.html('') .call(iD.svg.Icon('#icon-nearby', 'pre-text light')); var userList = d3.select(document.createElement('span')); @@ -37,29 +41,32 @@ iD.ui.Contributors = function(context) { }) .text(u.length - limit + 1); - selection.append('span') - .html(t('contributors.truncated_list', {users: userList.html(), count: count.html()})); + wrap.append('span') + .html(t('contributors.truncated_list', { users: userList.html(), count: count.html() })); + } else { - selection.append('span') - .html(t('contributors.list', {users: userList.html()})); + wrap.append('span') + .html(t('contributors.list', { users: userList.html() })); } if (!u.length) { - selection.transition().style('opacity', 0); - } else if (selection.style('opacity') === '0') { - selection.transition().style('opacity', 1); + hidden = true; + wrap + .transition() + .style('opacity', 0); + + } else if (hidden) { + wrap + .transition() + .style('opacity', 1); } } return function(selection) { - update(selection); + wrap = selection; + update(); - context.connection().on('loaded.contributors', function() { - update(selection); - }); - - context.map().on('move.contributors', _.debounce(function() { - update(selection); - }, 500)); + context.connection().on('loaded.contributors', debouncedUpdate); + context.map().on('move.contributors', debouncedUpdate); }; }; diff --git a/js/id/ui/info.js b/js/id/ui/info.js index fee64d1ba..948d08f19 100644 --- a/js/id/ui/info.js +++ b/js/id/ui/info.js @@ -1,6 +1,7 @@ iD.ui.Info = function(context) { var key = iD.ui.cmd('⌘I'), - imperial = (iD.detect().locale.toLowerCase() === 'en-us'); + imperial = (iD.detect().locale.toLowerCase() === 'en-us'), + hidden = true; function info(selection) { function radiansToMeters(r) { @@ -95,7 +96,7 @@ iD.ui.Info = function(context) { function redraw() { - if (hidden()) return; + if (hidden) return; var resolver = context.graph(), selected = _.filter(context.selectedIDs(), function(e) { return context.hasEntity(e); }), @@ -103,9 +104,9 @@ iD.ui.Info = function(context) { extent = iD.geo.Extent(), entity; - selection.html(''); - selection.append('h4') - .attr('class', 'selection-heading fillD') + wrap.html(''); + wrap.append('h4') + .attr('class', 'infobox-heading fillD') .text(singular || t('infobox.selected', { n: selected.length })); if (!selected.length) return; @@ -118,16 +119,16 @@ iD.ui.Info = function(context) { center = extent.center(); - var list = selection.append('ul'); + var list = wrap.append('ul'); - // multiple selection, just display extent center.. + // multiple wrap, just display extent center.. if (!singular) { list.append('li') .text(t('infobox.center') + ': ' + center[0].toFixed(5) + ', ' + center[1].toFixed(5)); return; } - // single selection, display details.. + // single wrap, display details.. if (!entity) return; var geometry = entity.geometry(resolver); @@ -156,7 +157,7 @@ iD.ui.Info = function(context) { var toggle = imperial ? 'imperial' : 'metric'; - selection.append('a') + wrap.append('a') .text(t('infobox.' + toggle)) .attr('href', '#') .attr('class', 'button') @@ -178,26 +179,13 @@ iD.ui.Info = function(context) { } - function hidden() { - return selection.style('display') === 'none'; - } - - function toggle() { if (d3.event) d3.event.preventDefault(); - if (hidden()) { - selection - .style('display', 'block') - .style('opacity', 0) - .transition() - .duration(200) - .style('opacity', 1); + hidden = !hidden; - redraw(); - - } else { - selection + if (hidden) { + wrap .style('display', 'block') .style('opacity', 1) .transition() @@ -206,9 +194,27 @@ iD.ui.Info = function(context) { .each('end', function() { d3.select(this).style('display', 'none'); }); + } else { + wrap + .style('display', 'block') + .style('opacity', 0) + .transition() + .duration(200) + .style('opacity', 1); + + redraw(); } } + + var wrap = selection.selectAll('.infobox') + .data([0]); + + wrap.enter() + .append('div') + .attr('class', 'infobox fillD2') + .style('display', (hidden ? 'none' : 'block')); + context.map() .on('drawn.info', redraw); diff --git a/js/id/ui/map_data.js b/js/id/ui/map_data.js index 0298910e4..9ca8b9ace 100644 --- a/js/id/ui/map_data.js +++ b/js/id/ui/map_data.js @@ -1,6 +1,7 @@ iD.ui.MapData = function(context) { var key = 'F', features = context.features().keys(), + layers = context.layers(), fills = ['wireframe', 'partial', 'full'], fillDefault = context.storage('area-fill') || 'partial', fillSelected = fillDefault; @@ -38,30 +39,38 @@ iD.ui.MapData = function(context) { update(); } + function toggleLayer(which) { + var layer = layers.layer(which); + if (layer) { + layer.enabled(!layer.enabled()); + update(); + } + } + function clickGpx() { - context.background().toggleGpxLayer(); - update(); + toggleLayer('gpx'); } function clickMapillaryImages() { - context.background().toggleMapillaryImageLayer(); - update(); + toggleLayer('mapillary-images'); } function clickMapillarySigns() { - context.background().toggleMapillarySignLayer(); - update(); + toggleLayer('mapillary-signs'); } function drawMapillaryItems(selection) { - var mapillary = iD.services.mapillary, - supportsMapillaryImages = !!mapillary, - supportsMapillarySigns = !!mapillary && mapillary().signsSupported(); + var mapillaryImages = layers.layer('mapillary-images'), + mapillarySigns = layers.layer('mapillary-signs'), + supportsMapillaryImages = mapillaryImages && mapillaryImages.supported(), + supportsMapillarySigns = mapillarySigns && mapillarySigns.supported(), + showsMapillaryImages = supportsMapillaryImages && mapillaryImages.enabled(), + showsMapillarySigns = supportsMapillarySigns && mapillarySigns.enabled(); var mapillaryList = selection - .selectAll('.mapillary-list') - .data([0]); + .selectAll('.mapillary-list') + .data([0]); // Enter mapillaryList @@ -70,8 +79,8 @@ iD.ui.MapData = function(context) { .attr('class', 'layer-list mapillary-list'); var mapillaryImageLayerItem = mapillaryList - .selectAll('.item-mapillary-images') - .data(supportsMapillaryImages ? [0] : []); + .selectAll('.item-mapillary-images') + .data(supportsMapillaryImages ? [0] : []); var enterImages = mapillaryImageLayerItem.enter() .append('li') @@ -91,8 +100,8 @@ iD.ui.MapData = function(context) { var mapillarySignLayerItem = mapillaryList - .selectAll('.item-mapillary-signs') - .data(supportsMapillarySigns ? [0] : []); + .selectAll('.item-mapillary-signs') + .data(supportsMapillarySigns ? [0] : []); var enterSigns = mapillarySignLayerItem.enter() .append('li') @@ -111,9 +120,6 @@ iD.ui.MapData = function(context) { .text(t('mapillary_signs.title')); // Update - var showsMapillaryImages = supportsMapillaryImages && context.background().showsMapillaryImageLayer(), - showsMapillarySigns = supportsMapillarySigns && context.background().showsMapillarySignLayer(); - mapillaryImageLayerItem .classed('active', showsMapillaryImages) .selectAll('input') @@ -133,10 +139,13 @@ iD.ui.MapData = function(context) { function drawGpxItem(selection) { - var supportsGpx = iD.detect().filedrop, - gpxLayerItem = selection - .selectAll('.layer-gpx') - .data(supportsGpx ? [0] : []); + var gpx = layers.layer('gpx'), + hasGpx = gpx && gpx.hasGpx(), + showsGpx = hasGpx && gpx.enabled(); + + var gpxLayerItem = selection + .selectAll('.layer-gpx') + .data(gpx ? [0] : []); // Enter var enter = gpxLayerItem.enter() @@ -153,7 +162,7 @@ iD.ui.MapData = function(context) { .on('click', function() { d3.event.preventDefault(); d3.event.stopPropagation(); - context.background().zoomToGpxLayer(); + gpx.fitZoom(); }) .call(iD.svg.Icon('#icon-search')); @@ -166,7 +175,7 @@ iD.ui.MapData = function(context) { d3.select(document.createElement('input')) .attr('type', 'file') .on('change', function() { - context.background().gpxLayerFiles(d3.event.target.files); + gpx.files(d3.event.target.files); }) .node().click(); }) @@ -185,9 +194,6 @@ iD.ui.MapData = function(context) { .text(t('gpx.local_layer')); // Update - var hasGpx = supportsGpx && context.background().hasGpxLayer(), - showsGpx = supportsGpx && context.background().showsGpxLayer(); - gpxLayerItem .classed('active', showsGpx) .selectAll('input') diff --git a/js/id/ui/map_in_map.js b/js/id/ui/map_in_map.js index d57026aa3..f0a5ead02 100644 --- a/js/id/ui/map_in_map.js +++ b/js/id/ui/map_in_map.js @@ -2,21 +2,18 @@ iD.ui.MapInMap = function(context) { var key = '/'; function map_in_map(selection) { - var backgroundLayer = iD.TileLayer(), overlayLayers = {}, - dispatch = d3.dispatch('change'), - gpxLayer = iD.GpxLayer(context, dispatch), projection = iD.geo.RawMercator(), + gpxLayer = iD.svg.Gpx(projection, context).showLabels(false), zoom = d3.behavior.zoom() .scaleExtent([ztok(0.5), ztok(24)]) .on('zoom', zoomPan), transformed = false, panning = false, + hidden = true, zDiff = 6, // by default, minimap renders at (main zoom - 6) - tStart, tLast, tCurr, kLast, kCurr, tiles, svg, gpx, timeoutId; - - iD.ui.MapInMap.gpxLayer = gpxLayer; + tStart, tLast, tCurr, kLast, kCurr, tiles, svg, timeoutId; function ztok(z) { return 256 * Math.pow(2, z); } function ktoz(k) { return Math.log(k) / Math.LN2 - 8; } @@ -72,7 +69,7 @@ iD.ui.MapInMap = function(context) { panning = false; if (tCurr[0] !== tStart[0] && tCurr[1] !== tStart[1]) { - var dMini = selection.dimensions(), + var dMini = wrap.dimensions(), cMini = [ dMini[0] / 2, dMini[1] / 2 ]; context.map().center(projection.invert(cMini)); @@ -82,7 +79,7 @@ iD.ui.MapInMap = function(context) { function updateProjection() { var loc = context.map().center(), - dMini = selection.dimensions(), + dMini = wrap.dimensions(), cMini = [ dMini[0] / 2, dMini[1] / 2 ], tMain = context.projection.translate(), kMain = context.projection.scale(), @@ -122,15 +119,15 @@ iD.ui.MapInMap = function(context) { function redraw() { - if (hidden()) return; + if (hidden) return; updateProjection(); - var dMini = selection.dimensions(), + var dMini = wrap.dimensions(), zMini = ktoz(projection.scale() * 2 * Math.PI); // setup tile container - tiles = selection + tiles = wrap .selectAll('.map-in-map-tiles') .data([0]); @@ -156,6 +153,7 @@ iD.ui.MapInMap = function(context) { background .call(backgroundLayer); + // redraw overlay var overlaySources = context.background().overlayLayerSources(); var activeOverlayLayers = []; @@ -189,27 +187,27 @@ iD.ui.MapInMap = function(context) { overlays.exit() .remove(); - // redraw gpx overlay - gpxLayer - .projection(projection); - gpx = tiles + var gpx = tiles .selectAll('.map-in-map-gpx') - .data([0]); + .data(gpxLayer.enabled() ? [0] : []); gpx.enter() - .append('div') + .append('svg') .attr('class', 'map-in-map-gpx'); + gpx.exit() + .remove(); + gpx.call(gpxLayer); - gpx.dimensions(dMini); + // redraw bounding box if (!panning) { var getPath = d3.geo.path().projection(projection), bbox = { type: 'Polygon', coordinates: [context.map().extent().polygon()] }; - svg = selection.selectAll('.map-in-map-svg') + svg = wrap.selectAll('.map-in-map-svg') .data([0]); svg.enter() @@ -236,31 +234,17 @@ iD.ui.MapInMap = function(context) { } - function hidden() { - return selection.style('display') === 'none'; - } - - function toggle() { if (d3.event) d3.event.preventDefault(); + hidden = !hidden; + var label = d3.select('.minimap-toggle'); + label.classed('active', !hidden) + .select('input').property('checked', !hidden); - if (hidden()) { - selection - .style('display', 'block') - .style('opacity', 0) - .transition() - .duration(200) - .style('opacity', 1); - - label.classed('active', true) - .select('input').property('checked', true); - - redraw(); - - } else { - selection + if (hidden) { + wrap .style('display', 'block') .style('opacity', 1) .transition() @@ -269,19 +253,29 @@ iD.ui.MapInMap = function(context) { .each('end', function() { d3.select(this).style('display', 'none'); }); + } else { + wrap + .style('display', 'block') + .style('opacity', 0) + .transition() + .duration(200) + .style('opacity', 1); - label.classed('active', false) - .select('input').property('checked', false); + redraw(); } } iD.ui.MapInMap.toggle = toggle; - selection - .on('mousedown.map-in-map', startMouse) - .on('mouseup.map-in-map', endMouse); + var wrap = selection.selectAll('.map-in-map') + .data([0]); - selection + wrap.enter() + .append('div') + .attr('class', 'map-in-map') + .style('display', (hidden ? 'none' : 'block')) + .on('mousedown.map-in-map', startMouse) + .on('mouseup.map-in-map', endMouse) .call(zoom) .on('dblclick.zoom', null); diff --git a/js/id/ui/preset/restrictions.js b/js/id/ui/preset/restrictions.js index aa0b91ee6..fe95a9138 100644 --- a/js/id/ui/preset/restrictions.js +++ b/js/id/ui/preset/restrictions.js @@ -1,36 +1,39 @@ iD.ui.preset.restrictions = function(field, context) { var dispatch = d3.dispatch('change'), + hover = iD.behavior.Hover(context), vertexID, fromNodeID; + function restrictions(selection) { + // if form field is hidden or has detached from dom, clean up. + if (!d3.select('.inspector-wrap.inspector-hidden').empty() || !selection.node().parentNode) { + selection.call(restrictions.off); + return; + } + var wrap = selection.selectAll('.preset-input-wrap') .data([0]); - var enter = wrap.enter().append('div') + var enter = wrap.enter() + .append('div') .attr('class', 'preset-input-wrap'); - enter.append('div') + enter + .append('div') .attr('class', 'restriction-help'); - enter.append('svg') - .call(iD.svg.Surface(context)) - .call(iD.behavior.Hover(context)); var intersection = iD.geo.Intersection(context.graph(), vertexID), graph = intersection.graph, vertex = graph.entity(vertexID), - surface = wrap.selectAll('svg'), - filter = function () { return true; }, + filter = d3.functor(true), extent = iD.geo.Extent(), - projection = iD.geo.RawMercator(), - lines = iD.svg.Lines(projection, context), - vertices = iD.svg.Vertices(projection, context), - turns = iD.svg.Turns(projection, context); + projection = iD.geo.RawMercator(); var d = wrap.dimensions(), c = [d[0] / 2, d[1] / 2], - z = 21; + z = 24; projection .scale(256 * Math.pow(2, z) / (2 * Math.PI)); @@ -41,10 +44,24 @@ iD.ui.preset.restrictions = function(field, context) { .translate([c[0] - s[0], c[1] - s[1]]) .clipExtent([[0, 0], d]); + var drawLayers = iD.svg.Layers(projection, context).only('osm').dimensions(d), + drawVertices = iD.svg.Vertices(projection, context), + drawLines = iD.svg.Lines(projection, context), + drawTurns = iD.svg.Turns(projection, context); + + enter + .call(drawLayers) + .selectAll('.surface') + .call(hover); + + + var surface = wrap.selectAll('.surface'); + surface - .call(vertices, graph, [vertex], filter, extent, z) - .call(lines, graph, intersection.ways, filter) - .call(turns, graph, intersection.turns(fromNodeID)); + .dimensions(d) + .call(drawVertices, graph, [vertex], filter, extent, z) + .call(drawLines, graph, intersection.ways, filter) + .call(drawTurns, graph, intersection.turns(fromNodeID)); surface .on('click.restrictions', click) @@ -67,7 +84,10 @@ iD.ui.preset.restrictions = function(field, context) { .on('change.restrictions', render); d3.select(window) - .on('resize.restrictions', render); + .on('resize.restrictions', function() { + wrap.dimensions(null); + render(); + }); function click() { var datum = d3.event.target.__data__; @@ -136,5 +156,19 @@ iD.ui.preset.restrictions = function(field, context) { restrictions.tags = function() {}; restrictions.focus = function() {}; + restrictions.off = function(selection) { + selection.selectAll('.surface') + .call(hover.off) + .on('click.restrictions', null) + .on('mouseover.restrictions', null) + .on('mouseout.restrictions', null); + + context.history() + .on('change.restrictions', null); + + d3.select(window) + .on('resize.restrictions', null); + }; + return d3.rebind(restrictions, dispatch, 'on'); }; diff --git a/js/lib/d3.dimensions.js b/js/lib/d3.dimensions.js index eef257401..7bba5516b 100644 --- a/js/lib/d3.dimensions.js +++ b/js/lib/d3.dimensions.js @@ -1,17 +1,23 @@ d3.selection.prototype.dimensions = function (dimensions) { - if (!arguments.length) { - var node = this.node(); - if (!node) return; - - var prop = this.property('__dimensions__'); - if (!prop) { - var cr = node.getBoundingClientRect(); - prop = [cr.width, cr.height]; - this.property('__dimensions__', prop); - } + var refresh = (function(node) { + var cr = node.getBoundingClientRect(); + prop = [cr.width, cr.height]; + this.property('__dimensions__', prop); return prop; + }).bind(this); + + var node = this.node(); + + if (!arguments.length) { + if (!node) return [0,0]; + return this.property('__dimensions__') || refresh(node); + } + if (dimensions === null) { + if (!node) return [0,0]; + return refresh(node); } - this.property('__dimensions__', [dimensions[0], dimensions[1]]); - return this.attr({width: dimensions[0], height: dimensions[1]}); + return this + .property('__dimensions__', [dimensions[0], dimensions[1]]) + .attr({width: dimensions[0], height: dimensions[1]}); }; diff --git a/test/index.html b/test/index.html index 83f8b99fc..2325206c7 100644 --- a/test/index.html +++ b/test/index.html @@ -62,20 +62,21 @@ - - - + + + + - + diff --git a/test/rendering.html b/test/rendering.html index c6a7f9437..35d8d8dfa 100644 --- a/test/rendering.html +++ b/test/rendering.html @@ -21,8 +21,9 @@ + - + @@ -146,7 +147,7 @@ .attr('width', 30) .attr('height', 40) .attr('data-zoom', function (d) { return d.zoom; }) - .call(iD.svg.Surface(context)) + .call(iD.svg.Layers(context)) .each(function (d) { var n = node.update({tags: d}), graph = iD.Graph([n]); @@ -214,7 +215,7 @@ .attr('width', 30) .attr('height', 30) .attr('data-zoom', function (d) { return d.zoom; }) - .call(iD.svg.Surface(context)) + .call(iD.svg.Layers(context)) .each(function (d) { var n = node.update({tags: d.tags}), graph = iD.Graph([n, way]); @@ -302,7 +303,7 @@ .attr('width', 200) .attr('height', 30) .attr('data-zoom', function (d) { return d.zoom; }) - .call(iD.svg.Surface(context)) + .call(iD.svg.Layers(context)) .each(function (d) { var highway = way.update({tags: d.tags}), graph = iD.Graph([a, b, highway]); @@ -364,7 +365,7 @@ .append('svg') .attr('width', 100) .attr('height', 100) - .call(iD.svg.Surface(context)) + .call(iD.svg.Layers(context)) .each(function (datum) { var area = way.update({tags: datum.tags}), graph = iD.Graph([a, b, c, d, area]); diff --git a/test/spec/behavior/select.js b/test/spec/behavior/select.js index b393dd555..9e7df8d0f 100644 --- a/test/spec/behavior/select.js +++ b/test/spec/behavior/select.js @@ -15,7 +15,7 @@ describe("iD.behavior.Select", function() { .append('div') .attr('class', 'inspector-wrap'); - context.surface().selectAll('circle') + context.surface().select('.data-layer-osm').selectAll('circle') .data([a, b]) .enter().append('circle') .attr('class', function(d) { return d.id; }); @@ -33,7 +33,7 @@ describe("iD.behavior.Select", function() { }); specify("click on entity selects the entity", function() { - happen.click(context.surface().select('.' + a.id).node()); + happen.click(context.surface().selectAll('.' + a.id).node()); expect(context.selectedIDs()).to.eql([a.id]); }); @@ -45,19 +45,19 @@ describe("iD.behavior.Select", function() { specify("shift-click on unselected entity adds it to the selection", function() { context.enter(iD.modes.Select(context, [a.id])); - happen.click(context.surface().select('.' + b.id).node(), {shiftKey: true}); + happen.click(context.surface().selectAll('.' + b.id).node(), {shiftKey: true}); expect(context.selectedIDs()).to.eql([a.id, b.id]); }); specify("shift-click on selected entity removes it from the selection", function() { context.enter(iD.modes.Select(context, [a.id, b.id])); - happen.click(context.surface().select('.' + b.id).node(), {shiftKey: true}); + happen.click(context.surface().selectAll('.' + b.id).node(), {shiftKey: true}); expect(context.selectedIDs()).to.eql([a.id]); }); specify("shift-click on last selected entity clears the selection", function() { context.enter(iD.modes.Select(context, [a.id])); - happen.click(context.surface().select('.' + a.id).node(), {shiftKey: true}); + happen.click(context.surface().selectAll('.' + a.id).node(), {shiftKey: true}); expect(context.mode().id).to.eql('browse'); }); diff --git a/test/spec/svg/areas.js b/test/spec/svg/areas.js index fcbcb1792..8e6d06fe3 100644 --- a/test/spec/svg/areas.js +++ b/test/spec/svg/areas.js @@ -7,7 +7,7 @@ describe("iD.svg.Areas", function () { beforeEach(function () { surface = d3.select(document.createElementNS('http://www.w3.org/2000/svg', 'svg')) - .call(iD.svg.Surface(iD())); + .call(iD.svg.Layers(iD())); }); it("adds way and area classes", function () { diff --git a/test/spec/svg/lines.js b/test/spec/svg/lines.js index b94575a60..27fc0ec10 100644 --- a/test/spec/svg/lines.js +++ b/test/spec/svg/lines.js @@ -7,7 +7,7 @@ describe("iD.svg.Lines", function () { beforeEach(function () { surface = d3.select(document.createElementNS('http://www.w3.org/2000/svg', 'svg')) - .call(iD.svg.Surface(iD())); + .call(iD.svg.Layers(iD())); }); it("adds way and line classes", function () { diff --git a/test/spec/svg/midpoints.js b/test/spec/svg/midpoints.js index eaef0d16e..4d012aae3 100644 --- a/test/spec/svg/midpoints.js +++ b/test/spec/svg/midpoints.js @@ -7,7 +7,7 @@ describe("iD.svg.Midpoints", function () { beforeEach(function () { context = iD(); surface = d3.select(document.createElementNS('http://www.w3.org/2000/svg', 'svg')) - .call(iD.svg.Surface(context)); + .call(iD.svg.Layers(context)); }); it("creates midpoint on segment completely within the extent", function () { diff --git a/test/spec/svg/points.js b/test/spec/svg/points.js index d6a7f5e85..38d043430 100644 --- a/test/spec/svg/points.js +++ b/test/spec/svg/points.js @@ -6,13 +6,14 @@ describe("iD.svg.Points", function () { beforeEach(function () { context = iD().presets(iD.data.presets); surface = d3.select(document.createElementNS('http://www.w3.org/2000/svg', 'svg')) - .call(iD.svg.Surface(context)); + .call(iD.svg.Layers(context)); }); it("adds tag classes", function () { - var point = iD.Node({tags: {amenity: "cafe"}, loc: [0, 0]}); + var point = iD.Node({tags: {amenity: "cafe"}, loc: [0, 0]}), + graph = iD.Graph([point]); - surface.call(iD.svg.Points(projection, context), [point]); + surface.call(iD.svg.Points(projection, context), graph, [point]); expect(surface.select('.point')).to.be.classed('tag-amenity'); expect(surface.select('.point')).to.be.classed('tag-amenity-cafe'); diff --git a/test/spec/svg/vertices.js b/test/spec/svg/vertices.js index 20182756b..0eec0e45b 100644 --- a/test/spec/svg/vertices.js +++ b/test/spec/svg/vertices.js @@ -6,7 +6,7 @@ describe("iD.svg.Vertices", function () { beforeEach(function () { context = iD(); surface = d3.select(document.createElementNS('http://www.w3.org/2000/svg', 'svg')) - .call(iD.svg.Surface(context)); + .call(iD.svg.Layers(context)); }); it("adds the .shared class to vertices that are members of two or more ways", function () {