Skip to content

Commit

Permalink
Add "rubberband" zoom.
Browse files Browse the repository at this point in the history
This resolves issue #346.
  • Loading branch information
manthey committed Jun 3, 2016
1 parent d8e109a commit 66414b5
Show file tree
Hide file tree
Showing 4 changed files with 290 additions and 28 deletions.
27 changes: 27 additions & 0 deletions src/event.js
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,33 @@ geo_event.brushend = 'geo_brushend';
//////////////////////////////////////////////////////////////////////////////
geo_event.brushstart = 'geo_brushstart';

//////////////////////////////////////////////////////////////////////////////
/**
* Triggered after a selection ends.
* The event object extends {@link geo.brushSelection}.
* @mixes geo.brushSelection
*/
//////////////////////////////////////////////////////////////////////////////
geo_event.select = 'geo_select';

//////////////////////////////////////////////////////////////////////////////
/**
* Triggered after a zoom selection ends.
* The event object extends {@link geo.brushSelection}.
* @mixes geo.brushSelection
*/
//////////////////////////////////////////////////////////////////////////////
geo_event.zoomselect = 'geo_zoomselect';

//////////////////////////////////////////////////////////////////////////////
/**
* Triggered after an unzoom selection ends.
* The event object extends {@link geo.brushSelection}.
* @mixes geo.brushSelection
*/
//////////////////////////////////////////////////////////////////////////////
geo_event.unzoomselect = 'geo_unzoomselect';

//////////////////////////////////////////////////////////////////////////////
/**
* Triggered before a map navigation animation begins. Set
Expand Down
98 changes: 90 additions & 8 deletions src/mapInteractor.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,19 +68,23 @@ var mapInteractor = function (args) {
zoomMoveButton: 'right',
zoomMoveModifiers: {},
rotateMoveButton: 'left',
rotateMoveModifiers: {'ctrl': true},
rotateMoveModifiers: {ctrl: true},
panWheelEnabled: false,
panWheelModifiers: {},
zoomWheelEnabled: true,
zoomWheelModifiers: {},
rotateWheelEnabled: true,
rotateWheelModifiers: {'ctrl': true},
rotateWheelModifiers: {ctrl: true},
wheelScaleX: 1,
wheelScaleY: 1,
zoomScale: 1,
rotateWheelScale: 6 * Math.PI / 180,
selectionButton: 'left',
selectionModifiers: {'shift': true},
selectionModifiers: {shift: true, ctrl: true},
zoomSelectionButton: 'left',
zoomSelectionModifiers: {shift: true},
unzoomSelectionButton: 'right',
unzoomSelectionModifiers: {shift: true},
momentum: {
enabled: true,
maxSpeed: 2.5,
Expand Down Expand Up @@ -202,7 +206,7 @@ var mapInteractor = function (args) {
// // a mousemove.
// click: {
// enabled: true | false,
// buttons: {'left': true, 'right': true, 'middle': true}
// buttons: {left: true, right: true, middle: true}
// duration: 0,
// cancelOnMove: true // cancels click if the mouse is moved before release
// }
Expand Down Expand Up @@ -406,7 +410,9 @@ var mapInteractor = function (args) {
if (m_options.panMoveButton === 'right' ||
m_options.zoomMoveButton === 'right' ||
m_options.rotateMoveButton === 'right' ||
m_options.selectionButton === 'right') {
m_options.selectionButton === 'right' ||
m_options.zoomSelectionButton === 'right' ||
m_options.unzoomSelectionButton === 'right') {
$node.on('contextmenu.geojs', function () { return false; });
}
return m_this;
Expand Down Expand Up @@ -644,6 +650,10 @@ var mapInteractor = function (args) {
action = 'rotate';
} else if (eventMatch(m_options.selectionButton, m_options.selectionModifiers)) {
action = 'select';
} else if (eventMatch(m_options.zoomSelectionButton, m_options.zoomSelectionModifiers)) {
action = 'zoomselect';
} else if (eventMatch(m_options.unzoomSelectionButton, m_options.unzoomSelectionModifiers)) {
action = 'unzoomselect';
}

// cancel transitions and momentum on click
Expand All @@ -668,7 +678,7 @@ var mapInteractor = function (args) {
delta: {x: 0, y: 0}
};

if (action === 'select') {
if (action === 'select' || action === 'zoomselect' || action === 'unzoomselect') {
// Make sure the old selection layer is gone.
if (m_selectionLayer) {
m_selectionLayer.clear();
Expand Down Expand Up @@ -789,7 +799,7 @@ var mapInteractor = function (args) {
cx = m_mouse.map.x - m_this.map().size().width / 2;
cy = m_mouse.map.y - m_this.map().size().height / 2;
m_this.map().rotation(m_state.origin.rotation + Math.atan2(cy, cx));
} else if (m_state.action === 'select') {
} else if (m_state.action === 'select' || m_state.action === 'zoomselect' || m_state.action === 'unzoomselect') {
// Get the bounds of the current selection
selectionObj = m_this._getSelection();
m_this.map().geoTrigger(geo_event.brush, selectionObj);
Expand Down Expand Up @@ -876,6 +886,71 @@ var mapInteractor = function (args) {
};
}

////////////////////////////////////////////////////////////////////////////
/**
* Based on the screen coodinates of a selection, zoom or unzoom and
* recenter.
*
* @private
* @param {string} action Either 'zoomselect' or 'unzoomselect'.
* @param {object} lowerLeft the x and y coordinates of the lower left corner
* of the zoom rectangle.
* @param {object} upperRight the x and y coordinates of the upper right
* corner of the zoom rectangle.
*/
////////////////////////////////////////////////////////////////////////////
this._zoomFromSelection = function (action, lowerLeft, upperRight) {
if (action !== 'zoomselect' && action !== 'unzoomselect') {
return;
}
if (lowerLeft.x === upperRight.x || lowerLeft.y === upperRight.y) {
return;
}
var zoom, center,
map = m_this.map(),
mapsize = map.size();
/* To arbitrarily handle rotation and projection, we center the map at the
* central coordinate of the selection and set the zoom level such that the
* four corners are just barely on the map. When unzooming (zooming out),
* we ensure that the previous view is centered in the selection but use
* the maximal size for the zoom factor. */
var scaling = {
x: Math.abs((upperRight.x - lowerLeft.x) / mapsize.width),
y: Math.abs((upperRight.y - lowerLeft.y) / mapsize.height)
};
if (action === 'zoomselect') {
center = map.displayToGcs({
x: (lowerLeft.x + upperRight.x) / 2,
y: (lowerLeft.y + upperRight.y) / 2
}, null);
zoom = map.zoom() - Math.log2(Math.max(scaling.x, scaling.y));
} else { /* unzoom */
/* To make the existing visible map entirely within the selection
* rectangle, this would be changed to Math.min instead of Math.max of
* the scaling factors. This felt wrong, though. */
zoom = map.zoom() + Math.log2(Math.max(scaling.x, scaling.y));
/* Record the current center. Later, this is panned to the center of the
* selection rectangle. */
center = map.center(undefined, null);
}
/* When discrete zoom is enable, always round down. We have to do this
* explicitly, as otherwise we may zoom too far and the selection will not
* be completely visible. */
if (map.discreteZoom()) {
zoom = Math.floor(zoom);
}
map.zoom(zoom);
if (action === 'zoomselect') {
map.center(center, null);
} else {
var newcenter = map.gcsToDisplay(center, null);
map.pan({
x: (lowerLeft.x + upperRight.x) / 2 - newcenter.x,
y: (lowerLeft.y + upperRight.y) / 2 - newcenter.y
});
}
};

