Get data from OSM using Overpass API and render it on a Mapbox GL map.
Last active
June 29, 2018 08:01
-
-
Save jsanz/1bc2f6a9c998fbc014bc1820dcb2c76d to your computer and use it in GitHub Desktop.
#MapboxGL + #OSM data
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<html> | |
<head> | |
<link rel='stylesheet' href='https://api.tiles.mapbox.com/mapbox-gl-js/v0.44.1/mapbox-gl.css' /> | |
<script src='https://api.tiles.mapbox.com/mapbox-gl-js/v0.44.1/mapbox-gl.js'></script> | |
<script src="https://tyrasd.github.io/osmtogeojson/osmtogeojson.js"></script> | |
<style> | |
#map { | |
margin: 0; | |
height: 100vh; | |
width: 100%; | |
} | |
.mapboxgl-popup-content a { | |
color: #4a86cf; | |
text-decoration-line: none; | |
} | |
.mapboxgl-popup-content ul { | |
padding-left: 18px; | |
} | |
.osm-source{ | |
font-size: 0.7em; | |
} | |
.filter-ctrl { | |
position: absolute; | |
top: 18px; | |
right: 60px; | |
z-index: 1; | |
width: 180px; | |
} | |
.filter-ctrl input[type=text] { | |
font: 12px/20px 'Helvetica Neue', Arial, Helvetica, sans-serif; | |
width: 100%; | |
border: 0; | |
background-color: #fff; | |
height: 40px; | |
margin: 0; | |
color: rgba(0,0,0,.5); | |
padding: 10px; | |
box-shadow: 0 0 0 2px rgba(0,0,0,0.1); | |
border-radius: 3px; | |
} | |
.mapboxgl-popup-anchor-top .mapboxgl-popup-content{ | |
background-color: #B4C6DD; | |
padding: 5px; | |
} | |
</style> | |
</head> | |
<body> | |
<div id="map" style="width: 100%; height: 100vh;"></div> | |
<div class='filter-ctrl'> | |
<input id='filter-input' type='text' name='filter' placeholder='Filter by name' /> | |
</div> | |
<script> | |
/* parameters */ | |
var /* List of ways to include in the map */ | |
osm_ways = [ | |
27152910, // almeria railway | |
29220363, // almeria university | |
435775764, // Civitas | |
37923960, // airport | |
{ osm_id: 36406179, name: 'UAL Parking'}, // UAL parking | |
], | |
/* List of nodes to include in the map */ | |
osm_nodes = [ | |
4414057566, // la mala | |
{ osm_id: 4433529185, name: 'Overriden name'}, // hortensia | |
2870058034, // 292 | |
974730957, // 144 | |
], | |
/* Basemap Styles | |
OpenMapTiles https://openmaptiles.github.io/positron-gl-style/style-cdn.json | |
CARTO https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json | |
*/ | |
basemap_style = 'https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json', | |
/* initial center and zoom level, you can use bboxfinder.com to find proper values */ | |
center = [-2.421,36.823], | |
zoom = 12, | |
/* Main color to use anywhere */ | |
main_color = '#4a86cf', | |
/* Icon for the points ont the map */ | |
icon = 'https://i.imgur.com/cux5ypg.png', // '/theme/img/Gnomelogo-footprint.png', | |
/* White list of properties to allow to | |
be displayed in the popup, order matters! */ | |
popup_properties = [ | |
'description', | |
'shop','amenity','wheelchair', | |
'highway', 'network', 'bench', 'shelter', 'ref', | |
'adr:street', | |
'website','wikidata','wikipedia' | |
] | |
; | |
</script> | |
<script> | |
/* Functions and helpers */ | |
var overpass_url = 'http://overpass-api.de/api/interpreter', | |
/* from https://www.npmjs.com/package/geojson-polygon-center */ | |
polygon_center = function (polygon) { | |
var minx = miny = 1000, | |
maxx = maxy = -1000 | |
polygon = polygon[0] | |
for (var i = 0; i < polygon.length; i++) { | |
var point = polygon[i] | |
var x = point[0] | |
var y = point[1] | |
if (x < minx) minx = x | |
else if (x > maxx) maxx = x | |
if (y < miny) miny = y | |
else if (y > maxy) maxy = y | |
} | |
return { | |
type: 'Point', | |
coordinates: [ | |
minx + ((maxx - minx) / 2), | |
miny + ((maxy - miny) / 2) | |
] | |
} | |
}, | |
/* helper to get the list of ids */ | |
get_ids = function(el){ | |
return typeof el == "number" ? el : el['osm_id']; | |
}, | |
/* takes a feature, and augment it with any custom properties | |
passed on thi list of nodes and ways */ | |
get_props = function(feature){ | |
var /* filter checker */ | |
filter_node = function(node){ | |
return typeof node != "number" && node['osm_id'] == feat_id; | |
}, | |
properties = feature['properties'], | |
/* feature id */ | |
feat_id = properties['id'], | |
/* candidates */ | |
candidate = osm_nodes.filter(filter_node) | |
.concat(osm_ways.filter(filter_node)); | |
if (candidate.length == 1){ | |
properties = Object.assign(properties,candidate[0]); | |
}; | |
return feature; | |
}, | |
/* Generate a valid OSM Overpass API request */ | |
get_osm_query = function(){ | |
var ways = osm_ways.map(get_ids).join(','); | |
var nodes = osm_nodes.map(get_ids).join(','); | |
return `[out:xml][timeout:300]; | |
( | |
way(id:${ways}); | |
node(id:${nodes}); | |
)->.a; | |
(.a; .a >;);out qt;` | |
} | |
/* moves tags up to the main properties function */ | |
tags_to_props = function(feature){ | |
properties = feature['properties'] | |
tags = properties['tags']; | |
Object.assign(properties, tags); | |
delete properties['tags']; | |
if (properties['id'] == undefined){ | |
properties['id'] = String(feature['id']) | |
} else { | |
properties['id'] = String(properties['id']) | |
}; | |
// Override the name in Engish, if it exists | |
if (properties['name:en'] != undefined){ | |
properties['name'] = properties['name:en'] | |
}; | |
return feature | |
}, | |
/* transforms OSM data into geojson and adds that as | |
points and labels to the map */ | |
load_osm_data = function(data){ | |
console.log('loading data...'); | |
var // Convert to GeoJSON | |
geojson_data = osmtogeojson(data), | |
// Filter ways | |
polys_geojson = geojson_data.features.filter(function(feature){ | |
return feature.properties.type == "way" | |
}), | |
// Filter points | |
points_geojson = geojson_data.features.filter(function(feature){ | |
return feature.properties.type == "node" | |
}), | |
// Generate centroids for points | |
polys_geojson_points = polys_geojson.map(function(poly){ | |
copy = JSON.parse(JSON.stringify(poly)); | |
copy['geometry'] = polygon_center(copy.geometry.coordinates); | |
return copy | |
}), | |
// Get together both set of points | |
all_features = points_geojson.concat(polys_geojson_points), | |
// Get all properties out from the tags | |
points_geojson_props = all_features.map(tags_to_props), | |
// Override any custom properties | |
final_points = points_geojson_props.map(get_props); | |
// Build final geojson collection | |
return { | |
'type': 'FeatureCollection', | |
'features': final_points | |
}; | |
}, | |
/* Loads the data retrieved into a mapboxgl map */ | |
add_layers = function(map, data){ | |
map.loadImage(icon, function(error, image) { | |
if (error) throw error; | |
map.addImage('gnome', image); | |
/* Icon layer */ | |
map.addLayer({ | |
'id': 'guadec_icon', | |
'type': 'symbol', | |
'source': { | |
'type': 'geojson', | |
'data': geojson_data | |
}, | |
'layout': { | |
"symbol-placement": "point", | |
"text-field": '{name}' , | |
"icon-image": "gnome", | |
"text-font": ["Open Sans Regular"], | |
"icon-allow-overlap": true, | |
"text-offset": [.3,.3], | |
"text-anchor": "top-left", | |
"text-max-width": 5, | |
"text-justify": "left", | |
"text-allow-overlap": false, | |
"text-optional": true, | |
"icon-size": { | |
"stops": [ | |
[0, 0.10], | |
[12, 0.12], | |
[14, 0.15], | |
[18, 0.25] | |
] | |
}, | |
"text-size": { | |
"stops": [ | |
[0, 0], | |
[9,0], | |
[12, 15], | |
[16, 20] | |
] | |
} | |
}, | |
'paint': { | |
"text-color": main_color, | |
"icon-opacity": 1, | |
"text-opacity": { | |
"stops": [ | |
[0, 0], | |
[9,0], | |
[12, 1] | |
] | |
}, | |
"text-halo-color": "white", | |
"text-halo-width": 1.5, | |
"text-halo-blur": 1 | |
} | |
},'place_hamlet'); | |
}); | |
}; | |
</script> | |
<script> | |
/* SCRIPT */ | |
var geojson_data = {}, | |
map = new mapboxgl.Map({ | |
container: 'map', | |
style: basemap_style, | |
center: center, | |
zoom: zoom, | |
attributionControl: true | |
}); | |
/*Once map is loaded, get data from OSM to add as a new layer */ | |
map.on('load', function() { | |
console.log('fetching osm data...') | |
fetch(overpass_url,{ | |
method: "POST", | |
body: get_osm_query()}) | |
.then(response => response.text()) | |
.then(str => (new window.DOMParser()).parseFromString(str, "text/xml")) | |
.then(function(data){ | |
// Get the geojson to work | |
geojson_data = load_osm_data(data); | |
console.log(geojson_data); | |
// Add it as a layer | |
add_layers(map,geojson_data); | |
}) | |
.catch(error => console.log("Error:", error)); | |
}); | |
/* Navigation control */ | |
map.addControl(new mapboxgl.NavigationControl()); | |
/* Popup up singleton */ | |
var tooltip = new mapboxgl.Popup({ | |
closeButton: false, | |
closeOnClick: true, | |
anchor: "top" | |
}); | |
// helper to render the properties | |
var get_properties_list = function(properties){ | |
return popup_properties.filter(function(key){ | |
return (Object.keys(properties).findIndex(x => x == key) > -1) | |
}) | |
.map(function(key){ | |
if (key == 'website'){ | |
return `<li><a href="${properties[key]}">${key}</a></li>` | |
} else if (key == 'wikidata'){ | |
return `<li><a href="https://www.wikidata.org/wiki/${properties[key]}">${key}</a></li>` | |
} else if (key == 'wikipedia'){ | |
return `<li><a href="https://en.wikipedia.org/wiki/${properties[key]}">${key}</a></li>` | |
} else { | |
return `<li><strong>${key}</strong>: ${properties[key]}</li>` | |
} | |
}).join('') | |
}; | |
var interactivity_handler = function(location,is_tooltip){ | |
var features = map.queryRenderedFeatures(location.point, { | |
layers: ['guadec_icon'] | |
}); | |
tooltip.remove(); | |
if (features != ''){ | |
popup = null; | |
var feature = features[0]; | |
if (is_tooltip){ | |
tooltip.setHTML(`<span>${feature.properties.name}</span>`) | |
.setLngLat(location.lngLat) | |
.addTo(map); | |
} else { | |
popup = new mapboxgl.Popup({anchor:'bottom'}) | |
.setHTML(` | |
<h3>${feature.properties.name}</h3> | |
<ul> | |
${get_properties_list(feature.properties)} | |
</ul> | |
<p class="osm-source"><a href="https://www.openstreetmap.org/${feature.properties.type}/${feature.properties.id}">Source</a></p> | |
` | |
) | |
.setLngLat(location.lngLat) | |
.addTo(map); | |
} | |
} | |
} | |
/* Popup interactivity */ | |
map.on('click',function(location){ | |
interactivity_handler(location,false); | |
}); | |
/* Popup interactivity */ | |
map.on('mousemove',function(location){ | |
interactivity_handler(location,true); | |
}); | |
/* Filter control */ | |
document | |
.getElementById('filter-input') | |
.addEventListener('keyup', function(e) { | |
function normalize(string) { | |
return string.trim().toLowerCase(); | |
}; | |
// Get the value of the input | |
var value = normalize(e.target.value); | |
if (value == ""){ | |
// If it's empty remove the filter | |
map.setFilter('guadec_icon', null); | |
} else { | |
// Filter the geojson features and get their ids | |
ids = geojson_data.features | |
.filter( x => normalize( x['properties']['name']).match(new RegExp(value,"g")) != null) | |
.map(x => x['properties']['id']) | |
// Set the filter of the layer to match those ids | |
map.setFilter('guadec_icon', ['match', ['get', 'id'], ids, true, false]); | |
} | |
}); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment