An illustration with the text: Zoom zoom Zoom zoom

Writing a map library from scratch - Part 2: Zooming


If you haven’t read Part 1 yet, I recommend you do so before continuing.


In the previous article, we created a static map made up by tiles on a specific zoom level. But how do we make it interactive?

Interactivity

In essence, zooming (or panning) on a map is just a manipulation of the map center and the zoom level. If you can figure out what the new zoom level and the new map center will be after an interaction, you just need to re-render the map with the these values.

Zooming

Zooming towards the center of the container is fairly simple. We just need to change the zoom level and re-render the map, as the center does not change. However, zooming towards or away from the mouse cursor is a bit more complicated. We need to calculate the new map center based on the mouse position.

Zooming towards mouse cursor

When you zoom towards the mouse cursor, the new center of the map will be at midpoint between the mouse cursor and the old center.

Zoom In Demo

Zooming away from mouse cursor

When you zoom away from the mouse cursor, it’s the other way around. The old center is located at the midpoint between mouse cursor and the new center.

Zoom Out Demo

How do we calculate the new center?

If we zoom in, we need to calculate the midpoint between the mouse cursor and the center. We will calculate the distance in pixels. Let us take the same map from part 1:

  • The center is:(13.4, 52.52).
  • The zoom level is 14.
  • One tile is 256 pixels wide and high.
  • The map is inside a container of 400 pixels width and 300 pixels height.
  • The center of the container is at (200, 150).

Using the Web Mercator Projection, these geo-coordinates (on a globe) can be converted to (2253273.3155555557, 1375543.6427981234) in cartesian coordinates (on a flat surface). Let’s say the mouse cursor is at (100, 100) inside the container, and we want to zoom in by 1 level (to zoom level 15). The midpoint between the mouse cursor at (150, 125), which is 50 pixels to the left and 25 pixels up from the center.

So the cartesian coordinates of the new center are:

x = 2253273.3155555557 - 50;
x = 2253223.3155555557;

y = 1375543.6427981234 - 25;
y = 1375518.6427981234;

Now, we need to use the web mercator projection to convert these cartesian coordinates to geographic coordinates.

axisSize = 4194304 // 256*2^14

lng = 360 * (x / axisSize - 0.5);
lng = 13.395708465576188;
lat = 360 * (Math.atan(Math.exp(Math.PI * (1 - (2 * y) / axisSize))) / Math.PI - 0.25);
lat = 52.52130564660004;

If we plug those values back into the algorithm from Part 1, we found a way to zoom in!

How do we zoom out? Well, we just need to use the old center as the midpoint between the mouse cursor and the new center. Which means, with the same values, old center at (200, 150), mouse cursor at (100, 100), the new center would be at (300, 200). In cartesian coordinates, that would be:

x = 2253273.3155555557 + 100;
x = 2253373.3155555557;

y = 1375543.6427981234 + 50;
y = 1375593.6427981234;

In geographic coordinates:

axisSize = 4194304 // 256*2^14

lng = 360 * (x / axisSize - 0.5);
lng = 13.408583068847673;
lat = 360 * (Math.atan(Math.exp(Math.PI * (1 - (2 * y) / axisSize))) / Math.PI - 0.25);
lat = 52.51738859038791;

And that’s it! We can now zoom in and out on the map! Here is a working example, click/tap to zoom in, double click/double tap to zoom out:

Tile displaying a part of a map
Tile displaying a part of a map
Tile displaying a part of a map
Tile displaying a part of a map

Zoomable Map of Berlin (Source)

In Part 3, we will look at how panning works.