1. 程式人生 > >Prototyping a Smoother Map

Prototyping a Smoother Map

Fallback tiles

One edge case to handle when scaling and blending the tiles is what happens if there are no tiles to blend. If the lower zoom hasn’t loaded yet, then instead of animating opacity on the next level, it’s better to start it fully opaque — otherwise there will be patchy gaps in the map.

Calculating the coverage of fallback tiles is one area that the custom canvas composition really shines — each draw operation can set custom opacity, and because canvas is really efficient for small draw operations (eg map tile sized), we can efficiently set custom opacity levels for every individual tile. If we were relying on the browser to do this with custom elements, all the individual animations could cause slowdown.

The client always draws fallback (the underlying) tiles at full opacity, and only changes the opacity of the primary (target zoom level) tiles which it overlays on top. Before the paint cycle, it checks to see if the primary tile has complete fallback coverage — ie if everything underneath that tile has been drawn.

For example, if we were zooming in here, from zoom 0 to 1 — those colored tiles do have complete coverage, and that single grey tile underneath them fully overlaps. This means they can safely have their opacity animated without leaving gaps.

However the opposite isn’t true. If we were zooming out, from 1 to 0 (so the colored tiles would be the underlay) we cannot animate the opacity on the grey tile because it would leave some gaps.

One possible solution to this would be to paint the primary tiles without coverage once as a fully opaque base layer, then paint the fallback tiles, then safely change the primary tile opacity (at worst it would be blending with itself) — but that would increase the number of draw operations for little noticeable improvement.

Another thing we can do with the opacity is to quickly animate in newly loaded tiles, so instead of snapping into place, there is a quick fade.

Zoom direction and velocity

The client also keeps track of which direction users are zooming (in or out), as well as how quickly they’re zooming, and uses this to determine if it should load new tiles.

For example, if zooming in from 14 to 15, the client won’t bother to load any more tiles from zoom level 14, but will instead prioritize fetching the new ones it needs from 15. If the opposite was occurring, zooming out from 15 to 14, the client would only try to load the new tiles from 14.

It uses the zoom speed to decide if there is any point to loading tiles at all, or if it is likely the layer will be zoomed past before the images have a chance to load. For example, when zooming rapidly the user may race all the way from zoom 4 to 15 in just a second — and there’s little point loading 5, 6, 7, 8… because it would just be a waste of tiles. Fortunately, for zooming-in we can scale just a few tiles (remember at zoom 0 the tile represents the entire world) to keep the colors/shapes vaguely representative.

Avoiding downscaling

Given the map gets increasingly detailed at higher zoom levels, I naively assumed that it would be preferable to use the higher levels and downscale them when zooming out.

Downscaling too many tiles

However in practice this didn’t work very well—while the scale of all the polygons (eg water and parks) is well preserved, all the labels and icons also get scaled, making a map that looks really cluttered, especially in the middle. The performance also took a huge hit, because instead of drawing a few dozen tiles there were suddenly hundreds available for drawing.

To avoid this, I disabled downscaling tiles by more than 1 zoom difference. Ie when zooming in the fractional levels of the 14s (eg 14.2) it may use tiles from zoom 15, but never, ever, tiles from 16 or higher.

Animations

My stated intent at the start of the project was to be able to programmatically synchronize animations with zoom, although it also has the side benefit of just feeling better — it’s smoother and more responsive to user input. When at integral zoom levels it behaves the same as the normal implementation. However I think it is quite effective for animating — you can judge for yourself.

Here is an example visualizing a possible flight path from San Francisco to Australia. [alternatively the full video, or one with the debug overlay]