mirror of
https://github.com/FoggedLens/iD.git
synced 2026-02-13 09:12:52 +00:00
There was an issue where the lines did not redraw their targets right away when entering drag node, which could make it possible for a quick drag node to try to connect to its parent line. With the chooseEdge exclusion it would not connect to the parent nearby, but in another weird part of the line.
830 lines
25 KiB
JavaScript
830 lines
25 KiB
JavaScript
import _compact from 'lodash-es/compact';
|
|
import _map from 'lodash-es/map';
|
|
import _throttle from 'lodash-es/throttle';
|
|
import _values from 'lodash-es/values';
|
|
|
|
import { set as d3_set } from 'd3-collection';
|
|
import { dispatch as d3_dispatch } from 'd3-dispatch';
|
|
import { interpolate as d3_interpolate } from 'd3-interpolate';
|
|
|
|
import {
|
|
event as d3_event,
|
|
select as d3_select
|
|
} from 'd3-selection';
|
|
|
|
import {
|
|
zoom as d3_zoom,
|
|
zoomIdentity as d3_zoomIdentity
|
|
} from 'd3-zoom';
|
|
|
|
import { t } from '../util/locale';
|
|
import { geoExtent } from '../geo';
|
|
|
|
import {
|
|
modeBrowse,
|
|
modeSelect
|
|
} from '../modes';
|
|
|
|
import {
|
|
svgAreas,
|
|
svgLabels,
|
|
svgLayers,
|
|
svgLines,
|
|
svgMidpoints,
|
|
svgPoints,
|
|
svgVertices
|
|
} from '../svg';
|
|
|
|
import { uiFlash } from '../ui';
|
|
|
|
import {
|
|
utilFastMouse,
|
|
utilFunctor,
|
|
utilRebind,
|
|
utilSetTransform
|
|
} from '../util';
|
|
|
|
import { utilBindOnce } from '../util/bind_once';
|
|
import { utilGetDimensions } from '../util/dimensions';
|
|
|
|
|
|
|
|
export function rendererMap(context) {
|
|
|
|
var dimensions = [1, 1],
|
|
dispatch = d3_dispatch('move', 'drawn'),
|
|
projection = context.projection,
|
|
curtainProjection = context.curtainProjection,
|
|
dblclickEnabled = true,
|
|
redrawEnabled = true,
|
|
transformStart = projection.transform(),
|
|
transformLast,
|
|
transformed = false,
|
|
minzoom = 0,
|
|
drawLayers = svgLayers(projection, context),
|
|
drawPoints = svgPoints(projection, context),
|
|
drawVertices = svgVertices(projection, context),
|
|
drawLines = svgLines(projection, context),
|
|
drawAreas = svgAreas(projection, context),
|
|
drawMidpoints = svgMidpoints(projection, context),
|
|
drawLabels = svgLabels(projection, context),
|
|
supersurface = d3_select(null),
|
|
wrapper = d3_select(null),
|
|
surface = d3_select(null),
|
|
mouse,
|
|
mousemove;
|
|
|
|
var zoom = d3_zoom()
|
|
.scaleExtent([ztok(2), ztok(24)])
|
|
.interpolate(d3_interpolate)
|
|
.filter(zoomEventFilter)
|
|
.on('zoom', zoomPan);
|
|
|
|
var _selection = d3_select(null);
|
|
|
|
var scheduleRedraw = _throttle(redraw, 750);
|
|
// var isRedrawScheduled = false;
|
|
// var pendingRedrawCall;
|
|
// function scheduleRedraw() {
|
|
// // Only schedule the redraw if one has not already been set.
|
|
// if (isRedrawScheduled) return;
|
|
// isRedrawScheduled = true;
|
|
// var that = this;
|
|
// var args = arguments;
|
|
// pendingRedrawCall = window.requestIdleCallback(function () {
|
|
// // Reset the boolean so future redraws can be set.
|
|
// isRedrawScheduled = false;
|
|
// redraw.apply(that, args);
|
|
// }, { timeout: 1400 });
|
|
// }
|
|
|
|
function cancelPendingRedraw() {
|
|
scheduleRedraw.cancel();
|
|
// isRedrawScheduled = false;
|
|
// window.cancelIdleCallback(pendingRedrawCall);
|
|
}
|
|
|
|
function map(selection) {
|
|
|
|
_selection = selection;
|
|
|
|
context
|
|
.on('change.map', immediateRedraw);
|
|
|
|
var osm = context.connection();
|
|
if (osm) {
|
|
osm.on('change.map', immediateRedraw);
|
|
}
|
|
|
|
context.history()
|
|
.on('change.map', immediateRedraw)
|
|
.on('undone.map redone.map', function(stack) {
|
|
var mode = context.mode().id;
|
|
if (mode !== 'browse' && mode !== 'select') return;
|
|
|
|
var followSelected = false;
|
|
if (Array.isArray(stack.selectedIDs)) {
|
|
followSelected = (stack.selectedIDs.length === 1 && stack.selectedIDs[0][0] === 'n');
|
|
context.enter(
|
|
modeSelect(context, stack.selectedIDs).follow(followSelected)
|
|
);
|
|
}
|
|
if (!followSelected && stack.transform) {
|
|
map.transformEase(stack.transform);
|
|
}
|
|
});
|
|
|
|
context.background()
|
|
.on('change.map', immediateRedraw);
|
|
|
|
context.features()
|
|
.on('redraw.map', immediateRedraw);
|
|
|
|
drawLayers
|
|
.on('change.map', function() {
|
|
context.background().updateImagery();
|
|
immediateRedraw();
|
|
});
|
|
|
|
selection
|
|
.on('dblclick.map', dblClick)
|
|
.call(zoom)
|
|
.call(zoom.transform, projection.transform());
|
|
|
|
supersurface = selection.append('div')
|
|
.attr('id', 'supersurface')
|
|
.call(utilSetTransform, 0, 0);
|
|
|
|
// Need a wrapper div because Opera can't cope with an absolutely positioned
|
|
// SVG element: http://bl.ocks.org/jfirebaugh/6fbfbd922552bf776c16
|
|
wrapper = supersurface
|
|
.append('div')
|
|
.attr('class', 'layer layer-data');
|
|
|
|
map.surface = surface = wrapper
|
|
.call(drawLayers)
|
|
.selectAll('.surface')
|
|
.attr('id', 'surface');
|
|
|
|
surface
|
|
.call(drawLabels.observe)
|
|
.on('mousedown.zoom', function() {
|
|
if (d3_event.button === 2) {
|
|
d3_event.stopPropagation();
|
|
}
|
|
}, true)
|
|
.on('mouseup.zoom', function() {
|
|
if (resetTransform()) immediateRedraw();
|
|
})
|
|
.on('mousemove.map', function() {
|
|
mousemove = d3_event;
|
|
})
|
|
.on('mouseover.vertices', function() {
|
|
if (map.editable() && !transformed) {
|
|
var hover = d3_event.target.__data__;
|
|
surface.selectAll('.data-layer-osm')
|
|
.call(drawVertices.drawHover, context.graph(), hover, map.extent());
|
|
dispatch.call('drawn', this, { full: false });
|
|
}
|
|
})
|
|
.on('mouseout.vertices', function() {
|
|
if (map.editable() && !transformed) {
|
|
var hover = d3_event.relatedTarget && d3_event.relatedTarget.__data__;
|
|
surface.selectAll('.data-layer-osm')
|
|
.call(drawVertices.drawHover, context.graph(), hover, map.extent());
|
|
dispatch.call('drawn', this, { full: false });
|
|
}
|
|
});
|
|
|
|
supersurface
|
|
.call(context.background());
|
|
|
|
context.on('enter.map', function() {
|
|
if (map.editable() && !transformed) {
|
|
|
|
// redraw immediately any objects affected by a change in selectedIDs.
|
|
var graph = context.graph();
|
|
var selectedAndParents = {};
|
|
context.selectedIDs().forEach(function(id) {
|
|
var entity = graph.hasEntity(id);
|
|
if (entity) {
|
|
selectedAndParents[entity.id] = entity;
|
|
if (entity.type === 'node') {
|
|
graph.parentWays(entity).forEach(function(parent) {
|
|
selectedAndParents[parent.id] = parent;
|
|
});
|
|
}
|
|
}
|
|
});
|
|
var data = _values(selectedAndParents);
|
|
var filter = function(d) { return d.id in selectedAndParents; };
|
|
|
|
data = context.features().filter(data, graph);
|
|
|
|
surface.selectAll('.data-layer-osm')
|
|
.call(drawVertices.drawSelected, graph, map.extent())
|
|
.call(drawLines, graph, data, filter)
|
|
.call(drawAreas, graph, data, filter)
|
|
.call(drawMidpoints, graph, data, filter, map.trimmedExtent());
|
|
|
|
dispatch.call('drawn', this, { full: false });
|
|
|
|
|
|
// redraw everything else later
|
|
scheduleRedraw();
|
|
}
|
|
});
|
|
|
|
map.dimensions(utilGetDimensions(selection));
|
|
}
|
|
|
|
|
|
function zoomEventFilter() {
|
|
// Fix for #2151, (see also d3/d3-zoom#60, d3/d3-brush#18)
|
|
// Intercept `mousedown` and check if there is an orphaned zoom gesture.
|
|
// This can happen if a previous `mousedown` occurred without a `mouseup`.
|
|
// If we detect this, dispatch `mouseup` to complete the orphaned gesture,
|
|
// so that d3-zoom won't stop propagation of new `mousedown` events.
|
|
if (d3_event.type === 'mousedown') {
|
|
var hasOrphan = false;
|
|
var listeners = window.__on;
|
|
for (var i = 0; i < listeners.length; i++) {
|
|
var listener = listeners[i];
|
|
if (listener.name === 'zoom' && listener.type === 'mouseup') {
|
|
hasOrphan = true;
|
|
break;
|
|
}
|
|
}
|
|
if (hasOrphan) {
|
|
var event = window.CustomEvent;
|
|
if (event) {
|
|
event = new event('mouseup');
|
|
} else {
|
|
event = window.document.createEvent('Event');
|
|
event.initEvent('mouseup', false, false);
|
|
}
|
|
// Event needs to be dispatched with an event.view property.
|
|
event.view = window;
|
|
window.dispatchEvent(event);
|
|
}
|
|
}
|
|
|
|
return d3_event.button !== 2; // ignore right clicks
|
|
}
|
|
|
|
|
|
function ztok(z) {
|
|
return 256 * Math.pow(2, z);
|
|
}
|
|
|
|
function ktoz(k) {
|
|
return Math.max(Math.log(k) / Math.LN2 - 8, 0);
|
|
}
|
|
|
|
function pxCenter() {
|
|
return [dimensions[0] / 2, dimensions[1] / 2];
|
|
}
|
|
|
|
|
|
function drawVector(difference, extent) {
|
|
var mode = context.mode();
|
|
var graph = context.graph();
|
|
var features = context.features();
|
|
var all = context.intersects(map.extent());
|
|
var fullRedraw = false;
|
|
var data;
|
|
var filter;
|
|
|
|
if (difference) {
|
|
var complete = difference.complete(map.extent());
|
|
data = _compact(_values(complete));
|
|
filter = function(d) { return d.id in complete; };
|
|
features.clear(data);
|
|
|
|
} else {
|
|
// force a full redraw if gatherStats detects that a feature
|
|
// should be auto-hidden (e.g. points or buildings)..
|
|
if (features.gatherStats(all, graph, dimensions)) {
|
|
extent = undefined;
|
|
}
|
|
|
|
if (extent) {
|
|
data = context.intersects(map.extent().intersection(extent));
|
|
var set = d3_set(_map(data, 'id'));
|
|
filter = function(d) { return set.has(d.id); };
|
|
|
|
} else {
|
|
data = all;
|
|
fullRedraw = true;
|
|
filter = utilFunctor(true);
|
|
}
|
|
}
|
|
|
|
data = features.filter(data, graph);
|
|
|
|
if (mode && mode.id === 'select') {
|
|
// update selected vertices - the user might have just double-clicked a way,
|
|
// creating a new vertex, triggering a partial redraw without a mode change
|
|
surface.selectAll('.data-layer-osm')
|
|
.call(drawVertices.drawSelected, graph, map.extent());
|
|
}
|
|
|
|
surface.selectAll('.data-layer-osm')
|
|
.call(drawVertices, graph, data, filter, map.extent(), fullRedraw)
|
|
.call(drawLines, graph, data, filter)
|
|
.call(drawAreas, graph, data, filter)
|
|
.call(drawMidpoints, graph, data, filter, map.trimmedExtent())
|
|
.call(drawLabels, graph, data, filter, dimensions, fullRedraw)
|
|
.call(drawPoints, graph, data, filter);
|
|
|
|
dispatch.call('drawn', this, {full: true});
|
|
}
|
|
|
|
|
|
function editOff() {
|
|
context.features().resetStats();
|
|
surface.selectAll('.layer-osm *').remove();
|
|
context.enter(modeBrowse(context));
|
|
dispatch.call('drawn', this, {full: true});
|
|
}
|
|
|
|
|
|
function dblClick() {
|
|
if (!dblclickEnabled) {
|
|
d3_event.preventDefault();
|
|
d3_event.stopImmediatePropagation();
|
|
}
|
|
}
|
|
|
|
|
|
function zoomPan(manualEvent) {
|
|
var event = (manualEvent || d3_event),
|
|
source = event.sourceEvent,
|
|
eventTransform = event.transform;
|
|
|
|
if (transformStart.x === eventTransform.x &&
|
|
transformStart.y === eventTransform.y &&
|
|
transformStart.k === eventTransform.k) {
|
|
return; // no change
|
|
}
|
|
|
|
// Normalize mousewheel - #3029
|
|
// If wheel delta is provided in LINE units, recalculate it in PIXEL units
|
|
// We are essentially redoing the calculations that occur here:
|
|
// https://github.com/d3/d3-zoom/blob/78563a8348aa4133b07cac92e2595c2227ca7cd7/src/zoom.js#L203
|
|
// See this for more info:
|
|
// https://github.com/basilfx/normalize-wheel/blob/master/src/normalizeWheel.js
|
|
if (source && source.type === 'wheel' && source.deltaMode === 1 /* LINE */) {
|
|
// pick sensible scroll amount if user scrolling fast or slow..
|
|
var lines = Math.abs(source.deltaY),
|
|
scroll = lines > 2 ? 40 : lines * 10;
|
|
|
|
var t0 = transformed ? transformLast : transformStart,
|
|
p0 = mouse(source),
|
|
p1 = t0.invert(p0),
|
|
k2 = t0.k * Math.pow(2, -source.deltaY * scroll / 500),
|
|
x2 = p0[0] - p1[0] * k2,
|
|
y2 = p0[1] - p1[1] * k2;
|
|
|
|
eventTransform = d3_zoomIdentity.translate(x2,y2).scale(k2);
|
|
_selection.node().__zoom = eventTransform;
|
|
}
|
|
|
|
if (ktoz(eventTransform.k * 2 * Math.PI) < minzoom) {
|
|
surface.interrupt();
|
|
uiFlash().text(t('cannot_zoom'))();
|
|
setZoom(context.minEditableZoom(), true);
|
|
scheduleRedraw();
|
|
dispatch.call('move', this, map);
|
|
return;
|
|
}
|
|
|
|
projection.transform(eventTransform);
|
|
|
|
var scale = eventTransform.k / transformStart.k,
|
|
tX = (eventTransform.x / scale - transformStart.x) * scale,
|
|
tY = (eventTransform.y / scale - transformStart.y) * scale;
|
|
|
|
if (context.inIntro()) {
|
|
curtainProjection.transform({
|
|
x: eventTransform.x - tX,
|
|
y: eventTransform.y - tY,
|
|
k: eventTransform.k
|
|
});
|
|
}
|
|
|
|
mousemove = event;
|
|
transformed = true;
|
|
transformLast = eventTransform;
|
|
utilSetTransform(supersurface, tX, tY, scale);
|
|
scheduleRedraw();
|
|
|
|
dispatch.call('move', this, map);
|
|
}
|
|
|
|
|
|
function resetTransform() {
|
|
if (!transformed) return false;
|
|
|
|
// deprecation warning - Radial Menu to be removed in iD v3
|
|
surface.selectAll('.edit-menu, .radial-menu').interrupt().remove();
|
|
utilSetTransform(supersurface, 0, 0);
|
|
transformed = false;
|
|
if (context.inIntro()) {
|
|
curtainProjection.transform(projection.transform());
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
function redraw(difference, extent) {
|
|
if (surface.empty() || !redrawEnabled) return;
|
|
|
|
// If we are in the middle of a zoom/pan, we can't do differenced redraws.
|
|
// It would result in artifacts where differenced entities are redrawn with
|
|
// one transform and unchanged entities with another.
|
|
if (resetTransform()) {
|
|
difference = extent = undefined;
|
|
}
|
|
|
|
var z = String(~~map.zoom());
|
|
if (surface.attr('data-zoom') !== z) {
|
|
surface.attr('data-zoom', z)
|
|
.classed('low-zoom', z <= 16);
|
|
}
|
|
|
|
if (!difference) {
|
|
supersurface.call(context.background());
|
|
}
|
|
|
|
wrapper
|
|
.call(drawLayers);
|
|
|
|
// OSM
|
|
if (map.editable()) {
|
|
context.loadTiles(projection, dimensions);
|
|
drawVector(difference, extent);
|
|
} else {
|
|
editOff();
|
|
}
|
|
|
|
transformStart = projection.transform();
|
|
|
|
return map;
|
|
}
|
|
|
|
|
|
|
|
var immediateRedraw = function(difference, extent) {
|
|
if (!difference && !extent) cancelPendingRedraw();
|
|
redraw(difference, extent);
|
|
};
|
|
|
|
|
|
function pointLocation(p) {
|
|
var translate = projection.translate(),
|
|
scale = projection.scale() * 2 * Math.PI;
|
|
return [(p[0] - translate[0]) / scale, (p[1] - translate[1]) / scale];
|
|
}
|
|
|
|
|
|
function locationPoint(l) {
|
|
var translate = projection.translate(),
|
|
scale = projection.scale() * 2 * Math.PI;
|
|
return [l[0] * scale + translate[0], l[1] * scale + translate[1]];
|
|
}
|
|
|
|
|
|
map.mouse = function() {
|
|
var event = mousemove || d3_event;
|
|
if (event) {
|
|
var s;
|
|
while ((s = event.sourceEvent)) { event = s; }
|
|
return mouse(event);
|
|
}
|
|
return null;
|
|
};
|
|
|
|
|
|
map.mouseCoordinates = function() {
|
|
return projection.invert(map.mouse());
|
|
};
|
|
|
|
|
|
map.dblclickEnable = function(_) {
|
|
if (!arguments.length) return dblclickEnabled;
|
|
dblclickEnabled = _;
|
|
return map;
|
|
};
|
|
|
|
|
|
map.redrawEnable = function(_) {
|
|
if (!arguments.length) return redrawEnabled;
|
|
redrawEnabled = _;
|
|
return map;
|
|
};
|
|
|
|
|
|
function setTransform(t2, duration, force) {
|
|
var t = projection.transform();
|
|
if (!force && t2.k === t.k && t2.x === t.x && t2.y === t.y) {
|
|
return false;
|
|
}
|
|
|
|
if (duration) {
|
|
_selection
|
|
.transition()
|
|
.duration(duration)
|
|
.on('start', function() { map.startEase(); })
|
|
.call(zoom.transform, d3_zoomIdentity.translate(t2.x, t2.y).scale(t2.k));
|
|
} else {
|
|
projection.transform(t2);
|
|
transformStart = t2;
|
|
_selection.call(zoom.transform, transformStart);
|
|
}
|
|
}
|
|
|
|
|
|
function setZoom(z2, force, duration) {
|
|
if (z2 === map.zoom() && !force) {
|
|
return false;
|
|
}
|
|
|
|
var k = projection.scale(),
|
|
k2 = Math.max(ztok(2), Math.min(ztok(24), ztok(z2))) / (2 * Math.PI),
|
|
center = pxCenter(),
|
|
l = pointLocation(center);
|
|
|
|
projection.scale(k2);
|
|
|
|
var t = projection.translate();
|
|
l = locationPoint(l);
|
|
|
|
t[0] += center[0] - l[0];
|
|
t[1] += center[1] - l[1];
|
|
|
|
if (duration) {
|
|
projection.scale(k); // reset scale
|
|
_selection
|
|
.transition()
|
|
.duration(duration)
|
|
.on('start', function() { map.startEase(); })
|
|
.call(zoom.transform, d3_zoomIdentity.translate(t[0], t[1]).scale(k2));
|
|
} else {
|
|
projection.translate(t);
|
|
transformStart = projection.transform();
|
|
_selection.call(zoom.transform, transformStart);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
function setCenter(loc2, duration) {
|
|
var c = map.center();
|
|
if (loc2[0] === c[0] && loc2[1] === c[1]) {
|
|
return false;
|
|
}
|
|
|
|
var t = projection.translate(),
|
|
k = projection.scale(),
|
|
pxC = pxCenter(),
|
|
ll = projection(loc2);
|
|
|
|
t[0] = t[0] - ll[0] + pxC[0];
|
|
t[1] = t[1] - ll[1] + pxC[1];
|
|
|
|
if (duration) {
|
|
_selection
|
|
.transition()
|
|
.duration(duration)
|
|
.on('start', function() { map.startEase(); })
|
|
.call(zoom.transform, d3_zoomIdentity.translate(t[0], t[1]).scale(k));
|
|
} else {
|
|
projection.translate(t);
|
|
transformStart = projection.transform();
|
|
_selection.call(zoom.transform, transformStart);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
map.pan = function(delta, duration) {
|
|
var t = projection.translate(),
|
|
k = projection.scale();
|
|
|
|
t[0] += delta[0];
|
|
t[1] += delta[1];
|
|
|
|
if (duration) {
|
|
_selection
|
|
.transition()
|
|
.duration(duration)
|
|
.on('start', function() { map.startEase(); })
|
|
.call(zoom.transform, d3_zoomIdentity.translate(t[0], t[1]).scale(k));
|
|
} else {
|
|
projection.translate(t);
|
|
transformStart = projection.transform();
|
|
_selection.call(zoom.transform, transformStart);
|
|
dispatch.call('move', this, map);
|
|
immediateRedraw();
|
|
}
|
|
|
|
return map;
|
|
};
|
|
|
|
|
|
map.dimensions = function(_) {
|
|
if (!arguments.length) return dimensions;
|
|
var center = map.center();
|
|
dimensions = _;
|
|
drawLayers.dimensions(dimensions);
|
|
context.background().dimensions(dimensions);
|
|
projection.clipExtent([[0, 0], dimensions]);
|
|
mouse = utilFastMouse(supersurface.node());
|
|
setCenter(center);
|
|
|
|
scheduleRedraw();
|
|
return map;
|
|
};
|
|
|
|
|
|
function zoomIn(delta) {
|
|
setZoom(~~map.zoom() + delta, true, 250);
|
|
}
|
|
|
|
function zoomOut(delta) {
|
|
setZoom(~~map.zoom() - delta, true, 250);
|
|
}
|
|
|
|
map.zoomIn = function() { zoomIn(1); };
|
|
map.zoomInFurther = function() { zoomIn(4); };
|
|
|
|
map.zoomOut = function() { zoomOut(1); };
|
|
map.zoomOutFurther = function() { zoomOut(4); };
|
|
|
|
|
|
map.center = function(loc2) {
|
|
if (!arguments.length) {
|
|
return projection.invert(pxCenter());
|
|
}
|
|
|
|
if (setCenter(loc2)) {
|
|
dispatch.call('move', this, map);
|
|
}
|
|
|
|
scheduleRedraw();
|
|
return map;
|
|
};
|
|
|
|
|
|
map.zoom = function(z2) {
|
|
if (!arguments.length) {
|
|
return Math.max(ktoz(projection.scale() * 2 * Math.PI), 0);
|
|
}
|
|
|
|
if (z2 < minzoom) {
|
|
surface.interrupt();
|
|
uiFlash().text(t('cannot_zoom'))();
|
|
z2 = context.minEditableZoom();
|
|
}
|
|
|
|
if (setZoom(z2)) {
|
|
dispatch.call('move', this, map);
|
|
}
|
|
|
|
scheduleRedraw();
|
|
return map;
|
|
};
|
|
|
|
|
|
map.zoomTo = function(entity, zoomLimits) {
|
|
var extent = entity.extent(context.graph());
|
|
if (!isFinite(extent.area())) return;
|
|
|
|
var z2 = map.trimmedExtentZoom(extent);
|
|
zoomLimits = zoomLimits || [context.minEditableZoom(), 20];
|
|
map.centerZoom(extent.center(), Math.min(Math.max(z2, zoomLimits[0]), zoomLimits[1]));
|
|
};
|
|
|
|
|
|
map.centerZoom = function(loc2, z2) {
|
|
var centered = setCenter(loc2),
|
|
zoomed = setZoom(z2);
|
|
|
|
if (centered || zoomed) {
|
|
dispatch.call('move', this, map);
|
|
}
|
|
|
|
scheduleRedraw();
|
|
return map;
|
|
};
|
|
|
|
|
|
map.centerEase = function(loc2, duration) {
|
|
duration = duration || 250;
|
|
setCenter(loc2, duration);
|
|
return map;
|
|
};
|
|
|
|
|
|
map.zoomEase = function(z2, duration) {
|
|
duration = duration || 250;
|
|
setZoom(z2, false, duration);
|
|
return map;
|
|
};
|
|
|
|
|
|
map.transformEase = function(t2, duration) {
|
|
duration = duration || 250;
|
|
setTransform(t2, duration, false);
|
|
return map;
|
|
};
|
|
|
|
|
|
map.startEase = function() {
|
|
utilBindOnce(surface, 'mousedown.ease', function() {
|
|
map.cancelEase();
|
|
});
|
|
return map;
|
|
};
|
|
|
|
|
|
map.cancelEase = function() {
|
|
_selection.interrupt();
|
|
return map;
|
|
};
|
|
|
|
|
|
map.extent = function(_) {
|
|
if (!arguments.length) {
|
|
return new geoExtent(projection.invert([0, dimensions[1]]),
|
|
projection.invert([dimensions[0], 0]));
|
|
} else {
|
|
var extent = geoExtent(_);
|
|
map.centerZoom(extent.center(), map.extentZoom(extent));
|
|
}
|
|
};
|
|
|
|
|
|
map.trimmedExtent = function(_) {
|
|
if (!arguments.length) {
|
|
var headerY = 60, footerY = 30, pad = 10;
|
|
return new geoExtent(projection.invert([pad, dimensions[1] - footerY - pad]),
|
|
projection.invert([dimensions[0] - pad, headerY + pad]));
|
|
} else {
|
|
var extent = geoExtent(_);
|
|
map.centerZoom(extent.center(), map.trimmedExtentZoom(extent));
|
|
}
|
|
};
|
|
|
|
|
|
function calcZoom(extent, dim) {
|
|
var tl = projection([extent[0][0], extent[1][1]]),
|
|
br = projection([extent[1][0], extent[0][1]]);
|
|
|
|
// Calculate maximum zoom that fits extent
|
|
var hFactor = (br[0] - tl[0]) / dim[0],
|
|
vFactor = (br[1] - tl[1]) / dim[1],
|
|
hZoomDiff = Math.log(Math.abs(hFactor)) / Math.LN2,
|
|
vZoomDiff = Math.log(Math.abs(vFactor)) / Math.LN2,
|
|
newZoom = map.zoom() - Math.max(hZoomDiff, vZoomDiff);
|
|
|
|
return newZoom;
|
|
}
|
|
|
|
|
|
map.extentZoom = function(_) {
|
|
return calcZoom(geoExtent(_), dimensions);
|
|
};
|
|
|
|
|
|
map.trimmedExtentZoom = function(_) {
|
|
var trimY = 120, trimX = 40,
|
|
trimmed = [dimensions[0] - trimX, dimensions[1] - trimY];
|
|
return calcZoom(geoExtent(_), trimmed);
|
|
};
|
|
|
|
|
|
map.editable = function() {
|
|
var osmLayer = surface.selectAll('.data-layer-osm');
|
|
if (!osmLayer.empty() && osmLayer.classed('disabled')) return false;
|
|
|
|
return map.zoom() >= context.minEditableZoom();
|
|
};
|
|
|
|
|
|
map.minzoom = function(_) {
|
|
if (!arguments.length) return minzoom;
|
|
minzoom = _;
|
|
return map;
|
|
};
|
|
|
|
|
|
map.layers = drawLayers;
|
|
|
|
|
|
return utilRebind(map, dispatch, 'on');
|
|
}
|