How to convert a what3words address to a street address

We will convert a users input (with Auto Suggest) into a street address and allow them to choose the correct one.

  1. Introduction
  2. Prerequisites
  3. Setting up the HTML page
  4. Auto Suggest
  5. Converting a what3words address into a street address
  6. Displaying it all on a Woosmap Map
  7. Conclusion

Introduction

This guide will explain to you how to build the following integration:

Convert a what3words address
        let selectedAddress: HTMLElement | null;
let addressDetailsContainer: HTMLElement;
let addressListContainer: HTMLElement;
let addressList: HTMLElement;
let subBuildingListContainer: HTMLElement;
let subBuildingList: HTMLElement;
let allResultsContainer: HTMLElement;
let resultsContainer: HTMLElement;
let autoSuggestResultsTitle: HTMLElement;
let results: HTMLElement;
let inputElement: HTMLInputElement;
let detailsHTML: HTMLElement;
let clearSearchBtn: HTMLButtonElement;
let map: woosmap.map.Map;
let marker: woosmap.map.Marker;
let debouncedAutosuggestW3W: (
  ...args: any[]
) => Promise<What3WordsSuggestionsResponse>;

const API_KEY = "YOUR_API_KEY"; // Replace YOUR_API_KEY with your actual API key

interface What3WordsSuggestionsResponse {
  suggestions: What3WordsSuggestion[];
}

interface What3WordsSuggestion {
  country: string;
  distanceToFocusKm: number | null;
  language: string;
  nearestPlace: string;
  rank: number;
  words: string;
}

interface What3WordsAddressesResponse {
  results: What3WordsAddress[];
}

interface What3WordsAddress {
  description: string;
  public_id: string;
  status: string | null;
  sub_buildings:
    | {
        description: string;
        public_id: string;
      }[]
    | null;
  types: string[];
}

function convertToAddress(words: string): Promise<What3WordsAddressesResponse> {
  return fetch(
    `https://api.woosmap.com/what3words/convert-to-address?key=${API_KEY}&words=${words}`,
  ).then((response) => response.json());
}


function getLocalitiesDetails(
  publicId: string,
): Promise<woosmap.map.localities.LocalitiesDetailsResponse> {
  return fetch(
    `https://api.woosmap.com/localities/details/?key=${API_KEY}&public_id=${publicId}`,
  ).then((response) => response.json());
}


function autosuggestW3W(input: string): Promise<What3WordsSuggestionsResponse> {
  return fetch(
    `https://api.woosmap.com/what3words/autosuggest?key=${API_KEY}&input=${input}`,
  ).then((response) => response.json());
}


function clearSection(section: HTMLElement): void {
  section.innerHTML = "";
}

function hideSection(section: HTMLElement): void {
  section.style.display = "none";
}

function displaySection(section: HTMLElement, mode = "block"): void {
  section.style.display = mode;
}

function setSelectedAddress(selectedElement: HTMLElement): void {
  if (selectedAddress) {
    selectedAddress.classList.remove("selected");
  }
  selectedAddress = selectedElement;
  selectedAddress.classList.add("selected");
}

function panMap(addressDetail) {
  if (addressDetail.geometry.viewport) {
    const { viewport } = addressDetail.geometry;
    const bounds = {
      east: viewport.northeast.lng,
      south: viewport.southwest.lat,
      north: viewport.northeast.lat,
      west: viewport.southwest.lng,
    };
    map.fitBounds(bounds);
    map.panTo(addressDetail.geometry.location);
  } else {
    let zoom = 17;
    if (addressDetail.types[0] === "address") {
      zoom = 18;
    }
    map.setZoom(zoom);
    map.panTo(addressDetail.geometry.location);
  }
}

function createAddressMarker(addressDetail) {
  if (marker) {
    marker.setMap(null);
  }
  marker = new woosmap.map.Marker({
    position: addressDetail.geometry.location,
    icon: {
      url: "https://images.woosmap.com/marker-alt.png",
      scaledSize: {
        height: 59,
        width: 37,
      },
    },
  });
  marker.setMap(map);
  panMap(addressDetail);
}

function fillAddressDetails(
  addressDetails: woosmap.map.localities.LocalitiesDetailsResult,
) {
  const details: string[] = [];
  if (addressDetails.formatted_address) {
    details.push(
      `<p class='option-detail'><span class='option-detail-label'>Formatted_address :</span><span class='bold'>${addressDetails.formatted_address}</span></p>`,
    );
  }
  if (addressDetails.types && addressDetails.types[0]) {
    details.push(
      `<p class='option-detail'><span class='option-detail-label'>Type : </span><span class='bold'>${addressDetails.types[0].replace("_", " ")}</span></p>`,
    );
  }
  if (addressDetails.geometry) {
    details.push(
      `<div class='option-detail'><div><span class='option-detail-label'>Latitude :</span> <span class='bold'>${addressDetails.geometry.location.lat.toString()}</span></div><div><span class='option-detail-label'>Longitude : </span><span class='bold'>${addressDetails.geometry.location.lng.toString()}</span></div></div>`,
    );
    if (addressDetails.address_components) {
      const compoHtml = addressDetails.address_components
        .map(
          (compo) =>
            `<p class='option-detail'><span class='option-detail-label'>${compo.types[0]}:</span> <span class='bold'>${compo.long_name}</span></p>`,
        )
        .join("");
      details.push(
        `<div class='address-components'><div class='title'>Address components</div><div>${compoHtml}</div>`,
      );
    }
  }
  detailsHTML.innerHTML = details.join("");
}


function getAddressDetails(target, publicId) {
  setSelectedAddress(target);
  getLocalitiesDetails(publicId)
    .then((detailResponse) => {
      const addressDetails = detailResponse.result;
      if (addressDetails) {
        createAddressMarker(addressDetails);
        fillAddressDetails(addressDetails);
        displaySection(addressDetailsContainer);
      }
    })
    .catch((error) => {
      console.error(error);
    });
}

function displaySubBuildings(target: HTMLElement, subBuildings) {
  setSelectedAddress(target);
  hideSection(addressListContainer);
  clearSection(subBuildingList);
  displaySection(subBuildingListContainer);
  displaySection(autoSuggestResultsTitle);
  const addressList = document.createElement("ul");

  subBuildings.forEach((address, index) => {
    const line = document.createElement("li");
    line.className = "address";
    line.addEventListener("click", (event) => {
      getAddressDetails(line, address.public_id);
    });
    line.innerHTML = `<span class='pin'></span><span>${address.description}</span>`;
    addressList.appendChild(line);
  });

  subBuildingList.appendChild(addressList);
}

function backToAddressList() {
  hideSection(autoSuggestResultsTitle);
  hideSection(subBuildingListContainer);
  hideSection(addressDetailsContainer);
  displaySection(addressListContainer);
}

function displayAddressList(addressDetails) {
  backToAddressList();
  const addressListContainer = addressList;
  const newAddressList = document.createElement("ul");
  const fragment = document.createDocumentFragment();

  if (!Array.isArray(addressDetails) || addressDetails.length === 0) {
    const line = document.createElement("li");
    line.className = "not-found";
    line.textContent =
      "what3words address invalid or no street address found within 200m of its location.";
    newAddressList.appendChild(line);
  } else {
    addressDetails.forEach((address, index) => {
      const line = document.createElement("li");
      line.className = "address";
      line.addEventListener("click", (event) => {
        if (address.public_id) {
          getAddressDetails(line, address.public_id);
        } else {
          displaySubBuildings(line, address.sub_buildings);
        }
      });
      line.innerHTML = `<span class='${
        address.public_id ? "pin" : "arrow"
      }'></span><span>${address.description}</span>`;
      fragment.appendChild(line);
    });
  }
  newAddressList.appendChild(fragment);
  addressListContainer.appendChild(newAddressList);
}

