diff --git a/modules/geo/raw_mercator.js b/modules/geo/raw_mercator.js index 290eae1d6..2a4a84710 100644 --- a/modules/geo/raw_mercator.js +++ b/modules/geo/raw_mercator.js @@ -41,6 +41,14 @@ export function RawMercator() { return projection; }; + projection.transform = function(_) { + if (!arguments.length) return d3.zoomIdentity.translate(x, y).scale(k); + x = +_.x; + y = +_.y; + k = +_.k; + return projection; + }; + projection.stream = d3.geoTransform({ point: function(x, y) { x = projection([x, y]); diff --git a/modules/renderer/map.js b/modules/renderer/map.js index ee561a70a..f62e8935f 100644 --- a/modules/renderer/map.js +++ b/modules/renderer/map.js @@ -16,11 +16,7 @@ export function Map(context) { projection = context.projection, dblclickEnabled = true, redrawEnabled = true, - transformStart = { - x: 0, - y: 0, - k: 1 - }, + transformStart = projection.transform(), transformed = false, easing = false, minzoom = 0, @@ -209,6 +205,12 @@ export function Map(context) { function zoomPan(manualEvent) { var eventTransform = (manualEvent || d3.event).transform; + if (transformStart.x === eventTransform.x && + transformStart.y === eventTransform.y && + transformStart.k === eventTransform.k) { + return; // no change + } + if (ktoz(eventTransform.k * 2 * Math.PI) < minzoom) { surface.interrupt(); flash(context.container()) @@ -220,16 +222,7 @@ export function Map(context) { return; } - var t = projection.translate(), - k = projection.scale(); - - if (t[0] === eventTransform.x && t[1] === eventTransform.y && k === eventTransform.k) { - return; // no change - } - - projection - .translate([eventTransform.x, eventTransform.y]) - .scale(eventTransform.k); + projection.transform(eventTransform); var scale = eventTransform.k / transformStart.k, tX = (eventTransform.x / scale - transformStart.x) * scale, @@ -377,8 +370,8 @@ setZoom(_, true); t[1] += center[1] - l[1]; projection.translate(t); - transformStart = { k: k, x: t[0], y: t[1] }; - _selection.call(zoom.transform, d3.zoomIdentity.translate(t[0], t[1]).scale(k)); + transformStart = projection.transform(); + _selection.call(zoom.transform, transformStart); return true; } @@ -397,8 +390,8 @@ setZoom(_, true); t[1] = t[1] - ll[1] + pxC[1]; projection.translate(t); - transformStart = { k: k, x: t[0], y: t[1] }; - _selection.call(zoom.transform, d3.zoomIdentity.translate(t[0], t[1]).scale(k)); + transformStart = projection.transform(); + _selection.call(zoom.transform, transformStart); return true; } @@ -411,8 +404,8 @@ setZoom(_, true); t[1] += d[1]; projection.translate(t); - transformStart = { k: k, x: t[0], y: t[1] }; - _selection.call(zoom.transform, d3.zoomIdentity.translate(t[0], t[1]).scale(k)); + transformStart = projection.transform(); + _selection.call(zoom.transform, transformStart); dispatch.call('move', this, map); return redraw(); diff --git a/modules/ui/map_in_map.js b/modules/ui/map_in_map.js index 1391a42ba..5f999e262 100644 --- a/modules/ui/map_in_map.js +++ b/modules/ui/map_in_map.js @@ -6,6 +6,13 @@ import { TileLayer } from '../renderer/index'; import { setTransform } from '../util/index'; import { getDimensions } from '../util/dimensions'; +function ztok(z) { return 256 * Math.pow(2, z); } +function ktoz(k) { return Math.log(k) / Math.LN2 - 8; } +function vecSub(a, b) { return [ a[0] - b[0], a[1] - b[1] ]; } +function vecScale(a, b) { return [ a[0] * b, a[1] * b ]; } +var TAU = 2 * Math.PI; + + export function MapInMap(context) { var key = '/'; @@ -22,51 +29,51 @@ export function MapInMap(context) { panning = false, hidden = true, zDiff = 6, // by default, minimap renders at (main zoom - 6) - tStart, tLast, tCurr, kLast, kCurr, tiles, viewport, timeoutId; - - function ztok(z) { return 256 * Math.pow(2, z); } - function ktoz(k) { return Math.log(k) / Math.LN2 - 8; } + tStart, tLast, tiles, viewport, timeoutId; function startMouse() { context.surface().on('mouseup.map-in-map-outside', endMouse); context.container().on('mouseup.map-in-map-outside', endMouse); - tStart = tLast = tCurr = projection.translate(); + tStart = tLast = projection.transform(); panning = true; } function zoomPan() { - var e = d3.event.sourceEvent, - t = d3.event.translate, - k = d3.event.scale, - zMain = ktoz(context.projection.scale() * 2 * Math.PI), - zMini = ktoz(k); + var tCurr = d3.event.transform; - // restrict minimap zoom to < (main zoom - 3) - if (zMini > zMain - 3) { - zMini = zMain - 3; - zoom.scale(kCurr).translate(tCurr); // restore last good values - return; + if (tCurr.x === tStart.x && tCurr.y === tStart.y && tCurr.k === tStart.k) { + return; // no change } - tCurr = t; - kCurr = k; - zDiff = zMain - zMini; + // var zMain = ktoz(context.projection.scale() * TAU), + // zMini = ktoz(tCurr.k * TAU), + // zDiff = zMain - zMini; - var scale = kCurr / kLast, - tX = (tCurr[0] / scale - tLast[0]) * scale, - tY = (tCurr[1] / scale - tLast[1]) * scale; + // // restrict minimap zoom to < (main zoom - 3) + // if (zMini > zMain - 3) { + // zMini = zMain - 3; + // wrap.call(zoom.transform, tLast); + // return; + // } + + + var scale = tCurr.k / tStart.k, + tX = (tCurr.x / scale - tStart.x) * scale, + tY = (tCurr.y / scale - tStart.y) * scale; setTransform(tiles, tX, tY, scale); setTransform(viewport, 0, 0, scale); transformed = true; + tLast = tCurr; queueRedraw(); - e.preventDefault(); - e.stopPropagation(); + // var e = d3.event.sourceEvent; + // e.preventDefault(); + // e.stopPropagation(); } @@ -74,56 +81,58 @@ export function MapInMap(context) { context.surface().on('mouseup.map-in-map-outside', null); context.container().on('mouseup.map-in-map-outside', null); + var changed = false; + if (tLast.x !== tStart.x && tLast.y !== tStart.y) { + changed = true; + } + updateProjection(); panning = false; - if (tCurr[0] !== tStart[0] && tCurr[1] !== tStart[1]) { + if (changed) { var dMini = getDimensions(wrap), - cMini = [ dMini[0] / 2, dMini[1] / 2 ]; - + cMini = vecScale(dMini, 0.5); context.map().center(projection.invert(cMini)); } } function updateProjection() { + zDiff = Math.max(zDiff, 3); + var loc = context.map().center(), dMini = getDimensions(wrap), - cMini = [ dMini[0] / 2, dMini[1] / 2 ], - tMain = context.projection.translate(), - kMain = context.projection.scale(), - zMain = ktoz(kMain * 2 * Math.PI), + cMini = vecScale(dMini, 0.5), + tMain = context.projection.transform(), + zMain = ktoz(tMain.k * TAU), zMini = Math.max(zMain - zDiff, 0.5), - kMini = ztok(zMini); + kMini = ztok(zMini) / TAU; projection - .translate(tMain) - .scale(kMini / (2 * Math.PI)); - - var s = projection(loc), - mouse = panning ? [ tCurr[0] - tStart[0], tCurr[1] - tStart[1] ] : [0, 0], - tMini = [ - cMini[0] - s[0] + tMain[0] + mouse[0], - cMini[1] - s[1] + tMain[1] + mouse[1] - ]; - - projection - .translate(tMini) - .clipExtent([[0, 0], dMini]); - - zoom - .center(cMini) - .translate(tMini) + .translate([tMain.x, tMain.y]) .scale(kMini); - tLast = tCurr = tMini; - kLast = kCurr = kMini; + var point = projection(loc), + mouse = panning ? vecSub(tLast, tStart) : [0, 0], + xMini = cMini[0] - point[0] + tMain.x + mouse[0], + yMini = cMini[1] - point[1] + tMain.y + mouse[1]; + + projection + .translate([xMini, yMini]) + .clipExtent([[0, 0], dMini]); + + tStart = tLast = d3.zoomIdentity.translate(xMini, yMini).scale(kMini); if (transformed) { setTransform(tiles, 0, 0); setTransform(viewport, 0, 0); transformed = false; } + + zoom + .scaleExtent([ztok(0.5) / TAU, ztok(zMain - 3) / TAU]); + + wrap.call(zoom.transform, tStart); } @@ -133,7 +142,7 @@ export function MapInMap(context) { updateProjection(); var dMini = getDimensions(wrap), - zMini = ktoz(projection.scale() * 2 * Math.PI); + zMini = ktoz(projection.scale() * TAU); // setup tile container tiles = wrap @@ -155,11 +164,10 @@ export function MapInMap(context) { .selectAll('.map-in-map-background') .data([0]); - background.enter() + background = background.enter() .append('div') - .attr('class', 'map-in-map-background'); - - background + .attr('class', 'map-in-map-background') + .merge(background) .call(backgroundLayer); @@ -180,35 +188,36 @@ export function MapInMap(context) { .selectAll('.map-in-map-overlay') .data([0]); - overlay.enter() + overlay = overlay.enter() .append('div') - .attr('class', 'map-in-map-overlay'); + .attr('class', 'map-in-map-overlay') + .merge(overlay); + var overlays = overlay .selectAll('div') .data(activeOverlayLayers, function(d) { return d.source().name(); }); - overlays.enter().append('div'); - overlays.each(function(layer) { - d3.select(this).call(layer); - }); - overlays.exit() .remove(); + overlays = overlays.enter() + .append('div') + .merge(overlays) + .each(function(layer) { d3.select(this).call(layer); }); + var dataLayers = tiles .selectAll('.map-in-map-data') .data([0]); - dataLayers.enter() - .append('svg') - .attr('class', 'map-in-map-data'); - dataLayers.exit() .remove(); - dataLayers + dataLayers = dataLayers.enter() + .append('svg') + .attr('class', 'map-in-map-data') + .merge(dataLayers) .call(gpxLayer) .call(debugLayer); @@ -221,18 +230,19 @@ export function MapInMap(context) { viewport = wrap.selectAll('.map-in-map-viewport') .data([0]); - viewport.enter() + viewport = viewport.enter() .append('svg') - .attr('class', 'map-in-map-viewport'); + .attr('class', 'map-in-map-viewport') + .merge(viewport); + var path = viewport.selectAll('.map-in-map-bbox') .data([bbox]); path.enter() .append('path') - .attr('class', 'map-in-map-bbox'); - - path + .attr('class', 'map-in-map-bbox') + .merge(path) .attr('d', getPath) .classed('thick', function(d) { return getPath.area(d) < 30; }); } @@ -255,22 +265,31 @@ export function MapInMap(context) { .select('input').property('checked', !hidden); if (hidden) { - wrap + // selection.selectAll('.map-in-map') + // .style('display', 'none') + // .style('opacity', '0'); + + selection.selectAll('.map-in-map') .style('display', 'block') - .style('opacity', 1) + .style('opacity', '1') .transition() .duration(200) - .style('opacity', 0) + .style('opacity', '0') .on('end', function() { - d3.select(this).style('display', 'none'); + selection.selectAll('.map-in-map') + .style('display', 'none'); }); } else { - wrap + // selection.selectAll('.map-in-map') + // .style('display', 'block') + // .style('opacity', '1'); + + selection.selectAll('.map-in-map') .style('display', 'block') - .style('opacity', 0) + .style('opacity', '0') .transition() .duration(200) - .style('opacity', 1); + .style('opacity', '1'); redraw(); } @@ -281,9 +300,12 @@ export function MapInMap(context) { var wrap = selection.selectAll('.map-in-map') .data([0]); - wrap.enter() + wrap = wrap.enter() .append('div') .attr('class', 'map-in-map') + .merge(wrap); + + wrap .style('display', (hidden ? 'none' : 'block')) .on('mousedown.map-in-map', startMouse) .on('mouseup.map-in-map', endMouse) diff --git a/modules/util/dimensions.js b/modules/util/dimensions.js index 139d0e5c1..363ac99a0 100644 --- a/modules/util/dimensions.js +++ b/modules/util/dimensions.js @@ -1,23 +1,27 @@ -function refresh(target, node) { +function refresh(selection, node) { var cr = node.getBoundingClientRect(); var prop = [cr.width, cr.height]; - target.property('__dimensions__', prop); + selection.property('__dimensions__', prop); return prop; } -export function getDimensions (target) { - if (!target) return [0, 0]; - var node = target.node(); - return target.property('__dimensions__') || refresh(target, node); +export function getDimensions (selection) { + if (!selection || selection.empty()) { + return [0, 0]; + } + var node = selection.node(); + return selection.property('__dimensions__') || refresh(selection, node); } -export function setDimensions (target, dimensions) { - var node = target.node(); - if (dimensions === null) { - if (!node) return [0,0]; - return refresh(target, node); +export function setDimensions (selection, dimensions) { + if (!selection || selection.empty()) { + return [0, 0]; } - return target + var node = selection.node(); + if (dimensions === null) { + return refresh(selection, node); + } + return selection .property('__dimensions__', [dimensions[0], dimensions[1]]) .attr('width', dimensions[0]) .attr('height', dimensions[1]);