Directions Service - Advanced

Using the Directions Service to get the distance and duration between two places. Add Waypoints to the route.

  1. Example
  2. Running the Sample Locally

Example

Directions Service - Advanced
        let map: woosmap.map.Map;
let bounds: woosmap.map.LatLngBounds;
let directionsService: woosmap.map.DirectionsService;
let directionsRequest: woosmap.map.DirectionRequest;
let markersArray: woosmap.map.Marker[] = [];
let waypointsList: HTMLElement;
let originContainer: HTMLElement;
let destinationContainer: HTMLElement;
const waypoints: woosmap.map.DirectionsWayPoint[] = [];
let directionsRenderer: woosmap.map.DirectionsRenderer;
const travelModeIcons = {
  DRIVING: "https://images.woosmap.com/directions/drive_black.png",
  WALKING: "https://images.woosmap.com/directions/walk_black.png",
  CYCLING: "https://images.woosmap.com/directions/bicycle_black.png",
};

function createMarker(
  position: woosmap.map.LatLngLiteral | woosmap.map.LatLng,
  label: string,
  url: string,
): woosmap.map.Marker {
  return new woosmap.map.Marker({
    map,
    position,
    draggable: true,
    icon: {
      url,
      labelOrigin: new woosmap.map.Point(13, 15),
      scaledSize: {
        height: 38,
        width: 26,
      },
    },
    label: {
      text: label,
      color: "white",
    },
  });
}

function clearMarkers(): void {
  for (const marker of markersArray) {
    marker.setMap(null);
  }
  markersArray = [];
}

function displayDirectionsMarkers(): void {
  clearMarkers();
  const originMarker = createMarker(
    directionsRequest.origin,
    "O",
    "https://images.woosmap.com/marker-blue.svg",
  );
  originMarker.addListener("dragend", () => {
    directionsRequest.origin = originMarker.getPosition();
    setLatLngToContainer(originContainer, originMarker.getPosition().toJSON());
    calculateDirections();
  });
  const destinationMarker = createMarker(
    directionsRequest.destination,
    "D",
    "https://images.woosmap.com/marker-red.svg",
  );
  destinationMarker.addListener("dragend", () => {
    directionsRequest.destination = destinationMarker.getPosition();
    setLatLngToContainer(
      destinationContainer,
      destinationMarker.getPosition().toJSON(),
    );
    calculateDirections();
  });
  markersArray.push(originMarker);
  markersArray.push(destinationMarker);
  bounds.extend(directionsRequest.origin as woosmap.map.LatLngLiteral);
  bounds.extend(directionsRequest.destination as woosmap.map.LatLngLiteral);
  waypoints.forEach((waypoint, index) => {
    bounds.extend(waypoint.location as woosmap.map.LatLngLiteral);
    const waypointMarker = createMarker(
      waypoint.location as woosmap.map.LatLngLiteral,
      (index + 1).toString(),
      "https://images.woosmap.com/marker-green.svg",
    );
    waypointMarker.addListener("dragend", () => {
      waypoints[index].location = waypointMarker.getPosition().toJSON();
      waypointsList.querySelectorAll("li")[index].innerHTML =
        `<span>lat: ${waypointMarker.getPosition().lat().toFixed(4)}, lng: ${waypointMarker.getPosition().lng().toFixed(4)}<span>`;
      calculateDirections();
    });
    markersArray.push(waypointMarker);
  });

  map.fitBounds(bounds, { top: 70, bottom: 40, left: 50, right: 50 }, true);
}

function createDefaultRequest(): woosmap.map.DirectionRequest {
  const origin = { lat: 51.6511, lng: -0.1615 };
  const destination = { lat: 51.5146, lng: -0.0212 };
  waypoints.push(
    { location: { lat: 51.4855, lng: -0.3179 } },
    { location: { lat: 51.5074, lng: -0.1278 } },
  );
  setLatLngToContainer(originContainer, origin);
  setLatLngToContainer(destinationContainer, destination);
  for (const waypoint of waypoints) {
    addLatLngToList(
      waypointsList,
      waypoint.location as woosmap.map.LatLngLiteral,
    );
  }

  return {
    origin,
    destination,
    waypoints,
    details: "full",
    provideRouteAlternatives: true,
    travelMode: woosmap.map.TravelMode.DRIVING,
    unitSystem: woosmap.map.UnitSystem.METRIC,
  };
}

function addLatLngToList(
  element: HTMLElement,
  location: woosmap.map.LatLngLiteral,
): void {
  const locationElement = document.createElement("li");
  locationElement.innerHTML = `<span>lat: ${location.lat.toFixed(4)}, lng: ${location.lng.toFixed(4)}<span>`;
  const removeButton = document.createElement("button");
  removeButton.classList.add("clear-searchButton");
  removeButton.innerHTML =
    '<svg class="clear-icon" viewBox="0 0 24 24"><path d="M7.074 5.754a.933.933 0 1 0-1.32 1.317L10.693 12l-4.937 4.929a.931.931 0 1 0 1.319 1.317l4.938-4.93 4.937 4.93a.933.933 0 0 0 1.581-.662.93.93 0 0 0-.262-.655L13.331 12l4.937-4.929a.93.93 0 0 0-.663-1.578.93.93 0 0 0-.656.261l-4.938 4.93z"></path></svg> ';
  removeButton.addEventListener("click", () => {
    element.removeChild(locationElement);
    if (element === waypointsList) {
      waypoints.splice(
        waypoints.findIndex((w) => w.location === location),
        1,
      );
    }
    calculateDirections();
  });
  locationElement.appendChild(removeButton);
  element.appendChild(locationElement);
}

function setLatLngToContainer(
  element: HTMLElement,
  location: woosmap.map.LatLngLiteral,
): void {
  element.innerHTML = `<li><span>lat: ${location.lat.toFixed(4)}, lng: ${location.lng.toFixed(4)}<span></li>`;
}