////////////////////////////////////////////////////////////////////////////
/**
* Handle event when a mouse button is unpressed on the document.
Expand Down Expand Up @@ -903,7 +978,8 @@ var mapInteractor = function (args) {
evt.preventDefault();
}

if (m_state.action === 'select') {
if (m_state.action === 'select' || m_state.action === 'zoomselect' || m_state.action === 'unzoomselect') {
m_this._getMousePosition(evt);
selectionObj = m_this._getSelection();

m_selectionLayer.clear();
Expand All @@ -912,6 +988,9 @@ var mapInteractor = function (args) {
m_selectionQuad = null;

m_this.map().geoTrigger(geo_event.brushend, selectionObj);
m_this.map().geoTrigger(geo_event[m_state.action], selectionObj);
m_this._zoomFromSelection(m_state.action, selectionObj.display.lowerLeft,
selectionObj.display.upperRight);
}

// reset the interactor state
Expand Down Expand Up @@ -1410,6 +1489,9 @@ var mapInteractor = function (args) {
}
}
);
if (type.indexOf('.geojs') >= 0) {
$(document).trigger(evt);
}
$node.trigger(evt);
};
this._connectEvents();
Expand Down
11 changes: 0 additions & 11 deletions src/renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,17 +62,6 @@ var renderer = function (arg) {
}
};

////////////////////////////////////////////////////////////////////////////
/**
* Get base layer that belongs to this renderer
*/
////////////////////////////////////////////////////////////////////////////
this.baseLayer = function () {
if (m_this.map()) {
return m_this.map().baseLayer();
}
};

////////////////////////////////////////////////////////////////////////////
/**
* Get/Set if renderer has been initialized
Expand Down
Loading

0 comments on commit 66414b5

Please sign in to comment.