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.
- Introduction
- Prerequisites
- Setting up the HTML page
- Auto Suggest
- Converting a what3words address into a street address
- Displaying it all on a Woosmap Map
- Conclusion
Introduction
This guide will explain to you how to build the following integration:
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.visible {
display: block;
}
#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:
- help a user input their what3words address using the /autosuggest endpoint.
- use the /convert-to-address endpoint to return a list of possible street addresses, eventually grouped by building or street number.
- allow the user to choose suitable address from the suggestions returned and get details on it thanks to a /details` on Localities (and therefore benefit from the richness of the Localities API)
Prerequisites
You will need a Woosmap Public Key with both:
- the
what3words
products enabled (Localities
is automatically enabled withwhat3words
). See how to set up your account. - the dedicated domain whitelisted (e.g.
localhost
) for development purposes. Learn about Woosmap API Keys domain restrictions.
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:
<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.visible {
display: block;
}
#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.
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.
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.
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());
}
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.
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.
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…
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.