Learn how to perform a text-based search to find places within a bounding box.
The ArcGIS Places service allows you to find places within an extent. An extent typically represents the visible area of a map. With the results of the search, you can make another request to the service and return place attributes such as street address and telephone number.
In this tutorial, use ArcGIS REST JS to perform a bounding box search based on the visible extent on the map and return details about each place. You also use Calcite components to create a basic search interface.
Prerequisites
An ArcGIS Location Platform account.
Steps
Get the starter app
Select a type of authentication and follow the steps to create a new app.
Choose API key authentication if you:
- Want the easiest way to get started.
- Want to build public applications that access ArcGIS Location Services and secure items.
- Have an ArcGIS Location Platform or ArcGIS Online account.
Choose user authentication if you:
- Want to build private applications.
- Require application users to sign in with their own ArcGIS account and access resources their behalf.
- Have an ArcGIS Online account.
To learn more about both types of authentication, go to Authentication.
Set up authentication
Set developer credentials
Get a Cesium ion access token
All Cesium applications must use an access token provided through Cesium ion. This token allows you to access assets such as Cesium World Terrain in your application.
-
Go to your Cesium ion dashboard to generate an access token. Copy the key to your clipboard.
-
Create a
cesiumvariable and replaceAccess Token YOURwith the access token you copied from the Cesium ion dashboard._CESIUM _ACCESS _TOKEN Use dark colors for code blocks <script> /* Use for API key authentication */ const accessToken = "YOUR_ACCESS_TOKEN"; // or /* Use for user authentication */ // const session = await arcgisRest.ArcGISIdentityManager.beginOAuth2({ // clientId: "YOUR_CLIENT_ID", // Your client ID from OAuth credentials // redirectUri: "YOUR_REDIRECT_URI", // The redirect URL registered in your OAuth credentials // portal: "YOUR_PORTAL_URL" // Your portal URL // }) // const accessToken = session.token; Cesium.ArcGisMapService.defaultAccessToken = accessToken; const cesiumAccessToken = "YOUR_CESIUM_ACCESS_TOKEN"; </script> -
Configure
Cesium.with the Cesium access token to validate the application.Ion.default Access Token Use dark colors for code blocks <script> /* Use for API key authentication */ const accessToken = "YOUR_ACCESS_TOKEN"; // or /* Use for user authentication */ // const session = await arcgisRest.ArcGISIdentityManager.beginOAuth2({ // clientId: "YOUR_CLIENT_ID", // Your client ID from OAuth credentials // redirectUri: "YOUR_REDIRECT_URI", // The redirect URL registered in your OAuth credentials // portal: "YOUR_PORTAL_URL" // Your portal URL // }) // const accessToken = session.token; Cesium.ArcGisMapService.defaultAccessToken = accessToken; const cesiumAccessToken = "YOUR_CESIUM_ACCESS_TOKEN"; Cesium.Ion.defaultAccessToken = cesiumAccessToken; </script>
Add script references
-
Reference the
routingandrequestpackages from ArcGIS REST JS.Use dark colors for code blocks <!-- Load Cesium from CDN --> <script src="https://cesium.com/downloads/cesiumjs/releases/1.135/Build/Cesium/Cesium.js"></script> <link href="https://cesium.com/downloads/cesiumjs/releases/1.135/Build/Cesium/Widgets/widgets.css" rel="stylesheet"> <!-- ArcGIS REST JS: request and places --> <script src="https://unpkg.com/@esri/arcgis-rest-request@4/dist/bundled/request.umd.js"></script> <script src="https://unpkg.com/@esri/arcgis-rest-places@1/dist/bundled/places.umd.js"></script> -
Reference the Calcite Design System library.
Use dark colors for code blocks <!-- Load Cesium from CDN --> <script src="https://cesium.com/downloads/cesiumjs/releases/1.135/Build/Cesium/Cesium.js"></script> <link href="https://cesium.com/downloads/cesiumjs/releases/1.135/Build/Cesium/Widgets/widgets.css" rel="stylesheet"> <!-- Calcite components --> <script type="module" src="https://js.arcgis.com/calcite-components/2.12.1/calcite.esm.js"></script> <link rel="stylesheet" href="https://js.arcgis.com/calcite-components/2.12.1/calcite.css"> <!-- ArcGIS REST JS: request and places --> <script src="https://unpkg.com/@esri/arcgis-rest-request@4/dist/bundled/request.umd.js"></script> <script src="https://unpkg.com/@esri/arcgis-rest-places@1/dist/bundled/places.umd.js"></script> -
Create a REST JS
Apiusing your access token.Key Manager Use dark colors for code blocks viewer.camera.setView({ destination: Cesium.Cartesian3.fromDegrees(-122.32116, 47.57737, 12000), orientation: { heading: Cesium.Math.toRadians(0.0), pitch: Cesium.Math.toRadians(-70.0) } }); // Add Esri attribution // Learn more in https://esriurl.com/attribution const poweredByEsri = new Cesium.Credit("Powered by <a href='https://www.esri.com/en-us/home' target='_blank'>Esri</a>", true); viewer.creditDisplay.addStaticCredit(poweredByEsri); const authentication = arcgisRest.ApiKeyManager.fromKey(accessToken);
Update interface
-
Change the map's viewpoint to
(-122.32116, 47.57737)with a scale of12000to focus on Seattle, Washington.Use dark colors for code blocks viewer.camera.setView({ destination: Cesium.Cartesian3.fromDegrees(-122.32116, 47.57737, 12000), orientation: { heading: Cesium.Math.toRadians(0.0), pitch: Cesium.Math.toRadians(-70.0) } }); -
Inside
<body, copy and paste the following HTML to create a basic search interface.> Use dark colors for code blocks <div id="place-control"> <div class="search"> <calcite-input type="text" id="search-input" placeholder="Type in a place name or category"> <calcite-button kind="inverse" icon-start="search" id="search-button" type="submit" slot="action" ></calcite-button> </calcite-input> </div> </div> <div id="cesiumContainer"></div> -
Update styling to the
cesiumandContainer body. Then, add styling for the new elements.Use dark colors for code blocks body { margin: 0; padding: 0; } #cesiumContainer { position: absolute; top: 0; bottom: 0; right: 0; left: 0; font-family: Arial, Helvetica, sans-serif; font-size: 14px; color: #323232; z-index: 1; } #place-control { position: absolute; top: 15px; left: 15px; display: flex; flex-direction: row; z-index: 2; } .search { margin-right: 15px; } #search-input { width: 270px; } .category-button { margin: auto 5px; } -
Create a set of dynamic buttons for place categories (e.g.,
Restaurants,Hotels,Museums) usingcalcite-buttonelements.Use dark colors for code blocks const control = document.getElementById("place-control"); const input = document.getElementById("search-input"); const placeKeywords = ["Restaurants", "Hotels", "Museums", "ATMs", "Breweries"]; placeKeywords.forEach((category) => { const categoryButton = document.createElement("calcite-button"); categoryButton.setAttribute("class", "category-button"); categoryButton.setAttribute("round", true); categoryButton.setAttribute("scale", "s"); categoryButton.setAttribute("kind", "inverse"); categoryButton.innerHTML = category; categoryButton.id = category; control.appendChild(categoryButton); }); document.getElementById(placeKeywords[0]).dispatchEvent(new Event("click"));
Add event listeners
The provided for this tutorial includes a basic user interface with a text input and category buttons. Add event listeners to this interface to make requests to the ArcGIS Places service on when they are clicked.
-
Create a
showfunction to make requests to the Places service.Places Use dark colors for code blocks function showPlaces(query) { }; -
Add an event listener to the search button that calls
showon click.Places Use dark colors for code blocks const control = document.getElementById("place-control"); const input = document.getElementById("search-input"); const placeKeywords = ["Restaurants", "Hotels", "Museums", "ATMs", "Breweries"]; document.getElementById("search-button").addEventListener("click", () => { showPlaces(input.value); }); placeKeywords.forEach((category) => { const categoryButton = document.createElement("calcite-button"); categoryButton.setAttribute("class", "category-button"); categoryButton.setAttribute("round", true); categoryButton.setAttribute("scale", "s"); categoryButton.setAttribute("kind", "inverse"); categoryButton.innerHTML = category; categoryButton.id = category; control.appendChild(categoryButton); }); document.getElementById(placeKeywords[0]).dispatchEvent(new Event("click")); -
Add an event listener to each category button that calls
showon click.Places Use dark colors for code blocks placeKeywords.forEach((category) => { const categoryButton = document.createElement("calcite-button"); categoryButton.setAttribute("class", "category-button"); categoryButton.setAttribute("round", true); categoryButton.setAttribute("scale", "s"); categoryButton.setAttribute("kind", "inverse"); categoryButton.innerHTML = category; categoryButton.id = category; control.appendChild(categoryButton); categoryButton.addEventListener("click", () => { input.value = category; showPlaces(category); }); }); document.getElementById(placeKeywords[0]).dispatchEvent(new Event("click"));
Find places in the map bounds
-
Calculate the current visible extent of the CesiumJS scene with
compute. Transform the bounds to latitude and longitude degrees for use with the places API.View Rectangle Use dark colors for code blocks function showPlaces(query) { const rect = viewer.camera.computeViewRectangle(); const bounds = [rect.west, rect.south, rect.east, rect.north]; for (let i = 0; i < bounds.length; i++) { bounds[i] = Cesium.Math.toDegrees(bounds[i]); } }; -
Use the ArcGIS REST JS
findoperation to make a request to the Places service. Set thePlaces Within Extent searchparameter to your query and pass the current map bounding box to theText xmin,xmax,ymin, andymaxparameters.Use dark colors for code blocks function showPlaces(query) { const rect = viewer.camera.computeViewRectangle(); const bounds = [rect.west, rect.south, rect.east, rect.north]; for (let i = 0; i < bounds.length; i++) { bounds[i] = Cesium.Math.toDegrees(bounds[i]); } arcgisRest.findPlacesWithinExtent({ xmin: bounds[0], ymin: bounds[1], xmax: bounds[2], ymax: bounds[3], searchText: query, authentication, f: "geojson" }) };
Display results
The response from the Places service will contain a list of place results. Each result will include a place's x/y coordinates, name, category, and unique ID. Add these each result to your CesiumJS application as an Entity.
-
Access the service results. For each result, add a new
Entitycontaining aname, anid, and apositionobtained from the result information.Use dark colors for code blocks arcgisRest.findPlacesWithinExtent({ xmin: bounds[0], ymin: bounds[1], xmax: bounds[2], ymax: bounds[3], searchText: query, authentication, f: "geojson" }) .then((response) => { response.results.forEach((result) => { viewer.entities.add({ name: result.name, description: "Details loading...", id: result.placeId, position: Cesium.Cartesian3.fromDegrees(result.location.x, result.location.y), }); }); }); -
Create a new
Pinto style the appearance of results. When a new request is made, remove all existing entities from the viewer.Builder Use dark colors for code blocks const pinBuilder = new Cesium.PinBuilder(); function showPlaces(query) { viewer.entities.removeAll(); const rect = viewer.camera.computeViewRectangle(); const bounds = [rect.west, rect.south, rect.east, rect.north]; for (let i = 0; i < bounds.length; i++) { bounds[i] = Cesium.Math.toDegrees(bounds[i]); } arcgisRest.findPlacesWithinExtent({ xmin: bounds[0], ymin: bounds[1], xmax: bounds[2], ymax: bounds[3], searchText: query, authentication, f: "geojson" }) .then((response) => { response.results.forEach((result) => { viewer.entities.add({ name: result.name, description: "Details loading...", id: result.placeId, position: Cesium.Cartesian3.fromDegrees(result.location.x, result.location.y), }); }); }); }; -
Add a
billboardto each result entity. Style the billboards with a blue pin icon.Use dark colors for code blocks .then((response) => { response.results.forEach((result) => { viewer.entities.add({ name: result.name, description: "Details loading...", id: result.placeId, position: Cesium.Cartesian3.fromDegrees(result.location.x, result.location.y), billboard: { verticalOrigin: Cesium.VerticalOrigin.BOTTOM, heightReference: Cesium.HeightReference.CLAMP_TO_GROUND, image: pinBuilder.fromColor(Cesium.Color.ROYALBLUE, 48).toDataURL() } }); }); }); -
Run the app. When you click a category button or search for a phrase, the map should display a set of points representing place results.
Display the place address and phone number
You can access more information about a place by using the unique place associated with it. Configure a popup that will perform a get request when the user clicks on an entity. Display the address and phone number of the clicked location.
-
Create a new
Screenfor the viewer to listen for clicks in the scene. Add a conditional statement to only take action if the user clicked on an entity.Space Event Handler Use dark colors for code blocks const getDetails = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas).setInputAction(() => { if (viewer.selectedEntity) { } }, Cesium.ScreenSpaceEventType.LEFT_CLICK); -
Use the ArcGIS REST JS
getfunction to get more information about the clicked place. Pass thePlace Details placeof the clicked Entity, and set theId requestedparameter to return theFields streetandAddress telephoneproperties.Use dark colors for code blocks const getDetails = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas).setInputAction(() => { if (viewer.selectedEntity) { arcgisRest.getPlaceDetails(({ placeId: viewer.selectedEntity.id, authentication, requestedFields: ["address:streetAddress", "contactInfo:telephone"] })) } }, Cesium.ScreenSpaceEventType.LEFT_CLICK); -
Access the service response. Set the Entity
descriptionto display results if they are available.Use dark colors for code blocks const getDetails = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas).setInputAction(() => { if (viewer.selectedEntity) { arcgisRest.getPlaceDetails(({ placeId: viewer.selectedEntity.id, authentication, requestedFields: ["address:streetAddress", "contactInfo:telephone"] })) .then((result) => { let description = ""; if (result.placeDetails.address.streetAddress) description += `${result.placeDetails.address.streetAddress}<br>`; if (result.placeDetails.contactInfo.telephone) description += `${result.placeDetails.contactInfo.telephone}`; viewer.selectedEntity.description = description; }); } }, Cesium.ScreenSpaceEventType.LEFT_CLICK);
Run the app
Run the app.
The app should display a map with a search control. Upon clicking a button or entering a phrase, place results should appear on the map. Clicking a result will submit another service request to get the place address and phone number.What's next?
Learn how to use additional location services in these tutorials:


