How To Add Driving Directions to a Wordpress Website

Build routes and directions with WordPress website
Build routes and directions with WordPress website

Were you thinking of adding driving directions websites functionality to your WordPress website? Then you are in the right place! This guide explains step-by-step how to add Geoapify Directions (driving, biking, walking, hiking) to your WordPress website. This tutorial is intended for readers with little or no experience in web development.

After completing this tutorial, you will have the ability to choose from a list of route parameters and have a map that shows your route on it and turn-by-turn driving instructions on the screen.

This tutorial doesn't need any plugins or visual components. All you'll need is a "Custom HTML" block or similar:

Custom HTML block

Step 1. Copy & paste required libraries and stylings

We will be using the Leaflet Map and Geoapify Route Directions libraries. We need to include the appropriate JS- and CSS- files in our WordPress page (or post).

  • Add a new "Custom HTML" block;
  • Copy & Paste the following lines:
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css">
<link rel="stylesheet" href="https://unpkg.com/@geoapify/route-directions@^1/styles/styles.min.css">
<script src="https://unpkg.com/[email protected]/dist/leaflet.js"></script>
<script src="https://unpkg.com/@geoapify/route-directions@^1/dist/index.min.js"></script>

Step 2. Copy & paste containers for a map, directions controls, instructions

You'll need three HTML containers: one to hold the map, a second for the Route Directions, and a third for the instructions.

Here's an example of adding containers to one custom HTML block:

  • Add a new "Custom HTML" block (or multiple "Custom HTML" blocks);
  • Copy & Paste the following lines:
<div id="geoapify-map" style="height:400px"></div>
<div id="geoapify-route-directions"></div>
<style>
    .direction-waypoints {
      font-size: 18px;
      font-weight: 600;
      color: rgba(0, 0, 0, 0.8);
      margin-bottom: 10px;
    }

    .direction-waypoints.smaller {
      font-size: 16px;
      font-weight: 500;
    }

    .direction-waypoints .direction-waypoints-from-to{
      font-size: 12px;
      font-weight: 400;
    }

    .direction-instruction {
      display: flex;
      flex-direction: row;
      margin: 10px 0;

      color: rgba(0, 0, 0, 0.8);
    }

    .direction-instruction-icon {
      min-width: 40px;
      max-width: 40px;
      display: flex;
    }
</style>
<div id="geoapify-instructions"></div>

The map height is 400px by default. To change the size by changing the number for the geoapify-map container.

Step 3. Copy & paste the JavaScript code

The code below uses the Geoapify services, which require an API key. To register and get your API key, go to the MyProjects page.

Here you can find more details about obtaining an API key.

Now we want to add JavaScript. You can add it using Custom HTML Blocks as well. You can use one Custom HTML block for all pieces or create separate ones for each part.

  • Add a new "Custom HTML" block (or multiple "Custom HTML" blocks);
  • Copy & Paste the following lines;
  • Replace YOUR_API_KEY with your Geoapify API key:
<script>
  // Get an API Key on https://myprojects.geoapify.com
  const apiKey = "YOUR_API_KEY";

  // Set map style
  const mapStyle = "osm-bright-smooth"; // More map styles on https://apidocs.geoapify.com/docs/maps/map-tiles/

  // Init marker icon
  const markerIcon = L.icon({
    iconUrl: `https://api.geoapify.com/v1/icon/?type=awesome&scaleFactor=2&color=%23ff4949&apiKey=${apiKey}`, //icon generated by Geoapify Marker Icon API
    iconSize: [31, 46], // size of the icon
    iconAnchor: [15.5, 42], // point of the icon which will correspond to marker's location
    popupAnchor: [0, -45] // point from which the popup should open relative to the iconAnchor
  });

  // Route directions options
  const routeDirectionsOptions = {
    supportedModes: ['walk', 'hike', 'scooter', 'motorcycle', 'drive', 'light_truck', 'medium_truck', 'truck', 'bicycle', 'mountain_bike', 'road_bike', 'bus'],
    supportedOptions: ['highways', 'tolls', 'ferries', 'units']
  };

  // Set geocoder options
  const geocoderOptions = {
    placeholder: "Enter an address here or click on the map"
  };
  
  // Route visualization options
  const routeVisualizationOptions = {
    color: '#6699ff',
    shadowColor: '#0055ff',
    width: 4
  };
