Search code examples
javascriptdictionaryprocessingzooming

How can I create a Google Maps style zoom using Javascript and Processing


I already have panning and zooming for my map program, as well as markers, but the zooming code I currently have works sporadically and doesn't feel natural. It seems as though it gets progressively worse the further from the center of the map you are. I've been trying to achieve a zoom similar to that seen in Google Maps, with little luck.

This is the current zooming code:

//Zoom function
function mouseWheel(event) {
  var e = event.deltaY;

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

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

  //Zoom out
  if (e > 0) {
    if (scale > minScale && !(windowWidth > 1840 && scale == -3)) {
      scale--;
      imgH = (int)(imgOGH / (1 + (zoomFactor * abs(scale))));
      imgW = (int)(imgOGW / (1 + (zoomFactor * abs(scale)))):

      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;
      }               
    }
  }

And here is how it's displayed in the draw loop:

function draw() {
  var zoomModifier = (1 + (zoomFactor * abs(scale)));

  background(0);
  imageMode(CENTER);
  image(map, centerX, centerY, imgW, imgH);

  for (var m of markers) {
    if (m.x/zoomModifier > viewX - (width/2) && m.x/zoomModifier < viewX + (width/2) && m.y/zoomModifier > viewY - (height/2) && m.y/zoomModifier < viewY + (height/2)) m.display();
  }
}

CenterX is originally set to half of the full map's width (and CenterY to half the height).

The full code can be viewed here and the project can be seen in action here.

Any help would be greatly appreciated.


Solution

  • This is a problem that I find myself having to think about every time I do something that involves interactive zooming. I'm always amazed at how awful the zoom functionality is in programs like MS Paint.

    You're actually quite close, and I think you have the right idea: find the distance between the cursor and the image center and scale it properly. Here's how I like to think about it: zooming is scaling the value (centerX - mouseX) by a certain amount (same with Y). In mock code, this would look something like

    (centerX - mouseX) = zoom * (centerX - mouseX);
    

    and in real code, it turns out that you just do some algebra:

    centerX = mouseX + (zoom * (centerX - mouseX));
    

    As an aside, there is a key difference between this and the zooming you have. You're using a reciprocal function, while this version is an exponential. I think that what Google uses is probably more along the lines of an exponential.

    Here is my full alteration (based on guessing how you define zoomFactor):

    function mouseWheel(event) {
      zoom = pow(1 + zoomFactor, event.deltaY/125);
      imgW *= zoom;
      imgH *= zoom;
      centerX = mouseX + (zoom * (centerX - mouseX));
      centerY = mouseY + (zoom * (centerY - mouseY));
    }