Location Scoring - Closest POI
Distance-weighted scores are designed to favor closer location without the hard-edge of a linear measurement. 14 minutes isn’t much better than 15, but 3 minutes is significantly better.
<!DOCTYPE html>
<html>
<head>
<!-- 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 id="reachable_POIs_label">Distance-weighted scores to nearest food/beverage shop:</b>
<br><br>
<b id="markerLabel1">Marker 1: </b><div id="markerScores1"></div>
<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 = 'distance' // Can be 'time' or 'distance'
const MAX_TRAVEL = 1300 // Integer that represents meters or seconds, depending on EDGE_WEIGHT's value
const TRAVEL_MODE = 'walk' // Can be 'walk', 'car', 'bike' or 'transit'
const POI_TYPE = 'g_food' // See POI Hierarchy
const POI_URL = `https://api.targomo.com/pointofinterest/{z}/{x}/{y}.mvt` +
`?apiKey=${client.serviceKey}&group=${POI_TYPE}&loadAllTags=true&layerType=node`
// Add the map and set the initial center
const map = new maplibregl.Map({
container: 'map',
style: client.basemaps.getGLStyleURL("Light"),
zoom: 15,
minZoom: 9,
center: [2.4162, 48.8350]
}).addControl(new maplibregl.NavigationControl())
// disable scroll zoom
map.scrollZoom.disable()
const emptyData = { 'type': 'FeatureCollection', 'features': [] }
const pBar = new mipb({ fg: '#FF8319' })
// Initialize markers
const marker1 = new maplibregl.Marker({
draggable: true,
color: '#FF8319'
})
const marker2 = new maplibregl.Marker({
draggable: true,
color: '#4bb5c5'
})
marker1.setLngLat([2.4084, 48.8333]).addTo(map)
marker1.on('dragend', updateMap)
marker2.setLngLat([2.4244, 48.8368]).addTo(map)
marker2.on('dragend', updateMap)
map.on('load', () => {
addSources()
addLayers()
setupMouseEvents()
updateMap()
})
function addSources() {
map.addSource('closest-route-sources', {
type: 'geojson',
data: emptyData,
attribution: '<a href="https://www.targomo.com/developers/resources/attribution/" target="_blank">© Targomo</a>'
})
map.addSource('closest-poi-sources', {
type: 'geojson',
data: emptyData,
attribution: '<a href="https://www.targomo.com/developers/resources/attribution/" target="_blank">© Targomo</a>'
})
}
function addLayers() {
addPoiLayer()
addRouteLayer()
addClosestPoiLayer()
}
function addPoiLayer() {
map.addLayer({
id: 'poi',
type: 'circle',
source: {
type: 'vector',
tiles: [POI_URL],
attribution: '<a href="https://www.targomo.com/developers/resources/attribution/" target="_blank">© Targomo</a>'
},
'source-layer': 'poi',
paint: {
'circle-radius': 4,
'circle-color': 'green'
}
})
}
function addRouteLayer() {
map.addLayer({
id: 'closest-route',
type: 'line',
source: 'closest-route-sources',
filter: ['==', ['geometry-type'], 'LineString'],
layout: {
'line-join': 'round',
'line-cap': 'round'
},
paint: {
'line-color': 'limegreen',
'line-width': 3
}
})
}
function addClosestPoiLayer() {
map.addLayer({
id: 'closest-poi',
source: 'closest-poi-sources',
type: 'circle',
paint: {
'circle-radius': 6,
'circle-color': 'green',
'circle-stroke-width': 4,
'circle-stroke-color': 'limegreen',
'circle-stroke-opacity': .5
}
})
}
function setupMouseEvents() {
setupHoveredPoi()
setupClickedPoi()
}
function setupHoveredPoi() {
// Change the cursor to a pointer when the mouse is over the poi layer
map.on('mouseenter', 'poi', () => {
map.getCanvas().style.cursor = 'pointer'
})
// Change it back to default when it leaves
map.on('mouseleave', 'poi', () => {
map.getCanvas().style.cursor = ''
})
}
function setupClickedPoi() {
map.on('click', 'poi', (e) => {
let properties = e.features[0].properties
let description = generateDescriptionForPoi(properties)
new maplibregl.Popup()
.setLngLat(e.lngLat)
.setHTML(description).addTo(map)
})
}
function generateDescriptionForPoi(properties) {
let description = ''
if (properties.numOfPois > 1) {
description += `<strong>Multiple POIs</strong><br><em>${properties.numOfPois}
POIs here,<br>zoom in to see details</em>`
} else {
description += `<strong>POI: ${properties.groupId.replace(/^\w/, c => c.toUpperCase())}</strong><br>`
const tags = properties.numOfPois === 1 ?
JSON.parse(properties.tags) : {}
description += tags.name ? tags.name : '<em>Name not listed...</em>'
}
return description
}
async function updateMap() {
pBar.show()
const locations = getMarkersLocations(marker1, marker2)
let scores = await getScores(locations)
const closestPois = getClosestPois(scores)
updateLegend(scores)
const poiGeoJSON = poiLocationsToJSON(closestPois)
setMapClosestPoiSource(poiGeoJSON)
// Getting and showing routes for nearest POI
await createRoutes(locations, closestPois)
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 results = await client.quality.fetch(locations, {
'poi': {
type: 'closestPoiDistance',
osmTypes: [
{
'key': 'group',
'value': POI_TYPE
}
],
maxEdgeWeight: MAX_TRAVEL,
edgeWeight: EDGE_WEIGHT,
travelMode: {
[TRAVEL_MODE]: {}
},
coreServiceUrl: 'https://api.targomo.com/westcentraleurope/'
}
}, true)
return results.data
}
function getClosestPois(scores) {
let closestPois = []
Object.values(scores).map((markerScore, index) => {
let poi = getFirstPoiInDetails(markerScore)
let latLngId = nodeToLatLngId(index, poi)
closestPois.push(latLngId)
})
return closestPois
}
function getFirstPoiInDetails(marker) {
return Object.keys(marker.details.poi).map((key) => {
return marker.details.poi[key][0]
})[0]
}
function nodeToLatLngId(id, node) {
return { id: id, lat: node.lat, lng: node.lng }
}
function updateLegend(data) {
let marker1Score = Math.round(data[0].scores['poi']*100)/100
let marker2Score = Math.round(data[1].scores['poi']*100)/100
document.querySelector('#markerScores1').innerHTML = marker1Score
document.querySelector('#markerScores2').innerHTML = marker2Score
}
function poiLocationsToJSON(latlngid) {
return turf.featureCollection(latlngid.map(p => turf.point([p.lng, p.lat])))
}
function setMapClosestPoiSource(data) {
map.getSource('closest-poi-sources').setData(data)
}
// Routing API request
async function createRoutes(locations, pois) {
const options = {
travelType: TRAVEL_MODE,
maxEdgeWeight: MAX_TRAVEL,
edgeWeight: EDGE_WEIGHT,
pathSerializer: 'geojson',
// yes, 'polygon'... this comes from a legacy implementation when polygons were the only service.
// Will be changing in the future to a more generalized approach.
polygon: {
srid: 4326
}
}
let features = []
for (let location of locations) {
const target = pois.find(p => +p.id === +location.id)
if (target) {
const route = await client.routes.fetch([location], [target], options)
features = [...features, ...route[0].features]
}
}
const routeGeoJSON = turf.featureCollection(features)
map.getSource('closest-route-sources').setData(routeGeoJSON)
map.fitBounds(turf.bbox(routeGeoJSON), { padding: 50 })
}
</script>
</body>
</html>
Copied to clipboard