Using OpenStreetMap For Your Web Projects
Tags: opensource, openstreetmap, node
Published: 2022-08-18 19:02:00

_Note: This is an old article I published in my personal blog on **August 2019**. I now publish articles on dev.to so I stopped my blog and moved this article here._

---


# What's wrong with Google Maps?

Nothing. But... let's consider this scenario: 
You're working on a web project, say an e-commerce platform for a real-estate company based in Algeria (where I currently live). One of the project's requirements is to show a map of the product's location, on its details page.

You could go with Google Maps, but since they changed to a pay-as-you-go pricing model, you should probably think twice before just jumping in. 

Using Google Maps could be the perfect solution in your case but for me it wasn't. I had to look for an alternative.

# OpenStreetMap to the rescue
First of all, what is OpenStreetMap ? 
OpenStreetMap is a collaborative project, that aims to provide **free, continuously updated geographic data** of the world. 

So can we use their maps? and how? Let's have a look.

## OpenStreetMap usage requirements
OpenStreetMap data is licensed [Open Data Commons Open Database License (ODbL)](https://opendatacommons.org/licenses/odbl/), 
and the map itself is licensed under [CC BY-SA 2.0](https://creativecommons.org/licenses/by-sa/2.0/). That means...

To be able to use OpenStreetMap in your websites, you need adhere by two perfectly reasonable requirements

1. **You must** display a proper attribution text in/around your map. Since we're using the map and the data, we'll add *© OpenStreetMap contributors* text inside the map (more on that later), and link it to the [copyright page]([https://www.openstreetmap.org/copyright](https://www.openstreetmap.org/copyright)).
2. When expecting high traffic (in our case we are), **you must** avoid flooding OpenStreetMap servers. You can read more about the tile server usage policy [here](https://operations.osmfoundation.org/policies/tiles/).

# Show me the code
Enough talk, we will now setup a map, with respect to the requirements mentioned above.

I'm going to use NodeJS to setup a map tile server proxy, I found this [library](https://github.com/naturalatlas/tilestrata) that can help us with that.

Let's install everything we'll be needing
```bash
npm install --save tilestrata tilestrata-proxy tilestrata-disk tilestrata-headers
```

Let's review what we installed: 

- Tilestrata is the main server code.
- TileStrata Proxy will help us proxy requests to OpenStreetMap servers.
- TileStrata Disk, will help us cache the map tiles, to avoid flooding the proxied servers.
- TileStrata Headers to help us with client-side caching of map tiles.

Let's first setup a config file
```javascript
module.exports = {
    // Host:Port to listen on
    host: '127.0.0.1',
    port: 12345,

    cache: {
        lifetime: 3600 * 24 * 7 * 4, // Cache for a month
        refresh: 3600 * 24 * 14, // 14 days refresh

        // The map-server script needs to be lunched from the project root
        dir: './map_tiles_cache',
    },
};
```

and now the server code:
```javascript
var tilestrata = require('tilestrata');
var headers    = require('tilestrata-headers');
var proxy      = require('tilestrata-proxy');
var disk       = require('tilestrata-disk');
var config     = require('./config');
var util       = require('util');

var strata = tilestrata();
strata.layer('osm') // highlight-line
    .route('*.png')
    .use(disk.cache({
        dir: config.cache.dir,
        refreshage: config.cache.refresh,
        maxage: config.cache.lifetime,
    }))
    .use(proxy({
        uri: 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', // highlight-line
        subdomains: ['a', 'b', 'c'],
    }))
    .use(headers({ // highlight-line
        'Cache-Control': 'public, max-age=' + config.cache.lifetime, // highlight-line
        'Expires': config.cache.lifetime, // highlight-line
    }));

process.stdout.write(util.format(
    "Map server listening on http://%s:%s\nPress Ctrl-C to quit.\n",
    config.host, config.port
));
strata.listen(config.port, config.host);
```

Let's review the highlighted lines
1. We called our map layer `osm`, you can call this whatever you want.
2. We proxy the request to OpenStreetMap tile servers, the variables are
    - `s` means sub-domain, and it's picked at random from the arrow bellow the highlighted line. Note that this is a required behavior.
    - `z` is the zoom level, `x` and `y` and numbers indicating the column and row the map. You don't have to worry about all this parameters, they will be passed automatically by our client map library. We'll still be using latitudes and longitudes like we're used to.  
3. Then we setup client side caching, using the `Cache-Control` and `Expires` header.

Now that our map server code is done, let's start it and use our map!
```bash
mkdir map_tiles_cache # for caching files
node mapserver.js
```
Let's create a simple page that displays our map. We'll be using [Leaflet JS](https://leafletjs.com/) for that.
```html
<html>
<head>
    <title> OpenStreenMap Example </title>
    <meta charset="utf-8">
    <link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.5.1/leaflet.css">
    <script src='https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.5.1/leaflet.js'></script>
</head>

<body>
    <div id="map" style="height: 700px; width: 100%;"></div>

    <script>
        var map = L.map('map', {
            center: [36.7597907,3.0665139], // The capital of Algeria
            zoom: 9, // default zoom level
        });

         L.tileLayer('http://127.0.0.1:12345/osm/{z}/{x}/{y}.png', {
             attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap contributors</a>'
        }).addTo(map);
    </script>
</body>
```

And we're done! We have ourselves a free, flexible map to use in our website.
Checkout the [Leaflet website](https://leafletjs.com/) for more documentation on how to ineract with your new map. 

### One more thing...
Depending on the use case, the cache disk space can grow very large.

As an example, The north side of Algeria, viewed from from zoom level 6 all the way up to zoom level 18 (which is the maximum zoom level supported by OpenStreetMap) will be around **30GB** (You can calculate your own area with this [calculator tool](http://tools.geofabrik.de/calc/) from [Geofabrik](https://www.geofabrik.de/)).

You have two options to fix that
1. Delete the cache folder periodically.
2. Setup map boundaries for the map. 

The first option is self-explanatory, so let's look at the second one.

From the [docs](https://leafletjs.com/reference-1.5.0.html#map-setmaxbounds), LeafletJS has map boundaries, 
which restricts the view to the given geographical bounds, 
bouncing the user back if they try to pan outside the view.

Let's set that up. I got the bounds for Algeria [here](https://openmaptiles.com/downloads/africa/algeria/) (you can get bounds for any country).

```javascript
// The bounds object
// representing a fixed rectangular area
var bounds = L.bounds(
    L.point(-8.704895, 18.92874), 
    L.point(12.03598, 37.77284)
);

// Set the max bounds
map.setMaxBounds(bounds);
```
<br>

# And we're done!

Of course everything has its pros and cons, and you should decide what's better suited for you own needs.

But as you can see, it was pretty easy to setup a map server based on OpenStreetMap and get started with it, so it's good to have it in your arsenal.

And that's about it! I hope this post was of help to you.
Make sure to drop a comment if you have any question or feedback. Thanks!