import DataSet from "./DataSet.js"
import { bboxMetricSize } from './gis.js'
class GeoDataSet extends DataSet {
/**
* Mostly the same a DataSet except it has bounds.
* A few methods, like slope, dzdx, dzdy, and aspect are different because they take into account the size of the bbox in meters
*
* @param {Number} width width of the DataSet in pixels
* @param {Number} height height of the DataSet in pixels
* @param {Array} bbox [west, south, east, north]
* @param {TypedArray} data
*/
constructor(width, height, bbox, data) {
super(width, height, data)
this.bbox = bbox
}
/**
* Create a view of a Dataset including the bounds.
*
* @static
* @param {DataSet} dataSet
* @param {Array} bbox [west, south, east, north]
* @returns {GeoDataSet} GeoDataSet view of the dataset data. It is a view not a copy.
*/
static viewFromDataSet(dataSet, bbox) {
return new GeoDataSet(dataSet.width, dataSet.height, bbox, dataSet.data)
}
lat2y(lat) {
const [west, south, east, north] = this.bbox
const y = Math.round(this.height * (lat - south) / (north - south))
return y
}
lon2x(lng) {
const [west, south, east, north] = this.bbox
const x = Math.round(this.width * (lng - west) / (east - west))
return x
}
// Convert from geoon/lat coords to pixel coords
toPixel(geoX, geoY) {
return [this.lon2x(geoX), this.lat2y(geoY)]
}
// Get pixel from geo lon/lat coords to pixel coords
getGeo(geoX, geoY) {
const [x, y] = this.toPixel(geoX, geoY)
return this.getXY(x, y)
}
// Set pixel from geo lon/lat coords to pixel coords
// I think this could be a slow way to set data for large amounts
setGeo(geoX, geoY, value) {
const [x, y] = this.toPixel(geoX, geoY)
return this.setXY(x, y, value)
}
/**
* Samples a pixel at a given latitude and longitude
*
* @param {Number} geoX longitude
* @param {Number} geoY latitude
* @param {Boolean} useNearest
* @throws Out Of Range Error - when it is outside of the bbox
* @returns {Number}
*/
sampleGeo(geoX, geoY, useNearest = true) {
const [x, y] = this.toPixel(geoX, geoY)
return this.sample(x, y, useNearest)
}
/**
* Change in z value in terms of x. Finite difference.
*
* @returns {GeoDataset}
*/
dzdx() {
const [widthMeters, heightMeters] = bboxMetricSize(this.bbox)
const pixelScale = widthMeters / this.width
const dzdx = super.dzdx(2, (1 / 8) * (1 / pixelScale)) // (1/8) for the kernel and 1/pixelscale to get units right
const dzdx2 = GeoDataSet.viewFromDataSet(dzdx, this.bbox)
return dzdx2
}
/**
* Change in z value in terms of y. Finite difference.
*
* @returns {GeoDataSet}
*/
dzdy() {
const [widthMeters, heightMeters] = bboxMetricSize(this.bbox)
const pixelScale = heightMeters / this.height
const dzdy = super.dzdy(2, (1 / 8) * (1 / pixelScale))
const dzdy2 = GeoDataSet.viewFromDataSet(dzdy, this.bbox)
return dzdy2
}
/**
* Create dzdx, dzdy, slope, and aspect in one function.
*
* @returns {SlopeAndAspect} {dzdx, dzdy, slope, aspect}
*/
slopeAndAspect() {
const dzdx = this.dzdx()
const dzdy = this.dzdy()
const slope = this.slope(dzdx, dzdy)
const aspect = this.aspect(dzdx, dzdy)
return { dzdx, dzdy, slope, aspect }
}
/**
* The aspect of each pixel in radians.
*
* @param {GeoDataset} dzdx
* @param {GeoDataSet} dzdy
* @returns {GeoDataSet} Aspect in radians.
*/
aspect(dzdx = this.dzdx(), dzdy = this.dzdy()) {
const asp = dzdx.map((x, i) => {
const y = dzdy.data[i]
const a = Math.atan2(-y, -x)
return a
})
return asp
}
/**
* Returns the slope in radians
*
* @param {GeoDataset} dzdx
* @param {GeoDataset} dzdy
* @returns {GeoDataset}
*/
slope(dzdx = this.dzdx(), dzdy = this.dzdy()) {
const slop = dzdx.map((x, i) => {
const y = dzdy.data[i]
const a = Math.hypot(-x, -y)
const sl = (Math.PI / 2) - Math.atan2(1, a)
return sl
})
return slop
}
//
// The functions below are the same as DataSet's version except they return a GeoDataset instead of a dataset.
// Is there a better way to do this?
//
//
clone() {
return new GeoDataSet(this.width, this.height, this.bbox, this.data)
}
resample(width, height, useNearest = true, Type = Array) {
const a = super.resample(width, height, useNearest, Type)
const b = GeoDataSet.viewFromDataSet(a, this.bbox)
return b
}
convolve(kernel, factor = 1, crop = false) {
const a = super.convolve(kernel, factor, crop)
const b = GeoDataSet.viewFromDataSet(a, this.bbox)
return b
}
normalize(lo = 0, hi = 1, round = false) {
const a = super.normalize(lo, hi, round)
const b = GeoDataSet.viewFromDataSet(a, this.bbox)
return b
}
map(f) {
const a = super.map(f)
const b = GeoDataSet.viewFromDataSet(a, this.bbox)
return b
}
}
export default GeoDataSet