function createRoutesTable(response: woosmap.map.DirectionResult) {
  const directionTripElements = response.routes.map(
    (route: woosmap.map.DirectionRoute, index: number) => {
      const leg = route.legs[0];
      const distanceTotal = route.legs.reduce(
        (total, leg) => total + leg.distance.value,
        0,
      );
      const durationTotal = route.legs.reduce(
        (total, leg) => total + leg.duration.value,
        0,
      );
      const directionTrip = document.createElement("div");
      directionTrip.className = "directionTrip";
      if (index === 0) {
        directionTrip.classList.add("directionTrip__selected");
      }
      const travelModeIconSrc =
        travelModeIcons[directionsRequest.travelMode as string] ||
        travelModeIcons["DRIVING"];

      directionTrip.innerHTML = `
            <img alt="travel mode" class="directionTrip__travelModeIcon" src="${travelModeIconSrc}">
            <div class="directionTrip__description">
                <div class="directionTrip__numbers">
                    <div class="directionTrip__duration">${formatTime(durationTotal)}</div>
                    <div class="directionTrip__distance">${formatDistance(distanceTotal)}</div>
                </div>
                <div class="directionTrip__title">through ${route.main_route_name ? route.main_route_name : JSON.stringify(leg.start_location)}</div>
                <div class="directionTrip__summary">${formatTime(durationTotal)} ${directionsRequest.departure_time || directionsRequest.arrival_time ? "with" : "without"} traffic</div>
                <div class="directionTrip__detailsMsg"></div>
            </div>
        `;

      directionTrip.addEventListener("click", () => {
        selectCorrectRoute(index);
        directionsRenderer.setRouteIndex(index);
      });

      return directionTrip;
    },
  );

  function formatDistance(meters: number): string {
    if (meters < 1000) {
      return `${meters} m`;
    } else {
      return `${(meters / 1000).toFixed(2)} km`;
    }
  }

  function formatTime(seconds: number): string {
    const minutes = Math.round(seconds / 60);
    if (minutes < 60) {
      return `${minutes}m`;
    } else {
      const hours = Math.floor(minutes / 60);
      const remainingMinutes = minutes % 60;
      return `${hours}h${remainingMinutes}m`;
    }
  }
  function selectCorrectRoute(index: number) {
    document
      .querySelectorAll(".directionTrip__selected")
      .forEach((selectedElement) => {
        selectedElement.classList.remove("directionTrip__selected");
      });
    directionTripElements[index].classList.add("directionTrip__selected");
  }

  const tableContainer = document.querySelector(
    ".tableContainer",
  ) as HTMLElement;
  tableContainer.innerHTML = "";
  directionTripElements.forEach((element) =>
    tableContainer.appendChild(element),
  );
  tableContainer.style.display = "block";
}

function displayOrHideError(error: string) {
  const infoMsgElement = document.getElementById("infoMessage") as HTMLElement;
  if (error === "") {
    infoMsgElement.innerText = "Drag markers to update route";
  } else {
    infoMsgElement.innerHTML = error;
    infoMsgElement.style.display = "block";
    const tableContainer = document.querySelector(
      ".tableContainer",
    ) as HTMLElement;
    tableContainer.innerHTML = "";
    tableContainer.style.display = "none";
  }
}

function toggleProgress() {
  (document.querySelector(".linear-progress") as HTMLElement).classList.toggle(
    "hide",
  );
}

function displayDirectionsRoute(response: woosmap.map.DirectionResult) {
  directionsRenderer.setDirections(response);
}

function calculateDirections(): void {
  toggleProgress();

  new Promise((resolve, reject): void => {
    // TODO: the current implementation of `directionsService.route` throws an error when no routes are returned. As a workaround, we employ this Timeout hack.
    const timeoutId = setTimeout(() => {
      reject(new Error("Callback not called within 3 secs"));
    }, 3000);

    directionsService.route(directionsRequest, (response, status) => {
      // If the callback is called, clear the timeout and resolve or reject the Promise
      clearTimeout(timeoutId);
      if (status === "OK" && response && response.routes) {
        resolve(response);
      } else {
        reject(new Error(`Error calculating distances: ${status}`));
      }
    });
  })
    .then((response: unknown) => {
      const directionResult = response as woosmap.map.DirectionResult;
      displayDirectionsMarkers();
      displayDirectionsRoute(directionResult);
      createRoutesTable(directionResult);
      displayOrHideError("");
    })
    .catch((error: Error) => {
      console.error(error);
      displayOrHideError(error.message);
    })
    .finally(() => {
      toggleProgress();
    });
}

function toggleTravelMode(travelMode: HTMLDivElement): void {
  document
    .querySelectorAll(".travelMode")
    .forEach((el) => el.classList.remove("travelMode__selected"));
  travelMode.classList.add("travelMode__selected");
}

function updateTravelModeButtons(): void {
  document.querySelectorAll(".travelMode").forEach((el) =>
    el.addEventListener("click", () => {
      toggleTravelMode(el as HTMLDivElement);
      directionsRequest.travelMode = (el as HTMLDivElement).dataset
        .travelmode as woosmap.map.TravelMode | undefined;
      calculateDirections();
    }),
  );
}

function updateAvoidance(): void {
  document.querySelectorAll(".avoid").forEach((el) =>
    el.addEventListener("click", () => {
      const avoidHighways = document.getElementById(
        "avoidHighways",
      ) as HTMLInputElement;
      const avoidTolls = document.getElementById(
        "avoidTolls",
      ) as HTMLInputElement;
      const avoidFerries = document.getElementById(
        "avoidFerries",
      ) as HTMLInputElement;
      directionsRequest.avoidFerries = avoidFerries.checked;
      directionsRequest.avoidHighways = avoidHighways.checked;
      directionsRequest.avoidTolls = avoidTolls.checked;
      calculateDirections();
    }),
  );
}

function updateOptimizeWaypoint(): void {
  (
    document.querySelector(
      'input[name="optimizeWaypoints"]',
    ) as HTMLInputElement
  ).addEventListener("change", () => {
    directionsRequest.optimizeWaypoints = (
      document.querySelector(
        'input[name="optimizeWaypoints"]',
      ) as HTMLInputElement
    ).checked;
    calculateDirections();
  });
}

function updateDistanceUnit(): void {
  document.querySelectorAll('input[name="distanceUnits"]').forEach((el) => {
    el.addEventListener("change", () => {
      directionsRequest.unitSystem = (el as HTMLInputElement).value as
        | woosmap.map.UnitSystem
        | undefined;
      calculateDirections();
    });
  });
}

function updateAlternatives(): void {
  document.querySelectorAll('input[name="alternatives"]').forEach((el) => {
    el.addEventListener("change", () => {
      if ((el as HTMLInputElement).checked) {
        directionsRequest.provideRouteAlternatives =
          (el as HTMLInputElement).value === "true";
        calculateDirections();
      }
    });
  });
}

function updateMethod(): void {
  document.querySelectorAll('input[name="method"]').forEach((el) => {
    el.addEventListener("change", () => {
      directionsRequest.method = (el as HTMLInputElement).value as
        | "distance"
        | "time"
        | undefined;
      calculateDirections();
    });
  });
}

function updateDetails(): void {
  document.querySelectorAll('input[name="details"]').forEach((el) => {
    el.addEventListener("change", () => {
      directionsRequest.details = (el as HTMLInputElement).value as
        | "none"
        | "full"
        | undefined;
      calculateDirections();
    });
  });
}

function updateLanguage(): void {
  const languageSelect = document.getElementById(
    "language",
  ) as HTMLSelectElement;
  languageSelect.addEventListener("change", () => {
    directionsRequest.language = languageSelect.value;
    calculateDirections();
  });
}

function isValidDate(date: Date): boolean {
  return date.getTime && typeof date.getTime === "function";
}

function updateDepartureTime(): void {
  const departureTimeElement = document.getElementById(
    "departure-time",
  ) as HTMLInputElement;
  const datetimeRadioButton = document.getElementById(
    "datetime",
  ) as HTMLInputElement;

  if (!departureTimeElement) {
    return;
  }
  departureTimeElement.min = new Date().toISOString().slice(0, 16);
  departureTimeElement.disabled = true;
  document.querySelectorAll('input[name="departureTime"]').forEach((el) => {
    el.addEventListener("change", () => {
      const selectedOption = (el as HTMLInputElement).value;
      switch (selectedOption) {
        case "empty":
          delete directionsRequest.departure_time;
          departureTimeElement.disabled = true;
          break;
        case "now":
          directionsRequest.departure_time = "now";
          departureTimeElement.disabled = true;
          break;
        case "datetime":
          if (departureTimeElement.value) {
            const newDate = new Date(departureTimeElement.value);
            directionsRequest.departure_time = isValidDate(newDate)
              ? newDate.getTime().toString()
              : undefined;
          } else {
            directionsRequest.departure_time = undefined;
          }
          departureTimeElement.disabled = false;
          break;
      }
      calculateDirections();
    });
  });

  departureTimeElement.addEventListener("change", () => {
    if (datetimeRadioButton) {
      datetimeRadioButton.checked = true;
    }
    const newDate = new Date(departureTimeElement.value);
    directionsRequest.departure_time = isValidDate(newDate)
      ? newDate.getTime().toString()
      : undefined;
    calculateDirections();
  });
}

function registerAddButton(
  selector: string,
  element: HTMLElement,
  loc: woosmap.map.LatLngLiteral[] | woosmap.map.LatLngLiteral,
): void {
  const button = document.querySelector(selector) as HTMLElement;
  button.addEventListener("click", () => {
    if (button.classList.contains("addLocation__selected")) {
      button.classList.remove("addLocation__selected");
      document.getElementById("map")?.classList.remove("cursor-crosshair");
      woosmap.map.event.clearListeners(map, "click");
      return;
    }
    button.classList.add("addLocation__selected");
    document.getElementById("map")?.classList.add("cursor-crosshair");
    woosmap.map.event.addListenerOnce(map, "click", (e) => {
      document.getElementById("map")?.classList.remove("cursor-crosshair");
      button.classList.remove("addLocation__selected");
      const location = e.latlng;
      if (Array.isArray(loc)) {
        waypoints.push({ location });
        addLatLngToList(element, location);
      } else {
        if (element === originContainer) {
          directionsRequest.origin = location;
        }
        if (element === destinationContainer) {
          directionsRequest.destination = location;
        }
        setLatLngToContainer(element, location);
      }
      calculateDirections();
    });
  });
}

function initUI(): void {
  updateTravelModeButtons();
  updateAvoidance();
  updateDistanceUnit();
  updateMethod();
  updateAlternatives();
  updateDetails();
  updateDepartureTime();
  updateLanguage();
  updateOptimizeWaypoint();
  registerAddButton(
    ".addLocation__destinations",
    destinationContainer,
    directionsRequest.destination as woosmap.map.LatLngLiteral,
  );
  registerAddButton(
    ".addLocation__origins",
    originContainer,
    directionsRequest.origin as woosmap.map.LatLngLiteral,
  );
  registerAddButton(
    ".addLocation__waypoints",
    waypointsList,
    waypoints.map((waypoint) => waypoint.location as woosmap.map.LatLngLiteral),
  );
}

function initMap(): void {
  map = new woosmap.map.Map(document.getElementById("map") as HTMLElement, {
    center: { lat: 51.5074, lng: -0.1478 },
    zoom: 10,
  });
  directionsService = new woosmap.map.DirectionsService();
  directionsRenderer = new woosmap.map.DirectionsRenderer({});
  directionsRenderer.setMap(map);
  bounds = new woosmap.map.LatLngBounds();
  originContainer = document.getElementById("origin") as HTMLElement;
  destinationContainer = document.getElementById("destination") as HTMLElement;
  waypointsList = document.getElementById("waypoints") as HTMLElement;
  directionsRequest = createDefaultRequest();
  initUI();
  calculateDirections();
}

declare global {
  interface Window {
    initMap: () => void;
  }
}
window.initMap = initMap;

    
        let map;
let bounds;
let directionsService;
let directionsRequest;
let markersArray = [];
let waypointsList;
let originContainer;
let destinationContainer;
const waypoints = [];
let directionsRenderer;
const travelModeIcons = {
  DRIVING: "https://images.woosmap.com/directions/drive_black.png",
  WALKING: "https://images.woosmap.com/directions/walk_black.png",
  CYCLING: "https://images.woosmap.com/directions/bicycle_black.png",
};

function createMarker(position, label, url) {
  return new woosmap.map.Marker({
    map,
    position,
    draggable: true,
    icon: {
      url,
      labelOrigin: new woosmap.map.Point(13, 15),
      scaledSize: {
        height: 38,
        width: 26,
      },
    },
    label: {
      text: label,
      color: "white",
    },
  });
}

function clearMarkers() {
  for (const marker of markersArray) {
    marker.setMap(null);
  }

  markersArray = [];
}

function displayDirectionsMarkers() {
  clearMarkers();

  const originMarker = createMarker(
    directionsRequest.origin,
    "O",
    "https://images.woosmap.com/marker-blue.svg",
  );

  originMarker.addListener("dragend", () => {
    directionsRequest.origin = originMarker.getPosition();
    setLatLngToContainer(originContainer, originMarker.getPosition().toJSON());
    calculateDirections();
  });

  const destinationMarker = createMarker(
    directionsRequest.destination,
    "D",
    "https://images.woosmap.com/marker-red.svg",
  );

  destinationMarker.addListener("dragend", () => {
    directionsRequest.destination = destinationMarker.getPosition();
    setLatLngToContainer(
      destinationContainer,
      destinationMarker.getPosition().toJSON(),
    );
    calculateDirections();
  });
  markersArray.push(originMarker);
  markersArray.push(destinationMarker);
  bounds.extend(directionsRequest.origin);
  bounds.extend(directionsRequest.destination);
  waypoints.forEach((waypoint, index) => {
    bounds.extend(waypoint.location);

    const waypointMarker = createMarker(
      waypoint.location,
      (index + 1).toString(),
      "https://images.woosmap.com/marker-green.svg",
    );

    waypointMarker.addListener("dragend", () => {
      waypoints[index].location = waypointMarker.getPosition().toJSON();
      waypointsList.querySelectorAll("li")[index].innerHTML =
        `<span>lat: ${waypointMarker.getPosition().lat().toFixed(4)}, lng: ${waypointMarker.getPosition().lng().toFixed(4)}<span>`;
      calculateDirections();
    });
    markersArray.push(waypointMarker);
  });
  map.fitBounds(bounds, { top: 70, bottom: 40, left: 50, right: 50 }, true);
}

function createDefaultRequest() {
  const origin = { lat: 51.6511, lng: -0.1615 };
  const destination = { lat: 51.5146, lng: -0.0212 };

  waypoints.push(
    { location: { lat: 51.4855, lng: -0.3179 } },
    { location: { lat: 51.5074, lng: -0.1278 } },
  );
  setLatLngToContainer(originContainer, origin);
  setLatLngToContainer(destinationContainer, destination);

  for (const waypoint of waypoints) {
    addLatLngToList(waypointsList, waypoint.location);
  }
  return {
    origin,
    destination,
    waypoints,
    details: "full",
    provideRouteAlternatives: true,
    travelMode: woosmap.map.TravelMode.DRIVING,
    unitSystem: woosmap.map.UnitSystem.METRIC,
  };
}

function addLatLngToList(element, location) {
  const locationElement = document.createElement("li");

  locationElement.innerHTML = `<span>lat: ${location.lat.toFixed(4)}, lng: ${location.lng.toFixed(4)}<span>`;

  const removeButton = document.createElement("button");

  removeButton.classList.add("clear-searchButton");
  removeButton.innerHTML =
    '<svg class="clear-icon" viewBox="0 0 24 24"><path d="M7.074 5.754a.933.933 0 1 0-1.32 1.317L10.693 12l-4.937 4.929a.931.931 0 1 0 1.319 1.317l4.938-4.93 4.937 4.93a.933.933 0 0 0 1.581-.662.93.93 0 0 0-.262-.655L13.331 12l4.937-4.929a.93.93 0 0 0-.663-1.578.93.93 0 0 0-.656.261l-4.938 4.93z"></path></svg> ';
  removeButton.addEventListener("click", () => {
    element.removeChild(locationElement);
    if (element === waypointsList) {
      waypoints.splice(
        waypoints.findIndex((w) => w.location === location),
        1,
      );
    }

    calculateDirections();
  });
  locationElement.appendChild(removeButton);
  element.appendChild(locationElement);
}

function setLatLngToContainer(element, location) {
  element.innerHTML = `<li><span>lat: ${location.lat.toFixed(4)}, lng: ${location.lng.toFixed(4)}<span></li>`;
}

function createRoutesTable(response) {
  const directionTripElements = response.routes.map((route, index) => {
    const leg = route.legs[0];
    const distanceTotal = route.legs.reduce(
      (total, leg) => total + leg.distance.value,
      0,
    );
    const durationTotal = route.legs.reduce(
      (total, leg) => total + leg.duration.value,
      0,
    );
    const directionTrip = document.createElement("div");

    directionTrip.className = "directionTrip";
    if (index === 0) {
      directionTrip.classList.add("directionTrip__selected");
    }

    const travelModeIconSrc =
      travelModeIcons[directionsRequest.travelMode] ||
      travelModeIcons["DRIVING"];

    directionTrip.innerHTML = `
            <img alt="travel mode" class="directionTrip__travelModeIcon" src="${travelModeIconSrc}">
            <div class="directionTrip__description">
                <div class="directionTrip__numbers">
                    <div class="directionTrip__duration">${formatTime(durationTotal)}</div>
                    <div class="directionTrip__distance">${formatDistance(distanceTotal)}</div>
                </div>
                <div class="directionTrip__title">through ${route.main_route_name ? route.main_route_name : JSON.stringify(leg.start_location)}</div>
                <div class="directionTrip__summary">${formatTime(durationTotal)} ${directionsRequest.departure_time || directionsRequest.arrival_time ? "with" : "without"} traffic</div>
                <div class="directionTrip__detailsMsg"></div>
            </div>
        `;
    directionTrip.addEventListener("click", () => {
      selectCorrectRoute(index);
      directionsRenderer.setRouteIndex(index);
    });
    return directionTrip;
  });

  function formatDistance(meters) {
    if (meters < 1000) {
      return `${meters} m`;
    } else {
      return `${(meters / 1000).toFixed(2)} km`;
    }
  }

  function formatTime(seconds) {
    const minutes = Math.round(seconds / 60);

    if (minutes < 60) {
      return `${minutes}m`;
    } else {
      const hours = Math.floor(minutes / 60);
      const remainingMinutes = minutes % 60;
      return `${hours}h${remainingMinutes}m`;
    }
  }

  function selectCorrectRoute(index) {
    document
      .querySelectorAll(".directionTrip__selected")
      .forEach((selectedElement) => {
        selectedElement.classList.remove("directionTrip__selected");
      });
    directionTripElements[index].classList.add("directionTrip__selected");
  }

  const tableContainer = document.querySelector(".tableContainer");

  tableContainer.innerHTML = "";
  directionTripElements.forEach((element) =>
    tableContainer.appendChild(element),
  );
  tableContainer.style.display = "block";
}

function displayOrHideError(error) {
  const infoMsgElement = document.getElementById("infoMessage");

  if (error === "") {
    infoMsgElement.innerText = "Drag markers to update route";
  } else {
    infoMsgElement.innerHTML = error;
    infoMsgElement.style.display = "block";

    const tableContainer = document.querySelector(".tableContainer");

    tableContainer.innerHTML = "";
    tableContainer.style.display = "none";
  }
}

function toggleProgress() {
  document.querySelector(".linear-progress").classList.toggle("hide");
}

function displayDirectionsRoute(response) {
  directionsRenderer.setDirections(response);
}

function calculateDirections() {
  toggleProgress();
  new Promise((resolve, reject) => {
    // TODO: the current implementation of `directionsService.route` throws an error when no routes are returned. As a workaround, we employ this Timeout hack.
    const timeoutId = setTimeout(() => {
      reject(new Error("Callback not called within 3 secs"));
    }, 3000);

    directionsService.route(directionsRequest, (response, status) => {
      // If the callback is called, clear the timeout and resolve or reject the Promise
      clearTimeout(timeoutId);
      if (status === "OK" && response && response.routes) {
        resolve(response);
      } else {
        reject(new Error(`Error calculating distances: ${status}`));
      }
    });
  })
    .then((response) => {
      const directionResult = response;

      displayDirectionsMarkers();
      displayDirectionsRoute(directionResult);
      createRoutesTable(directionResult);
      displayOrHideError("");
    })
    .catch((error) => {
      console.error(error);
      displayOrHideError(error.message);
    })
    .finally(() => {
      toggleProgress();
    });
}

function toggleTravelMode(travelMode) {
  document
    .querySelectorAll(".travelMode")
    .forEach((el) => el.classList.remove("travelMode__selected"));
  travelMode.classList.add("travelMode__selected");
}

function updateTravelModeButtons() {
  document.querySelectorAll(".travelMode").forEach((el) =>
    el.addEventListener("click", () => {
      toggleTravelMode(el);
      directionsRequest.travelMode = el.dataset.travelmode;
      calculateDirections();
    }),
  );
}

function updateAvoidance() {
  document.querySelectorAll(".avoid").forEach((el) =>
    el.addEventListener("click", () => {
      const avoidHighways = document.getElementById("avoidHighways");
      const avoidTolls = document.getElementById("avoidTolls");
      const avoidFerries = document.getElementById("avoidFerries");

      directionsRequest.avoidFerries = avoidFerries.checked;
      directionsRequest.avoidHighways = avoidHighways.checked;
      directionsRequest.avoidTolls = avoidTolls.checked;
      calculateDirections();
    }),
  );
}

function updateOptimizeWaypoint() {
  document
    .querySelector('input[name="optimizeWaypoints"]')
    .addEventListener("change", () => {
      directionsRequest.optimizeWaypoints = document.querySelector(
        'input[name="optimizeWaypoints"]',
      ).checked;
      calculateDirections();
    });
}

