Using a Service Worker to generate diagnostic XYZ tiles

Joel Malone
3 min readMar 14, 2022

--

Photo by Kelly Sikkema on Unsplash

Sometimes you’re developing a typical tile-based web map thing and need to sanity-check your vectors or imagery or whatevers.

In this situation, you might visually compare it to other vectors or imagery in your map. But what if you don’t have anything to compare to?

In this article, I present a quick-n-dirty “fake XYZ tile server” that you can drop into any web app, and use it to feed descriptive tiles to your map.

It looks like this:

I live in 26, 19, 5 and we hate (HATE) those guys in 27, 19, 5.

Live action demo

The image above is from the example website here:

https://fake-xyz-tile-sw.joelmalone.com.au/

And the source code is available here:

https://github.com/joelmalone/fake-xyz-tile-sw.js

Yeh but why

The “fake XYZ tile server” is useful because:

  • It will generate tile images that display the rendered X, Y and Z of the tile that was requested. Useful for debugging!
  • It’s completely client-side. It’s just a service worker, so you can drop it into any web app.
  • It doesn’t have any external dependencies.
  • It can be consumed by any tile-based web map framework that supports URLs.
  • It’s simple; you can easily tweak it to suit your needs.

This is what it looks like in action, with an OSM base map behind it to provide some context:

Fake XYZ tiles can be overlaid onto another layer, providing diagnostic context for both.

You could use this information to diagnose issues with your custom imagery or vector layers.

It’s even useful for continuing to work while offline, without a connection to your usual base map servers.

Yeh but how

The source code is reasonably simple, and easy to adapt. Simply drop this as a new .js file (e.g. serve-tiles-sw.js) into your web app:

/* serve-tiles-sw.js */function onFetch(event) {
const url = new URL(event.request.url)
if (url.host === 'fake-server') {
event.respondWith(
generateImage(url)
);
break;
}
}
async function generateImage(url) {
const [, x, y, z] = url.pathname
.split('/')
.map(i => Number.parseInt(i))
const canvas = new OffscreenCanvas(256, 256);
var context = canvas.getContext("2d");
context.fillStyle = 'orange';
context.fillRect(8, 8, 240, 240);
context.font = "20px Arial";
context.fillStyle = 'black';
context.fillText(`${x}, ${y}, ${z}`, 16, 128);
const blob = await canvas.convertToBlob() return new Response(blob)
}
self.addEventListener("fetch", onFetch);

And then call this (e.g. from index.html) to register the service worker:

navigator.serviceWorker.register(‘serve-tiles-sw.js’)

And then add a new XYZ layer to your map, using this as the URL, and adapting it as necessary to suit your map framework:

https://fake-server/{x}/{y}/{z}

Gotcha: WebKit doesn’t support OffscreenCanvas (or “oh right, I forgot we were doing web development”)

OffscreenCanvas is not supported on Safari iOS (nor Safari anywhere), as can be seen in the Browser Compatibility section for OffscreenCanvas on MDN:

C’mon Apple… c’monnnnnnnn

This means that the above code won’t work in the following browsers:

Development on OffscreenCanvas in WebKit seems to have stalled around a year ago, so it doesn’t look like we’ll see it implemented any time soon.

A straight-forward alternative to OffscreenCanvas would be to find a package on NPM that allows us to draw and encode images in a pure Javascript way. But uhhhh maybe I’ll do that another day.

--

--

Joel Malone

Software Engineer living in Southwest Western Australia