Transit Service - Advanced

Using the Transit Service to get the direction by public transport between an origin and a destination.

  1. Example
  2. Running the Sample Locally

Example

Transit Service - Advanced
        let map: woosmap.map.Map;
let transitService: woosmap.map.TransitService;
let transitRequest: woosmap.map.transit.TransitRouteRequest;
let markersArray: woosmap.map.Marker[] = [];
let originContainer: HTMLElement;
let destinationContainer: HTMLElement;
let transitRenderer: woosmap.map.TransitRenderer;

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 displayTransitMarkers(): void {
  clearMarkers();
  const originMarker = createMarker(
    transitRequest.origin,
    "O",
    "https://images.woosmap.com/marker-blue.svg",
  );
  originMarker.addListener("dragend", () => {
    transitRequest.origin = originMarker.getPosition();
    setLatLngToContainer(originContainer, originMarker.getPosition().toJSON());
    calculateTransit();
  });
  const destinationMarker = createMarker(
    transitRequest.destination,
    "D",
    "https://images.woosmap.com/marker-red.svg",
  );
  destinationMarker.addListener("dragend", () => {
    transitRequest.destination = destinationMarker.getPosition();
    setLatLngToContainer(
      destinationContainer,
      destinationMarker.getPosition().toJSON(),
    );
    calculateTransit();
  });
  markersArray.push(originMarker);
  markersArray.push(destinationMarker);
}

function createDefaultRequest(): woosmap.map.transit.TransitRouteRequest {
  const origin = { lat: 51.6511, lng: -0.1615 };
  const destination = { lat: 51.5146, lng: -0.0212 };
  setLatLngToContainer(originContainer, origin);
  setLatLngToContainer(destinationContainer, destination);

  return {
    origin,
    destination,
  };
}

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.transit.TransitRouteResponse) {
  const directionTripElements = response.routes.map(
    (route: woosmap.map.transit.TransitRoute, index: number) => {
      const distanceTotal = route.legs.reduce(
        (total, leg) => total + leg.distance,
        0,
      );
      const directionTrip = document.createElement("div");
      directionTrip.className = "directionTrip";
      if (index === 0) {
        directionTrip.classList.add("directionTrip__selected");
      }

      const startName =
        route.legs.find(
          (leg: woosmap.map.transit.TransitLeg) =>
            leg.start_location && leg.start_location.name !== null,
        )?.start_location.name || "Walking";

      const travelModeIconSrc =
        startName === "Walking"
          ? "https://images.woosmap.com/directions/walk_black.png"
          : "https://images.woosmap.com/directions/transit_black.png";

      directionTrip.innerHTML = `
            <img class="directionTrip__travelModeIcon" src="${travelModeIconSrc}">
            <div class="directionTrip__description">
                <div class="directionTrip__numbers">
                    <div class="directionTrip__duration">${formatTime(route.duration)}</div>
                    <div class="directionTrip__distance">${formatDistance(distanceTotal)}</div>
                </div>
                <div class="directionTrip__title">through ${startName}</div>
                <div class="directionTrip__summary">${formatTime(route.duration)}</div>
                <div class="directionTrip__detailsMsg"></div>
            </div>
        `;

      directionTrip.addEventListener("click", () => {
        selectCorrectRoute(index);
        transitRenderer.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 displayTransitRoute(routes: woosmap.map.transit.TransitRoute[]) {
  transitRenderer.setRoutes(routes);
}

function calculateTransit(): void {
  toggleProgress();

  transitService
    .route(transitRequest)
    .then(handleResponse)
    .catch((error) => {
      console.error("Error calculating transit route:", error);
      displayOrHideError(error);
      toggleProgress();
    });
}

function handleResponse(response: woosmap.map.transit.TransitRouteResponse) {
  displayTransitRoute(response.routes);
  displayTransitMarkers();
  createRoutesTable(response);
  displayOrHideError("");
  toggleProgress();
}

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

function updateTime(timeType: "departureTime" | "arrivalTime"): void {
  const timeElement = document.getElementById(
    `${timeType}-time`,
  ) as HTMLInputElement;
  const datetimeRadioButton = document.getElementById(
    "datetime",
  ) as HTMLInputElement;
  const otherTimeType =
    timeType === "departureTime" ? "arrivalTime" : "departureTime";
  const otherTimeElement = document.getElementById(
    `${otherTimeType}-time`,
  ) as HTMLInputElement;

  if (!timeElement || !otherTimeElement) {
    return;
  }
  timeElement.min = new Date().toISOString().slice(0, 16);
  timeElement.disabled = true;
  document.querySelectorAll(`input[name="${timeType}"]`).forEach((el) => {
    el.addEventListener("change", () => {
      const selectedOption = (el as HTMLInputElement).value;
      switch (selectedOption) {
        case "empty":
          delete transitRequest[timeType];
          timeElement.disabled = true;
          break;
        case "now":
        case "datetime":
          delete transitRequest[otherTimeType];
          (
            document.querySelector(
              `input[name="${otherTimeType}"][value="empty"]`,
            ) as HTMLInputElement
          ).checked = true;
          otherTimeElement.value = "";
          otherTimeElement.disabled = true;
          if (timeElement.value) {
            const newDate = new Date(timeElement.value);
            transitRequest[timeType] = isValidDate(newDate)
              ? newDate.getTime().toString()
              : undefined;
          } else {
            transitRequest[timeType] = undefined;
          }
          timeElement.disabled = false;
          break;
      }
      calculateTransit();
    });
  });

  timeElement.addEventListener("change", () => {
    if (datetimeRadioButton) {
      datetimeRadioButton.checked = true;
    }
    const newDate = new Date(timeElement.value);
    transitRequest[timeType] = isValidDate(newDate)
      ? newDate.getTime().toString()
      : undefined;
    calculateTransit();
  });
}

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 (element === originContainer) {
        transitRequest.origin = location;
      }
      if (element === destinationContainer) {
        transitRequest.destination = location;
      }
      setLatLngToContainer(element, location);
      calculateTransit();
    });
  });
}