function getPossibleAddress(words: string) {
  hideSection(resultsContainer);
  displaySection(addressListContainer);
  hideSection(addressDetailsContainer);
  clearSection(addressList);
  convertToAddress(words)
    .then(({ results }) => {
      if (results) {
        displayAddressList(results);
      }
    })
    .catch((error) => {
      console.error(error);
    });
}

function w3wClickCallback(suggestion: What3WordsSuggestion): void {
  (document.querySelector(".words") as HTMLElement).innerHTML =
    suggestion.words;
  (document.querySelector(".nearest") as HTMLElement).innerHTML =
    `Nearest place : ${suggestion.nearestPlace}`;
  (inputElement as HTMLInputElement).value = `///${suggestion.words}`;
  getPossibleAddress(suggestion.words);
}

function displayW3wSuggestion() {
  const value = inputElement.value;
  if (value) {
    displaySection(clearSearchBtn);
  }
  value.replace('"', '\\"').replace(/^\s+|\s+$/g, "");
  hideSection(autoSuggestResultsTitle);
  if ((value.match(/[.]/g) || []).length !== 2) {
    hideSection(resultsContainer);
    hideSection(addressListContainer);
    hideSection(subBuildingListContainer);
    return;
  }

  displaySection(allResultsContainer);
  hideSection(addressListContainer);
  hideSection(subBuildingListContainer);
  hideSection(addressDetailsContainer);

  debouncedAutosuggestW3W(value)
    .then(({ suggestions }) => {
      clearSection(results);
      const list = document.createElement("ul");
      if (suggestions) {
        suggestions.forEach((suggestion) => {
          const item = document.createElement("li");
          item.className = "suggestion";
          item.id = suggestion.rank.toString();
          item.innerHTML = `<div class="words">${suggestion.words}</div><div class="nearest">Nearest place : ${suggestion.nearestPlace}</div>`;
          item.dataset.word = suggestion.words;
          item.onclick = () => {
            w3wClickCallback(suggestion);
          };
          list.appendChild(item);
        });
        results.appendChild(list);
      } else {
        const failed = document.createElement("div");
        failed.innerHTML = "Failed to load suggestions";
        results.appendChild(failed);
      }

      displaySection(resultsContainer, "flex");
    })
    .catch((error) => {
      console.error(error);
    });
}

function initMap() {
  map = new woosmap.map.Map(document.getElementById("map") as HTMLElement, {
    center: {
      lat: 48.8534,
      lng: 2.3488,
    },
    disableDefaultUI: true,
    gestureHandling: "greedy",
    zoom: 5,
    styles: [
      {
        featureType: "poi",
        stylers: [{ visibility: "off" }],
      },
    ],
  });
  debouncedAutosuggestW3W = debouncePromise(autosuggestW3W, 0);
  initUI();
}

function resetUI() {
  hideSection(resultsContainer);
  hideSection(addressListContainer);
  hideSection(subBuildingListContainer);
  hideSection(addressDetailsContainer);
  hideSection(clearSearchBtn);
  hideSection(allResultsContainer);
  clearSection(addressList);
  clearSection(subBuildingList);
  clearSection(detailsHTML);
  inputElement.value = "";
  if (marker) {
    marker.setMap(null);
  }
  inputElement.focus();
}

function initUI() {
  inputElement = document.getElementById(
    "autocomplete-input",
  ) as HTMLInputElement;
  addressDetailsContainer = document.querySelector(
    ".addressDetails",
  ) as HTMLElement;
  addressListContainer = document.querySelector(
    ".address-list-container",
  ) as HTMLElement;
  addressList = document.getElementById("address-suggestions") as HTMLElement;
  subBuildingListContainer = document.getElementById(
    "sub-building-suggestions",
  ) as HTMLElement;
  subBuildingList = document.getElementById(
    "sub-building-suggestions",
  ) as HTMLElement;
  allResultsContainer = document.getElementById(
    "all-results-container",
  ) as HTMLElement;
  resultsContainer = document.querySelector(
    ".autosuggest-results-container",
  ) as HTMLElement;
  autoSuggestResultsTitle = document.querySelector(
    ".sub-building-list-container .autosuggest-results-title",
  ) as HTMLElement;
  detailsHTML = document.querySelector(
    ".addressDetails .options",
  ) as HTMLElement;
  results = document.querySelector(".autosuggest-results") as HTMLElement;
  clearSearchBtn = document.querySelector(
    ".clear-searchButton",
  ) as HTMLButtonElement;

  autoSuggestResultsTitle.addEventListener("click", backToAddressList);
  inputElement.addEventListener("input", displayW3wSuggestion);
  clearSearchBtn.addEventListener("click", resetUI);
}

type DebouncePromiseFunction<T, Args extends any[]> = (
  ...args: Args
) => Promise<T>;

function debouncePromise<T, Args extends any[]>(
  fn: (...args: Args) => Promise<T>,
  delay: number,
): DebouncePromiseFunction<T, Args> {
  let timeoutId: ReturnType<typeof setTimeout> | null = null;
  let latestResolve: ((value: T | PromiseLike<T>) => void) | null = null;
  let latestReject: ((reason?: any) => void) | null = null;

  return function (...args: Args): Promise<T> {
    return new Promise<T>((resolve, reject) => {
      if (timeoutId !== null) {
        clearTimeout(timeoutId);
      }
      latestResolve = resolve;
      latestReject = reject;
      timeoutId = setTimeout(() => {
        fn(...args)
          .then((result) => {
            if (latestResolve === resolve && latestReject === reject) {
              resolve(result);
            }
          })
          .catch((error) => {
            if (latestResolve === resolve && latestReject === reject) {
              reject(error);
            }
          });
      }, delay);
    });
  };
}

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

    
        let selectedAddress;
let addressDetailsContainer;
let addressListContainer;
let addressList;
let subBuildingListContainer;
let subBuildingList;
let allResultsContainer;
let resultsContainer;
let autoSuggestResultsTitle;
let results;
let inputElement;
let detailsHTML;
let clearSearchBtn;
let map;
let marker;
let debouncedAutosuggestW3W;
const API_KEY = "YOUR_API_KEY"; // Replace YOUR_API_KEY with your actual API key

function convertToAddress(words) {
  return fetch(
    `https://api.woosmap.com/what3words/convert-to-address?key=${API_KEY}&words=${words}`,
  ).then((response) => response.json());
}

function getLocalitiesDetails(publicId) {
  return fetch(
    `https://api.woosmap.com/localities/details/?key=${API_KEY}&public_id=${publicId}`,
  ).then((response) => response.json());
}

function autosuggestW3W(input) {
  return fetch(
    `https://api.woosmap.com/what3words/autosuggest?key=${API_KEY}&input=${input}`,
  ).then((response) => response.json());
}

function clearSection(section) {
  section.innerHTML = "";
}

function hideSection(section) {
  section.style.display = "none";
}

function displaySection(section, mode = "block") {
  section.style.display = mode;
}

function setSelectedAddress(selectedElement) {
  if (selectedAddress) {
    selectedAddress.classList.remove("selected");
  }

  selectedAddress = selectedElement;
  selectedAddress.classList.add("selected");
}

function panMap(addressDetail) {
  if (addressDetail.geometry.viewport) {
    const { viewport } = addressDetail.geometry;
    const bounds = {
      east: viewport.northeast.lng,
      south: viewport.southwest.lat,
      north: viewport.northeast.lat,
      west: viewport.southwest.lng,
    };

    map.fitBounds(bounds);
    map.panTo(addressDetail.geometry.location);
  } else {
    let zoom = 17;

    if (addressDetail.types[0] === "address") {
      zoom = 18;
    }

    map.setZoom(zoom);
    map.panTo(addressDetail.geometry.location);
  }
}

