Leaflet Day 5 - Working with Features

Today we’ll add towns along the trail route that are mentioned in the novels. I hesitate to call them towns, because in 1902, many of them consisted of a view indigenous people and sometimes a roadhouse.

The method to add these locations will be to add a GeoJSON layer and loop through each town, adding a marker and popup with some info.

The Data

The data for the locations is from the GNIS database for Alaska, containing over 35,000 locations. Using QGIS, it’s been filtered down and tweaked to only include those features that are important to the story. This results in just 17 locations along the trail. These were exported as a GeoJSON file for use with Leaflet.

The Markers

For the locations we’ll use circle markers. To create a single marker use:

  var circle = L.circleMarker([61.5, -149.5], {
    radius: 15
  });

A circle marker is added to the map as usual:

  circle.addTo(map);

To add them to our GeoJSON features, the syntax will be a bit different as we are processing them in a loop.

Adding the Layer with Circle Markers and Popup

To add the markers we’ll loop through the dataset using the onEachFeature option and also add a popup to display the town name, elevation, and latitude/longitude:

23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
var towns = L.geoJSON(trail_stops, 
          {pointToLayer: function(feature, latlng){
              return L.circleMarker(latlng, {radius: 5, color: '#00008b',
                                              fillOpacity: 0.5,
                                            });
          },
            onEachFeature: function( feature, layer){
                var townName = feature.properties.feature_na;
                var elevation = feature.properties.elev_in_m;
                var lat = feature.properties.prim_lat_d;
                var lon = feature.properties.prim_lon_1;

                layer.bindPopup('Name: ' + townName +
                                '<br/>Elevation: ' + elevation +
                                '<br/>Lat/Lon: ' + lat + ' , ' + lon);
                layer.on('mouseover', function() {layer.openPopup();});
                layer.on('mouseout', function() {layer.closePopup();});
            }
          });

towns.addTo(map);

In line 23, the GeoJSON layer is created, and in lines 24–28 we use the pointToLayer option to create a circleMarker for each feature.

In lines 29–41, we create varibles to hold the attributes for the popup, then bind the popup to the feature (lines 35–37). The cryptic field names (e.g. feature_na) are from the conversion of the original GNIS shapefile. We could have used the feature properties directly in the bindPopup statement, but assigning them to varibles makes it a bit easier to read the code.

Lastly, we add the mouse events to display and remove the popup on mouse over (lines 38–39), then add the layer to the map.

The result looks like this:

For more details and additional tips, see the Leaflet Cookbook.

The Code

Here’s the complete JavaScript code:

var map = L.map(document.getElementById('mapDIV'), {
    center: [62.7, -144.0],
    zoom: 6
});

// Base maps
var basetopo = L.tileLayer('https://basemap.nationalmap.gov/arcgis/rest/services/USGSTopo/MapServer/WMTS/tile/1.0.0/USGSTopo/default/default028mm/{z}/{y}/{x}.png', {});
var baserelief = L.tileLayer('https://tile.opentopomap.org/{z}/{x}/{y}.png', {});

basetopo.addTo(map);

// The trail
var thetrail = L.geoJSON(trail, {
    color: '#800000',
    weight: 3,
    dashArray: '12 8 12',
});

thetrail.bindTooltip('The Valdez-Eagle Trail')
thetrail.addTo(map);


var towns = L.geoJSON(trail_stops, 
                      {pointToLayer: function(feature, latlng){
                          return L.circleMarker(latlng, {radius: 5, color: '#00008b',
                                                         fillOpacity: 0.5});
                      },
                      onEachFeature: function( feature, layer){
                          var townName = feature.properties.feature_na;
                          var elevation = feature.properties.elev_in_m;
                          var lat = feature.properties.prim_lat_d;
                          var lon = feature.properties.prim_lon_1;
                          

                          layer.bindPopup('Name: ' + townName +
                                          '<br/>Elevation: ' + elevation +
                                         '<br/>Lat/Lon: ' + lat + ' , ' + lon);
                          layer.on('mouseover', function() {layer.openPopup();});
                          layer.on('mouseout', function() {layer.closePopup();});
                      }
                     });

towns.addTo(map);

var baselayers = {
    'Shaded Relief': baserelief,
    'National Map topo': basetopo
};
var overlays = {
    'The Trail': thetrail,
    'Towns': towns
};
L.control.layers(baselayers, overlays).addTo(map);

// Add scalebar

var scale = L.control.scale()
scale.addTo(map)

// Add attribution
map.attributionControl.addAttribution('National Map Topo');
map.attributionControl.addAttribution('OpenTopoMap');

Here is the HTML that sets up the map and includes the JavaScript file (line 21):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!DOCTYPE html>
<head>
    <link rel="stylesheet" href="https://unpkg.com/leaflet@1.4.0/dist/leaflet.css"
          integrity="sha512-puBpdR0798OZvTTbP4A8Ix/l+A4dHDD0DGqYW6RQ+9jxkRFclaxxQb/SJAWZfWAkuyeQUytO7+7N4QKrDh+drA=="
          crossorigin=""/>  
    <script src="https://unpkg.com/leaflet@1.4.0/dist/leaflet.js"
            integrity="sha512-QVftwZFqvtRNi0ZyCtsznlKSWOStnDORoefr1enyq5mVL4tmKB3S/EnC3rRJcxCPavG10IcrVGSmPh6Qw5lwrg=="
            crossorigin=""></script>
    <script src="/code/data/valdez_eagle_trail_wgs84.geojson"></script>
    <script src="/code/data/trail_stops.geojson"></script>
    <style>
     #mapDIV{
         height: 700px;
         width: 700px;
         border: solid 1px black;
     }
    </style>
</head>
<body>
    <div id='mapDIV'></div>
    <script src="/code/leaflet/leaflet_day5.js"></script>
</body>
</html>
You can view the map live here.