npmap.js
Version:
A JavaScript web mapping library, built as a Leaflet plugin, for the National Park Service.
837 lines (698 loc) • 24.4 kB
JavaScript
/* global L, NPMap */
/* jshint camelcase: false */
'use strict';
var baselayerPresets = require('./preset/baselayers.json'),
colorPresets = require('./preset/colors.json'),
humane = require('humane-js'),
nanobar = require('nanobar'),
overlayPresets = require('./preset/overlays.json'),
util = require('./util/util');
require('./popup.js');
(function() {
var style = colorPresets.gold;
L.Circle.mergeOptions(style);
L.CircleMarker.mergeOptions(style);
L.Control.Attribution.mergeOptions({
prefix: '<a href="http://www.nps.gov/npmap/disclaimer.html" target="_blank">Disclaimer</a>'
});
L.Map.addInitHook(function() {
var me = this;
function resize() {
var container = me.getContainer(),
left = util.getOuterDimensions(util.getChildElementsByClassName(container, 'leaflet-control-container')[0].childNodes[2]).width;
if (left) {
left = left + 15;
} else {
left = 10;
}
util.getChildElementsByClassName(container, 'leaflet-control-attribution')[0].style['max-width'] = (util.getOuterDimensions(container).width - left) + 'px';
}
if (this.options.attributionControl) {
this.attributionControl._update = function() {
var attribs = [],
prefixAndAttribs = [];
for (var attribution in this._attributions) {
if (this._attributions[attribution] > 0) {
var i = -1;
if (attribution) {
for (var j = 0; j < attribs.length; j++) {
if (attribs[j] === attribution) {
i = j;
break;
}
}
if (i === -1) {
attribs.push(attribution);
}
}
}
}
if (this.options.prefix) {
prefixAndAttribs.push(this.options.prefix);
}
if (attribs.length) {
prefixAndAttribs.push(attribs.join(' | '));
}
this._container.innerHTML = prefixAndAttribs.join(' | ');
if (typeof me._updateImproveLinks === 'function') {
me._updateImproveLinks();
}
};
this.on('resize', resize);
resize();
}
if (typeof me._updateImproveLinks === 'function') {
me.on('moveend', me._updateImproveLinks);
}
});
L.Polygon.mergeOptions(style);
L.Polyline.mergeOptions({
color: style.color,
opacity: style.opacity,
weight: style.weight
});
})();
var Map = L.Map.extend({
initialize: function(config) {
var baseLayerSet = false,
container = L.DomUtil.create('div', 'npmap-container'),
map = L.DomUtil.create('div', 'npmap-map'),
mapWrapper = L.DomUtil.create('div', 'npmap-map-wrapper'),
me = this,
modules = L.DomUtil.create('div', 'npmap-modules'),
npmap = L.DomUtil.create('div', 'npmap' + ((L.Browser.ie6 || L.Browser.ie7) ? ' npmap-oldie' : '') + (L.Browser.retina ? ' npmap-retina' : '')),
toolbar = L.DomUtil.create('div', 'npmap-toolbar'),
toolbarLeft = L.DomUtil.create('ul', 'left'),
toolbarRight = L.DomUtil.create('ul', 'right'),
zoomifyMode = false;
config = me._toLeaflet(config);
config.div.insertBefore(npmap, config.div.hasChildNodes() ? config.div.childNodes[0] : null);
npmap.appendChild(modules);
npmap.appendChild(container);
toolbar.appendChild(toolbarLeft);
toolbar.appendChild(toolbarRight);
container.appendChild(toolbar);
container.appendChild(mapWrapper);
mapWrapper.appendChild(map);
config.div = map;
config.zoomControl = false;
L.Map.prototype.initialize.call(me, config.div, config);
me._addEvents(me, config);
me._controllingCursor = true;
me._controllingInteractivity = true;
me._defaultCursor = me.getContainer().style.cursor;
me.getContainer().tabIndex = 1;
me.on('autopanstart', function() {
me._setCursor('');
});
me.notify = humane.create({
baseCls: 'humane-bootstrap',
container: map,
});
me.notify.danger = me.notify.spawn({
addnCls: 'humane-bootstrap-danger'
});
me.notify.info = me.notify.spawn({
addnCls: 'humane-bootstrap-info'
});
me.notify.success = me.notify.spawn({
addnCls: 'humane-bootstrap-success'
});
me.notify.warning = me.notify.spawn({
addnCls: 'humane-bootstrap-warning'
});
me._progress = new nanobar({
bg: '#d29700',
id: 'npmap-progress',
target: map
});
if (!me._loaded) {
me.setView(config.center, config.zoom);
}
if (config.baseLayers.length) {
var zoomify = [],
baseLayer, i;
for (i = 0; i < config.baseLayers.length; i++) {
baseLayer = config.baseLayers[i];
if (baseLayer.type === 'zoomify') {
zoomify.push(baseLayer);
}
}
if (zoomify.length) {
zoomifyMode = true;
for (i = 0; i < zoomify.length; i++) {
baseLayer = zoomify[i];
if (baseLayer.visible || typeof baseLayer.visible === 'undefined') {
baseLayer.visible = true;
baseLayer.L = L.npmap.layer.zoomify(baseLayer);
me._addEvents(baseLayer.L, baseLayer);
baseLayer.L.addTo(me);
break;
}
}
} else {
for (i = 0; i < config.baseLayers.length; i++) {
baseLayer = config.baseLayers[i];
baseLayer.zIndex = 0;
if (!baseLayerSet && (baseLayer.visible || typeof baseLayer.visible === 'undefined')) {
baseLayer.visible = true;
baseLayerSet = true;
if (baseLayer.type === 'arcgisserver') {
baseLayer.L = L.npmap.layer[baseLayer.type][baseLayer.tiled === true ? 'tiled' : 'dynamic'](baseLayer);
} else {
baseLayer.L = L.npmap.layer[baseLayer.type](baseLayer);
}
me._addEvents(baseLayer.L, baseLayer);
me.addLayer(baseLayer.L);
} else {
baseLayer.visible = false;
}
}
}
}
if (!zoomifyMode && config.overlays.length) {
var zIndex = 1;
for (var j = 0; j < config.overlays.length; j++) {
var overlay = config.overlays[j];
if (overlay.type === 'zoomify') {
throw new Error('Zoomify layers can only be added in the "baseLayers" config property.');
} else {
if (overlay.visible || typeof overlay.visible === 'undefined') {
overlay.visible = true;
overlay.zIndex = zIndex;
if (overlay.type === 'arcgisserver') {
overlay.L = L.npmap.layer[overlay.type][overlay.tiled === true ? 'tiled' : 'dynamic'](overlay);
} else {
overlay.L = L.npmap.layer[overlay.type](overlay);
}
me._addEvents(overlay.L, overlay);
me.addLayer(overlay.L);
zIndex++;
} else {
overlay.visible = false;
}
}
}
}
me._initializeModules();
me._setupPopup();
me._setupTooltip();
return this;
},
_addEvents: function(obj, config) {
if (config.events && config.events.length) {
for (var i = 0; i < config.events.length; i++) {
var e = config.events[i],
context = e.context || null;
if (e.single === true) {
obj.once(e.type, e.fn, context);
} else {
obj.on(e.type, e.fn, context);
}
if (e.type === 'error' && obj.errorFired) {
obj.fire('error', obj.errorFired);
} else if (e.type === 'load' && obj._loaded) {
obj.fire('load');
} else if (e.type === 'ready' && obj.readyFired) {
obj.fire('ready');
}
}
}
},
_initializeModules: function() {
if (this.options && this.options.modules && L.Util.isArray(this.options.modules) && this.options.modules.length) {
var initialize = null,
me = this,
modules = this.options.modules,
width = 0,
button, i;
this._divWrapper = this._container.parentNode.parentNode;
this._divModules = util.getChildElementsByClassName(this._divWrapper.parentNode.parentNode, 'npmap-modules')[0];
this._divModuleButtons = L.DomUtil.create('div', 'npmap-modules-buttons', this._container.parentNode);
this._buttonCloseModules = L.DomUtil.create('button', 'npmap-modules-buttons-button', this._divModuleButtons);
this._buttonCloseModules.style['background-image'] = 'url(' + window.L.Icon.Default.imagePath + '/font-awesome/times' + (L.Browser.retina ? '@2x' : '') + '.png)';
this._buttonCloseModules.title = 'Close';
L.DomEvent.addListener(this._buttonCloseModules, 'click', me.closeModules, this);
for (i = 0; i < modules.length; i++) {
var div = L.DomUtil.create('div', 'module', this._divModules),
divTitle = L.DomUtil.create('h2', 'title', div),
divContent = L.DomUtil.create('div', 'content', div),
module = modules[i],
content, icon, title;
if (module.type !== 'custom') {
this.options.modules[i] = module = L.npmap.module[module.type](module).addTo(this);
}
content = module.content;
icon = module.icon;
title = divTitle.innerHTML = module.title;
if (typeof content === 'string') {
divContent.innerHTML = content;
} else if ('nodeType' in content) {
divContent.appendChild(content);
}
button = L.DomUtil.create('button', 'npmap-modules-buttons-button', this._divModuleButtons);
button.id = 'npmap-modules-buttons_' + title.replace(/ /g, '_');
button.title = title;
button.style['background-image'] = 'url(' + window.L.Icon.Default.imagePath + '/font-awesome/' + icon + (L.Browser.retina ? '@2x' : '') + '.png)';
div.id = 'npmap-module_' + title.replace(/ /g, '_');
if (typeof module.width === 'number') {
if (module.width > width) {
width = module.width;
}
}
L.DomEvent.addListener(button, 'click', function() {
me.showModule(this.id.replace('npmap-modules-buttons_', ''));
});
if (!initialize && module.visible === true) {
initialize = title;
}
}
if (width !== 0) {
this._divModules.style.width = width + 'px';
}
if (initialize) {
this.showModule(initialize);
} else {
for (i = 1; i < this._divModuleButtons.childNodes.length; i++) {
button = this._divModuleButtons.childNodes[i];
button.style.display = 'inline-block';
}
}
}
},
_setCursor: function(type) {
this._container.style.cursor = type;
},
_setupPopup: function() {
var clicks = 0,
me = this,
canceled, changed, hasArcGisServer;
function done() {
me
.off('click', setCanceled)
.off('dragstart', setChanged)
.off('movestart', setChanged)
.off('zoomstart', setChanged);
if (hasArcGisServer) {
me._progress.go(100);
me._setCursor('');
}
}
function go(e) {
var queryable = [],
layer;
canceled = false;
changed = false;
me
.on('click', setCanceled)
.on('dragstart', setChanged)
.on('movestart', setChanged)
.on('zoomstart', setChanged);
for (var layerId in me._layers) {
layer = me._layers[layerId];
if (typeof layer.options === 'object' && (typeof layer.options.popup === 'undefined' || layer.options.popup !== false) && typeof layer._handleClick === 'function' && layer._hasInteractivity !== false) {
queryable.push(layer);
}
}
if (queryable.length) {
var completed = 0,
intervals = 0,
latLng = e.latlng.wrap(),
results = [],
i, interval;
hasArcGisServer = false;
for (i = 0; i < queryable.length; i++) {
layer = queryable[i];
if (layer.options && layer.options.type === 'arcgisserver') {
hasArcGisServer = true;
}
layer._handleClick(latLng, function(result) {
if (result) {
results.push(result);
}
completed++;
});
}
if (hasArcGisServer) {
me._progress.go(1);
me._setCursor('wait');
}
interval = setInterval(function() {
intervals++;
if (hasArcGisServer) {
me._progress.go(intervals);
}
if (canceled || changed) {
clearInterval(interval);
done();
} else if ((queryable.length === completed) || intervals > 98) {
clearInterval(interval);
done();
if (intervals > 98) {
me.notify.danger('One or more servers failed to respond.');
}
if (results.length) {
var actual = [];
for (var i = 0; i < results.length; i++) {
var result = results[i];
if (typeof result.results !== 'undefined') {
if (result.results && result.results !== 'loading') {
actual.push(result);
}
} else {
actual.push(result);
}
}
if (actual.length) {
var popup = L.npmap.popup({
autoPanPaddingTopLeft: util._getAutoPanPaddingTopLeft(me.getContainer()),
maxHeight: util._getAvailableVerticalSpace(me) - 74
});
popup.setContent(popup._handleResults(actual, me.options.popup)).setLatLng(latLng).openOn(me);
}
}
}
}, 100);
}
}
function setCanceled() {
canceled = true;
}
function setChanged() {
changed = true;
}
me.on('dblclick', function() {
clicks++;
});
me.on('click', function(e) {
clicks = 0;
if (me._controllingInteractivity) {
setTimeout(function() {
if (!clicks) {
go(e);
}
}, 200);
}
});
},
_setupTooltip: function() {
var me = this,
overData = [],
tooltip = L.npmap.tooltip({
map: me
});
function handle() {
if (me._controllingCursor) {
updateCursor();
}
if (me._tooltips.length) {
var changed = false,
childNodes = tooltip._container.childNodes,
html = '',
i, obj;
if (childNodes.length) {
var remove = [];
for (i = 0; i < childNodes.length; i++) {
var childNode = childNodes[i],
removeNode = true;
for (var j = 0; j < me._tooltips.length; j++) {
obj = me._tooltips[j];
// Also do comparison of html to see.
if (obj.added && (obj.layerId === parseInt(childNode.id.replace('tooltip-', ''), 10))) {
removeNode = false;
break;
}
}
if (removeNode) {
remove.push(childNode);
}
}
if (remove.length) {
for (i = 0; i < remove.length; i++) {
var div = remove[i];
div.parentNode.removeChild(div);
}
changed = true;
}
html = tooltip.getHtml();
}
for (i = 0; i < me._tooltips.length; i++) {
obj = me._tooltips[i];
if (!obj.added) {
changed = true;
html += '<div id="tooltip-' + obj.layerId + '">' + util.unescapeHtml(obj.html) + '</div>';
obj.added = true;
}
}
if (tooltip.isVisible()) {
if (changed) {
tooltip.setHtml(html);
}
tooltip.setPosition(me._cursorEvent.containerPoint);
} else {
tooltip.show(me._cursorEvent.containerPoint, html);
}
} else {
tooltip.hide();
tooltip.setHtml('');
}
}
function removeOverData(layerId) {
var remove = [],
i;
for (i = 0; i < overData.length; i++) {
if (overData[i] === layerId) {
remove.push(layerId);
}
}
if (remove.length) {
for (i = 0; i < remove.length; i++) {
overData.splice(overData.indexOf(remove[i]), 1);
}
}
}
function removeTooltip(layerId) {
var remove = [],
i;
for (i = 0; i < me._tooltips.length; i++) {
var obj = me._tooltips[i];
if (obj.layerId === layerId) {
remove.push(obj);
}
}
if (remove.length) {
for (i = 0; i < remove.length; i++) {
me._tooltips.splice(me._tooltips.indexOf(remove[i]), 1);
}
}
}
function updateCursor() {
if (overData.length) {
me._setCursor('pointer');
} else {
if (me.getContainer().style.cursor !== 'wait') {
me._setCursor('');
}
}
}
me._tooltips = [];
L.DomEvent.on(util.getChildElementsByClassName(me.getContainer(), 'leaflet-popup-pane')[0], 'mousemove', function(e) {
L.DomEvent.stopPropagation(e);
tooltip.hide();
});
me.on('mousemove', function(e) {
me._cursorEvent = e;
if (me._controllingCursor) {
handle();
for (var layerId in me._layers) {
var layer = me._layers[layerId];
if (typeof layer._handleMousemove === 'function' && layer._hasInteractivity !== false) {
layer._handleMousemove(me._cursorEvent.latlng.wrap(), function(result) {
if (result.results !== 'loading') {
var l = result.layer,
leafletId = l._leaflet_id;
removeOverData(leafletId);
removeTooltip(leafletId);
if (result.results) {
overData.push(leafletId);
if (l.options && l.options.tooltip) {
for (var i = 0; i < result.results.length; i++) {
var data = result.results[i],
tip;
if (typeof l.options.tooltip === 'function') {
tip = util.handlebars(l.options.tooltip(data));
} else if (typeof l.options.tooltip === 'string') {
tip = util.unescapeHtml(util.handlebars(l.options.tooltip, data));
}
if (tip && me._tooltips.indexOf(tip) === -1) {
me._tooltips.push({
html: tip,
layerId: leafletId
});
}
}
}
}
handle();
}
});
}
}
}
});
me.on('mouseout', function() {
tooltip.hide();
});
},
_toLeaflet: function(config) {
if (!config.div) {
throw new Error('The map config object must have a div property');
} else if (typeof config.div !== 'string' && typeof config.div !== 'object') {
throw new Error('The map config object must be either a string or object');
}
if (config.baseLayers === false || (L.Util.isArray(config.baseLayers) && !config.baseLayers.length)) {
config.baseLayers = [];
} else {
config.baseLayers = (function() {
var visible = false;
if (config.baseLayers && L.Util.isArray(config.baseLayers) && config.baseLayers.length) {
for (var i = 0; i < config.baseLayers.length; i++) {
var baseLayer = config.baseLayers[i];
if (typeof baseLayer === 'string') {
var name = baseLayer.split('-');
if (name[1]) {
baseLayer = baselayerPresets[name[0]][name[1]];
} else {
baseLayer = baselayerPresets[name];
}
}
if (baseLayer.visible === true || typeof baseLayer.visible === 'undefined') {
if (visible) {
baseLayer.visible = false;
} else {
baseLayer.visible = true;
visible = true;
}
} else {
baseLayer.visible = false;
}
baseLayer.zIndex = 0;
config.baseLayers[i] = baseLayer;
}
}
if (visible) {
return config.baseLayers;
} else {
var active = baselayerPresets.nps.parkTiles;
active.visible = true;
active.zIndex = 0;
return [
active
];
}
})();
}
config.center = (function() {
var c = config.center;
if (c) {
return new L.LatLng(c.lat, c.lng);
} else {
return new L.LatLng(39, -96);
}
})();
if (typeof config.div === 'string') {
config.div = document.getElementById(config.div);
}
if (config.layers && L.Util.isArray(config.layers) && config.layers.length) {
config.overlays = config.layers;
for (var j = 0; j < config.overlays.length; j++) {
var overlay = config.overlays[j];
if (typeof overlay === 'string') {
overlay = config.overlays[j] = overlayPresets[overlay];
}
}
} else if (!config.overlays || !L.Util.isArray(config.overlays)) {
config.overlays = [];
}
if (typeof config.maxZoom !== 'number') {
config.maxZoom = 19;
}
delete config.layers;
config.zoom = typeof config.zoom === 'number' ? config.zoom : 4;
return config;
},
_updateImproveLinks: function() {
if (this.attributionControl) {
var els = util.getChildElementsByClassName(this.attributionControl._container, 'improve-park-tiles');
if (els && els.length) {
var center = this.getCenter();
for (var i = 0; i < els.length; i++) {
var el = els[0];
el.href = el.href.split('#')[0] + '#' + this.getZoom() + '/' + center.lat.toFixed(5) + '/' + center.lng.toFixed(5);
}
}
}
},
closeModules: function() {
var buttons = this._divModuleButtons.childNodes;
this._buttonCloseModules.style.display = 'none';
this._divWrapper.style.left = '0';
this._divModules.style.display = 'none';
for (var i = 1; i < buttons.length; i++) {
var button = buttons[i];
L.DomUtil.removeClass(button, 'active');
button.style.display = 'inline-block';
}
this.invalidateSize();
},
showModule: function(title) {
var divModules = this._divModules,
childNodes = divModules.childNodes,
modules = this.options.modules,
i;
title = title.replace(/_/g, ' ');
for (i = 0; i < modules.length; i++) {
var module = modules[i],
visibility = 'none';
if (module.title === title) {
visibility = 'block';
}
module.visible = (visibility === 'block');
childNodes[i].style.display = visibility;
}
divModules.style.display = 'block';
this._divWrapper.style.left = util.getOuterDimensions(divModules).width + 'px';
this.invalidateSize();
for (i = 0; i < this._divModuleButtons.childNodes.length; i++) {
var button = this._divModuleButtons.childNodes[i];
if (i === 0) {
button.style.display = 'inline-block';
} else {
if (modules.length > 1) {
button.style.display = 'inline-block';
} else {
button.style.display = 'none';
}
}
if (button.id.replace('npmap-modules-buttons_', '').replace(/_/g, ' ') === title) {
L.DomUtil.addClass(button, 'active');
} else {
L.DomUtil.removeClass(button, 'active');
}
}
// TODO: Fire module 'show' event.
},
showModules: function() {
var buttons = this._divModuleButtons.childNodes;
this._buttonCloseModules.style.display = 'inline-block';
this._divModules.style.display = 'block';
this._divWrapper.style.left = util.getOuterDimensions(this._divModules).width + 'px';
for (var i = 1; i < buttons.length; i++) {
buttons[i].style.display = 'inline-block';
}
this.invalidateSize();
}
});
module.exports = function(config) {
return new Map(config);
};