function createAddressMarker(addressDetail) {
  if (marker) {
    marker.setMap(null);
  }

  marker = new woosmap.map.Marker({
    position: addressDetail.geometry.location,
    icon: {
      url: "https://images.woosmap.com/marker-alt.png",
      scaledSize: {
        height: 59,
        width: 37,
      },
    },
  });
  marker.setMap(map);
  panMap(addressDetail);
}

function fillAddressDetails(addressDetails) {
  const details = [];

  if (addressDetails.formatted_address) {
    details.push(
      `<p class='option-detail'><span class='option-detail-label'>Formatted_address :</span><span class='bold'>${addressDetails.formatted_address}</span></p>`,
    );
  }

  if (addressDetails.types && addressDetails.types[0]) {
    details.push(
      `<p class='option-detail'><span class='option-detail-label'>Type : </span><span class='bold'>${addressDetails.types[0].replace("_", " ")}</span></p>`,
    );
  }

  if (addressDetails.geometry) {
    details.push(
      `<div class='option-detail'><div><span class='option-detail-label'>Latitude :</span> <span class='bold'>${addressDetails.geometry.location.lat.toString()}</span></div><div><span class='option-detail-label'>Longitude : </span><span class='bold'>${addressDetails.geometry.location.lng.toString()}</span></div></div>`,
    );
    if (addressDetails.address_components) {
      const compoHtml = addressDetails.address_components
        .map(
          (compo) =>
            `<p class='option-detail'><span class='option-detail-label'>${compo.types[0]}:</span> <span class='bold'>${compo.long_name}</span></p>`,
        )
        .join("");

      details.push(
        `<div class='address-components'><div class='title'>Address components</div><div>${compoHtml}</div>`,
      );
    }
  }

  detailsHTML.innerHTML = details.join("");
}

function getAddressDetails(target, publicId) {
  setSelectedAddress(target);
  getLocalitiesDetails(publicId)
    .then((detailResponse) => {
      const addressDetails = detailResponse.result;

      if (addressDetails) {
        createAddressMarker(addressDetails);
        fillAddressDetails(addressDetails);
        displaySection(addressDetailsContainer);
      }
    })
    .catch((error) => {
      console.error(error);
    });
}

function displaySubBuildings(target, subBuildings) {
  setSelectedAddress(target);
  hideSection(addressListContainer);
  clearSection(subBuildingList);
  displaySection(subBuildingListContainer);
  displaySection(autoSuggestResultsTitle);

  const addressList = document.createElement("ul");

  subBuildings.forEach((address, index) => {
    const line = document.createElement("li");

    line.className = "address";
    line.addEventListener("click", (event) => {
      getAddressDetails(line, address.public_id);
    });
    line.innerHTML = `<span class='pin'></span><span>${address.description}</span>`;
    addressList.appendChild(line);
  });
  subBuildingList.appendChild(addressList);
}

function backToAddressList() {
  hideSection(autoSuggestResultsTitle);
  hideSection(subBuildingListContainer);
  hideSection(addressDetailsContainer);
  displaySection(addressListContainer);
}

function displayAddressList(addressDetails) {
  backToAddressList();

  const addressListContainer = addressList;
  const newAddressList = document.createElement("ul");
  const fragment = document.createDocumentFragment();

  if (!Array.isArray(addressDetails) || addressDetails.length === 0) {
    const line = document.createElement("li");

    line.className = "not-found";
    line.textContent =
      "what3words address invalid or no street address found within 200m of its location.";
    newAddressList.appendChild(line);
  } else {
    addressDetails.forEach((address, index) => {
      const line = document.createElement("li");

      line.className = "address";
      line.addEventListener("click", (event) => {
        if (address.public_id) {
          getAddressDetails(line, address.public_id);
        } else {
          displaySubBuildings(line, address.sub_buildings);
        }
      });
      line.innerHTML = `<span class='${address.public_id ? "pin" : "arrow"}'></span><span>${address.description}</span>`;
      fragment.appendChild(line);
    });
  }

  newAddressList.appendChild(fragment);
  addressListContainer.appendChild(newAddressList);
}

function getPossibleAddress(words) {
  hideSection(resultsContainer);
  displaySection(addressListContainer);
  hideSection(addressDetailsContainer);
  clearSection(addressList);
  convertToAddress(words)
    .then(({ results }) => {
      if (results) {
        displayAddressList(results);
      }
    })
    .catch((error) => {
      console.error(error);
    });
}

function w3wClickCallback(suggestion) {
  document.querySelector(".words").innerHTML = suggestion.words;
  document.querySelector(".nearest").innerHTML =
    `Nearest place : ${suggestion.nearestPlace}`;
  inputElement.value = `///${suggestion.words}`;
  getPossibleAddress(suggestion.words);
}

function displayW3wSuggestion() {
  const value = inputElement.value;

  if (value) {
    displaySection(clearSearchBtn);
  }

  value.replace('"', '\\"').replace(/^\s+|\s+$/g, "");
  hideSection(autoSuggestResultsTitle);
  if ((value.match(/[.]/g) || []).length !== 2) {
    hideSection(resultsContainer);
    hideSection(addressListContainer);
    hideSection(subBuildingListContainer);
    return;
  }

  displaySection(allResultsContainer);
  hideSection(addressListContainer);
  hideSection(subBuildingListContainer);
  hideSection(addressDetailsContainer);
  debouncedAutosuggestW3W(value)
    .then(({ suggestions }) => {
      clearSection(results);

      const list = document.createElement("ul");

      if (suggestions) {
        suggestions.forEach((suggestion) => {
          const item = document.createElement("li");

          item.className = "suggestion";
          item.id = suggestion.rank.toString();
          item.innerHTML = `<div class="words">${suggestion.words}</div><div class="nearest">Nearest place : ${suggestion.nearestPlace}</div>`;
          item.dataset.word = suggestion.words;
          item.onclick = () => {
            w3wClickCallback(suggestion);
          };

          list.appendChild(item);
        });
        results.appendChild(list);
      } else {
        const failed = document.createElement("div");

        failed.innerHTML = "Failed to load suggestions";
        results.appendChild(failed);
      }

      displaySection(resultsContainer, "flex");
    })
    .catch((error) => {
      console.error(error);
    });
}

function initMap() {
  map = new woosmap.map.Map(document.getElementById("map"), {
    center: {
      lat: 48.8534,
      lng: 2.3488,
    },
    disableDefaultUI: true,
    gestureHandling: "greedy",
    zoom: 5,
    styles: [
      {
        featureType: "poi",
        stylers: [{ visibility: "off" }],
      },
    ],
  });
  debouncedAutosuggestW3W = debouncePromise(autosuggestW3W, 0);
  initUI();
}

function resetUI() {
  hideSection(resultsContainer);
  hideSection(addressListContainer);
  hideSection(subBuildingListContainer);
  hideSection(addressDetailsContainer);
  hideSection(clearSearchBtn);
  hideSection(allResultsContainer);
  clearSection(addressList);
  clearSection(subBuildingList);
  clearSection(detailsHTML);
  inputElement.value = "";
  if (marker) {
    marker.setMap(null);
  }

  inputElement.focus();
}

