Distance Matrix - Advanced

Using the DistanceService to calculate distances and durations between multiple origins and destinations

  1. Example
  2. Running the Sample Locally

Example

Distance Matrix - Advanced
        let map: woosmap.map.Map;
let bounds: woosmap.map.LatLngBounds;
let distanceService: woosmap.map.DistanceService;
let distanceRequest: woosmap.map.distance.DistanceMatrixRequest;
let markersArray: woosmap.map.Marker[] = [];
let originsList: HTMLElement;
let destinationsList: HTMLElement;
const origins: woosmap.map.LatLngLiteral[] = [];
const destinations: woosmap.map.LatLngLiteral[] = [];
let line: woosmap.map.Polyline | null = null;

function createMarker(
  position: woosmap.map.LatLngLiteral,
  label: string,
  url: string,
): woosmap.map.Marker {
  return new woosmap.map.Marker({
    map,
    position,
    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 displayMatrixMarkers(
  origins: woosmap.map.LatLngLiteral[],
  destinations: woosmap.map.LatLngLiteral[],
): void {
  clearMarkers();
  origins.forEach((origin, index) => {
    bounds.extend(origin);
    markersArray.push(
      createMarker(
        origin,
        (index + 1).toString(),
        "https://images.woosmap.com/marker-blue.svg",
      ),
    );
  });

  destinations.forEach((destination, index) => {
    bounds.extend(destination);
    markersArray.push(
      createMarker(
        destination,
        (index + 1).toString(),
        "https://images.woosmap.com/marker-red.svg",
      ),
    );
  });

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

function createDefaultRequest(): woosmap.map.distance.DistanceMatrixRequest {
  origins.push({ lat: 51.6511, lng: -0.1615 }, { lat: 51.4269, lng: -0.0955 });
  destinations.push(
    { lat: 51.4855, lng: -0.3179 },
    { lat: 51.5146, lng: -0.0212 },
  );

  for (const origin of origins) {
    addLatLngToList(originsList, origin);
  }
  for (const destination of destinations) {
    addLatLngToList(destinationsList, destination);
  }

  return {
    origins,
    destinations,
    travelMode: woosmap.map.TravelMode.DRIVING,
    unitSystem: woosmap.map.UnitSystem.METRIC,
    avoidHighways: false,
    avoidTolls: false,
    elements: "duration_distance",
  };
}

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 === originsList) {
      origins.splice(origins.indexOf(location), 1);
    } else {
      destinations.splice(destinations.indexOf(location), 1);
    }
    calculateDistances();
  });
  locationElement.appendChild(removeButton);
  element.appendChild(locationElement);
}

function handleResponse(
  response: woosmap.map.distance.DistanceMatrixResponse,
): void {
  displayMatrixMarkers(
    distanceRequest.origins as woosmap.map.LatLngLiteral[],
    distanceRequest.destinations as woosmap.map.LatLngLiteral[],
  );
  createTable(response);
  displayOrHideError("");
  toggleProgress();
}

function createTable(response: woosmap.map.distance.DistanceMatrixResponse) {
  let table =
    "<table><thead><tr><th>From</th><th>To</th><th>Time</th><th>Distance</th></tr></thead><tbody>";

  response.rows.forEach((row, fromIndex) => {
    row.elements.forEach((element, toIndex) => {
      if (element.status === "OK") {
        const time = element.duration ? element.duration.text : "N/A";
        const start = `${origins[fromIndex].lat},${origins[fromIndex].lng}`;
        const end = `${destinations[toIndex].lat},${destinations[toIndex].lng}`;
        const distance = element.distance ? element.distance.text : "N/A";
        table += `<tr data-start=${start} data-end=${end}><td><span>${fromIndex + 1}</span></td><td><span>${toIndex + 1}</span></td><td>${time}</td><td>${distance}</td></tr>`;
      }
    });
  });

  table += "</tbody></table>";

  const tableContainer = document.querySelector(
    ".tableContainer",
  ) as HTMLElement;
  tableContainer.innerHTML = table;
  tableContainer.style.display = "block";
  registerLineHighlight(tableContainer);
}

function registerLineHighlight(tableContainer: HTMLElement) {
  const tableRows = tableContainer.querySelectorAll("tr");
  tableRows.forEach((row) => {
    row.addEventListener("mouseover", () => {
      const start = row.dataset.start?.split(",").map(Number);
      const end = row.dataset.end?.split(",").map(Number);
      if (line) {
        line.setMap(null);
      }
      if (!start || !end) {
        return;
      }
      line = new woosmap.map.Polyline({
        path: [
          { lat: start[0], lng: start[1] },
          { lat: end[0], lng: end[1] },
        ],
        geodesic: true,
        strokeColor: "#252525",
        strokeOpacity: 1.0,
        strokeWeight: 2,
      });
      line.setMap(map);
    });
    row.addEventListener("mouseout", () => {
      if (line) {
        line.setMap(null);
        line = null;
      }
    });
  });
}

