Searching
Searching through your MapsIndoors data is an key part of a great user experience with your maps. Users can look for places to go, or filter what is shown on the map.
Searches work on all MapsIndoors geodata. It is up to you to create a search experience that fits your use case.
Retrieve Specific Location: getLocation(id)
getLocation(id)
Use-Case 1: Center the Map to the Location
Example 1: Using MapsIndoors Location ID on the Card
This example assumes that you've stored the MapsIndoors Location ID directly on the card, accessible via a custom attribute or some other mechanism. When the card is clicked, getLocation(id)
retrieves the corresponding location, centers the map on it, and sets the zoom level.
card.addEventListener('click', () => {
const locationId = card.getAttribute('data-location-id'); // Assume the MapsIndoors ID is stored in a data attribute
mapsindoors.services.LocationsService.getLocation(locationId).then(location => {
mapsIndoorsInstance.setFloor(location.properties.floor);
mapInstance.setCenter({
lat: location.properties.anchor.coordinates[1],
lng: location.properties.anchor.coordinates[0]
});
mapInstance.setZoom(18);
});
});
Example 2: Using an External Custom ID on the Card
In this example, an external (custom) ID is stored on the card. The getLocationsByExternalId
method is used to fetch locations, taking into account that multiple locations could be returned.
card.addEventListener('click', () => {
const externalId = card.getAttribute('data-external-id'); // Assume the external ID is stored in a data attribute
// Use getLocationsByExternalId to fetch locations by their external IDs
mapsindoors.services.LocationsService.getLocationsByExternalId(externalId).then(locations => {
if (locations.length > 0) {
const location = locations[0]; // Take the first location if multiple are returned
// Set the floor and center the map
mapsIndoorsInstance.setFloor(location.properties.floor);
mapInstance.setCenter({
lat: location.properties.anchor.coordinates[1],
lng: location.properties.anchor.coordinates[0]
});
mapInstance.setZoom(18);
} else {
// Handle the case where no locations are returned for the given external ID
console.warn(`No locations found for external ID ${externalId}`);
}
});
});
Both examples work off a click event attached to a card element. The key difference lies in the method used to query MapsIndoors for the location information. Example 1 uses the native MapsIndoors ID, while Example 2 uses an external ID. Both methods then focus the map on the retrieved location.
Use-Case 2: Utilizing MapsIndoors SDK Highlighting and Filtering
Take advantage of using MapsIndoors native filtering and highlighting functionality via the SDK without needing to implement your own custom display logic.
Use-Case 3: Modify Display Rule
Example 1: Customizing Display Rule on Click Event with MapsIndoors Location ID
// Define the rule outside the listener for better readability and reusability
const rule = {
visible: true,
polygonVisible: true,
polygonFillColor: "#FF0000",
polygonFillOpacity: 1,
iconSize: { width: 30, height: 30 },
labelVisible: true,
};
let previousLocationId = null; // Keep track of previously filtered location
card.addEventListener('click', () => {
const locationId = card.getAttribute('data-location-id');
mapsindoors.services.LocationsService.getLocation(locationId).then(location => {
// Optionally set the floor and center the map
mapsIndoorsInstance.setFloor(location.properties.floor);
mapInstance.setCenter({
lat: location.properties.anchor.coordinates[1],
lng: location.properties.anchor.coordinates[0]
});
// Reset display rule for previously filtered location, if any
if (previousLocationId) {
mapsIndoorsInstance.setDisplayRule(previousLocationId, null);
}
// Apply the new display rule to the clicked location
mapsIndoorsInstance.setDisplayRule(location.id, rule);
// Update the previous location ID
previousLocationId = location.id;
});
});
Example 2: Using an External Custom ID
// Define the display rule
const rule = {
visible: true,
polygonVisible: true,
polygonFillColor: "#FF0000",
polygonFillOpacity: 1,
iconSize: { width: 30, height: 30 },
labelVisible: true,
};
let previousLocationId = null; // Keep track of the previously filtered location
card.addEventListener('click', () => {
const externalId = card.getAttribute('data-external-id');
// Use getLocationsByExternalId() to fetch locations by their external IDs
mapsindoors.services.LocationsService.getLocationsByExternalId(externalId).then(locations => {
if (locations.length > 0) {
const location = locations[0]; // Assume the first location is the one to display
// Optionally set the floor and center the map
mapsIndoorsInstance.setFloor(location.properties.floor);
mapInstance.setCenter({
lat: location.properties.anchor.coordinates[1],
lng: location.properties.anchor.coordinates[0]
});
// Reset display rule for the previously filtered location, if any
if (previousLocationId) {
mapsIndoorsInstance.setDisplayRule(previousLocationId, null);
}
// Apply the new display rule to the location
mapsIndoorsInstance.setDisplayRule(location.id, rule);
// Update the ID of the previously filtered location
previousLocationId = location.id;
} else {
console.warn(`No locations found for external ID ${externalId}`);
}
});
});
This code assumes that if multiple locations are returned from getLocationByExternalId()
, the first one is the relevant location for display. The remainder of the code remains largely similar to the previous example, but we're fetching the locations based on an external ID instead of a MapsIndoors Location ID.
Use-Case 4: Add a Popup / Info Window
For this use-case, we're focusing on how to display an information popup or info window when a MapsIndoors location is clicked. Both Mapbox and Google Maps implementations are provided. These popups will show a custom image and a link, allowing developers the flexibility to populate it with any content. Let's assume you're getting back your location object from one of the approaches in Use-Case 1.
Mapbox Implementation
In the Mapbox example, we add an event listener to the MapsIndoors instance that listens for 'click' events. When a location is clicked, we display a Mapbox popup at the location's coordinates. Any previously displayed popup will be removed to avoid clutter.
let currentPopup = null;
export const handleLocationClickForMapbox = (location, mapsIndoorsInstance, mapInstance) => {
if (currentPopup) {
currentPopup.remove();
}
const coords = location.properties.anchor.coordinates;
const popupContent = `
<img src="${'Your_custom_image_url_here'}" alt="${location.properties.description}" width="100" height="100" />
<h2><a href="${'Your_custom_link_here'}" target="_blank">${location.properties.name}</a></h2>
`;
currentPopup = new mapboxgl.Popup({ closeOnClick: true, closeButton: true })
.setLngLat(coords)
.setHTML(popupContent)
.addTo(mapInstance);
};
mapsIndoorsInstance.addListener('click', location => {
handleLocationClickForMapbox(location, mapsIndoorsInstance, mapInstance);
});
Google Maps Implementation
In the Google Maps example, we add an event listener to the MapsIndoors instance to listen for location clicks. When a location is clicked, an info window appears at the coordinates of that location. Similar to the Mapbox example, any previously displayed info window will be closed.
let previousInfoWindow = null;
mapsIndoorsInstance.addListener('click', location => {
if (previousInfoWindow) {
previousInfoWindow.close();
}
const coords = location.properties.anchor.coordinates;
const infoWindowContent = `
<img src="${'Your_custom_image_url_here'}" alt="${location.properties.description}" width="100" height="100" />
<h2><a href="${'Your_custom_link_here'}" target="_blank">${location.properties.name}</a></h2>
`;
const infoWindowOptions = {
content: infoWindowContent,
position: {
lat: coords[1],
lng: coords[0]
}
};
const infoWindow = new google.maps.InfoWindow(infoWindowOptions);
infoWindow.open(googleMapsInstance);
previousInfoWindow = infoWindow;
});
Both implementations ensure that only one popup or info window is open at a given time, closing any previous ones when a new location is clicked. This keeps the map clean and focuses the user's attention on the most recently clicked location.
Retrieve Queried Locations: getLocations(
args opt)
getLocations(
args opt)
To help you in this, there is a range of filters you can apply to the search queries to get the best results. E.g. you can filter by Categories, search only a specific part of the map or search near a Location.
All three return a list of Locations from your solution matching the parameters they are given. The results are ranked upon the three following factors:
If a "near" parameter is set, how close is the origin point to the result?
How well does the search input text match the text of the result?
(Our base algorithm for searching is using the "Levenshtein distance" algorithm)
Which kind of geodata is the result (e.g. Buildings are ranked over POIs)?
This means that the first item in the search result list will be the one best matching the three factors. You always have the ability to reorder your array of locations based on your preference before rendering them in your user interface, if you choose to handle that via some client-side code.
Feel free to refer to this table for a comprehensive understanding of each parameter's type, optional/required status, and functionality.
q
string
Optional
Use a text query to search for one or more locations.
fields
string
Optional
Fields to search in when using the search string parameter 'q'. Options: "name,description,aliases,categories"
types
Array.
Optional
Filter by types in a comma-separated list. A location only has one type.
mapsindoors.services.LocationsService.getLocations({ lr: 'en', types: ['meetingroom'] }).then(locations => { ... });
categories
Array.
Optional
Filter by categories in a comma-separated list. A location can be in multiple categories.
mapsindoors.services.LocationsService.getLocations({ categories: categoryKey, lr: 'en' }).then(locations => { ... });
bbox
Object
Optional
Limits the result to inside the bounding box. Must include bbox.east
, bbox.north
, bbox.south
, bbox.west
bbox.east
number
Required if bbox
Max longitude of the bounds in degrees.
bbox.north
number
Required if bbox
Max latitude of the bounds in degrees.
bbox.south
number
Required if bbox
Min latitude of the bounds in degrees.
bbox.west
number
Required if bbox
Min longitude of the bounds in degrees.
take
number
Optional
Max number of locations to get.
await mapsindoors.services.LocationsService.getLocations({ near: 'location:9897fd93fcb14bd39ec8110d', take: 5, ... });
skip
number
Optional
Skip the first number of entries.
near
LatLngLiteral | string
Optional
Can either be a coordinate {lat: number, lng: number}
or a string in the format "type:id".
await mapsindoors.services.LocationsService.getLocations({ near: 'location:9897fd93fcb14bd39ec8110d', radius: 50, ... });
radius
number
Optional
A radius in meters. Must be supplied when using near
with a point.
await mapsindoors.services.LocationsService.getLocations({ near: 'location:9897fd93fcb14bd39ec8110d', radius: 50, ... });
floor
integer
Optional
Filter locations to a specific floor.
await mapsindoors.services.LocationsService.getLocations({ near: 'location:9897fd93fcb14bd39ec8110d', floor: 50, ... });
orderBy
string
Optional
Which property the result should be sorted by.
sortOrder
string
Optional
Specifies in which order the results are sorted, either "ASC" or "DESC"
"ASC"
building
string
Optional
Limit the search for locations to a building.
venue
string
Optional
Limit the search for locations to a venue (id or name).
Example of Creating a Search Query
See the full list of parameters in the reference guide:
const searchParameters = {
q: 'Office',
near: { lat: 38.897579747054046, lng: -77.03658652944773 }, // // Blue Room, The White House
take: 1
}
mapsindoors.services.LocationsService.getLocations(searchParameters).then(locations => {
console.log(locations);
});
Display Search Results on the Map
When displaying the search results, it is helpful to filter the map to only show matching Locations. Matching Buildings and Venues will still be shown on the map, as they give context to the user, even if they aren't selectable on the map.
Example of Filtering the Map to Display Searched Locations on the Map
const searchParameters = {
q: 'Office',
near: { lat: 38.897579747054046, lng: -77.03658652944773 }, // // Blue Room, The White House
take: 1
}
mapsindoors.services.LocationsService.getLocations(searchParameters).then(locations => {
mapsIndoorsInstance.filter(locations.map(location => location.id), false);
});
Clearing the Map of Your Filter
After displaying the search results on your map you can then clear the filter so that all Locations show up on the map again.
Example of Clearing Your Map Filter to Show All Locations Again
mapsIndoorsInstance.filter(null);
Display Locations as List
You can also search for Locations, and have them presented to you as a list, instead of just displaying them on the map.
The full code example is shown in the JSFiddle below which will be examined below.
Search example
The mapsindoors.services.LocationsService
class exposes the getLocations
function that enables you to search for Locations.
It will return a promise that gets resolved when the query has executed.
See mapsindoors.services.LocationsService for more information.
searchElement.addEventListener('input', debounce((e) => {
const value = e.target.value;
if (value > '') {
mapsindoors.services.LocationsService.getLocations({ q: value, includeOutsidePOI: true })
.then(displayResults)
.then(filterMap);
} else {
clearResults();
clearFilter();
}
}, 500));
The debounce
method is there to ensure that the service is not being called in rapid succession. This method delays the execution of the function by 500ms, unless debounce
is called again within 500ms, in which case the timer is reset.
See this article "What is debouncing" by Jamis Charles for a more detailed description of the debounce
concept.
When the function executes, we check whether the input is empty or not. A request object is created if the input is not empty.
The getLocations
function expects either no input, in which case it returns all Locations, or an Object (please refer to the official documentation for an exhaustive list of properties). In this case, the constant value
is passed to the q
property and the includeOutsidePOI
property is set to true
. When the Promise resolves, the response is passed to the displayResults
helper function.
If the input is empty, we clear the result list and reset the map filter by calling the helper functions clearResults
and clearFilter
.
Checking for Results
We need to clear the previous results, and check if any Locations were returned. If so, we loop through them and add them to the result list.
function displayResults(locations) {
clearResults();
if (locations.length > 0) {
for (const location of locations) {
searchResults.innerHTML += `<li>${location.properties.name}</li>`;
}
} else {
searchResults.innerHTML = '<li class="no-results">No results matched the query.</li>';
}
return locations;
}
If no Locations are returned, a message is shown to the user stating "No results matched the query.". Otherwise, we pass the Locations on to the next helper function called filterMap
.
function filterMap(locations) {
mapsIndoors.filter(locations.map(location => location.id), false);
return locations;
}
The purpose of the filterMap
function is to create a list of location id
s used to filter the Locations on the map.
The second parameter tells MapsIndoors not to change the viewport of the map.
For more information, see MapsIndoors.filter
in the reference documentation.
Last updated
Was this helpful?