function initUI() {
  inputElement = document.getElementById("autocomplete-input");
  addressDetailsContainer = document.querySelector(".addressDetails");
  addressListContainer = document.querySelector(".address-list-container");
  addressList = document.getElementById("address-suggestions");
  subBuildingListContainer = document.getElementById(
    "sub-building-suggestions",
  );
  subBuildingList = document.getElementById("sub-building-suggestions");
  allResultsContainer = document.getElementById("all-results-container");
  resultsContainer = document.querySelector(".autosuggest-results-container");
  autoSuggestResultsTitle = document.querySelector(
    ".sub-building-list-container .autosuggest-results-title",
  );
  detailsHTML = document.querySelector(".addressDetails .options");
  results = document.querySelector(".autosuggest-results");
  clearSearchBtn = document.querySelector(".clear-searchButton");
  autoSuggestResultsTitle.addEventListener("click", backToAddressList);
  inputElement.addEventListener("input", displayW3wSuggestion);
  clearSearchBtn.addEventListener("click", resetUI);
}

function debouncePromise(fn, delay) {
  let timeoutId = null;
  let latestResolve = null;
  let latestReject = null;

  return function (...args) {
    return new Promise((resolve, reject) => {
      if (timeoutId !== null) {
        clearTimeout(timeoutId);
      }

      latestResolve = resolve;
      latestReject = reject;
      timeoutId = setTimeout(() => {
        fn(...args)
          .then((result) => {
            if (latestResolve === resolve && latestReject === reject) {
              resolve(result);
            }
          })
          .catch((error) => {
            if (latestResolve === resolve && latestReject === reject) {
              reject(error);
            }
          });
      }, delay);
    });
  };
}

window.initMap = initMap;

    
        /*
 * Always set the map height explicitly to define the size of the div element
 * that contains the map.
 */
#map {
  height: 100%;
}

/*
 * Optional: Makes the sample page fill the window.
 */
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;
}

#autocomplete-container {
  display: flex;
  position: absolute;
  top: 10px;
  left: 10px;
  z-index: 1;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2), 0 -1px 0px rgba(0, 0, 0, 0.02);
  background: #fff;
  border-radius: 12px;
  padding: 0 12px;
  max-width: 320px;
  width: 100%;
  height: 42px;
  border: none;
  box-sizing: border-box;
  align-items: center;
  cursor: text;
  font-size: 15px;
}

#autocomplete-container .search-icon, #autocomplete-container .clear-icon {
  color: inherit;
  flex-shrink: 0;
  height: 16px;
  width: 16px;
}

#autocomplete-container .clear-icon {
  transform: scale(1.3);
}

#autocomplete-input {
  box-sizing: border-box;
  padding: 0;
  height: 40px;
  line-height: 24px;
  vertical-align: top;
  transition-property: color;
  transition-duration: 0.3s;
  width: 100%;
  text-overflow: ellipsis;
  background: transparent;
  border-radius: 0;
  border: 0;
  margin: 0 8px;
  outline: 0;
  overflow: visible;
  appearance: textfield;
  font-size: 100%;
}

.clear-searchButton {
  display: none;
  height: 18px;
  width: 22px;
  background: none;
  border: none;
  vertical-align: middle;
  pointer-events: all;
  cursor: pointer;
}

#suggestions-list {
  border-radius: 12px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2), 0 -1px 0px rgba(0, 0, 0, 0.02);
  box-sizing: border-box;
  position: absolute;
  max-width: 320px;
  width: 100%;
  top: 100%;
  left: 0;
  z-index: 1;
  list-style: none;
  max-height: 80vh;
  margin: 5px 0 0;
  padding: 0;
  display: none;
  overflow-y: auto;
  background-color: #fff;
}

#suggestions-list li {
  padding: 12px;
  cursor: pointer;
  transition: background-color 0.3s ease;
}

#suggestions-list li:hover {
  background-color: #f2f2f2;
}

#app {
  height: 100%;
  font-size: 13px;
}

#autocomplete-container {
  font-size: 13px;
}

* {
  box-sizing: border-box;
}

p {
  margin: 5px 0;
}

li {
  list-style: none outside;
}

ul {
  margin: 0;
  padding: 0;
}

ul > :not(:last-child) {
  border-bottom: 1px solid rgba(0, 0, 0, 0.1);
}

.bold {
  font-weight: 700;
}

#all-results-container {
  position: absolute;
  top: 100%;
  width: 100%;
  left: 0;
  z-index: 1;
  list-style: none;
  margin: 8px 0 0;
  display: none;
  background-color: #fff;
  border-radius: 6px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2), 0 -1px 0px rgba(0, 0, 0, 0.02);
}

.autosuggest-results-container {
  display: none;
  flex-direction: column;
  background-color: #fff;
  border-radius: 6px;
}

.autosuggest-results {
  width: 100%;
  min-width: 0;
}

.autosuggest-results-title, .address-list .title {
  padding: 16px;
  background-color: rgba(0, 0, 0, 0.02);
  color: rgba(0, 0, 0, 0.5);
  font-size: 10px;
  text-transform: uppercase;
  border-bottom: 1px solid rgba(0, 0, 0, 0.06);
  letter-spacing: 0.5px;
  border-bottom: 1px solid rgba(0, 0, 0, 0.06);
  font-weight: 500;
}

.address-list {
  max-height: 305px;
  overflow-y: auto;
}

.addressDetails {
  display: none;
  position: absolute;
  right: 10px;
  bottom: 25px;
  border-radius: 6px;
  max-width: 240px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2), 0 -1px 0px rgba(0, 0, 0, 0.02);
  z-index: 1;
  overflow: hidden;
}
.addressDetails .info {
  padding: 12px 16px;
  border-bottom: 1px solid rgba(0, 0, 0, 0.06);
  background-color: #fff;
}
.addressDetails .options {
  overflow-y: auto;
  max-height: 240px;
  font-size: 12px;
  padding-top: 12px;
  background-color: #fff;
}
.addressDetails .options .option-detail {
  display: flex;
  flex-wrap: wrap;
  padding: 0 12px 8px 12px;
  margin: 0;
}
.addressDetails .options .option-detail-label {
  color: rgba(0, 0, 0, 0.5);
  margin-right: 4px;
}

.address-components {
  padding: 0 0 18px 0;
  background-color: rgba(0, 0, 0, 0.03);
}

.address-components .title {
  color: rgba(0, 0, 0, 0.5);
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 1px;
  padding: 16px 12px 10px 12px;
}

.options > label {
  display: block;
  padding: 5px;
}

.options select, .options button {
  padding: 3px;
  margin: 5px;
  width: 200px;
}

.selectBox {
  position: relative;
}

.w3w-results {
  display: flex;
  background-color: #fff;
  min-width: 300px;
  overflow-y: auto;
  max-height: 400px;
}

.w3w-results-container {
  display: none;
}

.address-list-container {
  display: none;
}

.suggestion {
  padding: 10px 16px;
  width: 100%;
  min-width: 0;
  transition: 0.3s all ease-in-out;
}

.suggestion:hover {
  background-color: rgba(0, 0, 0, 0.02);
  cursor: pointer;
}

.info .words, .suggestion .words {
  font-weight: 700;
  margin-bottom: 1px;
  transition: 0.3s all ease-in-out;
  color: #252525;
}

.info .words::before, .suggestion .words::before {
  content: "///";
  margin-right: 5px;
  color: #e01f25;
  letter-spacing: -0.06rem;
}

.suggestion:hover .words {
  color: #000;
}

.info .nearest, .suggestion .nearest {
  font-size: 12px;
  color: rgba(0, 0, 0, 0.5);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  transition: 0.3s all ease-in-out;
}

.suggestion:hover .nearest {
  color: #000;
}

.sub-building-list-container .autosuggest-results-title {
  cursor: pointer;
  display: none;
}

.not-found {
  padding: 12px 16px;
  color: #930e07;
}

