OpenStreetMap: Custom Web Map Viewer with Leaflet

First published — Nov 18, 2023
Last updated — Apr 30, 2024

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

Article Collection

This article is part of the following series:

1. Osm

Table of Contents

Introduction

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 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 article is based on examples from Leaflet tutorials.

Other resources to look up on an as-needed basis would be the 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.

The version of Leaflet used for this example was Leaflet 1.9.4.

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', {
        maxZoom: 19,
        attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, &copy; <a href="https://carto.com/attribution">CARTO</a>'
      });
      var baseMaps = {
        "CARTO.Positron": positron,
        "<span style='color: red'>OpenStreetMap.HOT</span>": osmHOT,
        "OpenStreetMap": osm,
      };

      // 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: [positron, osmHOT, osm] }).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.

If you have created the file locally, you can open it with e.g. firefox ./map.html.

Article Collection

This article is part of the following series:

1. Osm

Automatic Links

The following links appear in the article:

1. Leaflet Tutorials - https://leafletjs.com/examples.html
2. Leaflet Plugins - https://leafletjs.com/plugins.html
3. Leaflet API Reference - https://leafletjs.com/reference.html
4. Leaflet - https://wiki.openstreetmap.org/wiki/Leaflet