@spalger/kibana
Version:
Kibana is an open source (Apache Licensed), browser based analytics and search dashboard for Elasticsearch. Kibana is a snap to setup and start using. Kibana strives to be easy to get started with, while also being flexible and powerful, just like Elastic
303 lines (256 loc) • 9.28 kB
JavaScript
define(function (require) {
return function MapFactory(Private) {
var _ = require('lodash');
var $ = require('jquery');
var L = require('leaflet');
var defaultMapZoom = 2;
var defaultMapCenter = [15, 5];
var defaultMarkerType = 'Scaled Circle Markers';
var mapTiles = {
url: 'https://otile{s}-s.mqcdn.com/tiles/1.0.0/map/{z}/{x}/{y}.jpeg',
options: {
attribution: 'Tiles by <a href="http://www.mapquest.com/">MapQuest</a> — ' +
'Map data © <a href="http://openstreetmap.org">OpenStreetMap</a> contributors, ' +
'<a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>',
subdomains: '1234'
}
};
var markerTypes = {
'Scaled Circle Markers': Private(require('ui/vislib/visualizations/marker_types/scaled_circles')),
'Shaded Circle Markers': Private(require('ui/vislib/visualizations/marker_types/shaded_circles')),
'Shaded Geohash Grid': Private(require('ui/vislib/visualizations/marker_types/geohash_grid')),
'Heatmap': Private(require('ui/vislib/visualizations/marker_types/heatmap')),
};
/**
* Tile Map Maps
*
* @class Map
* @constructor
* @param container {HTML Element} Element to render map into
* @param chartData {Object} Elasticsearch query results for this map
* @param params {Object} Parameters used to build a map
*/
function TileMapMap(container, chartData, params) {
this._container = $(container).get(0);
this._chartData = chartData;
// keep a reference to all of the optional params
this._events = _.get(params, 'events');
this._markerType = markerTypes[params.markerType] ? params.markerType : defaultMarkerType;
this._valueFormatter = params.valueFormatter || _.identity;
this._tooltipFormatter = params.tooltipFormatter || _.identity;
this._geoJson = _.get(this._chartData, 'geoJson');
this._attr = params.attr || {};
var mapOptions = {
minZoom: 1,
maxZoom: 18,
noWrap: true,
maxBounds: L.latLngBounds([-90, -220], [90, 220]),
scrollWheelZoom: false,
fadeAnimation: false,
};
this._createMap(mapOptions);
}
TileMapMap.prototype.addBoundingControl = function () {
if (this._boundingControl) return;
var self = this;
var drawOptions = { draw: {} };
_.each(['polyline', 'polygon', 'circle', 'marker', 'rectangle'], function (drawShape) {
if (self._events && !self._events.listenerCount(drawShape)) {
drawOptions.draw[drawShape] = false;
} else {
drawOptions.draw[drawShape] = {
shapeOptions: {
stroke: false,
color: '#000'
}
};
}
});
this._boundingControl = new L.Control.Draw(drawOptions);
this.map.addControl(this._boundingControl);
};
TileMapMap.prototype.addFitControl = function () {
if (this._fitControl) return;
var self = this;
var fitContainer = L.DomUtil.create('div', 'leaflet-control leaflet-bar leaflet-control-fit');
// Add button to fit container to points
var FitControl = L.Control.extend({
options: {
position: 'topleft'
},
onAdd: function (map) {
$(fitContainer).html('<a class="fa fa-crop" href="#" title="Fit Data Bounds"></a>')
.on('click', function (e) {
e.preventDefault();
self._fitBounds();
});
return fitContainer;
},
onRemove: function (map) {
$(fitContainer).off('click');
}
});
this._fitControl = new FitControl();
this.map.addControl(this._fitControl);
};
/**
* Adds label div to each map when data is split
*
* @method addTitle
* @param mapLabel {String}
* @return {undefined}
*/
TileMapMap.prototype.addTitle = function (mapLabel) {
if (this._label) return;
var label = this._label = L.control();
label.onAdd = function () {
this._div = L.DomUtil.create('div', 'tilemap-info tilemap-label');
this.update();
return this._div;
};
label.update = function () {
this._div.innerHTML = '<h2>' + _.escape(mapLabel) + '</h2>';
};
// label.addTo(this.map);
this.map.addControl(label);
};
/**
* remove css class for desat filters on map tiles
*
* @method saturateTiles
* @return undefined
*/
TileMapMap.prototype.saturateTiles = function () {
if (!this._attr.isDesaturated) {
$('img.leaflet-tile-loaded').addClass('filters-off');
}
};
TileMapMap.prototype.updateSize = function () {
this.map.invalidateSize({
debounceMoveend: true
});
};
TileMapMap.prototype.destroy = function () {
if (this._label) this._label.removeFrom(this.map);
if (this._fitControl) this._fitControl.removeFrom(this.map);
if (this._boundingControl) this._boundingControl.removeFrom(this.map);
if (this._markers) this._markers.destroy();
this.map.remove();
this.map = undefined;
};
/**
* Switch type of data overlay for map:
* creates featurelayer from mapData (geoJson)
*
* @method _addMarkers
*/
TileMapMap.prototype._addMarkers = function () {
if (!this._geoJson) return;
if (this._markers) this._markers.destroy();
this._markers = this._createMarkers({
tooltipFormatter: this._tooltipFormatter,
valueFormatter: this._valueFormatter,
attr: this._attr
});
if (this._geoJson.features.length > 1) {
this._markers.addLegend();
}
};
/**
* Create the marker instance using the given options
*
* @method _createMarkers
* @param options {Object} options to give to marker class
* @return {Object} marker layer
*/
TileMapMap.prototype._createMarkers = function (options) {
var MarkerType = markerTypes[this._markerType];
return new MarkerType(this.map, this._geoJson, options);
};
TileMapMap.prototype._attachEvents = function () {
var self = this;
var saturateTiles = self.saturateTiles.bind(self);
this._tileLayer.on('tileload', saturateTiles);
this.map.on('unload', function () {
self._tileLayer.off('tileload', saturateTiles);
});
this.map.on('moveend', function setZoomCenter(ev) {
// update internal center and zoom references
self._mapCenter = self.map.getCenter();
self._mapZoom = self.map.getZoom();
self._addMarkers();
if (!self._events) return;
self._events.emit('mapMoveEnd', {
chart: self._chartData,
map: self.map,
center: self._mapCenter,
zoom: self._mapZoom,
});
});
this.map.on('draw:created', function (e) {
var drawType = e.layerType;
if (!self._events || !self._events.listenerCount(drawType)) return;
// TODO: Different drawTypes need differ info. Need a switch on the object creation
var bounds = e.layer.getBounds();
self._events.emit(drawType, {
e: e,
chart: self._chartData,
bounds: {
top_left: {
lat: bounds.getNorthWest().lat,
lon: bounds.getNorthWest().lng
},
bottom_right: {
lat: bounds.getSouthEast().lat,
lon: bounds.getSouthEast().lng
}
}
});
});
this.map.on('zoomend', function () {
self._mapZoom = self.map.getZoom();
if (!self._events) return;
self._events.emit('mapZoomEnd', {
chart: self._chartData,
map: self.map,
zoom: self._mapZoom,
});
});
};
TileMapMap.prototype._createMap = function (mapOptions) {
if (this.map) this.destroy();
// get center and zoom from mapdata, or use defaults
this._mapCenter = _.get(this._geoJson, 'properties.center') || defaultMapCenter;
this._mapZoom = _.get(this._geoJson, 'properties.zoom') || defaultMapZoom;
// add map tiles layer, using the mapTiles object settings
this._tileLayer = L.tileLayer(mapTiles.url, mapTiles.options);
// append tile layers, center and zoom to the map options
mapOptions.layers = this._tileLayer;
mapOptions.center = this._mapCenter;
mapOptions.zoom = this._mapZoom;
this.map = L.map(this._container, mapOptions);
this._attachEvents();
this._addMarkers();
};
/**
* zoom map to fit all features in featureLayer
*
* @method _fitBounds
* @param map {Leaflet Object}
* @return {boolean}
*/
TileMapMap.prototype._fitBounds = function () {
this.map.fitBounds(this._getDataRectangles());
};
/**
* Get the Rectangles representing the geohash grid
*
* @return {LatLngRectangles[]}
*/
TileMapMap.prototype._getDataRectangles = function () {
if (!this._geoJson) return [];
return _.pluck(this._geoJson.features, 'properties.rectangle');
};
return Map;
};
});