.address {
  cursor: pointer;
  position: relative;
  display: flex;
  align-items: center;
  padding: 10px 16px 10px;
  min-height: 50px;
}
.address:hover {
  background-color: rgba(0, 0, 0, 0.02);
  color: #3949ab;
}
.address:hover .pin {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='23' viewBox='0 0 16 23'%3E%3Cpath fill='%233949ab' d='M7.904,22.449 C8.296,22.449 8.646,22.219 8.646,21.802 C8.646,16.597 15.808,13.584 15.808,7.904 C15.808,3.53874134 12.2692587,1.19949929e-24 7.904,1.19949929e-24 C3.53874134,1.19949929e-24 0,3.53874134 0,7.904 C0,13.583 7.162,16.597 7.162,21.802 C7.162,22.227 7.511,22.449 7.904,22.449 L7.904,22.449 Z M4.625,8.043 C4.625,6.23150602 6.09350602,4.763 7.905,4.763 C9.71649398,4.763 11.185,6.23150602 11.185,8.043 C11.185,9.85449398 9.71649398,11.323 7.905,11.323 C6.09350602,11.323 4.625,9.85449398 4.625,8.043 Z'/%3E%3C/svg%3E");
  background-size: contain;
  background-repeat: no-repeat;
  background-position: center;
}
.address:hover .arrow {
  background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="20" height="16" viewBox="0 0 20 16"><path fill="%233949ab" d="M0 15C0 14.4477153.44771525 14 1 14L19 14C19.5522847 14 20 14.4477153 20 15 20 15.5522847 19.5522847 16 19 16L1 16C.44771525 16 0 15.5522847 0 15M0 8C0 7.44771525.44771525 7 1 7L19 7C19.5522847 7 20 7.44771525 20 8 20 8.55228475 19.5522847 9 19 9L1 9C.44771525 9 0 8.55228475 0 8M0 1C0 .44771525.44771525 0 1 0L19 0C19.5522847 0 20 .44771525 20 1 20 1.55228475 19.5522847 2 19 2L1 2C.44771525 2 0 1.55228475 0 1"/></svg>');
  background-size: contain;
  background-repeat: no-repeat;
  background-position: center;
}
.address.selected {
  color: #da0082;
  font-weight: 700;
  background-color: rgba(85, 134, 255, 0.06);
}
.address.selected .pin {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='23' viewBox='0 0 16 23'%3E%3Cpath fill='%23da0082' d='M7.904,22.449 C8.296,22.449 8.646,22.219 8.646,21.802 C8.646,16.597 15.808,13.584 15.808,7.904 C15.808,3.53874134 12.2692587,1.19949929e-24 7.904,1.19949929e-24 C3.53874134,1.19949929e-24 0,3.53874134 0,7.904 C0,13.583 7.162,16.597 7.162,21.802 C7.162,22.227 7.511,22.449 7.904,22.449 L7.904,22.449 Z M4.625,8.043 C4.625,6.23150602 6.09350602,4.763 7.905,4.763 C9.71649398,4.763 11.185,6.23150602 11.185,8.043 C11.185,9.85449398 9.71649398,11.323 7.905,11.323 C6.09350602,11.323 4.625,9.85449398 4.625,8.043 Z'/%3E%3C/svg%3E");
  background-size: contain;
  background-repeat: no-repeat;
  background-position: center;
}
.address.selected .arrow {
  background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="20" height="16" viewBox="0 0 20 16"><path fill="%23da0082" d="M0 15C0 14.4477153.44771525 14 1 14L19 14C19.5522847 14 20 14.4477153 20 15 20 15.5522847 19.5522847 16 19 16L1 16C.44771525 16 0 15.5522847 0 15M0 8C0 7.44771525.44771525 7 1 7L19 7C19.5522847 7 20 7.44771525 20 8 20 8.55228475 19.5522847 9 19 9L1 9C.44771525 9 0 8.55228475 0 8M0 1C0 .44771525.44771525 0 1 0L19 0C19.5522847 0 20 .44771525 20 1 20 1.55228475 19.5522847 2 19 2L1 2C.44771525 2 0 1.55228475 0 1"/></svg>');
  background-size: contain;
  background-repeat: no-repeat;
  background-position: center;
}

.pin, .arrow {
  flex: 0 0 13px;
  height: 18px;
  margin-right: 12px;
}

.pin {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='23' viewBox='0 0 16 23'%3E%3Cpath fill='%231f88e5' d='M7.904,22.449 C8.296,22.449 8.646,22.219 8.646,21.802 C8.646,16.597 15.808,13.584 15.808,7.904 C15.808,3.53874134 12.2692587,1.19949929e-24 7.904,1.19949929e-24 C3.53874134,1.19949929e-24 0,3.53874134 0,7.904 C0,13.583 7.162,16.597 7.162,21.802 C7.162,22.227 7.511,22.449 7.904,22.449 L7.904,22.449 Z M4.625,8.043 C4.625,6.23150602 6.09350602,4.763 7.905,4.763 C9.71649398,4.763 11.185,6.23150602 11.185,8.043 C11.185,9.85449398 9.71649398,11.323 7.905,11.323 C6.09350602,11.323 4.625,9.85449398 4.625,8.043 Z'/%3E%3C/svg%3E");
  background-size: contain;
  background-repeat: no-repeat;
  background-position: center;
}

.arrow {
  background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="20" height="16" viewBox="0 0 20 16"><path fill="%231f88e5" d="M0 15C0 14.4477153.44771525 14 1 14L19 14C19.5522847 14 20 14.4477153 20 15 20 15.5522847 19.5522847 16 19 16L1 16C.44771525 16 0 15.5522847 0 15M0 8C0 7.44771525.44771525 7 1 7L19 7C19.5522847 7 20 7.44771525 20 8 20 8.55228475 19.5522847 9 19 9L1 9C.44771525 9 0 8.55228475 0 8M0 1C0 .44771525.44771525 0 1 0L19 0C19.5522847 0 20 .44771525 20 1 20 1.55228475 19.5522847 2 19 2L1 2C.44771525 2 0 1.55228475 0 1"/></svg>');
  background-size: contain;
  background-repeat: no-repeat;
  background-position: center;
}


    
        <html>
  <head>
    <title>Woosmap for What3Words</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="app">
      <div class="addressDetails">
        <div class="info">
          <div class="words"></div>
          <div class="nearest"></div>
        </div>
        <div class="options"></div>
      </div>
      <div id="autocomplete-container">
        <svg class="search-icon" viewBox="0 0 16 16">
          <path
            d="M3.617 7.083a4.338 4.338 0 1 1 8.676 0 4.338 4.338 0 0 1-8.676 0m4.338-5.838a5.838 5.838 0 1 0 2.162 11.262l2.278 2.279a1 1 0 0 0 1.415-1.414l-1.95-1.95A5.838 5.838 0 0 0 7.955 1.245"
            fill-rule="evenodd"
            clip-rule="evenodd"
          ></path>
        </svg>

        <input
          type="text"
          id="autocomplete-input"
          placeholder="your what3words (e.g. filled.count.soap)"
          autocomplete="off"
        />
        <button aria-label="Clear" class="clear-searchButton" type="button">
          <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>
        </button>
        <div id="all-results-container">
          <div class="autosuggest-results-container">
            <div class="autosuggest-results-title">what3words results</div>
            <div class="autosuggest-results"></div>

            <div class="w3w-results-container">
              <div class="w3w-results"></div>
            </div>
          </div>
          <div class="address-list-container">
            <div class="autosuggest-results-title">
              Woosmap addresses results
            </div>
            <div id="address-suggestions" class="address-list"></div>
          </div>
          <div class="sub-building-list-container">
            <div class="autosuggest-results-title">
              ← Back to woosmap results
            </div>
            <div id="sub-building-suggestions" class="address-list"></div>
          </div>
        </div>
        <ul id="suggestions-list"></ul>
      </div>
      <div id="map"></div>
    </div>

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

    

