mirror of
https://github.com/jiawanlong/Cesium-Examples.git
synced 2025-07-06 16:31:03 +00:00
907 lines
26 KiB
JavaScript
907 lines
26 KiB
JavaScript
![]() |
export const waveData = {
|
||
|
TRANSPARENT_BLACK: [0, 0, 0, 0],
|
||
|
HOLE_VECTOR: [NaN, NaN, null],
|
||
|
NULL_WIND_VECTOR: [NaN, NaN, null],
|
||
|
|
||
|
SECOND: 1000,
|
||
|
MINUTE: 60 * 1000,
|
||
|
HOUR: 60 * 60 * 1000,
|
||
|
MAX_TASK_TIME: 10,
|
||
|
RTOD: 57.29578049044297,
|
||
|
|
||
|
T: 2 * Math.PI,
|
||
|
H: 0.000036, // 0.0000360°lat_p ~: 4m
|
||
|
OVERLAY_ALPHA: Math.floor(1 * 255),
|
||
|
MIN_SLEEP_TIME: 2,
|
||
|
|
||
|
//每帧调用
|
||
|
INTENSITY_SCALE_STEP: 10,
|
||
|
maxIntensity: 180,
|
||
|
PARTICLE_MULTIPLIER: 1 / 300,
|
||
|
INTERPOLATEDIV: 2,
|
||
|
// velocityScale_o: 0.000016666666666666667 * 0.03,
|
||
|
velocityScale_o: 0.0003,
|
||
|
|
||
|
// MAX_PARTICLE_AGE: 20,
|
||
|
// fadeFillStyle: "rgba(0, 0, 0, 0.95)",
|
||
|
// PARTICLE_LINEWIDTH: 1,
|
||
|
// WindScale: 1.0 * 60,
|
||
|
|
||
|
// WindScale: 1.0 * 15,
|
||
|
// speedValue: 1.0,
|
||
|
// MAX_PARTICLE_AGE: 200,
|
||
|
// fadeFillStyle: 'rgba(0, 0, 0, 0.85)',
|
||
|
// PARTICLE_LINEWIDTH: 7,
|
||
|
WindScale: 0.8 * 15,
|
||
|
speedValue: 0.4,
|
||
|
MAX_PARTICLE_AGE: 200,
|
||
|
fadeFillStyle: 'rgba(211, 211, 211, 0.87)',
|
||
|
PARTICLE_LINEWIDTH: 5,
|
||
|
|
||
|
fadeToWhite: colorInterpolator(sinebowColor(1.0, 0), [255, 255, 255]),
|
||
|
floorMod: function (a, n) {
|
||
|
var f = a - n * Math.floor(a / n)
|
||
|
// HACK: when a is extremely close to an n transition, f can be equal to n. This is bad because f must be
|
||
|
// within range [0, n). Check for this corner case. Example: a:=-1e-16, n:=10. What is the proper fix?
|
||
|
return f === n ? 0 : f
|
||
|
},
|
||
|
|
||
|
isValue: function (x) {
|
||
|
return x !== null && x !== undefined
|
||
|
},
|
||
|
|
||
|
bilinearInterpolateVector: function (x, y, g00, g10, g01, g11) {
|
||
|
var rx = 1 - x
|
||
|
var ry = 1 - y
|
||
|
var a = rx * ry,
|
||
|
b = x * ry,
|
||
|
c = rx * y,
|
||
|
d = x * y
|
||
|
var u = g00[0] * a + g10[0] * b + g01[0] * c + g11[0] * d
|
||
|
var v = g00[1] * a + g10[1] * b + g01[1] * c + g11[1] * d
|
||
|
return [u, v, Math.sqrt(u * u + v * v)]
|
||
|
},
|
||
|
|
||
|
buildGrid: function (builder) {
|
||
|
var header = builder.header
|
||
|
var lng1 = header.lo1,
|
||
|
lat1 = header.la1 // the grid's origin (e.g., 0.0E, 90.0N)
|
||
|
var dx1 = header.dx,
|
||
|
dy1 = header.dy // distance between grid points (e.g., 2.5 deg lon, 2.5 deg lat)
|
||
|
var ni = header.nx,
|
||
|
nj = header.ny // number of grid points W-E and N-S (e.g., 144 x 73)
|
||
|
// var date = new Date(header.refTime);
|
||
|
// date.setHours(date.getHours() + header.forecastTime);
|
||
|
|
||
|
// Scan mode 0 assumed. Longitude increases from lng1, and latitude decreases from lat1.
|
||
|
// http://www.nco.ncep.noaa.gov/pmb/docs/grib2/grib2_table3-4.shtml
|
||
|
var grid = [],
|
||
|
p = 0
|
||
|
var isContinuous = Math.floor(ni * dx1) >= 360
|
||
|
for (var j = 0; j < nj; j++) {
|
||
|
var row = []
|
||
|
for (var i = 0; i < ni; i++, p++) {
|
||
|
row[i] = builder.data(p)
|
||
|
}
|
||
|
if (isContinuous) {
|
||
|
// For wrapped grids, duplicate first column as last column to simplify interpolation logic
|
||
|
row.push(row[0])
|
||
|
}
|
||
|
grid[j] = row
|
||
|
}
|
||
|
|
||
|
this.interpolate = function (lng_p, lat_p) {
|
||
|
var i = waveData.floorMod(lng_p - lng1, 360) / dx1 // calculate longitude index in wrapped range [0, 360)
|
||
|
var j = (lat1 - lat_p) / dy1 // calculate latitude index in direction +90 to -90
|
||
|
|
||
|
// 1 2 After converting lng_p and lat_p to fractional grid indexes i and j, we find the
|
||
|
// fi i ci four points "G" that enclose point (i, j). These points are at the four
|
||
|
// | =1.4 | corners specified by the floor and ceiling of i and j. For example, given
|
||
|
// ---G--|---G--- fj 8 i = 1.4 and j = 8.3, the four surrounding grid points are (1, 8), (2, 8),
|
||
|
// j ___|_ . | (1, 9) and (2, 9).
|
||
|
// =8.3 | |
|
||
|
// ---G------G--- cj 9 Note that for wrapped grids, the first column is duplicated as the last
|
||
|
// | | column, so the index ci can be used without taking a modulo.
|
||
|
|
||
|
var fi = Math.floor(i),
|
||
|
ci = fi + 1
|
||
|
var fj = Math.floor(j),
|
||
|
cj = fj + 1
|
||
|
|
||
|
var row
|
||
|
if ((row = grid[fj])) {
|
||
|
var g00 = row[fi]
|
||
|
var g10 = row[ci]
|
||
|
if (
|
||
|
waveData.isValue(g00) &&
|
||
|
waveData.isValue(g10) &&
|
||
|
(row = grid[cj])
|
||
|
) {
|
||
|
var g01 = row[fi]
|
||
|
var g11 = row[ci]
|
||
|
if (waveData.isValue(g01) && waveData.isValue(g11)) {
|
||
|
// All four points found, so interpolate the value.
|
||
|
return builder.interpolate(i - fi, j - fj, g00, g10, g01, g11)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
// console.log("cannot interpolate: " + lng_p + "," + lat_p + ": " + fi + " " + ci + " " + fj + " " + cj);
|
||
|
return null
|
||
|
}
|
||
|
},
|
||
|
|
||
|
multiplyComponents: function (v1, v2) {
|
||
|
return { x: v1.x * v2.x, y: v1.y * v2.y, z: v1.z * v2.z }
|
||
|
},
|
||
|
|
||
|
magnitudeSquared: function (v) {
|
||
|
return v.x * v.x + v.y * v.y + v.z * v.z
|
||
|
},
|
||
|
|
||
|
dot: function (v1, v2) {
|
||
|
return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z
|
||
|
},
|
||
|
|
||
|
multiplyByScalar: function (v, s) {
|
||
|
return { x: v.x * s, y: v.y * s, z: v.z * s }
|
||
|
},
|
||
|
|
||
|
magnitude: function (v) {
|
||
|
return Math.sqrt(this.magnitudeSquared(v))
|
||
|
},
|
||
|
|
||
|
normalize: function (cartesian) {
|
||
|
var result = {}
|
||
|
var magnitude = this.magnitude(cartesian)
|
||
|
result.x = cartesian.x / magnitude
|
||
|
result.y = cartesian.y / magnitude
|
||
|
result.z = cartesian.z / magnitude
|
||
|
if (isNaN(result.x) || isNaN(result.y) || isNaN(result.z)) {
|
||
|
throw new DeveloperError('normalized result is not a number')
|
||
|
}
|
||
|
return result
|
||
|
},
|
||
|
|
||
|
subtract: function (left, right) {
|
||
|
var result = {}
|
||
|
result.x = left.x - right.x
|
||
|
result.y = left.y - right.y
|
||
|
result.z = left.z - right.z
|
||
|
return result
|
||
|
},
|
||
|
|
||
|
add: function (left, right) {
|
||
|
var result = {}
|
||
|
result.x = left.x + right.x
|
||
|
result.y = left.y + right.y
|
||
|
result.z = left.z + right.z
|
||
|
return result
|
||
|
},
|
||
|
|
||
|
divideByScalar: function (cartesian, scalar) {
|
||
|
var result = {}
|
||
|
result.x = cartesian.x / scalar
|
||
|
result.y = cartesian.y / scalar
|
||
|
result.z = cartesian.z / scalar
|
||
|
return result
|
||
|
},
|
||
|
|
||
|
sign: function (value) {
|
||
|
value = +value // coerce to number
|
||
|
if (value === 0 || value !== value) {
|
||
|
return value
|
||
|
}
|
||
|
return value > 0 ? 1 : -1
|
||
|
},
|
||
|
|
||
|
toRadians: function (degrees) {
|
||
|
return (degrees * Math.PI) / 180.0
|
||
|
},
|
||
|
|
||
|
rayEllipsoid: function (ray) {
|
||
|
var inverseRadii = {
|
||
|
x: 1.567855942887398e-7,
|
||
|
y: 1.567855942887398e-7,
|
||
|
z: 1.573130351105623e-7
|
||
|
}
|
||
|
var q = this.multiplyComponents(inverseRadii, ray.origin)
|
||
|
var w = this.multiplyComponents(inverseRadii, ray.direction)
|
||
|
|
||
|
var q2 = this.magnitudeSquared(q)
|
||
|
var qw = this.dot(q, w)
|
||
|
|
||
|
var difference, w2, product, discriminant, temp
|
||
|
|
||
|
if (q2 > 1.0) {
|
||
|
// Outside ellipsoid.
|
||
|
if (qw >= 0.0) {
|
||
|
// Looking outward or tangent (0 intersections).
|
||
|
return undefined
|
||
|
}
|
||
|
|
||
|
// qw < 0.0.
|
||
|
var qw2 = qw * qw
|
||
|
difference = q2 - 1.0 // Positively valued.
|
||
|
w2 = this.magnitudeSquared(w)
|
||
|
product = w2 * difference
|
||
|
|
||
|
if (qw2 < product) {
|
||
|
// Imaginary roots (0 intersections).
|
||
|
return undefined
|
||
|
} else if (qw2 > product) {
|
||
|
// Distinct roots (2 intersections).
|
||
|
discriminant = qw * qw - product
|
||
|
temp = -qw + Math.sqrt(discriminant) // Avoid cancellation.
|
||
|
var root0 = temp / w2
|
||
|
var root1 = difference / temp
|
||
|
if (root0 < root1) {
|
||
|
return { start: root0, stop: root1 }
|
||
|
}
|
||
|
|
||
|
return { start: root1, stop: root0 }
|
||
|
}
|
||
|
// qw2 == product. Repeated roots (2 intersections).
|
||
|
var root = Math.sqrt(difference / w2)
|
||
|
return { start: root, stop: root }
|
||
|
} else if (q2 < 1.0) {
|
||
|
// Inside ellipsoid (2 intersections).
|
||
|
difference = q2 - 1.0 // Negatively valued.
|
||
|
w2 = this.magnitudeSquared(w)
|
||
|
product = w2 * difference // Negatively valued.
|
||
|
|
||
|
discriminant = qw * qw - product
|
||
|
temp = -qw + Math.sqrt(discriminant) // Positively valued.
|
||
|
return { start: 0.0, stop: temp / w2 }
|
||
|
}
|
||
|
// q2 == 1.0. On ellipsoid.
|
||
|
if (qw < 0.0) {
|
||
|
// Looking inward.
|
||
|
w2 = this.magnitudeSquared(w)
|
||
|
return { start: 0.0, stop: -qw / w2 }
|
||
|
}
|
||
|
|
||
|
// qw >= 0.0. Looking outward or tangent.
|
||
|
return undefined
|
||
|
},
|
||
|
|
||
|
getPickRay: function (
|
||
|
width,
|
||
|
height,
|
||
|
fovy,
|
||
|
aspectRatio,
|
||
|
near,
|
||
|
position,
|
||
|
directionWC,
|
||
|
rightWC,
|
||
|
upWC,
|
||
|
windowPosition
|
||
|
) {
|
||
|
var tanPhi = Math.tan(fovy * 0.5)
|
||
|
var tanTheta = aspectRatio * tanPhi
|
||
|
|
||
|
var x = (2.0 / width) * windowPosition.x - 1.0
|
||
|
var y = (2.0 / height) * (height - windowPosition.y) - 1.0
|
||
|
|
||
|
var nearCenter = this.multiplyByScalar(directionWC, near)
|
||
|
nearCenter = this.add(position, nearCenter)
|
||
|
var xDir = this.multiplyByScalar(rightWC, x * near * tanTheta)
|
||
|
var yDir = this.multiplyByScalar(upWC, y * near * tanPhi)
|
||
|
var direction = this.add(nearCenter, xDir)
|
||
|
direction = this.add(direction, yDir)
|
||
|
direction = this.subtract(direction, position)
|
||
|
direction = this.normalize(direction)
|
||
|
|
||
|
var result = {}
|
||
|
result.origin = position
|
||
|
result.direction = direction
|
||
|
return result
|
||
|
},
|
||
|
|
||
|
pickEllipsoid: function (params, windowPosition) {
|
||
|
var ray = this.getPickRay(
|
||
|
params.clientWidth,
|
||
|
params.clientHeight,
|
||
|
params.fovy,
|
||
|
params.aspectRatio,
|
||
|
params.near,
|
||
|
params.positionWC,
|
||
|
params.directionWC,
|
||
|
params.rightWC,
|
||
|
params.upWC,
|
||
|
windowPosition
|
||
|
)
|
||
|
var intersection = this.rayEllipsoid(ray)
|
||
|
if (!intersection) {
|
||
|
return null
|
||
|
}
|
||
|
var t = intersection.start > 0.0 ? intersection.start : intersection.stop
|
||
|
var result = this.multiplyByScalar(ray.direction, t)
|
||
|
return this.add(ray.origin, result)
|
||
|
},
|
||
|
|
||
|
scaleToGeodeticSurface: function (cartesian) {
|
||
|
var positionX = cartesian.x
|
||
|
var positionY = cartesian.y
|
||
|
var positionZ = cartesian.z
|
||
|
|
||
|
var oneOverRadiiX = 1.567855942887398e-7
|
||
|
var oneOverRadiiY = 1.567855942887398e-7
|
||
|
var oneOverRadiiZ = 1.573130351105623e-7
|
||
|
|
||
|
var x2 = positionX * positionX * oneOverRadiiX * oneOverRadiiX
|
||
|
var y2 = positionY * positionY * oneOverRadiiY * oneOverRadiiY
|
||
|
var z2 = positionZ * positionZ * oneOverRadiiZ * oneOverRadiiZ
|
||
|
|
||
|
// Compute the squared ellipsoid norm.
|
||
|
var squaredNorm = x2 + y2 + z2
|
||
|
var ratio = Math.sqrt(1.0 / squaredNorm)
|
||
|
|
||
|
// As an initial approximation, assume that the radial intersection is the projection point.
|
||
|
var intersection = this.multiplyByScalar(cartesian, ratio)
|
||
|
|
||
|
// If the position is near the center, the iteration will not converge.
|
||
|
var centerToleranceSquared = 0.1
|
||
|
if (squaredNorm < centerToleranceSquared) {
|
||
|
return !isFinite(ratio) ? undefined : intersection
|
||
|
}
|
||
|
|
||
|
var oneOverRadiiSquaredX = 2.458172257647332e-14
|
||
|
var oneOverRadiiSquaredY = 2.458172257647332e-14
|
||
|
var oneOverRadiiSquaredZ = 2.4747391015697002e-14
|
||
|
|
||
|
// Use the gradient at the intersection point in place of the true unit normal.
|
||
|
// The difference in magnitude will be absorbed in the multiplier.
|
||
|
var gradient = {}
|
||
|
gradient.x = intersection.x * oneOverRadiiSquaredX * 2.0
|
||
|
gradient.y = intersection.y * oneOverRadiiSquaredY * 2.0
|
||
|
gradient.z = intersection.z * oneOverRadiiSquaredZ * 2.0
|
||
|
|
||
|
// Compute the initial guess at the normal vector multiplier, lambda.
|
||
|
var lambda =
|
||
|
((1.0 - ratio) * this.magnitude(cartesian)) /
|
||
|
(0.5 * this.magnitude(gradient))
|
||
|
var correction = 0.0
|
||
|
|
||
|
var func
|
||
|
var denominator
|
||
|
var xMultiplier
|
||
|
var yMultiplier
|
||
|
var zMultiplier
|
||
|
var xMultiplier2
|
||
|
var yMultiplier2
|
||
|
var zMultiplier2
|
||
|
var xMultiplier3
|
||
|
var yMultiplier3
|
||
|
var zMultiplier3
|
||
|
|
||
|
do {
|
||
|
lambda -= correction
|
||
|
|
||
|
xMultiplier = 1.0 / (1.0 + lambda * oneOverRadiiSquaredX)
|
||
|
yMultiplier = 1.0 / (1.0 + lambda * oneOverRadiiSquaredY)
|
||
|
zMultiplier = 1.0 / (1.0 + lambda * oneOverRadiiSquaredZ)
|
||
|
|
||
|
xMultiplier2 = xMultiplier * xMultiplier
|
||
|
yMultiplier2 = yMultiplier * yMultiplier
|
||
|
zMultiplier2 = zMultiplier * zMultiplier
|
||
|
|
||
|
xMultiplier3 = xMultiplier2 * xMultiplier
|
||
|
yMultiplier3 = yMultiplier2 * yMultiplier
|
||
|
zMultiplier3 = zMultiplier2 * zMultiplier
|
||
|
|
||
|
func = x2 * xMultiplier2 + y2 * yMultiplier2 + z2 * zMultiplier2 - 1.0
|
||
|
|
||
|
// "denominator" here refers to the use of this expression in the velocity and acceleration
|
||
|
// computations in the sections to follow.
|
||
|
denominator =
|
||
|
x2 * xMultiplier3 * oneOverRadiiSquaredX +
|
||
|
y2 * yMultiplier3 * oneOverRadiiSquaredY +
|
||
|
z2 * zMultiplier3 * oneOverRadiiSquaredZ
|
||
|
|
||
|
var derivative = -2.0 * denominator
|
||
|
|
||
|
correction = func / derivative
|
||
|
} while (Math.abs(func) > 0.000000000001)
|
||
|
|
||
|
var result = {}
|
||
|
result.x = positionX * xMultiplier
|
||
|
result.y = positionY * yMultiplier
|
||
|
result.z = positionZ * zMultiplier
|
||
|
return result
|
||
|
},
|
||
|
|
||
|
geodeticSurfaceNormal: function (v) {
|
||
|
var oneOverRadiiSquared = {
|
||
|
x: 2.458172257647332e-14,
|
||
|
y: 2.458172257647332e-14,
|
||
|
z: 2.4747391015697002e-14
|
||
|
}
|
||
|
var result = this.multiplyComponents(v, oneOverRadiiSquared)
|
||
|
return this.normalize(result)
|
||
|
},
|
||
|
|
||
|
cartesianToCartographic: function (cartesian) {
|
||
|
var p = this.scaleToGeodeticSurface(cartesian)
|
||
|
if (p == null) {
|
||
|
return undefined
|
||
|
}
|
||
|
|
||
|
var n = this.geodeticSurfaceNormal(p)
|
||
|
var h = this.subtract(cartesian, p)
|
||
|
|
||
|
var longitude = Math.atan2(n.y, n.x)
|
||
|
var latitude = Math.asin(n.z)
|
||
|
var height = this.sign(this.dot(h, cartesian)) * this.magnitude(h)
|
||
|
|
||
|
var result = {}
|
||
|
result.longitude = longitude
|
||
|
result.latitude = latitude
|
||
|
result.height = height
|
||
|
return result
|
||
|
},
|
||
|
|
||
|
//屏幕坐标转经纬度,返回值为角度
|
||
|
ScreenToLonlat: function (params, pos) {
|
||
|
var resultRay = this.pickEllipsoid(params, pos)
|
||
|
if (resultRay == undefined) {
|
||
|
return null
|
||
|
} else {
|
||
|
var cartographic = this.cartesianToCartographic(resultRay)
|
||
|
cartographic.longitude *= waveData.RTOD
|
||
|
cartographic.latitude *= waveData.RTOD
|
||
|
return cartographic
|
||
|
}
|
||
|
},
|
||
|
createField: function (columns, bounds, mask) {
|
||
|
/**
|
||
|
* @returns {Array} wind vector [u, v, magnitude] at the point (x, y), or [NaN, NaN, null] if wind
|
||
|
* is undefined at that point.
|
||
|
*/
|
||
|
function field(x, y) {
|
||
|
var column = columns[Math.round(x)]
|
||
|
return (column && column[Math.round(y)]) || waveData.NULL_WIND_VECTOR
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @returns {boolean} true if the field is valid at the point (x, y)
|
||
|
*/
|
||
|
field.isDefined = function (x, y) {
|
||
|
return field(x, y)[2] !== null
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @returns {boolean} true if the point (x, y) lies inside the outer boundary of the vector field, even if
|
||
|
* the vector field has a hole (is undefined) at that point, such as at an island in a field of
|
||
|
* ocean currents.
|
||
|
*/
|
||
|
field.isInsideBoundary = function (x, y) {
|
||
|
return field(x, y) !== waveData.NULL_WIND_VECTOR
|
||
|
}
|
||
|
|
||
|
// Frees the massive "columns" array for GC. Without this, the array is leaked (in Chrome) each time a new
|
||
|
// field is interpolated because the field closure's context is leaked, for reasons that defy explanation.
|
||
|
field.release = function () {
|
||
|
columns = []
|
||
|
}
|
||
|
|
||
|
field.randomize = function (o) {
|
||
|
// UNDONE: this method is terrible
|
||
|
var x, y
|
||
|
var safetyNet = 0
|
||
|
do {
|
||
|
x = Math.round(waveData.random(bounds.x, bounds.xMax))
|
||
|
y = Math.round(waveData.random(bounds.y, bounds.yMax))
|
||
|
} while (!field.isDefined(x, y) && safetyNet++ < 30)
|
||
|
o.x = x
|
||
|
o.y = y
|
||
|
return o
|
||
|
}
|
||
|
|
||
|
return field
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Interpolates a sinebow color where 0 <= i <= j, then fades to white where j < i <= 1.
|
||
|
*
|
||
|
* @param i number in the range [0, 1]
|
||
|
* @param a alpha value in range [0, 255]
|
||
|
* @returns {Array} [r, g, b, a]
|
||
|
*/
|
||
|
extendedSinebowColor: function (i, a) {
|
||
|
return i <= waveData.BOUNDARY
|
||
|
? sinebowColor((i / waveData.BOUNDARY) * 2, a)
|
||
|
: waveData.fadeToWhite(
|
||
|
(i - waveData.BOUNDARY) / (1 - waveData.BOUNDARY),
|
||
|
a
|
||
|
)
|
||
|
},
|
||
|
|
||
|
geodeticSurfaceNormalCartographic: function (cartographic) {
|
||
|
var longitude = cartographic.longitude
|
||
|
var latitude = cartographic.latitude
|
||
|
var cosLatitude = Math.cos(latitude)
|
||
|
|
||
|
var x = cosLatitude * Math.cos(longitude)
|
||
|
var y = cosLatitude * Math.sin(longitude)
|
||
|
var z = Math.sin(latitude)
|
||
|
|
||
|
var result = {}
|
||
|
result.x = x
|
||
|
result.y = y
|
||
|
result.z = z
|
||
|
return this.normalize(result)
|
||
|
},
|
||
|
|
||
|
cartographicToCartesian: function (cartographic) {
|
||
|
var radiiSquared = {
|
||
|
x: 40680631590769,
|
||
|
y: 40680631590769,
|
||
|
z: 40408299984661.445
|
||
|
}
|
||
|
var n = this.geodeticSurfaceNormalCartographic(cartographic)
|
||
|
var k = this.multiplyComponents(radiiSquared, n)
|
||
|
var gamma = Math.sqrt(this.dot(n, k))
|
||
|
k = this.divideByScalar(k, gamma)
|
||
|
n = this.multiplyByScalar(n, cartographic.height)
|
||
|
return this.add(k, n)
|
||
|
},
|
||
|
|
||
|
multiplyByVector: function (matrix, cartesian) {
|
||
|
var vX = cartesian.x
|
||
|
var vY = cartesian.y
|
||
|
var vZ = cartesian.z
|
||
|
var vW = cartesian.w
|
||
|
|
||
|
var x = matrix[0] * vX + matrix[4] * vY + matrix[8] * vZ + matrix[12] * vW
|
||
|
var y = matrix[1] * vX + matrix[5] * vY + matrix[9] * vZ + matrix[13] * vW
|
||
|
var z = matrix[2] * vX + matrix[6] * vY + matrix[10] * vZ + matrix[14] * vW
|
||
|
var w = matrix[3] * vX + matrix[7] * vY + matrix[11] * vZ + matrix[15] * vW
|
||
|
|
||
|
var result = {}
|
||
|
result.x = x
|
||
|
result.y = y
|
||
|
result.z = z
|
||
|
result.w = w
|
||
|
return result
|
||
|
},
|
||
|
|
||
|
multiplyByPoint: function (matrix, cartesian) {
|
||
|
var vX = cartesian.x
|
||
|
var vY = cartesian.y
|
||
|
var vZ = cartesian.z
|
||
|
|
||
|
var x = matrix[0] * vX + matrix[4] * vY + matrix[8] * vZ + matrix[12]
|
||
|
var y = matrix[1] * vX + matrix[5] * vY + matrix[9] * vZ + matrix[13]
|
||
|
var z = matrix[2] * vX + matrix[6] * vY + matrix[10] * vZ + matrix[14]
|
||
|
|
||
|
var result = {}
|
||
|
result.x = x
|
||
|
result.y = y
|
||
|
result.z = z
|
||
|
return result
|
||
|
},
|
||
|
|
||
|
computeViewportTransformation: function (
|
||
|
viewport,
|
||
|
nearDepthRange,
|
||
|
farDepthRange
|
||
|
) {
|
||
|
var x = viewport.x
|
||
|
var y = viewport.y
|
||
|
var width = viewport.width
|
||
|
var height = viewport.height
|
||
|
|
||
|
var halfWidth = width * 0.5
|
||
|
var halfHeight = height * 0.5
|
||
|
var halfDepth = (farDepthRange - nearDepthRange) * 0.5
|
||
|
|
||
|
var column0Row0 = halfWidth
|
||
|
var column1Row1 = halfHeight
|
||
|
var column2Row2 = halfDepth
|
||
|
var column3Row0 = x + halfWidth
|
||
|
var column3Row1 = y + halfHeight
|
||
|
var column3Row2 = nearDepthRange + halfDepth
|
||
|
var column3Row3 = 1.0
|
||
|
|
||
|
var result = {}
|
||
|
result[0] = column0Row0
|
||
|
result[1] = 0.0
|
||
|
result[2] = 0.0
|
||
|
result[3] = 0.0
|
||
|
result[4] = 0.0
|
||
|
result[5] = column1Row1
|
||
|
result[6] = 0.0
|
||
|
result[7] = 0.0
|
||
|
result[8] = 0.0
|
||
|
result[9] = 0.0
|
||
|
result[10] = column2Row2
|
||
|
result[11] = 0.0
|
||
|
result[12] = column3Row0
|
||
|
result[13] = column3Row1
|
||
|
result[14] = column3Row2
|
||
|
result[15] = column3Row3
|
||
|
return result
|
||
|
},
|
||
|
|
||
|
worldToClip: function (position, eyeOffset, viewMatrix, projectionMatrix) {
|
||
|
var positionEC = this.multiplyByVector(viewMatrix, {
|
||
|
x: position.x,
|
||
|
y: position.y,
|
||
|
z: position.z,
|
||
|
w: 1
|
||
|
})
|
||
|
|
||
|
var zEyeOffset = this.multiplyComponents(
|
||
|
eyeOffset,
|
||
|
this.normalize(positionEC)
|
||
|
)
|
||
|
positionEC.x += eyeOffset.x + zEyeOffset.x
|
||
|
positionEC.y += eyeOffset.y + zEyeOffset.y
|
||
|
positionEC.z += zEyeOffset.z
|
||
|
|
||
|
return this.multiplyByVector(projectionMatrix, positionEC)
|
||
|
},
|
||
|
|
||
|
clipToGLWindowCoordinates: function (viewport, position) {
|
||
|
// Perspective divide to transform from clip coordinates to normalized device coordinates
|
||
|
var positionNDC = this.divideByScalar(position, position.w)
|
||
|
|
||
|
// Viewport transform to transform from clip coordinates to window coordinates
|
||
|
var viewportTransform = this.computeViewportTransformation(
|
||
|
viewport,
|
||
|
0.0,
|
||
|
1.0
|
||
|
)
|
||
|
var positionWC = this.multiplyByPoint(viewportTransform, positionNDC)
|
||
|
|
||
|
return { x: positionWC.x, y: positionWC.y }
|
||
|
},
|
||
|
|
||
|
wgs84ToWindowCoordinates: function (
|
||
|
x,
|
||
|
y,
|
||
|
width,
|
||
|
height,
|
||
|
viewMatrix,
|
||
|
projectionMatrix,
|
||
|
position,
|
||
|
eyeOffset
|
||
|
) {
|
||
|
var viewport = {}
|
||
|
viewport.x = x
|
||
|
viewport.y = y
|
||
|
viewport.width = width
|
||
|
viewport.height = height
|
||
|
|
||
|
// View-projection matrix to transform from world coordinates to clip coordinates
|
||
|
var positionCC = this.worldToClip(
|
||
|
position,
|
||
|
eyeOffset,
|
||
|
viewMatrix,
|
||
|
projectionMatrix
|
||
|
)
|
||
|
if (positionCC.z < 0) {
|
||
|
return undefined
|
||
|
}
|
||
|
|
||
|
var result = this.clipToGLWindowCoordinates(viewport, positionCC)
|
||
|
result.y = height - result.y
|
||
|
return result
|
||
|
},
|
||
|
|
||
|
//屏幕坐标转经纬度,返回值为角度
|
||
|
LonlatToScreen: function (params, pos) {
|
||
|
var position = {
|
||
|
longitude: this.toRadians(pos[0]),
|
||
|
latitude: this.toRadians(pos[1]),
|
||
|
height: 0.0
|
||
|
}
|
||
|
var cartesianPosition = this.cartographicToCartesian(position)
|
||
|
var posEnd = this.wgs84ToWindowCoordinates(
|
||
|
0,
|
||
|
0,
|
||
|
params.clientWidth,
|
||
|
params.clientHeight,
|
||
|
params.viewMatrix,
|
||
|
params.projectionMatrix,
|
||
|
cartesianPosition,
|
||
|
{
|
||
|
x: 0,
|
||
|
y: 0,
|
||
|
z: 0
|
||
|
}
|
||
|
)
|
||
|
return posEnd
|
||
|
},
|
||
|
|
||
|
distortion: function (params, lng_p, lat_p, x, y) {
|
||
|
var hlng = lng_p < 0 ? waveData.H : -waveData.H
|
||
|
var hlat_p = lat_p < 0 ? waveData.H : -waveData.H
|
||
|
var plng = waveData.LonlatToScreen(params, [lng_p + hlng, lat_p])
|
||
|
var plat_p = waveData.LonlatToScreen(params, [lng_p, lat_p + hlat_p])
|
||
|
|
||
|
// Meridian scale factor (see Snyder, equation 4-3), where R = 1. This handles issue where length of 1° lng_p
|
||
|
// changes depending on lat_p. Without this, there is a pinching effect at the poles.
|
||
|
var k = Math.cos((lat_p / 360) * waveData.T)
|
||
|
|
||
|
return [
|
||
|
(plng.x - x) / hlng / k,
|
||
|
(plng.y - y) / hlng / k,
|
||
|
(plat_p.x - x) / hlat_p,
|
||
|
(plat_p.y - y) / hlat_p
|
||
|
]
|
||
|
},
|
||
|
|
||
|
distort: function (params, lng_p, lat_p, x, y, scale, wind) {
|
||
|
var u = wind[0] * scale
|
||
|
var v = wind[1] * scale
|
||
|
var d = waveData.distortion(params, lng_p, lat_p, x, y)
|
||
|
|
||
|
// Scale windData.distortion vectors by u and v, then add.
|
||
|
wind[0] = d[0] * u + d[2] * v
|
||
|
wind[1] = d[1] * u + d[3] * v
|
||
|
return wind
|
||
|
},
|
||
|
|
||
|
asColorStyle: function (r, g, b, a) {
|
||
|
return 'rgba(' + r + ', ' + g + ', ' + b + ', ' + a + ')'
|
||
|
},
|
||
|
|
||
|
windIntensityColorScale: function (step, maxWind) {
|
||
|
var result = []
|
||
|
for (var j = 85; j <= 255; j += step) {
|
||
|
result.push(waveData.asColorStyle(255, 255, 255, 255.0))
|
||
|
}
|
||
|
result.indexFor = function (m) {
|
||
|
// map wind speed to a style
|
||
|
return Math.floor((Math.min(m, maxWind) / maxWind) * (result.length - 1))
|
||
|
}
|
||
|
return result
|
||
|
},
|
||
|
|
||
|
random: function (min, max) {
|
||
|
if (max == null) {
|
||
|
max = min
|
||
|
min = 0
|
||
|
}
|
||
|
return min + Math.floor(Math.random() * (max - min + 1))
|
||
|
},
|
||
|
|
||
|
CreateMask: function (width, height, offscreen) {
|
||
|
var canvas = null
|
||
|
if (offscreen) {
|
||
|
canvas = new OffscreenCanvas(width, height)
|
||
|
} else {
|
||
|
canvas = document.createElement('canvas')
|
||
|
;(canvas.width = width), (canvas.height = height)
|
||
|
}
|
||
|
var context = canvas.getContext('2d')
|
||
|
context.fillStyle = 'rgba(255, 0, 0, 0)'
|
||
|
context.fill()
|
||
|
|
||
|
var imageData = context.getImageData(0, 0, width, height)
|
||
|
var data = imageData.data // layout: [r, g, b, a, r, g, b, a, ...]
|
||
|
|
||
|
return {
|
||
|
imageData: imageData,
|
||
|
set: function (x, y, rgba) {
|
||
|
var i = (y * width + x) * 4
|
||
|
data[i] = rgba[0]
|
||
|
data[i + 1] = rgba[1]
|
||
|
data[i + 2] = rgba[2]
|
||
|
data[i + 3] = rgba[3]
|
||
|
return this
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
|
||
|
reCreate: function (params, builder, offscreen) {
|
||
|
//这个纹理重复每次生成,也可以生成一次
|
||
|
var bounds = params.bounds
|
||
|
var mask = this.CreateMask(bounds.width, bounds.height, offscreen)
|
||
|
var velocityScale = waveData.velocityScale_o
|
||
|
|
||
|
var columns = []
|
||
|
var point = {}
|
||
|
let ySize = 0
|
||
|
|
||
|
function interpolateColumn(params, x) {
|
||
|
var column = []
|
||
|
ySize = 0
|
||
|
for (var y = bounds.y; y <= bounds.yMax; y += waveData.INTERPOLATEDIV) {
|
||
|
ySize++
|
||
|
point.x = x
|
||
|
point.y = y
|
||
|
|
||
|
var coord = waveData.ScreenToLonlat(params, point)
|
||
|
var color = waveData.TRANSPARENT_BLACK
|
||
|
var wind = null
|
||
|
if (coord) {
|
||
|
var lng_p = coord.longitude,
|
||
|
lat_p = coord.latitude
|
||
|
if (isFinite(lng_p)) {
|
||
|
if (builder) {
|
||
|
wind = builder.interpolate(lng_p, lat_p)
|
||
|
var scalar = null
|
||
|
if (wind) {
|
||
|
wind = waveData.distort(
|
||
|
params,
|
||
|
lng_p,
|
||
|
lat_p,
|
||
|
x,
|
||
|
y,
|
||
|
velocityScale,
|
||
|
wind
|
||
|
)
|
||
|
scalar = wind[2]
|
||
|
}
|
||
|
if (waveData.isValue(scalar)) {
|
||
|
color = waveData.extendedSinebowColor(
|
||
|
Math.min(scalar, 100) / 100,
|
||
|
waveData.OVERLAY_ALPHA
|
||
|
)
|
||
|
}
|
||
|
} else {
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
column[y + 1] = column[y] = wind || waveData.HOLE_VECTOR
|
||
|
mask
|
||
|
.set(x, y, color)
|
||
|
.set(x + 1, y, color)
|
||
|
.set(x, y + 1, color)
|
||
|
.set(x + 1, y + 1, color)
|
||
|
}
|
||
|
columns[x + 1] = columns[x] = column
|
||
|
}
|
||
|
|
||
|
var x = bounds.x
|
||
|
while (x < bounds.xMax) {
|
||
|
interpolateColumn(params, x)
|
||
|
x += waveData.INTERPOLATEDIV
|
||
|
}
|
||
|
|
||
|
return { columns: columns, mask: mask }
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Produces a color style in a rainbow-like trefoil color space. Not quite HSV, but produces a nice
|
||
|
* spectrum. See http://krazydad.com/tutorials/makecolors.php.
|
||
|
*
|
||
|
* @param hue the hue rotation in the range [0, 1]
|
||
|
* @param a the alpha value in the range [0, 255]
|
||
|
* @returns {Array} [r, g, b, a]
|
||
|
*/
|
||
|
function sinebowColor(hue, a) {
|
||
|
// Map hue [0, 1] to radians [0, 5/6T]. Don't allow a full rotation because that keeps hue == 0 and
|
||
|
// hue == 1 from mapping to the same color.
|
||
|
var rad = (hue * 2 * Math.PI * 5) / 6
|
||
|
rad *= 0.75 // increase frequency to 2/3 cycle per rad
|
||
|
|
||
|
var s = Math.sin(rad)
|
||
|
var c = Math.cos(rad)
|
||
|
var r = Math.floor(Math.max(0, -c) * 255)
|
||
|
var g = Math.floor(Math.max(s, 0) * 255)
|
||
|
var b = Math.floor(Math.max(c, 0, -s) * 255)
|
||
|
return [r, g, b, a]
|
||
|
}
|
||
|
|
||
|
function colorInterpolator(start, end) {
|
||
|
var r = start[0],
|
||
|
g = start[1],
|
||
|
b = start[2]
|
||
|
var r2 = end[0] - r,
|
||
|
g2 = end[1] - g,
|
||
|
b2 = end[2] - b
|
||
|
return function (i, a) {
|
||
|
return [
|
||
|
Math.floor(r + i * r2),
|
||
|
Math.floor(g + i * g2),
|
||
|
Math.floor(b + i * b2),
|
||
|
a
|
||
|
]
|
||
|
}
|
||
|
}
|