function updateDistanceUnit() {
  document.querySelectorAll('input[name="distanceUnits"]').forEach((el) => {
    el.addEventListener("change", () => {
      directionsRequest.unitSystem = el.value;
      calculateDirections();
    });
  });
}

function updateAlternatives() {
  document.querySelectorAll('input[name="alternatives"]').forEach((el) => {
    el.addEventListener("change", () => {
      if (el.checked) {
        directionsRequest.provideRouteAlternatives = el.value === "true";
        calculateDirections();
      }
    });
  });
}

function updateMethod() {
  document.querySelectorAll('input[name="method"]').forEach((el) => {
    el.addEventListener("change", () => {
      directionsRequest.method = el.value;
      calculateDirections();
    });
  });
}

function updateDetails() {
  document.querySelectorAll('input[name="details"]').forEach((el) => {
    el.addEventListener("change", () => {
      directionsRequest.details = el.value;
      calculateDirections();
    });
  });
}

function updateLanguage() {
  const languageSelect = document.getElementById("language");

  languageSelect.addEventListener("change", () => {
    directionsRequest.language = languageSelect.value;
    calculateDirections();
  });
}

function isValidDate(date) {
  return date.getTime && typeof date.getTime === "function";
}

function updateDepartureTime() {
  const departureTimeElement = document.getElementById("departure-time");
  const datetimeRadioButton = document.getElementById("datetime");

  if (!departureTimeElement) {
    return;
  }

  departureTimeElement.min = new Date().toISOString().slice(0, 16);
  departureTimeElement.disabled = true;
  document.querySelectorAll('input[name="departureTime"]').forEach((el) => {
    el.addEventListener("change", () => {
      const selectedOption = el.value;

      switch (selectedOption) {
        case "empty":
          delete directionsRequest.departure_time;
          departureTimeElement.disabled = true;
          break;
        case "now":
          directionsRequest.departure_time = "now";
          departureTimeElement.disabled = true;
          break;
        case "datetime":
          if (departureTimeElement.value) {
            const newDate = new Date(departureTimeElement.value);

            directionsRequest.departure_time = isValidDate(newDate)
              ? newDate.getTime().toString()
              : undefined;
          } else {
            directionsRequest.departure_time = undefined;
          }

          departureTimeElement.disabled = false;
          break;
      }

      calculateDirections();
    });
  });
  departureTimeElement.addEventListener("change", () => {
    if (datetimeRadioButton) {
      datetimeRadioButton.checked = true;
    }

    const newDate = new Date(departureTimeElement.value);

    directionsRequest.departure_time = isValidDate(newDate)
      ? newDate.getTime().toString()
      : undefined;
    calculateDirections();
  });
}

function registerAddButton(selector, element, loc) {
  const button = document.querySelector(selector);

  button.addEventListener("click", () => {
    if (button.classList.contains("addLocation__selected")) {
      button.classList.remove("addLocation__selected");
      document.getElementById("map")?.classList.remove("cursor-crosshair");
      woosmap.map.event.clearListeners(map, "click");
      return;
    }

    button.classList.add("addLocation__selected");
    document.getElementById("map")?.classList.add("cursor-crosshair");
    woosmap.map.event.addListenerOnce(map, "click", (e) => {
      document.getElementById("map")?.classList.remove("cursor-crosshair");
      button.classList.remove("addLocation__selected");

      const location = e.latlng;

      if (Array.isArray(loc)) {
        waypoints.push({ location });
        addLatLngToList(element, location);
      } else {
        if (element === originContainer) {
          directionsRequest.origin = location;
        }

        if (element === destinationContainer) {
          directionsRequest.destination = location;
        }

        setLatLngToContainer(element, location);
      }

      calculateDirections();
    });
  });
}

function initUI() {
  updateTravelModeButtons();
  updateAvoidance();
  updateDistanceUnit();
  updateMethod();
  updateAlternatives();
  updateDetails();
  updateDepartureTime();
  updateLanguage();
  updateOptimizeWaypoint();
  registerAddButton(
    ".addLocation__destinations",
    destinationContainer,
    directionsRequest.destination,
  );
  registerAddButton(
    ".addLocation__origins",
    originContainer,
    directionsRequest.origin,
  );
  registerAddButton(
    ".addLocation__waypoints",
    waypointsList,
    waypoints.map((waypoint) => waypoint.location),
  );
}

function initMap() {
  map = new woosmap.map.Map(document.getElementById("map"), {
    center: { lat: 51.5074, lng: -0.1478 },
    zoom: 10,
  });
  directionsService = new woosmap.map.DirectionsService();
  directionsRenderer = new woosmap.map.DirectionsRenderer({});
  directionsRenderer.setMap(map);
  bounds = new woosmap.map.LatLngBounds();
  originContainer = document.getElementById("origin");
  destinationContainer = document.getElementById("destination");
  waypointsList = document.getElementById("waypoints");
  directionsRequest = createDefaultRequest();
  initUI();
  calculateDirections();
}

window.initMap = initMap;

    
        html,
body {
  height: 100%;
  margin: 0;
  padding: 0;
  font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji;
}

#container {
  height: 100%;
  display: flex;
}

#sidebar {
  flex-basis: 12rem;
  flex-grow: 1;
  max-width: 30rem;
  height: 100%;
  box-sizing: border-box;
  overflow: auto;
}

#map {
  flex-basis: 70vw;
  flex-grow: 5;
  height: 100%;
}

#sidebar {
  flex-basis: 18rem;
  box-shadow: 0 -2px 4px 0 rgba(0, 0, 0, 0.12);
  z-index: 1;
}

#mapContainer {
  display: flex;
  flex-direction: column;
  flex-basis: 70vw;
  flex-grow: 5;
  position: relative;
}

.tableContainer {
  max-height: 35%;
  overflow-y: auto;
  font-size: 13px;
}

.directionTrip {
  cursor: pointer;
  color: rgba(0, 0, 0, 0.5411764706);
  border-bottom: 1px solid #e6e6e6;
  flex: none;
  padding: 1rem;
  position: relative;
}
.directionTrip__selected :before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  border-left: 5px solid #3d5afe;
  height: 100%;
}
.directionTrip__selected .directionTrip__detailsMsg {
  display: block;
}
.directionTrip__detailsMsg {
  display: none;
}
.directionTrip__travelModeIcon {
  position: absolute;
  top: 18px;
  width: 24px;
  height: 24px;
  opacity: 0.6;
}
.directionTrip__description {
  padding-left: 48px;
}
.directionTrip__numbers {
  float: right;
  text-align: right;
  padding-left: 10px;
}
.directionTrip__duration, .directionTrip__title {
  color: rgba(0, 0, 0, 0.87);
  font-size: 15px;
  vertical-align: top;
}
.directionTrip__duration {
  color: #188038;
}
.directionTrip__distance, .directionTrip__summary {
  line-height: 16px;
  padding: 4px 0;
  font-size: 13px;
}
.directionTrip__detailsMsg {
  font-size: 13px;
  font-weight: 500;
  text-transform: uppercase;
  color: #3d5afe;
}
.directionTrip__empty {
  padding: 1rem;
}
.directionTrip__error {
  margin-top: 1rem;
  font-size: 0.9rem;
}
.directionTrip__error:before {
  content: "";
  display: inline-block;
  background-size: cover;
  width: 17px;
  height: 15px;
  padding-right: 5px;
  overflow: hidden;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='48' width='48'%3E%3Cpath d='M2 42 24 4l22 38Zm22.2-5.85q.65 0 1.075-.425.425-.425.425-1.075 0-.65-.425-1.075-.425-.425-1.075-.425-.65 0-1.075.425Q22.7 34 22.7 34.65q0 .65.425 1.075.425.425 1.075.425Zm-1.5-5.55h3V19.4h-3Z' fill= '%23F3d5afe' /%3E%3C/svg%3E");
}

