gis-tools-ts
Version:
A collection of geospatial tools primarily designed for WGS84, Web Mercator, and S2.
118 lines • 5.31 kB
JavaScript
import { pointDistance as distance } from '../../geometry/s2/point';
import { averageInterpolation, defaultGetInterpolateCurrentValue } from '.';
import { lRGBToGamma, sRGBToLinear } from '../..';
/**
* Sometimes you're given a large swathe of points, and so this function helps find the closest 4
* "corners" relative to a pont
* @param point - point to interpolate
* @param refData - reference data to interpolate from
* @returns - the corner points closest to the reference point
*/
export function getBilinearPoints(point, refData) {
const { x: refX, y: refY } = point;
// 1) reduce to four corner points
const topLeft = refData
.filter((p) => p.x <= refX && p.y > refY)
.reduce((a, b) => (distance(a, point) < distance(b, point) ? a : b));
const topRight = refData
.filter((p) => p.x > refX && p.y > refY)
.reduce((a, b) => (distance(a, point) < distance(b, point) ? a : b));
const bottomLeft = refData
.filter((p) => p.x <= refX && p.y <= refY)
.reduce((a, b) => (distance(a, point) < distance(b, point) ? a : b));
const bottomRight = refData
.filter((p) => p.x > refX && p.y <= refY)
.reduce((a, b) => (distance(a, point) < distance(b, point) ? a : b));
// Check if any of the corners are undefined
if (topLeft === undefined ||
topRight === undefined ||
bottomLeft === undefined ||
bottomRight === undefined) {
throw new Error('Insufficient data to determine all four bilinear corner points.');
}
return [topLeft, topRight, bottomLeft, bottomRight];
}
/**
* # Bilinear Interpolation
*
* ## Description
* Given a reference of data, interpolate a point using bilinear interpolation
*
* ## Usage
* ```ts
* import { bilinearInterpolation, getBilinearPoints, PointIndexFast } from 'gis-tools-ts';
* import type { VectorPoint } from 'gis-tools-ts';
*
* // We have m-value data that we want to interpolate
* interface TempData { temp: number; }
*
* const pointIndex = new PointIndexFast<TempData>();
* // add lots of points
* pointIndex.insertLonLat(lon, lat, data);
* // ....
*
* // given a point we are interested in
* const point: VectorPoint = { x: 20, y: -40 };
* // get a collection of points relative to the point
* const data = await pointIndex.searchRadius(point.x, point.y, radius);
*
* // interpolate
* const interpolatedValue = bilinearInterpolation<TempData>(point, data, (p) => p.m.temp);
*
* // if you reuse the same data, you can pass in the corners for performance gains
* const corners = getBilinearPoints(point, data);
* const interpolatedValue = bilinearInterpolation<TempData>(point, data, (p) => p.m.temp, corners);
* ```
* @param point - point to interpolate
* @param refData - reference data to interpolate from
* @param getValue - function to get value from reference data. Can be the z value or a property in the m-values
* defaults to function that returns the z value or 0 if the z value is undefined
* @param corners - the 4 corner points relative to the reference point. Add this if you don't want to keep
* recomputing the corners on the same data.
* @returns - the interpolated value
*/
export function bilinearInterpolation(point, refData, getValue = defaultGetInterpolateCurrentValue, corners) {
if (refData.length === 0)
return 0;
// 1) Extract corner points and values
const [tl, tr, bl, br] = corners ?? getBilinearPoints(point, refData);
const { x: px, y: py } = point;
const { x: x1, y: y1 } = tl;
const { x: x2, y: y2 } = br;
const q11 = getValue(tl);
const q21 = getValue(tr);
const q12 = getValue(bl);
const q22 = getValue(br);
// 2) Calculate the weights for bilinear interpolation
const weight11 = ((x2 - px) * (y2 - py)) / ((x2 - x1) * (y2 - y1));
const weight21 = ((px - x1) * (y2 - py)) / ((x2 - x1) * (y2 - y1));
const weight12 = ((x2 - px) * (py - y1)) / ((x2 - x1) * (y2 - y1));
const weight22 = ((px - x1) * (py - y1)) / ((x2 - x1) * (y2 - y1));
// 3) Compute the weighted average of the values
const interpolatedValue = weight11 * q11 + weight21 * q21 + weight12 * q12 + weight22 * q22;
return interpolatedValue;
}
/**
* Helper function for {@link bilinearInterpolation} on RGB(A) data.
* Light in RGB data is logarithmically weighted (gamma corrected), so we need to expand each component by n^2 to
* get the correct weight for each component.
* @param point - Point to interpolate
* @param refData - Reference data points
* @returns - The interpolated RGBA data.
*/
export function rgbaBilinearInterpolation(point, refData) {
if (refData.length === 0)
return { r: 0, g: 0, b: 0, a: 255 };
const corners = getBilinearPoints(point, refData);
const rData = bilinearInterpolation(point, refData, (p) => sRGBToLinear(p.m?.r ?? 0), corners);
const gData = bilinearInterpolation(point, refData, (p) => sRGBToLinear(p.m?.g ?? 0), corners);
const bData = bilinearInterpolation(point, refData, (p) => sRGBToLinear(p.m?.b ?? 0), corners);
const a = averageInterpolation(point, refData, (p) => p.m?.a ?? 255);
return {
r: lRGBToGamma(rData),
g: lRGBToGamma(gData),
b: lRGBToGamma(bData),
a,
};
}
//# sourceMappingURL=bilinear.js.map