// Replace the API key with your actual Geoapify API Key obtained from https://myprojects.geoapify.com
const myAPIKey = "6dc7fb95a3b246cfa0f3bcef5ce9ed9a"
// Create a Leaflet map with default view centered at [0, 0] and zoom level 1
const map = L.map("my-map").setView([0, 0], 1)
// OPTIONALLY! Add a base map layer.
/*
// Determine if the device has a Retina display to load appropriate map tiles
const isRetina = L.Browser.retina;
// Define the base URLs for standard and Retina-quality map tiles
const baseUrl = "https://maps.geoapify.com/v1/tile/osm-bright/{z}/{x}/{y}.png?apiKey={apiKey}";
const retinaUrl = "https://maps.geoapify.com/v1/tile/osm-bright/{z}/{x}/{y}@2x.png?apiKey={apiKey}";
// Find more map styles here - https://apidocs.geoapify.com/docs/maps/map-tiles/#about
L.tileLayer(isRetina ? retinaUrl : baseUrl, {
attribution: 'Powered by <a href="https://www.geoapify.com/" target="_blank">Geoapify</a> | <a href="http://openmaptiles.org/" target="_blank">© OpenMapTiles</a> <a href="https://www.openstreetmap.org/copyright" target="_blank">© OpenStreetMap</a> contributors',
apiKey: myAPIKey,
maxZoom: 20,
id: 'osm-bright',
}).addTo(map);
*/
// Specify the place (e.g., "California") for which you want to retrieve administrative boundaries
const place = "Brittany, France"
const sublevel = 2
// Call the function to fetch administrative divisions using Geoapify's Geocoding API
getAdministrativeDivisions(place)
// palette
const palette = [
"#B7EFC5",
"#92E6A7",
"#6EDE8A",
"#4AD66D",
"#2DC653",
"#25A244",
"#208B3A",
"#1A7431",
"#155D27",
"#10451D",
]
buildLegend()
async function getAdministrativeDivisions(place) {
// Fetch place data (including place ID and bounding box) using Geoapify's Geocoding API
const placeData = await fetch(
`https://api.geoapify.com/v1/geocode/search?text=${encodeURIComponent(place)}&limit=1&apiKey=${myAPIKey}`,
).then((result) => result.json())
if (placeData && placeData.features.length) {
// Extract place ID and bounding box from the API response
const placeId = placeData.features[0].properties.place_id
const bbox = placeData.features[0].bbox
document.getElementById("header").textContent =
placeData.features[0].properties.formatted
// Adjust the map view to fit the bounding box of the place
map.fitBounds([
[bbox[1], bbox[0]],
[bbox[3], bbox[2]],
])
// OPTIONALLY. Add maximal map bounds
map.setMaxBounds([
[bbox[1], bbox[0]],
[bbox[3], bbox[2]],
])
// Fetch administrative divisions using Geoapify's Boundaries API
// You can store the data on your side, so you don't need to query it every time
const divisionsData = await fetch(
`https://api.geoapify.com/v1/boundaries/consists-of?id=${placeId}&sublevel=${sublevel}&geometry=geometry_1000&apiKey=${myAPIKey}`,
).then((result) => result.json())
if (divisionsData && divisionsData.features) {
addChoroplethMapData(divisionsData)
// Display the administrative divisions boundary on the map using Leaflet's geoJSON layer
const divisionsLayer = L.geoJSON(divisionsData, {
onEachFeature: (feature, layer) => {
layer.bindPopup(feature.properties.displayValue)
},
style: (feature) => {
return {
stroke: true,
fill: true,
fillColor: feature.properties.valueColor,
fillOpacity: 0.5,
color: "#3e3e3e",
weight: 2,
opacity: 0.7,
smoothFactor: 0.5,
}
},
}).addTo(map)
}
} else {
console.log("Place is not found")
}
}
// This function generates values randomly. Add the real data here. Use address parts like municipality, state, county, etc. to identify boundaries.
function addChoroplethMapData(divisionsData) {
// let's generate randomly some data per boundary
divisionsData.features.forEach((feature) => {
feature.properties.value = Math.random()
feature.properties.valueColor =
palette[Math.floor(feature.properties.value * 10)]
feature.properties.displayValue = `<b>${feature.properties.address_line1}</b>: ${feature.properties.value.toFixed(2)}`
})
}
function buildLegend() {
L.Control.Legend = L.Control.extend({
options: {
position: "topright",
},
onAdd: (map) => {
const paletteElement = L.DomUtil.create("div", "Legend")
// Loop through the density intervals and generate a label with a colored square for each interval
palette.forEach((color, index) => {
const paletteItemElement = document.createElement("div")
paletteItemElement.classList.add("palette-item")
const colorElement = document.createElement("div")
colorElement.classList.add("palette-item-color")
colorElement.style.backgroundColor = color
paletteItemElement.appendChild(colorElement)
const textElement = document.createElement("div")
const textValue = `${(0.1 * index).toFixed(2)} to ${(0.1 * (index + 1) - 0.01).toFixed(2)}`
textElement.classList.add("palette-item-text")
textElement.textContent = textValue
paletteItemElement.appendChild(textElement)
paletteElement.appendChild(paletteItemElement)
})
return paletteElement
},
onRemove: function (map) {},
})
const control = new L.Control.Legend();
control.addTo(map)
}
<div class="header" id="header"></div>
<div class="main">
<div class="map-container" id="my-map"></div>
</div>
html,
body {
margin: 0;
width: 100%;
height: 100%;
}
#my-map {
background: white;
}
.main {
width: 100%;
height: 100%;
display: flex;
flex-direction: row;
}
.map-container {
flex: 1;
height: 100%;
}
.header{
position: absolute;
pointer-events: none;
top: 10px;
left: 100px;
z-index: 1;
}
.palette-item {
display: flex;
flex-direction: row;
align-items: center;
margin: 3px 0;
}
.palette-item-color {
width: 30px;
height: 20px;
opacity: 0.5;
border: 2px solid #3e3e3e;
border-radius: 2px;
margin-right: 10px;
}