const includedModes: string[] = [];
const excludedModes: string[] = [];
const modes = {
  train: [
    "highSpeedTrain",
    "intercityTrain",
    "interRegionalTrain",
    "regionalTrain",
    "cityTrain",
    "subway",
    "lightRail",
    "monorail",
    "inclined",
  ],
  bus: ["bus", "privateBus", "busRapid"],
  ferry: ["ferry"],
  aerial: ["aerial", "flight", "spaceship"],
};

function updateTransitOptions() {
  const checkboxes = document.querySelectorAll('.modesChk[type="checkbox"]');
  const includeRadio = document.querySelector(
    "#includeModes",
  ) as HTMLInputElement;
  const excludeRadio = document.querySelector(
    "#excludeModes",
  ) as HTMLInputElement;

  function updateMode() {
    includedModes.length = 0;
    excludedModes.length = 0;

    Object.keys(modes).forEach((mode) => {
      const checkbox = document.querySelector(
        `#mode${mode.charAt(0).toUpperCase() + mode.slice(1)}`,
      ) as HTMLInputElement;
      if (checkbox.checked) {
        modes[mode].forEach((subMode) => {
          if (includeRadio.checked) {
            includedModes.push(subMode);
          } else if (excludeRadio.checked) {
            excludedModes.push("-" + subMode);
          }
        });
      }
    });

    if (includedModes.length === 0 && excludedModes.length === 0) {
      delete transitRequest.modes;
    } else {
      transitRequest.modes = includeRadio.checked
        ? includedModes
        : excludedModes;
    }
    calculateTransit();
  }

  checkboxes.forEach((checkbox) => {
    checkbox.addEventListener("change", updateMode);
  });

  includeRadio.addEventListener("change", updateMode);
  excludeRadio.addEventListener("change", updateMode);
}

function initUI(): void {
  updateTime("departureTime");
  updateTime("arrivalTime");
  updateTransitOptions();
  registerAddButton(
    ".addLocation__destinations",
    destinationContainer,
    transitRequest.destination as woosmap.map.LatLngLiteral,
  );
  registerAddButton(
    ".addLocation__origins",
    originContainer,
    transitRequest.origin 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,
  });
  transitService = new woosmap.map.TransitService();
  transitRenderer = new woosmap.map.TransitRenderer({});
  transitRenderer.setMap(map);
  originContainer = document.getElementById("origin") as HTMLElement;
  destinationContainer = document.getElementById("destination") as HTMLElement;
  transitRequest = createDefaultRequest();
  initUI();
  calculateTransit();
}

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

    
        let map;
