diff --git a/js/id/renderer/background.js b/js/id/renderer/background.js index 305ab66e0..7a420e014 100644 --- a/js/id/renderer/background.js +++ b/js/id/renderer/background.js @@ -1,9 +1,7 @@ iD.Background = function(context) { var dispatch = d3.dispatch('change'), - baseLayer = iD.TileLayer() - .projection(context.projection), - gpxLayer = iD.svg.Gpx(context, dispatch) - .projection(context.projection), + baseLayer = iD.TileLayer().projection(context.projection), + gpxLayer = iD.svg.Gpx(context.projection, context), mapillaryImageLayer, mapillarySignLayer, overlayLayers = []; @@ -78,13 +76,20 @@ 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 gpx = selection.selectAll('.layer-gpx') + // .data([0]); + + // gpx.enter().insert('div') + // .attr('class', 'layer-layer layer-gpx'); + + // gpx.call(gpxLayer); + + selection.selectAll('#surface') + .call(gpxLayer); + + var mapillary = iD.services.mapillary, @@ -133,7 +138,6 @@ iD.Background = function(context) { background.dimensions = function(_) { baseLayer.dimensions(_); - gpxLayer.dimensions(_); if (mapillaryImageLayer) mapillaryImageLayer.dimensions(_); if (mapillarySignLayer) mapillarySignLayer.dimensions(_); @@ -156,51 +160,20 @@ iD.Background = function(context) { background.baseLayerSource(findSource('Bing')); }; + background.gpxLayer = function() { + return gpxLayer; + }; + 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)); - } - } + return background.hasGpxLayer() && gpxLayer.enabled(); }; background.toggleGpxLayer = function() { - gpxLayer.enable(!gpxLayer.enable()); - iD.ui.MapInMap.gpxLayer.enable(!iD.ui.MapInMap.gpxLayer.enable()); + gpxLayer.enabled(!gpxLayer.enabled()); dispatch.change(); }; @@ -317,15 +290,8 @@ 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) { + gpxLayer.url(q.gpx); } }; diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index 8e21e7a8b..3c606061f 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -145,7 +145,7 @@ iD.Map = function(context) { function editOff() { context.features().resetStats(); - surface.selectAll('.layer *').remove(); + surface.selectAll('.layer-osm *').remove(); dispatch.drawn({full: true}); } diff --git a/js/id/svg/gpx.js b/js/id/svg/gpx.js index c12326a1e..bc4699fef 100644 --- a/js/id/svg/gpx.js +++ b/js/id/svg/gpx.js @@ -1,101 +1,160 @@ -iD.svg.Gpx = function(context) { - var projection, - gj = {}, - enable = true, - svg; +iD.svg.Gpx = function(projection, context) { + var showLabels = true, + layer; - function render(selection) { - svg = selection.selectAll('svg') - .data([render]); - svg.enter() - .append('svg'); + function init() { + if (iD.svg.Gpx.initialized) return; // run once - svg.style('display', enable ? 'block' : 'none'); + iD.svg.Gpx.geojson = {}; + iD.svg.Gpx.enabled = true; - var paths = svg + 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; + gpx.files(d3.event.dataTransfer.files); + }) + .on('dragenter.localgpx', over) + .on('dragexit.localgpx', over) + .on('dragover.localgpx', over); + + iD.svg.Gpx.initialized = true; + } + + + function gpx(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 layer-gpx'); + + layer.exit() + .remove(); + + + var paths = layer .selectAll('path') - .data([gj]); + .data([geojson]); - paths - .enter() + paths.enter() .append('path') .attr('class', 'gpx'); + paths.exit() + .remove(); + 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]; - }); + 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'); + } + + + gpx.showLabels = function(_) { + if (!arguments.length) return showLabels; + showLabels = _; + return gpx; + }; + + gpx.enabled = function(_) { + if (!arguments.length) return iD.svg.Gpx.enabled; + iD.svg.Gpx.enabled = _; + return gpx; + }; + + gpx.geojson = function(gj) { + if (!arguments.length) return iD.svg.Gpx.geojson; + if (_.isEmpty(gj) || _.isEmpty(gj.features)) return gpx; + iD.svg.Gpx.geojson = gj; + return gpx; + }; + + gpx.url = function(url) { + d3.text(url, function(err, data) { + if (!err) { + gpx.geojson(toGeoJSON.gpx(toDom(data))); + // dispatch.change(); + } + }); + return gpx; + }; + + gpx.files = function(fileList) { + var f = fileList[0], + reader = new FileReader(); + + reader.onload = function(e) { + gpx.geojson(toGeoJSON.gpx(toDom(e.target.result))).fitZoom(); + // dispatch.change(); + }; + + reader.readAsText(f); + return gpx; + }; + + gpx.fitZoom = function() { + var geojson = iD.svg.Gpx.geojson; + if (_.isEmpty(geojson) || _.isEmpty(geojson.features)) return gpx; + + 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)); } - } - render.projection = function(_) { - if (!arguments.length) return projection; - projection = _; - return render; + return gpx; }; - 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; + init(); + return gpx; }; diff --git a/js/id/svg/surface.js b/js/id/svg/surface.js index fbd7ba99d..0aafadd1b 100644 --- a/js/id/svg/surface.js +++ b/js/id/svg/surface.js @@ -9,6 +9,6 @@ iD.svg.Surface = function() { .data(['areas', 'lines', 'hit', 'halo', 'label']); layers.enter().append('g') - .attr('class', function(d) { return 'layer layer-' + d; }); + .attr('class', function(d) { return 'layer layer-osm layer-' + d; }); }; }; diff --git a/js/id/ui/map_data.js b/js/id/ui/map_data.js index 0298910e4..b3dee69b5 100644 --- a/js/id/ui/map_data.js +++ b/js/id/ui/map_data.js @@ -133,10 +133,9 @@ iD.ui.MapData = function(context) { function drawGpxItem(selection) { - var supportsGpx = iD.detect().filedrop, - gpxLayerItem = selection - .selectAll('.layer-gpx') - .data(supportsGpx ? [0] : []); + var gpxLayerItem = selection + .selectAll('.layer-gpx') + .data([0]); // Enter var enter = gpxLayerItem.enter() @@ -153,7 +152,7 @@ iD.ui.MapData = function(context) { .on('click', function() { d3.event.preventDefault(); d3.event.stopPropagation(); - context.background().zoomToGpxLayer(); + context.background().gpxLayer().fitZoom(); }) .call(iD.svg.Icon('#icon-search')); @@ -166,7 +165,7 @@ iD.ui.MapData = function(context) { d3.select(document.createElement('input')) .attr('type', 'file') .on('change', function() { - context.background().gpxLayerFiles(d3.event.target.files); + context.background().gpxLayer().files(d3.event.target.files); }) .node().click(); }) @@ -185,8 +184,8 @@ iD.ui.MapData = function(context) { .text(t('gpx.local_layer')); // Update - var hasGpx = supportsGpx && context.background().hasGpxLayer(), - showsGpx = supportsGpx && context.background().showsGpxLayer(); + var hasGpx = context.background().hasGpxLayer(), + showsGpx = context.background().showsGpxLayer(); gpxLayerItem .classed('active', showsGpx) diff --git a/js/id/ui/map_in_map.js b/js/id/ui/map_in_map.js index 58a08874b..07a7e4cf6 100644 --- a/js/id/ui/map_in_map.js +++ b/js/id/ui/map_in_map.js @@ -4,18 +4,15 @@ iD.ui.MapInMap = function(context) { function map_in_map(selection) { var backgroundLayer = iD.TileLayer(), overlayLayers = {}, - dispatch = d3.dispatch('change'), - gpxLayer = iD.svg.Gpx(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, 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; } @@ -155,6 +152,7 @@ iD.ui.MapInMap = function(context) { background .call(backgroundLayer); + // redraw overlay var overlaySources = context.background().overlayLayerSources(); var activeOverlayLayers = []; @@ -188,20 +186,20 @@ 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) {