Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

Field.js 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433
  1. /**
  2. * @Author: Caven
  3. * @Date: 2021-01-18 20:13:30
  4. */
  5. import Vector from './Vector'
  6. class Field {
  7. constructor(params) {
  8. this.grid = []
  9. this.xmin = params.xmin
  10. this.xmax = params.xmax
  11. this.ymin = params.ymin
  12. this.ymax = params.ymax
  13. this.cols = params.cols // 列数
  14. this.rows = params.rows // 行数
  15. this.us = params.us //
  16. this.vs = params.vs
  17. this.deltaX = params.deltaX // x 方向增量
  18. this.deltaY = params.deltaY // y方向增量
  19. if (this.deltaY < 0 && this.ymin < this.ymax) {
  20. // eslint-disable-next-line no-console
  21. console.warn('[wind-core]: The data is flipY')
  22. } else {
  23. this.ymin = Math.min(params.ymax, params.ymin)
  24. this.ymax = Math.max(params.ymax, params.ymin)
  25. }
  26. this.isFields = true
  27. let cols = Math.ceil((this.xmax - this.xmin) / params.deltaX) // 列
  28. let rows = Math.ceil((this.ymax - this.ymin) / params.deltaY) // 行
  29. if (cols !== this.cols || rows !== this.rows) {
  30. // eslint-disable-next-line no-console
  31. console.warn('[wind-core]: The data grid not equal')
  32. }
  33. // Math.floor(ni * Δλ) >= 360;
  34. this.isContinuous = Math.floor(this.cols * params.deltaX) >= 360
  35. this.wrappedX = 'wrappedX' in params ? params.wrappedX : this.xmax > 180 // [0, 360] --> [-180, 180];
  36. this.grid = this.buildGrid()
  37. this.range = this.calculateRange()
  38. }
  39. // from https://github.com/sakitam-fdd/wind-layer/blob/95368f9433/src/windy/windy.js#L110
  40. buildGrid() {
  41. let grid = []
  42. let p = 0
  43. let _a = this,
  44. rows = _a.rows,
  45. cols = _a.cols,
  46. us = _a.us,
  47. vs = _a.vs
  48. for (let j = 0; j < rows; j++) {
  49. let row = []
  50. for (let i = 0; i < cols; i++, p++) {
  51. let u = us[p]
  52. let v = vs[p]
  53. let valid = this.isValid(u) && this.isValid(v)
  54. row[i] = valid ? new Vector(u, v) : null
  55. }
  56. if (this.isContinuous) {
  57. row.push(row[0])
  58. }
  59. grid[j] = row
  60. }
  61. return grid
  62. }
  63. /**
  64. *
  65. */
  66. release() {
  67. this.grid = []
  68. }
  69. /***
  70. *
  71. * @returns {(*)[]}
  72. */
  73. extent() {
  74. return [this.xmin, this.ymin, this.xmax, this.ymax]
  75. }
  76. /**
  77. * Bilinear interpolation for Vector
  78. * https://en.wikipedia.org/wiki/Bilinear_interpolation
  79. * @param {Number} x
  80. * @param {Number} y
  81. * @param {Number[]} g00
  82. * @param {Number[]} g10
  83. * @param {Number[]} g01
  84. * @param {Number[]} g11
  85. * @returns {Vector}
  86. */
  87. bilinearInterpolateVector(x, y, g00, g10, g01, g11) {
  88. let rx = 1 - x
  89. let ry = 1 - y
  90. let a = rx * ry
  91. let b = x * ry
  92. let c = rx * y
  93. let d = x * y
  94. let u = g00.u * a + g10.u * b + g01.u * c + g11.u * d
  95. let v = g00.v * a + g10.v * b + g01.v * c + g11.v * d
  96. return new Vector(u, v)
  97. }
  98. /**
  99. * calculate vector value range
  100. */
  101. calculateRange() {
  102. if (!this.grid || !this.grid[0]) {
  103. return
  104. }
  105. let rows = this.grid.length
  106. let cols = this.grid[0].length
  107. // const vectors = [];
  108. let min
  109. let max
  110. // @from: https://stackoverflow.com/questions/13544476/how-to-find-max-and-min-in-array-using-minimum-comparisons
  111. for (let j = 0; j < rows; j++) {
  112. for (let i = 0; i < cols; i++) {
  113. let vec = this.grid[j][i]
  114. if (vec !== null) {
  115. let val = vec.m || vec.magnitude()
  116. // vectors.push();
  117. if (min === undefined) {
  118. min = val
  119. } else if (max === undefined) {
  120. max = val
  121. // update min max
  122. // 1. Pick 2 elements(a, b), compare them. (say a > b)
  123. min = Math.min(min, max)
  124. max = Math.max(min, max)
  125. } else {
  126. // 2. Update min by comparing (min, b)
  127. // 3. Update max by comparing (max, a)
  128. min = Math.min(val, min)
  129. max = Math.max(val, max)
  130. }
  131. }
  132. }
  133. }
  134. return [min, max]
  135. }
  136. /**
  137. *
  138. * @param x
  139. * @private
  140. */
  141. isValid(x) {
  142. return x !== null && x !== undefined
  143. }
  144. getWrappedLongitudes() {
  145. let xmin = this.xmin
  146. let xmax = this.xmax
  147. if (this.wrappedX) {
  148. if (this.isContinuous) {
  149. xmin = -180
  150. xmax = 180
  151. } else {
  152. xmax = this.xmax - 360
  153. xmin = this.xmin - 360
  154. }
  155. }
  156. return [xmin, xmax]
  157. }
  158. contains(lon, lat) {
  159. let _a = this.getWrappedLongitudes(),
  160. xmin = _a[0],
  161. xmax = _a[1]
  162. let longitudeIn = lon >= xmin && lon <= xmax
  163. let latitudeIn
  164. if (this.deltaY >= 0) {
  165. latitudeIn = lat >= this.ymin && lat <= this.ymax
  166. } else {
  167. latitudeIn = lat >= this.ymax && lat <= this.ymin
  168. }
  169. return longitudeIn && latitudeIn
  170. }
  171. /**
  172. *
  173. * @param a
  174. * @param n
  175. * @returns {number}
  176. */
  177. floorMod(a, n) {
  178. return a - n * Math.floor(a / n)
  179. }
  180. /**
  181. *
  182. * @param lon
  183. * @param lat
  184. */
  185. getDecimalIndexes(lon, lat) {
  186. let i = this.floorMod(lon - this.xmin, 360) / this.deltaX // calculate longitude index in wrapped range [0, 360)
  187. let j = (this.ymax - lat) / this.deltaY // calculate latitude index in direction +90 to -90
  188. return [i, j]
  189. }
  190. /**
  191. * Nearest value at lon-lat coordinates
  192. *
  193. * @param lon
  194. * @param lat
  195. */
  196. valueAt(lon, lat) {
  197. if (!this.contains(lon, lat)) {
  198. return null
  199. }
  200. let indexes = this.getDecimalIndexes(lon, lat)
  201. let ii = Math.floor(indexes[0])
  202. let jj = Math.floor(indexes[1])
  203. let ci = this.clampColumnIndex(ii)
  204. let cj = this.clampRowIndex(jj)
  205. return this.valueAtIndexes(ci, cj)
  206. }
  207. /**
  208. * Get interpolated grid value lon-lat coordinates
  209. * @param lon
  210. * @param lat
  211. */
  212. interpolatedValueAt(lon, lat) {
  213. if (!this.contains(lon, lat)) {
  214. return null
  215. }
  216. let _a = this.getDecimalIndexes(lon, lat),
  217. i = _a[0],
  218. j = _a[1]
  219. return this.interpolatePoint(i, j)
  220. }
  221. hasValueAt(lon, lat) {
  222. let value = this.valueAt(lon, lat)
  223. return value !== null
  224. }
  225. /**
  226. *
  227. * @param i
  228. * @param j
  229. */
  230. interpolatePoint(i, j) {
  231. // 1 2 After converting λ and φ to fractional grid indexes i and j, we find the
  232. // fi i ci four points 'G' that enclose point (i, j). These points are at the four
  233. // | =1.4 | corners specified by the floor and ceiling of i and j. For example, given
  234. // ---G--|---G--- fj 8 i = 1.4 and j = 8.3, the four surrounding grid points are (1, 8), (2, 8),
  235. // j ___|_ . | (1, 9) and (2, 9).
  236. // =8.3 | |
  237. // ---G------G--- cj 9 Note that for wrapped grids, the first column is duplicated as the last
  238. // | | column, so the index ci can be used without taking a modulo.
  239. let indexes = this.getFourSurroundingIndexes(i, j)
  240. let fi = indexes[0],
  241. ci = indexes[1],
  242. fj = indexes[2],
  243. cj = indexes[3]
  244. let values = this.getFourSurroundingValues(fi, ci, fj, cj)
  245. if (values) {
  246. let g00 = values[0],
  247. g10 = values[1],
  248. g01 = values[2],
  249. g11 = values[3]
  250. // @ts-ignore
  251. return this.bilinearInterpolateVector(i - fi, j - fj, g00, g10, g01, g11)
  252. }
  253. return null
  254. }
  255. /**
  256. * Check the column index is inside the field,
  257. * adjusting to min or max when needed
  258. * @private
  259. * @param {Number} ii - index
  260. * @returns {Number} i - inside the allowed indexes
  261. */
  262. clampColumnIndex(ii) {
  263. let i = ii
  264. if (ii < 0) {
  265. i = 0
  266. }
  267. let maxCol = this.cols - 1
  268. if (ii > maxCol) {
  269. i = maxCol
  270. }
  271. return i
  272. }
  273. /**
  274. * Check the row index is inside the field,
  275. * adjusting to min or max when needed
  276. * @private
  277. * @param {Number} jj index
  278. * @returns {Number} j - inside the allowed indexes
  279. */
  280. clampRowIndex(jj) {
  281. let j = jj
  282. if (jj < 0) {
  283. j = 0
  284. }
  285. let maxRow = this.rows - 1
  286. if (jj > maxRow) {
  287. j = maxRow
  288. }
  289. return j
  290. }
  291. /**
  292. * from: https://github.com/IHCantabria/Leaflet.CanvasLayer.Field/blob/master/src/Field.js#L252
  293. * @private
  294. * @param {Number} i - decimal index
  295. * @param {Number} j - decimal index
  296. * @returns {Array} [fi, ci, fj, cj]
  297. */
  298. getFourSurroundingIndexes(i, j) {
  299. let fi = Math.floor(i) // 左
  300. let ci = fi + 1 // 右
  301. // duplicate colum to simplify interpolation logic (wrapped value)
  302. if (this.isContinuous && ci >= this.cols) {
  303. ci = 0
  304. }
  305. ci = this.clampColumnIndex(ci)
  306. let fj = this.clampRowIndex(Math.floor(j)) // 上 纬度方向索引(取整)
  307. let cj = this.clampRowIndex(fj + 1) // 下
  308. return [fi, ci, fj, cj]
  309. }
  310. /**
  311. * from https://github.com/IHCantabria/Leaflet.CanvasLayer.Field/blob/master/src/Field.js#L277
  312. * Get four surrounding values or null if not available,
  313. * from 4 integer indexes
  314. * @private
  315. * @param {Number} fi
  316. * @param {Number} ci
  317. * @param {Number} fj
  318. * @param {Number} cj
  319. * @returns {Array}
  320. */
  321. getFourSurroundingValues(fi, ci, fj, cj) {
  322. let row
  323. if ((row = this.grid[fj])) {
  324. let g00 = row[fi] // << left
  325. let g10 = row[ci] // right >>
  326. if (this.isValid(g00) && this.isValid(g10) && (row = this.grid[cj])) {
  327. // lower row vv
  328. let g01 = row[fi] // << left
  329. let g11 = row[ci] // right >>
  330. if (this.isValid(g01) && this.isValid(g11)) {
  331. return [g00, g10, g01, g11] // 4 values found!
  332. }
  333. }
  334. }
  335. return null
  336. }
  337. /**
  338. * Value for grid indexes
  339. * @param {Number} i - column index (integer)
  340. * @param {Number} j - row index (integer)
  341. * @returns {Vector|Number}
  342. */
  343. valueAtIndexes(i, j) {
  344. return this.grid[j][i] // <-- j,i !!
  345. }
  346. /**
  347. * Lon-Lat for grid indexes
  348. * @param {Number} i - column index (integer)
  349. * @param {Number} j - row index (integer)
  350. * @returns {Number[]} [lon, lat]
  351. */
  352. lonLatAtIndexes(i, j) {
  353. let lon = this.longitudeAtX(i)
  354. let lat = this.latitudeAtY(j)
  355. return [lon, lat]
  356. }
  357. /**
  358. * Longitude for grid-index
  359. * @param {Number} i - column index (integer)
  360. * @returns {Number} longitude at the center of the cell
  361. */
  362. longitudeAtX(i) {
  363. let halfXPixel = this.deltaX / 2.0
  364. let lon = this.xmin + halfXPixel + i * this.deltaX
  365. if (this.wrappedX) {
  366. lon = lon > 180 ? lon - 360 : lon
  367. }
  368. return lon
  369. }
  370. /**
  371. * Latitude for grid-index
  372. * @param j
  373. * @returns {number}
  374. */
  375. latitudeAtY(j) {
  376. let halfYPixel = this.deltaY / 2.0
  377. return this.ymax - halfYPixel - j * this.deltaY
  378. }
  379. /**
  380. *
  381. * @param o
  382. * @param width
  383. * @param height
  384. * @param unproject
  385. * @returns {{}}
  386. */
  387. randomize(o, width, height, unproject) {
  388. if (o === void 0) {
  389. o = {}
  390. }
  391. let i = (Math.random() * (width || this.cols)) | 0
  392. let j = (Math.random() * (height || this.rows)) | 0
  393. let coords = unproject([i, j])
  394. if (coords !== null) {
  395. o.x = coords[0]
  396. o.y = coords[1]
  397. } else {
  398. o.x = this.longitudeAtX(i)
  399. o.y = this.latitudeAtY(j)
  400. }
  401. return o
  402. }
  403. /**
  404. * check is custom field
  405. */
  406. checkFields() {
  407. return this.isFields
  408. }
  409. }
  410. export default Field