Map Zoom and Pan in Processing

Zooming and panning are very common functions in interactive maps and other similar image-viewing applications. In this blog post, I included a snippet of Processing code that allows zooming and panning on a map or other images using intuitive mouse gestures, which mimic a Google-Map-like experience.

Last semester, I took a programming course in Cartography Design, and one of the assignments was to develop an interactive map for the SDSU campus in Processing. We were given the satellite image of the campus to use as the base map. I thought implementing the zooming and panning functions would be something easy given the core functions in Processing, but it turned out I was wrong (just like me on a lot of other things) , or at least, it is not as easy as I thought to implement a decent version of them.

Here are my expectations for the zooming and panning functions:
– The classic mouse click-and-drag will pan the map;
– Scrolling the mouse wheel upward or downward will zoom in/out;
– The focus point of zooming in/out should be where the cursor is pointing at;
(Thanks to online mapping services like Google Map, the points above can be summarized as Google-Map-like zoom and pan)
– The map should always fully occupies the display window. In other words, the background behind the map should never be visible.

There are a few code examples for map/image zoom and pan in Processing (see herehere, and here). But they are either too old (i.e., Processing 1.x), or not meeting all my four expectations (maybe I was being too greedy..?).. So, I’m sharing my own take here, and hope you don’t have to deal with it yourself — hence, save more time and energy to develop some more amazing interactive map features.

A gif demo of the map zoom and pan in Processing (Click if not playing).

A gif demo of the map zoom and pan in Processing (Click if not playing).

/*Map Zoom and Pan in Processing
Author: Jinlong Yang
Email: jinlong.yang@mail.sdsu.edu
*/

/*When use the code, you may change
the zoomFactor to have finer/coarser
zooming, or change the maxScale to
limit the most detailed level of zooming
*/

//Define the map vars
PImage map;
int imgW;
int imgH;

int centerX;
int centerY;

//Define the zoom vars
int scale = 1;
int maxScale = 10;
float zoomFactor = 0.4;

//Define the pan vars
int panFromX;
int panFromY;

int panToX;
int panToY;

int xShift = 0;
int yShift = 0;

void setup() {
  map = loadImage("image.jpg");

  imgW = map.width;
  imgH = map.height;

  centerX = imgW / 2;
  centerY = imgH / 2;

  size(800, 600);
}

void draw(){
  background(0);
  imageMode(CENTER);
  image(map, centerX, centerY, imgW, imgH);
}

//Pan function
void mousePressed(){
  if(mouseButton == LEFT){
    panFromX = mouseX;
    panFromY = mouseY;
  }
}

//Pan function continued..
void mouseDragged(){
  if(mouseButton == LEFT){
    panToX = mouseX;
    panToY = mouseY;

    xShift = panToX - panFromX;
    yShift = panToY - panFromY;

    //Only pan with the image occupies the whole display
    if(centerX - imgW / 2 <= 0
    && centerX + imgW / 2 >= width
    && centerY - imgH / 2 <= 0
    && centerY + imgH / 2 >= height){
      centerX = centerX + xShift;
      centerY = centerY + yShift;
    }

    //Set the constraints for pan
    if(centerX - imgW / 2 > 0){
      centerX = imgW / 2;
    }

    if(centerX + imgW / 2 < width){
      centerX = width - imgW / 2;
    }

    if(centerY - imgH / 2 > 0){
      centerY = imgH / 2;
    }

    if(centerY + imgH / 2 < height){
      centerY = height - imgH / 2;
    }

    panFromX = panToX;
    panFromY = panToY;
  }
}

//Zoom function
void mouseWheel(MouseEvent event) {
  float e = event.getAmount();

  //Zoom in
  if(e == -1){
    if(scale < maxScale){
      scale++;
      imgW = int(imgW * (1+zoomFactor));
      imgH = int(imgH * (1+zoomFactor));

      int oldCenterX = centerX;
      int oldCenterY = centerY;

      centerX = centerX - int(zoomFactor * (mouseX - centerX));
      centerY = centerY - int(zoomFactor * (mouseY - centerY));
    }
  }

  //Zoom out
  if(e == 1){
    if(scale < 1){
      scale = 1;
      imgW = map.width;
      imgH = map.height;
    }

    if(scale > 1){
      scale--;
      imgH = int(imgH/(1+zoomFactor));
      imgW = int(imgW/(1+zoomFactor));

      int oldCenterX = centerX;
      int oldCenterY = centerY;

      centerX = centerX + int((mouseX - centerX)
      * (zoomFactor/(zoomFactor + 1)));
      centerY = centerY + int((mouseY - centerY)
      * (zoomFactor/(zoomFactor + 1)));

      if(centerX - imgW / 2 > 0){
        centerX = imgW / 2;
      }

      if(centerX + imgW / 2 < width){
        centerX = width - imgW / 2;
      }

      if(centerY - imgH / 2 > 0){
        centerY = imgH / 2;
      }

      if(centerY + imgH / 2 < height){
        centerY = height - imgH / 2;
      }
    }
  }
}

The code snippet above is also available at my GitHub. As a closing note, this code snippet is not working if you want to deploy a web version using the Processing.js. I will update this post if I figure out how to do it in JavaScript.

Advertisements

2 thoughts on “Map Zoom and Pan in Processing

    • Hi Till,
      Thanks for the suggestion! I’ve briefly tried Unfolding Maps and it’s amazing. The reason I come up with this code was that I want to have zoom and pan functions on a specific image (e.g., a satellite image), instead of on raster images from web mapping services. Does Unfolding Maps support that?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s