<html lang='en'>
  <head>
    <meta charset='utf-8' />
    <title>Munros</title>
    <meta name='viewport' content='width=device-width, initial-scale=1' />
    <script src='https://api.tiles.mapbox.com/mapbox-gl-js/v2.6.1/mapbox-gl.js'></script>
    <link href='https://api.tiles.mapbox.com/mapbox-gl-js/v2.6.1/mapbox-gl.css' rel='stylesheet' />
<script src="https://api.mapbox.com/mapbox.js/plugins/turf/v3.0.11/turf.min.js"></script>
    
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">

  </head>
  <body>
    
    <!--Main map-->
<div id="map"></div>
    
    <!--Text for header-->
<div class='header'><span style="font-size: 15pt; padding-left:10px; text-align: left">&nbsp;Munros by Train </span><span style="font-size: 10pt; padding-right:12px; float:right"> By Keelin Titzer </span></div>
    
    <!--Text for information overlay-->
    <div class='map-overlay' id='features'><div id='pd'><p>282 mountains in Scotland over 3000 feet tall are classified as Munros.</p><p>How easily can a hiker reach each Munro by train?</p><h4>Click a Munro to see its nearest train station.</h4>
</p></div></div>
    
    <!--Insert icons and text for a legend-->
    <div class='map-overlay' id='legend'>
    
        <div width: 100%>
        <p><img src="https://i.ibb.co/yVtxQSc/mountain-green-e.png" class = "logo" alt="Mountain">
        &nbsp;Munro</p>
          <div width: 100%>
          <p><img src="https://i.ibb.co/GnCrk54/national-rail.png" class="logo" alt="Train station">&nbsp;&nbsp;Train station</p></div>
      </div>


 <!--Add custom font-->
  <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Dosis:wght@300&family=Quicksand:wght@300&family=Rubik:wght@300&display=swap" rel="stylesheet">
<script>


</script>
  </body>
</html>
/* Style for the header */
.header {
  background: rgba(255, 255, 255, 0.75);
  position: fixed;
  height: 6%;/* Because page body is 94% */
  width: 100%;
  top: 0px;
  line-height: 40px; /* To make sure text has vertical space */
  font-family: 'Quicksand', sans-serif;
}
/* Different header height for short screens such as phones in landscape mode */
@media only screen and (max-height: 480px) {
  .header {
    height: 12%;
  }}


/* Style for body of screen */
body { 
        margin: 0; 
        padding: 0; 
      }
      #map { 
        position: absolute; 
        bottom: 0; 
        width: 100%; 
        height: 94%; /* Body and header separated add up to 100%. Navigation control does not overlay header */
      }
/* Different map body height for short screens such as phones in landscape mode */
@media only screen and (max-height: 480px) {
  #map {
    height: 88%; /* Body and header separated add up to 100%. Navigation control does not overlay header */
  }}



/* Style for the headings */
h3 {
  border-top: 15px blue;
  font-family: Quicksand, sans-serif;
}

/* Style for the paragraph in the pop-up window */
p {
  font-size: 15px;
  font-family: Quicksand, sans-serif;
}


/* Set width for icons in legend */
img {
  width: 15px;
}

/* Style for the mountain mouseover pop-up window */
.my-popup .mapboxgl-popup-content {
  background: rgba(255, 255, 255, 0.75); /* red, green, blue, and alpha */
  border-top: 30px black;
  padding: 8px;
  color: black;
}

/* Style for the mountain click pop-up window */
.click-popup .mapboxgl-popup-content {
  background: rgba(255, 255, 255, 0.75); /* red, green, blue, and alpha */
  border-top: 0px black;
  padding: 2px;
  padding-top: -10px;
  color: black;
}

/* General style and position for overlay boxes */
.map-overlay {
  position: absolute;
  bottom: 0;
  right: 0;
  background: #fff;
  margin-right: 12px;
  font-family: 'Quicksand', sans-serif;
  overflow: auto;
  border-radius: 3px;
}

