/* * Options: * el - id of the map element * title - template to update the page title on move * bbox - fallback bbox viewport */ function theCragMap(options){ this.params = { // How close clusters will be in px, smaller is more clusters maxClusterRadius: 20, // How visually wide an area is before we load it's child data loadFov: 100, // How visually wide an area is before we look into it's children for more labels. labelFov: 200 }; var that = this; this.options = options; this.debug = !!options.debug; this.map = L.map(options.el, { zoomSnap: L.Browser.mobile ? 0 : 1, inertiaDeceleration: 1000, thecragmap: this, zoomControl: false, gestureHandling: this.options.embed }); this.styles = { fence: { base: { weight: 1, color: '#fff', opacity: .5, fillColor: '#fff', fillOpacity: .1, className: ' tcm-f-base' // Keep space }, region: { weight: 1, color: '#fff0', fillOpacity: 0, className: ' tcm-f-region' // Keep space }, closed: { weight: 2, opacity: 1, color: '#f00', fillColor: '#f00', fillOpacity: .1, className: ' tcm-f-closed' // Keep space }, here: { weight: 1, opacity: 1, fillOpacity: 0 }, inside: { weight: 1, opacity: 1, fillOpacity: 0 }, hover: { color: '#08c', opacity: 1, fillColor: '#def', fillOpacity: .2, weight: 2 }, selected: { color: '#08c', opacity: 1, fillColor: '#def', fillOpacity: .2, weight: 2 } } }; this.panes = { labels: this.map.createPane('labelPane'), locate: this.map.createPane('locatePane') }; // Default marker is 600, tooltips are 650 this.map.getPane('labelPane' ).style.zIndex = 640; this.map.getPane('locatePane').style.zIndex = 650; this.layerGroups = { bbox: L.layerGroup(),//.addTo(this.map); number: L.layerGroup(),//.addTo(this.map); probe: L.layerGroup(),//.addTo(this.map); label: L.layerGroup({pane: 'labelPane'}).addTo(this.map), fence: L.layerGroup().addTo(this.map), pie: L.markerClusterGroup({ showCoverageOnHover: false, maxClusterRadius: this.params.maxClusterRadius, singleMarkerMode: true, zoomToBoundsOnClick: false, iconCreateFunction: this.renderGearPieFromMarkers, chunkedLoading: true, chunkInterval: 50 }).addTo(this.map), locate: L.layerGroup({pane: 'locatePane'}).addTo(this.map) }; this.newPies = []; // Temp queue for pies so we can batch load // All map controls // this.controls = {}; L.control.zoom({ position: 'bottomright', zoomInTitle: thecrag.getText('template.maps.zoomin'), zoomOutTitle: thecrag.getText('template.maps.zoomout') }).addTo(this.map); this.initProviders(); this.autoLayer = null; this.selectedLayer = 'auto'; // This is either auto or an explicity chosen layer this.controls.layers = L.control.layers({}, {}, { collapsed: this.options.embed ? 1 : (L.Browser.mobile ? 1 : 0), position: 'topleft' }).addTo(this.map); this.controls.layers.addOverlay(this.layerGroups.fence, thecrag.getTextUC('object.boundary.many')); this.controls.layers.addOverlay(this.layerGroups.pie, thecrag.getTextUC('object.route.many')); this.controls.layers.addOverlay(this.layerGroups.label, thecrag.getTextUC('object.label.many')); if (this.debug) { this.controls.layers.addOverlay(this.layerGroups.bbox, "nbbox"); this.controls.layers.addOverlay(this.layerGroups.number, "number"); this.controls.layers.addOverlay(this.layerGroups.probe, "probe"); } L.control.scale().addTo(this.map); if (document.fullscreenEnabled || document.webkitFullscreenEnabled) { this.map.addControl(new L.Control.Fullscreen({ position: 'bottomright', title: { 'false': thecrag.getText('template.maps.fullscreen'), 'true': thecrag.getText('template.maps.exitfullscreen') } })); } this.controls.locate = L.control.locate({ icon: 'icon-location-arrow', position: 'bottomright', setView: 'untilPan', layer: this.layerGroups.locate, circlePadding: [50,50], cacheLocation: true, returnToPrevBounds: false, compassStyle: { weight: 1, radius: 7 }, strings: { title: thecrag.getText('template.maps.your-location') }, locateOptions: { enableHighAccuracy: true, maxZoom: 20 } }).addTo(this.map); // this.controls.locate.start(); this.map.on('moveend', function(e) { this.loadMap(); var that = this; window.setTimeout(function() { that.updateLayerControl(); }, 20); }, this); if (this.options.embed) { L.Control.LinkControl = L.Control.extend({ options: { position: 'topright', label: 'button', href: '#' }, onAdd: function (map) { var container = L.DomUtil.create('div', 'leaflet-bar leaflet-control leaflet-control-editnode'); this.link = L.DomUtil.create('a', 'leaflet-control-edit-button leaflet-bar-part', container); this.link.href = this.options.href; this.link.style.padding = "0 8px"; this.link.style.width = "auto"; this.link.innerHTML = this.options.label; return container; } }); this.controls.edit = new L.Control.LinkControl({ href: this.options.edit, label: thecrag.getText('template.maps.edit-location') }).addTo(this.map); } // Set viewport this.initView(); this.map.on('moveend zoomend', function(e) { this.options.thecragmap.updateURL(); }); this.map.on('baselayerchange', function (e) { if (!this.autoLayerSwapping) { this.selectedLayer = e.layer.options.id; } this.updateURL(); }, this); this.layerGroups.pie.on({ 'clustermouseover': function (e) { var nodes = that.getNodesFromCluster(e.layer); $('body').trigger('node.over', {id: nodes[0].id, nodes: nodes }); }, 'clustermouseout': function (e) { var nodes = that.getNodesFromCluster(e.layer); $('body').trigger('node.out', {id: nodes[0].id, nodes: nodes }); }, 'clusterclick': function (e) { var nodes = that.getNodesFromCluster(e.layer); $('body').trigger('node.focus', {id: nodes[0].id, nodes: nodes }); } }); $('body').bind('node.over', function(e,data){ that.nodeOver(data.id, data.nodes); }); $('body').bind('node.out', function(e,data){ that.nodeOut(data.id, data.nodes); }); $('body').bind('node.focus', function(e,data){ var node = that.index[data.id]; var nodes = data.nodes || [node] that.popupForNodes(nodes); }); $('body').bind('node.select', function(e,data){ var node = that.index[data.id]; if (!node) return; that.nodeSelect(data.id); }); $('body').bind('node.deselect', function(e,data){ var node = that.index[data.id]; if (!node) return; that.nodeDeselect(data.id); }); } theCragMap.prototype = { tree: {}, // In memory data store index: {}, // nid index to nodes in the tree // Layer in-memory store: store: { bbox: {}, // debug number: {}, // debug probes: null, // debug pie: {}, fence: {}, labels: {} }, node2url: function(node) { var url = node.stub ? ('/climbing/'+node.stub) : ('/'+node.id); return url; }, /* * Given either 'auto' or a string like 'satellite.sixmaps' */ selectBaseLayer: function(name) { // var parts = name.split('.'); var type = parts[0]; var provider = parts[1]; if(this.layers[provider] && this.layers[provider][type]) { this.layers[provider][type].layer.addTo(this.map); this.selectedLayer = type + '.'+provider; } else { // Might not have decided the baselayer yet if (this.autoLayer) { this.autoLayer.layer.addTo(this.map); } this.selectedLayer = 'auto'; } }, /* * Given a node update the crumb trail * TODO should actually replace the top nav menu too via common.js */ updateCrumbFrom: function (node) { if (this.options.embed) return; var crumbs = []; var last = node; while(node) { crumbs.unshift({ nid: node.id, href: this.node2url(node), label: node.name }); node = node.up; } crumbs[0].label = null; crumbs[0].icon = 'globe'; crumbs.push({ href: this.node2url(last) + '/maps' + location.hash, label: this.options.title }); // TODO fix loading race condition (move to prod ready js file will do this for free) if (thecrag.crumbs) { thecrag.crumbs.replace(crumbs); } }, /** * Show a popup for a node, analagous to a crag card */ popupForNodes: function(nodes) { var node = nodes[0]; var $html = $('
'+(node.numberRoutes ||0)+' routes
').appendTo($stats); } $(''+(node.numberAscents||0)+' ascents
').appendTo($stats); if (node.children && node.children.length && node.res.numberRoutes > 0) { $(''+(node.res.numberRoutes ||0)+' unlocated routes
').appendTo($stats); } $stats.appendTo($info); if (node.rt) { $('').appendTo($info); } else { $('').appendTo($info); } $info.appendTo($html); // Other neaby nodes //html.+ ' + ' +(nodes.length-1) + ' more'; var popup = L.popup({'className' : 'tcm-popup'}) .setLatLng(this.index[node.id].rcenter) .setContent($html[0].outerHTML); if (this.controls.locate.stopFollowing) { this.controls.locate.stopFollowing(); } this.map.openPopup(popup); }, bubbleSize: function(routes) { return Math.round(10 + Math.log(routes+1)*3); }, renderGearPieFromMarkers: function(cluster) { var markers = cluster.getAllChildMarkers(); var routes = 0; var styles = {}; for (var i=0; i