function displayOrHideError(error: string) {
  const errorElement = document.getElementById("errorMessage") as HTMLElement;
  if (error === "") {
    errorElement.style.display = "none";
  } else {
    errorElement.innerHTML = error;
    errorElement.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 calculateDistances(): void {
  toggleProgress();
  distanceService
    .getDistanceMatrix(distanceRequest)
    .then(handleResponse)
    .catch((error) => {
      console.error("Error calculating distances:", error);
      displayOrHideError(error);
      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);
      distanceRequest.travelMode = (el as HTMLDivElement).dataset.travelmode as
        | woosmap.map.TravelMode
        | undefined;
      calculateDistances();
    }),
  );
}

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;
      distanceRequest.avoidHighways = avoidHighways.checked;
      distanceRequest.avoidTolls = avoidTolls.checked;
      distanceRequest.avoidFerries = avoidFerries.checked;
      calculateDistances();
    }),
  );
}

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

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

function updateElements(): void {
  document.querySelectorAll('input[name="elements"]').forEach((el) => {
    el.addEventListener("change", () => {
      distanceRequest.elements = (el as HTMLInputElement).value as
        | "duration_distance"
        | "distance"
        | "duration"
        | undefined;
      calculateDistances();
    });
  });
}

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

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.disabled = true;
  document.querySelectorAll('input[name="departureTime"]').forEach((el) => {
    el.addEventListener("change", () => {
      const selectedOption = (el as HTMLInputElement).value;
      switch (selectedOption) {
        case "empty":
          delete distanceRequest.departureTime;
          departureTimeElement.disabled = true;
          break;
        case "now":
          distanceRequest.departureTime = "now";
          departureTimeElement.disabled = true;
          break;
        case "datetime":
          if (departureTimeElement.value) {
            const newDate = new Date(departureTimeElement.value);
            distanceRequest.departureTime = isValidDate(newDate)
              ? newDate
              : undefined;
          } else {
            distanceRequest.departureTime = undefined;
          }
          departureTimeElement.disabled = false;
          break;
      }
      calculateDistances();
    });
  });

  departureTimeElement.addEventListener("change", () => {
    if (datetimeRadioButton) {
      datetimeRadioButton.checked = true;
    }
    const newDate = new Date(departureTimeElement.value);
    distanceRequest.departureTime = isValidDate(newDate) ? newDate : undefined;
    calculateDistances();
  });
}

function registerAddButton(
  selector: string,
  list: HTMLElement,
  locations: 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;
      locations.push(location);
      addLatLngToList(list, location);
      calculateDistances();
    });
  });
}

function initUI(): void {
  updateTravelModeButtons();
  updateAvoidance();
  updateDistanceUnit();
  updateMethod();
  updateElements();
  updateDepartureTime();
  updateLanguage();
  originsList = document.getElementById("origins") as HTMLElement;
  destinationsList = document.getElementById("destinations") as HTMLElement;
  registerAddButton(
    ".addLocation__destinations",
    destinationsList,
    destinations,
  );
  registerAddButton(".addLocation__origins", originsList, origins);
}

function initMap(): void {
  map = new woosmap.map.Map(document.getElementById("map") as HTMLElement, {
    center: { lat: 51.4855, lng: -0.3179 },
    zoom: 6,
  });
  distanceService = new woosmap.map.DistanceService();
  bounds = new woosmap.map.LatLngBounds();
  initUI();
  distanceRequest = createDefaultRequest();
  calculateDistances();
}

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

    
        let map;
let bounds;
let distanceService;
let distanceRequest;
let markersArray = [];
let originsList;
let destinationsList;
const origins = [];
const destinations = [];
let line = null;