This example is made to showcase how-to:

Prerequisites

You will need a Woosmap Public Key with both:

Setting up the HTML page

We will create our index.html first, this will give us divs to display information to the user and later display the map.

The following is a basic html starting block along with dedicated styles for the integration:

Setting up the HTML and Style
        <html>
  <head>
    <title>Woosmap for What3Words</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="app">
      <div class="addressDetails">
        <div class="info">
          <div class="words"></div>
          <div class="nearest"></div>
        </div>
        <div class="options"></div>
      </div>
      <div id="autocomplete-container">
        <svg class="search-icon" viewBox="0 0 16 16">
          <path
            d="M3.617 7.083a4.338 4.338 0 1 1 8.676 0 4.338 4.338 0 0 1-8.676 0m4.338-5.838a5.838 5.838 0 1 0 2.162 11.262l2.278 2.279a1 1 0 0 0 1.415-1.414l-1.95-1.95A5.838 5.838 0 0 0 7.955 1.245"
            fill-rule="evenodd"
            clip-rule="evenodd"
          ></path>
        </svg>

        <input
          type="text"
          id="autocomplete-input"
          placeholder="your what3words (e.g. filled.count.soap)"
          autocomplete="off"
        />
        <button aria-label="Clear" class="clear-searchButton" type="button">
          <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>
        </button>
        <div id="all-results-container">
          <div class="autosuggest-results-container">
            <div class="autosuggest-results-title">what3words results</div>
            <div class="autosuggest-results"></div>

            <div class="w3w-results-container">
              <div class="w3w-results"></div>
            </div>
          </div>
          <div class="address-list-container">
            <div class="autosuggest-results-title">
              Woosmap addresses results
            </div>
            <div id="address-suggestions" class="address-list"></div>
          </div>
          <div class="sub-building-list-container">
            <div class="autosuggest-results-title">
              ← Back to woosmap results
            </div>
            <div id="sub-building-suggestions" class="address-list"></div>
          </div>
        </div>
        <ul id="suggestions-list"></ul>
      </div>
      <div id="map"></div>
    </div>

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

    
        /*
 * Always set the map height explicitly to define the size of the div element
 * that contains the map.
 */
#map {
  height: 100%;
}

/*
 * Optional: Makes the sample page fill the window.
 */
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;
}

#autocomplete-container {
  display: flex;
  position: absolute;
  top: 10px;
  left: 10px;
  z-index: 1;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2), 0 -1px 0px rgba(0, 0, 0, 0.02);
  background: #fff;
  border-radius: 12px;
  padding: 0 12px;
  max-width: 320px;
  width: 100%;
  height: 42px;
  border: none;
  box-sizing: border-box;
  align-items: center;
  cursor: text;
  font-size: 15px;
}

#autocomplete-container .search-icon, #autocomplete-container .clear-icon {
  color: inherit;
  flex-shrink: 0;
  height: 16px;
  width: 16px;
}

#autocomplete-container .clear-icon {
  transform: scale(1.3);
}

#autocomplete-input {
  box-sizing: border-box;
  padding: 0;
  height: 40px;
  line-height: 24px;
  vertical-align: top;
  transition-property: color;
  transition-duration: 0.3s;
  width: 100%;
  text-overflow: ellipsis;
  background: transparent;
  border-radius: 0;
  border: 0;
  margin: 0 8px;
  outline: 0;
  overflow: visible;
  appearance: textfield;
  font-size: 100%;
}

.clear-searchButton {
  display: none;
  height: 18px;
  width: 22px;
  background: none;
  border: none;
  vertical-align: middle;
  pointer-events: all;
  cursor: pointer;
}

#suggestions-list {
  border-radius: 12px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2), 0 -1px 0px rgba(0, 0, 0, 0.02);
  box-sizing: border-box;
  position: absolute;
  max-width: 320px;
  width: 100%;
  top: 100%;
  left: 0;
  z-index: 1;
  list-style: none;
  max-height: 80vh;
  margin: 5px 0 0;
  padding: 0;
  display: none;
  overflow-y: auto;
  background-color: #fff;
}

#suggestions-list li {
  padding: 12px;
  cursor: pointer;
  transition: background-color 0.3s ease;
}

#suggestions-list li:hover {
  background-color: #f2f2f2;
}

#app {
  height: 100%;
  font-size: 13px;
}

#autocomplete-container {
  font-size: 13px;
}

* {
  box-sizing: border-box;
}

p {
  margin: 5px 0;
}

li {
  list-style: none outside;
}

ul {
  margin: 0;
  padding: 0;
}

ul > :not(:last-child) {
  border-bottom: 1px solid rgba(0, 0, 0, 0.1);
}

.bold {
  font-weight: 700;
}

#all-results-container {
  position: absolute;
  top: 100%;
  width: 100%;
  left: 0;
  z-index: 1;
  list-style: none;
  margin: 8px 0 0;
  display: none;
  background-color: #fff;
  border-radius: 6px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2), 0 -1px 0px rgba(0, 0, 0, 0.02);
}

.autosuggest-results-container {
  display: none;
  flex-direction: column;
  background-color: #fff;
  border-radius: 6px;
}

.autosuggest-results {
  width: 100%;
  min-width: 0;
}

.autosuggest-results-title, .address-list .title {
  padding: 16px;
  background-color: rgba(0, 0, 0, 0.02);
  color: rgba(0, 0, 0, 0.5);
  font-size: 10px;
  text-transform: uppercase;
  border-bottom: 1px solid rgba(0, 0, 0, 0.06);
  letter-spacing: 0.5px;
  border-bottom: 1px solid rgba(0, 0, 0, 0.06);
  font-weight: 500;
}

.address-list {
  max-height: 305px;
  overflow-y: auto;
}

.addressDetails {
  display: none;
  position: absolute;
  right: 10px;
  bottom: 25px;
  border-radius: 6px;
  max-width: 240px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2), 0 -1px 0px rgba(0, 0, 0, 0.02);
  z-index: 1;
  overflow: hidden;
}
.addressDetails .info {
  padding: 12px 16px;
  border-bottom: 1px solid rgba(0, 0, 0, 0.06);
  background-color: #fff;
}
.addressDetails .options {
  overflow-y: auto;
  max-height: 240px;
  font-size: 12px;
  padding-top: 12px;
  background-color: #fff;
}
.addressDetails .options .option-detail {
  display: flex;
  flex-wrap: wrap;
  padding: 0 12px 8px 12px;
  margin: 0;
}
.addressDetails .options .option-detail-label {
  color: rgba(0, 0, 0, 0.5);
  margin-right: 4px;
}

.address-components {
  padding: 0 0 18px 0;
  background-color: rgba(0, 0, 0, 0.03);
}

.address-components .title {
  color: rgba(0, 0, 0, 0.5);
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 1px;
  padding: 16px 12px 10px 12px;
}

.options > label {
  display: block;
  padding: 5px;
}

.options select, .options button {
  padding: 3px;
  margin: 5px;
  width: 200px;
}

.selectBox {
  position: relative;
}

.w3w-results {
  display: flex;
  background-color: #fff;
  min-width: 300px;
  overflow-y: auto;
  max-height: 400px;
}

.w3w-results-container {
  display: none;
}

.address-list-container {
  display: none;
}

.suggestion {
  padding: 10px 16px;
  width: 100%;
  min-width: 0;
  transition: 0.3s all ease-in-out;
}

.suggestion:hover {
  background-color: rgba(0, 0, 0, 0.02);
  cursor: pointer;
}

