UNPKG

leaflet-canvaslayer-field

Version:

A set of layers using canvas to draw ASCIIGrid or GeoTIFF files. This includes a basic raster layer (*ScalaField*) and an animated layer for vector fields, such as wind or currents (*VectorFieldAnim*)

215 lines (188 loc) 6.67 kB
import Field from './Field'; /** * Scalar Field */ export default class ScalarField extends Field { /** * Creates a ScalarField from the content of an ASCIIGrid file * @param {String} asc * @returns {ScalarField} */ static fromASCIIGrid(asc, scaleFactor = 1) { //console.time('ScalarField from ASC'); let lines = asc.split('\n'); // Header var header = ScalarField._parseASCIIGridHeader(lines.slice(0, 6)); // Data (left-right and top-down) let zs = []; for (let i = 6; i < lines.length; i++) { let line = lines[i].trim(); if (line === '') break; let items = line.split(' '); items.forEach(it => { let floatItem = parseFloat(it); let v = floatItem !== header.noDataValue ? floatItem * scaleFactor : null; zs.push(v); }); } let p = header; p.zs = zs; //console.timeEnd('ScalarField from ASC'); return new ScalarField(p); } /** * Parse an ASCII Grid header, made with 6 lines * It allows the use of XLLCORNER/YLLCORNER or XLLCENTER/YLLCENTER conventions * @param {Array.String} headerLines */ static _parseASCIIGridHeader(headerLines) { try { const headerItems = headerLines.map(line => { var items = line.split(' ').filter(i => i != ''); var param = items[0].trim().toUpperCase(); var value = parseFloat(items[1].trim()); return { [param]: value }; }); const usesCorner = 'XLLCORNER' in headerItems[2]; const cellSize = headerItems[4]['CELLSIZE']; const header = { nCols: parseInt(headerItems[0]['NCOLS']), nRows: parseInt(headerItems[1]['NROWS']), xllCorner: usesCorner ? headerItems[2]['XLLCORNER'] : headerItems[2]['XLLCENTER'] - cellSize, yllCorner: usesCorner ? headerItems[3]['YLLCORNER'] : headerItems[3]['YLLCENTER'] - cellSize, cellXSize: cellSize, cellYSize: cellSize, noDataValue: headerItems[5]['NODATA_VALUE'] }; return header; } catch (err) { throw new Error(`Not a valid ASCIIGrid Header: ${err}`); } } /** * Creates a ScalarField from the content of a GeoTIFF file * @param {ArrayBuffer} data * @param {Number} bandIndex * @returns {ScalarField} */ static fromGeoTIFF(data, bandIndex = 0) { return ScalarField.multipleFromGeoTIFF(data, [bandIndex])[0]; } /** * Creates a ScalarField array (one per band) from the content of a GeoTIFF file * @param {ArrayBuffer} data * @param {Array} bandIndexes - if not provided all bands are returned * @returns {Array.<ScalarField>} */ static multipleFromGeoTIFF(data, bandIndexes) { //console.time('ScalarField from GeoTIFF'); let tiff = GeoTIFF.parse(data); // geotiff.js let image = tiff.getImage(); let rasters = image.readRasters(); let tiepoint = image.getTiePoints()[0]; let fileDirectory = image.getFileDirectory(); let [xScale, yScale] = fileDirectory.ModelPixelScale; if (typeof bandIndexes === 'undefined' || bandIndexes.length === 0) { bandIndexes = [...Array(rasters.length).keys()]; } let scalarFields = []; scalarFields = bandIndexes.map(function(bandIndex) { let zs = rasters[bandIndex]; // left-right and top-down order if (fileDirectory.GDAL_NODATA) { let noData = parseFloat(fileDirectory.GDAL_NODATA); // console.log(noData); let simpleZS = Array.from(zs); // to simple array, so null is allowed | TODO efficiency?? zs = simpleZS.map(function(z) { return z === noData ? null : z; }); } let p = { nCols: image.getWidth(), nRows: image.getHeight(), xllCorner: tiepoint.x, yllCorner: tiepoint.y - image.getHeight() * yScale, cellXSize: xScale, cellYSize: yScale, zs: zs }; return new ScalarField(p); }); //console.timeEnd('ScalarField from GeoTIFF'); return scalarFields; } constructor(params) { super(params); this.zs = params['zs']; this.grid = this._buildGrid(); this._updateRange(); //console.log(`ScalarField created (${this.nCols} x ${this.nRows})`); } /** * Builds a grid with a Number at each point, from an array * 'zs' following x-ascending & y-descending order * (same as in ASCIIGrid) * @private * @returns {Array.<Array.<Number>>} - grid[row][column]--> Number */ _buildGrid() { let grid = this._arrayTo2d(this.zs, this.nRows, this.nCols); return grid; } _arrayTo2d(array, nRows, nCols) { let grid = []; let p = 0; for (var j = 0; j < nRows; j++) { var row = []; for (var i = 0; i < nCols; i++, p++) { let z = array[p]; row[i] = this._isValid(z) ? z : null; // <<< } grid[j] = row; } return grid; } _newDataArrays(params) { params['zs'] = []; } _pushValueToArrays(params, value) { params['zs'].push(value); } _makeNewFrom(params) { return new ScalarField(params); } /** * Calculate min & max values * @private * @returns {Array} - [min, max] */ _calculateRange() { var data = this.zs; if (this._inFilter) { data = data.filter(this._inFilter); } return [d3.min(data), d3.max(data)]; } /** * Bilinear interpolation for Number * https://en.wikipedia.org/wiki/Bilinear_interpolation * @param {Number} x * @param {Number} y * @param {Number} g00 * @param {Number} g10 * @param {Number} g01 * @param {Number} g11 * @returns {Number} */ _doInterpolation(x, y, g00, g10, g01, g11) { var rx = 1 - x; var ry = 1 - y; return g00 * rx * ry + g10 * x * ry + g01 * rx * y + g11 * x * y; } }