function createMarker(position, label, url) {
  return new woosmap.map.Marker({
    map,
    position,
    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 displayMatrixMarkers(origins, destinations) {
  clearMarkers();
  origins.forEach((origin, index) => {
    bounds.extend(origin);
    markersArray.push(
      createMarker(
        origin,
        (index + 1).toString(),
        "https://images.woosmap.com/marker-blue.svg",
      ),
    );
  });
  destinations.forEach((destination, index) => {
    bounds.extend(destination);
    markersArray.push(
      createMarker(
        destination,
        (index + 1).toString(),
        "https://images.woosmap.com/marker-red.svg",
      ),
    );
  });
  map.fitBounds(bounds, { top: 70, bottom: 40, left: 50, right: 50 }, true);
}

function createDefaultRequest() {
  origins.push({ lat: 51.6511, lng: -0.1615 }, { lat: 51.4269, lng: -0.0955 });
  destinations.push(
    { lat: 51.4855, lng: -0.3179 },
    { lat: 51.5146, lng: -0.0212 },
  );

  for (const origin of origins) {
    addLatLngToList(originsList, origin);
  }

  for (const destination of destinations) {
    addLatLngToList(destinationsList, destination);
  }
  return {
    origins,
    destinations,
    travelMode: woosmap.map.TravelMode.DRIVING,
    unitSystem: woosmap.map.UnitSystem.METRIC,
    avoidHighways: false,
    avoidTolls: false,
    elements: "duration_distance",
  };
}

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 === originsList) {
      origins.splice(origins.indexOf(location), 1);
    } else {
      destinations.splice(destinations.indexOf(location), 1);
    }

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

function handleResponse(response) {
  displayMatrixMarkers(distanceRequest.origins, distanceRequest.destinations);
  createTable(response);
  displayOrHideError("");
  toggleProgress();
}

function createTable(response) {
  let table =
    "<table><thead><tr><th>From</th><th>To</th><th>Time</th><th>Distance</th></tr></thead><tbody>";

  response.rows.forEach((row, fromIndex) => {
    row.elements.forEach((element, toIndex) => {
      if (element.status === "OK") {
        const time = element.duration ? element.duration.text : "N/A";
        const start = `${origins[fromIndex].lat},${origins[fromIndex].lng}`;
        const end = `${destinations[toIndex].lat},${destinations[toIndex].lng}`;
        const distance = element.distance ? element.distance.text : "N/A";

        table += `<tr data-start=${start} data-end=${end}><td><span>${fromIndex + 1}</span></td><td><span>${toIndex + 1}</span></td><td>${time}</td><td>${distance}</td></tr>`;
      }
    });
  });
  table += "</tbody></table>";

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

  tableContainer.innerHTML = table;
  tableContainer.style.display = "block";
  registerLineHighlight(tableContainer);
}

function registerLineHighlight(tableContainer) {
  const tableRows = tableContainer.querySelectorAll("tr");

  tableRows.forEach((row) => {
    row.addEventListener("mouseover", () => {
      const start = row.dataset.start?.split(",").map(Number);
      const end = row.dataset.end?.split(",").map(Number);

      if (line) {
        line.setMap(null);
      }

      if (!start || !end) {
        return;
      }

      line = new woosmap.map.Polyline({
        path: [
          { lat: start[0], lng: start[1] },
          { lat: end[0], lng: end[1] },
        ],
        geodesic: true,
        strokeColor: "#252525",
        strokeOpacity: 1.0,
        strokeWeight: 2,
      });
      line.setMap(map);
    });
    row.addEventListener("mouseout", () => {
      if (line) {
        line.setMap(null);
        line = null;
      }
    });
  });
}

function displayOrHideError(error) {
  const errorElement = document.getElementById("errorMessage");

  if (error === "") {
    errorElement.style.display = "none";
  } else {
    errorElement.innerHTML = error;
    errorElement.style.display = "block";

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

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

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

function calculateDistances() {
  toggleProgress();
  distanceService
    .getDistanceMatrix(distanceRequest)
    .then(handleResponse)
    .catch((error) => {
      console.error("Error calculating distances:", error);
      displayOrHideError(error);
      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);
      distanceRequest.travelMode = el.dataset.travelmode;
      calculateDistances();
    }),
  );
}

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");

      distanceRequest.avoidHighways = avoidHighways.checked;
      distanceRequest.avoidTolls = avoidTolls.checked;
      distanceRequest.avoidFerries = avoidFerries.checked;
      calculateDistances();
    }),
  );
}

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

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

function updateElements() {
  document.querySelectorAll('input[name="elements"]').forEach((el) => {
    el.addEventListener("change", () => {
      distanceRequest.elements = el.value;
      calculateDistances();
    });
  });
}

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

  languageSelect.addEventListener("change", () => {
    distanceRequest.language = languageSelect.value;
    calculateDistances();
  });
}

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.disabled = true;
  document.querySelectorAll('input[name="departureTime"]').forEach((el) => {
    el.addEventListener("change", () => {
      const selectedOption = el.value;

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

            distanceRequest.departureTime = isValidDate(newDate)
              ? newDate
              : undefined;
          } else {
            distanceRequest.departureTime = undefined;
          }

          departureTimeElement.disabled = false;
          break;
      }

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

    const newDate = new Date(departureTimeElement.value);

    distanceRequest.departureTime = isValidDate(newDate) ? newDate : undefined;
    calculateDistances();
  });
}