.info .words, .suggestion .words {
  font-weight: 700;
  margin-bottom: 1px;
  transition: 0.3s all ease-in-out;
  color: #252525;
}

.info .words::before, .suggestion .words::before {
  content: "///";
  margin-right: 5px;
  color: #e01f25;
  letter-spacing: -0.06rem;
}

.suggestion:hover .words {
  color: #000;
}

.info .nearest, .suggestion .nearest {
  font-size: 12px;
  color: rgba(0, 0, 0, 0.5);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  transition: 0.3s all ease-in-out;
}

.suggestion:hover .nearest {
  color: #000;
}

.sub-building-list-container .autosuggest-results-title {
  cursor: pointer;
  display: none;
}

.not-found {
  padding: 12px 16px;
  color: #930e07;
}

.address {
  cursor: pointer;
  position: relative;
  display: flex;
  align-items: center;
  padding: 10px 16px 10px;
  min-height: 50px;
}
.address:hover {
  background-color: rgba(0, 0, 0, 0.02);
  color: #3949ab;
}
.address:hover .pin {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='23' viewBox='0 0 16 23'%3E%3Cpath fill='%233949ab' d='M7.904,22.449 C8.296,22.449 8.646,22.219 8.646,21.802 C8.646,16.597 15.808,13.584 15.808,7.904 C15.808,3.53874134 12.2692587,1.19949929e-24 7.904,1.19949929e-24 C3.53874134,1.19949929e-24 0,3.53874134 0,7.904 C0,13.583 7.162,16.597 7.162,21.802 C7.162,22.227 7.511,22.449 7.904,22.449 L7.904,22.449 Z M4.625,8.043 C4.625,6.23150602 6.09350602,4.763 7.905,4.763 C9.71649398,4.763 11.185,6.23150602 11.185,8.043 C11.185,9.85449398 9.71649398,11.323 7.905,11.323 C6.09350602,11.323 4.625,9.85449398 4.625,8.043 Z'/%3E%3C/svg%3E");
  background-size: contain;
  background-repeat: no-repeat;
  background-position: center;
}
.address:hover .arrow {
  background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="20" height="16" viewBox="0 0 20 16"><path fill="%233949ab" d="M0 15C0 14.4477153.44771525 14 1 14L19 14C19.5522847 14 20 14.4477153 20 15 20 15.5522847 19.5522847 16 19 16L1 16C.44771525 16 0 15.5522847 0 15M0 8C0 7.44771525.44771525 7 1 7L19 7C19.5522847 7 20 7.44771525 20 8 20 8.55228475 19.5522847 9 19 9L1 9C.44771525 9 0 8.55228475 0 8M0 1C0 .44771525.44771525 0 1 0L19 0C19.5522847 0 20 .44771525 20 1 20 1.55228475 19.5522847 2 19 2L1 2C.44771525 2 0 1.55228475 0 1"/></svg>');
  background-size: contain;
  background-repeat: no-repeat;
  background-position: center;
}
.address.selected {
  color: #da0082;
  font-weight: 700;
  background-color: rgba(85, 134, 255, 0.06);
}
.address.selected .pin {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='23' viewBox='0 0 16 23'%3E%3Cpath fill='%23da0082' d='M7.904,22.449 C8.296,22.449 8.646,22.219 8.646,21.802 C8.646,16.597 15.808,13.584 15.808,7.904 C15.808,3.53874134 12.2692587,1.19949929e-24 7.904,1.19949929e-24 C3.53874134,1.19949929e-24 0,3.53874134 0,7.904 C0,13.583 7.162,16.597 7.162,21.802 C7.162,22.227 7.511,22.449 7.904,22.449 L7.904,22.449 Z M4.625,8.043 C4.625,6.23150602 6.09350602,4.763 7.905,4.763 C9.71649398,4.763 11.185,6.23150602 11.185,8.043 C11.185,9.85449398 9.71649398,11.323 7.905,11.323 C6.09350602,11.323 4.625,9.85449398 4.625,8.043 Z'/%3E%3C/svg%3E");
  background-size: contain;
  background-repeat: no-repeat;
  background-position: center;
}
.address.selected .arrow {
  background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="20" height="16" viewBox="0 0 20 16"><path fill="%23da0082" d="M0 15C0 14.4477153.44771525 14 1 14L19 14C19.5522847 14 20 14.4477153 20 15 20 15.5522847 19.5522847 16 19 16L1 16C.44771525 16 0 15.5522847 0 15M0 8C0 7.44771525.44771525 7 1 7L19 7C19.5522847 7 20 7.44771525 20 8 20 8.55228475 19.5522847 9 19 9L1 9C.44771525 9 0 8.55228475 0 8M0 1C0 .44771525.44771525 0 1 0L19 0C19.5522847 0 20 .44771525 20 1 20 1.55228475 19.5522847 2 19 2L1 2C.44771525 2 0 1.55228475 0 1"/></svg>');
  background-size: contain;
  background-repeat: no-repeat;
  background-position: center;
}

.pin, .arrow {
  flex: 0 0 13px;
  height: 18px;
  margin-right: 12px;
}

.pin {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='23' viewBox='0 0 16 23'%3E%3Cpath fill='%231f88e5' d='M7.904,22.449 C8.296,22.449 8.646,22.219 8.646,21.802 C8.646,16.597 15.808,13.584 15.808,7.904 C15.808,3.53874134 12.2692587,1.19949929e-24 7.904,1.19949929e-24 C3.53874134,1.19949929e-24 0,3.53874134 0,7.904 C0,13.583 7.162,16.597 7.162,21.802 C7.162,22.227 7.511,22.449 7.904,22.449 L7.904,22.449 Z M4.625,8.043 C4.625,6.23150602 6.09350602,4.763 7.905,4.763 C9.71649398,4.763 11.185,6.23150602 11.185,8.043 C11.185,9.85449398 9.71649398,11.323 7.905,11.323 C6.09350602,11.323 4.625,9.85449398 4.625,8.043 Z'/%3E%3C/svg%3E");
  background-size: contain;
  background-repeat: no-repeat;
  background-position: center;
}

.arrow {
  background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="20" height="16" viewBox="0 0 20 16"><path fill="%231f88e5" d="M0 15C0 14.4477153.44771525 14 1 14L19 14C19.5522847 14 20 14.4477153 20 15 20 15.5522847 19.5522847 16 19 16L1 16C.44771525 16 0 15.5522847 0 15M0 8C0 7.44771525.44771525 7 1 7L19 7C19.5522847 7 20 7.44771525 20 8 20 8.55228475 19.5522847 9 19 9L1 9C.44771525 9 0 8.55228475 0 8M0 1C0 .44771525.44771525 0 1 0L19 0C19.5522847 0 20 .44771525 20 1 20 1.55228475 19.5522847 2 19 2L1 2C.44771525 2 0 1.55228475 0 1"/></svg>');
  background-size: contain;
  background-repeat: no-repeat;
  background-position: center;
}


    

Auto Suggest

Now that we have the visual components to display our results, we can proceed to create the JavaScript functionality. Let’s start by creating a function to auto-suggest possible what3words addresses to the user. This feature operates in a manner similar to our Localities API Autocomplete feature.

Auto Suggest Promise
          debouncedAutosuggestW3W(value)
    .then(({ suggestions }) => {
      clearSection(results);
      const list = document.createElement("ul");
      if (suggestions) {
        suggestions.forEach((suggestion) => {
          const item = document.createElement("li");
          item.className = "suggestion";
          item.id = suggestion.rank.toString();
          item.innerHTML = `<div class="words">${suggestion.words}</div><div class="nearest">Nearest place : ${suggestion.nearestPlace}</div>`;
          item.dataset.word = suggestion.words;
          item.onclick = () => {
            w3wClickCallback(suggestion);
          };
          list.appendChild(item);
        });
        results.appendChild(list);
      } else {
        const failed = document.createElement("div");
        failed.innerHTML = "Failed to load suggestions";
        results.appendChild(failed);
      }

      displaySection(resultsContainer, "flex");
    })
    .catch((error) => {
      console.error(error);
    });

    
          debouncedAutosuggestW3W(value)
    .then(({ suggestions }) => {
      clearSection(results);

      const list = document.createElement("ul");

      if (suggestions) {
        suggestions.forEach((suggestion) => {
          const item = document.createElement("li");

          item.className = "suggestion";
          item.id = suggestion.rank.toString();
          item.innerHTML = `<div class="words">${suggestion.words}</div><div class="nearest">Nearest place : ${suggestion.nearestPlace}</div>`;
          item.dataset.word = suggestion.words;
          item.onclick = () => {
            w3wClickCallback(suggestion);
          };

          list.appendChild(item);
        });
        results.appendChild(list);
      } else {
        const failed = document.createElement("div");

        failed.innerHTML = "Failed to load suggestions";
        results.appendChild(failed);
      }

      displaySection(resultsContainer, "flex");
    })
    .catch((error) => {
      console.error(error);
    });

    

The debouncedAutosuggestW3W() function dispatches a request to the /autosuggest endpoint. This function is invoked each time the user enters a new character into the input field. Refer to this section on why debouncing the autocomplete request is important.

Auto Suggest Promise
        function autosuggestW3W(input: string): Promise<What3WordsSuggestionsResponse> {
  return fetch(
    `https://api.woosmap.com/what3words/autosuggest?key=${API_KEY}&input=${input}`,
  ).then((response) => response.json());
}


    
        function autosuggestW3W(input) {
  return fetch(
    `https://api.woosmap.com/what3words/autosuggest?key=${API_KEY}&input=${input}`,
  ).then((response) => response.json());
}


    

Before sending the request, it verifies that the user’s input contains at least two . characters. This is because the /autosuggest endpoint requires a minimum of two words to function correctly.

Upon receiving the response, the function generates and displays a list of suggestions. Click event listeners are added to each suggestion. These listeners update the input field value to the clicked suggestion and then invoke the getPossibleAddress() function, which we will develop in the subsequent section.

Converting a what3words address into a street address

Now that we can retrieve a what3words address, we need to create the functions to transform it into possible street addresses.

Convert to Address Promise
        function convertToAddress(words: string): Promise<What3WordsAddressesResponse> {
  return fetch(
    `https://api.woosmap.com/what3words/convert-to-address?key=${API_KEY}&words=${words}`,
  ).then((response) => response.json());
}


    
        function convertToAddress(words) {
  return fetch(
    `https://api.woosmap.com/what3words/convert-to-address?key=${API_KEY}&words=${words}`,
  ).then((response) => response.json());
}


    
Convert to Address Call
          convertToAddress(words)
    .then(({ results }) => {
      if (results) {
        displayAddressList(results);
      }
    })
    .catch((error) => {
      console.error(error);
    });

    
          convertToAddress(words)
    .then(({ results }) => {
      if (results) {
        displayAddressList(results);
      }
    })
    .catch((error) => {
      console.error(error);
    });

    

This works in a similar way as before, we clear the state of the integration and then we make a request to the /convert-to-address endpoint. The response from this call is then displayed as a list of street address suggestions, each one of them containing a public_id. Use this public_id to request Localities details endpoint and retrieve geometry (lat/lng) and address components.

Convert to Address
        function getLocalitiesDetails(
  publicId: string,
): Promise<woosmap.map.localities.LocalitiesDetailsResponse> {
  return fetch(
    `https://api.woosmap.com/localities/details/?key=${API_KEY}&public_id=${publicId}`,
  ).then((response) => response.json());
}


    
        function getLocalitiesDetails(publicId) {
  return fetch(
    `https://api.woosmap.com/localities/details/?key=${API_KEY}&public_id=${publicId}`,
  ).then((response) => response.json());
}


    

You are now ready to display results on a map with a marker and populate the information container in the bottom right of the map.

The demo code fetches the detailed data for the first address of the list in order to display it automatically on the map. The other addresses are fetched on click. Then we can fill the detailed information container using the full address information.

Fill Address Details
        function displayAddressList(addressDetails) {
  backToAddressList();
  const addressListContainer = addressList;
  const newAddressList = document.createElement("ul");
  const fragment = document.createDocumentFragment();

  if (!Array.isArray(addressDetails) || addressDetails.length === 0) {
    const line = document.createElement("li");
    line.className = "not-found";
    line.textContent =
      "what3words address invalid or no street address found within 200m of its location.";
    newAddressList.appendChild(line);
  } else {
    addressDetails.forEach((address, index) => {
      const line = document.createElement("li");
      line.className = "address";
      line.addEventListener("click", (event) => {
        if (address.public_id) {
          getAddressDetails(line, address.public_id);
        } else {
          displaySubBuildings(line, address.sub_buildings);
        }
      });
      line.innerHTML = `<span class='${
        address.public_id ? "pin" : "arrow"
      }'></span><span>${address.description}</span>`;
      fragment.appendChild(line);
    });
  }
  newAddressList.appendChild(fragment);
  addressListContainer.appendChild(newAddressList);
}


    
        function displayAddressList(addressDetails) {
  backToAddressList();

  const addressListContainer = addressList;
  const newAddressList = document.createElement("ul");
  const fragment = document.createDocumentFragment();

  if (!Array.isArray(addressDetails) || addressDetails.length === 0) {
    const line = document.createElement("li");

    line.className = "not-found";
    line.textContent =
      "what3words address invalid or no street address found within 200m of its location.";
    newAddressList.appendChild(line);
  } else {
    addressDetails.forEach((address, index) => {
      const line = document.createElement("li");

      line.className = "address";
      line.addEventListener("click", (event) => {
        if (address.public_id) {
          getAddressDetails(line, address.public_id);
        } else {
          displaySubBuildings(line, address.sub_buildings);
        }
      });
      line.innerHTML = `<span class='${address.public_id ? "pin" : "arrow"}'></span><span>${address.description}</span>`;
      fragment.appendChild(line);
    });
  }

  newAddressList.appendChild(fragment);
  addressListContainer.appendChild(newAddressList);
}


    

Displaying it all on a Woosmap Map

Of course, it’s hard to put a marker on a Map without a Map, so the last Javascript file we need to create is src/index.js. In here we will initialise the input listener and create the Woosmap Map.

Creating a Woosmap Map is super easy…

Woosmap Map
          map = new woosmap.map.Map(document.getElementById("map") as HTMLElement, {
    center: {
      lat: 48.8534,
      lng: 2.3488,
    },
    disableDefaultUI: true,
    gestureHandling: "greedy",
    zoom: 5,
    styles: [
      {
        featureType: "poi",
        stylers: [{ visibility: "off" }],
      },
    ],
  });

    
          map = new woosmap.map.Map(document.getElementById("map"), {
    center: {
      lat: 48.8534,
      lng: 2.3488,
    },
    disableDefaultUI: true,
    gestureHandling: "greedy",
    zoom: 5,
    styles: [
      {
        featureType: "poi",
        stylers: [{ visibility: "off" }],
      },
    ],
  });

    

The listener on the input uses a debounce function to stop making multiple requests from quick user input.

Conclusion

Voilà, easily take your users what3words address and convert it into a street address.

We hope this how-to guide shows you the power of our partnership with what3words. This integration is a great example of how to use these APIs in a few different use cases. For example, taking users address quickly at checkout or to allow delivery drivers to take a what3words address but get an actual street address to route too.

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