#innerWrapper {
  display: flex;
  flex-direction: column;
  flex-grow: 1;
  overflow: hidden;
  overflow-y: auto;
  padding: 0 10px 40px;
}

input,
select {
  font-family: inherit;
  font-size: 100%;
  box-sizing: border-box;
}

.travelModeSelector {
  display: flex;
  align-content: flex-start;
  justify-content: space-between;
  max-width: 140px;
  text-align: center;
  height: 48px;
}
.travelModeSelector .travelMode {
  position: relative;
  display: inline-block;
  text-align: center;
  z-index: 0;
}
.travelModeSelector .travelMode button {
  display: flex;
  cursor: pointer;
  background: transparent;
  border: 0;
  border-radius: 0;
  font: inherit;
  list-style: none;
  margin: 0;
  outline: 0;
  overflow: visible;
  padding: 0;
  vertical-align: baseline;
  position: relative;
}
.travelModeSelector .travelMode .iconTravelMode {
  width: 24px;
  height: 24px;
  margin: 12px 9px 11px 9px;
}
.travelModeSelector .travelMode .iconTravelMode__DRIVING, .travelModeSelector .travelMode .iconTravelMode__WALKING, .travelModeSelector .travelMode .iconTravelMode__CYCLING {
  display: block;
}
.travelModeSelector .travelMode .iconTravelMode__DRIVING__selected, .travelModeSelector .travelMode .iconTravelMode__WALKING__selected, .travelModeSelector .travelMode .iconTravelMode__CYCLING__selected {
  display: none;
}
.travelMode__selected .travelModeSelector .travelMode .iconTravelMode__DRIVING, .travelMode__selected .travelModeSelector .travelMode .iconTravelMode__WALKING, .travelMode__selected .travelModeSelector .travelMode .iconTravelMode__CYCLING {
  display: none;
}
.travelModeSelector .travelMode__selected button {
  cursor: default;
}
.travelModeSelector .travelMode__selected .iconTravelMode__DRIVING, .travelModeSelector .travelMode__selected .iconTravelMode__WALKING, .travelModeSelector .travelMode__selected .iconTravelMode__CYCLING {
  display: none;
}
.travelModeSelector .travelMode__selected .iconTravelMode__DRIVING__selected, .travelModeSelector .travelMode__selected .iconTravelMode__WALKING__selected, .travelModeSelector .travelMode__selected .iconTravelMode__CYCLING__selected {
  display: block;
}
.travelModeSelector .travelMode__selected::after {
  background-color: #3d5afe;
}
.travelModeSelector .travelMode::after,
.travelModeSelector .travelMode button::after {
  content: "";
  border-radius: 100%;
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: -1;
  margin: 6px 3px 5px 3px;
  height: 36px;
  width: 36px;
}
.travelModeSelector .travelMode:hover button:after {
  background-color: rgba(60, 64, 67, 0.04);
}

.directionsOptions {
  padding: 0;
  margin: 0;
  list-style: none;
  height: 100%;
  background: #fff;
  display: flex;
  font-size: 13px;
}
.directionsOptions__list {
  width: 50%;
  height: 100%;
}
.directionsOptions__header {
  font-weight: 600;
  line-height: 24px;
  display: flex;
}
.directionsOptions__input {
  height: 24px;
  display: flex;
  align-items: baseline;
}
.directionsOptions__content {
  padding: 10px 0;
}
.directionsOptions__content:first-child {
  padding-top: 0;
}

.addLocation {
  font-size: 0.75em;
  display: flex;
  color: #222;
  cursor: pointer;
}
.addLocation:hover {
  text-decoration: underline;
}
.addLocation div {
  margin-left: 5px;
}
.addLocation__selected {
  color: #3d5afe;
}
.addLocation__selected svg path {
  fill: #3d5afe;
}

.sectionHeader {
  background: #f1f1f1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  border-bottom: 1px solid #eeeeee;
  margin: 20px -10px 5px;
  padding: 5px 10px;
  color: #222;
}
.sectionHeader span {
  font-size: 0.85em;
  font-weight: 600;
}
.sectionHeader:first-child {
  margin-top: 0;
}

.customCounter {
  margin: 0;
  padding: 0;
  list-style-type: none;
  width: 100%;
}

.customCounter li {
  counter-increment: step-counter;
  line-height: 14px;
  height: 22px;
  display: flex;
  align-items: center;
}
.customCounter li span {
  flex-grow: 1;
}

.customCounter li::before {
  margin-right: 5px;
  font-size: 80%;
  color: #fff;
  background-position: bottom;
  font-weight: 600;
  width: 20px;
  height: 20px;
  text-align: center;
  background-size: contain;
  background-repeat: no-repeat;
}

.customCounter__destination li::before {
  background-image: url(https://images.woosmap.com/marker-red.svg);
  content: "D";
}

.customCounter__origin li::before {
  background-image: url(https://images.woosmap.com/marker-blue.svg);
  content: "O";
}

.customCounter__waypoints li::before {
  background-image: url(https://images.woosmap.com/marker-green.svg);
  content: counter(step-counter);
}

.clear-searchButton {
  display: block;
  height: 18px;
  width: 22px;
  background: none;
  border: none;
  vertical-align: middle;
  pointer-events: all;
  cursor: pointer;
}
.clear-searchButton .clear-icon {
  color: inherit;
  flex-shrink: 0;
  height: 16px;
  width: 16px;
}

#map.cursor-crosshair .mapboxgl-canvas-container {
  cursor: crosshair !important;
}

#infoMessage {
  font-size: 12px;
  max-width: 200px;
  position: absolute;
  top: 0;
  background-color: #fff;
  border-radius: 3px;
  box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.1);
  margin: 10px;
  padding: 5px;
  overflow: hidden;
  z-index: 1;
}

.linear-progress {
  position: absolute;
  width: 100%;
  z-index: 2;
  bottom: 0;
  height: 4px;
  border-radius: 3px;
  overflow: hidden;
}

