Location Scoring - Reachable POI Count
Comparing multiple locations based on how many categorical POIs they can access.
<!DOCTYPE html>
<html>
<head>
<!-- use maki-based iconfont to symbolize POIs by type -->
<link rel="stylesheet" href="https://releases.targomo.com/tgm-icons/webfont/tgm-icon.css">
<!-- Include maplibregl javascript and css -->
<script src="https://unpkg.com/maplibre-gl@2.4.0/dist/maplibre-gl.js"></script>
<link href="https://unpkg.com/maplibre-gl@2.4.0/dist/maplibre-gl.css" rel="stylesheet">
<!-- Include targomo core -->
<script src="https://releases.targomo.com/core/latest.min.js"></script>
<!-- Include turf for view fitting -->
<script src="https://npmcdn.com/@turf/turf/turf.min.js"></script>
<!-- Include micro progress bar -->
<script src="https://www.targomo.com/developers/scripts/mipb.min.js"></script>
<style>
body, html {
margin: 0;
width: 100%;
height: 100%;
}
#map {
width: 100%;
height: 100%;
}
.score_result {
position: absolute; padding: 8px;
top: 10px; left: 10px;
font-family: 'Open Sans', sans-serif;
background: white; border-radius: 4px;
box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.1);
z-index: 1;
}
.maplibregl-marker {
cursor: pointer;
}
#markerScores1, #markerScores2 {
display: inline;
}
#markerLabel1 {
color: #FF8319;
}
#markerLabel2 {
color: #4BB5C5;
}
</style>
</head>
<body>
<div class="score_result">
<b>Number of reachable POIs:</b>
<br><br>
<b id="markerLabel1">Marker 1: </b><div id="markerScores1"></div><br>
<b id="markerLabel2">Marker 2: </b><div id="markerScores2"></div>
</div>
<!-- where the map will live -->
<div id="map"></div>
<script>// create targomo client
const client = new tgm.TargomoClient('westcentraleurope', '__targomo_key_here__')
// Travel options
const EDGE_WEIGHT = 'time' // Can be 'time' or 'distance'
const MAX_TRAVEL = 400 // Integer that represents meters or seconds, depending on EDGE_WEIGHT's value
const TRAVEL_MODE = 'walk' // Can be 'walk', 'car', 'bike' or 'transit'
// Specifying which POI groups we want to show on the map... See the POI hierarchy for more info.
const POI_TYPES = ['supermarket', 'convenience']
const POI_URL = `https://api.targomo.com/pointofinterest/{z}/{x}/{y}.mvt` +
`?apiKey=${client.serviceKey}&group=${POI_TYPES[0]}&group=${POI_TYPES[1]}&loadAllTags=false&layerType=node`
// Add the map and set the initial center
const map = new maplibregl.Map({
container: 'map',
style: client.basemaps.getGLStyleURL('Light'),
zoom: 14,
minZoom: 9,
center: [2.378, 48.8451]
}).addControl(new maplibregl.NavigationControl())
// disable scroll zoom
map.scrollZoom.disable()
const emptyData = { 'type': 'FeatureCollection', 'features': [] }
const pBar = new mipb({ fg: '#FF8319' })
// Initialize markers
let marker1 = new maplibregl.Marker({
draggable: true,
color: '#FF8319'
})
let marker2 = new maplibregl.Marker({
draggable: true,
color: '#4BB5C5'
})
marker1.setLngLat([2.3722, 48.8458]).addTo(map)
marker1.on('dragend', updateMap)
marker2.setLngLat([2.3893, 48.8443]).addTo(map)
marker2.on('dragend', updateMap)
map.on('load', async () => {
preLoadIconsForMap()
addSources()
addLayers()
updateMap()
})
function addSources() {
map.addSource('reachable-poi-sources', {
type: 'geojson',
data: emptyData,
attribution: '<a href="https://www.targomo.com/developers/resources/attribution/" target="_blank">© Targomo</a>'
})
}
function preLoadIconsForMap() {
for (let icon of ['tgm-convenience', 'tgm-grocery', 'tgm-circle']) {
map.loadImage(`https://releases.targomo.com/tgm-icons/images/${icon}.png`, (error, image) => {
if (error) throw error
if (!map.hasImage(icon)) map.addImage(icon, image, { sdf: true })
})
}
}
function addLayers() {
addPoisLayer()
addReachablePoisLayer()
addAggregatedPoisLayer()
}
function addPoisLayer() {
map.addLayer({
id: 'poi',
type: 'symbol',
source: {
type: 'vector',
tiles: [POI_URL],
minzoom: 9,
attribution: '<a href="https://www.targomo.com/developers/resources/attribution/" target="_blank">© Targomo</a>'
},
'source-layer': 'poi',
layout: {
'icon-allow-overlap': true,
'icon-image': [
'match', ['get', 'groupId'],
'convenience', 'tgm-convenience',
'supermarket', 'tgm-grocery',
'tgm-circle'
],
'icon-size': 0.25
},
filter: ['==', ['get', 'numOfPois'], 1],
paint: {
'icon-color': 'gray'
}
})
}
function addReachablePoisLayer() {
map.addLayer({
id: 'reachable-poi',
type: 'symbol',
source: 'reachable-poi-sources',
layout: {
'icon-allow-overlap': true,
'icon-image': [
'match', ['get', 'groupId'],
'convenience', 'tgm-convenience',
'supermarket', 'tgm-grocery',
'tgm-circle'
],
'icon-size': 0.25
},
paint: {
'icon-color': [
'match', ['get', 'groupId'],
'convenience', 'mediumpurple',
'supermarket', 'darkblue',
'gray'
]
}
})
}
function addAggregatedPoisLayer() {
map.addLayer({
id: 'poi-many',
type: 'symbol',
source: {
type: 'vector',
tiles: [POI_URL],
minzoom: 9,
attribution: '<a href="https://www.targomo.com/developers/resources/attribution/" target="_blank">© Targomo</a>'
},
'source-layer': 'poi',
layout: {
'icon-allow-overlap': true,
'icon-image': 'tgm-circle',
'icon-size': 0.25,
'text-field': '{numOfPois}',
'text-size': 8
},
filter: ['>', ['get', 'numOfPois'], 1],
paint: {
'icon-color': 'gray',
'text-color': 'white'
}
})
}
async function updateMap() {
pBar.show()
let locations = getMarkersLocations(marker1, marker2)
let scores = await getScores(locations)
const reachablePois = getReachablePois(scores)
const poiGeoJSON = poiLocationsToJSON(reachablePois)
setMapReachablePoiSource(poiGeoJSON)
updateLegend(scores)
pBar.hide()
}
function getMarkersLocations(marker1, marker2) {
let locations = []
locations[0] = { ...marker1.getLngLat(), id: 0 }
locations[1] = { ...marker2.getLngLat(), id: 1 }
return locations
}
// Location Scoring API request.
async function getScores(locations) {
const config = POI_TYPES.reduce((acc, cur) => {
acc[cur] = {
type: 'poiCoverageCount',
osmTypes: [ { 'key': 'group', 'value': cur } ],
maxEdgeWeight: MAX_TRAVEL,
edgeWeight: EDGE_WEIGHT,
travelMode: { [TRAVEL_MODE]: {} },
coreServiceUrl: 'https://api.targomo.com/westcentraleurope/'
}
return acc
}, {})
let result = await client.quality.fetch(locations, config, true)
return result.data
}
function getReachablePois(scores) {
let reachablePois = []
Object.values(scores).map((markerScore) => {
let reachableConvenience = getReachablePoiByGroupId(markerScore, 'convenience')
let reachableSupermarkets = getReachablePoiByGroupId(markerScore, 'supermarket')
reachablePois.push(...reachableConvenience, ...reachableSupermarkets)
})
return reachablePois
}
function getReachablePoiByGroupId(marker, groupId) {
return Object.keys(marker.details[groupId]).map((key) => {
let node = marker.details[groupId][key][0]
return nodeToLatLngId(node, groupId)
})
}
function nodeToLatLngId(node, groupId) {
return { id: 0, lat: node.lat, lng: node.lng, groupId: groupId}
}
function poiLocationsToJSON(latlngid) {
return turf.featureCollection(latlngid.map(p => {
let point = turf.point([p.lng, p.lat])
point.properties = {'groupId':p.groupId}
return point
}))
}
function setMapReachablePoiSource(data) {
map.getSource('reachable-poi-sources').setData(data)
}
function updateLegend(data) {
let marker1scores = data[0].scores
let marker2scores = data[1].scores
let supermarketsCountMarker1 = marker1scores[POI_TYPES[0]]
let convenienceCountMarker1 = marker1scores[POI_TYPES[1]]
let supermarketsCountMarker2 = marker2scores[POI_TYPES[0]]
let convenienceCountMarker2 = marker2scores[POI_TYPES[1]]
let poiElem1 = document.querySelector('#markerScores1')
poiElem1.innerHTML = supermarketsCountMarker1 + ' supermarkets, ' + convenienceCountMarker1 + ' convenience stores'
let poiElem2 = document.querySelector('#markerScores2')
poiElem2.innerHTML = supermarketsCountMarker2 + ' supermarkets, ' + convenienceCountMarker2 + ' convenience stores'
}</script>
</body>
</html>
Copied to clipboard