UNPKG

gis-api

Version:

GIS API for DHIS 2 based on Leaflet

304 lines (243 loc) 9.07 kB
// Leaflet plugin to add map layers from Google Earth Engine import L from 'leaflet'; import 'script-loader!../lib/ee_api_js_debug'; // LayerGroup is used as a Google Earth Engine visualization can consists of more than one tilelayer export const EarthEngine = L.LayerGroup.extend({ options: { url: 'https://earthengine.googleapis.com/map/{mapid}/{z}/{x}/{y}?token={token}', tokenType: 'Bearer', aggregation: 'none', popup: '{name}: {value} {unit}', }, initialize(options = {}) { L.setOptions(this, options); this._layers = {}; this._legend = options.legend || this.createLegend(); }, onAdd() { this.getAuthToken(this.onValidAuthToken.bind(this)); }, onRemove(map) { if (this._popup) { map.closePopup(this._popup); } L.GeoJSON.prototype.onRemove.call(this, map); }, // Get OAuth2 token needed to create and load Google Earth Engine layers getAuthToken(callback) { const accessToken = this.options.accessToken; if (accessToken) { if (accessToken instanceof Function) { // Callback function returning auth obect accessToken(callback); } else { // Auth token as object callback(accessToken); } } }, // Configures client-side authentication of EE API calls by providing a OAuth2 token to use. onValidAuthToken(token) { ee.data.setAuthToken(token.client_id, this.options.tokenType, token.access_token, token.expires_in, null, null, false); ee.data.setAuthTokenRefresher(this.refreshAccessToken.bind(this)); ee.initialize(); this.createImage(); this.fire('initialized'); }, // Refresh OAuth2 token when expired refreshAccessToken(authArgs, callback) { const self = this; this.getAuthToken(token => { callback({ token_type: self.options.tokenType, access_token: token.access_token, state: authArgs.scope, expires_in: token.expires_in, }); }); }, // Create EE tile layer from params (override for each layer type) createImage() { const options = this.options; let eeCollection; let eeImage; if (options.filter) { // Image collection eeCollection = ee.ImageCollection(options.id); // eslint-disable-line eeCollection = this.applyFilter(eeCollection); if (options.aggregation === 'mosaic') { this.eeCollection = eeCollection; eeImage = eeCollection.mosaic(); } else { eeImage = ee.Image(eeCollection.first()); // eslint-disable-line } } else { // Single image eeImage = ee.Image(options.id); // eslint-disable-line } if (options.band) { eeImage = eeImage.select(options.band); } if (options.mask) { // Mask out 0-values eeImage = eeImage.updateMask(eeImage.gt(0)); } // Run methods on image eeImage = this.runMethods(eeImage); this.eeImage = eeImage; // Classify image if (!options.legend) { // Don't classify if legend is provided eeImage = this.classifyImage(eeImage); } this.addLayer(this.visualize(eeImage)); }, // Add EE image to map as TileLayer addLayer(eeImage) { const eeMap = eeImage.getMap(); const layer = L.tileLayer(this.options.url, L.extend({ token: eeMap.token, mapid: eeMap.mapid, }, this.options)); L.LayerGroup.prototype.addLayer.call(this, layer); }, applyFilter(collection, filterOpt) { const filter = filterOpt || this.options.filter; if (filter) { for (const item of filter) { collection = collection.filter(ee.Filter[item.type].apply(this, item.arguments)); // eslint-disable-line } } return collection; }, // Run methods on image // https://code.earthengine.google.com/a19f5cec73720aba049b457d55672cee // https://code.earthengine.google.com/37e4e9cc4436a22e5c3e0f63acb4c0bc runMethods(image) { const methods = this.options.methods; let eeImage = image; if (methods) { Object.keys(methods).forEach(method => { if (eeImage[method]) { // Make sure method exist eeImage = eeImage[method].apply(eeImage, methods[method]); } }); } return eeImage; }, // Classify image according to legend classifyImage(eeImage) { const legend = this._legend; let zones; for (let i = 0, item; i < legend.length - 1; i++) { item = legend[i]; if (!zones) { zones = eeImage.gt(item.to); } else { zones = zones.add(eeImage.gt(item.to)); } } return zones; }, // Visualize image (turn into RGB) visualize(eeImage) { const options = this.options; return eeImage.visualize(options.legend ? options.params : { min: 0, max: this._legend.length - 1, palette: options.params.palette, }); }, createLegend() { const params = this.options.params; const min = params.min; const max = params.max; const palette = params.palette.split(','); const step = (params.max - min) / (palette.length - (min > 0 ? 2 : 1)); let from = min; let to = Math.round(min + step); return palette.map((color, index) => { const item = { color: color, }; if (index === 0 && min > 0) { // Less than min item.from = 0; item.to = min; item.name = '< ' + item.to; to = min; } else if (from < max) { item.from = from; item.to = to; item.name = item.from + ' - ' + item.to; } else { // Higher than max item.from = from; item.name = '> ' + item.from; } from = to; to = Math.round(min + (step * (index + (min > 0 ? 1 : 2)))); return item; }); }, // Returns a HTML legend for this EE layer getLegend() { const options = this.options; let legend = '<div class="dhis2-legend">'; legend += '<h2>' + options.name; if (options.image) { legend += ' ' + options.image; } legend += '</h2>'; if (options.description) { legend += '<p>' + options.description + '</p>'; } legend += '<dl>'; if (options.unit) { legend += '<dt></dt><dd><strong>' + options.unit + '</strong></dd>'; } for (let i = 0, item; i < this._legend.length; i++) { item = this._legend[i]; legend += '<dt style="background-color:' + item.color + ';box-shadow:1px 1px 2px #aaa;"></dt>'; legend += '<dd>' + item.name; } legend += '</dl>'; if (options.attribution) { legend += '<p>Data: ' + options.attribution + '</p>'; } legend += '<div>'; return legend; }, setOpacity(opacity) { this.options.opacity = opacity; this.eachLayer(layer => layer.setOpacity(opacity)); }, // Returns value at location in a callback getValue(latlng, callback) { const point = ee.Geometry.Point(latlng.lng, latlng.lat); // eslint-disable-line const options = this.options; let dictionary; if (options.aggregation === 'mosaic') { dictionary = this.eeImage.reduceRegion(ee.Reducer.mean(), point, options.resolution, options.projection); } else { dictionary = this.eeImage.reduceRegion(ee.Reducer.mean(), point); } dictionary.getInfo(valueObj => { const band = options.band || Object.keys(valueObj)[0]; let value = valueObj[band]; if (options.legend && options.legend[value]) { value = options.legend[value].name; } else if (options.value) { // Needs calculation value = options.value(value); } callback(value); }); }, // Shows the value at location (popup) showValue(latlng) { const options = this.options; this.getValue(latlng, value => { this._popup = L.popup() .setLatLng(latlng) .setContent(L.Util.template(options.popup, L.extend({}, options, { value: value, }))) .openOn(this._map); }); }, }); export default function earthEngine(options) { return new EarthEngine(options); }