OpenStreetMap: Custom Web Map Viewer with Leaflet

First published — Nov 18, 2023
Last updated — Nov 18, 2023

Computer cartography. OpenStreetMap (OSM), Leaflet, Slippy. Custom map viewer.

Table of Contents

Introduction

As mentioned in OpenStreetMap and Computer Cartography , all components of OpenStreetMap exist as Free Software and can be self-hosted and customized.

When setting up a custom or self-hosted solution, it is probably easiest to replace components from the top down, i.e. starting with the viewer and ending with the tile server.

This article explains the first step – how to easily create a custom web page that displays OpenStreetMap data using a custom map stylesheet and other settings.

Leaflet

OSM web-based viewer Leaflet is very easy to set up.

A custom web page that “just works” can be set up in 10 lines or so, and can be loaded from either a web location or a local file on disk.

This articles is based on examples from Leaflet tutorials .

Other resources to look up on an as-needed basis are Leaflet API reference , and Leaflet plugins .

HTML Page

Leaflet setup and toggling of its JavaScript-based features is so easy that it is unnecessary to create a longer introductory text.

Here is an HTML page with hopefully all the comments necessary. It was created by copying tips from Leaflet tutorials into a working page:

<!DOCTYPE HTML>
<html lang="en">
  <head>
    <meta charset="utf-8" />

    <!-- This section includes tips from:
      https://leafletjs.com/examples/mobile/
      https://leafletjs.com/examples/accessibility/
    -->

    <!-- <meta name="viewport" content="width=device-width, initial-scale=1.0"> -->
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />

    <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin=""/>
    <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
    <!-- <script src='https://unpkg.com/wicg-inert@latest/dist/inert.min.js'></script> -->

    <style>
      html, body {
        padding: 0;
        margin: 0;
      }
      html, body, #map {
        height: 100%;
        width: 100vw;
      }
    </style>

  </head>

  <body>
    <div id="map"></div>
    <script>
      ////// Tips from https://leafletjs.com/examples/layers-control/

      // Add 3 base/tile layers:
      var osm = L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
        maxZoom: 19,
        attribution: '&copy; <a href="https://openstreetmap.org/copyright">OpenStreetMap contributors</a>'
      });
      var osmHOT = L.tileLayer('https://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png', {
        maxZoom: 19,
        attribution: '© OpenStreetMap contributors, Tiles style by Humanitarian OpenStreetMap Team hosted by OpenStreetMap France'
      });
      var positron = L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png', {
        attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, &copy; <a href="https://carto.com/attribution">CARTO</a>'
      });
      var baseMaps = {
        "OpenStreetMap": osm,
        "<span style='color: red'>OpenStreetMap.HOT</span>": osmHOT,
        "CARTO.Positron": positron,
      };

      // Add one overlay layer showing some cities:
      var littleton = L.marker([39.61, -105.02]).bindPopup('This is Littleton, CO.'),
        denver    = L.marker([39.74, -104.99]).bindPopup('This is Denver, CO.'),
        aurora    = L.marker([39.73, -104.8]).bindPopup('This is Aurora, CO.'),
        golden    = L.marker([39.77, -105.23]).bindPopup('This is Golden, CO.');
      var cities = L.layerGroup([littleton, denver, aurora, golden]);
      var overlayMaps = {
        "Cities": cities
      };

      // Create map - position it at specified latitude/longitude (0,0) and with a given zoom level (0-19)
      var map = L.map('map', { minZoom: 0, maxZoom: 19, layers: [osm, osmHOT, positron] }).setView({lat: 0, lon: 3}, 5); //.fitWorld();

      var layerControl = L.control.layers(baseMaps, overlayMaps).addTo(map);

      // show the scale bar on the lower left corner
      L.control.scale({imperial: true, metric: true}).addTo(map);

      ////// Tips from https://leafletjs.com/examples/quick-start/

      // show a marker on the map and other graphical elements:
      var marker = L.marker({lon: 0, lat: 0}, {alt: "Center"}).bindPopup('The center of the world at latitude/longitude 0/0.<br>To customize icon, see <a target="_new" href="https://leafletjs.com/examples/custom-icons/">Custom Icons</a>').addTo(map);

      var circle = L.circle([1, -2], { color: 'red', fillColor: '#f03', fillOpacity: 0.5, radius: 50000 }).addTo(map);
      circle.bindPopup("I am a circle.");

      var polygon = L.polygon([ [3,0], [4,1], [5,2], [3,2] ]).addTo(map);
      polygon.bindPopup("I am a polygon.");

      var popup = L.popup().setLatLng([7, 0]).setContent("I am a standalone popup, unrelated to an element.<br>Click on other elements to see their popups.<br>Or click anywhere on the map to see the point's latitude/longitude.").openOn(map);

      var latlng_popup = L.popup();
      function onMapClick(e) {
        latlng_popup
          .setLatLng(e.latlng)
          .setContent("You clicked the map at:<br>" + e.latlng.toString())
          .openOn(map);
      }
      map.on('click', onMapClick);

      ////// Tips from https://leafletjs.com/examples/mobile/

      // To geolocate user instead of show map at lat/lng 0/0:
      //map.locate({setView: true, maxZoom: 19});

      function onLocationFound(e) {
        var radius = e.accuracy;
      
        L.marker(e.latlng).addTo(map)
          .bindPopup("You are within " + radius + " meters from this point").openPopup();
      
        L.circle(e.latlng, radius).addTo(map);
      }
      
      map.on('locationfound', onLocationFound);
      
      function onLocationError(e) {
        alert(e.message);
      }
      
      map.on('locationerror', onLocationError);

      ////// Tips from https://leafletjs.com/examples/geojson/
      ////// See referenced page for more examples

      var geojsonFeature = {
        "type": "Feature",
        "properties": {
          "name": "Example GeoJSON in own layer",
          "amenity": "At some amenity",
          "popupContent": "And with some popup!"
        },
        "geometry": {
          "type": "Point",
          "coordinates": [0, -4]
        }
      };

      L.geoJSON(geojsonFeature).addTo(map);

      var myLines = [{
        "type": "LineString",
        "coordinates": [[-5,-3], [-2,-3]]
      }
      ];

      L.geoJSON(myLines).addTo(map);

    </script>
  </body>
</html>

Preview

Preview of the above page can be seen in map.html .

Further Reading

This article is part of the following series:

1. OpenStreetMap

Automatic Links

The following links appear in the article:

1. OpenStreetMap and Computer Cartography - /osm-cartography/
2. Leaflet Tutorials - https://leafletjs.com/examples.html
3. Leaflet Plugins - https://leafletjs.com/plugins.html
4. Leaflet API Reference - https://leafletjs.com/reference.html
5. Leaflet - https://wiki.openstreetmap.org/wiki/Leaflet
6. map.html