Hi @rbrundritt ,
Thank you for your response. As per your reply, we cannot store the boundary data in the application because we need to search multiple times and across different locations. The new API you mentioned in your response and sample code resolves my response time issue. However, in the old API, I used to use entityType to restrict my search to specific levels, such as country, postal code, and county.
In the new API, how can I restrict the search to a particular postal code and ensure it is precise? I've added multiple parameters to the query, like country, state, and the desired postal code region address, but it still returns multiple results instead of just one postal code. For example:
var geocodeUrl = 'https://{azMapsDomain}/geocode?api-version=2023-06-01&view=Auto&countryRegion={countryRegion}&postalCode={postalCode}&adminDistrict={adminDistrict}';
var geocodeRequestUrl = geocodeUrl
.replace('{view}',"Auto").replace('{countryRegion}',"USA").replace('{adminDistrict}',"California").replace('{postalCode}',"90251");
It returns multiple results instead of just one.
- My second question is: Can I use this URL (geocode URL) directly, replace the values I want, and then send it to the API? I was trying to use the entire API query this way.
// var geocodeUrl='GET https://atlas.microsoft.com/geocode?api-version=2023-06-01&view={view}&addressLine={addressLine}&countryRegion={countryRegion}&adminDistrict={adminDistrict}&adminDistrict2={adminDistrict2}&adminDistrict3={adminDistrict3}&locality={locality}&postalCode={postalCode}'
But it was giving me an error in the network response.
- Before your response, I was using the old sample code API, and the
getPolygon
method used to return multiple polygon collections, which I used to filter the markers in my coordinates collection. However, the new API's getPolygon
method returns a direct JSON object, which is a geometry collection. I'm finding it difficult to filter this with Turf. I am sharing both different responses. The geometry collection is the new API result I am getting. I am Using
turf.booleanPointInPolygon
And here is my sample code. I have created a function to generate a MultiPolygon from the data, but I want to use Turf instead of my current code.
Note I Have Used Hardcorded data into my code
<!DOCTYPE html>
<html lang="en">
<head>
<title>Search for boundaries - Azure Maps Web SDK Samples</title>
<meta charset="utf-8" />
<link rel="shortcut icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<meta name="description"
content="This sample shows how to use the Services module for Azure Maps to search for locations that have boundaries and display them on the map. Azure Maps provides boundary data for administrative areas such as states, countries, cities, postal codes, and other boundaries such as industrial areas." />
<meta name="keywords"
content="Microsoft maps, map, gis, API, SDK, services, module, geolocation, search, geocode, geocoding, adminstrative boundaries, boundary, boundaries, polygon" />
<meta name="author" content="Microsoft Azure Maps" />
<meta name="version" content="1.0" />
<meta name="screenshot" content="screenshot.jpg" />
<!-- Add references to the Azure Maps Map control JavaScript and CSS files. -->
<link href="https://atlas.microsoft.com/sdk/javascript/mapcontrol/3/atlas.min.css" rel="stylesheet" />
<script src="https://atlas.microsoft.com/sdk/javascript/mapcontrol/3/atlas.min.js"></script>
<script src='https://unpkg.com/@turf/turf@6/turf.min.js'></script>
<!-- Add a reference to the Azure Maps Rest Helper JavaScript file. -->
<script src="https://samples.azuremaps.com/lib/azure-maps/azure-maps-helper.min.js"></script>
<script>
var displayedLocation;
var map, datasource, layer, popup, temp = [], poly, primaryMarkerLayer, hoverLayer, entityType;
var coordinates = [
[40.7128, -74.0060],
[34.0522, -118.2437],
[41.8781, -87.6298],
[29.7604, -95.3698],
[33.4484, -112.0740],
[39.9526, -75.1652],
[37.7749, -122.4194],
[32.7767, -96.7970],
[30.2672, -97.7431],
[35.2271, -80.8431],
[49.6062, -122.3321],
[25.7617, -80.1918],
[42.3601, -71.0589],
[38.9072, -77.0369],
[36.7783, -119.4179],
[33.7490, -84.3880],
[34.0522, -118.2437],
[40.7128, -74.0060],
[37.7749, -122.4194],
[41.8781, -87.6298],
[32.7157, -117.1611],
[39.2904, -76.6122],
[29.7604, -95.3698],
[33.4484, -112.0740],
[35.2271, -80.8431],
[30.2672, -97.7431],
[34.0522, -118.2437],
[42.3601, -71.0589],
[38.9072, -77.0369],
[36.7783, -119.4179],
[33.7490, -84.3880],
[40.7128, -74.0060],
[37.7749, -122.4194],
[41.8781, -87.6298],
[32.7157, -117.1611],
[39.2904, -76.6122],
[29.7604, -95.3698],
[33.4484, -112.0740],
[35.2271, -80.8431],
[30.2672, -97.7431],
[34.0522, -118.2437],
[42.3601, -71.0589],
[38.9072, -77.0369],
[36.7783, -119.4179],
[33.7490, -84.3880],
[40.7128, -74.0060],
[37.7749, -122.4194],
[41.8781, -87.6298],
[32.7157, -117.1611],
[39.2904, -76.6122],
[29.7604, -95.3698],
[33.4484, -112.0740],
[35.2271, -80.8431],
[30.2672, -97.7431],
[34.0522, -118.2437],
[42.3601, -71.0589],
[38.9072, -77.0369],
[36.7783, -119.4179],
[33.7490, -84.3880],
[40.7128, -74.0060],
[19.079047958462464, 73.02419406695735],
[19.200018343066304, 72.81517632316684],
[19.02502055135496, 73.10387092438363]
]
// Your Azure Maps client id for accessing your Azure Maps account.
var azureMapsClientId = 'e6b6ab59-eb5d-4d25-aa57-581135b927f0';
// URL to your authentication service that retrieves an Microsoft Entra ID Token.
var tokenServiceUrl = 'https://samples.azuremaps.com/api/GetAzureMapsToken';
// URL for the Azure Maps Search Geocoder API.
var geocodeUrl = 'https://{azMapsDomain}/geocode?api-version=2023-06-01&view=Auto&countryRegion={countryRegion}&postalCode={postalCode}&adminDistrict={adminDistrict}';
// var geocodeUrl='GET https://atlas.microsoft.com/geocode?api-version=2023-06-01&view={view}&addressLine={addressLine}&countryRegion={countryRegion}&adminDistrict={adminDistrict}&adminDistrict2={adminDistrict2}&adminDistrict3={adminDistrict3}&locality={locality}&postalCode={postalCode}'
// URL for the Azure Maps Search Polygon API.
var polygonUrl = 'https://{azMapsDomain}/search/polygon?api-version=2023-06-01&coordinates={coordinates}&view=Auto&resultType={resultType}&resolution={resolution}';
//A list of entity types that the boundary service supports.
var boundaryEntityTypes = ['adminDistrict', 'adminDistrict2', 'countryRegion', 'locality', 'neighborhood', 'postalCode', 'postalCode2', 'postalCode3', 'postalCode4'];
//A simple local-cache to store information about boundaries for this sample.
// Format: { id: { position: [], entityType: '', bbox: [] } } where the id is a pipe delimited formatted address and entity type since many places could have the same formatted address.
//Not caching boundary polygon for this sample as we want to be able to switch between resolutions.
var boundaryCache = {}, entityType;
// Function to retrieve an Azure Maps access token.
function getMap() {
// Initialize a map instance.
// Initialize a map instance.
map = new atlas.Map('myMap', {
// style: 'grayscale_light',
view: 'Auto',
// Add authentication details for connecting to Azure Maps.
authOptions: {
// Use Microsoft Entra ID authentication.
// authType: 'anonymous',
// clientId: azureMapsClientId,
// getToken: getToken
// Alternatively, use an Azure Maps key.
// Get an Azure Maps key at https://azure.com/maps.
// NOTE: The primary key should be used as the key.
authType: 'subscriptionKey',
subscriptionKey: ''
}
});
// Wait until the map resources are ready.
map.events.add('ready', function () {
// Create a data source and add it to the map.
datasource = new atlas.source.DataSource();
map.sources.add(datasource);
// Add a layers for rendering the boundaries as polygons.
layer = new atlas.layer.PolygonLayer(datasource, null, {
fillColor: 'hotpink'
});
map.layers.add(layer, 'labels');
plotMarkersonMap(coordinates)
// Create a popup but leave it closed so we can update it and display it later.
popup = new atlas.Popup();
// Add a click event to the layer.
map.events.add('click', layer, layerClicked);
});
}
function search() {
var query = document.getElementById('input').value;
entityType = document.getElementById('regionalType').value;
// Remove any previous results from the map.
datasource.clear();
document.getElementById('resultList').innerHTML = '';
popup.close();
// Show the loading icon.
document.getElementById('loadingIcon').style.display = '';
var query = "Texas,Florida,California,Alaska"
query = query.split(",")
// query.map(function (item) {
test()
// })
};
function test() {
// Search for locatoins.
var geocodeRequestUrl = geocodeUrl
.replace('{view}',"Auto").replace('{countryRegion}',"USA").replace('{adminDistrict}',"California").replace('{postalCode}',"90251");
// .replace('{view}', "Auto").replace('{countryRegion}', "USA");
processRequest(geocodeRequestUrl).then(fc => {
var r = fc.features;
var html = ['<h3>Results</h3><ol>'];
var hasBoundaries = false;
var firstResult;
// Loop through the results and extract the ones that might have boundaries.
for (var i = 0; i < r.length; i++) {
var t = r[i].properties.type;
//Convert the type property to a boundary Entity type value as it seems these values differ between services.
switch (t) {
case 'CountryRegion':
t = 'countryRegion';
break;
case 'AdminDivision1':
t = 'adminDistrict';
break;
case 'AdminDivision2':
t = 'adminDistrict2';
break;
case 'Postcode1':
t = 'postalCode';
break;
case 'Postcode2':
t = 'postalCode2';
break;
case 'Postcode3':
t = 'postalCode3';
break;
case 'Postcode4':
t = 'postalCode4';
break;
case 'PopulatedPlace':
t = 'locality';
break;
case 'Neighborhood':
t = 'neighborhood';
break;
}
//Check to see if the search result could have a boundary.
if (boundaryEntityTypes.indexOf(t) != -1) {
var id = `${r[i].properties.address.formattedAddress}|${t}`;
//Cache some basic information about the location.
boundaryCache[id] = {
position: r[i].geometry.coordinates,
entityType: t
};
if (!firstResult) {
firstResult = id;
}
// Create a link to select and zoom into the boundary.
html.push(`<li><a href="javascript:void(0)" onclick="getPolygon('${id}');">${r[i].properties.address.formattedAddress}</a> (${t}, Confidence: ${r[i].properties.confidence})</li>`);
hasBoundaries = true;
}
}
html.push('</ol>');
if (hasBoundaries) {
// Focus on the first polygon in the list.
getPolygon(firstResult);
// Display the list of results.
document.getElementById('resultList').innerHTML = html.join('');
// Hide the loading icon.
document.getElementById('loadingIcon').style.display = 'none';
} else {
document.getElementById('resultList').innerHTML = 'No boundary found.';
// Hide the loading icon.
document.getElementById('loadingIcon').style.display = 'none';
}
});
}
function plotLayersonMap() {
iconPromises = [
map.imageSprite.add('marker_icon', `<svg width="26" height="28" viewBox="-4 0 36 36" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd">
<path d="M14 0c7.732 0 14 5.641 14 12.6C28 23.963 14 36 14 36S0 24.064 0 12.6C0 5.641 6.268 0 14 0Z" fill="#fff" stroke="#000" stroke-width="1"/><circle fill="#FF474C" fill-rule="nonzero" cx="14" cy="14" r="10" /></g>
</svg>`)];
Promise.all(iconPromises).then(function () {
//Check If Main DataSource is already Added or not loaded then add it
if (!map.sources.isSourceLoaded("MainDatasource")) { //??
// Create a SymbolLayer with filtering based on zoom level and entity type.
primaryMarkerLayer = new atlas.layer.SymbolLayer(datasource, "PrimaryMarkerLayer", {
iconOptions: {
image: 'marker_icon',
options: true,
allowOverlap: true,
ignorePlacement: true,
},
textOptions: {
textField: ['get', ''],
anchor: "top", // Show the label on top of the pin.
color: ['get', 'black'], // Set the color of the text.
options: true,
allowOverlap: true,
ignorePlacement: true,
// offset: [0, -5], // Adjust offset to move text above the pin.
haloColor: 'white', // Add halo effect for better visibility.
haloWidth: 9, // Set halo width.
},
});
hoverLayer = new atlas.layer.SymbolLayer(datasource, "HoverLayer", {
iconOptions: {
image: 'none',//Hide the icon image.
allowOverlap: true,
ignorePlacement: true,
},
textOptions: {
textField: ['get', ''], //Alternatively replace with 'label' property name.
anchor: "top", //Show the label below the pin.
color: ['get', 'black'], //Set the color of the text. Alternatively pass in a color value here if the color will always be one color.
allowOverlap: true,
ignorePlacement: true,
},
filter: ['==', ['id'], ''] //Default filter to an ID of empty string. This should never happen, so this layer won't render anything at this time.
});
map.events.add('mousemove', primaryMarkerLayer, function (e) {
hoverLayer.setOptions({ filter: ['==', ['get', '_azureMapsShapeId'], e.shapes[0].getId()] });
});
// add touch events
map.events.add('mouseleave', primaryMarkerLayer, function (e) {
hoverLayer.setOptions({ filter: ['==', ['get', '_azureMapsShapeId'], ''] });
});
map.layers.add([primaryMarkerLayer, hoverLayer]);
datasource.clear();
datasource.add(temp)
}
});
}
function plotMarkersonMap(longlatColl) {
longlatColl.forEach(function (data, index) {
let markers = new atlas.Shape(new atlas.data.Point([parseFloat(data[1]), parseFloat(data[0])]), undefined, {
color: 'DodgerBlue',
label: "test",
title: "test",
});
temp.push(markers);
})
plotLayersonMap()
}
function getPolygon(id) {
popup.close();
//Make sure we have information in the cache for the provided ID.
if (boundaryCache[id]) {
displayedLocation = id;
var b = boundaryCache[id];
//Get the selected resolution type.
var resolution = document.getElementById('resolutionSelector').value;
//Retrieve the boundary from Azure Maps.
// Show the loading icon.
document.getElementById('loadingIcon').style.display = '';
// Retrieve the boudary polygons. The response may be large, so allow a longer abort time.
var polygonRequestUrl = polygonUrl
.replace('{coordinates}', b.position.join(','))
.replace('{resultType}', b.entityType)
.replace('{resolution}', resolution);
processRequest(polygonRequestUrl).then(f => {
console.log(`Response size for '${id}' boundary: ${Math.round(JSON.stringify(f).length / 1024 / 1024 * 100) / 100}MB`);
let test = [];
//Cache the boundary.
b.boundary = f;
poly = geometryCollectionToMultiPolygon(f.geometry.geometries);
datasource.add(f);
//Caclaulte and cache the bounding box of the boundary.
b.bbox = atlas.data.BoundingBox.fromData(f);
//Update the map camera so that it focuses on the geometry.
map.setCamera({ bounds: b.bbox, padding: 40 });
// Hide the loading icon.
document.getElementById('loadingIcon').style.display = 'none';
}, e => {
//An error occurred, clear the data source.
alert('Unable to retrieve boundary for this location.');
// datasource.clear();
// Hide the loading icon.
document.getElementById('loadingIcon').style.display = 'none';
});
}
}
function geometryCollectionToMultiPolygon(geometryCollection) {
const polygons = geometryCollection.filter(geometry => geometry.type === 'Polygon');
const multiPolygon = {
"type": "MultiPolygon",
"coordinates": polygons.map(polygon => polygon.coordinates)
};
return multiPolygon;
}
function reloadBoundary() {
if (displayedLocation) {
getPolygon(displayedLocation);
}
}
function layerClicked(e) {
// Get the GeoJSON version of the feature.
var f = e.shapes[0].toJson();
// Calculate stats for the boundary.
var numPos = 0, numPolygons = 0, i, j;
if (f.geometry.type === 'Polygon') {
numPolygons = 1;
// Count the number of positions in the polygons.
for (i = 0; i < f.geometry.coordinates.length; i++) {
numPos += f.geometry.coordinates[i].length;
}
} else {
// Must by MultiPolygon
// Count the number of polygons in the MultiPolygon
numPolygons = f.geometry.coordinates.length;
// Count the number of positions in all the polygons.
for (i = 0; i < f.geometry.coordinates.length; i++) {
for (j = 0; j < f.geometry.coordinates[i].length; j++) {
numPos += f.geometry.coordinates[i][j].length;
}
}
}
// Set the popup options.
popup.setOptions({
// Update the content of the popup.
content: `<div style='padding:15px;'>Type: ${f.geometry.type}<br/># polygons: ${numPolygons.toLocaleString()}<br/># positions: ${numPos.toLocaleString()}</div>`,
// Place the popup where the user clicked.
position: e.position
});
// Open the popup.
popup.open(map);
}
function filter() {
let functionName = "filter"
let filterrecords = [];
let isWithinShape = false;
try {
temp.map(function (data, index) {
isWithinShape = turf.booleanPointInPolygon(data.data.geometry.coordinates, poly)
if (isWithinShape) {
filterrecords.push(data);
}
})
} catch (error) {
console.log(error.message + "" + functionName)
}
}
</script>
</head>
<body onload="getMap()">
<div id="myMap" style="position:relative;width:100%;min-width:290px;height:600px;"></div>
<div style="position:absolute;top:15px;left:15px;background-color:white;padding:10px;border-radius:10px;">
<input type="text" id="input" value="London" />
<input type="button" onClick="search()" value="Search" />
<select id="regionalType">
<option value="">Select Region Type</option>
<option value="Country">Country</option>
<option value="CountrySecondarySubdivision">Country Secondary Subdivision</option>
<option value="CountrySubdivision">Country Subdivision</option>
<option value="CountryTertiarySubdivision">Country Tertiary Subdivision</option>
<option value="Municipality">Municipality</option>
<option value="MunicipalitySubdivision">Municipality Subdivision</option>
<option value="Neighbourhood">Neighbourhood</option>
<option value="PostalCodeArea">Postal Code Area</option>
</select>
Resolution:
<select id="resolutionSelector" onchange="reloadBoundary()">
<option value="small">Small</option>
<option value="medium" selected>Medium</option>
<option value="large">Large</option>
<option value="huge">Huge</option>
</select>
<input type="button" onClick="filter()" value="Filter" />
<div id="resultList"></div>
</div>
<img id="loadingIcon" src="/images/loadingIcon.gif" title="Loading"
style="position:absolute;left:calc(50% - 25px);top:250px;display:none;" />
<fieldset style="width:calc(100% - 30px);min-width:290px;margin-top:10px;">
<legend>Search for boundaries</legend>
This sample shows how to use Azure Maps to search for locations that have boundaries and display them on the
map.
Azure Maps provides boundary data for administrative areas such as states, countries, cities, postal codes, and
other boundaries such as industrial areas.
</fieldset>
</body>
</html>