/* Style specifically for box showing mountain info */
#features {
  top: 6%; /* to match with top of body */
  min-height: 10%;
  max-height: 22%;
  width: 250px;
  line-height: 85%;
  margin-top: 12px; /* 12px lower than header for consistency with right margin */
  padding: 0px 10px 0px 10px; /* Extra padding on right and left sides */
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
  overflow-y: scroll;
}
/* Different box style for small likely landscape screens */
@media only screen and (max-height: 480px) {
  #features {
    top: 12%;
    min-height: 6%;
    max-height: 20%;
    width: 75%;
  }}
/* Different box style for other phone screens */
@media only screen and (max-width: 600px) {
  #features {
    top: 6%;
    max-height: 12%;
    width: 250px;
  }}

#legend {
 padding: 0px 0px 0px 10px;
 box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
 height: 70px;
 bottom: 40px;
 width: 120px;
 line-height: 50%;

  
}
.legend-key {
 display: inline;
 border-radius: 20%;
 width: 20px;
 height: 20px;
 padding: 5px;
}

mapboxgl.accessToken =
  "pk.eyJ1Ijoia3RpdHplciIsImEiOiJja3p5MWl4cTgwMWRqMm5wY3d5YWRreXB1In0.8YKr-h7pmDokMoPaIqbBLA";

const map = new mapboxgl.Map({
        container: 'map',
        style: "mapbox://styles/ktitzer/cl0sko1ii00kz15qgbhvjsu5h",
        center: [-4.35, 57.4],
        minZoom: 4,
        zoom: 6.6,
  attributionControl: false, // So that attribution can be modified to include copyright for the new layers added
    });

// Add custom copyright information to the default copyright info
map.addControl(new mapboxgl.AttributionControl({
  customAttribution: 'Contains OS data © Crown copyright [and database right] 2022. Contains data © Improvement Service and Database of British & Irish Hills.',
  compact: true // Make copyright info only visible on click because it would otherwise cover the mapbox logo due to its length
}));

// URL to station dataset
const stations_url =
  "https://api.mapbox.com/datasets/v1/ktitzer/cl0s1n86w001n27pqxu7s5v3i/features?access_token=pk.eyJ1Ijoia3RpdHplciIsImEiOiJja3p5MWl4cTgwMWRqMm5wY3d5YWRreXB1In0.8YKr-h7pmDokMoPaIqbBLA";

// URL to mountain dataset
const mountains_url = "https://api.mapbox.com/datasets/v1/ktitzer/cl0i510650b0027o26pjjtxg9/features?access_token=pk.eyJ1Ijoia3RpdHplciIsImEiOiJja3p5MWl4cTgwMWRqMm5wY3d5YWRreXB1In0.8YKr-h7pmDokMoPaIqbBLA"


// Add a scale bar
const scale = new mapboxgl.ScaleControl({
  maxWidth: 80, //size of the scale bar
  unit: "metric",
});
map.addControl(scale);

// Add the navigation control to the map to the top left corner.
map.addControl(new mapboxgl.NavigationControl(),'top-left');



