/* * Map panel that displays BBCode. See show() method and options. * Localization is in 'strings/*.js' files. */ window.MapBBCode = L.Class.extend({ options: { createLayers: null, // function(L) { return [L.tileLayer(...), ...]; }, layers: null, // array of strings, if LayerList.js included maxInitialZoom: 15, defaultPosition: [22, 11], defaultZoom: 2, leafletOptions: {}, polygonOpacity: 0.1, editorHeight: 400, // here and below 0 for 100% viewWidth: 600, viewHeight: 300, fullViewHeight: 600, fullScreenButton: true, fullFromStart: false, windowWidth: 800, windowHeight: 500, windowFeatures: 'resizable,status,dialog', windowPath: 'lib/mapbbcode-window.html', editorCloseButtons: true, confirmFormSubmit: true, outerLinkTemplate: false, // 'http://openstreetmap.org/#map={zoom}/{lat}/{lon}', helpButton: true, allowedHTML: '[auib]|span|br|em|strong|tt', letterIconLength: 2, popupIconLength: 30, enablePolygons: true, preferStandardLayerSwitcher: true, hideInsideClasses: [], watchResize: false, panelHook: null, // function({map, getBBCode(), ...}) externalEndpoint: 'http://share.mapbbcode.org/', exportTypes: 'csv,geojson,gpx,plt,wpt,kml', uploadButton: false }, strings: {}, initialize: function( options ) { L.setOptions(this, options); if( L.Browser.ie && options && options.defaultPosition && 'splice' in options.defaultPosition && options.defaultPosition.length == 2 ) this.options.defaultPosition = [options.defaultPosition[0], options.defaultPosition[1]]; // in IE arrays can be [object Object] and break L.latLon() }, setStrings: function( strings ) { this.strings = L.extend({}, this.strings, strings); }, _eachHandler: function( callback, context, layer ) { var handlers = window.mapBBCodeHandlers; if( handlers ) { for( var i = 0; i < handlers.length; i++ ) { if( !layer || ('applicableTo' in handlers[i] && handlers[i].applicableTo(layer)) ) { callback.call(context || this, handlers[i]); } } } }, objectToLayer: function( obj ) { var m; if( obj.coords.length == 1 ) { m = L.marker(obj.coords[0]); } else if( obj.coords.length > 2 && obj.coords[0].equals(obj.coords[obj.coords.length-1]) ) { obj.coords.splice(obj.coords.length - 1, 1); m = L.polygon(obj.coords, { weight: 3, opacity: 0.7, fill: true, fillOpacity: this.options.polygonOpacity }); } else { m = L.polyline(obj.coords, { weight: 5, opacity: 0.7 }); } this._eachHandler(function(handler) { if( 'objectToLayer' in handler ) { var p = []; if( 'reKeys' in handler ) for( var j = 0; j < obj.params.length; j++ ) if( handler.reKeys.test(obj.params[j]) ) p.push(obj.params[j]); handler.objectToLayer(m, handler.text ? obj.text : p, this); } }, this, m); m._objParams = obj.params; return m; }, _zoomToLayer: function( map, layer, stored, initial ) { var bounds = layer.getBounds(); if( !bounds || !bounds.isValid() ) { if( stored && stored.zoom ) map.setView(stored.pos || this.options.defaultPosition, stored.zoom); else if( initial ) map.setView(this.options.defaultPosition, this.options.defaultZoom); return; } var applyZoom = function() { if( stored && stored.pos ) { map.setView(stored.pos, stored.zoom || this.options.maxInitialZoom); } else { var maxZoom = Math.max(this.options.maxInitialZoom, initial ? 0 : map.getZoom()); map.fitBounds(bounds, { animate: false, paddingTopLeft: [30, 30], paddingBottomRight: [30, 5] }); if( stored && stored.zoom ) map.setZoom(stored.zoom, { animate: false }); else if( map.getZoom() > maxZoom ) map.setZoom(maxZoom, { animate: false }); } }; var boundsZoom = map.getBoundsZoom(bounds, false); if( boundsZoom ) applyZoom.call(this); else map.on('load', applyZoom, this); }, createOpenStreetMapLayer: function(L1) { var LL = L1 || L; return LL.tileLayer('http://tile.openstreetmap.org/{z}/{x}/{y}.png', { name: 'OpenStreetMap', attribution: 'Map © OpenStreetMap', minZoom: 2, maxZoom: 18 }); }, _addLayers: function( map ) { var layers = this.options.createLayers ? this.options.createLayers.call(this, L) : null; if( (!layers || !layers.length) && window.layerList && this.options.layers ) layers = window.layerList.getLeafletLayers(this.options.layers, L); if( !layers || !layers.length ) layers = [this.createOpenStreetMapLayer(L)]; map.addLayer(layers[0]); if( layers.length > 1 ) { var control, i; if( !this.options.preferStandardLayerSwitcher && L.StaticLayerSwitcher ) { control = L.staticLayerSwitcher(null, { enforceOSM: true }); for( i = 0; i < layers.length; i++ ) if( layers[i] && 'options' in layers[i] ) control.addLayer(layers[i].options.name, layers[i]); } else { control = L.control.layers(); for( i = 0; i < layers.length; i++ ) if( layers[i] && 'options' in layers[i] ) control.addBaseLayer(layers[i], layers[i].options.name); } map.addControl(control); } }, _hideClassPresent: function( element ) { if( typeof element.className !== 'string' ) return false; var classNames = element.className.split(' '), classHide = this.options.hideInsideClasses, i, j; if( !classHide || !classHide.length ) return false; for( i = 0; i < classNames.length; i++ ) for( j = 0; j < classHide.length; j++ ) if( classNames[i] === classHide[j] ) return true; return element.parentNode && this._hideClassPresent(element.parentNode); }, _checkResize: function( map, drawn ) { var size = new L.Point(map.getContainer().clientWidth, map.getContainer().clientHeight); if( !('_oldSize' in map) ) map._oldSize = size; if( size.x && size.y ) { var diff = size.subtract(map._oldSize); if( diff.x || diff.y ) { map._oldSize = size; map._sizeChanged = true; // fix my own leaflet bug, to remove for leaflet 0.7.2 this._zoomToLayer(map, drawn); } if( !this.options.watchResize && map._bbSizePinger ) window.clearInterval(map._bbSizePinger); } }, _createControlAndCallHooks: function( mapDiv, map, drawn, extra ) { var control = { _ui: this, map: map, close: function() { this.map = this._ui = null; mapDiv.close(); }, eachLayer: function(callback, context) { drawn.eachLayer(function(layer) { callback.call(context || this, layer); }, this); }, zoomToData: function() { this._ui._zoomToLayer(map, drawn); } }; control = L.extend(control, extra); this._eachHandler(function(handler) { if( 'panelHook' in handler ) handler.panelHook(control, this); }); if( this.options.panelHook ) this.options.panelHook.call(this, control); return control; }, _px: function( size ) { return size ? size + 'px' : '100%'; }, _createMapPanel: function( element, iseditor ) { var el = typeof element === 'string' ? document.getElementById(element) : element; if( !el ) return; var bbcode = el.getAttribute('bbcode') || el.getAttribute('value') || el.innerHTML.replace(/^\s+|\s+$/g, ''); var closeTag = window.MapBBCodeProcessor.getCloseTag(); if( (bbcode && bbcode.toLowerCase().indexOf(closeTag) < 0) || (!bbcode && el.getAttribute('map')) ) { var pos = el.getAttribute('map'), openTag = window.MapBBCodeProcessor.getOpenTagWithPart(pos); bbcode = openTag + bbcode + closeTag; } while( el.firstChild ) el.removeChild(el.firstChild); if( !iseditor && this._hideClassPresent(el) ) return; var mapDiv = document.createElement('div'); mapDiv.style.width = iseditor ? '100%' : this.options.fullFromStart ? '100%' : this._px(this.options.viewWidth); mapDiv.style.height = iseditor ? this._px(this.options.editorHeight) : this.options.fullFromStart ? this._px(this.options.fullViewHeight) : this._px(this.options.viewHeight); el.appendChild(mapDiv); mapDiv.storedBBCode = bbcode; mapDiv.close = function() { el.removeChild(mapDiv); }; return mapDiv; }, // Create map panel, parse and display bbcode (it can be skipped: so it's an attribute or contents of element) show: function( element, bbcode ) { var mapDiv = this._createMapPanel(element); if( !mapDiv ) return; if( !bbcode ) bbcode = mapDiv.storedBBCode; if( !bbcode || typeof bbcode !== 'string' ) bbcode = ''; var map = L.map(mapDiv, L.extend({}, { scrollWheelZoom: false, zoomControl: false, attributionEditLink: true }, this.options.leafletOptions)); map.once('focus', function() { map.scrollWheelZoom.enable(); }); map.addControl(new L.Control.Zoom({ zoomInTitle: this.strings.zoomInTitle, zoomOutTitle: this.strings.zoomOutTitle })); if( map.attributionControl ) map.attributionControl.setPrefix('MapBBCode'); this._addLayers(map); var drawn = new L.FeatureGroup(); drawn.addTo(map); var data = window.MapBBCodeProcessor.stringToObjects(bbcode), objs = data.objs; for( var i = 0; i < objs.length; i++ ) this.objectToLayer(objs[i]).addTo(drawn); this._zoomToLayer(map, drawn, { zoom: data.zoom, pos: data.pos }, true); if( !mapDiv.offsetHeight || this.options.watchResize ) map._bbSizePinger = window.setInterval(L.bind(this._checkResize, this, map, drawn), 500); if( this.options.fullScreenButton && !this.options.fullFromStart ) { var fs = L.functionButtons([{ content: window.MapBBCode.buttonsImage, bgPos: [0, 0], alt: '↘', title: this.strings.fullScreenTitle }], { position: 'topright' }), isFull = false, oldSize; map.addControl(fs); fs.on('clicked', function() { var style = map.getContainer().style; if( !isFull && !oldSize ) oldSize = [style.width, style.height]; isFull = !isFull; style.width = isFull ? '100%' : oldSize[0]; style.height = isFull ? this._px(this.options.fullViewHeight) : oldSize[1]; map.invalidateSize(); fs.setBgPos([isFull ? 26 : 0, 0]); var dZoom = isFull ? 1 : -1; map.setZoom(map.getZoom() + dZoom, { animate: false }); }, this); } if( this.options.outerLinkTemplate && this.options.outerLinkTemplate.substring(0, 4) == 'http' ) { var outer = L.functionButtons([{ content: window.MapBBCode.buttonsImage, bgPos: [52, 0], alt: '↷', title: this.strings.outerTitle, href: 'about:blank' }], { position: 'topright' }); var template = this.options.outerLinkTemplate; var updateOuterLink = function() { outer.setHref(template .replace('{zoom}', map.getZoom()) .replace('{lat}', L.Util.formatNum(map.getCenter().lat, 4)) .replace('{lon}', L.Util.formatNum(map.getCenter().lng, 4)) ); }; updateOuterLink(); map.on('move', updateOuterLink); map.addControl(outer); } return this._createControlAndCallHooks(mapDiv, map, drawn, { editor: false, getBBCode: function() { return bbcode; }, updateBBCode: function( bbcode1, noZoom ) { bbcode = bbcode1; var data = window.MapBBCodeProcessor.stringToObjects(bbcode), objs = data.objs; drawn.clearLayers(); for( var i = 0; i < objs.length; i++ ) this._ui.objectToLayer(objs[i]).addTo(drawn); if( !noZoom ) this._ui._zoomToLayer(map, drawn, { zoom: data.zoom, pos: data.pos }, true); }, toggleObjects: function( newState ) { if( newState === undefined ) newState = !map.hasLayer(drawn); if( newState ) map.addLayer(drawn); else map.removeLayer(drawn); return newState; } }); } });