let transitService;
let transitRequest;
let markersArray = [];
let originContainer;
let destinationContainer;
let transitRenderer;

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 displayTransitMarkers() {
  clearMarkers();

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

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

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

  destinationMarker.addListener("dragend", () => {
    transitRequest.destination = destinationMarker.getPosition();
    setLatLngToContainer(
      destinationContainer,
      destinationMarker.getPosition().toJSON(),
    );
    calculateTransit();
  });
  markersArray.push(originMarker);
  markersArray.push(destinationMarker);
}

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

  setLatLngToContainer(originContainer, origin);
  setLatLngToContainer(destinationContainer, destination);
  return {
    origin,
    destination,
  };
}

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 distanceTotal = route.legs.reduce(
      (total, leg) => total + leg.distance,
      0,
    );
    const directionTrip = document.createElement("div");

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

    const startName =
      route.legs.find(
        (leg) => leg.start_location && leg.start_location.name !== null,
      )?.start_location.name || "Walking";
    const travelModeIconSrc =
      startName === "Walking"
        ? "https://images.woosmap.com/directions/walk_black.png"
        : "https://images.woosmap.com/directions/transit_black.png";

    directionTrip.innerHTML = `
            <img class="directionTrip__travelModeIcon" src="${travelModeIconSrc}">
            <div class="directionTrip__description">
                <div class="directionTrip__numbers">
                    <div class="directionTrip__duration">${formatTime(route.duration)}</div>
                    <div class="directionTrip__distance">${formatDistance(distanceTotal)}</div>
                </div>
                <div class="directionTrip__title">through ${startName}</div>
                <div class="directionTrip__summary">${formatTime(route.duration)}</div>
                <div class="directionTrip__detailsMsg"></div>
            </div>
        `;
    directionTrip.addEventListener("click", () => {
      selectCorrectRoute(index);
      transitRenderer.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 displayTransitRoute(routes) {
  transitRenderer.setRoutes(routes);
}

function calculateTransit() {
  toggleProgress();
  transitService
    .route(transitRequest)
    .then(handleResponse)
    .catch((error) => {
      console.error("Error calculating transit route:", error);
      displayOrHideError(error);
      toggleProgress();
    });
}

function handleResponse(response) {
  displayTransitRoute(response.routes);
  displayTransitMarkers();
  createRoutesTable(response);
  displayOrHideError("");
  toggleProgress();
}

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

function updateTime(timeType) {
  const timeElement = document.getElementById(`${timeType}-time`);
  const datetimeRadioButton = document.getElementById("datetime");
  const otherTimeType =
    timeType === "departureTime" ? "arrivalTime" : "departureTime";
  const otherTimeElement = document.getElementById(`${otherTimeType}-time`);

  if (!timeElement || !otherTimeElement) {
    return;
  }

  timeElement.min = new Date().toISOString().slice(0, 16);
  timeElement.disabled = true;
  document.querySelectorAll(`input[name="${timeType}"]`).forEach((el) => {
    el.addEventListener("change", () => {
      const selectedOption = el.value;

      switch (selectedOption) {
        case "empty":
          delete transitRequest[timeType];
          timeElement.disabled = true;
          break;
        case "now":
        case "datetime":
          delete transitRequest[otherTimeType];
          document.querySelector(
            `input[name="${otherTimeType}"][value="empty"]`,
          ).checked = true;
          otherTimeElement.value = "";
          otherTimeElement.disabled = true;
          if (timeElement.value) {
            const newDate = new Date(timeElement.value);

            transitRequest[timeType] = isValidDate(newDate)
              ? newDate.getTime().toString()
              : undefined;
          } else {
            transitRequest[timeType] = undefined;
          }

          timeElement.disabled = false;
          break;
      }

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

    const newDate = new Date(timeElement.value);

    transitRequest[timeType] = isValidDate(newDate)
      ? newDate.getTime().toString()
      : undefined;
    calculateTransit();
  });
}

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 (element === originContainer) {
        transitRequest.origin = location;
      }

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

      setLatLngToContainer(element, location);
      calculateTransit();
    });
  });
}

const includedModes = [];
const excludedModes = [];
const modes = {
  train: [
    "highSpeedTrain",
    "intercityTrain",
    "interRegionalTrain",
    "regionalTrain",
    "cityTrain",
    "subway",
    "lightRail",
    "monorail",
    "inclined",
  ],
  bus: ["bus", "privateBus", "busRapid"],
  ferry: ["ferry"],
  aerial: ["aerial", "flight", "spaceship"],
};

function updateTransitOptions() {
  const checkboxes = document.querySelectorAll('.modesChk[type="checkbox"]');
  const includeRadio = document.querySelector("#includeModes");
  const excludeRadio = document.querySelector("#excludeModes");

  function updateMode() {
    includedModes.length = 0;
    excludedModes.length = 0;
    Object.keys(modes).forEach((mode) => {
      const checkbox = document.querySelector(
        `#mode${mode.charAt(0).toUpperCase() + mode.slice(1)}`,
      );

      if (checkbox.checked) {
        modes[mode].forEach((subMode) => {
          if (includeRadio.checked) {
            includedModes.push(subMode);
          } else if (excludeRadio.checked) {
            excludedModes.push("-" + subMode);
          }
        });
      }
    });
    if (includedModes.length === 0 && excludedModes.length === 0) {
      delete transitRequest.modes;
    } else {
      transitRequest.modes = includeRadio.checked
        ? includedModes
        : excludedModes;
    }

    calculateTransit();
  }

  checkboxes.forEach((checkbox) => {
    checkbox.addEventListener("change", updateMode);
  });
  includeRadio.addEventListener("change", updateMode);
  excludeRadio.addEventListener("change", updateMode);
}