function registerAddButton(selector, list, locations) {
  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;

      locations.push(location);
      addLatLngToList(list, location);
      calculateDistances();
    });
  });
}

function initUI() {
  updateTravelModeButtons();
  updateAvoidance();
  updateDistanceUnit();
  updateMethod();
  updateElements();
  updateDepartureTime();
  updateLanguage();
  originsList = document.getElementById("origins");
  destinationsList = document.getElementById("destinations");
  registerAddButton(
    ".addLocation__destinations",
    destinationsList,
    destinations,
  );
  registerAddButton(".addLocation__origins", originsList, origins);
}

function initMap() {
  map = new woosmap.map.Map(document.getElementById("map"), {
    center: { lat: 51.4855, lng: -0.3179 },
    zoom: 6,
  });
  distanceService = new woosmap.map.DistanceService();
  bounds = new woosmap.map.LatLngBounds();
  initUI();
  distanceRequest = createDefaultRequest();
  calculateDistances();
}

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%;
  min-height: 150px;
  overflow-y: auto;
  font-size: 13px;
}
.tableContainer table {
  border-collapse: collapse;
  width: 100%;
}
.tableContainer tr:nth-child(even) {
  background-color: #f5f5f5;
}
.tableContainer tr:nth-child(odd) {
  background-color: #ffffff;
}
.tableContainer thead th {
  position: sticky;
  top: 0;
  z-index: 1;
}
.tableContainer th {
  background: #eee;
  font-weight: bold;
}
.tableContainer td {
  white-space: nowrap;
}
.tableContainer th, .tableContainer td {
  text-align: center;
  padding: 8px 16px;
}
.tableContainer tr td:first-child span, .tableContainer tr td:nth-child(2) span {
  background-repeat: no-repeat;
  background-position: center;
  background-size: contain;
  color: white;
  text-align: center;
  display: inline-block;
  width: 20px;
  height: 20px;
  font-size: 80%;
  font-weight: 600;
  line-height: 14px;
}
.tableContainer tr td:first-child span {
  background-image: url("https://images.woosmap.com/marker-blue.svg");
}
.tableContainer tr td:first-child {
  position: relative;
}
.tableContainer tr td:first-child:after {
  position: absolute;
  right: 0;
  color: #222;
  content: "→";
  top: 12px;
}
.tableContainer tr td:nth-child(2) span {
  background-image: url("https://images.woosmap.com/marker-red.svg");
}

#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 {
  content: counter(step-counter);
  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__destinations li::before {
  background-image: url(https://images.woosmap.com/marker-red.svg);
}

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

.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;
}

#errorMessage {
  color: #ff1744;
  font-size: 12px;
  max-width: 200px;
  display: none;
  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>Distance Matrix - 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>Origins</span>
            <div
              aria-label="Add Origin"
              class="addLocation addLocation__origins"
            >
              <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 origin</div>
            </div>
          </div>
          <div class="directionsOptions">
            <ol id="origins" class="customCounter customCounter__origins"></ol>
          </div>
          <div class="sectionHeader">
            <span>Destinations</span>
            <div
              aria-label="Add Destination"
              class="addLocation addLocation__destinations"
            >
              <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 destination</div>
            </div>
          </div>
          <div class="directionsOptions">
            <ol
              id="destinations"
              class="customCounter customCounter__destinations"
            ></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">Elements</span>
                <div class="directionsOptions__input">
                  <input
                    id="duration"
                    aria-label="Duration"
                    name="elements"
                    type="radio"
                    value="duration"
                  />
                  <label for="duration">Duration</label>
                </div>
                <div class="directionsOptions__input">
                  <input
                    id="distance"
                    aria-label="Distance"
                    name="elements"
                    type="radio"
                    value="distance"
                  />
                  <label for="distance">Distance</label>
                </div>
                <div class="directionsOptions__input">
                  <input
                    id="durationdistance"
                    aria-label="Duration & Distance"
                    name="elements"
                    type="radio"
                    value="duration_distance"
                    checked=""
                  />
                  <label for="durationdistance">All</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>
      </div>
      <div id="mapContainer">
        <div class="linear-progress hide">
          <div class="progress"></div>
        </div>
        <div id="map"></div>
        <div id="errorMessage"></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/distance-matrix-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