Files
iD/modules/ui/map_in_map.js
2016-07-06 12:12:08 +05:30

306 lines
9.1 KiB
JavaScript

import { Debug, Gpx } from '../svg/index';
import { RawMercator } from '../geo/index';
import { TileLayer } from '../renderer/index';
import { setTransform } from '../util/index';
export function MapInMap(context) {
var key = '/';
function map_in_map(selection) {
var backgroundLayer = TileLayer(context),
overlayLayers = {},
projection = RawMercator(),
gpxLayer = Gpx(projection, context).showLabels(false),
debugLayer = Debug(projection, context),
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, viewport, timeoutId;
function ztok(z) { return 256 * Math.pow(2, z); }
function ktoz(k) { return Math.log(k) / Math.LN2 - 8; }
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();
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);
// restrict minimap zoom to < (main zoom - 3)
if (zMini > zMain - 3) {
zMini = zMain - 3;
zoom.scale(kCurr).translate(tCurr); // restore last good values
return;
}
tCurr = t;
kCurr = k;
zDiff = zMain - zMini;
var scale = kCurr / kLast,
tX = (tCurr[0] / scale - tLast[0]) * scale,
tY = (tCurr[1] / scale - tLast[1]) * scale;
setTransform(tiles, tX, tY, scale);
setTransform(viewport, 0, 0, scale);
transformed = true;
queueRedraw();
e.preventDefault();
e.stopPropagation();
}
function endMouse() {
context.surface().on('mouseup.map-in-map-outside', null);
context.container().on('mouseup.map-in-map-outside', null);
updateProjection();
panning = false;
if (tCurr[0] !== tStart[0] && tCurr[1] !== tStart[1]) {
var dMini = wrap.dimensions(),
cMini = [ dMini[0] / 2, dMini[1] / 2 ];
context.map().center(projection.invert(cMini));
}
}
function updateProjection() {
var loc = context.map().center(),
dMini = wrap.dimensions(),
cMini = [ dMini[0] / 2, dMini[1] / 2 ],
tMain = context.projection.translate(),
kMain = context.projection.scale(),
zMain = ktoz(kMain * 2 * Math.PI),
zMini = Math.max(zMain - zDiff, 0.5),
kMini = ztok(zMini);
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)
.scale(kMini);
tLast = tCurr = tMini;
kLast = kCurr = kMini;
if (transformed) {
setTransform(tiles, 0, 0);
setTransform(viewport, 0, 0);
transformed = false;
}
}
function redraw() {
if (hidden) return;
updateProjection();
var dMini = wrap.dimensions(),
zMini = ktoz(projection.scale() * 2 * Math.PI);
// setup tile container
tiles = wrap
.selectAll('.map-in-map-tiles')
.data([0]);
tiles
.enter()
.append('div')
.attr('class', 'map-in-map-tiles');
// redraw background
backgroundLayer
.source(context.background().baseLayerSource())
.projection(projection)
.dimensions(dMini);
var background = tiles
.selectAll('.map-in-map-background')
.data([0]);
background.enter()
.append('div')
.attr('class', 'map-in-map-background');
background
.call(backgroundLayer);
// redraw overlay
var overlaySources = context.background().overlayLayerSources();
var activeOverlayLayers = [];
for (var i = 0; i < overlaySources.length; i++) {
if (overlaySources[i].validZoom(zMini)) {
if (!overlayLayers[i]) overlayLayers[i] = TileLayer(context);
activeOverlayLayers.push(overlayLayers[i]
.source(overlaySources[i])
.projection(projection)
.dimensions(dMini));
}
}
var overlay = tiles
.selectAll('.map-in-map-overlay')
.data([0]);
overlay.enter()
.append('div')
.attr('class', 'map-in-map-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();
var dataLayers = tiles
.selectAll('.map-in-map-data')
.data([0]);
dataLayers.enter()
.append('svg')
.attr('class', 'map-in-map-data');
dataLayers.exit()
.remove();
dataLayers
.call(gpxLayer)
.call(debugLayer);
// redraw viewport bounding box
if (!panning) {
var getPath = d3.geo.path().projection(projection),
bbox = { type: 'Polygon', coordinates: [context.map().extent().polygon()] };
viewport = wrap.selectAll('.map-in-map-viewport')
.data([0]);
viewport.enter()
.append('svg')
.attr('class', 'map-in-map-viewport');
var path = viewport.selectAll('.map-in-map-bbox')
.data([bbox]);
path.enter()
.append('path')
.attr('class', 'map-in-map-bbox');
path
.attr('d', getPath)
.classed('thick', function(d) { return getPath.area(d) < 30; });
}
}
function queueRedraw() {
clearTimeout(timeoutId);
timeoutId = setTimeout(function() { redraw(); }, 300);
}
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) {
wrap
.style('display', 'block')
.style('opacity', 1)
.transition()
.duration(200)
.style('opacity', 0)
.each('end', function() {
d3.select(this).style('display', 'none');
});
} else {
wrap
.style('display', 'block')
.style('opacity', 0)
.transition()
.duration(200)
.style('opacity', 1);
redraw();
}
}
MapInMap.toggle = toggle;
var wrap = selection.selectAll('.map-in-map')
.data([0]);
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);
context.map()
.on('drawn.map-in-map', function(drawn) {
if (drawn.full === true) redraw();
});
redraw();
var keybinding = d3.keybinding('map-in-map')
.on(key, toggle);
d3.select(document)
.call(keybinding);
}
return map_in_map;
}