open_trails
Version:
package for visualizing trail data from OpenStreetMap
365 lines (300 loc) • 11.3 kB
JavaScript
/*
SETUP MAP
~~~~~~~~~~~~~~~~~~~~~~~~
Initialize the map, load tiles and define some styles
*/
//limit view to North America for which there is trail data available
var map = L.map('map',{minZoom:5, maxBounds:[[1.209763, -177.780297],[82.916148, -23.092799]]})
.setView([39.994116, -105.303509], 13);
var tooltip = $('#tooltip')
var infoPanel = $("#infoPanel")
var dataExtent
// create the tile layer
L.tileLayer('https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token={accessToken}', {
attribution: 'Map data © <a href="http://openstreetmap.org">OpenStreetMap</a>, <a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, Imagery © <a href="http://mapbox.com">Mapbox</a>',
maxZoom: 18,
id:'mapbox.run-bike-hike',
accessToken: 'YOUR ACCESS TOKEN'
}).addTo(map);
var visibleStyle = {
"weight": 1.5,
"opacity": 1.0,
"clickable": false
};
var clicksStyle = {
"weight": 10,
"opacity": 0
};
var highlightStyle = {
"opacity": 1
};
function highlightFeature(e) {
geojson.setStyle(e.target);
}
function resetFeatureStyle(e) {
resetStyle(e.target);
}
/*
EVENT HANDLERS
~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
$('#zoom-msg').popover({
content: 'Zoom in to view trail data',
trigger: 'manual',
template: '<div class="popover" role="tooltip"><h3 class="popover-title"></h3><div class="popover-content"></div></div>'
});
var searchErrorPopup = L.popup().setContent('<p>Trail data is not available for that location.</p>');
$(function() {
$('.toggle-info').click(function() {
toggleInfoPanel();
});
});
function toggleInfoPanel() {
if ($('#infoPanel').hasClass('show-infoPanel')) {
$('#infoPanel').removeClass('show-infoPanel');
} else {
$('#infoPanel').addClass('show-infoPanel');
}
}
$(function () {
$('[data-toggle="tooltip"]').tooltip()
})
map.on('moveend', function(e) {
//$('#search_bar').popover('hide');
var newbnds = map.getBounds();
var current_zoom = map.getZoom();
if (current_zoom == 12) {
$('#zoom-msg').popover('show');
$('.leaflet-overlay-pane').hide();
}
else if (current_zoom == 13) {
$('#zoom-msg').popover('hide');
$('.leaflet-overlay-pane').show();
}
if (!dataExtent.contains(newbnds) & current_zoom>12) {
map.removeLayer(geoJson_clicks);
map.removeLayer(geoJson_visible);
load_paths();
dataExtent = newbnds.pad(0.3);
}
});
/*
MAP FUNCTIONS
~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
function onEachFeature_clicks(feature, layer) {
switch (feature.properties.tags.highway) {
case 'cycleway': layer.setStyle({className: "cycleway"}); break;
case 'track': layer.setStyle({className: "track"}); break;
case 'path': layer.setStyle({className: "path"}); break;
}
layer.setStyle(clicksStyle);
layer.on('mouseover',function(e){
tooltip.toggleClass("hidden")
.css({top: event.pageY, left: event.pageX})
.find('#value').text((feature.properties.tags.name) ? feature.properties.tags.name:'\u2014');
});
layer.on('mouseout',function(e){
tooltip.toggleClass("hidden");
});
layer.on('click',function(e){
getElevations(feature);
infoPanel.addClass('show-infoPanel');
infoPanel.find('#trailname').text((feature.properties.tags.name) ? feature.properties.tags.name:'\u2014')
});
}
function onEachFeature_visible(feature, layer) {
switch (feature.properties.tags.highway) {
case 'cycleway': layer.setStyle({className: "cycleway"}); break;
case 'track': layer.setStyle({className: "track"}); break;
case 'path': layer.setStyle({className: "path"}); break;
}
layer.setStyle(visibleStyle);
}
function getElevations(feature) {
var LatLng_array = [];
feature.geometry.coordinates.forEach( function(s) {
LatLng_array.push(new google.maps.LatLng(s[1],s[0]) );
});
var elevator = new google.maps.ElevationService;
var len = LatLng_array.length
var block_size = 300;
if (len > block_size) { //break up request because of Google Elevation limit of 512 points per query
var blocks = Math.floor(len/block_size);
var result_array = [];
var last_block = [];
var len_cnt = 0;
for (var bl = 0; bl <= blocks; bl++) {
var locations = LatLng_array.slice(bl*block_size, (bl+1)*block_size<len ? (bl+1)*block_size : len);
elevator.getElevationForLocations({
'locations': locations
}, function(results, status) {
if (status === google.maps.ElevationStatus.OK) {
//catch the last, short block which returns early so we can append the blocks in the right order
if (results.length == block_size) {
result_array = result_array.concat(results)
len_cnt += results.length
} else {
last_block = last_block.concat(results)
len_cnt += results.length
}
if (len_cnt == len) {
result_array = result_array.concat(last_block)
feature.geometry.coordinates.forEach( function(s,i,a) {
s.push(result_array[i].elevation);
});
addData(feature);
}
} else {
console.log('Elevation service failed due to: ' + status);
}
});
}
} else {
elevator.getElevationForLocations({
'locations': LatLng_array
}, function(results, status) {
if (status === google.maps.ElevationStatus.OK) {
feature.geometry.coordinates.forEach( function(s,i,a) {
s.push(results[i].elevation);
});
addData(feature);
} else {
console.log('Elevation service failed due to: ' + status);
}
});
};
}
function addData(e) {
el.clear();
el.addData(e);
infoPanel.find('#ele_max').text(el.MaxElevation);
infoPanel.find('#ele_min').text(el.MinElevation);
infoPanel.find('#ele_gain').text(el.up);
infoPanel.find('#ele_loss').text(el.down);
infoPanel.find('#distance').text(el.TotalDist);
}
function load_paths() {
bnds = map.getBounds();
dataExtent = bnds.pad(0.3);
bnds_str = dataExtent.getSouth()+','+dataExtent.getWest()+','+dataExtent.getNorth()+','+dataExtent.getEast()
baseurl = "http://ec2-54-85-8-224.compute-1.amazonaws.com/api/interpreter?";
query = 'data=[out:json][timeout:25];(way["highway"]('+bnds_str+'););out body;>;out skel qt;';
$(".load-icon").show();
$.getJSON(baseurl+query, function(data) {
$(".load-icon").hide();
paths = osmtogeojson(data);
geoJson_clicks = L.geoJson(paths,{onEachFeature: onEachFeature_clicks}).addTo(map);
geoJson_visible = L.geoJson(paths,{onEachFeature: onEachFeature_visible}).addTo(map);
});
}
function formatJSON(rawjson){
var json = {},
key, loc, disp = [];
for (var i in rawjson) {
key = rawjson[i].formatted_address;
loc = L.latLng( rawjson[i].geometry.location.lat(), rawjson[i].geometry.location.lng() );
json[ key ]= loc;
}
return json;
}
/*
MAP CONTROLS
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
//Add elevation profile to the #infoPanel
var el = L.control.elevation({
position: "topright",
theme: "steelblue-theme",
width: 300,
height: 100,
margins: {
top: 30,
right: 20,
bottom: 30,
left: 50
},
useHeightIndicator: true, //if false a marker is drawn at map position
interpolation: "linear", //see https://github.com/mbostock/d3/wiki/SVG-Shapes#wiki-area_interpolate
hoverNumber: {
decimalsX: 1, //decimals on distance (always in km)
decimalsY: 0, //deciamls on height (always in m)
formatter: undefined //custom formatter function may be injected
},
xTicks: undefined, //number of ticks in x axis, calculated by default according to width
yTicks: undefined, //number of ticks on y axis, calculated by default according to height
collapsed: false //collapsed mode, show chart on click or mouseover
});
el.addTo(map)
el._container.remove();
document.getElementById('ele_profile').appendChild(el.onAdd(map));
//Add search bar with geocoding provided by google.maps.Geocoder()
//Uses leaflet-search plugin
var geocoder = new google.maps.Geocoder();
function googleGeocoding(text, callResponse){
geocoder.geocode({address: text}, callResponse);
}
var searchOpts = {
sourceData: googleGeocoding,
formatData: formatJSON,
container: 'search_box', //place in the #search_box div
textPlaceholder: 'Search ...',
markerLocation: false,
animateLocation: false,
circleLocation: false,
autoType: false,
collapsed: false,
autoCollapse: true,
minLength: 2,
zoom: 13
};
var searchControl = new L.Control.Search(searchOpts).addTo(map);
searchControl.on('search_locationfound',function(e) {
if (!map.getBounds().contains(e.latlng)) {
searchErrorPopup.setLatLng(map.getCenter());
map.openPopup(searchErrorPopup);
}
});
//Add geo locator button
//Uses leaflet.locatecontrol plugin
L.control.locate({
position: 'topleft', // set the location of the control
layer: undefined, // use your own layer for the location marker, creates a new layer by default
drawCircle: true, // controls whether a circle is drawn that shows the uncertainty about the location
follow: false, // follow the user's location
setView: true, // automatically sets the map view to the user's location, enabled if `follow` is true
keepCurrentZoomLevel: false, // keep the current map zoom level when displaying the user's location. (if `false`, use maxZoom)
stopFollowingOnDrag: false, // stop following when the map is dragged if `follow` is true (deprecated, see below)
remainActive: false, // if true locate control remains active on click even if the user's location is in view.
markerClass: L.circleMarker, // L.circleMarker or L.marker
circleStyle: {}, // change the style of the circle around the user's location
markerStyle: {},
followCircleStyle: {}, // set difference for the style of the circle around the user's location while following
followMarkerStyle: {},
icon: 'fa fa-map-marker', // class for icon, fa-location-arrow or fa-map-marker
iconLoading: 'fa fa-spinner fa-spin', // class for loading icon
iconElementTag: 'span', // tag for the icon element, span or i
circlePadding: [0, 0], // padding around accuracy circle, value is passed to setBounds
metric: false, // use metric or imperial units
onLocationError: function(err) {alert(err.message)}, // define an error callback function
onLocationOutsideMapBounds: function(context) { // called when outside map boundaries
alert(context.options.strings.outsideMapBoundsMsg);
},
showPopup: true, // display a popup when the user click on the inner marker
strings: {
title: "Show me where I am", // title of the locate control
metersUnit: "meters", // string for metric units
feetUnit: "feet", // string for imperial units
popup: "You are within {distance} {unit} from this point", // text to appear if user clicks on circle
outsideMapBoundsMsg: "You seem located outside the boundaries of the map" // default message for onLocationOutsideMapBounds
},
locateOptions: {maxZoom: 13} // define location options e.g enableHighAccuracy: true or maxZoom: 10
}).addTo(map);
//Add a legend to the map
//uses leaflet.legend plugin
var Legend = new L.Control.Legend();
map.addControl( Legend );
$(".legend-container").append( $("#legend") );
$(".legend-toggle").append( "<i class='legend-toggle-icon fa fa-info fa-2x' style='color: #000'></i>" );
//Load trail data onto the map
load_paths()