</script>
<script>
  // Create a Leaflet map
  const map = L.map('geoapify-map');

  const mapURL = L.Browser.retina
    ? `https://maps.geoapify.com/v1/tile/{mapStyle}/{z}/{x}/{y}.png?apiKey={apiKey}`
    : `https://maps.geoapify.com/v1/tile/{mapStyle}/{z}/{x}/{y}@2x.png?apiKey={apiKey}`;

  // Add map tiles layer. Set 20 as the maximal zoom and provide map data attribution.
  L.tileLayer(mapURL, {
    attribution: 'Powered by <a href="https://www.geoapify.com/" target="_blank">Geoapify</a> | <a href="https://openmaptiles.org/" rel="nofollow" target="_blank">© OpenMapTiles</a> <a href="https://www.openstreetmap.org/copyright" rel="nofollow" target="_blank">© OpenStreetMap</a> contributors',
    apiKey: apiKey,
    mapStyle: mapStyle,
    maxZoom: 20
  }).addTo(map);
</script>
<script>
  // Set map center
  const ipGeolocationApiUrl = `https://api.geoapify.com/v1/ipinfo?apiKey=${apiKey}`;
  fetch(ipGeolocationApiUrl).then(result => result.json()).then(ipGeolocationData => {
    map.setView([ipGeolocationData.location.latitude, ipGeolocationData.location.longitude], 6);
    map.invalidateSize();
  });
</script>
<script>
  // Initialize Route Directions
  const routeDirections = new directions.RouteDirections(document.getElementById("geoapify-route-directions"), apiKey, routeDirectionsOptions, geocoderOptions);
</script>
<script>
  // add locations by click on the map
  map.on("click", (event) => {
    routeDirections.addLocation(event.latlng.lat, event.latlng.lng);
  });

  // add callbacks
  routeDirections.on('waypointChanged', (waypoint, reason) => {
    if (reason !== "added" && routeLayer) {
      routeLayer.remove();
      routeShadowLayer.remove();
      instructionMarkers.forEach(marker => marker.remove());
      instructionMarkers = [];
    }

    const instrictionContainer = document.getElementById("geoapify-instructions");
    instrictionContainer.innerHTML = '';
    updateMarkers();
  });

  routeDirections.on('routeCalculated', (geojson) => {
    visualizeRoute(geojson);
    generateInstructions(geojson);
  });
