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


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.


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 .


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:

<html lang="en">
    <meta charset="utf-8" />

    <!-- This section includes tips from:

    <!-- <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="" integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin=""/>
    <script src="" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
    <!-- <script src=''></script> -->

      html, body {
        padding: 0;
        margin: 0;
      html, body, #map {
        height: 100%;
        width: 100vw;


    <div id="map"></div>
      ////// Tips from

      // Add 3 base/tile layers:
      var osm = L.tileLayer('{z}/{x}/{y}.png', {
        maxZoom: 19,
        attribution: '&copy; <a href="">OpenStreetMap contributors</a>'
      var osmHOT = L.tileLayer('https://{s}{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}{z}/{x}/{y}.png', {
        attribution: '&copy; <a href="">OpenStreetMap</a> contributors, &copy; <a href="">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 ='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

      // 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="">Custom Icons</a>').addTo(map);

      var 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) {
          .setContent("You clicked the map at:<br>" + e.latlng.toString())
      map.on('click', onMapClick);

      ////// Tips from

      // 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;
          .bindPopup("You are within " + radius + " meters from this point").openPopup();
     , radius).addTo(map);
      map.on('locationfound', onLocationFound);
      function onLocationError(e) {
      map.on('locationerror', onLocationError);

      ////// Tips from
      ////// 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]


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




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 -
3. Leaflet Plugins -
4. Leaflet API Reference -
5. Leaflet -
6. map.html