diff --git a/css/app.css b/css/app.css index aa1d38082..d41276d04 100644 --- a/css/app.css +++ b/css/app.css @@ -2107,7 +2107,7 @@ img.wiki-image { /* Map-In-Map ------------------------------------------------------- */ -.map-in-map-wrap { +.map-in-map { position: absolute; overflow: hidden; top: 60px; @@ -2117,13 +2117,25 @@ img.wiki-image { background: #000; border: #aaa 1px solid; box-shadow: 0 0 2em black; - pointer-events: none; } -.map-in-map-wrap svg { +.map-in-map-tiles { + transform-origin:0 0; + -ms-transform-origin:0 0; + -webkit-transform-origin:0 0; + -moz-transform-origin:0 0; + -o-transform-origin:0 0; + -moz-user-select: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.map-in-map-svg { position: relative; overflow: hidden; height: 100%; + width: 100%; } .map-in-map-bbox { diff --git a/css/map.css b/css/map.css index b3ec162e5..c3edae105 100644 --- a/css/map.css +++ b/css/map.css @@ -1032,6 +1032,7 @@ g.turn circle { /* Cursors */ +.map-in-map, #map { cursor: auto; /* Opera */ cursor: url(img/cursor-grab.png) 9 9, auto; /* FF */ diff --git a/js/id/ui.js b/js/id/ui.js index ebe689a88..e61c3160d 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -33,7 +33,7 @@ iD.ui = function(context) { .call(map); content.append('div') - .attr('class', 'map-in-map-wrap') + .attr('class', 'map-in-map') .style('display', 'none') .call(iD.ui.MapInMap(context)); diff --git a/js/id/ui/map_in_map.js b/js/id/ui/map_in_map.js index 577470a32..e93282ed4 100644 --- a/js/id/ui/map_in_map.js +++ b/js/id/ui/map_in_map.js @@ -1,37 +1,121 @@ iD.ui.MapInMap = function(context) { - var key = 'M', - backgroundLayer = iD.TileLayer(), - overlayLayer = iD.TileLayer(); + var key = 'M'; function map_in_map(selection) { + var backgroundLayer = iD.TileLayer(), + overlayLayer = iD.TileLayer(), + projection = iD.geo.RawMercator(), + zoom = d3.behavior.zoom() + // .scaleExtent([1024, 256 * Math.pow(2, 24)]) + .on('zoom', zoomPan), + transformed = false, + panning = false, + tLast = [0, 0], + tCurr = [0, 0], + tiles, + timeoutId; - function render() { - if (hidden()) return; + function startZoomPan() { + context.surface().on('mouseup.map-in-map-outside', endZoomPan); + context.container().on('mouseup.map-in-map-outside', endZoomPan); + } + + + function zoomPan() { + var t = d3.event.translate, + e = d3.event.sourceEvent; + + if (e.type === 'wheel') { + // for now, throw out wheel events + zoom.translate(tCurr).scale(1); + + } else if (e.type === 'mousemove') { + var tDiff = [ t[0] - tLast[0], t[1] - tLast[1] ]; + tCurr = t; + + iD.util.setTransform(tiles, tDiff[0], tDiff[1]); + transformed = true; + panning = true; + queueRedraw(); + } + + e.preventDefault(); + e.stopPropagation(); + } + + + function endZoomPan() { + context.surface().on('mouseup.map-in-map-outside', null); + context.container().on('mouseup.map-in-map-outside', null); + + updateProjection(); + + tLast = [0, 0]; + tCurr = [0, 0]; + zoom.translate([0, 0]).scale(1); + panning = false; + + var d = selection.dimensions(), + c = [ d[0] / 2, d[1] / 2 ]; + + context.map().center(projection.invert(c)); + } + + + function updateProjection() { var loc = context.map().center(), d = selection.dimensions(), - c = [d[0] / 2, d[1] / 2], + c = [ d[0] / 2, d[1] / 2 ], k1 = context.projection.scale(), z1 = Math.log(k1 * 2 * Math.PI) / Math.LN2 - 8, - z = Math.max(z1 - 6, 2); - - var projection = iD.geo.RawMercator() - .scale(256 * Math.pow(2, z) / (2 * Math.PI)); - - var s = projection(loc); + z = Math.max(z1 - 6, 0.5); projection - .translate([c[0] - s[0], c[1] - s[1]]) + .translate([0,0]) + .scale(256 * Math.pow(2, z) / (2 * Math.PI)); + + var s = projection(loc), + t = [c[0] - s[0] + tCurr[0], + c[1] - s[1] + tCurr[1] ]; + + projection + .translate(t) .clipExtent([[0, 0], d]); + } - // render background + function redraw() { + if (hidden()) return; + + updateProjection(); + + var d = selection.dimensions(), + z = Math.log(projection.scale() * 2 * Math.PI) / Math.LN2 - 8; + + // setup tile container + tiles = selection + .selectAll('.map-in-map-tiles') + .data([0]); + + tiles + .enter() + .append('div') + .attr('class', 'map-in-map-tiles'); + + if (transformed) { + tLast = tCurr; + iD.util.setTransform(tiles, 0, 0); + transformed = false; + } + + // redraw background backgroundLayer .source(context.background().baseLayerSource()) .projection(projection) .dimensions(d); - var background = selection + var background = tiles .selectAll('.map-in-map-background') .data([0]); @@ -42,8 +126,7 @@ iD.ui.MapInMap = function(context) { background .call(backgroundLayer); - - // render overlay + // redraw overlay var overlaySources = context.background().overlayLayerSources(), hasOverlay = false; @@ -59,7 +142,7 @@ iD.ui.MapInMap = function(context) { } } - var overlay = selection + var overlay = tiles .selectAll('.map-in-map-overlay') .data(hasOverlay ? [0] : []); @@ -70,29 +153,39 @@ iD.ui.MapInMap = function(context) { overlay.exit() .remove(); - overlay - .call(overlayLayer); + if (hasOverlay) { + overlay + .call(overlayLayer); + } + + // redraw bounding box + if (!panning) { + var getPath = d3.geo.path().projection(projection), + bbox = { type: 'Polygon', coordinates: [context.map().extent().polygon()] }; + + var svg = selection.selectAll('.map-in-map-svg') + .data([0]); + + svg.enter() + .append('svg') + .attr('class', 'map-in-map-svg'); + + var path = svg.selectAll('.map-in-map-bbox') + .data([bbox]); + + path.enter() + .append('path') + .attr('class', 'map-in-map-bbox'); + + path + .attr('d', getPath); + } + } - // render bounding box - var getPath = d3.geo.path().projection(projection), - bbox = { type: 'Polygon', coordinates: [context.map().extent().polygon()] }; - - var svg = selection.selectAll('svg') - .data([0]); - - svg.enter() - .append('svg'); - - var path = svg.selectAll('path') - .data([bbox]); - - path.enter() - .append('path') - .attr('class', 'map-in-map-bbox'); - - path - .attr('d', getPath); + function queueRedraw() { + clearTimeout(timeoutId); + timeoutId = setTimeout(function() { redraw(); }, 300); } @@ -112,7 +205,7 @@ iD.ui.MapInMap = function(context) { .duration(200) .style('opacity', 1); - render(); + redraw(); } else { selection @@ -128,8 +221,18 @@ iD.ui.MapInMap = function(context) { } - context.map().on('drawn.map-in-map', render); - render(); + selection + .on('mousedown.map-in-map', startZoomPan) + .on('mouseup.map-in-map', endZoomPan); + + selection + .call(zoom) + .on('dblclick.zoom', null); + + context.map() + .on('drawn.map-in-map', redraw); + + redraw(); var keybinding = d3.keybinding('map-in-map') .on(key, toggle);