</script>
<script>

  // Initialize markers, layers
  let markers = [];
  let routeLayer;
  let routeShadowLayer;
  let instructionMarkers = [];

  function updateMarkers() {
    markers.forEach(marker => marker.remove());
    markers = [];
    const bounds = L.latLngBounds();

    const options = routeDirections.getOptions();
    options.waypoints.filter(waypoint => waypoint.lat && waypoint.lon).forEach(waypoint => {
      bounds.extend([waypoint.lat, waypoint.lon]);
      markers.push(L.marker([waypoint.lat, waypoint.lon], {
        icon: markerIcon
      }).addTo(map));
    });

    if (markers.length > 1 && bounds.isValid()) {
      map.fitBounds(bounds, { padding: [50, 50] });
    } else if (markers.length) {
      map.setView(markers[0].getLatLng())
    }
  }

  function visualizeRoute(geojson) {
    if (routeLayer) {
      routeLayer.remove();
      routeShadowLayer.remove();
      instructionMarkers.forEach(marker => marker.remove());
      instructionMarkers = [];
    }

    if (!geojson || !geojson.properties) {
      return;
    }

    routeShadowLayer = L.geoJSON(geojson, {
      style: function (feature) {
        return { color: routeVisualizationOptions.shadowColor, weight: routeVisualizationOptions.width + 2 };
      }
    }).addTo(map);

    // create a route layer, chack more styling options here - https://leafletjs.com/reference.html#path 
    routeLayer = L.geoJSON(geojson, {
      style: function (feature) {
        return { color: routeVisualizationOptions.color, weight: routeVisualizationOptions.width };
      }
    }).addTo(map);

    const points = geojson.geometry.coordinates;
    geojson.properties.legs.forEach((leg, legIndex) => {
      const legPoints = points[legIndex];
      leg.steps.forEach(step => {
        // create turn-by-turn instruction marker, check more styling options here - https://leafletjs.com/reference.html#path
        instructionMarkers.push(L.circleMarker([legPoints[step.from_index][1], legPoints[step.from_index][0]], {
          radius: routeVisualizationOptions.width + 1,
          fill: true,
          fillOpacity: 1,
          fillColor: "#fff",
          color: routeVisualizationOptions.shadowColor,
          weight: 1
        }).bindPopup(step.instruction.text).addTo(map));
      });
    });
  }

  function generateInstructions(geojson) {
    const type2icon = {
      "StartAt": "navigation",
      "StartAtRight": "navigation",
      "StartAtLeft": "navigation",
      "DestinationReached": "place",
      "DestinationReachedRight": "place",
      "DestinationReachedLeft": "place",
      "Straight": "straight",
      "SlightRight": "turn_slight_right",
      "Right": "turn_right",
      "SharpRight": "turn_right",
      "TurnAroundRight": "u_turn_right",
      "TurnAroundLeft": "u_turn_left",
      "SharpLeft": "turn_left",
      "Left": "turn_left",
      "SlightLeft": "turn_slight_left",
      "ExitRight": "turn_slight_right",
      "ExitLeft": "turn_slight_left",
      "StayRight": "straight",
      "StayLeft": "straight",
      "Merge": "merge",
      "FerryEnter": "directions_boat",
      "FerryExit": "directions_boat",
      "MergeRight": "ramp_right",
      "MergeLeft": "ramp_left",
      "Roundabout": "roundabout"
    }

    const waypoints = routeDirections.getOptions().waypoints;

    const instrictionContainer = document.getElementById("geoapify-instructions");
    instrictionContainer.innerHTML = '';

    const isMetric = geojson.properties.distance_units === 'meters';

    if (geojson.properties.legs.length > 1) {        
      const waypointsInfo = document.createElement("div");
      waypointsInfo.classList.add("direction-waypoints");

      const distance = toPrettyDistance(geojson.properties.distance, isMetric);
      const time = toPrettyTime(geojson.properties.time);
      waypointsInfo.textContent = `${distance}, ${time}`;

      instrictionContainer.appendChild(waypointsInfo);
    }

    geojson.properties.legs.forEach((leg, index) => {
      const waypointsInfo = document.createElement("div");
      waypointsInfo.classList.add("direction-waypoints");

      const from = `${waypoints[index].address ? waypoints[index].address : `${waypoints[index].lat} ${waypoints[index].lon}`}`;
      const to = `${waypoints[index + 1].address ? waypoints[index + 1].address : `${waypoints[index + 1].lat} ${waypoints[index + 1].lon}`}`;
      const distance = toPrettyDistance(leg.distance, isMetric);
      const time = toPrettyTime(leg.time);
      waypointsInfo.textContent = `${distance}, ${time}`;       

      if (geojson.properties.legs.length > 1) {
        waypointsInfo.classList.add("smaller");

        const fromTo = document.createElement("div");
        fromTo.classList.add("direction-waypoints-from-to");
        fromTo.textContent = `(${from} - ${to})`;
        waypointsInfo.appendChild(fromTo);
      }

      instrictionContainer.appendChild(waypointsInfo);

      leg.steps.forEach(step => {
        // create instruction for each step
        const instruction = document.createElement("div");
        instruction.classList.add("direction-instruction");

        const iconElement = document.createElement("div");
        iconElement.classList.add("direction-instruction-icon");

        if (type2icon[step.instruction.type]) {
          addIcon(iconElement, type2icon[step.instruction.type]);
        }
        instruction.appendChild(iconElement);

        const infoElement = document.createElement("div");
        infoElement.classList.add("direction-instruction-info");

        const textElement = document.createElement("div");
        textElement.classList.add("direction-instruction-text");
        let text = step.instruction.text;

        if (step.instruction.streets) {
          step.instruction.streets.forEach(street => {
            text = text.split(street).join(`<b>${street}</b>`);
          })
        }

        textElement.innerHTML = text;
        infoElement.appendChild(textElement);

        if (step.instruction.post_transition_instruction && step.instruction.post_transition_instruction !== step.instruction.text) {
          const textElementPost = document.createElement("div");
          textElementPost.classList.add("direction-instruction-text-post");
          textElementPost.textContent = step.instruction.post_transition_instruction;
          infoElement.appendChild(textElementPost);
        }

        instruction.appendChild(infoElement);
        instrictionContainer.appendChild(instruction);
      });
    });
  }

  function addIcon(element, icon) {

    const icons = {
      navigation: "M12 2L4.5 20.29l.71.71L12 18l6.79 3 .71-.71z",
      place: "M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z",
      straight: "11,6.83 9.41,8.41 8,7 12,3 16,7 14.59,8.41 13,6.83 13,21 11,21",
      turn_slight_right: "M12.34,6V4H18v5.66h-2V7.41l-5,5V20H9v-7.58c0-0.53,0.21-1.04,0.59-1.41l5-5H12.34z",
      turn_right: "M17.17,11l-1.59,1.59L17,14l4-4l-4-4l-1.41,1.41L17.17,9L9,9c-1.1,0-2,0.9-2,2v9h2v-9L17.17,11z",
      u_turn_right: "M6,9v12h2V9c0-2.21,1.79-4,4-4s4,1.79,4,4v4.17l-1.59-1.59L13,13l4,4l4-4l-1.41-1.41L18,13.17V9c0-3.31-2.69-6-6-6 S6,5.69,6,9z",
      u_turn_left: "M18,9v12h-2V9c0-2.21-1.79-4-4-4S8,6.79,8,9v4.17l1.59-1.59L11,13l-4,4l-4-4l1.41-1.41L6,13.17V9c0-3.31,2.69-6,6-6 S18,5.69,18,9z",
      turn_left: "M6.83,11l1.59,1.59L7,14l-4-4l4-4l1.41,1.41L6.83,9L15,9c1.1,0,2,0.9,2,2v9h-2v-9L6.83,11z",
      turn_slight_left: "M11.66,6V4H6v5.66h2V7.41l5,5V20h2v-7.58c0-0.53-0.21-1.04-0.59-1.41l-5-5H11.66z",
      merge: "M6.41,21L5,19.59l4.83-4.83c0.75-0.75,1.17-1.77,1.17-2.83v-5.1L9.41,8.41L8,7l4-4l4,4l-1.41,1.41L13,6.83v5.1 c0,1.06,0.42,2.08,1.17,2.83L19,19.59L17.59,21L12,15.41L6.41,21z",
      directions_boat: "M20 21c-1.39 0-2.78-.47-4-1.32-2.44 1.71-5.56 1.71-8 0C6.78 20.53 5.39 21 4 21H2v2h2c1.38 0 2.74-.35 4-.99 2.52 1.29 5.48 1.29 8 0 1.26.65 2.62.99 4 .99h2v-2h-2zM3.95 19H4c1.6 0 3.02-.88 4-2 .98 1.12 2.4 2 4 2s3.02-.88 4-2c.98 1.12 2.4 2 4 2h.05l1.89-6.68c.08-.26.06-.54-.06-.78s-.34-.42-.6-.5L20 10.62V6c0-1.1-.9-2-2-2h-3V1H9v3H6c-1.1 0-2 .9-2 2v4.62l-1.29.42c-.26.08-.48.26-.6.5s-.15.52-.06.78L3.95 19zM6 6h12v3.97L12 8 6 9.97V6z",
      ramp_right: "M11,21h2V6.83l1.59,1.59L16,7l-4-4L8,7l1.41,1.41L11,6.83V9c0,4.27-4.03,7.13-6,8.27l1.46,1.46 C8.37,17.56,9.9,16.19,11,14.7L11,21z",
      ramp_left: "M13,21h-2V6.83L9.41,8.41L8,7l4-4l4,4l-1.41,1.41L13,6.83V9c0,4.27,4.03,7.13,6,8.27l-1.46,1.46 c-1.91-1.16-3.44-2.53-4.54-4.02L13,21z",
      roundabout: "M 21.896702,16.807279 c -0.616921,-1.781843 -1.233842,-3.563685 -1.850763,-5.345528 -1.781843,0.616921 -3.563686,1.233841 -5.345529,1.850762 0.899536,0.436846 1.799073,0.873692 2.698609,1.310538 -1.11625,2.404166 -3.955561,3.812593 -6.545968,3.267515 -0.12287,0.655398 -0.245741,1.310795 -0.368611,1.966193 3.392477,0.709283 7.107172,-1.090998 8.636322,-4.204987 0.180625,-0.304366 0.638154,0.204973 0.942889,0.265315 0.611017,0.296731 1.222034,0.593461 1.833051,0.890192 z M 3.0679937,18.424041 c 1.8609148,0.304223 3.7218297,0.608445 5.5827445,0.912668 C 8.9549611,17.475794 9.2591839,15.61488 9.5634068,13.753965 8.7514802,14.337724 7.9395535,14.921484 7.1276269,15.505243 5.5430465,13.380359 5.6535648,10.212845 7.3644605,8.1929056 6.8462477,7.7732646 6.328035,7.3536235 5.8098222,6.9339825 3.573199,9.5815282 3.3913242,13.70547 5.4041365,16.531034 5.5860587,16.834627 4.9204584,16.995049 4.7225152,17.234472 4.171008,17.630995 3.6195009,18.027518 3.0679937,18.424041 Z M 11.169997,1.0313754 C 9.941056,2.4615011 8.712115,3.8916268 7.483174,5.3217525 c 1.4301257,1.228941 2.860251,2.4578821 4.290377,3.6868231 -0.07544,-0.99715 -0.150889,-1.9943 -0.226333,-2.99145 2.63923,-0.2459573 5.285628,1.4981573 6.118854,4.0107364 C 18.294206,9.8040536 18.922341,9.5802452 19.550475,9.3564368 18.455312,6.0681852 15.02962,3.7650111 11.569211,4.0115874 11.215296,4.0087627 11.425023,3.3570159 11.323735,3.0633399 11.272489,2.3860184 11.221243,1.7086969 11.169997,1.0313754 Z"
    }

    var svgElement = document.createElementNS("http://www.w3.org/2000/svg", 'svg');
    svgElement.setAttribute('viewBox', "0 0 24 24");
    svgElement.setAttribute('height', "24");

    if (icons[icon].startsWith("M")) {
      var iconElement = document.createElementNS("http://www.w3.org/2000/svg", 'path');
      iconElement.setAttribute("d", icons[icon]);
      iconElement.setAttribute('fill', 'currentColor');
      svgElement.appendChild(iconElement);
    } else {
      var iconElement = document.createElementNS("http://www.w3.org/2000/svg", 'polygon');
      iconElement.setAttribute("points", icons[icon]);
      iconElement.setAttribute('fill', 'currentColor');
      svgElement.appendChild(iconElement);
    }

    element.appendChild(svgElement);
  }


  function toPrettyTime(seconds) {
    if (seconds === 0) {
      return '0'
    }

    if (seconds < 120) {
      return seconds + 's';
    }

    let hours = Math.floor(seconds / 3600);
    let minutes = Math.floor((seconds - (hours * 3600)) / 60);


    if (!hours) {
      return minutes + 'min';
    }

    if (!minutes) {
      return hours + 'h';
    }

    return hours + 'h ' + minutes + 'm';
  }

  function toPrettyDistance(value, isMetric) {
    if (!isMetric) {
      
      if (value >= 0.1) {
        return `${value.toFixed(1)}mi`
      }

      return `${Math.round(value * 5280)}feet`
    }

    if (value > 10000) {
      return `${(value / 1000).toFixed(1)}km`
    }

    if (value > 5000) {
      return `${(value / 1000).toFixed(1)}km`
    }

    return `${Math.round(value)}m`
  }