map.on("load", () => {
  map.addLayer({
    id: "stations",
    type: "symbol",
    source: {
      type: "geojson",
      data: stations_url // URL to stations dataset
    },
    layout: {
      "icon-image": "national-rail", // custom UK train station icon
      "icon-size": 0.8,
      "icon-allow-overlap": true,
      "text-allow-overlap": false,
      'text-field': ['get', 'Name'],
'text-variable-anchor': ['top', 'bottom', 'left', 'right'],
      'text-size': 10,
      'text-radial-offset': 0.7,
      'text-justify': 'auto',
    },
    paint: {}
  });

  map.addLayer({
    id: "mountains",
    type: "symbol",
    source: {
      type: "geojson",
      data: mountains_url
    },

    layout: {
      "icon-image": "mountain-green-e", // Custom mountain icon
      "icon-size": 1.0,
      "icon-allow-overlap": false
    },
    paint: {}
  });

  map.addSource("nearest-station", {
    type: "geojson",
    data: {
      type: "FeatureCollection",
      features: []
    }
  });


  // Create legend
  const legend = document.getElementById("legend");

 
// Create popup
        const popup = new mapboxgl.Popup({className: "my-popup", closeButton: false, offset: [0, -5] })
        

        // Events to occur when mouse moves over a mountain
  map.on('mousemove', (event) => {
          const features = map.queryRenderedFeatures(event.point, {
            layers: ['mountains']
          });
    
          if (!features.length) {
            popup.remove();
            return;
          }

          const feature = features[0];

          popup
            .setLngLat(feature.geometry.coordinates)
            .setHTML(feature.properties.Name)
            .addTo(map);

   
    // Change to pointer cursor when over clickable feature
    map.getCanvas().style.cursor = 'pointer';

     });
  
// Populate feature information box with name of mountain, nearest station, and linear distance. Fly to mountain when clicked.
    map.on("click", (event) => {
    const mountain = map.queryRenderedFeatures(event.point, {
      layers: ["mountains"]
    });
 
      
      if (!mountain.length) {
    return;
  }
  // This variable feature is the marker clicked.
  // Feature has geometry and properties.
  // Properties are the columns in the attribute table.
  const selectedmountain = mountain[0];   
      
      // Fly to the point when clicked.
  map.flyTo({
    center: selectedmountain.geometry.coordinates, // fly to coordinates of selected mountain
    zoom:8.8, //zoom to level 8.8
    
    // flyTo options from https://docs.mapbox.com/mapbox-gl-js/example/flyto-options/
    bearing: 0,
    
// These options control the flight curve, making it move
// slowly and zoom out almost completely before starting
// to pan.
speed: 0.7, // choose flight speed
curve: 1, // change the speed at which it zooms out
    
    // This can be any easing function: it takes a number between 0 and 1 and returns another number between 0 and 1.
easing: (t) => t,
 
// This animation is considered essential with respect to prefers-reduced-motion
essential: true
 });
 
  
      // Add info to overlay box
  document.getElementById("pd").innerHTML = mountain.length
      ? `<h4>Munro name: ${mountain[0].properties.Name}</h4><p>Closest station: ${mountain[0].properties.NearestStation}</p><p>Distance from station: ${mountain[0].properties.HubDist_2} km</p><p>Elevation: ${mountain[0].properties.Height_ft} feet</p>`
      
      : `<p>Click a mountain to see its details and nearest train station.</p>`;
    

// Create popup
        const popup = new mapboxgl.Popup({className: "my-popup", closeButton: false, offset: [0, -5] })
        

// Have popup stay visible when a mountain is clicked     
                popup
            .setLngLat(mountain[0].geometry.coordinates)
            .setHTML(mountain[0].properties.Name)
            .addTo(map);
    });


// When pointer leaves mountains layer, change to regular cursor for UI  
map.on('mouseleave', 'mountains', (event) => {     
  map.getCanvas().style.cursor = '';
  });

// When pointer leaves stations layer, change to regular cursor for UI       
map.on('mouseleave', 'stations', (event) => {       
  map.getCanvas().style.cursor = '';
  });
  
  
// Find closest station to each mountain using turf API  
map.on("click", (event) => {
    const mountainFeatures = map.queryRenderedFeatures(event.point, {
      layers: ["mountains"]
    });
    if (!mountainFeatures.length) {
      return;
    }

    const mountainFeature = mountainFeatures[0];

   // Get all features from the station layers
    var features = map.querySourceFeatures("stations");

    //Create the turf collection to conform with the turf format
    const stations = turf.featureCollection(features);

    const nearestStation = turf.nearest(mountainFeature, stations);

    if (nearestStation === null) return;
    map.getSource("nearest-station").setData({
      type: "FeatureCollection",
      features: [nearestStation]
    });

  if (map.getLayer("nearest-station")) {
      map.removeLayer("nearest-station");
    }  

  
  // Display circle around the nearest station
    map.addLayer(
      {
        id: "nearest-station",
        type: "circle",
        source: "nearest-station",
        paint: {
          "circle-radius": 20,
          "circle-opacity": 0.5,
          "circle-color": "#006d2c",
          "circle-stroke-color": '#ffffff',
          "circle-stroke-width": 1
        }
      },
      "stations"
    );
  });
});

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.