Cesium-Examples/examples/cesiumEx/ocean/layers/LayerOceanWave/wave/WaveData.js
2025-03-11 17:51:04 +08:00

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
]
}
}