</script>

Optionally. Customize options, styling and colors

You can use the code as-is, or you can make some customizations:

Change a map style

You can choose a map style that fits your website's design. The available map styles are listed on the Map Tiles Documentation page.

Replace the map style in the const mapStyle = "osm-bright-smooth"; code line. For example:

<script>
  ...
  const mapStyle = "toner-grey";
  ...
</script>

Change marker icons

You can create custom map markers that visualize waypoints. We use the Geoapify Marker Icons API to generate these custom icons online with our API Playground.

Replace the icon URL with the generated URL:

<script>
  ...
  const markerIcon = L.icon({
    iconUrl: `https://api.geoapify.com/v1/icon/?type=awesome&scaleFactor=2&color=%23ff4949&apiKey=${apiKey}`,
    iconSize: [31, 46], // size of the icon
    iconAnchor: [15.5, 42], // point of the icon which will correspond to marker's location
    popupAnchor: [0, -45] // point from which the popup should open relative to the iconAnchor
  });
  ...
</script>

Change route line style

You can change the color and width of the route line that appears on the map. You can do this by changing the routeVisualizationOptions object:

<script>
  ...
  const routeVisualizationOptions = {
    color: '#6699ff',
    shadowColor: '#0055ff',
    width: 4
  };
  ...
</script>

