Learn how to find places around a location with a keyword search, then get detailed place information.
A nearby search finds places within a given radius of a location using the places service. The location typically represents a point on a map or the geolocation of a device.
Import the places module. With the results of the search, you can make another request to the service and return place attributes including the name, categories, ratings, and store hours.
In this tutorial, you will use ArcGIS Maps SDK for JavaScript to perform a nearby search to find points of interest and return all available attributes associated with a place.
Prerequisites
Steps
Create a new pen
- To get started, either complete the Display a map tutorial or .
Get an access token
You need an access token with the correct privileges to access the location services used in this tutorial.
- Go to the Create an API key tutorial and create an API key with the following privilege(s):
- Privileges
- Location services > Basemaps
- Location services > Places
- Privileges
- In CodePen, set
esri
to your API key..Config.api Key Use dark colors for code blocks esriConfig.apiKey = "YOUR_ACCESS_TOKEN"; const map = new Map({ basemap: "arcgis/topographic" // basemap styles service });
To learn about other ways to get an access token, go to Types of authentication.
Add modules
- Import the
places
,Fetch
,Place Parameters Places
,Query Parameters Circle
,Graphic
,Graphics
, andLayer Web
modules.Style Symbol The ArcGIS Maps SDK for JavaScript is available as AMD modules and ES modules, but this tutorial is based on AMD. The AMD
require
function uses references to determine which modules will be loaded – for example, you can specify"esri/
for loading the Map module. After the modules are loaded, they are passed as parameters (e.g.Map" Map
) to the callback function where they can be used in your application. It is important to keep the module references and callback parameters in the same order. To learn more about the API's different modules visit the Overview Guide page.Use dark colors for code blocks <script> require([ "esri/config", "esri/Map", "esri/views/MapView", "esri/rest/places", "esri/rest/support/FetchPlaceParameters", "esri/rest/support/PlacesQueryParameters", "esri/geometry/Circle", "esri/Graphic", "esri/layers/GraphicsLayer", "esri/symbols/WebStyleSymbol" ], function(esriConfig, Map, MapView, places, FetchPlaceParameters, PlacesQueryParameters, Circle, Graphic, GraphicsLayer, WebStyleSymbol) { }); </script> </head> <body> </body> </html>
Create variables
To perform a place search, you typically need a category ID. Each category ID corresponds to a unique category of place, such as "Chinese Restaurants" (13099
) or "Sports and Recreation" (18000
). After the place search, the results need to be handled, and displayed to the user. The search area should also be visualized on the map, centered around a user's click.
- Create variables for the info panel, map interactions, and GraphicsLayers for the place features and the buffer extent. One
Graphics
will represent the area that will be searched as a buffer, the other will represent the place features on the map.Layer Use dark colors for code blocks esriConfig.apiKey = "YOUR_ACCESS_TOKEN"; let infoPanel; // Info panel for place information let clickPoint; // Clicked point on the map let activeCategory = "16000"; // Landmarks and Outdoors category // GraphicsLayer for places features const placesLayer = new GraphicsLayer({ id: "placesLayer" }); // GraphicsLayer for map buffer const bufferLayer = new GraphicsLayer({ id: "bufferLayer" }); // Info panel interactions const categorySelect = document.getElementById("categorySelect"); const resultPanel = document.getElementById("results"); const flow = document.getElementById("flow");
Update the map
A streets basemap layer is typically used in geocoding applications. Update the basemap
property to use the arcgis/navigation
basemap layer and change the position of the map to center on Venice Beach, CA.
We'll also need to add the two Graphics
from the previous step, and update the Map
.
-
Update the
basemap
property fromarcgis/topographic
toarcgis/navigation
. Then, add the layers representing the search area and place locations to the map.Use dark colors for code blocks // Map with the GraphicsLayers const map = new Map({ basemap: "arcgis/navigation", layers: [bufferLayer, placesLayer] });
-
Update the
center
property to[-118.46651, 33.98621]
.Use dark colors for code blocks // View centered around Venice Beach, CA const view = new MapView({ map: map, center: [-118.46651, 33.98621], zoom: 13, container: "viewDiv" });
Update the UI
Update the UI to use calcite components and create div
elements for places category and feature interactions.
-
Add references to use calcite components.
Use dark colors for code blocks <script type="module" src="https://js.arcgis.com/calcite-components/2.11.1/calcite.esm.js"></script> <link rel="stylesheet" type="text/css" href="https://js.arcgis.com/calcite-components/2.11.1/calcite.css" />
-
Add a calcite-combobox to handle category changing and a calcite-flow to display the results. Note that the "Landmarks and Outdoors"(
16000
) category is selected by default, matching theactive
value.Category Use dark colors for code blocks <calcite-shell> <calcite-shell-panel slot="panel-start" position="start" id="contents"> <calcite-combobox id="categorySelect" placeholder="Filter by category" overlay-positioning="fixed" selection-mode="single"> <calcite-combobox-item value="10000" text-label="Arts and Entertainment"></calcite-combobox-item> <calcite-combobox-item value="11000" text-label="Business and Professional Services"></calcite-combobox-item> <calcite-combobox-item value="12000" text-label="Community and Government"></calcite-combobox-item> <calcite-combobox-item value="13000" text-label="Dining and Drinking"></calcite-combobox-item> <calcite-combobox-item value="15000" text-label="Health and Medicine"></calcite-combobox-item> <calcite-combobox-item selected value="16000" text-label="Landmarks and Outdoors"></calcite-combobox-item> <calcite-combobox-item value="17000" text-label="Retail"></calcite-combobox-item> <calcite-combobox-item value="18000" text-label="Sports and Recreation"></calcite-combobox-item> <calcite-combobox-item value="19000" text-label="Travel and Transportation"></calcite-combobox-item> </calcite-combobox> <calcite-panel class="contents"> <calcite-flow id="flow"> <calcite-flow-item> <calcite-list id="results"> <calcite-notice open> <div slot="message"> Click on the map to search for nearby places </div> </calcite-notice> </calcite-list> </calcite-flow-item> </calcite-flow> </calcite-panel> </calcite-shell-panel> <div id="viewDiv"></div> </calcite-shell>
-
Add
div
elements to the CSS code.Use dark colors for code blocks html, body { height: 100%; width: 100%; margin: 0; padding: 0; } #viewDiv { flex: 1; } #categorySelect { margin: 5px; } #contents { --calcite-shell-panel-min-width: 340px; }
Map interactions
This app handles user input in the form of clicking on the map to search for nearby places, and changing categories. If the category is changed after the first map click, the results will be updated for the new category at the same location. Lastly, results on the map and in the info panel need to be removed before the next set of results can be displayed.
-
Create a
clear
function to clear the graphics and results from a previous place search.Graphics() Use dark colors for code blocks // Clear graphics and results from the previous place search function clearGraphics() { bufferLayer.removeAll(); // Remove graphics from GraphicsLayer of previous buffer placesLayer.removeAll(); // Remove graphics from GraphicsLayer of previous places search resultPanel.innerHTML = ""; if (infoPanel) infoPanel.remove(); }
-
Add a
Map
on-click event to capture the places search location. When the map is clicked, the results from the previous click need to be removed. Then the point where the map was clicked needs to be passed to theView show
function.Places() Use dark colors for code blocks // View on-click event to capture places search location view.on("click", (event) => { clearGraphics(); clickPoint = event.mapPoint; // Pass point to the showPlaces() function clickPoint && showPlaces(clickPoint); });
-
Add an event listener for places category changes. When the category changes, update the
active
value and remove the results from the previous click so new results can be visualized.Category Use dark colors for code blocks // Event listener for category changes categorySelect.addEventListener("calciteComboboxChange", () => { activeCategory = categorySelect.value; clearGraphics(); // Pass point to the showPlaces() function with new category value clickPoint && showPlaces(clickPoint); });
Find places near a point
Once a location and category have been set, make a request to the places service to find places near a point.
-
Define a new asynchronous function called
show
. Here we will create our search area buffer's geometry and graphic and add it to itsPlaces() Graphics
.Layer Use dark colors for code blocks // Display map click search area and pass to places service async function showPlaces(placePoint) { // Buffer graphic represents click location and search radius const circleGeometry = new Circle({ center: placePoint, geodesic: true, numberOfPoints: 100, radius: 500, // set radius to 500 meters radiusUnit: "meters" }); const circleGraphic = new Graphic({ geometry: circleGeometry, symbol: { type: "simple-fill", // autocasts as SimpleFillSymbol style: "solid", color: [3, 140, 255, 0.1], outline: { width: 1, color: [3, 140, 255], }, } }); // Add buffer graphic to the view bufferLayer.graphics.add(circleGraphic); }
-
Use the
query
method to submit a request to the places service. Pass the categoryIds, radius, and point asPlaces Near Point() Places
. Authentication to the places service was handled previously withQuery Parameters esri
. Pass the results to theConfig tabulate
function.Places() Use dark colors for code blocks // Display map click search area and pass to places service async function showPlaces(placePoint) { // Buffer graphic represents click location and search radius const circleGeometry = new Circle({ center: placePoint, geodesic: true, numberOfPoints: 100, radius: 500, // set radius to 500 meters radiusUnit: "meters" }); const circleGraphic = new Graphic({ geometry: circleGeometry, symbol: { type: "simple-fill", // autocasts as SimpleFillSymbol style: "solid", color: [3, 140, 255, 0.1], outline: { width: 1, color: [3, 140, 255], }, } }); // Add buffer graphic to the view bufferLayer.graphics.add(circleGraphic); // Parameters for queryPlacesNearPoint() const placesQueryParameters = new PlacesQueryParameters({ categoryIds: [activeCategory], radius: 500, // set radius to 500 meters point: placePoint }); // The results variable represents the PlacesQueryResult const results = await places.queryPlacesNearPoint( placesQueryParameters ); // Pass the PlacesQueryResult to the tabulatePlaces() function tabulatePlaces(results); }
-
Define a new function called
tabulate
. The result of thePlaces() query
method, which is typePlaces Near Point() Places
, contains aQuery Result results
property that is an array of typePlace
. Pass eachResult Place
object to theResult add
function.Result() Use dark colors for code blocks // Investigate the individual PlaceResults from the array of results // from the PlacesQueryResult and process them function tabulatePlaces(results) { results.results.forEach((placeResult) => { // Pass each result to the addResult() function addResult(placeResult); }); }
Display results
The places service will return a list of places near your location that match the specified category. Display these results as appropriately symbolized points on your map. To show more information about each place, you will also display place results as an element in a Calcite list.
-
Create a
create
function to create a WebStyleSymbol for each feature based on the category name.Web Style() Use dark colors for code blocks // Creates webstyles based on the given name function createWebStyle(symbolName) { return new WebStyleSymbol({ name: symbolName, styleName: "Esri2DPointSymbolsStyle" }); }
-
Define a new asynchronous function called
add
. Create a variable calledResult() place
to display each feature from the results on the map.Graphic Use dark colors for code blocks // Visualize the places on the map based on category // and list them on the info panel with more details async function addResult(place) { const placeGraphic = new Graphic({ geometry: place.location }); }
-
Add a switch statement to symbolize each feature based on the category value. Category names are passed to the
create
function to create symbology. After symbolizing each feature, add them to theWeb Style() Graphics
to display on the map.Layer Use dark colors for code blocks // Visualize the places on the map based on category // and list them on the info panel with more details async function addResult(place) { const placeGraphic = new Graphic({ geometry: place.location }); switch (activeCategory) { case "10000": // Arts and Entertainment placeGraphic.symbol = createWebStyle("museum"); break; case "11000": // Business and Professional Services placeGraphic.symbol = createWebStyle("industrial-complex"); break; case "12000": // Community and Government placeGraphic.symbol = createWebStyle("embassy"); break; case "13000": // Dining and Drinking placeGraphic.symbol = createWebStyle("vineyard"); break; case "15000": // Health and Medicine placeGraphic.symbol = createWebStyle("hospital"); break; case "16000": // Landmarks and Outdoors category placeGraphic.symbol = createWebStyle("landmark"); break; case "17000": // Retail placeGraphic.symbol = createWebStyle("shopping-center"); break; case "18000": // Sports and Recreation placeGraphic.symbol = createWebStyle("sports-complex"); break; case "19000": // Travel and Transportation placeGraphic.symbol = createWebStyle("trail"); break; default: placeGraphic.symbol = createWebStyle("museum"); } // Add each graphic to the GraphicsLayer placesLayer.graphics.add(placeGraphic); }
-
Create a new
calcite-list-item
element to display place information. Set the item properties to display the place name, category, and distance from the user's click. Add the element to the list of results.Use dark colors for code blocks // Visualize the places on the map based on category // and list them on the info panel with more details async function addResult(place) { const placeGraphic = new Graphic({ geometry: place.location }); switch (activeCategory) { case "10000": // Arts and Entertainment placeGraphic.symbol = createWebStyle("museum"); break; case "11000": // Business and Professional Services placeGraphic.symbol = createWebStyle("industrial-complex"); break; case "12000": // Community and Government placeGraphic.symbol = createWebStyle("embassy"); break; case "13000": // Dining and Drinking placeGraphic.symbol = createWebStyle("vineyard"); break; case "15000": // Health and Medicine placeGraphic.symbol = createWebStyle("hospital"); break; case "16000": // Landmarks and Outdoors category placeGraphic.symbol = createWebStyle("landmark"); break; case "17000": // Retail placeGraphic.symbol = createWebStyle("shopping-center"); break; case "18000": // Sports and Recreation placeGraphic.symbol = createWebStyle("sports-complex"); break; case "19000": // Travel and Transportation placeGraphic.symbol = createWebStyle("trail"); break; default: placeGraphic.symbol = createWebStyle("museum"); } // Add each graphic to the GraphicsLayer placesLayer.graphics.add(placeGraphic); const infoDiv = document.createElement("calcite-list-item"); infoDiv.label = place.name; infoDiv.description = ` ${place.categories[0].label} - ${Number((place.distance / 1000).toFixed(1))} km`;; }
-
When the HTML element from the info panel is clicked, show the popup and navigate the map to the associated feature.
Use dark colors for code blocks // Visualize the places on the map based on category // and list them on the info panel with more details async function addResult(place) { const placeGraphic = new Graphic({ geometry: place.location }); switch (activeCategory) { case "10000": // Arts and Entertainment placeGraphic.symbol = createWebStyle("museum"); break; case "11000": // Business and Professional Services placeGraphic.symbol = createWebStyle("industrial-complex"); break; case "12000": // Community and Government placeGraphic.symbol = createWebStyle("embassy"); break; case "13000": // Dining and Drinking placeGraphic.symbol = createWebStyle("vineyard"); break; case "15000": // Health and Medicine placeGraphic.symbol = createWebStyle("hospital"); break; case "16000": // Landmarks and Outdoors category placeGraphic.symbol = createWebStyle("landmark"); break; case "17000": // Retail placeGraphic.symbol = createWebStyle("shopping-center"); break; case "18000": // Sports and Recreation placeGraphic.symbol = createWebStyle("sports-complex"); break; case "19000": // Travel and Transportation placeGraphic.symbol = createWebStyle("trail"); break; default: placeGraphic.symbol = createWebStyle("museum"); } // Add each graphic to the GraphicsLayer placesLayer.graphics.add(placeGraphic); const infoDiv = document.createElement("calcite-list-item"); infoDiv.label = place.name; infoDiv.description = ` ${place.categories[0].label} - ${Number((place.distance / 1000).toFixed(1))} km`;; // If a place in the info panel is clicked // then open the feature's popup infoDiv.addEventListener("click", async () => { view.openPopup({ location: place.location, title: place.name }); // Move the view to center on the selected place feature view.goTo(placeGraphic); }
-
Create new
Fetch
using the placeId and all possible fields. Then pass thePlace Parameters Fetch
andPlace Parameters place
to aId get
function.Details() Use dark colors for code blocks // Visualize the places on the map based on category // and list them on the info panel with more details async function addResult(place) { const placeGraphic = new Graphic({ geometry: place.location }); switch (activeCategory) { case "10000": // Arts and Entertainment placeGraphic.symbol = createWebStyle("museum"); break; case "11000": // Business and Professional Services placeGraphic.symbol = createWebStyle("industrial-complex"); break; case "12000": // Community and Government placeGraphic.symbol = createWebStyle("embassy"); break; case "13000": // Dining and Drinking placeGraphic.symbol = createWebStyle("vineyard"); break; case "15000": // Health and Medicine placeGraphic.symbol = createWebStyle("hospital"); break; case "16000": // Landmarks and Outdoors category placeGraphic.symbol = createWebStyle("landmark"); break; case "17000": // Retail placeGraphic.symbol = createWebStyle("shopping-center"); break; case "18000": // Sports and Recreation placeGraphic.symbol = createWebStyle("sports-complex"); break; case "19000": // Travel and Transportation placeGraphic.symbol = createWebStyle("trail"); break; default: placeGraphic.symbol = createWebStyle("museum"); } // Add each graphic to the GraphicsLayer placesLayer.graphics.add(placeGraphic); const infoDiv = document.createElement("calcite-list-item"); infoDiv.label = place.name; infoDiv.description = ` ${place.categories[0].label} - ${Number((place.distance / 1000).toFixed(1))} km`;; // If a place in the info panel is clicked // then open the feature's popup infoDiv.addEventListener("click", async () => { view.openPopup({ location: place.location, title: place.name }); // Move the view to center on the selected place feature view.goTo(placeGraphic); // Fetch more details about each place based // on the place ID with all possible fields const fetchPlaceParameters = new FetchPlaceParameters({ placeId: place.placeId, requestedFields: ["all"] }); // Pass the FetchPlaceParameters and the location of the // selected place feature to the getDetails() function getDetails(fetchPlaceParameters, place.location); }); resultPanel.appendChild(infoDiv); }
Get place details
The user of your application should be able to click on a result in the info panel in order to see more information about it. Perform a fetch
request to obtain detailed attributes about a specific point of interest.
-
Define a new asynchronous function called
get
that acceptsDetails() Fetch
and a placeId. Pass thePlace Parameters Fetch
to thePlace Parameters fetch
method and store the results in aPlace() results
variable.Use dark colors for code blocks // Get place details and display in the info panel async function getDetails(fetchPlaceParameters, placePoint) { // Get place details const result = await places.fetchPlace(fetchPlaceParameters); const placeDetails = result.placeDetails; }
-
Access the response from the places service and create a new
calcite-flow-item
element to display results. Then set the name and description of the panel using the service response, and pass to theset
function if they are valid. Select a Calcite UI icon for each attribute.Attribute() Use dark colors for code blocks // Get place details and display in the info panel async function getDetails(fetchPlaceParameters, placePoint) { // Get place details const result = await places.fetchPlace(fetchPlaceParameters); const placeDetails = result.placeDetails; // Set-up panel on the info for more place information infoPanel = document.createElement("calcite-flow-item"); flow.appendChild(infoPanel); infoPanel.heading = placeDetails.name; infoPanel.description = placeDetails.categories[0].label; // Pass attributes from each place to the setAttribute() function setAttribute("Description", "information", placeDetails.description); setAttribute( "Address", "map-pin", placeDetails.address.streetAddress ); setAttribute("Phone", "mobile", placeDetails.contactInfo.telephone); setAttribute("Hours", "clock", placeDetails.hours.openingText); setAttribute("Rating", "star", placeDetails.rating.user); setAttribute( "Email", "email-address", placeDetails.contactInfo.email ); setAttribute( "Facebook", "speech-bubble-social", placeDetails.socialMedia.facebookId ? `www.facebook.com/${placeDetails.socialMedia.facebookId}` : null ); setAttribute( "Twitter", "speech-bubbles", placeDetails.socialMedia.twitter ? `www.twitter.com/${placeDetails.socialMedia.twitter}` : null ); setAttribute( "Instagram", "camera", placeDetails.socialMedia.instagram ? `www.instagram.com/${placeDetails.socialMedia.instagram}` : null ); }
-
When the info panel is closed, close any open map popups.
Use dark colors for code blocks // Get place details and display in the info panel async function getDetails(fetchPlaceParameters, placePoint) { // Get place details const result = await places.fetchPlace(fetchPlaceParameters); const placeDetails = result.placeDetails; // Set-up panel on the info for more place information infoPanel = document.createElement("calcite-flow-item"); flow.appendChild(infoPanel); infoPanel.heading = placeDetails.name; infoPanel.description = placeDetails.categories[0].label; // Pass attributes from each place to the setAttribute() function setAttribute("Description", "information", placeDetails.description); setAttribute( "Address", "map-pin", placeDetails.address.streetAddress ); setAttribute("Phone", "mobile", placeDetails.contactInfo.telephone); setAttribute("Hours", "clock", placeDetails.hours.openingText); setAttribute("Rating", "star", placeDetails.rating.user); setAttribute( "Email", "email-address", placeDetails.contactInfo.email ); setAttribute( "Facebook", "speech-bubble-social", placeDetails.socialMedia.facebookId ? `www.facebook.com/${placeDetails.socialMedia.facebookId}` : null ); setAttribute( "Twitter", "speech-bubbles", placeDetails.socialMedia.twitter ? `www.twitter.com/${placeDetails.socialMedia.twitter}` : null ); setAttribute( "Instagram", "camera", placeDetails.socialMedia.instagram ? `www.instagram.com/${placeDetails.socialMedia.instagram}` : null ); // If another place is clicked in the info panel, then close // the popup and remove the highlight of the previous feature infoPanel.addEventListener("calciteFlowItemBack", async () => { view.closePopup(); }); }
Display place details
Display the resulting place details in an info panel.
- Define a new function called
set
. This accepts the elements to display the results on the info panel in a newAttribute() calcite-block
.Use dark colors for code blocks // Take each place attribute and display on info panel function setAttribute(heading, icon, validValue) { if (validValue) { const element = document.createElement("calcite-block"); element.heading = heading; element.description = validValue; const attributeIcon = document.createElement("calcite-icon"); attributeIcon.icon = icon; attributeIcon.slot = "icon"; attributeIcon.scale = "m"; element.appendChild(attributeIcon); infoPanel.appendChild(element); } }
Run the app
In CodePen, run your code to display the map.
The app should display a map with an info panel on the left, which will populate with values when the map is clicked. Upon clicking on a place result, more information about the place should appear in the panel.
What's next?
Learn how to use additional API features and ArcGIS services in these tutorials: