mirror of
https://github.com/FoggedLens/iD.git
synced 2026-02-13 17:23:02 +00:00
1. All services are disabled in testing now to prevent network accesses 2. Only services are enabled when needed to test something 3. Many changes throughout code to allow iD to run with services disabled (e.g. check for osm service instead of assuming context.connection() will work) 4. Actually export the services so we can disable and enable them
758 lines
22 KiB
JavaScript
758 lines
22 KiB
JavaScript
import * as d3 from 'd3';
|
|
import _ from 'lodash';
|
|
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);
|
|
|
|
|
|
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(), map.zoom());
|
|
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(), map.zoom());
|
|
dispatch.call('drawn', this, {full: false});
|
|
}
|
|
});
|
|
|
|
supersurface
|
|
.call(context.background());
|
|
|
|
context.on('enter.map', function() {
|
|
if (map.editable() && !transformed) {
|
|
var all = context.intersects(map.extent()),
|
|
filter = utilFunctor(true),
|
|
graph = context.graph();
|
|
|
|
all = context.features().filter(all, graph);
|
|
surface.selectAll('.data-layer-osm')
|
|
.call(drawVertices, graph, all, filter, map.extent(), map.zoom())
|
|
.call(drawMidpoints, graph, all, filter, map.trimmedExtent());
|
|
dispatch.call('drawn', this, {full: false});
|
|
}
|
|
});
|
|
|
|
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 graph = context.graph(),
|
|
features = context.features(),
|
|
all = context.intersects(map.extent()),
|
|
data, 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;
|
|
filter = utilFunctor(true);
|
|
}
|
|
}
|
|
|
|
data = features.filter(data, graph);
|
|
|
|
surface.selectAll('.data-layer-osm')
|
|
.call(drawVertices, graph, data, filter, map.extent(), map.zoom())
|
|
.call(drawLines, graph, data, filter)
|
|
.call(drawAreas, graph, data, filter)
|
|
.call(drawMidpoints, graph, data, filter, map.trimmedExtent())
|
|
.call(drawLabels, graph, data, filter, dimensions, !difference && !extent)
|
|
.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);
|
|
queueRedraw();
|
|
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);
|
|
queueRedraw();
|
|
|
|
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 queueRedraw = _.throttle(redraw, 750);
|
|
|
|
|
|
var immediateRedraw = function(difference, extent) {
|
|
if (!difference && !extent) queueRedraw.cancel();
|
|
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);
|
|
|
|
queueRedraw();
|
|
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);
|
|
}
|
|
|
|
queueRedraw();
|
|
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);
|
|
}
|
|
|
|
queueRedraw();
|
|
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);
|
|
}
|
|
|
|
queueRedraw();
|
|
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() {
|
|
return map.zoom() >= context.minEditableZoom();
|
|
};
|
|
|
|
|
|
map.minzoom = function(_) {
|
|
if (!arguments.length) return minzoom;
|
|
minzoom = _;
|
|
return map;
|
|
};
|
|
|
|
|
|
map.layers = drawLayers;
|
|
|
|
|
|
return utilRebind(map, dispatch, 'on');
|
|
}
|