.progress {
  height: 100%;
  animation: progress 1.5s infinite;
  background: linear-gradient(to right, #3D5AFE, #3D5AFE);
  transform: translateX(-100%);
}

@keyframes progress {
  0% {
    transform: translateX(-100%);
  }
  50% {
    transform: translateX(0);
  }
  100% {
    transform: translateX(100%);
  }
}
.linear-progress.hide {
  display: none;
}


    
        <html>
  <head>
    <title>Directions Service - Advanced</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta charset="utf-8" />

    <link rel="stylesheet" type="text/css" href="./style.css" />
    <script type="module" src="./index.js"></script>
  </head>
  <body>
    <div id="container">
      <div id="sidebar">
        <div id="innerWrapper">
          <div class="sectionHeader"><span>Travel mode</span></div>
          <div id="travelModeContainer">
            <div class="travelModeSelector">
              <div
                data-travelmode="DRIVING"
                class="travelMode travelMode__selected"
              >
                <button role="radio">
                  <img
                    alt="DRIVING"
                    class="iconTravelMode iconTravelMode__DRIVING"
                    src="https://images.woosmap.com/directions/drive_black.png"
                  />
                  <img
                    alt="DRIVING"
                    class="iconTravelMode iconTravelMode__DRIVING__selected"
                    src="https://images.woosmap.com/directions/drive_selected.png"
                  />
                </button>
              </div>
              <div data-travelmode="WALKING" class="travelMode">
                <button role="radio">
                  <img
                    alt="WALKING"
                    class="iconTravelMode iconTravelMode__WALKING"
                    src="https://images.woosmap.com/directions/walk_black.png"
                  />
                  <img
                    alt="WALKING"
                    class="iconTravelMode iconTravelMode__WALKING__selected"
                    src="https://images.woosmap.com/directions/walk_selected.png"
                  />
                </button>
              </div>
              <div data-travelmode="CYCLING" class="travelMode">
                <button role="radio">
                  <img
                    alt="CYCLING"
                    class="iconTravelMode iconTravelMode__CYCLING"
                    src="https://images.woosmap.com/directions/bicycle_black.png"
                  />
                  <img
                    alt="CYCLING"
                    class="iconTravelMode iconTravelMode__CYCLING__selected"
                    src="https://images.woosmap.com/directions/bicycle_selected.png"
                  />
                </button>
              </div>
            </div>
          </div>
          <div class="sectionHeader">
            <span>Origin</span>
            <div
              aria-label="Set Origin"
              class="addLocation addLocation__origins"
            >
              <svg
                width="10"
                height="14"
                viewBox="0 0 10 14"
                xmlns="http://www.w3.org/2000/svg"
              >
                <path
                  d="M4.21278471,13.2173279 C4.21344203,13.4210003 4.29852035,13.6152829 4.447752,13.7538931 C4.59698368,13.8925033 4.79700821,13.9630315 5.00017514,13.9486763 C5.20260653,13.9603467 5.40103075,13.8888849 5.54953735,13.7508253 C5.69804403,13.6127657 5.78376442,13.4200717 5.786865,13.2173279 C5.786865,11.8485015 6.73642703,10.7280113 7.74168082,9.54202199 C8.85201342,8.23203971 9.9998241,6.87722382 9.9998241,5.04184757 C10.0148585,3.24561164 9.06519921,1.57935055 7.51207057,0.676864524 C5.958942,-0.225621508 4.04105803,-0.225621508 2.48792942,0.676864524 C0.93480081,1.57935055 -0.0148585204,3.24561164 0.000175901018,5.04184757 C0.000175901018,6.87722382 1.14833685,8.23203971 2.25901969,9.54202199 C3.26322269,10.7280113 4.21278471,11.8485015 4.21278471,13.2173279 Z"
                  fill="#787878"
                />
              </svg>
              <div>Set origin</div>
            </div>
          </div>
          <div class="directionsOptions">
            <ol id="origin" class="customCounter customCounter__origin"></ol>
          </div>
          <div class="sectionHeader">
            <span>Destination</span>
            <div
              aria-label="Set Destination"
              class="addLocation addLocation__destinations"
            >
              <svg
                width="10"
                height="14"
                viewBox="0 0 10 14"
                xmlns="http://www.w3.org/2000/svg"
              >
                <path
                  d="M4.21278471,13.2173279 C4.21344203,13.4210003 4.29852035,13.6152829 4.447752,13.7538931 C4.59698368,13.8925033 4.79700821,13.9630315 5.00017514,13.9486763 C5.20260653,13.9603467 5.40103075,13.8888849 5.54953735,13.7508253 C5.69804403,13.6127657 5.78376442,13.4200717 5.786865,13.2173279 C5.786865,11.8485015 6.73642703,10.7280113 7.74168082,9.54202199 C8.85201342,8.23203971 9.9998241,6.87722382 9.9998241,5.04184757 C10.0148585,3.24561164 9.06519921,1.57935055 7.51207057,0.676864524 C5.958942,-0.225621508 4.04105803,-0.225621508 2.48792942,0.676864524 C0.93480081,1.57935055 -0.0148585204,3.24561164 0.000175901018,5.04184757 C0.000175901018,6.87722382 1.14833685,8.23203971 2.25901969,9.54202199 C3.26322269,10.7280113 4.21278471,11.8485015 4.21278471,13.2173279 Z"
                  fill="#787878"
                />
              </svg>
              <div>Set destination</div>
            </div>
          </div>
          <div class="directionsOptions">
            <ol
              id="destination"
              class="customCounter customCounter__destination"
            ></ol>
          </div>
          <div class="sectionHeader">
            <span>Waypoints</span>
            <div
              aria-label="Add Waypoint"
              class="addLocation addLocation__waypoints"
            >
              <svg
                width="19"
                height="14"
                viewBox="0 0 19 14"
                xmlns="http://www.w3.org/2000/svg"
              >
                <path
                  d="M16,5 L16,2 L14,2 L14,5 L11,5 L11,7 L14,7 L14,10 L16,10 L16,7 L19,7 L19,5 L16,5 Z M4.21278471,13.2173279 C4.21344203,13.4210003 4.29852035,13.6152829 4.447752,13.7538931 C4.59698368,13.8925033 4.79700821,13.9630315 5.00017514,13.9486763 C5.20260653,13.9603467 5.40103075,13.8888849 5.54953735,13.7508253 C5.69804403,13.6127657 5.78376442,13.4200717 5.786865,13.2173279 C5.786865,11.8485015 6.73642703,10.7280113 7.74168082,9.54202199 C8.85201342,8.23203971 9.9998241,6.87722382 9.9998241,5.04184757 C10.0148585,3.24561164 9.06519921,1.57935055 7.51207057,0.676864524 C5.958942,-0.225621508 4.04105803,-0.225621508 2.48792942,0.676864524 C0.93480081,1.57935055 -0.0148585204,3.24561164 0.000175901018,5.04184757 C0.000175901018,6.87722382 1.14833685,8.23203971 2.25901969,9.54202199 C3.26322269,10.7280113 4.21278471,11.8485015 4.21278471,13.2173279 Z"
                  fill="#787878"
                />
              </svg>
              <div>Add waypoint</div>
            </div>
          </div>
          <div class="directionsOptions">
            <ol
              id="waypoints"
              class="customCounter customCounter__waypoints"
            ></ol>
          </div>
          <div class="sectionHeader"><span>Options</span></div>
          <div class="directionsOptions">
            <div class="directionsOptions__list">
              <div class="directionsOptions__content">
                <span class="directionsOptions__header">Avoid</span>
                <div class="directionsOptions__input">
                  <input
                    class="avoid"
                    id="avoidHighways"
                    aria-label="Avoid highways"
                    name="avoidParams"
                    type="checkbox"
                    value="highways"
                  />
                  <label for="avoidHighways">Highways</label>
                </div>
                <div class="directionsOptions__input">
                  <input
                    class="avoid"
                    id="avoidTolls"
                    aria-label="Avoid tolls"
                    name="avoidParams"
                    type="checkbox"
                    value="tolls"
                  />
                  <label for="avoidTolls">Tolls</label>
                </div>
                <div class="directionsOptions__input">
                  <input
                    class="avoid"
                    id="avoidFerries"
                    aria-label="Avoid ferries"
                    name="avoidParams"
                    type="checkbox"
                    value="ferries"
                  />
                  <label for="avoidFerries">Ferries</label>
                </div>
              </div>
            </div>
            <div class="directionsOptions__list">
              <div class="directionsOptions__content">
                <span class="directionsOptions__header">Distance units</span>
                <div class="directionsOptions__input">
                  <input
                    id="unitkm"
                    aria-label="Unit METRIC"
                    name="distanceUnits"
                    type="radio"
                    value="METRIC"
                    checked=""
                  />
                  <label for="unitkm">km</label>
                </div>
                <div class="directionsOptions__input">
                  <input
                    id="unitmiles"
                    aria-label="Unit IMPERIAL"
                    name="distanceUnits"
                    type="radio"
                    value="IMPERIAL"
                  />
                  <label for="unitmiles">miles</label>
                </div>
              </div>
            </div>
          </div>
          <div class="directionsOptions">
            <div class="directionsOptions__list">
              <div class="directionsOptions__content">
                <span class="directionsOptions__header">Details</span>
                <div class="directionsOptions__input">
                  <input
                    id="none"
                    aria-label="None"
                    name="details"
                    type="radio"
                    value="None"
                  />
                  <label for="none">None</label>
                </div>
                <div class="directionsOptions__input">
                  <input
                    id="full"
                    aria-label="Full"
                    name="details"
                    type="radio"
                    value="full"
                    checked=""
                  />
                  <label for="full">Full</label>
                </div>
              </div>
            </div>
            <div class="directionsOptions__list">
              <div class="directionsOptions__content">
                <span class="directionsOptions__header">Method</span>
                <div class="directionsOptions__input">
                  <input
                    id="fastest"
                    aria-label="Fastest"
                    name="method"
                    type="radio"
                    value="time"
                    checked=""
                  />
                  <label for="fastest">fastest</label>
                </div>
                <div class="directionsOptions__input">
                  <input
                    id="shortest"
                    aria-label="shortest"
                    name="method"
                    type="radio"
                    value="distance"
                  />
                  <label for="shortest">shortest</label>
                </div>
              </div>
            </div>
          </div>
          <div class="directionsOptions">
            <div class="directionsOptions__list">
              <div class="directionsOptions__content">
                <span class="directionsOptions__header">Departure time</span>
                <div class="directionsOptions__input">
                  <input
                    id="empty"
                    aria-label="Empty"
                    name="departureTime"
                    type="radio"
                    value="empty"
                    checked=""
                  />
                  <label for="empty">Not Define</label>
                </div>
                <div class="directionsOptions__input">
                  <input
                    id="now"
                    aria-label="Now"
                    name="departureTime"
                    type="radio"
                    value="now"
                  />
                  <label for="now">Now</label>
                </div>
                <div class="directionsOptions__input">
                  <input
                    id="datetime"
                    aria-label="Datetime"
                    name="departureTime"
                    type="radio"
                    value="datetime"
                  />
                  <input
                    id="departure-time"
                    name="departure-time"
                    type="datetime-local"
                  />
                </div>
              </div>
            </div>
            <div class="directionsOptions__list">
              <div class="directionsOptions__content">
                <span class="directionsOptions__header">Language</span>
                <div class="directionsOptions__input">
                  <select id="language">
                    <option value="en">English</option>
                    <option value="fr">French</option>
                    <option value="it">Italian</option>
                    <option value="es">Spanish</option>
                    <option value="de">Deutsch</option>
                    <option value="nl">Dutch</option>
                    <option value="ja">Japanese</option>
                    <option value="zh">Chinese</option>
                    <option value="ru">Russian</option>
                  </select>
                </div>
              </div>
            </div>
          </div>
          <div class="directionsOptions">
            <div class="directionsOptions__list">
              <div class="directionsOptions__content">
                <span class="directionsOptions__header">Alternatives</span>
                <div class="directionsOptions__input">
                  <input
                    id="alternativesTrue"
                    aria-label="Yes"
                    name="alternatives"
                    type="radio"
                    value="true"
                    checked=""
                  />
                  <label for="true">Yes</label>
                </div>
                <div class="directionsOptions__input">
                  <input
                    id="alternativesFalse"
                    aria-label="No"
                    name="alternatives"
                    type="radio"
                    value="false"
                  />
                  <label for="false">No</label>
                </div>
              </div>
            </div>
            <div class="directionsOptions__list">
              <div class="directionsOptions__content">
                <span class="directionsOptions__header">Waypoints</span>
                <div class="directionsOptions__input">
                  <input
                    id="optimizeWaypoints"
                    aria-label="Optimize Waypoints"
                    name="optimizeWaypoints"
                    type="checkbox"
                  />
                  <label for="optimizeWaypoints">Optimize</label>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
      <div id="mapContainer">
        <div class="linear-progress hide">
          <div class="progress"></div>
        </div>
        <div id="map"></div>
        <div id="infoMessage">Drag markers to update route</div>
        <div class="tableContainer"></div>
      </div>
    </div>


    <script
      src="https://sdk.woosmap.com/map/map.js?key=woos-48c80350-88aa-333e-835a-07f4b658a9a4&callback=initMap"
      defer
    ></script>
  </body>
</html>

    

Running the Sample Locally

Before you can run this sample on your local machine, you need to have Git and Node.js installed. If they’re not already installed, follow these instructions to get them set up.

Once you have Git and Node.js installed, you can run the sample by following these steps:

  1. Clone the repository and navigate to the directory of the sample.

  2. Install the necessary dependencies.

  3. Start running the sample.

Here are the commands you can use in your terminal to do this:

Shell
        git clone -b sample/directions-advanced https://github.com/woosmap/js-samples.git
cd js-samples
npm i
npm start

    

You can experiment with other samples by switching to any branch that starts with the pattern sample/SAMPLE_NAME.

Shell
        git checkout sample/SAMPLE_NAME
npm i
npm start

    
Was this article helpful?
Have more questions? Submit a request