function initUI() {
  updateTime("departureTime");
  updateTime("arrivalTime");
  updateTransitOptions();
  registerAddButton(
    ".addLocation__destinations",
    destinationContainer,
    transitRequest.destination,
  );
  registerAddButton(
    ".addLocation__origins",
    originContainer,
    transitRequest.origin,
  );
}

function initMap() {
  map = new woosmap.map.Map(document.getElementById("map"), {
    center: { lat: 51.5074, lng: -0.1478 },
    zoom: 10,
  });
  transitService = new woosmap.map.TransitService();
  transitRenderer = new woosmap.map.TransitRenderer({});
  transitRenderer.setMap(map);
  originContainer = document.getElementById("origin");
  destinationContainer = document.getElementById("destination");
  transitRequest = createDefaultRequest();
  initUI();
  calculateTransit();
}

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

.transitOptions {
  padding: 0;
  margin: 0;
  list-style: none;
  height: 100%;
  background: #fff;
  display: flex;
  font-size: 13px;
}
.transitOptions__list {
  width: 100%;
  height: 100%;
}
.transitOptions__header {
  font-weight: 600;
  line-height: 24px;
  display: flex;
}
.transitOptions__input {
  height: 24px;
  display: flex;
  align-items: baseline;
}
.transitOptions__content {
  padding: 10px 0;
}
.transitOptions__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";
}

.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>Transit 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>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="transitOptions">
            <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="transitOptions">
            <ol
              id="destination"
              class="customCounter customCounter__destination"
            ></ol>
          </div>
          <div class="sectionHeader"><span>Options</span></div>
          <div class="transitOptions">
            <div class="transitOptions__list">
              <div class="transitOptions__content">
                <span class="transitOptions__header">Departure time</span>
                <div class="transitOptions__input">
                  <input
                    id="empty"
                    aria-label="Empty"
                    name="departureTime"
                    type="radio"
                    value="empty"
                    checked=""
                  />
                  <label for="empty">Not Define (now)</label>
                </div>
                <div class="transitOptions__input">
                  <input
                    id="datetime"
                    aria-label="Datetime"
                    name="departureTime"
                    type="radio"
                    value="datetime"
                  />
                  <input
                    id="departureTime-time"
                    name="departure-time"
                    type="datetime-local"
                  />
                </div>
              </div>
            </div>
          </div>
          <div class="transitOptions">
            <div class="transitOptions__list">
              <div class="transitOptions__content">
                <span class="transitOptions__header">Arrival time</span>
                <div class="transitOptions__input">
                  <input
                    id="empty"
                    aria-label="Empty"
                    name="arrivalTime"
                    type="radio"
                    value="empty"
                    checked=""
                  />
                  <label for="empty">Not Define</label>
                </div>
                <div class="transitOptions__input">
                  <input
                    id="datetime"
                    aria-label="Datetime"
                    name="arrivalTime"
                    type="radio"
                    value="datetime"
                  />
                  <input
                    id="arrivalTime-time"
                    name="arrival-time"
                    type="datetime-local"
                  />
                </div>
              </div>
            </div>
          </div>

          <div class="transitOptions">
            <div class="transitOptions__list">
              <div class="transitOptions__content">
                <span class="transitOptions__header">Specify modes</span>
                <div class="transitOptions__input">
                  <input
                    id="includeModes"
                    aria-label="include"
                    name="modeOption"
                    type="radio"
                    value="include"
                    checked=""
                  />
                  <label for="empty">Include only</label>
                </div>
                <div class="transitOptions__input">
                  <input
                    id="excludeModes"
                    aria-label="exclude"
                    name="modeOption"
                    type="radio"
                    value="exclude"
                  />
                  <label for="empty">Exclude</label>
                </div>
                <div class="transitOptions__input">
                  <input
                    class="modesChk"
                    id="modeTrain"
                    aria-label="Mode Train"
                    name="train"
                    type="checkbox"
                  />
                  <label for="modeTrain">Train</label>
                </div>
                <div class="transitOptions__input">
                  <input
                    class="modesChk"
                    id="modeBus"
                    aria-label="Mode Bus"
                    name="bus"
                    type="checkbox"
                  />
                  <label for="modeBus">Bus</label>
                </div>
                <div class="transitOptions__input">
                  <input
                    class="modesChk"
                    id="modeFerry"
                    aria-label="ferry"
                    name="modeFerry"
                    type="checkbox"
                  />
                  <label for="modeFerry">Ferry</label>
                </div>
                <div class="transitOptions__input">
                  <input
                    class="modesChk"
                    id="modeAerial"
                    aria-label="aerial"
                    name="modeAerial"
                    type="checkbox"
                  />
                  <label for="modeAerial">Aerial</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/transit-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