1227 lines
33 KiB
JavaScript
1227 lines
33 KiB
JavaScript
import "./chunk-UX4SNNYH.js";
|
||
import "./chunk-JV6MJGV5.js";
|
||
import "./chunk-NNDEYVSB.js";
|
||
import "./chunk-F3ARDFTC.js";
|
||
import "./chunk-KFWBGRAN.js";
|
||
import "./chunk-4V7XJAUG.js";
|
||
import "./chunk-MY3Y56DQ.js";
|
||
import "./chunk-PZZAKLYX.js";
|
||
import "./chunk-L47B4DRW.js";
|
||
import "./chunk-FPVJKBJO.js";
|
||
import "./chunk-QL7JR4NF.js";
|
||
import "./chunk-SQ4UGRSZ.js";
|
||
import "./chunk-4YO3ZZ7L.js";
|
||
import "./chunk-TPN5ONKP.js";
|
||
import "./chunk-D56KDQKC.js";
|
||
import "./chunk-MESSQWK4.js";
|
||
import "./chunk-LRXO5GLT.js";
|
||
import "./chunk-2RRPH7ER.js";
|
||
import "./chunk-HM3IY3H4.js";
|
||
import "./chunk-RAYGMRYA.js";
|
||
import "./chunk-XHKILN37.js";
|
||
import {
|
||
Layer_default
|
||
} from "./chunk-JOOKRY6I.js";
|
||
import "./chunk-V7WRBSQ6.js";
|
||
import "./chunk-FDGVG43Y.js";
|
||
import "./chunk-C66424RK.js";
|
||
import "./chunk-POHYZJBN.js";
|
||
import "./chunk-5D2XPBR2.js";
|
||
import "./chunk-F6XZ22ZQ.js";
|
||
import "./chunk-OVJRLVXU.js";
|
||
import "./chunk-E5F6ZCFZ.js";
|
||
import "./chunk-PZOVY5ZG.js";
|
||
import "./chunk-EMRMEHGR.js";
|
||
import "./chunk-GNM7L5BH.js";
|
||
import {
|
||
Layer_default as Layer_default2
|
||
} from "./chunk-S5OMZ56B.js";
|
||
import "./chunk-LMC3RO5P.js";
|
||
import "./chunk-FM44FOIC.js";
|
||
import "./chunk-GMHZLYJW.js";
|
||
import "./chunk-PPP4FLHO.js";
|
||
import "./chunk-YWIWRQT2.js";
|
||
import "./chunk-3HOSDZVQ.js";
|
||
import "./chunk-5TDNKDLD.js";
|
||
import "./chunk-5XHD7RSF.js";
|
||
import "./chunk-53ZECYYG.js";
|
||
import "./chunk-VIJW6LYV.js";
|
||
import "./chunk-RBA5LKAR.js";
|
||
import "./chunk-GA6VLMXX.js";
|
||
import "./chunk-CXIHWQDX.js";
|
||
import "./chunk-JFXZSSOM.js";
|
||
import "./chunk-NLIGXLAR.js";
|
||
import "./chunk-YUSNUQO6.js";
|
||
import "./chunk-YUTQGDGI.js";
|
||
import {
|
||
apply,
|
||
compose,
|
||
create,
|
||
makeInverse,
|
||
toString as toString2
|
||
} from "./chunk-JFONEOYG.js";
|
||
import {
|
||
fromUserCoordinate,
|
||
fromUserExtent,
|
||
toUserCoordinate,
|
||
transform
|
||
} from "./chunk-XZU4LSFD.js";
|
||
import "./chunk-ZLPTRF2L.js";
|
||
import "./chunk-3JZANJYE.js";
|
||
import "./chunk-54BTDBAD.js";
|
||
import {
|
||
containsCoordinate,
|
||
containsExtent,
|
||
getIntersection,
|
||
intersects,
|
||
isEmpty
|
||
} from "./chunk-CKDBVGKM.js";
|
||
import "./chunk-QFCIXVZ3.js";
|
||
import "./chunk-H47PV7W6.js";
|
||
import "./chunk-KJXIHBKT.js";
|
||
import "./chunk-FQY6EMA7.js";
|
||
import "./chunk-5RHQVMYD.js";
|
||
import "./chunk-LK32TJAX.js";
|
||
|
||
// node_modules/wind-core/dist/wind-core.esm.js
|
||
var hasOwnProperty = Object.prototype.hasOwnProperty;
|
||
var symToStringTag = typeof Symbol !== "undefined" ? Symbol.toStringTag : void 0;
|
||
function baseGetTag(value) {
|
||
if (value === null) {
|
||
return value === void 0 ? "[object Undefined]" : "[object Null]";
|
||
}
|
||
if (!(symToStringTag && symToStringTag in Object(value))) {
|
||
return toString.call(value);
|
||
}
|
||
const isOwn = hasOwnProperty.call(value, symToStringTag);
|
||
const tag = value[symToStringTag];
|
||
let unmasked = false;
|
||
try {
|
||
value[symToStringTag] = void 0;
|
||
unmasked = true;
|
||
} catch (e) {
|
||
}
|
||
const result = Object.prototype.toString.call(value);
|
||
if (unmasked) {
|
||
if (isOwn) {
|
||
value[symToStringTag] = tag;
|
||
} else {
|
||
delete value[symToStringTag];
|
||
}
|
||
}
|
||
return result;
|
||
}
|
||
function isFunction(value) {
|
||
if (!isObject(value)) {
|
||
return false;
|
||
}
|
||
const tag = baseGetTag(value);
|
||
return tag === "[object Function]" || tag === "[object AsyncFunction]" || tag === "[object GeneratorFunction]" || tag === "[object Proxy]";
|
||
}
|
||
function isObject(value) {
|
||
const type = typeof value;
|
||
return value !== null && (type === "object" || type === "function");
|
||
}
|
||
function isString(value) {
|
||
if (value == null) {
|
||
return false;
|
||
}
|
||
return typeof value === "string" || value.constructor !== null && value.constructor === String;
|
||
}
|
||
function isNumber(value) {
|
||
return Object.prototype.toString.call(value) === "[object Number]" && !isNaN(value);
|
||
}
|
||
function isArray(arr) {
|
||
return Array.isArray(arr);
|
||
}
|
||
function assign(target, ...sources) {
|
||
return Object.assign(target, ...sources);
|
||
}
|
||
function warnLog(msg, n) {
|
||
console.warn(`${n || "wind-layer"}: ${msg}`);
|
||
}
|
||
var warnings = {};
|
||
function warnOnce(namespaces, msg) {
|
||
if (!warnings[msg]) {
|
||
warnLog(msg, namespaces);
|
||
warnings[msg] = true;
|
||
}
|
||
}
|
||
function floorMod(a, n) {
|
||
return a - n * Math.floor(a / n);
|
||
}
|
||
function isValide(val) {
|
||
return val !== void 0 && val !== null && !isNaN(val);
|
||
}
|
||
function formatData(data, options = {}) {
|
||
let uComp = void 0;
|
||
let vComp = void 0;
|
||
data.forEach(function(record) {
|
||
switch (record.header.parameterCategory + "," + record.header.parameterNumber) {
|
||
case "1,2":
|
||
case "2,2":
|
||
uComp = record;
|
||
break;
|
||
case "1,3":
|
||
case "2,3":
|
||
vComp = record;
|
||
break;
|
||
}
|
||
});
|
||
if (!vComp || !uComp) {
|
||
return void 0;
|
||
}
|
||
const header = uComp.header;
|
||
const vectorField = new Field({
|
||
xmin: header.lo1,
|
||
// 一般格点数据是按照矩形范围来切割,所以定义其经纬度范围
|
||
ymin: header.la1,
|
||
xmax: header.lo2,
|
||
ymax: header.la2,
|
||
deltaX: header.dx,
|
||
// x(经度)增量
|
||
deltaY: header.dy,
|
||
// y(维度)增量
|
||
cols: header.nx,
|
||
// 列(可由 `(xmax - xmin) / deltaX` 得到)
|
||
rows: header.ny,
|
||
// 行
|
||
us: uComp.data,
|
||
// U分量
|
||
vs: vComp.data,
|
||
// V分量
|
||
...options
|
||
});
|
||
return vectorField;
|
||
}
|
||
var Vector = class {
|
||
constructor(u, v) {
|
||
this.u = u;
|
||
this.v = v;
|
||
this.m = this.magnitude();
|
||
}
|
||
/**
|
||
* 向量值(这里指风速)
|
||
* @returns {Number}
|
||
*/
|
||
magnitude() {
|
||
return Math.sqrt(this.u ** 2 + this.v ** 2);
|
||
}
|
||
/**
|
||
* 流体方向 (这里指风向,范围为0-360º)
|
||
* N is 0º and E is 90º
|
||
* @returns {Number}
|
||
*/
|
||
directionTo() {
|
||
const verticalAngle = Math.atan2(this.u, this.v);
|
||
let inDegrees = verticalAngle * (180 / Math.PI);
|
||
if (inDegrees < 0) {
|
||
inDegrees += 360;
|
||
}
|
||
return inDegrees;
|
||
}
|
||
/**
|
||
* Angle in degrees (0 to 360º) From x-->
|
||
* N is 0º and E is 90º
|
||
* @returns {Number}
|
||
*/
|
||
directionFrom() {
|
||
const a = this.directionTo();
|
||
return (a + 180) % 360;
|
||
}
|
||
};
|
||
var Field = class {
|
||
constructor(params) {
|
||
this.grid = [];
|
||
this.xmin = params.xmin;
|
||
this.xmax = params.xmax;
|
||
this.ymin = params.ymin;
|
||
this.ymax = params.ymax;
|
||
this.cols = params.cols;
|
||
this.rows = params.rows;
|
||
this.us = params.us;
|
||
this.vs = params.vs;
|
||
this.deltaX = params.deltaX;
|
||
this.deltaY = params.deltaY;
|
||
this.flipY = Boolean(params.flipY);
|
||
this.ymin = Math.min(params.ymax, params.ymin);
|
||
this.ymax = Math.max(params.ymax, params.ymin);
|
||
if (!(this.deltaY < 0 && this.ymin < this.ymax)) {
|
||
if (params.flipY === void 0) {
|
||
this.flipY = true;
|
||
}
|
||
console.warn("[wind-core]: The data is flipY");
|
||
}
|
||
this.isFields = true;
|
||
const cols = Math.ceil((this.xmax - this.xmin) / params.deltaX);
|
||
const rows = Math.ceil((this.ymax - this.ymin) / params.deltaY);
|
||
if (cols !== this.cols || rows !== this.rows) {
|
||
console.warn("[wind-core]: The data grid not equal");
|
||
}
|
||
this.isContinuous = Math.floor(this.cols * params.deltaX) >= 360;
|
||
this.translateX = "translateX" in params ? params.translateX : this.xmax > 180;
|
||
if ("wrappedX" in params) {
|
||
warnOnce("[wind-core]: ", "`wrappedX` namespace will deprecated please use `translateX` instead!");
|
||
}
|
||
this.wrapX = Boolean(params.wrapX);
|
||
this.grid = this.buildGrid();
|
||
this.range = this.calculateRange();
|
||
}
|
||
// from https://github.com/sakitam-fdd/wind-layer/blob/95368f9433/src/windy/windy.js#L110
|
||
buildGrid() {
|
||
const grid = [];
|
||
let p = 0;
|
||
const { rows, cols, us, vs } = this;
|
||
for (let j = 0; j < rows; j++) {
|
||
const row = [];
|
||
for (let i = 0; i < cols; i++, p++) {
|
||
const u = us[p];
|
||
const v = vs[p];
|
||
const valid = this.isValid(u) && this.isValid(v);
|
||
row[i] = valid ? new Vector(u, v) : null;
|
||
}
|
||
if (this.isContinuous) {
|
||
row.push(row[0]);
|
||
}
|
||
grid[j] = row;
|
||
}
|
||
return grid;
|
||
}
|
||
/**
|
||
* release data
|
||
*/
|
||
release() {
|
||
this.grid = [];
|
||
}
|
||
/**
|
||
* grib data extent
|
||
* 格点数据范围
|
||
*/
|
||
extent() {
|
||
return [this.xmin, this.ymin, this.xmax, this.ymax];
|
||
}
|
||
/**
|
||
* Bilinear interpolation for Vector
|
||
* 针对向量进行双线性插值
|
||
* 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 {Vector}
|
||
*/
|
||
bilinearInterpolateVector(x, y, g00, g10, g01, g11) {
|
||
const rx = 1 - x;
|
||
const ry = 1 - y;
|
||
const a = rx * ry;
|
||
const b = x * ry;
|
||
const c = rx * y;
|
||
const d = x * y;
|
||
const u = g00.u * a + g10.u * b + g01.u * c + g11.u * d;
|
||
const v = g00.v * a + g10.v * b + g01.v * c + g11.v * d;
|
||
return new Vector(u, v);
|
||
}
|
||
/**
|
||
* calculate vector value range
|
||
*/
|
||
calculateRange() {
|
||
if (!this.grid || !this.grid[0])
|
||
return;
|
||
const rows = this.grid.length;
|
||
const cols = this.grid[0].length;
|
||
let min;
|
||
let max;
|
||
for (let j = 0; j < rows; j++) {
|
||
for (let i = 0; i < cols; i++) {
|
||
const vec = this.grid[j][i];
|
||
if (vec !== null) {
|
||
const val = vec.m || vec.magnitude();
|
||
if (min === void 0) {
|
||
min = val;
|
||
} else if (max === void 0) {
|
||
max = val;
|
||
min = Math.min(min, max);
|
||
max = Math.max(min, max);
|
||
} else {
|
||
min = Math.min(val, min);
|
||
max = Math.max(val, max);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return [min, max];
|
||
}
|
||
/**
|
||
* 检查 uv是否合法
|
||
* @param x
|
||
* @private
|
||
*/
|
||
isValid(x) {
|
||
return x !== null && x !== void 0;
|
||
}
|
||
getWrappedLongitudes() {
|
||
let xmin = this.xmin;
|
||
let xmax = this.xmax;
|
||
if (this.translateX) {
|
||
if (this.isContinuous) {
|
||
xmin = -180;
|
||
xmax = 180;
|
||
} else {
|
||
xmax = this.xmax - 360;
|
||
xmin = this.xmin - 360;
|
||
}
|
||
}
|
||
return [xmin, xmax];
|
||
}
|
||
contains(lon, lat) {
|
||
const [xmin, xmax] = this.getWrappedLongitudes();
|
||
if (xmax > 180 && lon >= -180 && lon <= xmax - 360) {
|
||
lon += 360;
|
||
} else if (xmin < -180 && lon <= 180 && lon >= xmin + 360) {
|
||
lon -= 360;
|
||
}
|
||
const longitudeIn = lon >= xmin && lon <= xmax;
|
||
let latitudeIn;
|
||
if (this.deltaY >= 0) {
|
||
latitudeIn = lat >= this.ymin && lat <= this.ymax;
|
||
} else {
|
||
latitudeIn = lat >= this.ymax && lat <= this.ymin;
|
||
}
|
||
return longitudeIn && latitudeIn;
|
||
}
|
||
/**
|
||
* 获取经纬度所在的位置索引
|
||
* @param lon
|
||
* @param lat
|
||
*/
|
||
getDecimalIndexes(lon, lat) {
|
||
const i = floorMod(lon - this.xmin, 360) / this.deltaX;
|
||
if (this.flipY) {
|
||
const j = (this.ymax - lat) / this.deltaY;
|
||
return [i, j];
|
||
} else {
|
||
const j = (this.ymin + lat) / this.deltaY;
|
||
return [i, j];
|
||
}
|
||
}
|
||
/**
|
||
* Nearest value at lon-lat coordinates
|
||
* 线性插值
|
||
* @param lon
|
||
* @param lat
|
||
*/
|
||
valueAt(lon, lat) {
|
||
let flag = false;
|
||
if (this.wrapX) {
|
||
flag = true;
|
||
} else if (this.contains(lon, lat)) {
|
||
flag = true;
|
||
}
|
||
if (!flag)
|
||
return null;
|
||
const indexes = this.getDecimalIndexes(lon, lat);
|
||
const ii = Math.floor(indexes[0]);
|
||
const jj = Math.floor(indexes[1]);
|
||
const ci = this.clampColumnIndex(ii);
|
||
const cj = this.clampRowIndex(jj);
|
||
return this.valueAtIndexes(ci, cj);
|
||
}
|
||
/**
|
||
* Get interpolated grid value lon-lat coordinates
|
||
* 双线性插值
|
||
* @param lon
|
||
* @param lat
|
||
*/
|
||
interpolatedValueAt(lon, lat) {
|
||
let flag = false;
|
||
if (this.wrapX) {
|
||
flag = true;
|
||
} else if (this.contains(lon, lat)) {
|
||
flag = true;
|
||
}
|
||
if (!flag)
|
||
return null;
|
||
const [i, j] = this.getDecimalIndexes(lon, lat);
|
||
return this.interpolatePoint(i, j);
|
||
}
|
||
hasValueAt(lon, lat) {
|
||
const value = this.valueAt(lon, lat);
|
||
return value !== null;
|
||
}
|
||
/**
|
||
* 基于向量的双线性插值
|
||
* @param i
|
||
* @param j
|
||
*/
|
||
interpolatePoint(i, j) {
|
||
const indexes = this.getFourSurroundingIndexes(i, j);
|
||
const [fi, ci, fj, cj] = indexes;
|
||
const values = this.getFourSurroundingValues(fi, ci, fj, cj);
|
||
if (values) {
|
||
const [g00, g10, g01, g11] = values;
|
||
return this.bilinearInterpolateVector(i - fi, j - fj, g00, g10, g01, g11);
|
||
}
|
||
return null;
|
||
}
|
||
/**
|
||
* Check the column index is inside the field,
|
||
* adjusting to min or max when needed
|
||
* @private
|
||
* @param {Number} ii - index
|
||
* @returns {Number} i - inside the allowed indexes
|
||
*/
|
||
clampColumnIndex(ii) {
|
||
let i = ii;
|
||
if (ii < 0) {
|
||
i = 0;
|
||
}
|
||
const maxCol = this.cols - 1;
|
||
if (ii > maxCol) {
|
||
i = maxCol;
|
||
}
|
||
return i;
|
||
}
|
||
/**
|
||
* Check the row index is inside the field,
|
||
* adjusting to min or max when needed
|
||
* @private
|
||
* @param {Number} jj index
|
||
* @returns {Number} j - inside the allowed indexes
|
||
*/
|
||
clampRowIndex(jj) {
|
||
let j = jj;
|
||
if (jj < 0) {
|
||
j = 0;
|
||
}
|
||
const maxRow = this.rows - 1;
|
||
if (jj > maxRow) {
|
||
j = maxRow;
|
||
}
|
||
return j;
|
||
}
|
||
/**
|
||
* 计算索引位置周围的数据
|
||
* @private
|
||
* @param {Number} i - decimal index
|
||
* @param {Number} j - decimal index
|
||
* @returns {Array} [fi, ci, fj, cj]
|
||
*/
|
||
getFourSurroundingIndexes(i, j) {
|
||
const fi = Math.floor(i);
|
||
let ci = fi + 1;
|
||
if (this.isContinuous && ci >= this.cols) {
|
||
ci = 0;
|
||
}
|
||
ci = this.clampColumnIndex(ci);
|
||
const fj = this.clampRowIndex(Math.floor(j));
|
||
const cj = this.clampRowIndex(fj + 1);
|
||
return [fi, ci, fj, cj];
|
||
}
|
||
/**
|
||
* Get four surrounding values or null if not available,
|
||
* from 4 integer indexes
|
||
* @private
|
||
* @param {Number} fi
|
||
* @param {Number} ci
|
||
* @param {Number} fj
|
||
* @param {Number} cj
|
||
* @returns {Array}
|
||
*/
|
||
getFourSurroundingValues(fi, ci, fj, cj) {
|
||
let row;
|
||
if (row = this.grid[fj]) {
|
||
const g00 = row[fi];
|
||
const g10 = row[ci];
|
||
if (this.isValid(g00) && this.isValid(g10) && (row = this.grid[cj])) {
|
||
const g01 = row[fi];
|
||
const g11 = row[ci];
|
||
if (this.isValid(g01) && this.isValid(g11)) {
|
||
return [g00, g10, g01, g11];
|
||
}
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
/**
|
||
* Value for grid indexes
|
||
* @param {Number} i - column index (integer)
|
||
* @param {Number} j - row index (integer)
|
||
* @returns {Vector|Number}
|
||
*/
|
||
valueAtIndexes(i, j) {
|
||
return this.grid[j][i];
|
||
}
|
||
/**
|
||
* Lon-Lat for grid indexes
|
||
* @param {Number} i - column index (integer)
|
||
* @param {Number} j - row index (integer)
|
||
* @returns {Number[]} [lon, lat]
|
||
*/
|
||
lonLatAtIndexes(i, j) {
|
||
const lon = this.longitudeAtX(i);
|
||
const lat = this.latitudeAtY(j);
|
||
return [lon, lat];
|
||
}
|
||
/**
|
||
* Longitude for grid-index
|
||
* @param {Number} i - column index (integer)
|
||
* @returns {Number} longitude at the center of the cell
|
||
*/
|
||
longitudeAtX(i) {
|
||
const halfXPixel = this.deltaX / 2;
|
||
let lon = this.xmin + halfXPixel + i * this.deltaX;
|
||
if (this.translateX) {
|
||
lon = lon > 180 ? lon - 360 : lon;
|
||
}
|
||
return lon;
|
||
}
|
||
/**
|
||
* Latitude for grid-index
|
||
* @param {Number} j - row index (integer)
|
||
* @returns {Number} latitude at the center of the cell
|
||
*/
|
||
latitudeAtY(j) {
|
||
const halfYPixel = this.deltaY / 2;
|
||
return this.ymax - halfYPixel - j * this.deltaY;
|
||
}
|
||
/**
|
||
* 生成粒子位置
|
||
* @param o
|
||
* @param width
|
||
* @param height
|
||
* @param unproject
|
||
* @return IPosition
|
||
*/
|
||
randomize(o = {}, width, height, unproject) {
|
||
const i = Math.random() * (width || this.cols) | 0;
|
||
const j = Math.random() * (height || this.rows) | 0;
|
||
const coords = unproject([i, j]);
|
||
if (coords !== null) {
|
||
o.x = coords[0];
|
||
o.y = coords[1];
|
||
} else {
|
||
o.x = this.longitudeAtX(i);
|
||
o.y = this.latitudeAtY(j);
|
||
}
|
||
return o;
|
||
}
|
||
/**
|
||
* 判断是否是 `Field` 的实例
|
||
* @return boolean
|
||
*/
|
||
checkFields() {
|
||
return this.isFields;
|
||
}
|
||
};
|
||
var defaultOptions = {
|
||
globalAlpha: 0.9,
|
||
// 全局透明度
|
||
lineWidth: 1,
|
||
// 线条宽度
|
||
colorScale: "#fff",
|
||
velocityScale: 1 / 25,
|
||
// particleAge: 90,
|
||
maxAge: 90,
|
||
// alias for particleAge
|
||
// particleMultiplier: 1 / 300, // TODO: PATHS = Math.round(width * height * particleMultiplier);
|
||
paths: 800,
|
||
frameRate: 20,
|
||
useCoordsDraw: true
|
||
};
|
||
function indexFor(m, min, max, colorScale) {
|
||
return Math.max(0, Math.min(colorScale.length - 1, Math.round((m - min) / (max - min) * (colorScale.length - 1))));
|
||
}
|
||
var _WindCore = class _WindCore {
|
||
constructor(ctx, options, field) {
|
||
this.particles = [];
|
||
this.generated = false;
|
||
this.ctx = ctx;
|
||
if (!this.ctx) {
|
||
throw new Error("ctx error");
|
||
}
|
||
this.animate = this.animate.bind(this);
|
||
this.setOptions(options);
|
||
if (field) {
|
||
this.updateData(field);
|
||
}
|
||
}
|
||
/**
|
||
* 设置配置项
|
||
* @param options
|
||
*/
|
||
setOptions(options) {
|
||
this.options = { ...defaultOptions, ...options };
|
||
const { width, height } = this.ctx.canvas;
|
||
if ("particleAge" in options && !("maxAge" in options) && isNumber(this.options.particleAge)) {
|
||
this.options.maxAge = this.options.particleAge;
|
||
}
|
||
if ("particleMultiplier" in options && !("paths" in options) && isNumber(this.options.particleMultiplier)) {
|
||
this.options.paths = Math.round(width * height * this.options.particleMultiplier);
|
||
}
|
||
this.prerender();
|
||
}
|
||
/**
|
||
* 获取配置项
|
||
*/
|
||
getOptions() {
|
||
return this.options;
|
||
}
|
||
/**
|
||
* 更新数据
|
||
* @param field
|
||
*/
|
||
updateData(field) {
|
||
this.field = field;
|
||
if (!this.generated) {
|
||
return;
|
||
}
|
||
this.particles = this.prepareParticlePaths();
|
||
}
|
||
// @ts-ignore
|
||
project(...args) {
|
||
throw new Error("project must be overriden");
|
||
}
|
||
// @ts-ignore
|
||
unproject(...args) {
|
||
throw new Error("unproject must be overriden");
|
||
}
|
||
/**
|
||
* 判断位置是否在当前视窗内
|
||
* @param coordinates
|
||
*/
|
||
intersectsCoordinate(coordinates) {
|
||
throw new Error("must be overriden");
|
||
}
|
||
/**
|
||
* 清空当前画布
|
||
*/
|
||
clearCanvas() {
|
||
this.stop();
|
||
this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
|
||
this.forceStop = false;
|
||
}
|
||
isStop() {
|
||
return !this.starting;
|
||
}
|
||
/**
|
||
* 启动粒子动画
|
||
*/
|
||
start() {
|
||
this.starting = true;
|
||
this.forceStop = false;
|
||
this.then = Date.now();
|
||
this.animate();
|
||
}
|
||
/**
|
||
* 停止粒子动画
|
||
*/
|
||
stop() {
|
||
cancelAnimationFrame(this.animationLoop);
|
||
this.starting = false;
|
||
this.forceStop = true;
|
||
}
|
||
animate() {
|
||
if (this.animationLoop) {
|
||
cancelAnimationFrame(this.animationLoop);
|
||
}
|
||
this.animationLoop = requestAnimationFrame(this.animate);
|
||
const now = Date.now();
|
||
const delta = now - this.then;
|
||
if (delta > this.options.frameRate) {
|
||
this.then = now - delta % this.options.frameRate;
|
||
this.render();
|
||
}
|
||
}
|
||
/**
|
||
* 渲染前处理
|
||
*/
|
||
prerender() {
|
||
this.generated = false;
|
||
if (!this.field) {
|
||
return;
|
||
}
|
||
this.particles = this.prepareParticlePaths();
|
||
this.generated = true;
|
||
if (!this.starting && !this.forceStop) {
|
||
this.starting = true;
|
||
this.then = Date.now();
|
||
this.animate();
|
||
}
|
||
}
|
||
/**
|
||
* 开始渲染
|
||
*/
|
||
render() {
|
||
this.moveParticles();
|
||
this.drawParticles();
|
||
this.postrender();
|
||
}
|
||
/**
|
||
* each frame render end
|
||
*/
|
||
postrender() {
|
||
}
|
||
moveParticles() {
|
||
const { width, height } = this.ctx.canvas;
|
||
const particles = this.particles;
|
||
const maxAge = this.options.maxAge;
|
||
const velocityScale = isFunction(this.options.velocityScale) ? this.options.velocityScale() : this.options.velocityScale;
|
||
let i = 0;
|
||
const len = particles.length;
|
||
for (; i < len; i++) {
|
||
const particle = particles[i];
|
||
if (particle.age > maxAge) {
|
||
particle.age = 0;
|
||
this.field.randomize(particle, width, height, this.unproject);
|
||
}
|
||
const x = particle.x;
|
||
const y = particle.y;
|
||
const vector = this.field.interpolatedValueAt(x, y);
|
||
if (vector === null) {
|
||
particle.age = maxAge;
|
||
} else {
|
||
const xt = x + vector.u * velocityScale;
|
||
const yt = y + vector.v * velocityScale;
|
||
if (this.field.hasValueAt(xt, yt)) {
|
||
particle.xt = xt;
|
||
particle.yt = yt;
|
||
particle.m = vector.m;
|
||
} else {
|
||
particle.x = xt;
|
||
particle.y = yt;
|
||
particle.age = maxAge;
|
||
}
|
||
}
|
||
particle.age++;
|
||
}
|
||
}
|
||
fadeIn() {
|
||
const prev = this.ctx.globalCompositeOperation;
|
||
this.ctx.globalCompositeOperation = "destination-in";
|
||
this.ctx.fillRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
|
||
this.ctx.globalCompositeOperation = prev;
|
||
}
|
||
drawParticles() {
|
||
const particles = this.particles;
|
||
this.fadeIn();
|
||
this.ctx.globalAlpha = this.options.globalAlpha;
|
||
this.ctx.fillStyle = `rgba(0, 0, 0, ${this.options.globalAlpha})`;
|
||
this.ctx.lineWidth = isNumber(this.options.lineWidth) ? this.options.lineWidth : 1;
|
||
this.ctx.strokeStyle = isString(this.options.colorScale) ? this.options.colorScale : "#fff";
|
||
let i = 0;
|
||
const len = particles.length;
|
||
if (this.field && len > 0) {
|
||
let min;
|
||
let max;
|
||
if (isValide(this.options.minVelocity) && isValide(this.options.maxVelocity)) {
|
||
min = this.options.minVelocity;
|
||
max = this.options.maxVelocity;
|
||
} else {
|
||
[min, max] = this.field.range;
|
||
}
|
||
for (; i < len; i++) {
|
||
this[this.options.useCoordsDraw ? "drawCoordsParticle" : "drawPixelParticle"](particles[i], min, max);
|
||
}
|
||
}
|
||
}
|
||
/**
|
||
* 用于绘制像素粒子
|
||
* @param particle
|
||
* @param min
|
||
* @param max
|
||
*/
|
||
drawPixelParticle(particle, min, max) {
|
||
const pointPrev = [particle.x, particle.y];
|
||
const pointNext = [particle.xt, particle.yt];
|
||
if (pointNext && pointPrev && isValide(pointNext[0]) && isValide(pointNext[1]) && isValide(pointPrev[0]) && isValide(pointPrev[1]) && particle.age <= this.options.maxAge) {
|
||
this.ctx.beginPath();
|
||
this.ctx.moveTo(pointPrev[0], pointPrev[1]);
|
||
this.ctx.lineTo(pointNext[0], pointNext[1]);
|
||
if (isFunction(this.options.colorScale)) {
|
||
this.ctx.strokeStyle = this.options.colorScale(particle.m);
|
||
} else if (Array.isArray(this.options.colorScale)) {
|
||
const colorIdx = indexFor(particle.m, min, max, this.options.colorScale);
|
||
this.ctx.strokeStyle = this.options.colorScale[colorIdx];
|
||
}
|
||
if (isFunction(this.options.lineWidth)) {
|
||
this.ctx.lineWidth = this.options.lineWidth(particle.m);
|
||
}
|
||
particle.x = particle.xt;
|
||
particle.y = particle.yt;
|
||
this.ctx.stroke();
|
||
}
|
||
}
|
||
/**
|
||
* 用于绘制坐标粒子
|
||
* @param particle
|
||
* @param min
|
||
* @param max
|
||
*/
|
||
drawCoordsParticle(particle, min, max) {
|
||
const source = [particle.x, particle.y];
|
||
const target = [particle.xt, particle.yt];
|
||
if (target && source && isValide(target[0]) && isValide(target[1]) && isValide(source[0]) && isValide(source[1]) && this.intersectsCoordinate(target) && particle.age <= this.options.maxAge) {
|
||
const pointPrev = this.project(source);
|
||
const pointNext = this.project(target);
|
||
if (pointPrev && pointNext) {
|
||
this.ctx.beginPath();
|
||
this.ctx.moveTo(pointPrev[0], pointPrev[1]);
|
||
this.ctx.lineTo(pointNext[0], pointNext[1]);
|
||
particle.x = particle.xt;
|
||
particle.y = particle.yt;
|
||
if (isFunction(this.options.colorScale)) {
|
||
this.ctx.strokeStyle = this.options.colorScale(particle.m);
|
||
} else if (Array.isArray(this.options.colorScale)) {
|
||
const colorIdx = indexFor(particle.m, min, max, this.options.colorScale);
|
||
this.ctx.strokeStyle = this.options.colorScale[colorIdx];
|
||
}
|
||
if (isFunction(this.options.lineWidth)) {
|
||
this.ctx.lineWidth = this.options.lineWidth(particle.m);
|
||
}
|
||
this.ctx.stroke();
|
||
}
|
||
}
|
||
}
|
||
prepareParticlePaths() {
|
||
const { width, height } = this.ctx.canvas;
|
||
const particleCount = typeof this.options.paths === "function" ? this.options.paths(this) : this.options.paths;
|
||
const particles = [];
|
||
if (!this.field) {
|
||
return [];
|
||
}
|
||
let i = 0;
|
||
for (; i < particleCount; i++) {
|
||
particles.push(
|
||
this.field.randomize(
|
||
{
|
||
age: this.randomize()
|
||
},
|
||
width,
|
||
height,
|
||
this.unproject
|
||
)
|
||
);
|
||
}
|
||
return particles;
|
||
}
|
||
randomize() {
|
||
return Math.floor(Math.random() * this.options.maxAge);
|
||
}
|
||
};
|
||
_WindCore.Field = Field;
|
||
var WindCore = _WindCore;
|
||
|
||
// node_modules/ol-wind/dist/ol-wind.esm.js
|
||
var ViewHint = {
|
||
ANIMATING: 0,
|
||
INTERACTING: 1
|
||
};
|
||
var WindLayerRender = class extends Layer_default {
|
||
constructor(layer) {
|
||
super(layer);
|
||
this.pixelTransform = create();
|
||
this.inversePixelTransform = create();
|
||
}
|
||
// useContainer(target: HTMLElement, transform: string, backgroundColor: number) 这里在 v6.3.0 后有 break change
|
||
useContainer(target, transform2, backgroundColor) {
|
||
super.useContainer(null, transform2, backgroundColor);
|
||
}
|
||
getBackground(frameState) {
|
||
if (super.getBackground) {
|
||
return super.getBackground(frameState);
|
||
}
|
||
return "";
|
||
}
|
||
prepareFrame(frameState) {
|
||
var _a, _b;
|
||
const layerState = frameState.layerStatesArray[frameState.layerIndex];
|
||
const viewState = frameState.viewState;
|
||
const hints = frameState.viewHints;
|
||
let renderedExtent = frameState.extent;
|
||
if (layerState.extent !== void 0) {
|
||
renderedExtent = getIntersection(
|
||
renderedExtent,
|
||
fromUserExtent(layerState.extent, viewState.projection)
|
||
);
|
||
}
|
||
if (!hints[ViewHint.ANIMATING] && !frameState.animate && !hints[ViewHint.INTERACTING] && !isEmpty(renderedExtent)) {
|
||
if ((_b = (_a = this.wind) == null ? void 0 : _a.isStop) == null ? void 0 : _b.call(_a)) {
|
||
this.wind.start();
|
||
}
|
||
return true;
|
||
} else {
|
||
const layer = this.getLayer();
|
||
return layer.get("forceRender");
|
||
}
|
||
}
|
||
prepareContainer(frameState, target) {
|
||
const size = frameState.size;
|
||
const rotation = frameState.viewState.rotation;
|
||
const pixelRatio = frameState.pixelRatio;
|
||
const width = Math.round(size[0] * pixelRatio);
|
||
const height = Math.round(size[1] * pixelRatio);
|
||
compose(
|
||
this.pixelTransform,
|
||
frameState.size[0] / 2,
|
||
frameState.size[1] / 2,
|
||
1 / pixelRatio,
|
||
1 / pixelRatio,
|
||
rotation,
|
||
-width / 2,
|
||
-height / 2
|
||
);
|
||
makeInverse(this.inversePixelTransform, this.pixelTransform);
|
||
const canvasTransform = toString2(this.pixelTransform);
|
||
this.useContainer(target, canvasTransform, this.getBackground(frameState));
|
||
if (!this.containerReused) {
|
||
const canvas = this.context.canvas;
|
||
if (canvas.width != width || canvas.height != height) {
|
||
canvas.width = width;
|
||
canvas.height = height;
|
||
}
|
||
if (canvasTransform !== canvas.style.transform) {
|
||
canvas.style.transform = canvasTransform;
|
||
}
|
||
}
|
||
}
|
||
getRenderContext(frameState) {
|
||
return this.context;
|
||
}
|
||
renderFrame(frameState, target) {
|
||
const layerState = frameState.layerStatesArray[frameState.layerIndex];
|
||
const viewState = frameState.viewState;
|
||
this.prepareContainer(frameState, target);
|
||
const context = this.getRenderContext(frameState);
|
||
context.imageSmoothingEnabled = false;
|
||
this.preRender(context, frameState);
|
||
let clipped = false;
|
||
let render = true;
|
||
if (layerState.extent) {
|
||
const layerExtent = fromUserExtent(layerState.extent, viewState.projection);
|
||
render = intersects(layerExtent, frameState.extent);
|
||
clipped = render && !containsExtent(layerExtent, frameState.extent);
|
||
if (clipped) {
|
||
this.clipUnrotated(context, frameState, layerExtent);
|
||
}
|
||
}
|
||
const layer = this.getLayer();
|
||
const opt = layer.getWindOptions();
|
||
const data = layer.getData();
|
||
this.execute(this.context, frameState, opt, data);
|
||
this.postRender(this.context, frameState);
|
||
if (clipped) {
|
||
context.restore();
|
||
}
|
||
context.imageSmoothingEnabled = true;
|
||
return this.container;
|
||
}
|
||
setOptions(options) {
|
||
if (this.wind) {
|
||
this.wind.setOptions(options);
|
||
}
|
||
}
|
||
setData(field) {
|
||
if (this.wind) {
|
||
this.wind.updateData(field);
|
||
}
|
||
}
|
||
execute(context, frameState, opt, data) {
|
||
if (!this.wind) {
|
||
this.wind = new WindCore(context, opt, data);
|
||
this.wind.project = this.getPixelFromCoordinateInternal.bind(this);
|
||
this.wind.unproject = this.getCoordinateFromPixel.bind(this);
|
||
this.wind.intersectsCoordinate = this.intersectsCoordinate.bind(this);
|
||
this.wind.postrender = () => {
|
||
};
|
||
this.wind.prerender();
|
||
}
|
||
}
|
||
getPixelFromCoordinateInternal(coordinate) {
|
||
const frameState = this.frameState;
|
||
if (!frameState) {
|
||
return null;
|
||
} else {
|
||
const viewState = frameState.viewState;
|
||
const pixelRatio = frameState.pixelRatio;
|
||
const point = transform(coordinate, "EPSG:4326", viewState.projection);
|
||
const viewCoordinate = fromUserCoordinate(point, viewState.projection);
|
||
const pixel = apply(frameState.coordinateToPixelTransform, viewCoordinate.slice(0, 2));
|
||
return [pixel[0] * pixelRatio, pixel[1] * pixelRatio];
|
||
}
|
||
}
|
||
getCoordinateFromPixel(pixel) {
|
||
const frameState = this.frameState;
|
||
if (!frameState) {
|
||
return null;
|
||
} else {
|
||
const viewState = frameState.viewState;
|
||
const viewCoordinate = apply(frameState.pixelToCoordinateTransform, pixel.slice(0, 2));
|
||
const coordinate = toUserCoordinate(viewCoordinate, viewState.projection);
|
||
const point = transform(coordinate, viewState.projection, "EPSG:4326");
|
||
return [point[0], point[1]];
|
||
}
|
||
}
|
||
intersectsCoordinate(coordinate) {
|
||
const frameState = this.frameState;
|
||
if (frameState) {
|
||
const viewState = frameState.viewState;
|
||
const point = transform(coordinate, "EPSG:4326", viewState.projection);
|
||
const viewCoordinate = fromUserCoordinate(point, viewState.projection);
|
||
return containsCoordinate(frameState.extent, viewCoordinate.slice(0, 2));
|
||
}
|
||
return true;
|
||
}
|
||
};
|
||
var _options = {
|
||
forceRender: true,
|
||
windOptions: {}
|
||
};
|
||
var WindLayer = class extends Layer_default2 {
|
||
constructor(data, options) {
|
||
const opt = assign({}, _options, options);
|
||
super(opt);
|
||
this.options = opt;
|
||
this.className_ = options.className !== void 0 ? options.className : "wind-layer";
|
||
this.pickWindOptions();
|
||
if (data) {
|
||
this.setData(data, options.fieldOptions);
|
||
}
|
||
}
|
||
/**
|
||
* 兼容旧版调用方式,现在可以使用以下方式添加图层:
|
||
* ```ts
|
||
* 1. 常规方式
|
||
* map.addLayer(windLayer);
|
||
*
|
||
* 2. setMap 会脱离 ol 地图的图层管理
|
||
*
|
||
* layer.setMap(map);
|
||
*
|
||
* 3. 调用 appendTo
|
||
*
|
||
* layer.appendTo(map);
|
||
* ```
|
||
* @param map
|
||
*/
|
||
appendTo(map) {
|
||
map.addLayer(this);
|
||
}
|
||
onAdd() {
|
||
var _a;
|
||
const renderer = this.getRenderer();
|
||
if (renderer) {
|
||
(_a = renderer.wind) == null ? void 0 : _a.start();
|
||
}
|
||
}
|
||
onRemove() {
|
||
const renderer = this.getRenderer();
|
||
if (renderer) {
|
||
renderer.wind.stop();
|
||
}
|
||
}
|
||
createRenderer() {
|
||
return new WindLayerRender(this);
|
||
}
|
||
getRenderer() {
|
||
return super.getRenderer();
|
||
}
|
||
pickWindOptions() {
|
||
Object.keys(defaultOptions).forEach((key) => {
|
||
if (key in this.options) {
|
||
if (this.options.windOptions === void 0) {
|
||
this.options.windOptions = {};
|
||
}
|
||
this.options.windOptions[key] = this.options[key];
|
||
}
|
||
});
|
||
}
|
||
/**
|
||
* 获取图层现有数据
|
||
* get wind layer data
|
||
*/
|
||
// @ts-ignore overwrite base layer
|
||
getData() {
|
||
return this.field;
|
||
}
|
||
/**
|
||
* 设置图层数据
|
||
* set layer data
|
||
* @param data
|
||
* @param options
|
||
* @returns {WindLayer}
|
||
*/
|
||
setData(data, options = {}) {
|
||
if (data && data.checkFields && data.checkFields()) {
|
||
this.field = data;
|
||
} else if (isArray(data)) {
|
||
this.field = formatData(data, options);
|
||
} else {
|
||
console.error("Illegal data");
|
||
}
|
||
const renderer = this.getRenderer();
|
||
if (renderer && this.field) {
|
||
renderer.setData(this.field);
|
||
}
|
||
this.changed();
|
||
return this;
|
||
}
|
||
/**
|
||
* 设置风场图层的配置项
|
||
* @param options
|
||
*/
|
||
setWindOptions(options) {
|
||
const beforeOptions = this.options.windOptions || {};
|
||
this.options = assign(this.options, {
|
||
windOptions: assign(beforeOptions, options || {})
|
||
});
|
||
const renderer = this.getRenderer();
|
||
if (renderer) {
|
||
const windOptions = this.options.windOptions;
|
||
renderer.setOptions(windOptions);
|
||
}
|
||
this.changed();
|
||
}
|
||
/**
|
||
* 获取风场图层渲染的配置项
|
||
*/
|
||
getWindOptions() {
|
||
return this.options.windOptions || {};
|
||
}
|
||
render(frameState, target) {
|
||
const layerRenderer = this.getRenderer();
|
||
if (layerRenderer && layerRenderer.prepareFrame(frameState)) {
|
||
this.rendered = true;
|
||
return layerRenderer.renderFrame(frameState, target);
|
||
}
|
||
return null;
|
||
}
|
||
// since v6
|
||
setMapInternal(map) {
|
||
super.setMapInternal(map);
|
||
if (!map) {
|
||
this.onRemove();
|
||
} else {
|
||
this.onAdd();
|
||
}
|
||
}
|
||
/**
|
||
* 支持以 setMap 方式添加图层
|
||
* @param map
|
||
*/
|
||
setMap(map) {
|
||
super.setMap(map);
|
||
if (!map) {
|
||
this.onRemove();
|
||
} else {
|
||
this.onAdd();
|
||
}
|
||
}
|
||
};
|
||
export {
|
||
Field,
|
||
WindLayer
|
||
};
|
||
//# sourceMappingURL=ol-wind.js.map
|