Change Route Directions options

The Geoapify Route Directions library is flexible and customized to accommodate different travel modes, options, and locations. Here's how you can do this:

Show only desired travel modes

List the desired travel modes in supportedModes object. For example:

<script>
  ...
  const routeDirectionsOptions = {
    supportedModes: ['walk', 'hike', 'bicycle', 'mountain_bike', 'road_bike'],
    ...
  }; 
  ...
</script>

Show only desired options

The Route Directions control shows route options - avoids and units. You can change the options by changing supportedOptions. You can also set default options. For example, this way you can hide units control and set miles as a default unit:

<script>
  ...
  const routeDirectionsOptions = {
    supportedOptions: ['highways', 'tolls', 'ferries'],
    units: 'imperial'
    ...
  }; 
  ...
</script>

Show the "Calculate" button

To add a button that triggers the route calculation, proceed as follows:

<script>
  ...
  const routeDirectionsOptions = {
    calculateRouteTrigger: 'buttonClick'
    ...
  }; 
  ...
</script>

Localize Route Directions

The control provides localizability. For example, you can translate the control to Spanish (translated with Google Translate):

<script>
  ...
  const routeDirectionsOptions = {
    labels: {
      "avoid": "Evitar:",
      "addDestination": "Agregar nuevo destino",
      "avoidTolls": "Peajes",
      "avoidFerries": "Transbordadoras",
      "avoidHighways": "Carreteras",
      "calculateButton": "Calcular",
      "noRouteFound": "No se encontró ninguna ruta"
    },
    lang: 'es'
    ...
  }; 

  const geocoderOptions = {
    placeholder: "Introduzca una dirección aquí o haga clic en el mapa"
  };
  ...
</script>

What's next