Instantly share code, notes, and snippets.

Forked from mbostock/.block
Last active October 12, 2017 15:28
Show Gist options
• Save patricksurry/6621971 to your computer and use it in GitHub Desktop.
Rolling pan and zoom with Mercator projection

Example illustrating zoom and pan with a "rolling" Mercator projection. Drag left-right to rotate projection cylinder, and up-down to translate, clamped by max absolute latitude. Ensures projection always fits properly in viewbox.

This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters

Display the source blob
Display the rendered blob
Raw
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

### AlfredMan commented Apr 21, 2016

Thanks for your code. Very clear and self explanatory. I do run into problem when I try to make custom zoom extent to a bounding box containing some data at different lng/lat. Problem is: when I calculate the bounding box, sometimes the map will calculate the bounding box from the outer left margin warping around the the outer right margin. Wonder is there any simple algorithm to create bounding box always warping the inside margin.

My code:

// datas is an array contains lng, lat information
let boundLngLat=[datas[0].lng,datas[0].lat],[datas[0].lng,datas[0].lat];
for (let data of datas){
boundLngLat[0][0]=Math.min(data.lng,boundLngLat[0][0]);
boundLngLat[0][1]=Math.max(data.lat,boundLngLat[0][1]);
boundLngLat[1][0]=Math.max(data.lng,boundLngLat[1][0]);
boundLngLat[1][1]=Math.min(data.lat,boundLngLat[1][1]);
}
let boundXY=[this.projection(boundLngLat[0]),this.projection(boundLngLat[1]) ];

then I use try to zoom extent to this boundXY by:
redraw(bound){
if (bound){
let boundCenter=[(boundXY[0][0]+boundXY[1][0])/2 , (boundXY[0][1]+boundXY[1][1])/2];

let dx=this.width/2-boundCenter[0];
let dy=this.height/2-boundCenter[1];

let yaw = this.projection.rotate()[0],
tp = this.projection.translate();
this.projection.rotate([yaw+360.*dx/this.width*this.scaleExtent[0]/scale, 0, 0]);
let b = this.mercatorBounds(this.projection, this.maxlat);
if (b[0][1] + dy > 0) dy = -b[0][1];
else if (b[1][1] + dy < this.height) dy = this.height-b[1][1];
this.projection.translate([tp[0],tp[1]+dy]);

this.g.selectAll('path')       // re-project path data
.attr('d', this.path);

let boundWidth=(boundXY[1][0]-boundXY[0][0]);
let boundHeight=(boundXY[1][1]-boundXY[0][1]);

scale=1/Math.max(boundWidth / this.width, boundHeight / this.height);
scale*=this.projection.scale();

if (scale<this.scaleExtent[0]){scale=this.scaleExtent[0]}
if (scale>this.scaleExtent[1]){scale=this.scaleExtent[1]}

this.projection.scale(scale);
this.g.selectAll('path')       // re-project path data
.attr('d', this.path);

//These 2 lines here doesnt work with case when user do mouse zoom/pan after this custom zoom caused d3.event.zoom and d3.event.translate seems out of sync from this.slast and this.tlast registered here.
this.slast=scale;
this.tlast = this.projection.translate();

}else if (d3.event){
... run original code for zoom/pan

}