|
| 1 | +--- |
| 2 | +title: How to create Azure Maps applications using the JavaScript REST SDK (preview) |
| 3 | +titleSuffix: Azure Maps |
| 4 | +description: How to develop applications that incorporate Azure Maps using the JavaScript SDK Developers Guide. |
| 5 | +author: stevemunk |
| 6 | +ms.author: v-munksteve |
| 7 | +ms.date: 11/07/2021 |
| 8 | +ms.topic: how-to |
| 9 | +ms.service: azure-maps |
| 10 | +services: azure-maps |
| 11 | +--- |
| 12 | + |
| 13 | +# JavaScript/TypeScript REST SDK Developers Guide (preview) |
| 14 | + |
| 15 | +The Azure Maps JavaScript/TypeScript REST SDK (JavaScript SDK) supports searching using the [Azure Maps search Rest API][search], like searching for an address, fuzzy searching for a point of interest (POI), and searching by coordinates. This article will help you get started building location-aware applications that incorporate the power of Azure Maps. |
| 16 | + |
| 17 | +> [!NOTE] |
| 18 | +> Azure Maps JavaScript SDK supports the LTS version of Node.js. For more information, see [Node.js Release Working Group][Node.js Release]. |
| 19 | +
|
| 20 | +## Prerequisites |
| 21 | + |
| 22 | +- [Azure Maps account][Azure Maps account]. |
| 23 | +- [Subscription key][Subscription key] or other form of [authentication][authentication]. |
| 24 | +- [Node.js][Node.js]. |
| 25 | + |
| 26 | +> [!TIP] |
| 27 | +> You can create an Azure Maps account programmatically, Here's an example using the Azure CLI: |
| 28 | +> |
| 29 | +> ```azurecli |
| 30 | +> az maps account create --kind "Gen2" --account-name "myMapAccountName" --resource-group "<resource group>" --sku "G2" |
| 31 | +> ``` |
| 32 | +
|
| 33 | +## Install the search package |
| 34 | +
|
| 35 | +To use Azure Maps JavaScript SDK, you'll need to install the search package. Each of the Azure Maps services including search, routing, rendering and geolocation are each in their own package. |
| 36 | +
|
| 37 | +```powershell |
| 38 | +npm install @azure/maps-search |
| 39 | +``` |
| 40 | +
|
| 41 | +Once the package is installed, create a `search.js` file in the `mapsDemo` directory: |
| 42 | + |
| 43 | +```text |
| 44 | +mapsDemo |
| 45 | ++-- package.json |
| 46 | ++-- package-lock.json |
| 47 | ++-- node_modules/ |
| 48 | ++-- search.js |
| 49 | +``` |
| 50 | + |
| 51 | +### Azure Maps search service |
| 52 | + |
| 53 | +| Service Name | NPM package | Samples | |
| 54 | +|---------------|-------------------------|--------------| |
| 55 | +| [Search][search readme] | [Azure.Maps.Search][search package] | [search samples][search sample] | |
| 56 | +| [Route][js route readme] | [@azure-rest/maps-route][js route package] | [route samples][js route sample] | |
| 57 | + |
| 58 | +## Create a Node.js project |
| 59 | + |
| 60 | +The example below creates a new directory then a Node.js program named _mapsDemo_ using NPM: |
| 61 | + |
| 62 | +```powershell |
| 63 | +mkdir mapsDemo |
| 64 | +cd mapsDemo |
| 65 | +npm init |
| 66 | +``` |
| 67 | + |
| 68 | +## Create and authenticate a MapsSearchClient |
| 69 | + |
| 70 | +You'll need a `credential` object for authentication when creating the `MapsSearchClient` object used to access the Azure Maps search APIs. You can use either an Azure Active Directory (Azure AD) credential or an Azure subscription key to authenticate. For more information on authentication, see [Authentication with Azure Maps][authentication]. |
| 71 | + |
| 72 | +> [!TIP] |
| 73 | +> The`MapsSearchClient` is the primary interface for developers using the Azure Maps search library. See [Azure Maps Search client library][JS-SDK] to learn more about the search methods available. |
| 74 | +
|
| 75 | +### Using an Azure AD credential |
| 76 | + |
| 77 | +You can authenticate with Azure AD using the [Azure Identity library][Identity library]. To use the [DefaultAzureCredential][defaultazurecredential] provider, you'll need to install the `@azure/identity` package: |
| 78 | + |
| 79 | +```powershell |
| 80 | +npm install @azure/identity |
| 81 | +``` |
| 82 | + |
| 83 | +You'll need to register the new Azure AD application and grant access to Azure Maps by assigning the required role to your service principal. For more information, see [Host a daemon on non-Azure resources][Host daemon]. During this process you'll get an Application (client) ID, a Directory (tenant) ID, and a client secret. Copy these values and store them in a secure place. You'll need them in the following steps. |
| 84 | + |
| 85 | +Set the values of the Application (client) ID, Directory (tenant) ID, and client secret of your Azure AD application, and the map resource’s client ID as environment variables: |
| 86 | + |
| 87 | +| Environment Variable | Description | |
| 88 | +|-----------------------|-----------------------------------------------------------------| |
| 89 | +| AZURE_CLIENT_ID | Application (client) ID in your registered application | |
| 90 | +| AZURE_CLIENT_SECRET | The value of the client secret in your registered application | |
| 91 | +| AZURE_TENANT_ID | Directory (tenant) ID in your registered application | |
| 92 | +| MAPS_CLIENT_ID | The client ID in your Azure Map account | |
| 93 | + |
| 94 | +You can use a `.env` file for these variables. You'll need to install the [dotenv][dotenv] package: |
| 95 | + |
| 96 | +```powershell |
| 97 | +npm install dotenv |
| 98 | +``` |
| 99 | + |
| 100 | +Next, add a `.env` file in the **mapsDemo** directory and specify these properties: |
| 101 | + |
| 102 | +```text |
| 103 | +AZURE_CLIENT_ID="<client-id>" |
| 104 | +AZURE_CLIENT_SECRET="<client-secret>" |
| 105 | +AZURE_TENANT_ID="<tenant-id>" |
| 106 | +MAPS_CLIENT_ID="<maps-client-id>" |
| 107 | +``` |
| 108 | + |
| 109 | +Once your environment variables are created, you can access them in your JavaScript code: |
| 110 | + |
| 111 | +```JavaScript |
| 112 | +const { MapsSearchClient } = require("@azure/maps-search"); |
| 113 | +const { DefaultAzureCredential } = require("@azure/identity"); |
| 114 | +require("dotenv").config(); |
| 115 | + |
| 116 | +const credential = new DefaultAzureCredential(); |
| 117 | +const client = new MapsSearchClient(credential, process.env.MAPS_CLIENT_ID); |
| 118 | +``` |
| 119 | + |
| 120 | +### Using a subscription key credential |
| 121 | + |
| 122 | +You can authenticate with your Azure Maps subscription key. Your subscription key can be found in the **Authentication** section in the Azure Maps account as shown in the following screenshot: |
| 123 | + |
| 124 | +:::image type="content" source="./media/rest-sdk-dev-guides/subscription-key.png" alt-text="A screenshot showing the subscription key in the Authentication section of an Azure Maps account." lightbox="./media/rest-sdk-dev-guides/subscription-key.png"::: |
| 125 | + |
| 126 | +You need to pass the subscription key to the `AzureKeyCredential` class provided by the [Azure Maps Search client library for JavaScript/TypeScript][JS-SDK]. For security reasons, it's better to specify the key as an environment variable than to include it in your source code. |
| 127 | + |
| 128 | +You can accomplish this by using a `.env` file to store the subscription key variable. You'll need to install the [dotenv][dotenv] package to retrieve the value: |
| 129 | + |
| 130 | +```powershell |
| 131 | +npm install dotenv |
| 132 | +``` |
| 133 | + |
| 134 | +Next, add a `.env` file in the **mapsDemo** directory and specify the property: |
| 135 | + |
| 136 | +```text |
| 137 | +MAPS_SUBSCRIPTION_KEY="<subscription-key>" |
| 138 | +``` |
| 139 | + |
| 140 | +Once your environment variable is created, you can access it in your JavaScript code: |
| 141 | + |
| 142 | +```JavaScript |
| 143 | +const { MapsSearchClient, AzureKeyCredential } = require("@azure/maps-search"); |
| 144 | +require("dotenv").config(); |
| 145 | + |
| 146 | +const credential = new AzureKeyCredential(process.env.MAPS_SUBSCRIPTION_KEY); |
| 147 | +const client = new MapsSearchClient(credential); |
| 148 | +``` |
| 149 | + |
| 150 | +## Fuzzy search an entity |
| 151 | + |
| 152 | +The following code snippet demonstrates how, in a simple console application, to import the `azure-maps-search` package and perform a fuzzy search on “Starbucks” near Seattle: |
| 153 | + |
| 154 | +```JavaScript |
| 155 | + |
| 156 | +const { MapsSearchClient, AzureKeyCredential } = require("@azure/maps-search"); |
| 157 | +require("dotenv").config(); |
| 158 | + |
| 159 | +async function main() { |
| 160 | + // Authenticate with Azure Map Subscription Key |
| 161 | + const credential = new AzureKeyCredential(process.env.MAPS_SUBSCRIPTION_KEY); |
| 162 | + const client = new MapsSearchClient(credential); |
| 163 | + |
| 164 | + // Setup the fuzzy search query |
| 165 | + const response = await client.fuzzySearch({ |
| 166 | + query: "Starbucks", |
| 167 | + coordinates: [47.61010, -122.34255], |
| 168 | + countryCodeFilter: ["US"], |
| 169 | + }); |
| 170 | + |
| 171 | + // Log the result |
| 172 | + console.log(`Starbucks search result nearby Seattle:`); |
| 173 | + response.results.forEach((result) => { |
| 174 | + console.log(`\ |
| 175 | + * ${result.address.streetNumber} ${result.address.streetName} |
| 176 | + ${result.address.municipality} ${result.address.countryCode} ${result.address.postalCode} |
| 177 | + Coordinate: (${result.position[0].toFixed(4)}, ${result.position[1].toFixed(4)})\ |
| 178 | + `); |
| 179 | +} |
| 180 | + |
| 181 | +main().catch((err) => { |
| 182 | + console.error(err); |
| 183 | +}); |
| 184 | + |
| 185 | +``` |
| 186 | +
|
| 187 | +In the above code snippet, you create a `MapsSearchClient` object using your Azure credentials. This is done using your Azure Maps subscription key, however you could use the [Azure AD credential](#using-an-azure-ad-credential) discussed in the previous section. You then pass the search query and options to the `fuzzySearch` method. Search for Starbucks (`query: "Starbucks"`) near Seattle (`coordinates: [47.61010, -122.34255], countryFilter: ["US"]`). For more information, see [FuzzySearchRequest][FuzzySearchRequest] in the [Azure Maps Search client library for JavaScript/TypeScript][JS-SDK]. |
| 188 | +
|
| 189 | +The method `fuzzySearch` provided by `MapsSearchClient` will forward the request to Azure Maps REST endpoints. When the results are returned, they're written to the console. For more information, see [SearchAddressResult][SearchAddressResult]. |
| 190 | +
|
| 191 | +Run `search.js` with Node.js: |
| 192 | +
|
| 193 | +```powershell |
| 194 | +node search.js |
| 195 | +``` |
| 196 | +
|
| 197 | +## Search an Address |
| 198 | +
|
| 199 | +The [searchAddress][searchAddress] method can be used to get the coordinates of an address. Modify the `search.js` from the sample as follows: |
| 200 | +
|
| 201 | +```JavaScript |
| 202 | +const { MapsSearchClient, AzureKeyCredential } = require("@azure/maps-search"); |
| 203 | +require("dotenv").config(); |
| 204 | + |
| 205 | +async function main() { |
| 206 | + const credential = new AzureKeyCredential(process.env.MAPS_SUBSCRIPTION_KEY); |
| 207 | + const client = new MapsSearchClient(credential); |
| 208 | + |
| 209 | + const response = await client.searchAddress( |
| 210 | + "1912 Pike Pl, Seattle, WA 98101, US" |
| 211 | + ); |
| 212 | + |
| 213 | + console.log(`The coordinate is: ${response.results[0].position}`);} |
| 214 | + |
| 215 | +main().catch((err) => { |
| 216 | + console.error(err); |
| 217 | +}); |
| 218 | +``` |
| 219 | +
|
| 220 | +The results returned from `client.searchAddress` are ordered by confidence score and in this example only the first result returned with be displayed to the screen. |
| 221 | +
|
| 222 | +## Batch reverse search |
| 223 | +
|
| 224 | +Azure Maps Search also provides some batch query methods. These methods will return Long Running Operations (LRO) objects. The requests might not return all the results immediately, so you can wait until completion or query the result periodically. The example below demonstrates how to call batched reverse search method: |
| 225 | +
|
| 226 | +```JavaScript |
| 227 | + const poller = await client.beginReverseSearchAddressBatch([ |
| 228 | + // This is an invalid query |
| 229 | + { coordinates: [148.858561, 2.294911] }, |
| 230 | + { |
| 231 | + coordinates: [47.61010, -122.34255], |
| 232 | + }, |
| 233 | + { coordinates: [47.6155, -122.33817] }, |
| 234 | + options: { radiusInMeters: 5000 }, |
| 235 | + ]); |
| 236 | +``` |
| 237 | +
|
| 238 | +In this example, three queries are passed into the _batched reverse search_ request. The first query is invalid, see [Handing failed requests](#handing-failed-requests) for an example showing how to handle the invalid query. |
| 239 | +
|
| 240 | +Use the `getResult` method from the poller to check the current result. You check the status using `getOperationState` to see if the poller is still running. If it is, you can keep calling `poll` until the operation is finished: |
| 241 | +
|
| 242 | +```JavaScript |
| 243 | + while (poller.getOperationState().status === "running") { |
| 244 | + const partialResponse = poller.getResult(); |
| 245 | + logResponse(partialResponse) |
| 246 | + await poller.poll(); |
| 247 | + } |
| 248 | +``` |
| 249 | +
|
| 250 | +Alternatively, you can wait until the operation has completed, by using `pollUntilDone()`: |
| 251 | +
|
| 252 | +```JavaScript |
| 253 | +const response = await poller.pollUntilDone(); |
| 254 | +logResponse(response) |
| 255 | +``` |
| 256 | +
|
| 257 | +A common scenario for LRO is to resume a previous operation later. Do that by serializing the poller’s state with the `toString` method, and rehydrating the state with a new poller using `resumeReverseSearchAddressBatch`: |
| 258 | +
|
| 259 | +```JavaScript |
| 260 | + const serializedState = poller.toString(); |
| 261 | + const rehydratedPoller = await client.resumeReverseSearchAddressBatch( |
| 262 | + serializedState |
| 263 | + ); |
| 264 | + const response = await rehydratedPoller.pollUntilDone(); |
| 265 | + logResponse(response); |
| 266 | +``` |
| 267 | +
|
| 268 | +Once you get the response, you can log it: |
| 269 | +
|
| 270 | +```JavaScript |
| 271 | +function logResponse(response) { |
| 272 | + console.log( |
| 273 | + `${response.totalSuccessfulRequests}/${response.totalRequests} succeed.` |
| 274 | + ); |
| 275 | + response.batchItems.forEach((item, idx) => { |
| 276 | + console.log(`The result for ${idx + 1}th request:`); |
| 277 | + // Check if the request is failed |
| 278 | + if (item.response.error) { |
| 279 | + console.error(item.response.error); |
| 280 | + } else { |
| 281 | + item.response.results.forEach((result) => { |
| 282 | + console.log(result.address.freeformAddress); |
| 283 | + }); |
| 284 | + } |
| 285 | + }); |
| 286 | +} |
| 287 | +``` |
| 288 | +
|
| 289 | +### Handing failed requests |
| 290 | +
|
| 291 | +Handle failed requests by checking for the `error` property in the response batch item. See the `logResponse` function in the completed batch reverse search example below. |
| 292 | +
|
| 293 | +### Completed batch reverse search example |
| 294 | +
|
| 295 | +The complete code for the reverse address batch search example: |
| 296 | +
|
| 297 | +```JavaScript |
| 298 | +const { MapsSearchClient, AzureKeyCredential } = require("@azure/maps-search"); |
| 299 | +require("dotenv").config(); |
| 300 | + |
| 301 | +async function main() { |
| 302 | + const credential = new AzureKeyCredential(process.env.MAPS_SUBSCRIPTION_KEY); |
| 303 | + const client = new MapsSearchClient(credential); |
| 304 | + |
| 305 | + const poller = await client.beginReverseSearchAddressBatch([ |
| 306 | + // This is an invalid query |
| 307 | + { coordinates: [148.858561, 2.294911] }, |
| 308 | + { |
| 309 | + coordinates: [47.61010, -122.34255], |
| 310 | + }, |
| 311 | + { coordinates: [47.6155, -122.33817] }, |
| 312 | + options: { radiusInMeters: 5000 }, |
| 313 | + ]); |
| 314 | + |
| 315 | + // Get the partial result and keep polling |
| 316 | + while (poller.getOperationState().status === "running") { |
| 317 | + const partialResponse = poller.getResult(); |
| 318 | + logResponse(partialResponse); |
| 319 | + await poller.poll(); |
| 320 | + } |
| 321 | + |
| 322 | + // You can simply wait for the operation is done |
| 323 | + // const response = await poller.pollUntilDone(); |
| 324 | + // logResponse(response) |
| 325 | + |
| 326 | + // Resume the poller |
| 327 | + const serializedState = poller.toString(); |
| 328 | + const rehydratedPoller = await client.resumeReverseSearchAddressBatch( |
| 329 | + serializedState |
| 330 | + ); |
| 331 | + const response = await rehydratedPoller.pollUntilDone(); |
| 332 | + logResponse(response); |
| 333 | +} |
| 334 | + |
| 335 | +function logResponse(response) { |
| 336 | + console.log( |
| 337 | + `${response.totalSuccessfulRequests}/${response.totalRequests} succeed.` |
| 338 | + ); |
| 339 | + response.batchItems.forEach((item, idx) => { |
| 340 | + console.log(`The result for ${idx + 1}th request:`); |
| 341 | + if (item.response.error) { |
| 342 | + console.error(item.response.error); |
| 343 | + } else { |
| 344 | + item.response.results.forEach((result) => { |
| 345 | + console.log(result.address.freeformAddress); |
| 346 | + }); |
| 347 | + } |
| 348 | + }); |
| 349 | +} |
| 350 | + |
| 351 | +main().catch((err) => { |
| 352 | + console.error(err); |
| 353 | +}); |
| 354 | +``` |
| 355 | +
|
| 356 | +## Additional information |
| 357 | +
|
| 358 | +- The [Azure Maps Search client library for JavaScript/TypeScript][JS-SDK]. |
| 359 | +
|
| 360 | +[JS-SDK]: /javascript/api/overview/azure/maps-search-readme?view=azure-node-preview |
| 361 | +
|
| 362 | +[defaultazurecredential]: https://github.com/Azure/azure-sdk-for-js/tree/@azure/maps-search_1.0.0-beta.1/sdk/identity/identity#defaultazurecredential |
| 363 | +
|
| 364 | +[searchAddress]: /javascript/api/@azure/maps-search/mapssearchclient?view=azure-node-preview#@azure-maps-search-mapssearchclient-searchaddress |
| 365 | +
|
| 366 | +[FuzzySearchRequest]: /javascript/api/@azure/maps-search/fuzzysearchrequest?view=azure-node-preview |
| 367 | +
|
| 368 | +[SearchAddressResult]: /javascript/api/@azure/maps-search/searchaddressresult?view=azure-node-preview |
| 369 | +
|
| 370 | +[search]: /rest/api/maps/search |
| 371 | +[Node.js Release]: https://github.com/nodejs/release#release-schedule |
| 372 | +[Node.js]: https://nodejs.org/en/download/ |
| 373 | +[Azure Maps account]: quick-demo-map-app.md#create-an-azure-maps-account |
| 374 | +[Subscription key]: quick-demo-map-app.md#get-the-primary-key-for-your-account |
| 375 | +
|
| 376 | +[authentication]: azure-maps-authentication.md |
| 377 | +[Identity library]: /javascript/api/overview/azure/identity-readme |
| 378 | +
|
| 379 | +[Host daemon]: /azure/azure-maps/how-to-secure-daemon-app#host-a-daemon-on-non-azure-resources |
| 380 | +[dotenv]: https://github.com/motdotla/dotenv#readme |
| 381 | +
|
| 382 | +[search package]: https://www.npmjs.com/package/@azure/maps-search |
| 383 | +[search readme]: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/maps/maps-search/README.md |
| 384 | +[search sample]: https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/maps/maps-search/samples/v1-beta |
| 385 | +
|
| 386 | +[js route readme]: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/maps/maps-route-rest/README.md |
| 387 | +[js route package]: https://www.npmjs.com/package/@azure-rest/maps-route |
| 388 | +[js route sample]: https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/maps/maps-route-rest/samples/v1-beta |
0 commit comments