Localities Nearby POI
Use the LocalitiesService to find out nearby POI.
Example
Localities Nearby POI
const availableCategories = [
"transit.station",
"transit.station.airport",
"transit.station.rail",
"business",
"business.cinema",
"business.theatre",
"business.nightclub",
"business.finance",
"business.finance.bank",
"business.fuel",
"business.parking",
"business.mall",
"business.food_and_drinks",
"business.food_and_drinks.bar",
"business.food_and_drinks.biergarten",
"business.food_and_drinks.cafe",
"business.food_and_drinks.fast_food",
"business.food_and_drinks.pub",
"business.food_and_drinks.restaurant",
"business.food_and_drinks.food_court",
"business.shop",
"business.shop.mall",
"business.shop.bakery",
"business.shop.butcher",
"business.shop.library",
"business.shop.grocery",
"business.shop.sports",
"business.shop.toys",
"business.shop.clothes",
"business.shop.furniture",
"business.shop.electronics",
"education",
"education.school",
"education.kindergarten",
"education.university",
"education.college",
"education.library",
"hospitality",
"hospitality.hotel",
"hospitality.hostel",
"hospitality.guest_house",
"hospitality.bed_and_breakfast",
"hospitality.motel",
"medical",
"medical.hospital",
"medical.pharmacy",
"medical.clinic",
"tourism",
"tourism.attraction",
"tourism.attraction.amusement_park",
"tourism.attraction.zoo",
"tourism.attraction.aquarium",
"tourism.monument",
"tourism.monument.castle",
"tourism.museum",
"government",
"park",
"place_of_worship",
"police",
"post_office",
"sports",
];
const categories: Set<string> = new Set();
let map: woosmap.map.Map;
let results: HTMLOListElement;
let nearbyCircle: woosmap.map.Circle;
let marker: woosmap.map.Marker;
let localitiesService: woosmap.map.LocalitiesService;
let autocompleteRequest: woosmap.map.localities.LocalitiesAutocompleteRequest;
let nearbyRequest: woosmap.map.localities.LocalitiesNearbyRequest;
const buildTree = (availableCategories: string[]) => {
const tree = {};
availableCategories.forEach((category) => {
const parts = category.split(".");
let node = tree;
parts.forEach((part) => {
node[part] = node[part] || {};
node = node[part];
});
});
return tree;
};
const createList = (node: any, prefix = "") => {
const ul = document.createElement("ul");
for (const key in node) {
const fullKey = prefix ? `${prefix}.${key}` : key;
const li = document.createElement("li");
const checkbox = document.createElement("input");
checkbox.type = "checkbox";
checkbox.id = `category-${fullKey}`;
checkbox.name = "categories";
checkbox.classList.add("category");
checkbox.value = fullKey;
const label = document.createElement("label");
label.htmlFor = `category-${fullKey}`;
label.textContent = key;
li.appendChild(checkbox);
li.appendChild(label);
const children = createList(node[key], fullKey);
if (children) {
li.appendChild(children);
}
ul.appendChild(li);
}
return ul.childElementCount ? ul : null;
};
function initMap() {
map = new window.woosmap.map.Map(
document.getElementById("map") as HTMLElement,
{
center: { lat: 40.71399, lng: -74.00499 },
zoom: 14,
styles: [
{
featureType: "point_of_interest",
elementType: "all",
stylers: [
{
visibility: "on",
},
],
},
],
},
);
localitiesService = new woosmap.map.LocalitiesService();
map.addListener("click", (e) => {
handleRadius(nearbyRequest.radius || 1000, e.latlng);
});
autocompleteRequest = {
input: "",
types: ["locality", "postal_code"],
};
nearbyRequest = {
types: "point_of_interest",
location: map.getCenter(),
radius: 1000,
categories: "",
page: 1,
limit: 10,
};
marker = new woosmap.map.Marker({
position: { lat: 0, lng: 0 },
icon: {
url: "https://images.woosmap.com/marker.png",
scaledSize: {
height: 50,
width: 32,
},
},
});
initUI();
performNearbyRequest();
}
function buildCategoriesList() {
const tree = buildTree(availableCategories);
const list = createList(tree);
(
document.querySelector(".categoriesOptions__list") as HTMLDivElement
).appendChild(list as HTMLElement);
document.querySelectorAll(".category").forEach((el) =>
el.addEventListener("click", (ev) => {
const inputElement = ev.target as HTMLInputElement;
const parentLi = inputElement.closest("li");
const childrenCheckboxes = parentLi
? Array.from(parentLi.children)
.filter((child) => child !== inputElement)
.flatMap((child) => Array.from(child.querySelectorAll(".category")))
: [];
if (inputElement.checked) {
categories.add(inputElement.value);
if (childrenCheckboxes.length > 0) {
childrenCheckboxes.forEach((checkbox) => {
(checkbox as HTMLInputElement).disabled = true;
});
}
} else {
categories.delete(inputElement.value);
if (childrenCheckboxes.length > 0) {
childrenCheckboxes.forEach((checkbox) => {
(checkbox as HTMLInputElement).disabled = false;
});
}
}
performNearbyRequest();
}),
);
}
function handleRadius(
radiusValue: number,
center?: woosmap.map.LatLng | woosmap.map.LatLngLiteral | null,
) {
const label = document.getElementById("radius-label");
if (radiusValue < 1000 && label) {
label.innerHTML = `${radiusValue} m`;
} else if (label) {
label.innerHTML = `${radiusValue / 1000} km`;
}
// circle.getBounds() returns wrong LatLngBounds -> const bounds = nearbyCircle.getBounds();
// TODO fixed circle getBounds
// used this log scale to compute the zoom level between z18 (radius 10m) and z7 (radius 50km)
const zoomLevel = Math.round(
18 - (Math.log(radiusValue / 10) / Math.log(50000 / 10)) * (18 - 7),
);
map.flyTo({ center: center || nearbyCircle.getCenter(), zoom: zoomLevel });
nearbyRequest.radius = radiusValue;
performNearbyRequest(new woosmap.map.LatLng(center || nearbyCircle.getCenter()));
}
function initUI() {
results = document.querySelector("#results") as HTMLOListElement;
buildCategoriesList();
const debouncedHandleRadius = debounce(handleRadius, 300);
document.getElementById("radius")?.addEventListener("input", (e) => {
const radiusValue = parseInt((e.target as HTMLInputElement).value);
debouncedHandleRadius(radiusValue);
});
document.getElementById("page-previous")?.addEventListener("click", previousPage);
document.getElementById("page-next")?.addEventListener("click", nextPage);
}
function previousPage(){
let newQuery = true
if(nearbyRequest.page && nearbyRequest.page > 1) {
nearbyRequest.page--;
newQuery=false;
}
performNearbyRequest(null, newQuery);
}
function nextPage(){
let newQuery = true
if(nearbyRequest.page) {
nearbyRequest.page++;
newQuery=false;
}
performNearbyRequest(null, newQuery);
}
function performNearbyRequest(
overrideCenter: woosmap.map.LatLng | null = null,
newQuery = true,
) {
const requestCenter = overrideCenter || map.getCenter();
nearbyRequest.location = requestCenter;
nearbyRequest.categories = "";
if (categories.size > 0) {
nearbyRequest.categories = Array.from(categories).join("|");
}
if (newQuery) {
nearbyRequest.page = 1;
}
results.innerHTML = "";
if (nearbyRequest.radius && nearbyRequest.radius > 50000) {
results.innerHTML = "<li style='color: red;'><b>Radius should be less than or equal to 50km.</b></li>";
return;
}
else if (nearbyRequest.radius && nearbyRequest.radius < 10) {
results.innerHTML = "<li style='color: red;'><b>Radius should be greater than or equal to 10m.</b></li>";
return;
}
//@ts-ignore
localitiesService.nearby(nearbyRequest).then((responseJson) => {
drawNearbyZone(requestCenter, nearbyRequest.radius);
updateResults(responseJson, requestCenter);
});
}
function drawNearbyZone(center, radius) {
if (nearbyCircle) {
nearbyCircle.setMap(null);
}
nearbyCircle = new woosmap.map.Circle({
map,
center: center,
radius: radius,
strokeColor: "#1165c2",
strokeOpacity: 0.8,
strokeWeight: 2,
fillColor: "#3283c5",
fillOpacity: 0.2,
});
}
function updatePagination(pagination: woosmap.map.localities.LocalitiesNearbyPagination) {
if (pagination.next_page) {
document.getElementById("page-next")?.removeAttribute("disabled");
} else {
document.getElementById("page-next")?.setAttribute("disabled", "true");
}
if(pagination.previous_page) {
document.getElementById("page-previous")?.removeAttribute("disabled");
} else {
document.getElementById("page-previous")?.setAttribute("disabled", "true");
}
}
function updateResults(response: woosmap.map.localities.LocalitiesNearbyResponse, center) {
updatePagination(response.pagination);
response.results.forEach((result:woosmap.map.localities.LocalitiesNearbyResult) => {
const distance = measure(
center.lat(),
center.lng(),
result.geometry.location.lat,
result.geometry.location.lng,
);
const resultListItem = document.createElement("li");
resultListItem.innerHTML = `
<b>${result.name}</b>
<i>${result.categories}</i>
<span class="distance">${distance.toFixed(0)}m</span>
`;
resultListItem.addEventListener("click", () => {
map.setCenter({
lat: result.geometry.location.lat,
lng: result.geometry.location.lng,
});
marker.setPosition({
lat: result.geometry.location.lat,
lng: result.geometry.location.lng,
});
marker.setMap(map);
});
results.appendChild(resultListItem);
});
}
function measure(lat1, lon1, lat2, lon2) {
// generally used geo measurement function
const R = 6378.137; // Radius of earth in KM
const dLat = (lat2 * Math.PI) / 180 - (lat1 * Math.PI) / 180;
const dLon = (lon2 * Math.PI) / 180 - (lon1 * Math.PI) / 180;
const a =
Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos((lat1 * Math.PI) / 180) *
Math.cos((lat2 * Math.PI) / 180) *
Math.sin(dLon / 2) *
Math.sin(dLon / 2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
const d = R * c;
return d * 1000; // meters
}
const inputElement = document.getElementById(
"autocomplete-input",
) as HTMLInputElement;
const suggestionsList = document.getElementById(
"suggestions-list",
) as HTMLUListElement;
const clearSearchBtn = document.getElementsByClassName(
"clear-searchButton",
)[0] as HTMLButtonElement;
if (inputElement && suggestionsList) {
inputElement.addEventListener("input", handleAutocomplete);
inputElement.addEventListener("keydown", (event) => {
if (event.key === "Enter") {
const firstLi = suggestionsList.querySelector("li");
if (firstLi) {
firstLi.click();
}
}
});
}
clearSearchBtn.addEventListener("click", () => {
inputElement.value = "";
suggestionsList.style.display = "none";
clearSearchBtn.style.display = "none";
if (marker) {
marker.setMap(null);
}
inputElement.focus();
});
function handleAutocomplete(): void {
if (inputElement && suggestionsList) {
autocompleteRequest.input = inputElement.value;
if (autocompleteRequest.input) {
localitiesService
.autocomplete(autocompleteRequest)
.then((localities) => displaySuggestions(localities))
.catch((error) =>
console.error("Error autocomplete localities:", error),
);
} else {
suggestionsList.style.display = "none";
clearSearchBtn.style.display = "none";
}
}
}
function handleDetails(publicId: string) {
localitiesService
.getDetails({ publicId })
.then((locality) => displayLocality(locality.result))
.catch((error) => console.error("Error getting locality details:", error));
}
function displayLocality(
locality: woosmap.map.localities.LocalitiesDetailsResult,
) {
if (locality?.geometry && nearbyRequest.radius) {
map.setCenter(locality.geometry.location);
handleRadius(nearbyRequest.radius, locality.geometry.location);
}
}
function displaySuggestions(
localitiesPredictions: woosmap.map.localities.LocalitiesAutocompleteResponse,
) {
if (inputElement && suggestionsList) {
suggestionsList.innerHTML = "";
if (localitiesPredictions.localities.length > 0 && autocompleteRequest["input"]) {
localitiesPredictions.localities.forEach((locality) => {
const li = document.createElement("li");
li.textContent = locality.description ?? "";
li.addEventListener("click", () => {
inputElement.value = locality.description ?? "";
suggestionsList.style.display = "none";
handleDetails(locality.public_id);
});
suggestionsList.appendChild(li);
});
suggestionsList.style.display = "block";
clearSearchBtn.style.display = "block";
} else {
suggestionsList.style.display = "none";
}
}
}
document.addEventListener("click", (event) => {
const targetElement = event.target as Element;
const isClickInsideAutocomplete = targetElement.closest(
"#autocomplete-container",
);
if (!isClickInsideAutocomplete && suggestionsList) {
suggestionsList.style.display = "none";
}
});
function debounce(func: (...args: any[]) => void, wait: number) {
let timeout: NodeJS.Timeout;
return function executedFunction(...args: any[]) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
document.addEventListener('DOMContentLoaded', () => {
const radiusInput = document.getElementById('radius') as HTMLInputElement;
const radiusLabel = document.getElementById('radius-label') as HTMLLabelElement;
if (!radiusInput || !radiusLabel) {
console.error('Elements not found in the DOM.');
return;
}
// Update the range input when the label content is modified
radiusLabel.addEventListener('blur', () => {
const parsedValue = parseLabel(radiusLabel.textContent || '');
if (parsedValue !== null) {
radiusInput.value = parsedValue.toString();
handleRadius(parsedValue)
} else {
// Revert to the current range value if parsing fails
radiusLabel.textContent = formatValue(parseInt(radiusInput.value, 10));
}
});
radiusLabel.addEventListener('keypress', (e: KeyboardEvent) => {
if (e.key === 'Enter') {
e.preventDefault(); // Prevent line breaks
radiusLabel.blur(); // Trigger the blur event to validate and update
}
});
// Format the value in meters to "km" or "m" for display
const formatValue = (value: number): string => {
return value >= 1000 ? `${value / 1000} km` : `${value} m`;
};
// Parse the label content back to meters
const parseLabel = (label: string): number | null => {
const kmMatch = label.match(/^(\d+(?:\.\d+)?)\s*km$/i);
const mMatch = label.match(/^(\d+)\s*m$/i);
if (kmMatch) {
return Math.round(parseFloat(kmMatch[1]) * 1000); // Convert km to meters
} else if (mMatch) {
return parseInt(mMatch[1], 10); // Keep value in meters
}
return null; // Invalid input
};
});
declare global {
interface Window {
initMap: () => void;
}
}
window.initMap = initMap;
const availableCategories = [
"transit.station",
"transit.station.airport",
"transit.station.rail",
"business",
"business.cinema",
"business.theatre",
"business.nightclub",
"business.finance",
"business.finance.bank",
"business.fuel",
"business.parking",
"business.mall",
"business.food_and_drinks",
"business.food_and_drinks.bar",
"business.food_and_drinks.biergarten",
"business.food_and_drinks.cafe",
"business.food_and_drinks.fast_food",
"business.food_and_drinks.pub",
"business.food_and_drinks.restaurant",
"business.food_and_drinks.food_court",
"business.shop",
"business.shop.mall",
"business.shop.bakery",
"business.shop.butcher",
"business.shop.library",
"business.shop.grocery",
"business.shop.sports",
"business.shop.toys",
"business.shop.clothes",
"business.shop.furniture",
"business.shop.electronics",
"education",
"education.school",
"education.kindergarten",
"education.university",
"education.college",
"education.library",
"hospitality",
"hospitality.hotel",
"hospitality.hostel",
"hospitality.guest_house",
"hospitality.bed_and_breakfast",
"hospitality.motel",
"medical",
"medical.hospital",
"medical.pharmacy",
"medical.clinic",
"tourism",
"tourism.attraction",
"tourism.attraction.amusement_park",
"tourism.attraction.zoo",
"tourism.attraction.aquarium",
"tourism.monument",
"tourism.monument.castle",
"tourism.museum",
"government",
"park",
"place_of_worship",
"police",
"post_office",
"sports",
];
const categories = new Set();
let map;
let results;
let nearbyCircle;
let marker;
let localitiesService;
let autocompleteRequest;
let nearbyRequest;
const buildTree = (availableCategories) => {
const tree = {};
availableCategories.forEach((category) => {
const parts = category.split(".");
let node = tree;
parts.forEach((part) => {
node[part] = node[part] || {};
node = node[part];
});
});
return tree;
};
const createList = (node, prefix = "") => {
const ul = document.createElement("ul");
for (const key in node) {
const fullKey = prefix ? `${prefix}.${key}` : key;
const li = document.createElement("li");
const checkbox = document.createElement("input");
checkbox.type = "checkbox";
checkbox.id = `category-${fullKey}`;
checkbox.name = "categories";
checkbox.classList.add("category");
checkbox.value = fullKey;
const label = document.createElement("label");
label.htmlFor = `category-${fullKey}`;
label.textContent = key;
li.appendChild(checkbox);
li.appendChild(label);
const children = createList(node[key], fullKey);
if (children) {
li.appendChild(children);
}
ul.appendChild(li);
}
return ul.childElementCount ? ul : null;
};
function initMap() {
map = new window.woosmap.map.Map(document.getElementById("map"), {
center: { lat: 40.71399, lng: -74.00499 },
zoom: 14,
styles: [
{
featureType: "point_of_interest",
elementType: "all",
stylers: [
{
visibility: "on",
},
],
},
],
});
localitiesService = new woosmap.map.LocalitiesService();
map.addListener("click", (e) => {
handleRadius(nearbyRequest.radius || 1000, e.latlng);
});
autocompleteRequest = {
input: "",
types: ["locality", "postal_code"],
};
nearbyRequest = {
types: "point_of_interest",
location: map.getCenter(),
radius: 1000,
categories: "",
page: 1,
limit: 10,
};
marker = new woosmap.map.Marker({
position: { lat: 0, lng: 0 },
icon: {
url: "https://images.woosmap.com/marker.png",
scaledSize: {
height: 50,
width: 32,
},
},
});
initUI();
performNearbyRequest();
}
function buildCategoriesList() {
const tree = buildTree(availableCategories);
const list = createList(tree);
document.querySelector(".categoriesOptions__list").appendChild(list);
document.querySelectorAll(".category").forEach((el) =>
el.addEventListener("click", (ev) => {
const inputElement = ev.target;
const parentLi = inputElement.closest("li");
const childrenCheckboxes = parentLi
? Array.from(parentLi.children)
.filter((child) => child !== inputElement)
.flatMap((child) => Array.from(child.querySelectorAll(".category")))
: [];
if (inputElement.checked) {
categories.add(inputElement.value);
if (childrenCheckboxes.length > 0) {
childrenCheckboxes.forEach((checkbox) => {
checkbox.disabled = true;
});
}
} else {
categories.delete(inputElement.value);
if (childrenCheckboxes.length > 0) {
childrenCheckboxes.forEach((checkbox) => {
checkbox.disabled = false;
});
}
}
performNearbyRequest();
}),
);
}
function handleRadius(radiusValue, center) {
const label = document.getElementById("radius-label");
if (radiusValue < 1000 && label) {
label.innerHTML = `${radiusValue} m`;
} else if (label) {
label.innerHTML = `${radiusValue / 1000} km`;
}
// circle.getBounds() returns wrong LatLngBounds -> const bounds = nearbyCircle.getBounds();
// TODO fixed circle getBounds
// used this log scale to compute the zoom level between z18 (radius 10m) and z7 (radius 50km)
const zoomLevel = Math.round(
18 - (Math.log(radiusValue / 10) / Math.log(50000 / 10)) * (18 - 7),
);
map.flyTo({ center: center || nearbyCircle.getCenter(), zoom: zoomLevel });
nearbyRequest.radius = radiusValue;
performNearbyRequest(
new woosmap.map.LatLng(center || nearbyCircle.getCenter()),
);
}
function initUI() {
results = document.querySelector("#results");
buildCategoriesList();
const debouncedHandleRadius = debounce(handleRadius, 300);
document.getElementById("radius")?.addEventListener("input", (e) => {
const radiusValue = parseInt(e.target.value);
debouncedHandleRadius(radiusValue);
});
document
.getElementById("page-previous")
?.addEventListener("click", previousPage);
document.getElementById("page-next")?.addEventListener("click", nextPage);
}
function previousPage() {
let newQuery = true;
if (nearbyRequest.page && nearbyRequest.page > 1) {
nearbyRequest.page--;
newQuery = false;
}
performNearbyRequest(null, newQuery);
}
function nextPage() {
let newQuery = true;
if (nearbyRequest.page) {
nearbyRequest.page++;
newQuery = false;
}
performNearbyRequest(null, newQuery);
}
function performNearbyRequest(overrideCenter = null, newQuery = true) {
const requestCenter = overrideCenter || map.getCenter();
nearbyRequest.location = requestCenter;
nearbyRequest.categories = "";
if (categories.size > 0) {
nearbyRequest.categories = Array.from(categories).join("|");
}
if (newQuery) {
nearbyRequest.page = 1;
}
results.innerHTML = "";
if (nearbyRequest.radius && nearbyRequest.radius > 50000) {
results.innerHTML =
"<li style='color: red;'><b>Radius should be less than or equal to 50km.</b></li>";
return;
} else if (nearbyRequest.radius && nearbyRequest.radius < 10) {
results.innerHTML =
"<li style='color: red;'><b>Radius should be greater than or equal to 10m.</b></li>";
return;
}
//@ts-ignore
localitiesService.nearby(nearbyRequest).then((responseJson) => {
drawNearbyZone(requestCenter, nearbyRequest.radius);
updateResults(responseJson, requestCenter);
});
}
function drawNearbyZone(center, radius) {
if (nearbyCircle) {
nearbyCircle.setMap(null);
}
nearbyCircle = new woosmap.map.Circle({
map,
center: center,
radius: radius,
strokeColor: "#1165c2",
strokeOpacity: 0.8,
strokeWeight: 2,
fillColor: "#3283c5",
fillOpacity: 0.2,
});
}
function updatePagination(pagination) {
if (pagination.next_page) {
document.getElementById("page-next")?.removeAttribute("disabled");
} else {
document.getElementById("page-next")?.setAttribute("disabled", "true");
}
if (pagination.previous_page) {
document.getElementById("page-previous")?.removeAttribute("disabled");
} else {
document.getElementById("page-previous")?.setAttribute("disabled", "true");
}
}
function updateResults(response, center) {
updatePagination(response.pagination);
response.results.forEach((result) => {
const distance = measure(
center.lat(),
center.lng(),
result.geometry.location.lat,
result.geometry.location.lng,
);
const resultListItem = document.createElement("li");
resultListItem.innerHTML = `
<b>${result.name}</b>
<i>${result.categories}</i>
<span class="distance">${distance.toFixed(0)}m</span>
`;
resultListItem.addEventListener("click", () => {
map.setCenter({
lat: result.geometry.location.lat,
lng: result.geometry.location.lng,
});
marker.setPosition({
lat: result.geometry.location.lat,
lng: result.geometry.location.lng,
});
marker.setMap(map);
});
results.appendChild(resultListItem);
});
}
function measure(lat1, lon1, lat2, lon2) {
// generally used geo measurement function
const R = 6378.137; // Radius of earth in KM
const dLat = (lat2 * Math.PI) / 180 - (lat1 * Math.PI) / 180;
const dLon = (lon2 * Math.PI) / 180 - (lon1 * Math.PI) / 180;
const a =
Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos((lat1 * Math.PI) / 180) *
Math.cos((lat2 * Math.PI) / 180) *
Math.sin(dLon / 2) *
Math.sin(dLon / 2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
const d = R * c;
return d * 1000; // meters
}
const inputElement = document.getElementById("autocomplete-input");
const suggestionsList = document.getElementById("suggestions-list");
const clearSearchBtn = document.getElementsByClassName("clear-searchButton")[0];
if (inputElement && suggestionsList) {
inputElement.addEventListener("input", handleAutocomplete);
inputElement.addEventListener("keydown", (event) => {
if (event.key === "Enter") {
const firstLi = suggestionsList.querySelector("li");
if (firstLi) {
firstLi.click();
}
}
});
}
clearSearchBtn.addEventListener("click", () => {
inputElement.value = "";
suggestionsList.style.display = "none";
clearSearchBtn.style.display = "none";
if (marker) {
marker.setMap(null);
}
inputElement.focus();
});
function handleAutocomplete() {
if (inputElement && suggestionsList) {
autocompleteRequest.input = inputElement.value;
if (autocompleteRequest.input) {
localitiesService
.autocomplete(autocompleteRequest)
.then((localities) => displaySuggestions(localities))
.catch((error) =>
console.error("Error autocomplete localities:", error),
);
} else {
suggestionsList.style.display = "none";
clearSearchBtn.style.display = "none";
}
}
}
function handleDetails(publicId) {
localitiesService
.getDetails({ publicId })
.then((locality) => displayLocality(locality.result))
.catch((error) => console.error("Error getting locality details:", error));
}
function displayLocality(locality) {
if (locality?.geometry && nearbyRequest.radius) {
map.setCenter(locality.geometry.location);
handleRadius(nearbyRequest.radius, locality.geometry.location);
}
}
function displaySuggestions(localitiesPredictions) {
if (inputElement && suggestionsList) {
suggestionsList.innerHTML = "";
if (
localitiesPredictions.localities.length > 0 &&
autocompleteRequest["input"]
) {
localitiesPredictions.localities.forEach((locality) => {
const li = document.createElement("li");
li.textContent = locality.description ?? "";
li.addEventListener("click", () => {
inputElement.value = locality.description ?? "";
suggestionsList.style.display = "none";
handleDetails(locality.public_id);
});
suggestionsList.appendChild(li);
});
suggestionsList.style.display = "block";
clearSearchBtn.style.display = "block";
} else {
suggestionsList.style.display = "none";
}
}
}
document.addEventListener("click", (event) => {
const targetElement = event.target;
const isClickInsideAutocomplete = targetElement.closest(
"#autocomplete-container",
);
if (!isClickInsideAutocomplete && suggestionsList) {
suggestionsList.style.display = "none";
}
});
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
document.addEventListener("DOMContentLoaded", () => {
const radiusInput = document.getElementById("radius");
const radiusLabel = document.getElementById("radius-label");
if (!radiusInput || !radiusLabel) {
console.error("Elements not found in the DOM.");
return;
}
// Update the range input when the label content is modified
radiusLabel.addEventListener("blur", () => {
const parsedValue = parseLabel(radiusLabel.textContent || "");
if (parsedValue !== null) {
radiusInput.value = parsedValue.toString();
handleRadius(parsedValue);
} else {
// Revert to the current range value if parsing fails
radiusLabel.textContent = formatValue(parseInt(radiusInput.value, 10));
}
});
radiusLabel.addEventListener("keypress", (e) => {
if (e.key === "Enter") {
e.preventDefault(); // Prevent line breaks
radiusLabel.blur(); // Trigger the blur event to validate and update
}
});
// Format the value in meters to "km" or "m" for display
const formatValue = (value) => {
return value >= 1000 ? `${value / 1000} km` : `${value} m`;
};
// Parse the label content back to meters
const parseLabel = (label) => {
const kmMatch = label.match(/^(\d+(?:\.\d+)?)\s*km$/i);
const mMatch = label.match(/^(\d+)\s*m$/i);
if (kmMatch) {
return Math.round(parseFloat(kmMatch[1]) * 1000); // Convert km to meters
} else if (mMatch) {
return parseInt(mMatch[1], 10); // Keep value in meters
}
return null; // Invalid input
};
});
window.initMap = initMap;
html,
body {
height: 100%;
margin: 0;
padding: 0;
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji;
}
#container {
height: 100%;
display: flex;
}
#sidebar {
flex-basis: 12rem;
flex-grow: 1;
max-width: 30rem;
height: 100%;
box-sizing: border-box;
overflow: auto;
}
#map {
flex-basis: 70vw;
flex-grow: 5;
height: 100%;
}
/*
* 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;
}
#sidebar {
flex-basis: 18rem;
box-shadow: 0 -2px 4px 0 rgba(0, 0, 0, 0.12);
z-index: 1;
}
#innerWrapper {
display: flex;
flex-direction: column;
flex-grow: 1;
overflow: hidden;
overflow-y: auto;
padding: 0 10px 40px;
}
#mapContainer {
display: flex;
flex-direction: column;
flex-basis: 70vw;
flex-grow: 5;
position: relative;
}
#map.cursor-crosshair .mapboxgl-canvas-container {
cursor: crosshair !important;
}
.sectionHeader {
background: #f1f1f1;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid #eeeeee;
margin: 20px -10px 5px;
padding: 5px 10px;
color: #222;
}
.sectionHeader span {
font-size: 0.85em;
font-weight: 600;
}
.sectionHeader:first-child {
margin-top: 0;
}
.categoriesOptions {
padding: 0;
margin: 0;
list-style: none;
height: 100%;
background: #fff;
display: flex;
font-size: 13px;
}
.categoriesOptions__list {
width: 100%;
height: 100%;
max-height: 30vh;
overflow: scroll;
}
.categoriesOptions__list ul {
list-style: none;
padding: 0;
margin: 0;
}
.categoriesOptions__list ul li {
padding: 3px 0;
}
.categoriesOptions__list ul li input[type=checkbox] {
margin-right: 3px;
}
.categoriesOptions__list ul li input[type=checkbox]:disabled {
box-shadow: inset 0 0 20px #999;
}
.categoriesOptions__list ul li label {
margin-left: 3px;
font-size: 0.85em;
}
.categoriesOptions__list ul li ul {
margin-left: 10px;
padding-left: 5px;
border-left: 1px solid #ddd;
}
.categoriesOptions__input {
height: 24px;
display: flex;
align-items: baseline;
}
.radius__container {
display: flex;
flex-direction: row;
align-items: center;
}
.radius__container > label {
padding-left: 10px;
}
#page-previous {
margin-right: 5px;
}
ol#results {
list-style-type: none;
margin: 0;
padding-left: 0;
}
ol#results > li {
margin-top: 10px;
background-color: white;
font-size: 10pt;
line-height: 1.2rem;
padding: 5px;
}
ol#results > li > * {
display: block;
}
ol#results > li > .distance {
padding-top: 0.2rem;
font-weight: lighter;
}
ol#results > li:hover {
cursor: pointer;
background-color: #f2f2f2;
}
#radius {
flex: 1; /* Take all remaining space */
}
#radius-label {
border: 1px dashed #ccc;
outline: none;
}
<html>
<head>
<title>Localities Nearby POI</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta charset="utf-8" />
<link rel="stylesheet" type="text/css" href="./style.css" />
<script type="module" src="./index.js"></script>
</head>
<body>
<div id="container">
<div id="sidebar">
<div id="innerWrapper">
<div class="sectionHeader"><span>Categories</span></div>
<div class="categoriesOptions__list"></div>
<div class="sectionHeader"><span>Radius</span></div>
<div class="radius__container">
<input
type="range"
id="radius"
min="100"
max="10000"
value="1000"
step="100"
/><label id="radius-label" contenteditable="true"
>1 km</label
>
</div>
<div class="sectionHeader">
<span>Results</span>
<span is="pagination"
><button id="page-previous" disabled>‹</button
><button id="page-next" disabled>›</button></span
>
</div>
<ol id="results"></ol>
</div>
</div>
<div id="mapContainer">
<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="Search Localities..."
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>
<ul id="suggestions-list"></ul>
</div>
<div id="map"></div>
</div>
</div>
<script
src="https://sdk.woosmap.com/map/map.js?key=woos-48c80350-88aa-333e-835a-07f4b658a9a4&callback=initMap"
defer
></script>
</body>
</html>
Running the Sample Locally
Before you can run this sample on your local machine, you need to have Git and Node.js installed. If they’re not already installed, follow these instructions to get them set up.
Once you have Git and Node.js installed, you can run the sample by following these steps:
-
Clone the repository and navigate to the directory of the sample.
-
Install the necessary dependencies.
-
Start running the sample.
Here are the commands you can use in your terminal to do this:
git clone -b sample/localities-nearby-poi https://github.com/woosmap/js-samples.git
cd js-samples
npm i
npm start
You can experiment with other samples by switching to any branch that starts with the pattern sample/SAMPLE_NAME
.
git checkout sample/SAMPLE_NAME
npm i
npm start
Was this article helpful?
Have more questions? Submit a request