3440 lines
121 KiB
JavaScript
3440 lines
121 KiB
JavaScript
import {
|
|
ARRAY_BUFFER,
|
|
AttributeType,
|
|
Buffer_default,
|
|
DYNAMIC_DRAW,
|
|
DefaultUniform,
|
|
ELEMENT_ARRAY_BUFFER,
|
|
FEATURE_ID_PROPERTY_NAME,
|
|
GEOMETRY_TYPE_PROPERTY_NAME,
|
|
Layer_default,
|
|
UNDEFINED_PROP_VALUE,
|
|
applyContextToBuilder,
|
|
colorToGlsl,
|
|
expressionToGlsl,
|
|
generateAttributesFromContext,
|
|
generateUniformsFromContext,
|
|
getGlslSizeFromType,
|
|
getGlslTypeFromType,
|
|
getStringNumberEquivalent,
|
|
newCompilationContext,
|
|
numberToGlsl,
|
|
stringToGlsl
|
|
} from "./chunk-PZZAKLYX.js";
|
|
import {
|
|
create as create2,
|
|
fromTransform
|
|
} from "./chunk-L47B4DRW.js";
|
|
import {
|
|
VectorEventType_default
|
|
} from "./chunk-V7WRBSQ6.js";
|
|
import {
|
|
Feature_default
|
|
} from "./chunk-FDGVG43Y.js";
|
|
import {
|
|
BaseVector_default,
|
|
buildExpression,
|
|
newEvaluationContext
|
|
} from "./chunk-OVJRLVXU.js";
|
|
import {
|
|
BooleanType,
|
|
ColorType,
|
|
NumberArrayType,
|
|
NumberType,
|
|
SizeType,
|
|
StringType,
|
|
computeGeometryType,
|
|
newParsingContext
|
|
} from "./chunk-E5F6ZCFZ.js";
|
|
import {
|
|
ViewHint_default
|
|
} from "./chunk-S5OMZ56B.js";
|
|
import {
|
|
createCanvasContext2D
|
|
} from "./chunk-YWIWRQT2.js";
|
|
import {
|
|
inflateEnds
|
|
} from "./chunk-RBA5LKAR.js";
|
|
import {
|
|
transform2D
|
|
} from "./chunk-YUTQGDGI.js";
|
|
import {
|
|
apply,
|
|
create,
|
|
makeInverse,
|
|
multiply,
|
|
setFromArray,
|
|
translate
|
|
} from "./chunk-JFONEOYG.js";
|
|
import {
|
|
getTransformFromProjections,
|
|
getUserProjection,
|
|
toUserExtent,
|
|
toUserResolution
|
|
} from "./chunk-XZU4LSFD.js";
|
|
import {
|
|
clamp
|
|
} from "./chunk-54BTDBAD.js";
|
|
import {
|
|
buffer,
|
|
createEmpty,
|
|
equals as equals2,
|
|
getWidth
|
|
} from "./chunk-CKDBVGKM.js";
|
|
import {
|
|
assert
|
|
} from "./chunk-QFCIXVZ3.js";
|
|
import {
|
|
getUid
|
|
} from "./chunk-H47PV7W6.js";
|
|
import {
|
|
listen,
|
|
unlistenByKey
|
|
} from "./chunk-KJXIHBKT.js";
|
|
import {
|
|
equals
|
|
} from "./chunk-FQY6EMA7.js";
|
|
|
|
// node_modules/ol/style/flat.js
|
|
function createDefaultStyle() {
|
|
return {
|
|
"fill-color": "rgba(255,255,255,0.4)",
|
|
"stroke-color": "#3399CC",
|
|
"stroke-width": 1.25,
|
|
"circle-radius": 5,
|
|
"circle-fill-color": "rgba(255,255,255,0.4)",
|
|
"circle-stroke-width": 1.25,
|
|
"circle-stroke-color": "#3399CC"
|
|
};
|
|
}
|
|
|
|
// node_modules/ol/render/webgl/bufferUtil.js
|
|
var LINESTRING_ANGLE_COSINE_CUTOFF = 0.985;
|
|
|
|
// node_modules/ol/render/webgl/ShaderBuilder.js
|
|
var COMMON_HEADER = `#ifdef GL_FRAGMENT_PRECISION_HIGH
|
|
precision highp float;
|
|
#else
|
|
precision mediump float;
|
|
#endif
|
|
uniform mat4 u_projectionMatrix;
|
|
uniform mat4 u_screenToWorldMatrix;
|
|
uniform vec2 u_viewportSizePx;
|
|
uniform float u_pixelRatio;
|
|
uniform float u_globalAlpha;
|
|
uniform float u_time;
|
|
uniform float u_zoom;
|
|
uniform float u_resolution;
|
|
uniform float u_rotation;
|
|
uniform vec4 u_renderExtent;
|
|
uniform vec2 u_patternOrigin;
|
|
uniform float u_depth;
|
|
uniform mediump int u_hitDetection;
|
|
|
|
const float PI = 3.141592653589793238;
|
|
const float TWO_PI = 2.0 * PI;
|
|
float currentLineMetric = 0.; // an actual value will be used in the stroke shaders
|
|
`;
|
|
var DEFAULT_STYLE = createDefaultStyle();
|
|
var ShaderBuilder = class {
|
|
constructor() {
|
|
this.uniforms_ = [];
|
|
this.attributes_ = [];
|
|
this.hasSymbol_ = false;
|
|
this.symbolSizeExpression_ = `vec2(${numberToGlsl(
|
|
DEFAULT_STYLE["circle-radius"]
|
|
)} + ${numberToGlsl(DEFAULT_STYLE["circle-stroke-width"] * 0.5)})`;
|
|
this.symbolRotationExpression_ = "0.0";
|
|
this.symbolOffsetExpression_ = "vec2(0.0)";
|
|
this.symbolColorExpression_ = colorToGlsl(
|
|
/** @type {string} */
|
|
DEFAULT_STYLE["circle-fill-color"]
|
|
);
|
|
this.texCoordExpression_ = "vec4(0.0, 0.0, 1.0, 1.0)";
|
|
this.discardExpression_ = "false";
|
|
this.symbolRotateWithView_ = false;
|
|
this.hasStroke_ = false;
|
|
this.strokeWidthExpression_ = numberToGlsl(DEFAULT_STYLE["stroke-width"]);
|
|
this.strokeColorExpression_ = colorToGlsl(
|
|
/** @type {string} */
|
|
DEFAULT_STYLE["stroke-color"]
|
|
);
|
|
this.strokeOffsetExpression_ = "0.";
|
|
this.strokeCapExpression_ = stringToGlsl("round");
|
|
this.strokeJoinExpression_ = stringToGlsl("round");
|
|
this.strokeMiterLimitExpression_ = "10.";
|
|
this.strokeDistanceFieldExpression_ = "-1000.";
|
|
this.hasFill_ = false;
|
|
this.fillColorExpression_ = colorToGlsl(
|
|
/** @type {string} */
|
|
DEFAULT_STYLE["fill-color"]
|
|
);
|
|
this.vertexShaderFunctions_ = [];
|
|
this.fragmentShaderFunctions_ = [];
|
|
}
|
|
/**
|
|
* Adds a uniform accessible in both fragment and vertex shaders.
|
|
* The given name should include a type, such as `sampler2D u_texture`.
|
|
* @param {string} name Uniform name, including the `u_` prefix
|
|
* @param {'float'|'vec2'|'vec3'|'vec4'|'sampler2D'} type GLSL type
|
|
* @return {ShaderBuilder} the builder object
|
|
*/
|
|
addUniform(name, type) {
|
|
this.uniforms_.push({
|
|
name,
|
|
type
|
|
});
|
|
return this;
|
|
}
|
|
/**
|
|
* Adds an attribute accessible in the vertex shader, read from the geometry buffer.
|
|
* The given name should include a type, such as `vec2 a_position`.
|
|
* Attributes will also be made available under the same name in fragment shaders.
|
|
* @param {string} name Attribute name, including the `a_` prefix
|
|
* @param {'float'|'vec2'|'vec3'|'vec4'} type GLSL type
|
|
* @param {string} [varyingExpression] Expression which will be assigned to the varying in the vertex shader, and
|
|
* passed on to the fragment shader.
|
|
* @param {'float'|'vec2'|'vec3'|'vec4'} [varyingType] Type of the attribute after transformation;
|
|
* e.g. `vec4` after unpacking color components
|
|
* @return {ShaderBuilder} the builder object
|
|
*/
|
|
addAttribute(name, type, varyingExpression, varyingType) {
|
|
this.attributes_.push({
|
|
name,
|
|
type,
|
|
varyingName: name.replace(/^a_/, "v_"),
|
|
varyingType: varyingType ?? type,
|
|
varyingExpression: varyingExpression ?? name
|
|
});
|
|
return this;
|
|
}
|
|
/**
|
|
* Sets an expression to compute the size of the shape.
|
|
* This expression can use all the uniforms and attributes available
|
|
* in the vertex shader, and should evaluate to a `vec2` value.
|
|
* @param {string} expression Size expression
|
|
* @return {ShaderBuilder} the builder object
|
|
*/
|
|
setSymbolSizeExpression(expression) {
|
|
this.hasSymbol_ = true;
|
|
this.symbolSizeExpression_ = expression;
|
|
return this;
|
|
}
|
|
/**
|
|
* @return {string} The current symbol size expression
|
|
*/
|
|
getSymbolSizeExpression() {
|
|
return this.symbolSizeExpression_;
|
|
}
|
|
/**
|
|
* Sets an expression to compute the rotation of the shape.
|
|
* This expression can use all the uniforms and attributes available
|
|
* in the vertex shader, and should evaluate to a `float` value in radians.
|
|
* @param {string} expression Size expression
|
|
* @return {ShaderBuilder} the builder object
|
|
*/
|
|
setSymbolRotationExpression(expression) {
|
|
this.symbolRotationExpression_ = expression;
|
|
return this;
|
|
}
|
|
/**
|
|
* Sets an expression to compute the offset of the symbol from the point center.
|
|
* This expression can use all the uniforms and attributes available
|
|
* in the vertex shader, and should evaluate to a `vec2` value.
|
|
* @param {string} expression Offset expression
|
|
* @return {ShaderBuilder} the builder object
|
|
*/
|
|
setSymbolOffsetExpression(expression) {
|
|
this.symbolOffsetExpression_ = expression;
|
|
return this;
|
|
}
|
|
/**
|
|
* @return {string} The current symbol offset expression
|
|
*/
|
|
getSymbolOffsetExpression() {
|
|
return this.symbolOffsetExpression_;
|
|
}
|
|
/**
|
|
* Sets an expression to compute the color of the shape.
|
|
* This expression can use all the uniforms, varyings and attributes available
|
|
* in the fragment shader, and should evaluate to a `vec4` value.
|
|
* @param {string} expression Color expression
|
|
* @return {ShaderBuilder} the builder object
|
|
*/
|
|
setSymbolColorExpression(expression) {
|
|
this.hasSymbol_ = true;
|
|
this.symbolColorExpression_ = expression;
|
|
return this;
|
|
}
|
|
/**
|
|
* @return {string} The current symbol color expression
|
|
*/
|
|
getSymbolColorExpression() {
|
|
return this.symbolColorExpression_;
|
|
}
|
|
/**
|
|
* Sets an expression to compute the texture coordinates of the vertices.
|
|
* This expression can use all the uniforms and attributes available
|
|
* in the vertex shader, and should evaluate to a `vec4` value.
|
|
* @param {string} expression Texture coordinate expression
|
|
* @return {ShaderBuilder} the builder object
|
|
*/
|
|
setTextureCoordinateExpression(expression) {
|
|
this.texCoordExpression_ = expression;
|
|
return this;
|
|
}
|
|
/**
|
|
* Sets an expression to determine whether a fragment (pixel) should be discarded,
|
|
* i.e. not drawn at all.
|
|
* This expression can use all the uniforms, varyings and attributes available
|
|
* in the fragment shader, and should evaluate to a `bool` value (it will be
|
|
* used in an `if` statement)
|
|
* @param {string} expression Fragment discard expression
|
|
* @return {ShaderBuilder} the builder object
|
|
*/
|
|
setFragmentDiscardExpression(expression) {
|
|
this.discardExpression_ = expression;
|
|
return this;
|
|
}
|
|
/**
|
|
* @return {string} The current fragment discard expression
|
|
*/
|
|
getFragmentDiscardExpression() {
|
|
return this.discardExpression_;
|
|
}
|
|
/**
|
|
* Sets whether the symbols should rotate with the view or stay aligned with the map.
|
|
* Note: will only be used for point geometry shaders.
|
|
* @param {boolean} rotateWithView Rotate with view
|
|
* @return {ShaderBuilder} the builder object
|
|
*/
|
|
setSymbolRotateWithView(rotateWithView) {
|
|
this.symbolRotateWithView_ = rotateWithView;
|
|
return this;
|
|
}
|
|
/**
|
|
* @param {string} expression Stroke width expression, returning value in pixels
|
|
* @return {ShaderBuilder} the builder object
|
|
*/
|
|
setStrokeWidthExpression(expression) {
|
|
this.hasStroke_ = true;
|
|
this.strokeWidthExpression_ = expression;
|
|
return this;
|
|
}
|
|
/**
|
|
* @param {string} expression Stroke color expression, evaluate to `vec4`: can rely on currentLengthPx and currentRadiusPx
|
|
* @return {ShaderBuilder} the builder object
|
|
*/
|
|
setStrokeColorExpression(expression) {
|
|
this.hasStroke_ = true;
|
|
this.strokeColorExpression_ = expression;
|
|
return this;
|
|
}
|
|
/**
|
|
* @return {string} The current stroke color expression
|
|
*/
|
|
getStrokeColorExpression() {
|
|
return this.strokeColorExpression_;
|
|
}
|
|
/**
|
|
* @param {string} expression Stroke color expression, evaluate to `float`
|
|
* @return {ShaderBuilder} the builder object
|
|
*/
|
|
setStrokeOffsetExpression(expression) {
|
|
this.strokeOffsetExpression_ = expression;
|
|
return this;
|
|
}
|
|
/**
|
|
* @param {string} expression Stroke line cap expression, evaluate to `float`
|
|
* @return {ShaderBuilder} the builder object
|
|
*/
|
|
setStrokeCapExpression(expression) {
|
|
this.strokeCapExpression_ = expression;
|
|
return this;
|
|
}
|
|
/**
|
|
* @param {string} expression Stroke line join expression, evaluate to `float`
|
|
* @return {ShaderBuilder} the builder object
|
|
*/
|
|
setStrokeJoinExpression(expression) {
|
|
this.strokeJoinExpression_ = expression;
|
|
return this;
|
|
}
|
|
/**
|
|
* @param {string} expression Stroke miter limit expression, evaluate to `float`
|
|
* @return {ShaderBuilder} the builder object
|
|
*/
|
|
setStrokeMiterLimitExpression(expression) {
|
|
this.strokeMiterLimitExpression_ = expression;
|
|
return this;
|
|
}
|
|
/**
|
|
* @param {string} expression Stroke distance field expression, evaluate to `float`
|
|
* This can override the default distance field; can rely on currentLengthPx and currentRadiusPx
|
|
* @return {ShaderBuilder} the builder object
|
|
*/
|
|
setStrokeDistanceFieldExpression(expression) {
|
|
this.strokeDistanceFieldExpression_ = expression;
|
|
return this;
|
|
}
|
|
/**
|
|
* @param {string} expression Fill color expression, evaluate to `vec4`
|
|
* @return {ShaderBuilder} the builder object
|
|
*/
|
|
setFillColorExpression(expression) {
|
|
this.hasFill_ = true;
|
|
this.fillColorExpression_ = expression;
|
|
return this;
|
|
}
|
|
/**
|
|
* @return {string} The current fill color expression
|
|
*/
|
|
getFillColorExpression() {
|
|
return this.fillColorExpression_;
|
|
}
|
|
addVertexShaderFunction(code) {
|
|
if (this.vertexShaderFunctions_.includes(code)) {
|
|
return this;
|
|
}
|
|
this.vertexShaderFunctions_.push(code);
|
|
return this;
|
|
}
|
|
addFragmentShaderFunction(code) {
|
|
if (this.fragmentShaderFunctions_.includes(code)) {
|
|
return this;
|
|
}
|
|
this.fragmentShaderFunctions_.push(code);
|
|
return this;
|
|
}
|
|
/**
|
|
* Generates a symbol vertex shader from the builder parameters
|
|
* @return {string|null} The full shader as a string; null if no size or color specified
|
|
*/
|
|
getSymbolVertexShader() {
|
|
if (!this.hasSymbol_) {
|
|
return null;
|
|
}
|
|
return `${COMMON_HEADER}
|
|
${this.uniforms_.map((uniform) => `uniform ${uniform.type} ${uniform.name};`).join("\n")}
|
|
attribute vec2 a_position;
|
|
attribute float a_index;
|
|
attribute vec4 a_hitColor;
|
|
|
|
varying vec2 v_texCoord;
|
|
varying vec2 v_quadCoord;
|
|
varying vec4 v_hitColor;
|
|
varying vec2 v_centerPx;
|
|
varying float v_angle;
|
|
varying vec2 v_quadSizePx;
|
|
|
|
${this.attributes_.map(
|
|
(attribute) => `attribute ${attribute.type} ${attribute.name};
|
|
varying ${attribute.varyingType} ${attribute.varyingName};`
|
|
).join("\n")}
|
|
${this.vertexShaderFunctions_.join("\n")}
|
|
vec2 pxToScreen(vec2 coordPx) {
|
|
vec2 scaled = coordPx / u_viewportSizePx / 0.5;
|
|
return scaled;
|
|
}
|
|
|
|
vec2 screenToPx(vec2 coordScreen) {
|
|
return (coordScreen * 0.5 + 0.5) * u_viewportSizePx;
|
|
}
|
|
|
|
void main(void) {
|
|
v_quadSizePx = ${this.symbolSizeExpression_};
|
|
vec2 halfSizePx = v_quadSizePx * 0.5;
|
|
vec2 centerOffsetPx = ${this.symbolOffsetExpression_};
|
|
vec2 offsetPx = centerOffsetPx;
|
|
if (a_index == 0.0) {
|
|
offsetPx -= halfSizePx;
|
|
} else if (a_index == 1.0) {
|
|
offsetPx += halfSizePx * vec2(1., -1.);
|
|
} else if (a_index == 2.0) {
|
|
offsetPx += halfSizePx;
|
|
} else {
|
|
offsetPx += halfSizePx * vec2(-1., 1.);
|
|
}
|
|
float angle = ${this.symbolRotationExpression_}${this.symbolRotateWithView_ ? " + u_rotation" : ""};
|
|
float c = cos(-angle);
|
|
float s = sin(-angle);
|
|
offsetPx = vec2(c * offsetPx.x - s * offsetPx.y, s * offsetPx.x + c * offsetPx.y);
|
|
vec4 center = u_projectionMatrix * vec4(a_position, 0.0, 1.0);
|
|
gl_Position = center + vec4(pxToScreen(offsetPx), u_depth, 0.);
|
|
vec4 texCoord = ${this.texCoordExpression_};
|
|
float u = a_index == 0.0 || a_index == 3.0 ? texCoord.s : texCoord.p;
|
|
float v = a_index == 2.0 || a_index == 3.0 ? texCoord.t : texCoord.q;
|
|
v_texCoord = vec2(u, v);
|
|
v_hitColor = a_hitColor;
|
|
v_angle = angle;
|
|
c = cos(-v_angle);
|
|
s = sin(-v_angle);
|
|
centerOffsetPx = vec2(c * centerOffsetPx.x - s * centerOffsetPx.y, s * centerOffsetPx.x + c * centerOffsetPx.y);
|
|
v_centerPx = screenToPx(center.xy) + centerOffsetPx;
|
|
${this.attributes_.map(
|
|
(attribute) => ` ${attribute.varyingName} = ${attribute.varyingExpression};`
|
|
).join("\n")}
|
|
}`;
|
|
}
|
|
/**
|
|
* Generates a symbol fragment shader from the builder parameters
|
|
* @return {string|null} The full shader as a string; null if no size or color specified
|
|
*/
|
|
getSymbolFragmentShader() {
|
|
if (!this.hasSymbol_) {
|
|
return null;
|
|
}
|
|
return `${COMMON_HEADER}
|
|
${this.uniforms_.map((uniform) => `uniform ${uniform.type} ${uniform.name};`).join("\n")}
|
|
varying vec2 v_texCoord;
|
|
varying vec4 v_hitColor;
|
|
varying vec2 v_centerPx;
|
|
varying float v_angle;
|
|
varying vec2 v_quadSizePx;
|
|
${this.attributes_.map(
|
|
(attribute) => `varying ${attribute.varyingType} ${attribute.varyingName};`
|
|
).join("\n")}
|
|
${this.fragmentShaderFunctions_.join("\n")}
|
|
|
|
void main(void) {
|
|
${this.attributes_.map(
|
|
(attribute) => ` ${attribute.varyingType} ${attribute.name} = ${attribute.varyingName}; // assign to original attribute name`
|
|
).join("\n")}
|
|
if (${this.discardExpression_}) { discard; }
|
|
vec2 coordsPx = gl_FragCoord.xy / u_pixelRatio - v_centerPx; // relative to center
|
|
float c = cos(v_angle);
|
|
float s = sin(v_angle);
|
|
coordsPx = vec2(c * coordsPx.x - s * coordsPx.y, s * coordsPx.x + c * coordsPx.y);
|
|
gl_FragColor = ${this.symbolColorExpression_};
|
|
gl_FragColor.rgb *= gl_FragColor.a;
|
|
if (u_hitDetection > 0) {
|
|
if (gl_FragColor.a < 0.05) { discard; };
|
|
gl_FragColor = v_hitColor;
|
|
}
|
|
}`;
|
|
}
|
|
/**
|
|
* Generates a stroke vertex shader from the builder parameters
|
|
* @return {string|null} The full shader as a string; null if no size or color specified
|
|
*/
|
|
getStrokeVertexShader() {
|
|
if (!this.hasStroke_) {
|
|
return null;
|
|
}
|
|
return `${COMMON_HEADER}
|
|
${this.uniforms_.map((uniform) => `uniform ${uniform.type} ${uniform.name};`).join("\n")}
|
|
attribute vec2 a_segmentStart;
|
|
attribute vec2 a_segmentEnd;
|
|
attribute float a_measureStart;
|
|
attribute float a_measureEnd;
|
|
attribute float a_parameters;
|
|
attribute float a_distance;
|
|
attribute vec2 a_joinAngles;
|
|
attribute vec4 a_hitColor;
|
|
|
|
varying vec2 v_segmentStart;
|
|
varying vec2 v_segmentEnd;
|
|
varying float v_angleStart;
|
|
varying float v_angleEnd;
|
|
varying float v_width;
|
|
varying vec4 v_hitColor;
|
|
varying float v_distanceOffsetPx;
|
|
varying float v_measureStart;
|
|
varying float v_measureEnd;
|
|
|
|
${this.attributes_.map(
|
|
(attribute) => `attribute ${attribute.type} ${attribute.name};
|
|
varying ${attribute.varyingType} ${attribute.varyingName};`
|
|
).join("\n")}
|
|
${this.vertexShaderFunctions_.join("\n")}
|
|
vec2 worldToPx(vec2 worldPos) {
|
|
vec4 screenPos = u_projectionMatrix * vec4(worldPos, 0.0, 1.0);
|
|
return (0.5 * screenPos.xy + 0.5) * u_viewportSizePx;
|
|
}
|
|
|
|
vec4 pxToScreen(vec2 pxPos) {
|
|
vec2 screenPos = 2.0 * pxPos / u_viewportSizePx - 1.0;
|
|
return vec4(screenPos, u_depth, 1.0);
|
|
}
|
|
|
|
bool isCap(float joinAngle) {
|
|
return joinAngle < -0.1;
|
|
}
|
|
|
|
vec2 getJoinOffsetDirection(vec2 normalPx, float joinAngle) {
|
|
float halfAngle = joinAngle / 2.0;
|
|
float c = cos(halfAngle);
|
|
float s = sin(halfAngle);
|
|
vec2 angleBisectorNormal = vec2(s * normalPx.x + c * normalPx.y, -c * normalPx.x + s * normalPx.y);
|
|
float length = 1.0 / s;
|
|
return angleBisectorNormal * length;
|
|
}
|
|
|
|
vec2 getOffsetPoint(vec2 point, vec2 normal, float joinAngle, float offsetPx) {
|
|
// if on a cap or the join angle is too high, offset the line along the segment normal
|
|
if (cos(joinAngle) > 0.998 || isCap(joinAngle)) {
|
|
return point - normal * offsetPx;
|
|
}
|
|
// offset is applied along the inverted normal (positive offset goes "right" relative to line direction)
|
|
return point - getJoinOffsetDirection(normal, joinAngle) * offsetPx;
|
|
}
|
|
|
|
void main(void) {
|
|
v_angleStart = a_joinAngles.x;
|
|
v_angleEnd = a_joinAngles.y;
|
|
float vertexNumber = floor(abs(a_parameters) / 10000. + 0.5);
|
|
currentLineMetric = vertexNumber < 1.5 ? a_measureStart : a_measureEnd;
|
|
// we're reading the fractional part while keeping the sign (so -4.12 gives -0.12, 3.45 gives 0.45)
|
|
float angleTangentSum = fract(abs(a_parameters) / 10000.) * 10000. * sign(a_parameters);
|
|
|
|
float lineWidth = ${this.strokeWidthExpression_};
|
|
float lineOffsetPx = ${this.strokeOffsetExpression_};
|
|
|
|
// compute segment start/end in px with offset
|
|
vec2 segmentStartPx = worldToPx(a_segmentStart);
|
|
vec2 segmentEndPx = worldToPx(a_segmentEnd);
|
|
vec2 tangentPx = normalize(segmentEndPx - segmentStartPx);
|
|
vec2 normalPx = vec2(-tangentPx.y, tangentPx.x);
|
|
segmentStartPx = getOffsetPoint(segmentStartPx, normalPx, v_angleStart, lineOffsetPx),
|
|
segmentEndPx = getOffsetPoint(segmentEndPx, normalPx, v_angleEnd, lineOffsetPx);
|
|
|
|
// compute current vertex position
|
|
float normalDir = vertexNumber < 0.5 || (vertexNumber > 1.5 && vertexNumber < 2.5) ? 1.0 : -1.0;
|
|
float tangentDir = vertexNumber < 1.5 ? 1.0 : -1.0;
|
|
float angle = vertexNumber < 1.5 ? v_angleStart : v_angleEnd;
|
|
vec2 joinDirection;
|
|
vec2 positionPx = vertexNumber < 1.5 ? segmentStartPx : segmentEndPx;
|
|
// if angle is too high, do not make a proper join
|
|
if (cos(angle) > ${LINESTRING_ANGLE_COSINE_CUTOFF} || isCap(angle)) {
|
|
joinDirection = normalPx * normalDir - tangentPx * tangentDir;
|
|
} else {
|
|
joinDirection = getJoinOffsetDirection(normalPx * normalDir, angle);
|
|
}
|
|
positionPx = positionPx + joinDirection * (lineWidth * 0.5 + 1.); // adding 1 pixel for antialiasing
|
|
gl_Position = pxToScreen(positionPx);
|
|
|
|
v_segmentStart = segmentStartPx;
|
|
v_segmentEnd = segmentEndPx;
|
|
v_width = lineWidth;
|
|
v_hitColor = a_hitColor;
|
|
v_distanceOffsetPx = a_distance / u_resolution - (lineOffsetPx * angleTangentSum);
|
|
v_measureStart = a_measureStart;
|
|
v_measureEnd = a_measureEnd;
|
|
${this.attributes_.map(
|
|
(attribute) => ` ${attribute.varyingName} = ${attribute.varyingExpression};`
|
|
).join("\n")}
|
|
}`;
|
|
}
|
|
/**
|
|
* Generates a stroke fragment shader from the builder parameters
|
|
*
|
|
* @return {string|null} The full shader as a string; null if no size or color specified
|
|
*/
|
|
getStrokeFragmentShader() {
|
|
if (!this.hasStroke_) {
|
|
return null;
|
|
}
|
|
return `${COMMON_HEADER}
|
|
${this.uniforms_.map((uniform) => `uniform ${uniform.type} ${uniform.name};`).join("\n")}
|
|
varying vec2 v_segmentStart;
|
|
varying vec2 v_segmentEnd;
|
|
varying float v_angleStart;
|
|
varying float v_angleEnd;
|
|
varying float v_width;
|
|
varying vec4 v_hitColor;
|
|
varying float v_distanceOffsetPx;
|
|
varying float v_measureStart;
|
|
varying float v_measureEnd;
|
|
${this.attributes_.map(
|
|
(attribute) => `varying ${attribute.varyingType} ${attribute.varyingName};`
|
|
).join("\n")}
|
|
${this.fragmentShaderFunctions_.join("\n")}
|
|
|
|
vec2 pxToWorld(vec2 pxPos) {
|
|
vec2 screenPos = 2.0 * pxPos / u_viewportSizePx - 1.0;
|
|
return (u_screenToWorldMatrix * vec4(screenPos, 0.0, 1.0)).xy;
|
|
}
|
|
|
|
bool isCap(float joinAngle) {
|
|
return joinAngle < -0.1;
|
|
}
|
|
|
|
float segmentDistanceField(vec2 point, vec2 start, vec2 end, float width) {
|
|
vec2 tangent = normalize(end - start);
|
|
vec2 normal = vec2(-tangent.y, tangent.x);
|
|
vec2 startToPoint = point - start;
|
|
return abs(dot(startToPoint, normal)) - width * 0.5;
|
|
}
|
|
|
|
float buttCapDistanceField(vec2 point, vec2 start, vec2 end) {
|
|
vec2 startToPoint = point - start;
|
|
vec2 tangent = normalize(end - start);
|
|
return dot(startToPoint, -tangent);
|
|
}
|
|
|
|
float squareCapDistanceField(vec2 point, vec2 start, vec2 end, float width) {
|
|
return buttCapDistanceField(point, start, end) - width * 0.5;
|
|
}
|
|
|
|
float roundCapDistanceField(vec2 point, vec2 start, vec2 end, float width) {
|
|
float onSegment = max(0., 1000. * dot(point - start, end - start)); // this is very high when inside the segment
|
|
return length(point - start) - width * 0.5 - onSegment;
|
|
}
|
|
|
|
float roundJoinDistanceField(vec2 point, vec2 start, vec2 end, float width) {
|
|
return roundCapDistanceField(point, start, end, width);
|
|
}
|
|
|
|
float bevelJoinField(vec2 point, vec2 start, vec2 end, float width, float joinAngle) {
|
|
vec2 startToPoint = point - start;
|
|
vec2 tangent = normalize(end - start);
|
|
float c = cos(joinAngle * 0.5);
|
|
float s = sin(joinAngle * 0.5);
|
|
float direction = -sign(sin(joinAngle));
|
|
vec2 bisector = vec2(c * tangent.x - s * tangent.y, s * tangent.x + c * tangent.y);
|
|
float radius = width * 0.5 * s;
|
|
return dot(startToPoint, bisector * direction) - radius;
|
|
}
|
|
|
|
float miterJoinDistanceField(vec2 point, vec2 start, vec2 end, float width, float joinAngle) {
|
|
if (cos(joinAngle) > ${LINESTRING_ANGLE_COSINE_CUTOFF}) { // avoid risking a division by zero
|
|
return bevelJoinField(point, start, end, width, joinAngle);
|
|
}
|
|
float miterLength = 1. / sin(joinAngle * 0.5);
|
|
float miterLimit = ${this.strokeMiterLimitExpression_};
|
|
if (miterLength > miterLimit) {
|
|
return bevelJoinField(point, start, end, width, joinAngle);
|
|
}
|
|
return -1000.;
|
|
}
|
|
|
|
float capDistanceField(vec2 point, vec2 start, vec2 end, float width, float capType) {
|
|
if (capType == ${stringToGlsl("butt")}) {
|
|
return buttCapDistanceField(point, start, end);
|
|
} else if (capType == ${stringToGlsl("square")}) {
|
|
return squareCapDistanceField(point, start, end, width);
|
|
}
|
|
return roundCapDistanceField(point, start, end, width);
|
|
}
|
|
|
|
float joinDistanceField(vec2 point, vec2 start, vec2 end, float width, float joinAngle, float joinType) {
|
|
if (joinType == ${stringToGlsl("bevel")}) {
|
|
return bevelJoinField(point, start, end, width, joinAngle);
|
|
} else if (joinType == ${stringToGlsl("miter")}) {
|
|
return miterJoinDistanceField(point, start, end, width, joinAngle);
|
|
}
|
|
return roundJoinDistanceField(point, start, end, width);
|
|
}
|
|
|
|
float computeSegmentPointDistance(vec2 point, vec2 start, vec2 end, float width, float joinAngle, float capType, float joinType) {
|
|
if (isCap(joinAngle)) {
|
|
return capDistanceField(point, start, end, width, capType);
|
|
}
|
|
return joinDistanceField(point, start, end, width, joinAngle, joinType);
|
|
}
|
|
|
|
float distanceFromSegment(vec2 point, vec2 start, vec2 end) {
|
|
vec2 tangent = end - start;
|
|
vec2 startToPoint = point - start;
|
|
// inspire by capsule fn in https://iquilezles.org/articles/distfunctions/
|
|
float h = clamp(dot(startToPoint, tangent) / dot(tangent, tangent), 0.0, 1.0);
|
|
return length(startToPoint - tangent * h);
|
|
}
|
|
|
|
void main(void) {
|
|
${this.attributes_.map(
|
|
(attribute) => ` ${attribute.varyingType} ${attribute.name} = ${attribute.varyingName}; // assign to original attribute name`
|
|
).join("\n")}
|
|
|
|
vec2 currentPoint = gl_FragCoord.xy / u_pixelRatio;
|
|
#ifdef GL_FRAGMENT_PRECISION_HIGH
|
|
vec2 worldPos = pxToWorld(currentPoint);
|
|
if (
|
|
abs(u_renderExtent[0] - u_renderExtent[2]) > 0.0 && (
|
|
worldPos[0] < u_renderExtent[0] ||
|
|
worldPos[1] < u_renderExtent[1] ||
|
|
worldPos[0] > u_renderExtent[2] ||
|
|
worldPos[1] > u_renderExtent[3]
|
|
)
|
|
) {
|
|
discard;
|
|
}
|
|
#endif
|
|
|
|
float segmentLength = length(v_segmentEnd - v_segmentStart);
|
|
vec2 segmentTangent = (v_segmentEnd - v_segmentStart) / segmentLength;
|
|
vec2 segmentNormal = vec2(-segmentTangent.y, segmentTangent.x);
|
|
vec2 startToPoint = currentPoint - v_segmentStart;
|
|
float lengthToPoint = max(0., min(dot(segmentTangent, startToPoint), segmentLength));
|
|
float currentLengthPx = lengthToPoint + v_distanceOffsetPx;
|
|
float currentRadiusPx = distanceFromSegment(currentPoint, v_segmentStart, v_segmentEnd);
|
|
float currentRadiusRatio = dot(segmentNormal, startToPoint) * 2. / v_width;
|
|
currentLineMetric = mix(
|
|
v_measureStart,
|
|
v_measureEnd,
|
|
lengthToPoint / max(segmentLength, 1.17549429e-38)
|
|
);
|
|
|
|
if (${this.discardExpression_}) { discard; }
|
|
|
|
float capType = ${this.strokeCapExpression_};
|
|
float joinType = ${this.strokeJoinExpression_};
|
|
float segmentStartDistance = computeSegmentPointDistance(currentPoint, v_segmentStart, v_segmentEnd, v_width, v_angleStart, capType, joinType);
|
|
float segmentEndDistance = computeSegmentPointDistance(currentPoint, v_segmentEnd, v_segmentStart, v_width, v_angleEnd, capType, joinType);
|
|
float distanceField = max(
|
|
segmentDistanceField(currentPoint, v_segmentStart, v_segmentEnd, v_width),
|
|
max(segmentStartDistance, segmentEndDistance)
|
|
);
|
|
distanceField = max(distanceField, ${this.strokeDistanceFieldExpression_});
|
|
|
|
vec4 color = ${this.strokeColorExpression_};
|
|
color.a *= smoothstep(0.5, -0.5, distanceField);
|
|
gl_FragColor = color;
|
|
gl_FragColor.a *= u_globalAlpha;
|
|
gl_FragColor.rgb *= gl_FragColor.a;
|
|
if (u_hitDetection > 0) {
|
|
if (gl_FragColor.a < 0.1) { discard; };
|
|
gl_FragColor = v_hitColor;
|
|
}
|
|
}`;
|
|
}
|
|
/**
|
|
* Generates a fill vertex shader from the builder parameters
|
|
*
|
|
* @return {string|null} The full shader as a string; null if no color specified
|
|
*/
|
|
getFillVertexShader() {
|
|
if (!this.hasFill_) {
|
|
return null;
|
|
}
|
|
return `${COMMON_HEADER}
|
|
${this.uniforms_.map((uniform) => `uniform ${uniform.type} ${uniform.name};`).join("\n")}
|
|
attribute vec2 a_position;
|
|
attribute vec4 a_hitColor;
|
|
|
|
varying vec4 v_hitColor;
|
|
|
|
${this.attributes_.map(
|
|
(attribute) => `attribute ${attribute.type} ${attribute.name};
|
|
varying ${attribute.varyingType} ${attribute.varyingName};`
|
|
).join("\n")}
|
|
${this.vertexShaderFunctions_.join("\n")}
|
|
void main(void) {
|
|
gl_Position = u_projectionMatrix * vec4(a_position, u_depth, 1.0);
|
|
v_hitColor = a_hitColor;
|
|
${this.attributes_.map(
|
|
(attribute) => ` ${attribute.varyingName} = ${attribute.varyingExpression};`
|
|
).join("\n")}
|
|
}`;
|
|
}
|
|
/**
|
|
* Generates a fill fragment shader from the builder parameters
|
|
* @return {string|null} The full shader as a string; null if no color specified
|
|
*/
|
|
getFillFragmentShader() {
|
|
if (!this.hasFill_) {
|
|
return null;
|
|
}
|
|
return `${COMMON_HEADER}
|
|
${this.uniforms_.map((uniform) => `uniform ${uniform.type} ${uniform.name};`).join("\n")}
|
|
varying vec4 v_hitColor;
|
|
${this.attributes_.map(
|
|
(attribute) => `varying ${attribute.varyingType} ${attribute.varyingName};`
|
|
).join("\n")}
|
|
${this.fragmentShaderFunctions_.join("\n")}
|
|
vec2 pxToWorld(vec2 pxPos) {
|
|
vec2 screenPos = 2.0 * pxPos / u_viewportSizePx - 1.0;
|
|
return (u_screenToWorldMatrix * vec4(screenPos, 0.0, 1.0)).xy;
|
|
}
|
|
|
|
vec2 worldToPx(vec2 worldPos) {
|
|
vec4 screenPos = u_projectionMatrix * vec4(worldPos, 0.0, 1.0);
|
|
return (0.5 * screenPos.xy + 0.5) * u_viewportSizePx;
|
|
}
|
|
|
|
void main(void) {
|
|
${this.attributes_.map(
|
|
(attribute) => ` ${attribute.varyingType} ${attribute.name} = ${attribute.varyingName}; // assign to original attribute name`
|
|
).join("\n")}
|
|
vec2 pxPos = gl_FragCoord.xy / u_pixelRatio;
|
|
vec2 pxOrigin = worldToPx(u_patternOrigin);
|
|
#ifdef GL_FRAGMENT_PRECISION_HIGH
|
|
vec2 worldPos = pxToWorld(pxPos);
|
|
if (
|
|
abs(u_renderExtent[0] - u_renderExtent[2]) > 0.0 && (
|
|
worldPos[0] < u_renderExtent[0] ||
|
|
worldPos[1] < u_renderExtent[1] ||
|
|
worldPos[0] > u_renderExtent[2] ||
|
|
worldPos[1] > u_renderExtent[3]
|
|
)
|
|
) {
|
|
discard;
|
|
}
|
|
#endif
|
|
if (${this.discardExpression_}) { discard; }
|
|
gl_FragColor = ${this.fillColorExpression_};
|
|
gl_FragColor.a *= u_globalAlpha;
|
|
gl_FragColor.rgb *= gl_FragColor.a;
|
|
if (u_hitDetection > 0) {
|
|
if (gl_FragColor.a < 0.1) { discard; };
|
|
gl_FragColor = v_hitColor;
|
|
}
|
|
}`;
|
|
}
|
|
};
|
|
|
|
// node_modules/ol/render/webgl/MixedGeometryBatch.js
|
|
var MixedGeometryBatch = class _MixedGeometryBatch {
|
|
constructor() {
|
|
this.globalCounter_ = 0;
|
|
this.refToFeature_ = /* @__PURE__ */ new Map();
|
|
this.uidToRef_ = /* @__PURE__ */ new Map();
|
|
this.freeGlobalRef_ = [];
|
|
this.polygonBatch = {
|
|
entries: {},
|
|
geometriesCount: 0,
|
|
verticesCount: 0,
|
|
ringsCount: 0
|
|
};
|
|
this.pointBatch = {
|
|
entries: {},
|
|
geometriesCount: 0
|
|
};
|
|
this.lineStringBatch = {
|
|
entries: {},
|
|
geometriesCount: 0,
|
|
verticesCount: 0
|
|
};
|
|
}
|
|
/**
|
|
* @param {Array<Feature|RenderFeature>} features Array of features to add to the batch
|
|
* @param {import("../../proj.js").TransformFunction} [projectionTransform] Projection transform.
|
|
*/
|
|
addFeatures(features, projectionTransform) {
|
|
for (let i = 0; i < features.length; i++) {
|
|
this.addFeature(features[i], projectionTransform);
|
|
}
|
|
}
|
|
/**
|
|
* @param {Feature|RenderFeature} feature Feature to add to the batch
|
|
* @param {import("../../proj.js").TransformFunction} [projectionTransform] Projection transform.
|
|
*/
|
|
addFeature(feature, projectionTransform) {
|
|
let geometry = feature.getGeometry();
|
|
if (!geometry) {
|
|
return;
|
|
}
|
|
if (projectionTransform) {
|
|
geometry = geometry.clone();
|
|
geometry.applyTransform(projectionTransform);
|
|
}
|
|
this.addGeometry_(geometry, feature);
|
|
}
|
|
/**
|
|
* @param {Feature|RenderFeature} feature Feature
|
|
* @return {GeometryBatchItem|void} the cleared entry
|
|
* @private
|
|
*/
|
|
clearFeatureEntryInPointBatch_(feature) {
|
|
const featureUid = getUid(feature);
|
|
const entry = this.pointBatch.entries[featureUid];
|
|
if (!entry) {
|
|
return;
|
|
}
|
|
this.pointBatch.geometriesCount -= entry.flatCoordss.length;
|
|
delete this.pointBatch.entries[featureUid];
|
|
return entry;
|
|
}
|
|
/**
|
|
* @param {Feature|RenderFeature} feature Feature
|
|
* @return {GeometryBatchItem|void} the cleared entry
|
|
* @private
|
|
*/
|
|
clearFeatureEntryInLineStringBatch_(feature) {
|
|
const featureUid = getUid(feature);
|
|
const entry = this.lineStringBatch.entries[featureUid];
|
|
if (!entry) {
|
|
return;
|
|
}
|
|
this.lineStringBatch.verticesCount -= entry.verticesCount;
|
|
this.lineStringBatch.geometriesCount -= entry.flatCoordss.length;
|
|
delete this.lineStringBatch.entries[featureUid];
|
|
return entry;
|
|
}
|
|
/**
|
|
* @param {Feature|RenderFeature} feature Feature
|
|
* @return {GeometryBatchItem|void} the cleared entry
|
|
* @private
|
|
*/
|
|
clearFeatureEntryInPolygonBatch_(feature) {
|
|
const featureUid = getUid(feature);
|
|
const entry = this.polygonBatch.entries[featureUid];
|
|
if (!entry) {
|
|
return;
|
|
}
|
|
this.polygonBatch.verticesCount -= entry.verticesCount;
|
|
this.polygonBatch.ringsCount -= entry.ringsCount;
|
|
this.polygonBatch.geometriesCount -= entry.flatCoordss.length;
|
|
delete this.polygonBatch.entries[featureUid];
|
|
return entry;
|
|
}
|
|
/**
|
|
* @param {import("../../geom.js").Geometry|RenderFeature} geometry Geometry
|
|
* @param {Feature|RenderFeature} feature Feature
|
|
* @private
|
|
*/
|
|
addGeometry_(geometry, feature) {
|
|
var _a;
|
|
const type = geometry.getType();
|
|
switch (type) {
|
|
case "GeometryCollection": {
|
|
const geometries = (
|
|
/** @type {import("../../geom.js").GeometryCollection} */
|
|
geometry.getGeometriesArray()
|
|
);
|
|
for (const geometry2 of geometries) {
|
|
this.addGeometry_(geometry2, feature);
|
|
}
|
|
break;
|
|
}
|
|
case "MultiPolygon": {
|
|
const multiPolygonGeom = (
|
|
/** @type {import("../../geom.js").MultiPolygon} */
|
|
geometry
|
|
);
|
|
this.addCoordinates_(
|
|
type,
|
|
multiPolygonGeom.getFlatCoordinates(),
|
|
multiPolygonGeom.getEndss(),
|
|
feature,
|
|
getUid(feature),
|
|
multiPolygonGeom.getStride()
|
|
);
|
|
break;
|
|
}
|
|
case "MultiLineString": {
|
|
const multiLineGeom = (
|
|
/** @type {import("../../geom.js").MultiLineString|RenderFeature} */
|
|
geometry
|
|
);
|
|
this.addCoordinates_(
|
|
type,
|
|
multiLineGeom.getFlatCoordinates(),
|
|
multiLineGeom.getEnds(),
|
|
feature,
|
|
getUid(feature),
|
|
multiLineGeom.getStride()
|
|
);
|
|
break;
|
|
}
|
|
case "MultiPoint": {
|
|
const multiPointGeom = (
|
|
/** @type {import("../../geom.js").MultiPoint|RenderFeature} */
|
|
geometry
|
|
);
|
|
this.addCoordinates_(
|
|
type,
|
|
multiPointGeom.getFlatCoordinates(),
|
|
null,
|
|
feature,
|
|
getUid(feature),
|
|
multiPointGeom.getStride()
|
|
);
|
|
break;
|
|
}
|
|
case "Polygon": {
|
|
const polygonGeom = (
|
|
/** @type {import("../../geom.js").Polygon|RenderFeature} */
|
|
geometry
|
|
);
|
|
this.addCoordinates_(
|
|
type,
|
|
polygonGeom.getFlatCoordinates(),
|
|
polygonGeom.getEnds(),
|
|
feature,
|
|
getUid(feature),
|
|
polygonGeom.getStride()
|
|
);
|
|
break;
|
|
}
|
|
case "Point": {
|
|
const pointGeom = (
|
|
/** @type {import("../../geom.js").Point} */
|
|
geometry
|
|
);
|
|
this.addCoordinates_(
|
|
type,
|
|
pointGeom.getFlatCoordinates(),
|
|
null,
|
|
feature,
|
|
getUid(feature),
|
|
pointGeom.getStride()
|
|
);
|
|
break;
|
|
}
|
|
case "LineString":
|
|
case "LinearRing": {
|
|
const lineGeom = (
|
|
/** @type {import("../../geom.js").LineString} */
|
|
geometry
|
|
);
|
|
const stride = lineGeom.getStride();
|
|
this.addCoordinates_(
|
|
type,
|
|
lineGeom.getFlatCoordinates(),
|
|
null,
|
|
feature,
|
|
getUid(feature),
|
|
stride,
|
|
(_a = lineGeom.getLayout) == null ? void 0 : _a.call(lineGeom)
|
|
);
|
|
break;
|
|
}
|
|
default:
|
|
}
|
|
}
|
|
/**
|
|
* @param {GeometryType} type Geometry type
|
|
* @param {Array<number>} flatCoords Flat coordinates
|
|
* @param {Array<number> | Array<Array<number>> | null} ends Coordinate ends
|
|
* @param {Feature|RenderFeature} feature Feature
|
|
* @param {string} featureUid Feature uid
|
|
* @param {number} stride Stride
|
|
* @param {import('../../geom/Geometry.js').GeometryLayout} [layout] Layout
|
|
* @private
|
|
*/
|
|
addCoordinates_(type, flatCoords, ends, feature, featureUid, stride, layout) {
|
|
let verticesCount;
|
|
switch (type) {
|
|
case "MultiPolygon": {
|
|
const multiPolygonEndss = (
|
|
/** @type {Array<Array<number>>} */
|
|
ends
|
|
);
|
|
for (let i = 0, ii = multiPolygonEndss.length; i < ii; i++) {
|
|
let polygonEnds = multiPolygonEndss[i];
|
|
const prevPolygonEnds = i > 0 ? multiPolygonEndss[i - 1] : null;
|
|
const startIndex = prevPolygonEnds ? prevPolygonEnds[prevPolygonEnds.length - 1] : 0;
|
|
const endIndex = polygonEnds[polygonEnds.length - 1];
|
|
polygonEnds = startIndex > 0 ? polygonEnds.map((end) => end - startIndex) : polygonEnds;
|
|
this.addCoordinates_(
|
|
"Polygon",
|
|
flatCoords.slice(startIndex, endIndex),
|
|
polygonEnds,
|
|
feature,
|
|
featureUid,
|
|
stride,
|
|
layout
|
|
);
|
|
}
|
|
break;
|
|
}
|
|
case "MultiLineString": {
|
|
const multiLineEnds = (
|
|
/** @type {Array<number>} */
|
|
ends
|
|
);
|
|
for (let i = 0, ii = multiLineEnds.length; i < ii; i++) {
|
|
const startIndex = i > 0 ? multiLineEnds[i - 1] : 0;
|
|
this.addCoordinates_(
|
|
"LineString",
|
|
flatCoords.slice(startIndex, multiLineEnds[i]),
|
|
null,
|
|
feature,
|
|
featureUid,
|
|
stride,
|
|
layout
|
|
);
|
|
}
|
|
break;
|
|
}
|
|
case "MultiPoint":
|
|
for (let i = 0, ii = flatCoords.length; i < ii; i += stride) {
|
|
this.addCoordinates_(
|
|
"Point",
|
|
flatCoords.slice(i, i + 2),
|
|
null,
|
|
feature,
|
|
featureUid,
|
|
null,
|
|
null
|
|
);
|
|
}
|
|
break;
|
|
case "Polygon": {
|
|
const polygonEnds = (
|
|
/** @type {Array<number>} */
|
|
ends
|
|
);
|
|
if (feature instanceof Feature_default) {
|
|
const multiPolygonEnds = inflateEnds(flatCoords, polygonEnds);
|
|
if (multiPolygonEnds.length > 1) {
|
|
this.addCoordinates_(
|
|
"MultiPolygon",
|
|
flatCoords,
|
|
multiPolygonEnds,
|
|
feature,
|
|
featureUid,
|
|
stride,
|
|
layout
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
if (!this.polygonBatch.entries[featureUid]) {
|
|
this.polygonBatch.entries[featureUid] = this.addRefToEntry_(
|
|
featureUid,
|
|
{
|
|
feature,
|
|
flatCoordss: [],
|
|
verticesCount: 0,
|
|
ringsCount: 0,
|
|
ringsVerticesCounts: []
|
|
}
|
|
);
|
|
}
|
|
verticesCount = flatCoords.length / stride;
|
|
const ringsCount = ends.length;
|
|
const ringsVerticesCount = ends.map(
|
|
(end, ind, arr) => ind > 0 ? (end - arr[ind - 1]) / stride : end / stride
|
|
);
|
|
this.polygonBatch.verticesCount += verticesCount;
|
|
this.polygonBatch.ringsCount += ringsCount;
|
|
this.polygonBatch.geometriesCount++;
|
|
this.polygonBatch.entries[featureUid].flatCoordss.push(
|
|
getFlatCoordinatesXY(flatCoords, stride)
|
|
);
|
|
this.polygonBatch.entries[featureUid].ringsVerticesCounts.push(
|
|
ringsVerticesCount
|
|
);
|
|
this.polygonBatch.entries[featureUid].verticesCount += verticesCount;
|
|
this.polygonBatch.entries[featureUid].ringsCount += ringsCount;
|
|
for (let i = 0, ii = polygonEnds.length; i < ii; i++) {
|
|
const startIndex = i > 0 ? polygonEnds[i - 1] : 0;
|
|
this.addCoordinates_(
|
|
"LinearRing",
|
|
flatCoords.slice(startIndex, polygonEnds[i]),
|
|
null,
|
|
feature,
|
|
featureUid,
|
|
stride,
|
|
layout
|
|
);
|
|
}
|
|
break;
|
|
}
|
|
case "Point":
|
|
if (!this.pointBatch.entries[featureUid]) {
|
|
this.pointBatch.entries[featureUid] = this.addRefToEntry_(
|
|
featureUid,
|
|
{
|
|
feature,
|
|
flatCoordss: []
|
|
}
|
|
);
|
|
}
|
|
this.pointBatch.geometriesCount++;
|
|
this.pointBatch.entries[featureUid].flatCoordss.push(flatCoords);
|
|
break;
|
|
case "LineString":
|
|
case "LinearRing":
|
|
if (!this.lineStringBatch.entries[featureUid]) {
|
|
this.lineStringBatch.entries[featureUid] = this.addRefToEntry_(
|
|
featureUid,
|
|
{
|
|
feature,
|
|
flatCoordss: [],
|
|
verticesCount: 0
|
|
}
|
|
);
|
|
}
|
|
verticesCount = flatCoords.length / stride;
|
|
this.lineStringBatch.verticesCount += verticesCount;
|
|
this.lineStringBatch.geometriesCount++;
|
|
this.lineStringBatch.entries[featureUid].flatCoordss.push(
|
|
getFlatCoordinatesXYM(flatCoords, stride, layout)
|
|
);
|
|
this.lineStringBatch.entries[featureUid].verticesCount += verticesCount;
|
|
break;
|
|
default:
|
|
}
|
|
}
|
|
/**
|
|
* @param {string} featureUid Feature uid
|
|
* @param {GeometryBatchItem} entry The entry to add
|
|
* @return {GeometryBatchItem} the added entry
|
|
* @private
|
|
*/
|
|
addRefToEntry_(featureUid, entry) {
|
|
const currentRef = this.uidToRef_.get(featureUid);
|
|
const ref = currentRef || this.freeGlobalRef_.pop() || ++this.globalCounter_;
|
|
entry.ref = ref;
|
|
if (!currentRef) {
|
|
this.refToFeature_.set(ref, entry.feature);
|
|
this.uidToRef_.set(featureUid, ref);
|
|
}
|
|
return entry;
|
|
}
|
|
/**
|
|
* Return a ref to the pool of available refs.
|
|
* @param {number} ref the ref to return
|
|
* @param {string} featureUid the feature uid
|
|
* @private
|
|
*/
|
|
removeRef_(ref, featureUid) {
|
|
if (!ref) {
|
|
throw new Error("This feature has no ref: " + featureUid);
|
|
}
|
|
this.refToFeature_.delete(ref);
|
|
this.uidToRef_.delete(featureUid);
|
|
this.freeGlobalRef_.push(ref);
|
|
}
|
|
/**
|
|
* @param {Feature|RenderFeature} feature Feature
|
|
* @param {import("../../proj.js").TransformFunction} [projectionTransform] Projection transform.
|
|
*/
|
|
changeFeature(feature, projectionTransform) {
|
|
if (!this.uidToRef_.get(getUid(feature))) {
|
|
return;
|
|
}
|
|
this.removeFeature(feature);
|
|
let geometry = feature.getGeometry();
|
|
if (!geometry) {
|
|
return;
|
|
}
|
|
if (projectionTransform) {
|
|
geometry = geometry.clone();
|
|
geometry.applyTransform(projectionTransform);
|
|
}
|
|
this.addGeometry_(geometry, feature);
|
|
}
|
|
/**
|
|
* @param {Feature|RenderFeature} feature Feature
|
|
*/
|
|
removeFeature(feature) {
|
|
let entry = this.clearFeatureEntryInPointBatch_(feature);
|
|
entry = this.clearFeatureEntryInPolygonBatch_(feature) || entry;
|
|
entry = this.clearFeatureEntryInLineStringBatch_(feature) || entry;
|
|
if (entry) {
|
|
this.removeRef_(entry.ref, getUid(entry.feature));
|
|
}
|
|
}
|
|
clear() {
|
|
this.polygonBatch.entries = {};
|
|
this.polygonBatch.geometriesCount = 0;
|
|
this.polygonBatch.verticesCount = 0;
|
|
this.polygonBatch.ringsCount = 0;
|
|
this.lineStringBatch.entries = {};
|
|
this.lineStringBatch.geometriesCount = 0;
|
|
this.lineStringBatch.verticesCount = 0;
|
|
this.pointBatch.entries = {};
|
|
this.pointBatch.geometriesCount = 0;
|
|
this.globalCounter_ = 0;
|
|
this.freeGlobalRef_ = [];
|
|
this.refToFeature_.clear();
|
|
this.uidToRef_.clear();
|
|
}
|
|
/**
|
|
* Resolve the feature associated to a ref.
|
|
* @param {number} ref Hit detected ref
|
|
* @return {Feature|RenderFeature} feature
|
|
*/
|
|
getFeatureFromRef(ref) {
|
|
return this.refToFeature_.get(ref);
|
|
}
|
|
isEmpty() {
|
|
return this.globalCounter_ === 0;
|
|
}
|
|
/**
|
|
* Will return a new instance of this class that only contains the features
|
|
* for which the provided callback returned true
|
|
* @param {function((Feature|RenderFeature)): boolean} featureFilter Feature filter callback
|
|
* @return {MixedGeometryBatch} Filtered geometry batch
|
|
*/
|
|
filter(featureFilter) {
|
|
const filtered = new _MixedGeometryBatch();
|
|
filtered.globalCounter_ = this.globalCounter_;
|
|
filtered.uidToRef_ = this.uidToRef_;
|
|
filtered.refToFeature_ = this.refToFeature_;
|
|
let empty = true;
|
|
for (const feature of this.refToFeature_.values()) {
|
|
if (featureFilter(feature)) {
|
|
filtered.addFeature(feature);
|
|
empty = false;
|
|
}
|
|
}
|
|
if (empty) {
|
|
return new _MixedGeometryBatch();
|
|
}
|
|
return filtered;
|
|
}
|
|
};
|
|
function getFlatCoordinatesXY(flatCoords, stride) {
|
|
if (stride === 2) {
|
|
return flatCoords;
|
|
}
|
|
return flatCoords.filter((v, i) => i % stride < 2);
|
|
}
|
|
function getFlatCoordinatesXYM(flatCoords, stride, layout) {
|
|
if (stride === 3 && layout === "XYM") {
|
|
return flatCoords;
|
|
}
|
|
if (stride === 4) {
|
|
return flatCoords.filter((v, i) => i % stride !== 2);
|
|
}
|
|
if (stride === 3) {
|
|
return flatCoords.map((v, i) => i % stride !== 2 ? v : 0);
|
|
}
|
|
return new Array(flatCoords.length * 1.5).fill(0).map((v, i) => i % 3 === 2 ? 0 : flatCoords[Math.round(i / 1.5)]);
|
|
}
|
|
var MixedGeometryBatch_default = MixedGeometryBatch;
|
|
|
|
// node_modules/ol/worker/webgl.js
|
|
function create3() {
|
|
const source = 'function t(t,n,x=2){const o=n&&n.length,i=o?n[0]*x:t.length;let u=e(t,0,i,x,!0);const l=[];if(!u||u.next===u.prev)return l;let c,h,y;if(o&&(u=function(t,n,r,x){const o=[];for(let r=0,i=n.length;r<i;r++){const u=e(t,n[r]*x,r<i-1?n[r+1]*x:t.length,x,!1);u===u.next&&(u.steiner=!0),o.push(a(u))}o.sort(f);for(let t=0;t<o.length;t++)r=s(o[t],r);return r}(t,n,u,x)),t.length>80*x){c=1/0,h=1/0;let e=-1/0,n=-1/0;for(let r=x;r<i;r+=x){const x=t[r],o=t[r+1];x<c&&(c=x),o<h&&(h=o),x>e&&(e=x),o>n&&(n=o)}y=Math.max(e-c,n-h),y=0!==y?32767/y:0}return r(u,l,x,c,h,y,0),l}function e(t,e,n,r,x){let o;if(x===function(t,e,n,r){let x=0;for(let o=e,i=n-r;o<n;o+=r)x+=(t[i]-t[o])*(t[o+1]+t[i+1]),i=o;return x}(t,e,n,r)>0)for(let x=e;x<n;x+=r)o=w(x/r|0,t[x],t[x+1],o);else for(let x=n-r;x>=e;x-=r)o=w(x/r|0,t[x],t[x+1],o);return o&&g(o,o.next)&&(A(o),o=o.next),o}function n(t,e){if(!t)return t;e||(e=t);let n,r=t;do{if(n=!1,r.steiner||!g(r,r.next)&&0!==v(r.prev,r,r.next))r=r.next;else{if(A(r),r=e=r.prev,r===r.next)break;n=!0}}while(n||r!==e);return e}function r(t,e,f,s,l,a,h){if(!t)return;!h&&a&&function(t,e,n,r){let x=t;do{0===x.z&&(x.z=c(x.x,x.y,e,n,r)),x.prevZ=x.prev,x.nextZ=x.next,x=x.next}while(x!==t);x.prevZ.nextZ=null,x.prevZ=null,function(t){let e,n=1;do{let r,x=t;t=null;let o=null;for(e=0;x;){e++;let i=x,u=0;for(let t=0;t<n&&(u++,i=i.nextZ,i);t++);let f=n;for(;u>0||f>0&&i;)0!==u&&(0===f||!i||x.z<=i.z)?(r=x,x=x.nextZ,u--):(r=i,i=i.nextZ,f--),o?o.nextZ=r:t=r,r.prevZ=o,o=r;x=i}o.nextZ=null,n*=2}while(e>1)}(x)}(t,s,l,a);let y=t;for(;t.prev!==t.next;){const c=t.prev,p=t.next;if(a?o(t,s,l,a):x(t))e.push(c.i,t.i,p.i),A(t),t=p.next,y=p.next;else if((t=p)===y){h?1===h?r(t=i(n(t),e),e,f,s,l,a,2):2===h&&u(t,e,f,s,l,a):r(n(t),e,f,s,l,a,1);break}}}function x(t){const e=t.prev,n=t,r=t.next;if(v(e,n,r)>=0)return!1;const x=e.x,o=n.x,i=r.x,u=e.y,f=n.y,s=r.y,l=Math.min(x,o,i),c=Math.min(u,f,s),a=Math.max(x,o,i),h=Math.max(u,f,s);let p=r.next;for(;p!==e;){if(p.x>=l&&p.x<=a&&p.y>=c&&p.y<=h&&y(x,u,o,f,i,s,p.x,p.y)&&v(p.prev,p,p.next)>=0)return!1;p=p.next}return!0}function o(t,e,n,r){const x=t.prev,o=t,i=t.next;if(v(x,o,i)>=0)return!1;const u=x.x,f=o.x,s=i.x,l=x.y,a=o.y,h=i.y,p=Math.min(u,f,s),g=Math.min(l,a,h),b=Math.max(u,f,s),M=Math.max(l,a,h),m=c(p,g,e,n,r),Z=c(b,M,e,n,r);let d=t.prevZ,w=t.nextZ;for(;d&&d.z>=m&&w&&w.z<=Z;){if(d.x>=p&&d.x<=b&&d.y>=g&&d.y<=M&&d!==x&&d!==i&&y(u,l,f,a,s,h,d.x,d.y)&&v(d.prev,d,d.next)>=0)return!1;if(d=d.prevZ,w.x>=p&&w.x<=b&&w.y>=g&&w.y<=M&&w!==x&&w!==i&&y(u,l,f,a,s,h,w.x,w.y)&&v(w.prev,w,w.next)>=0)return!1;w=w.nextZ}for(;d&&d.z>=m;){if(d.x>=p&&d.x<=b&&d.y>=g&&d.y<=M&&d!==x&&d!==i&&y(u,l,f,a,s,h,d.x,d.y)&&v(d.prev,d,d.next)>=0)return!1;d=d.prevZ}for(;w&&w.z<=Z;){if(w.x>=p&&w.x<=b&&w.y>=g&&w.y<=M&&w!==x&&w!==i&&y(u,l,f,a,s,h,w.x,w.y)&&v(w.prev,w,w.next)>=0)return!1;w=w.nextZ}return!0}function i(t,e){let r=t;do{const n=r.prev,x=r.next.next;!g(n,x)&&b(n,r,r.next,x)&&Z(n,x)&&Z(x,n)&&(e.push(n.i,r.i,x.i),A(r),A(r.next),r=t=x),r=r.next}while(r!==t);return n(r)}function u(t,e,x,o,i,u){let f=t;do{let t=f.next.next;for(;t!==f.prev;){if(f.i!==t.i&&p(f,t)){let s=d(f,t);return f=n(f,f.next),s=n(s,s.next),r(f,e,x,o,i,u,0),void r(s,e,x,o,i,u,0)}t=t.next}f=f.next}while(f!==t)}function f(t,e){let n=t.x-e.x;if(0===n&&(n=t.y-e.y,0===n)){n=(t.next.y-t.y)/(t.next.x-t.x)-(e.next.y-e.y)/(e.next.x-e.x)}return n}function s(t,e){const r=function(t,e){let n=e;const r=t.x,x=t.y;let o,i=-1/0;if(g(t,n))return n;do{if(g(t,n.next))return n.next;if(x<=n.y&&x>=n.next.y&&n.next.y!==n.y){const t=n.x+(x-n.y)*(n.next.x-n.x)/(n.next.y-n.y);if(t<=r&&t>i&&(i=t,o=n.x<n.next.x?n:n.next,t===r))return o}n=n.next}while(n!==e);if(!o)return null;const u=o,f=o.x,s=o.y;let c=1/0;n=o;do{if(r>=n.x&&n.x>=f&&r!==n.x&&h(x<s?r:i,x,f,s,x<s?i:r,x,n.x,n.y)){const e=Math.abs(x-n.y)/(r-n.x);Z(n,t)&&(e<c||e===c&&(n.x>o.x||n.x===o.x&&l(o,n)))&&(o=n,c=e)}n=n.next}while(n!==u);return o}(t,e);if(!r)return e;const x=d(r,t);return n(x,x.next),n(r,r.next)}function l(t,e){return v(t.prev,t,e.prev)<0&&v(e.next,t,t.next)<0}function c(t,e,n,r,x){return(t=1431655765&((t=858993459&((t=252645135&((t=16711935&((t=(t-n)*x|0)|t<<8))|t<<4))|t<<2))|t<<1))|(e=1431655765&((e=858993459&((e=252645135&((e=16711935&((e=(e-r)*x|0)|e<<8))|e<<4))|e<<2))|e<<1))<<1}function a(t){let e=t,n=t;do{(e.x<n.x||e.x===n.x&&e.y<n.y)&&(n=e),e=e.next}while(e!==t);return n}function h(t,e,n,r,x,o,i,u){return(x-i)*(e-u)>=(t-i)*(o-u)&&(t-i)*(r-u)>=(n-i)*(e-u)&&(n-i)*(o-u)>=(x-i)*(r-u)}function y(t,e,n,r,x,o,i,u){return!(t===i&&e===u)&&h(t,e,n,r,x,o,i,u)}function p(t,e){return t.next.i!==e.i&&t.prev.i!==e.i&&!function(t,e){let n=t;do{if(n.i!==t.i&&n.next.i!==t.i&&n.i!==e.i&&n.next.i!==e.i&&b(n,n.next,t,e))return!0;n=n.next}while(n!==t);return!1}(t,e)&&(Z(t,e)&&Z(e,t)&&function(t,e){let n=t,r=!1;const x=(t.x+e.x)/2,o=(t.y+e.y)/2;do{n.y>o!=n.next.y>o&&n.next.y!==n.y&&x<(n.next.x-n.x)*(o-n.y)/(n.next.y-n.y)+n.x&&(r=!r),n=n.next}while(n!==t);return r}(t,e)&&(v(t.prev,t,e.prev)||v(t,e.prev,e))||g(t,e)&&v(t.prev,t,t.next)>0&&v(e.prev,e,e.next)>0)}function v(t,e,n){return(e.y-t.y)*(n.x-e.x)-(e.x-t.x)*(n.y-e.y)}function g(t,e){return t.x===e.x&&t.y===e.y}function b(t,e,n,r){const x=m(v(t,e,n)),o=m(v(t,e,r)),i=m(v(n,r,t)),u=m(v(n,r,e));return x!==o&&i!==u||(!(0!==x||!M(t,n,e))||(!(0!==o||!M(t,r,e))||(!(0!==i||!M(n,t,r))||!(0!==u||!M(n,e,r)))))}function M(t,e,n){return e.x<=Math.max(t.x,n.x)&&e.x>=Math.min(t.x,n.x)&&e.y<=Math.max(t.y,n.y)&&e.y>=Math.min(t.y,n.y)}function m(t){return t>0?1:t<0?-1:0}function Z(t,e){return v(t.prev,t,t.next)<0?v(t,e,t.next)>=0&&v(t,t.prev,e)>=0:v(t,e,t.prev)<0||v(t,t.next,e)<0}function d(t,e){const n=E(t.i,t.x,t.y),r=E(e.i,e.x,e.y),x=t.next,o=e.prev;return t.next=e,e.prev=t,n.next=x,x.prev=n,r.next=n,n.prev=r,o.next=r,r.prev=o,r}function w(t,e,n,r){const x=E(t,e,n);return r?(x.next=r.next,x.prev=r,r.next.prev=x,r.next=x):(x.prev=x,x.next=x),x}function A(t){t.next.prev=t.prev,t.prev.next=t.next,t.prevZ&&(t.prevZ.nextZ=t.nextZ),t.nextZ&&(t.nextZ.prevZ=t.prevZ)}function E(t,e,n){return{i:t,x:e,y:n,prev:null,next:null,z:0,prevZ:null,nextZ:null,steiner:!1}}function I(t,e){const n=e[0],r=e[1];return e[0]=t[0]*n+t[2]*r+t[4],e[1]=t[1]*n+t[3]*r+t[5],e}function z(t,e){const n=(r=e)[0]*r[3]-r[1]*r[2];var r;!function(t,e){if(!t)throw new Error(e)}(0!==n,"Transformation matrix cannot be inverted");const x=e[0],o=e[1],i=e[2],u=e[3],f=e[4],s=e[5];return t[0]=u/n,t[1]=-o/n,t[2]=-i/n,t[3]=x/n,t[4]=(i*s-u*f)/n,t[5]=-(x*s-o*f)/n,t}new Array(6);const F=[],P={vertexPosition:0,indexPosition:0};function B(t,e,n,r,x){t[e+0]=n,t[e+1]=r,t[e+2]=x}function N(t,e,n,r,x,o){const i=3+x,u=t[e+0],f=t[e+1],s=F;s.length=x;for(let n=0;n<s.length;n++)s[n]=t[e+2+n];let l=o?o.vertexPosition:0,c=o?o.indexPosition:0;const a=l/i;return B(n,l,u,f,0),s.length&&n.set(s,l+3),l+=i,B(n,l,u,f,1),s.length&&n.set(s,l+3),l+=i,B(n,l,u,f,2),s.length&&n.set(s,l+3),l+=i,B(n,l,u,f,3),s.length&&n.set(s,l+3),l+=i,r[c++]=a,r[c++]=a+1,r[c++]=a+3,r[c++]=a+1,r[c++]=a+2,r[c++]=a+3,P.vertexPosition=l,P.indexPosition=c,P}function R(t,e,n,r,x,o,i,u,f,s,l){const c=10+u.length,a=o.length/c,h=[t[e+0],t[e+1]],y=[t[n],t[n+1]],p=t[e+2],v=t[n+2],g=I(f,[...h]),b=I(f,[...y]);function M(t,e,n){const r=Math.sqrt((e[0]-t[0])*(e[0]-t[0])+(e[1]-t[1])*(e[1]-t[1])),x=[(e[0]-t[0])/r,(e[1]-t[1])/r],o=[-x[1],x[0]],i=Math.sqrt((n[0]-t[0])*(n[0]-t[0])+(n[1]-t[1])*(n[1]-t[1])),u=[(n[0]-t[0])/i,(n[1]-t[1])/i],f=0===r||0===i?0:Math.acos((s=u[0]*x[0]+u[1]*x[1],l=-1,c=1,Math.min(Math.max(s,l),c)));var s,l,c;return u[0]*o[0]+u[1]*o[1]>0?f:2*Math.PI-f}let m=-1,Z=-1,d=l;const w=null!==x;if(null!==r){m=M(g,b,I(f,[...[t[r],t[r+1]]])),Math.cos(m)<=.985&&(d+=Math.tan((m-Math.PI)/2))}if(w){Z=M(b,g,I(f,[...[t[x],t[x+1]]])),Math.cos(Z)<=.985&&(d+=Math.tan((Math.PI-Z)/2))}function A(t,e){return 0===e?1e4*t:Math.sign(e)*(1e4*t+Math.abs(e))}return o.push(h[0],h[1],p,y[0],y[1],v,m,Z,s,A(0,l)),o.push(...u),o.push(h[0],h[1],p,y[0],y[1],v,m,Z,s,A(1,l)),o.push(...u),o.push(h[0],h[1],p,y[0],y[1],v,m,Z,s,A(2,l)),o.push(...u),o.push(h[0],h[1],p,y[0],y[1],v,m,Z,s,A(3,l)),o.push(...u),i.push(a,a+1,a+2,a+1,a+3,a+2),{length:s+Math.sqrt((b[0]-g[0])*(b[0]-g[0])+(b[1]-g[1])*(b[1]-g[1])),angle:d}}function S(e,n,r,x,o){const i=2+o;let u=n;const f=e.slice(u,u+o);u+=o;const s=e[u++];let l=0;const c=new Array(s-1);for(let t=0;t<s;t++)l+=e[u++],t<s-1&&(c[t]=l);const a=e.slice(u,u+2*l),h=t(a,c,2);for(let t=0;t<h.length;t++)x.push(h[t]+r.length/i);for(let t=0;t<a.length;t+=2)r.push(a[t],a[t+1],...f);return u+2*l}const T="GENERATE_POLYGON_BUFFERS",_="GENERATE_POINT_BUFFERS",O="GENERATE_LINE_STRING_BUFFERS",U=self;U.onmessage=t=>{const e=t.data;switch(e.type){case _:{const t=3,n=2,r=e.customAttributesSize,x=n+r,o=new Float32Array(e.renderInstructions),i=o.length/x,u=4*i*(r+t),f=new Uint32Array(6*i),s=new Float32Array(u);let l;for(let t=0;t<o.length;t+=x)l=N(o,t,s,f,r,l);const c=Object.assign({vertexBuffer:s.buffer,indexBuffer:f.buffer,renderInstructions:o.buffer},e);U.postMessage(c,[s.buffer,f.buffer,o.buffer]);break}case O:{const t=[],n=[],r=e.customAttributesSize,x=3,o=new Float32Array(e.renderInstructions);let i=0;const u=[1,0,0,1,0,0];let f,s;for(z(u,e.renderInstructionsTransform);i<o.length;){s=Array.from(o.slice(i,i+r)),i+=r,f=o[i++];const e=i,l=i+(f-1)*x,c=o[e]===o[l]&&o[e+1]===o[l+1];let a=0,h=0;for(let r=0;r<f-1;r++){let y=null;r>0?y=i+(r-1)*x:c&&(y=l-x);let p=null;r<f-2?p=i+(r+2)*x:c&&(p=e+x);const v=R(o,i+r*x,i+(r+1)*x,y,p,t,n,s,u,a,h);a=v.length,h=v.angle}i+=f*x}const l=Uint32Array.from(n),c=Float32Array.from(t),a=Object.assign({vertexBuffer:c.buffer,indexBuffer:l.buffer,renderInstructions:o.buffer},e);U.postMessage(a,[c.buffer,l.buffer,o.buffer]);break}case T:{const t=[],n=[],r=e.customAttributesSize,x=new Float32Array(e.renderInstructions);let o=0;for(;o<x.length;)o=S(x,o,t,n,r);const i=Uint32Array.from(n),u=Float32Array.from(t),f=Object.assign({vertexBuffer:u.buffer,indexBuffer:i.buffer,renderInstructions:x.buffer},e);U.postMessage(f,[u.buffer,i.buffer,x.buffer]);break}}};';
|
|
return new Worker(typeof Blob === "undefined" ? "data:application/javascript;base64," + Buffer.from(source, "binary").toString("base64") : URL.createObjectURL(new Blob([source], { type: "application/javascript" })));
|
|
}
|
|
|
|
// node_modules/ol/render/webgl/constants.js
|
|
var WebGLWorkerMessageType = {
|
|
GENERATE_POLYGON_BUFFERS: "GENERATE_POLYGON_BUFFERS",
|
|
GENERATE_POINT_BUFFERS: "GENERATE_POINT_BUFFERS",
|
|
GENERATE_LINE_STRING_BUFFERS: "GENERATE_LINE_STRING_BUFFERS"
|
|
};
|
|
|
|
// node_modules/ol/render/webgl/encodeUtil.js
|
|
function colorEncodeId(id, array) {
|
|
array = array || [];
|
|
const radix = 256;
|
|
const divide = radix - 1;
|
|
array[0] = Math.floor(id / radix / radix / radix) / divide;
|
|
array[1] = Math.floor(id / radix / radix) % radix / divide;
|
|
array[2] = Math.floor(id / radix) % radix / divide;
|
|
array[3] = id % radix / divide;
|
|
return array;
|
|
}
|
|
function colorDecodeId(color) {
|
|
let id = 0;
|
|
const radix = 256;
|
|
const mult = radix - 1;
|
|
id += Math.round(color[0] * radix * radix * radix * mult);
|
|
id += Math.round(color[1] * radix * radix * mult);
|
|
id += Math.round(color[2] * radix * mult);
|
|
id += Math.round(color[3] * mult);
|
|
return id;
|
|
}
|
|
|
|
// node_modules/ol/render/webgl/renderinstructions.js
|
|
function pushCustomAttributesInRenderInstructions(renderInstructions, customAttributes, batchEntry, currentIndex) {
|
|
let shift = 0;
|
|
for (const key in customAttributes) {
|
|
const attr = customAttributes[key];
|
|
const value = attr.callback.call(batchEntry, batchEntry.feature);
|
|
let first = (value == null ? void 0 : value[0]) ?? value;
|
|
if (first === UNDEFINED_PROP_VALUE) {
|
|
console.warn('The "has" operator might return false positives.');
|
|
}
|
|
if (first === void 0) {
|
|
first = UNDEFINED_PROP_VALUE;
|
|
} else if (first === null) {
|
|
first = 0;
|
|
}
|
|
renderInstructions[currentIndex + shift++] = first;
|
|
if (!attr.size || attr.size === 1) {
|
|
continue;
|
|
}
|
|
renderInstructions[currentIndex + shift++] = value[1];
|
|
if (attr.size < 3) {
|
|
continue;
|
|
}
|
|
renderInstructions[currentIndex + shift++] = value[2];
|
|
if (attr.size < 4) {
|
|
continue;
|
|
}
|
|
renderInstructions[currentIndex + shift++] = value[3];
|
|
}
|
|
return shift;
|
|
}
|
|
function getCustomAttributesSize(customAttributes) {
|
|
return Object.keys(customAttributes).reduce(
|
|
(prev, curr) => prev + (customAttributes[curr].size || 1),
|
|
0
|
|
);
|
|
}
|
|
function generatePointRenderInstructions(batch, renderInstructions, customAttributes, transform) {
|
|
const totalInstructionsCount = (2 + getCustomAttributesSize(customAttributes)) * batch.geometriesCount;
|
|
if (!renderInstructions || renderInstructions.length !== totalInstructionsCount) {
|
|
renderInstructions = new Float32Array(totalInstructionsCount);
|
|
}
|
|
const tmpCoords = [];
|
|
let renderIndex = 0;
|
|
for (const featureUid in batch.entries) {
|
|
const batchEntry = batch.entries[featureUid];
|
|
for (let i = 0, ii = batchEntry.flatCoordss.length; i < ii; i++) {
|
|
tmpCoords[0] = batchEntry.flatCoordss[i][0];
|
|
tmpCoords[1] = batchEntry.flatCoordss[i][1];
|
|
apply(transform, tmpCoords);
|
|
renderInstructions[renderIndex++] = tmpCoords[0];
|
|
renderInstructions[renderIndex++] = tmpCoords[1];
|
|
renderIndex += pushCustomAttributesInRenderInstructions(
|
|
renderInstructions,
|
|
customAttributes,
|
|
batchEntry,
|
|
renderIndex
|
|
);
|
|
}
|
|
}
|
|
return renderInstructions;
|
|
}
|
|
function generateLineStringRenderInstructions(batch, renderInstructions, customAttributes, transform) {
|
|
const totalInstructionsCount = 3 * batch.verticesCount + (1 + getCustomAttributesSize(customAttributes)) * batch.geometriesCount;
|
|
if (!renderInstructions || renderInstructions.length !== totalInstructionsCount) {
|
|
renderInstructions = new Float32Array(totalInstructionsCount);
|
|
}
|
|
const flatCoords = [];
|
|
let renderIndex = 0;
|
|
for (const featureUid in batch.entries) {
|
|
const batchEntry = batch.entries[featureUid];
|
|
for (let i = 0, ii = batchEntry.flatCoordss.length; i < ii; i++) {
|
|
flatCoords.length = batchEntry.flatCoordss[i].length;
|
|
transform2D(
|
|
batchEntry.flatCoordss[i],
|
|
0,
|
|
flatCoords.length,
|
|
3,
|
|
transform,
|
|
flatCoords,
|
|
3
|
|
);
|
|
renderIndex += pushCustomAttributesInRenderInstructions(
|
|
renderInstructions,
|
|
customAttributes,
|
|
batchEntry,
|
|
renderIndex
|
|
);
|
|
renderInstructions[renderIndex++] = flatCoords.length / 3;
|
|
for (let j = 0, jj = flatCoords.length; j < jj; j += 3) {
|
|
renderInstructions[renderIndex++] = flatCoords[j];
|
|
renderInstructions[renderIndex++] = flatCoords[j + 1];
|
|
renderInstructions[renderIndex++] = flatCoords[j + 2];
|
|
}
|
|
}
|
|
}
|
|
return renderInstructions;
|
|
}
|
|
function generatePolygonRenderInstructions(batch, renderInstructions, customAttributes, transform) {
|
|
const totalInstructionsCount = 2 * batch.verticesCount + (1 + getCustomAttributesSize(customAttributes)) * batch.geometriesCount + batch.ringsCount;
|
|
if (!renderInstructions || renderInstructions.length !== totalInstructionsCount) {
|
|
renderInstructions = new Float32Array(totalInstructionsCount);
|
|
}
|
|
const flatCoords = [];
|
|
let renderIndex = 0;
|
|
for (const featureUid in batch.entries) {
|
|
const batchEntry = batch.entries[featureUid];
|
|
for (let i = 0, ii = batchEntry.flatCoordss.length; i < ii; i++) {
|
|
flatCoords.length = batchEntry.flatCoordss[i].length;
|
|
transform2D(
|
|
batchEntry.flatCoordss[i],
|
|
0,
|
|
flatCoords.length,
|
|
2,
|
|
transform,
|
|
flatCoords
|
|
);
|
|
renderIndex += pushCustomAttributesInRenderInstructions(
|
|
renderInstructions,
|
|
customAttributes,
|
|
batchEntry,
|
|
renderIndex
|
|
);
|
|
renderInstructions[renderIndex++] = batchEntry.ringsVerticesCounts[i].length;
|
|
for (let j = 0, jj = batchEntry.ringsVerticesCounts[i].length; j < jj; j++) {
|
|
renderInstructions[renderIndex++] = batchEntry.ringsVerticesCounts[i][j];
|
|
}
|
|
for (let j = 0, jj = flatCoords.length; j < jj; j += 2) {
|
|
renderInstructions[renderIndex++] = flatCoords[j];
|
|
renderInstructions[renderIndex++] = flatCoords[j + 1];
|
|
}
|
|
}
|
|
}
|
|
return renderInstructions;
|
|
}
|
|
|
|
// node_modules/ol/render/webgl/style.js
|
|
function computeHash(input) {
|
|
const hash = JSON.stringify(input).split("").reduce((prev, curr) => (prev << 5) - prev + curr.charCodeAt(0), 0);
|
|
return (hash >>> 0).toString();
|
|
}
|
|
function parseCommonSymbolProperties(style, builder, vertContext, prefix) {
|
|
if (`${prefix}radius` in style && prefix !== "icon-") {
|
|
let radius = expressionToGlsl(
|
|
vertContext,
|
|
style[`${prefix}radius`],
|
|
NumberType
|
|
);
|
|
if (`${prefix}radius2` in style) {
|
|
const radius2 = expressionToGlsl(
|
|
vertContext,
|
|
style[`${prefix}radius2`],
|
|
NumberType
|
|
);
|
|
radius = `max(${radius}, ${radius2})`;
|
|
}
|
|
if (`${prefix}stroke-width` in style) {
|
|
radius = `(${radius} + ${expressionToGlsl(
|
|
vertContext,
|
|
style[`${prefix}stroke-width`],
|
|
NumberType
|
|
)} * 0.5)`;
|
|
}
|
|
builder.setSymbolSizeExpression(`vec2(${radius} * 2. + 0.5)`);
|
|
}
|
|
if (`${prefix}scale` in style) {
|
|
const scale = expressionToGlsl(
|
|
vertContext,
|
|
style[`${prefix}scale`],
|
|
SizeType
|
|
);
|
|
builder.setSymbolSizeExpression(
|
|
`${builder.getSymbolSizeExpression()} * ${scale}`
|
|
);
|
|
}
|
|
if (`${prefix}displacement` in style) {
|
|
builder.setSymbolOffsetExpression(
|
|
expressionToGlsl(
|
|
vertContext,
|
|
style[`${prefix}displacement`],
|
|
NumberArrayType
|
|
)
|
|
);
|
|
}
|
|
if (`${prefix}rotation` in style) {
|
|
builder.setSymbolRotationExpression(
|
|
expressionToGlsl(vertContext, style[`${prefix}rotation`], NumberType)
|
|
);
|
|
}
|
|
if (`${prefix}rotate-with-view` in style) {
|
|
builder.setSymbolRotateWithView(!!style[`${prefix}rotate-with-view`]);
|
|
}
|
|
}
|
|
function getColorFromDistanceField(distanceField, fillColor, strokeColor, strokeWidth, opacity) {
|
|
let color = "vec4(0.)";
|
|
if (fillColor !== null) {
|
|
color = fillColor;
|
|
}
|
|
if (strokeColor !== null && strokeWidth !== null) {
|
|
const strokeFillRatio = `smoothstep(-${strokeWidth} + 0.63, -${strokeWidth} - 0.58, ${distanceField})`;
|
|
color = `mix(${strokeColor}, ${color}, ${strokeFillRatio})`;
|
|
}
|
|
const shapeOpacity = `(1.0 - smoothstep(-0.63, 0.58, ${distanceField}))`;
|
|
let result = `${color} * vec4(1.0, 1.0, 1.0, ${shapeOpacity})`;
|
|
if (opacity !== null) {
|
|
result = `${result} * vec4(1.0, 1.0, 1.0, ${opacity})`;
|
|
}
|
|
return result;
|
|
}
|
|
function parseImageProperties(style, builder, uniforms, prefix, textureId) {
|
|
const image = new Image();
|
|
image.crossOrigin = style[`${prefix}cross-origin`] === void 0 ? "anonymous" : style[`${prefix}cross-origin`];
|
|
assert(
|
|
typeof style[`${prefix}src`] === "string",
|
|
`WebGL layers do not support expressions for the ${prefix}src style property`
|
|
);
|
|
image.src = /** @type {string} */
|
|
style[`${prefix}src`];
|
|
uniforms[`u_texture${textureId}_size`] = () => {
|
|
return image.complete ? [image.width, image.height] : [0, 0];
|
|
};
|
|
builder.addUniform(`u_texture${textureId}_size`, "vec2");
|
|
const size = `u_texture${textureId}_size`;
|
|
uniforms[`u_texture${textureId}`] = image;
|
|
builder.addUniform(`u_texture${textureId}`, "sampler2D");
|
|
return size;
|
|
}
|
|
function parseImageOffsetProperties(style, prefix, context, imageSize, sampleSize) {
|
|
let offsetExpression = expressionToGlsl(
|
|
context,
|
|
style[`${prefix}offset`],
|
|
SizeType
|
|
);
|
|
if (`${prefix}offset-origin` in style) {
|
|
switch (style[`${prefix}offset-origin`]) {
|
|
case "top-right":
|
|
offsetExpression = `vec2(${imageSize}.x, 0.) + ${sampleSize} * vec2(-1., 0.) + ${offsetExpression} * vec2(-1., 1.)`;
|
|
break;
|
|
case "bottom-left":
|
|
offsetExpression = `vec2(0., ${imageSize}.y) + ${sampleSize} * vec2(0., -1.) + ${offsetExpression} * vec2(1., -1.)`;
|
|
break;
|
|
case "bottom-right":
|
|
offsetExpression = `${imageSize} - ${sampleSize} - ${offsetExpression}`;
|
|
break;
|
|
default:
|
|
}
|
|
}
|
|
return offsetExpression;
|
|
}
|
|
function parseCircleProperties(style, builder, uniforms, context) {
|
|
context.functions["circleDistanceField"] = `float circleDistanceField(vec2 point, float radius) {
|
|
return length(point) - radius;
|
|
}`;
|
|
parseCommonSymbolProperties(style, builder, context, "circle-");
|
|
let opacity = null;
|
|
if ("circle-opacity" in style) {
|
|
opacity = expressionToGlsl(context, style["circle-opacity"], NumberType);
|
|
}
|
|
let currentPoint = "coordsPx";
|
|
if ("circle-scale" in style) {
|
|
const scale = expressionToGlsl(context, style["circle-scale"], SizeType);
|
|
currentPoint = `coordsPx / ${scale}`;
|
|
}
|
|
let fillColor = null;
|
|
if ("circle-fill-color" in style) {
|
|
fillColor = expressionToGlsl(
|
|
context,
|
|
style["circle-fill-color"],
|
|
ColorType
|
|
);
|
|
}
|
|
let strokeColor = null;
|
|
if ("circle-stroke-color" in style) {
|
|
strokeColor = expressionToGlsl(
|
|
context,
|
|
style["circle-stroke-color"],
|
|
ColorType
|
|
);
|
|
}
|
|
let radius = expressionToGlsl(context, style["circle-radius"], NumberType);
|
|
let strokeWidth = null;
|
|
if ("circle-stroke-width" in style) {
|
|
strokeWidth = expressionToGlsl(
|
|
context,
|
|
style["circle-stroke-width"],
|
|
NumberType
|
|
);
|
|
radius = `(${radius} + ${strokeWidth} * 0.5)`;
|
|
}
|
|
const distanceField = `circleDistanceField(${currentPoint}, ${radius})`;
|
|
const colorExpression = getColorFromDistanceField(
|
|
distanceField,
|
|
fillColor,
|
|
strokeColor,
|
|
strokeWidth,
|
|
opacity
|
|
);
|
|
builder.setSymbolColorExpression(colorExpression);
|
|
}
|
|
function parseShapeProperties(style, builder, uniforms, context) {
|
|
context.functions["round"] = `float round(float v) {
|
|
return sign(v) * floor(abs(v) + 0.5);
|
|
}`;
|
|
context.functions["starDistanceField"] = `float starDistanceField(vec2 point, float numPoints, float radius, float radius2, float angle) {
|
|
float startAngle = -PI * 0.5 + angle; // tip starts upwards and rotates clockwise with angle
|
|
float c = cos(startAngle);
|
|
float s = sin(startAngle);
|
|
vec2 pointRotated = vec2(c * point.x - s * point.y, s * point.x + c * point.y);
|
|
float alpha = TWO_PI / numPoints; // the angle of one sector
|
|
float beta = atan(pointRotated.y, pointRotated.x);
|
|
float gamma = round(beta / alpha) * alpha; // angle in sector
|
|
c = cos(-gamma);
|
|
s = sin(-gamma);
|
|
vec2 inSector = vec2(c * pointRotated.x - s * pointRotated.y, abs(s * pointRotated.x + c * pointRotated.y));
|
|
vec2 tipToPoint = inSector + vec2(-radius, 0.);
|
|
vec2 edgeNormal = vec2(radius2 * sin(alpha * 0.5), -radius2 * cos(alpha * 0.5) + radius);
|
|
return dot(normalize(edgeNormal), tipToPoint);
|
|
}`;
|
|
context.functions["regularDistanceField"] = `float regularDistanceField(vec2 point, float numPoints, float radius, float angle) {
|
|
float startAngle = -PI * 0.5 + angle; // tip starts upwards and rotates clockwise with angle
|
|
float c = cos(startAngle);
|
|
float s = sin(startAngle);
|
|
vec2 pointRotated = vec2(c * point.x - s * point.y, s * point.x + c * point.y);
|
|
float alpha = TWO_PI / numPoints; // the angle of one sector
|
|
float radiusIn = radius * cos(PI / numPoints);
|
|
float beta = atan(pointRotated.y, pointRotated.x);
|
|
float gamma = round((beta - alpha * 0.5) / alpha) * alpha + alpha * 0.5; // angle in sector from mid
|
|
c = cos(-gamma);
|
|
s = sin(-gamma);
|
|
vec2 inSector = vec2(c * pointRotated.x - s * pointRotated.y, abs(s * pointRotated.x + c * pointRotated.y));
|
|
return inSector.x - radiusIn;
|
|
}`;
|
|
parseCommonSymbolProperties(style, builder, context, "shape-");
|
|
let opacity = null;
|
|
if ("shape-opacity" in style) {
|
|
opacity = expressionToGlsl(context, style["shape-opacity"], NumberType);
|
|
}
|
|
let currentPoint = "coordsPx";
|
|
if ("shape-scale" in style) {
|
|
const scale = expressionToGlsl(context, style["shape-scale"], SizeType);
|
|
currentPoint = `coordsPx / ${scale}`;
|
|
}
|
|
let fillColor = null;
|
|
if ("shape-fill-color" in style) {
|
|
fillColor = expressionToGlsl(context, style["shape-fill-color"], ColorType);
|
|
}
|
|
let strokeColor = null;
|
|
if ("shape-stroke-color" in style) {
|
|
strokeColor = expressionToGlsl(
|
|
context,
|
|
style["shape-stroke-color"],
|
|
ColorType
|
|
);
|
|
}
|
|
let strokeWidth = null;
|
|
if ("shape-stroke-width" in style) {
|
|
strokeWidth = expressionToGlsl(
|
|
context,
|
|
style["shape-stroke-width"],
|
|
NumberType
|
|
);
|
|
}
|
|
const numPoints = expressionToGlsl(
|
|
context,
|
|
style["shape-points"],
|
|
NumberType
|
|
);
|
|
let angle = "0.";
|
|
if ("shape-angle" in style) {
|
|
angle = expressionToGlsl(context, style["shape-angle"], NumberType);
|
|
}
|
|
let shapeField;
|
|
let radius = expressionToGlsl(context, style["shape-radius"], NumberType);
|
|
if (strokeWidth !== null) {
|
|
radius = `${radius} + ${strokeWidth} * 0.5`;
|
|
}
|
|
if ("shape-radius2" in style) {
|
|
let radius2 = expressionToGlsl(context, style["shape-radius2"], NumberType);
|
|
if (strokeWidth !== null) {
|
|
radius2 = `${radius2} + ${strokeWidth} * 0.5`;
|
|
}
|
|
shapeField = `starDistanceField(${currentPoint}, ${numPoints}, ${radius}, ${radius2}, ${angle})`;
|
|
} else {
|
|
shapeField = `regularDistanceField(${currentPoint}, ${numPoints}, ${radius}, ${angle})`;
|
|
}
|
|
const colorExpression = getColorFromDistanceField(
|
|
shapeField,
|
|
fillColor,
|
|
strokeColor,
|
|
strokeWidth,
|
|
opacity
|
|
);
|
|
builder.setSymbolColorExpression(colorExpression);
|
|
}
|
|
function parseIconProperties(style, builder, uniforms, context) {
|
|
let color = "vec4(1.0)";
|
|
if ("icon-color" in style) {
|
|
color = expressionToGlsl(context, style["icon-color"], ColorType);
|
|
}
|
|
if ("icon-opacity" in style) {
|
|
color = `${color} * vec4(1.0, 1.0, 1.0, ${expressionToGlsl(
|
|
context,
|
|
style["icon-opacity"],
|
|
NumberType
|
|
)})`;
|
|
}
|
|
const textureId = computeHash(style["icon-src"]);
|
|
const sizeExpression = parseImageProperties(
|
|
style,
|
|
builder,
|
|
uniforms,
|
|
"icon-",
|
|
textureId
|
|
);
|
|
builder.setSymbolColorExpression(
|
|
`${color} * texture2D(u_texture${textureId}, v_texCoord)`
|
|
).setSymbolSizeExpression(sizeExpression);
|
|
if ("icon-width" in style && "icon-height" in style) {
|
|
builder.setSymbolSizeExpression(
|
|
`vec2(${expressionToGlsl(
|
|
context,
|
|
style["icon-width"],
|
|
NumberType
|
|
)}, ${expressionToGlsl(context, style["icon-height"], NumberType)})`
|
|
);
|
|
}
|
|
if ("icon-offset" in style && "icon-size" in style) {
|
|
const sampleSize = expressionToGlsl(
|
|
context,
|
|
style["icon-size"],
|
|
NumberArrayType
|
|
);
|
|
const fullsize = builder.getSymbolSizeExpression();
|
|
builder.setSymbolSizeExpression(sampleSize);
|
|
const offset = parseImageOffsetProperties(
|
|
style,
|
|
"icon-",
|
|
context,
|
|
"v_quadSizePx",
|
|
sampleSize
|
|
);
|
|
builder.setTextureCoordinateExpression(
|
|
`(vec4((${offset}).xyxy) + vec4(0., 0., ${sampleSize})) / (${fullsize}).xyxy`
|
|
);
|
|
}
|
|
parseCommonSymbolProperties(style, builder, context, "icon-");
|
|
if ("icon-anchor" in style) {
|
|
const anchor = expressionToGlsl(
|
|
context,
|
|
style["icon-anchor"],
|
|
NumberArrayType
|
|
);
|
|
let scale = `1.0`;
|
|
if (`icon-scale` in style) {
|
|
scale = expressionToGlsl(context, style[`icon-scale`], SizeType);
|
|
}
|
|
let shiftPx;
|
|
if (style["icon-anchor-x-units"] === "pixels" && style["icon-anchor-y-units"] === "pixels") {
|
|
shiftPx = `${anchor} * ${scale}`;
|
|
} else if (style["icon-anchor-x-units"] === "pixels") {
|
|
shiftPx = `${anchor} * vec2(vec2(${scale}).x, v_quadSizePx.y)`;
|
|
} else if (style["icon-anchor-y-units"] === "pixels") {
|
|
shiftPx = `${anchor} * vec2(v_quadSizePx.x, vec2(${scale}).x)`;
|
|
} else {
|
|
shiftPx = `${anchor} * v_quadSizePx`;
|
|
}
|
|
let offsetPx = `v_quadSizePx * vec2(0.5, -0.5) + ${shiftPx} * vec2(-1., 1.)`;
|
|
if ("icon-anchor-origin" in style) {
|
|
switch (style["icon-anchor-origin"]) {
|
|
case "top-right":
|
|
offsetPx = `v_quadSizePx * -0.5 + ${shiftPx}`;
|
|
break;
|
|
case "bottom-left":
|
|
offsetPx = `v_quadSizePx * 0.5 - ${shiftPx}`;
|
|
break;
|
|
case "bottom-right":
|
|
offsetPx = `v_quadSizePx * vec2(-0.5, 0.5) + ${shiftPx} * vec2(1., -1.)`;
|
|
break;
|
|
default:
|
|
}
|
|
}
|
|
builder.setSymbolOffsetExpression(
|
|
`${builder.getSymbolOffsetExpression()} + ${offsetPx}`
|
|
);
|
|
}
|
|
}
|
|
function parseStrokeProperties(style, builder, uniforms, context) {
|
|
if ("stroke-color" in style) {
|
|
builder.setStrokeColorExpression(
|
|
expressionToGlsl(context, style["stroke-color"], ColorType)
|
|
);
|
|
}
|
|
if ("stroke-pattern-src" in style) {
|
|
const textureId = computeHash(style["stroke-pattern-src"]);
|
|
const sizeExpression = parseImageProperties(
|
|
style,
|
|
builder,
|
|
uniforms,
|
|
"stroke-pattern-",
|
|
textureId
|
|
);
|
|
let sampleSizeExpression = sizeExpression;
|
|
let offsetExpression = "vec2(0.)";
|
|
if ("stroke-pattern-offset" in style && "stroke-pattern-size" in style) {
|
|
sampleSizeExpression = expressionToGlsl(
|
|
context,
|
|
style[`stroke-pattern-size`],
|
|
NumberArrayType
|
|
);
|
|
offsetExpression = parseImageOffsetProperties(
|
|
style,
|
|
"stroke-pattern-",
|
|
context,
|
|
sizeExpression,
|
|
sampleSizeExpression
|
|
);
|
|
}
|
|
let spacingExpression = "0.";
|
|
if ("stroke-pattern-spacing" in style) {
|
|
spacingExpression = expressionToGlsl(
|
|
context,
|
|
style["stroke-pattern-spacing"],
|
|
NumberType
|
|
);
|
|
}
|
|
context.functions["sampleStrokePattern"] = `vec4 sampleStrokePattern(sampler2D texture, vec2 textureSize, vec2 textureOffset, vec2 sampleSize, float spacingPx, float currentLengthPx, float currentRadiusRatio, float lineWidth) {
|
|
float currentLengthScaled = currentLengthPx * sampleSize.y / lineWidth;
|
|
float spacingScaled = spacingPx * sampleSize.y / lineWidth;
|
|
float uCoordPx = mod(currentLengthScaled, (sampleSize.x + spacingScaled));
|
|
// make sure that we're not sampling too close to the borders to avoid interpolation with outside pixels
|
|
uCoordPx = clamp(uCoordPx, 0.5, sampleSize.x - 0.5);
|
|
float vCoordPx = (-currentRadiusRatio * 0.5 + 0.5) * sampleSize.y;
|
|
vec2 texCoord = (vec2(uCoordPx, vCoordPx) + textureOffset) / textureSize;
|
|
return texture2D(texture, texCoord);
|
|
}`;
|
|
const textureName = `u_texture${textureId}`;
|
|
let tintExpression = "1.";
|
|
if ("stroke-color" in style) {
|
|
tintExpression = builder.getStrokeColorExpression();
|
|
}
|
|
builder.setStrokeColorExpression(
|
|
`${tintExpression} * sampleStrokePattern(${textureName}, ${sizeExpression}, ${offsetExpression}, ${sampleSizeExpression}, ${spacingExpression}, currentLengthPx, currentRadiusRatio, v_width)`
|
|
);
|
|
}
|
|
if ("stroke-width" in style) {
|
|
builder.setStrokeWidthExpression(
|
|
expressionToGlsl(context, style["stroke-width"], NumberType)
|
|
);
|
|
}
|
|
if ("stroke-offset" in style) {
|
|
builder.setStrokeOffsetExpression(
|
|
expressionToGlsl(context, style["stroke-offset"], NumberType)
|
|
);
|
|
}
|
|
if ("stroke-line-cap" in style) {
|
|
builder.setStrokeCapExpression(
|
|
expressionToGlsl(context, style["stroke-line-cap"], StringType)
|
|
);
|
|
}
|
|
if ("stroke-line-join" in style) {
|
|
builder.setStrokeJoinExpression(
|
|
expressionToGlsl(context, style["stroke-line-join"], StringType)
|
|
);
|
|
}
|
|
if ("stroke-miter-limit" in style) {
|
|
builder.setStrokeMiterLimitExpression(
|
|
expressionToGlsl(context, style["stroke-miter-limit"], NumberType)
|
|
);
|
|
}
|
|
if ("stroke-line-dash" in style) {
|
|
context.functions["getSingleDashDistance"] = `float getSingleDashDistance(float distance, float radius, float dashOffset, float dashLength, float dashLengthTotal, float capType, float lineWidth) {
|
|
float localDistance = mod(distance, dashLengthTotal);
|
|
float distanceSegment = abs(localDistance - dashOffset - dashLength * 0.5) - dashLength * 0.5;
|
|
distanceSegment = min(distanceSegment, dashLengthTotal - localDistance);
|
|
if (capType == ${stringToGlsl("square")}) {
|
|
distanceSegment -= lineWidth * 0.5;
|
|
} else if (capType == ${stringToGlsl("round")}) {
|
|
distanceSegment = min(distanceSegment, sqrt(distanceSegment * distanceSegment + radius * radius) - lineWidth * 0.5);
|
|
}
|
|
return distanceSegment;
|
|
}`;
|
|
let dashPattern = style["stroke-line-dash"].map(
|
|
(v) => expressionToGlsl(context, v, NumberType)
|
|
);
|
|
if (dashPattern.length % 2 === 1) {
|
|
dashPattern = [...dashPattern, ...dashPattern];
|
|
}
|
|
let offsetExpression = "0.";
|
|
if ("stroke-line-dash-offset" in style) {
|
|
offsetExpression = expressionToGlsl(
|
|
context,
|
|
style["stroke-line-dash-offset"],
|
|
NumberType
|
|
);
|
|
}
|
|
const uniqueDashKey = computeHash(style["stroke-line-dash"]);
|
|
const dashFunctionName = `dashDistanceField_${uniqueDashKey}`;
|
|
const dashLengthsParamsDef = dashPattern.map((v, i) => `float dashLength${i}`).join(", ");
|
|
const totalLengthDef = dashPattern.map((v, i) => `dashLength${i}`).join(" + ");
|
|
let currentDashOffset = "0.";
|
|
let distanceExpression = `getSingleDashDistance(distance, radius, ${currentDashOffset}, dashLength0, totalDashLength, capType, lineWidth)`;
|
|
for (let i = 2; i < dashPattern.length; i += 2) {
|
|
currentDashOffset = `${currentDashOffset} + dashLength${i - 2} + dashLength${i - 1}`;
|
|
distanceExpression = `min(${distanceExpression}, getSingleDashDistance(distance, radius, ${currentDashOffset}, dashLength${i}, totalDashLength, capType, lineWidth))`;
|
|
}
|
|
context.functions[dashFunctionName] = `float ${dashFunctionName}(float distance, float radius, float capType, float lineWidth, ${dashLengthsParamsDef}) {
|
|
float totalDashLength = ${totalLengthDef};
|
|
return ${distanceExpression};
|
|
}`;
|
|
const dashLengthsCalls = dashPattern.map((v, i) => `${v}`).join(", ");
|
|
builder.setStrokeDistanceFieldExpression(
|
|
`${dashFunctionName}(currentLengthPx + ${offsetExpression}, currentRadiusPx, capType, v_width, ${dashLengthsCalls})`
|
|
);
|
|
}
|
|
}
|
|
function parseFillProperties(style, builder, uniforms, context) {
|
|
if ("fill-color" in style) {
|
|
builder.setFillColorExpression(
|
|
expressionToGlsl(context, style["fill-color"], ColorType)
|
|
);
|
|
}
|
|
if ("fill-pattern-src" in style) {
|
|
const textureId = computeHash(style["fill-pattern-src"]);
|
|
const sizeExpression = parseImageProperties(
|
|
style,
|
|
builder,
|
|
uniforms,
|
|
"fill-pattern-",
|
|
textureId
|
|
);
|
|
let sampleSizeExpression = sizeExpression;
|
|
let offsetExpression = "vec2(0.)";
|
|
if ("fill-pattern-offset" in style && "fill-pattern-size" in style) {
|
|
sampleSizeExpression = expressionToGlsl(
|
|
context,
|
|
style[`fill-pattern-size`],
|
|
NumberArrayType
|
|
);
|
|
offsetExpression = parseImageOffsetProperties(
|
|
style,
|
|
"fill-pattern-",
|
|
context,
|
|
sizeExpression,
|
|
sampleSizeExpression
|
|
);
|
|
}
|
|
context.functions["sampleFillPattern"] = `vec4 sampleFillPattern(sampler2D texture, vec2 textureSize, vec2 textureOffset, vec2 sampleSize, vec2 pxOrigin, vec2 pxPosition) {
|
|
float scaleRatio = pow(2., mod(u_zoom + 0.5, 1.) - 0.5);
|
|
vec2 pxRelativePos = pxPosition - pxOrigin;
|
|
// rotate the relative position from origin by the current view rotation
|
|
pxRelativePos = vec2(pxRelativePos.x * cos(u_rotation) - pxRelativePos.y * sin(u_rotation), pxRelativePos.x * sin(u_rotation) + pxRelativePos.y * cos(u_rotation));
|
|
// sample position is computed according to the sample offset & size
|
|
vec2 samplePos = mod(pxRelativePos / scaleRatio, sampleSize);
|
|
// also make sure that we're not sampling too close to the borders to avoid interpolation with outside pixels
|
|
samplePos = clamp(samplePos, vec2(0.5), sampleSize - vec2(0.5));
|
|
samplePos.y = sampleSize.y - samplePos.y; // invert y axis so that images appear upright
|
|
return texture2D(texture, (samplePos + textureOffset) / textureSize);
|
|
}`;
|
|
const textureName = `u_texture${textureId}`;
|
|
let tintExpression = "1.";
|
|
if ("fill-color" in style) {
|
|
tintExpression = builder.getFillColorExpression();
|
|
}
|
|
builder.setFillColorExpression(
|
|
`${tintExpression} * sampleFillPattern(${textureName}, ${sizeExpression}, ${offsetExpression}, ${sampleSizeExpression}, pxOrigin, pxPos)`
|
|
);
|
|
}
|
|
}
|
|
function parseLiteralStyle(style, variables, filter) {
|
|
const context = newCompilationContext();
|
|
const builder = new ShaderBuilder();
|
|
const uniforms = {};
|
|
if ("icon-src" in style) {
|
|
parseIconProperties(style, builder, uniforms, context);
|
|
} else if ("shape-points" in style) {
|
|
parseShapeProperties(style, builder, uniforms, context);
|
|
} else if ("circle-radius" in style) {
|
|
parseCircleProperties(style, builder, uniforms, context);
|
|
}
|
|
parseStrokeProperties(style, builder, uniforms, context);
|
|
parseFillProperties(style, builder, uniforms, context);
|
|
if (filter) {
|
|
const parsedFilter = expressionToGlsl(context, filter, BooleanType);
|
|
builder.setFragmentDiscardExpression(`!${parsedFilter}`);
|
|
}
|
|
const attributes = {};
|
|
function defineSpecialInput(contextPropName, glslPropName, type, callback) {
|
|
if (!context[contextPropName]) {
|
|
return;
|
|
}
|
|
const glslType = getGlslTypeFromType(type);
|
|
const attrSize = getGlslSizeFromType(type);
|
|
builder.addAttribute(`a_${glslPropName}`, glslType);
|
|
attributes[glslPropName] = {
|
|
size: attrSize,
|
|
callback
|
|
};
|
|
}
|
|
defineSpecialInput(
|
|
"geometryType",
|
|
GEOMETRY_TYPE_PROPERTY_NAME,
|
|
StringType,
|
|
(feature) => getStringNumberEquivalent(computeGeometryType(feature.getGeometry()))
|
|
);
|
|
defineSpecialInput(
|
|
"featureId",
|
|
FEATURE_ID_PROPERTY_NAME,
|
|
StringType | NumberType,
|
|
(feature) => {
|
|
const id = feature.getId() ?? null;
|
|
return typeof id === "string" ? getStringNumberEquivalent(id) : id;
|
|
}
|
|
);
|
|
applyContextToBuilder(builder, context);
|
|
return {
|
|
builder,
|
|
attributes: { ...attributes, ...generateAttributesFromContext(context) },
|
|
uniforms: {
|
|
...uniforms,
|
|
...generateUniformsFromContext(context, variables)
|
|
}
|
|
};
|
|
}
|
|
function breakDownFlatStyle(style) {
|
|
const asArray = Array.isArray(style) ? style : [style];
|
|
if ("style" in asArray[0]) {
|
|
const styles = [];
|
|
const rules = (
|
|
/** @type {Array<import('../../style/flat.js').Rule>} */
|
|
asArray
|
|
);
|
|
const previousFilters = [];
|
|
for (const rule of rules) {
|
|
const ruleStyles = Array.isArray(rule.style) ? rule.style : [rule.style];
|
|
let currentFilter = rule.filter;
|
|
if (rule.else && previousFilters.length) {
|
|
currentFilter = [
|
|
"all",
|
|
...previousFilters.map((filter) => ["!", filter])
|
|
];
|
|
if (rule.filter) {
|
|
currentFilter.push(rule.filter);
|
|
}
|
|
if (currentFilter.length < 3) {
|
|
currentFilter = currentFilter[1];
|
|
}
|
|
}
|
|
if (rule.filter) {
|
|
previousFilters.push(rule.filter);
|
|
}
|
|
const stylesWithFilters = ruleStyles.map((style2) => ({
|
|
style: style2,
|
|
...currentFilter && { filter: currentFilter }
|
|
}));
|
|
styles.push(...stylesWithFilters);
|
|
}
|
|
return styles;
|
|
}
|
|
if ("builder" in asArray[0]) {
|
|
return (
|
|
/** @type {Array<StyleAsShaders>} */
|
|
asArray
|
|
);
|
|
}
|
|
return asArray.map(
|
|
(style2) => (
|
|
/** @type {StyleAsRule} */
|
|
{
|
|
style: style2
|
|
}
|
|
)
|
|
);
|
|
}
|
|
|
|
// node_modules/ol/render/webgl/VectorStyleRenderer.js
|
|
var tmpColor = [];
|
|
var WEBGL_WORKER;
|
|
function getWebGLWorker() {
|
|
if (!WEBGL_WORKER) {
|
|
WEBGL_WORKER = create3();
|
|
}
|
|
return WEBGL_WORKER;
|
|
}
|
|
var workerMessageCounter = 0;
|
|
var Attributes = {
|
|
POSITION: "a_position",
|
|
INDEX: "a_index",
|
|
SEGMENT_START: "a_segmentStart",
|
|
SEGMENT_END: "a_segmentEnd",
|
|
MEASURE_START: "a_measureStart",
|
|
MEASURE_END: "a_measureEnd",
|
|
PARAMETERS: "a_parameters",
|
|
JOIN_ANGLES: "a_joinAngles",
|
|
DISTANCE: "a_distance"
|
|
};
|
|
var VectorStyleRenderer = class {
|
|
/**
|
|
* @param {VectorStyle} styleOrShaders Literal style or custom shaders
|
|
* @param {import('../../style/flat.js').StyleVariables} variables Style variables
|
|
* @param {import('../../webgl/Helper.js').default} helper Helper
|
|
* @param {boolean} [enableHitDetection] Whether to enable the hit detection (needs compatible shader)
|
|
* @param {import("../../expr/expression.js").ExpressionValue} [filter] Optional filter expression
|
|
*/
|
|
constructor(styleOrShaders, variables, helper, enableHitDetection, filter) {
|
|
this.helper_;
|
|
this.hitDetectionEnabled_ = !!enableHitDetection;
|
|
let asShaders = (
|
|
/** @type {AsShaders} */
|
|
styleOrShaders
|
|
);
|
|
const isShaders = "builder" in styleOrShaders;
|
|
if (!isShaders) {
|
|
const asRule = (
|
|
/** @type {AsRule} */
|
|
styleOrShaders
|
|
);
|
|
const parseResult = parseLiteralStyle(
|
|
asRule.style,
|
|
variables,
|
|
asRule.filter
|
|
);
|
|
asShaders = {
|
|
builder: parseResult.builder,
|
|
attributes: parseResult.attributes,
|
|
uniforms: parseResult.uniforms
|
|
};
|
|
}
|
|
this.fillProgram_;
|
|
this.strokeProgram_;
|
|
this.symbolProgram_;
|
|
this.hasFill_ = !!asShaders.builder.getFillVertexShader();
|
|
if (this.hasFill_) {
|
|
this.fillVertexShader_ = asShaders.builder.getFillVertexShader();
|
|
this.fillFragmentShader_ = asShaders.builder.getFillFragmentShader();
|
|
}
|
|
this.hasStroke_ = !!asShaders.builder.getStrokeVertexShader();
|
|
if (this.hasStroke_) {
|
|
this.strokeVertexShader_ = asShaders.builder.getStrokeVertexShader();
|
|
this.strokeFragmentShader_ = asShaders.builder.getStrokeFragmentShader();
|
|
}
|
|
this.hasSymbol_ = !!asShaders.builder.getSymbolVertexShader();
|
|
if (this.hasSymbol_) {
|
|
this.symbolVertexShader_ = asShaders.builder.getSymbolVertexShader();
|
|
this.symbolFragmentShader_ = asShaders.builder.getSymbolFragmentShader();
|
|
}
|
|
this.featureFilter_ = null;
|
|
if (filter) {
|
|
this.featureFilter_ = this.computeFeatureFilter(filter);
|
|
}
|
|
const hitDetectionAttributes = this.hitDetectionEnabled_ ? {
|
|
hitColor: {
|
|
callback() {
|
|
return colorEncodeId(this.ref, tmpColor);
|
|
},
|
|
size: 4
|
|
}
|
|
} : {};
|
|
this.customAttributes_ = Object.assign(
|
|
{},
|
|
hitDetectionAttributes,
|
|
asShaders.attributes
|
|
);
|
|
this.uniforms_ = asShaders.uniforms;
|
|
const customAttributesDesc = Object.entries(this.customAttributes_).map(
|
|
([name, value]) => ({
|
|
name: `a_${name}`,
|
|
size: value.size || 1,
|
|
type: AttributeType.FLOAT
|
|
})
|
|
);
|
|
this.polygonAttributesDesc_ = [
|
|
{
|
|
name: Attributes.POSITION,
|
|
size: 2,
|
|
type: AttributeType.FLOAT
|
|
},
|
|
...customAttributesDesc
|
|
];
|
|
this.lineStringAttributesDesc_ = [
|
|
{
|
|
name: Attributes.SEGMENT_START,
|
|
size: 2,
|
|
type: AttributeType.FLOAT
|
|
},
|
|
{
|
|
name: Attributes.MEASURE_START,
|
|
size: 1,
|
|
type: AttributeType.FLOAT
|
|
},
|
|
{
|
|
name: Attributes.SEGMENT_END,
|
|
size: 2,
|
|
type: AttributeType.FLOAT
|
|
},
|
|
{
|
|
name: Attributes.MEASURE_END,
|
|
size: 1,
|
|
type: AttributeType.FLOAT
|
|
},
|
|
{
|
|
name: Attributes.JOIN_ANGLES,
|
|
size: 2,
|
|
type: AttributeType.FLOAT
|
|
},
|
|
{
|
|
name: Attributes.DISTANCE,
|
|
size: 1,
|
|
type: AttributeType.FLOAT
|
|
},
|
|
{
|
|
name: Attributes.PARAMETERS,
|
|
size: 1,
|
|
type: AttributeType.FLOAT
|
|
},
|
|
...customAttributesDesc
|
|
];
|
|
this.pointAttributesDesc_ = [
|
|
{
|
|
name: Attributes.POSITION,
|
|
size: 2,
|
|
type: AttributeType.FLOAT
|
|
},
|
|
{
|
|
name: Attributes.INDEX,
|
|
size: 1,
|
|
type: AttributeType.FLOAT
|
|
},
|
|
...customAttributesDesc
|
|
];
|
|
this.setHelper(helper);
|
|
}
|
|
/**
|
|
* Will apply the style filter when generating geometry batches (if it can be evaluated outside a map context)
|
|
* @param {import("../../expr/expression.js").ExpressionValue} filter Style filter
|
|
* @return {function(import('../../Feature.js').FeatureLike): boolean} Feature filter
|
|
* @private
|
|
*/
|
|
computeFeatureFilter(filter) {
|
|
const parsingContext = newParsingContext();
|
|
let compiled;
|
|
try {
|
|
compiled = buildExpression(filter, BooleanType, parsingContext);
|
|
} catch {
|
|
return null;
|
|
}
|
|
if (parsingContext.mapState || parsingContext.variables.size > 0) {
|
|
return null;
|
|
}
|
|
const evalContext = newEvaluationContext();
|
|
return (feature) => {
|
|
evalContext.properties = feature.getPropertiesInternal();
|
|
if (parsingContext.featureId) {
|
|
const id = feature.getId();
|
|
if (id !== void 0) {
|
|
evalContext.featureId = id;
|
|
} else {
|
|
evalContext.featureId = null;
|
|
}
|
|
}
|
|
evalContext.geometryType = computeGeometryType(feature.getGeometry());
|
|
return (
|
|
/** @type {boolean} */
|
|
compiled(evalContext)
|
|
);
|
|
};
|
|
}
|
|
/**
|
|
* @param {import('./MixedGeometryBatch.js').default} geometryBatch Geometry batch
|
|
* @param {import("../../transform.js").Transform} transform Transform to apply to coordinates
|
|
* @return {Promise<WebGLBuffers|null>} A promise resolving to WebGL buffers; returns null if buffers are empty
|
|
*/
|
|
async generateBuffers(geometryBatch, transform) {
|
|
let filteredBatch = geometryBatch;
|
|
if (this.featureFilter_) {
|
|
filteredBatch = filteredBatch.filter(this.featureFilter_);
|
|
if (filteredBatch.isEmpty()) {
|
|
return null;
|
|
}
|
|
}
|
|
const renderInstructions = this.generateRenderInstructions_(
|
|
filteredBatch,
|
|
transform
|
|
);
|
|
const [polygonBuffers, lineStringBuffers, pointBuffers] = await Promise.all(
|
|
[
|
|
this.generateBuffersForType_(
|
|
renderInstructions.polygonInstructions,
|
|
"Polygon",
|
|
transform
|
|
),
|
|
this.generateBuffersForType_(
|
|
renderInstructions.lineStringInstructions,
|
|
"LineString",
|
|
transform
|
|
),
|
|
this.generateBuffersForType_(
|
|
renderInstructions.pointInstructions,
|
|
"Point",
|
|
transform
|
|
)
|
|
]
|
|
);
|
|
const invertVerticesTransform = makeInverse(
|
|
create(),
|
|
transform
|
|
);
|
|
return {
|
|
polygonBuffers,
|
|
lineStringBuffers,
|
|
pointBuffers,
|
|
invertVerticesTransform
|
|
};
|
|
}
|
|
/**
|
|
* @param {import('./MixedGeometryBatch.js').default} geometryBatch Geometry batch
|
|
* @param {import("../../transform.js").Transform} transform Transform to apply to coordinates
|
|
* @return {RenderInstructions} Render instructions
|
|
* @private
|
|
*/
|
|
generateRenderInstructions_(geometryBatch, transform) {
|
|
const polygonInstructions = this.hasFill_ ? generatePolygonRenderInstructions(
|
|
geometryBatch.polygonBatch,
|
|
new Float32Array(0),
|
|
this.customAttributes_,
|
|
transform
|
|
) : null;
|
|
const lineStringInstructions = this.hasStroke_ ? generateLineStringRenderInstructions(
|
|
geometryBatch.lineStringBatch,
|
|
new Float32Array(0),
|
|
this.customAttributes_,
|
|
transform
|
|
) : null;
|
|
const pointInstructions = this.hasSymbol_ ? generatePointRenderInstructions(
|
|
geometryBatch.pointBatch,
|
|
new Float32Array(0),
|
|
this.customAttributes_,
|
|
transform
|
|
) : null;
|
|
return {
|
|
polygonInstructions,
|
|
lineStringInstructions,
|
|
pointInstructions
|
|
};
|
|
}
|
|
/**
|
|
* @param {Float32Array|null} renderInstructions Render instructions
|
|
* @param {import("../../geom/Geometry.js").Type} geometryType Geometry type
|
|
* @param {import("../../transform.js").Transform} transform Transform to apply to coordinates
|
|
* @return {Promise<Array<WebGLArrayBuffer>>|null} Indices buffer and vertices buffer; null if nothing to render
|
|
* @private
|
|
*/
|
|
generateBuffersForType_(renderInstructions, geometryType, transform) {
|
|
if (renderInstructions === null) {
|
|
return null;
|
|
}
|
|
const messageId = workerMessageCounter++;
|
|
let messageType;
|
|
switch (geometryType) {
|
|
case "Polygon":
|
|
messageType = WebGLWorkerMessageType.GENERATE_POLYGON_BUFFERS;
|
|
break;
|
|
case "LineString":
|
|
messageType = WebGLWorkerMessageType.GENERATE_LINE_STRING_BUFFERS;
|
|
break;
|
|
case "Point":
|
|
messageType = WebGLWorkerMessageType.GENERATE_POINT_BUFFERS;
|
|
break;
|
|
default:
|
|
}
|
|
const message = {
|
|
id: messageId,
|
|
type: messageType,
|
|
renderInstructions: renderInstructions.buffer,
|
|
renderInstructionsTransform: transform,
|
|
customAttributesSize: getCustomAttributesSize(this.customAttributes_)
|
|
};
|
|
const WEBGL_WORKER2 = getWebGLWorker();
|
|
WEBGL_WORKER2.postMessage(message, [renderInstructions.buffer]);
|
|
renderInstructions = null;
|
|
return new Promise((resolve) => {
|
|
const handleMessage = (event) => {
|
|
const received = event.data;
|
|
if (received.id !== messageId) {
|
|
return;
|
|
}
|
|
WEBGL_WORKER2.removeEventListener("message", handleMessage);
|
|
if (!this.helper_.getGL()) {
|
|
return;
|
|
}
|
|
const verticesBuffer = new Buffer_default(
|
|
ARRAY_BUFFER,
|
|
DYNAMIC_DRAW
|
|
).fromArrayBuffer(received.vertexBuffer);
|
|
const indicesBuffer = new Buffer_default(
|
|
ELEMENT_ARRAY_BUFFER,
|
|
DYNAMIC_DRAW
|
|
).fromArrayBuffer(received.indexBuffer);
|
|
this.helper_.flushBufferData(verticesBuffer);
|
|
this.helper_.flushBufferData(indicesBuffer);
|
|
resolve([indicesBuffer, verticesBuffer]);
|
|
};
|
|
WEBGL_WORKER2.addEventListener("message", handleMessage);
|
|
});
|
|
}
|
|
/**
|
|
* Render the geometries in the given buffers.
|
|
* @param {WebGLBuffers} buffers WebGL Buffers to draw
|
|
* @param {import("../../Map.js").FrameState} frameState Frame state
|
|
* @param {function(): void} preRenderCallback This callback will be called right before drawing, and can be used to set uniforms
|
|
*/
|
|
render(buffers, frameState, preRenderCallback) {
|
|
this.hasFill_ && this.renderInternal_(
|
|
buffers.polygonBuffers[0],
|
|
buffers.polygonBuffers[1],
|
|
this.fillProgram_,
|
|
this.polygonAttributesDesc_,
|
|
frameState,
|
|
preRenderCallback
|
|
);
|
|
this.hasStroke_ && this.renderInternal_(
|
|
buffers.lineStringBuffers[0],
|
|
buffers.lineStringBuffers[1],
|
|
this.strokeProgram_,
|
|
this.lineStringAttributesDesc_,
|
|
frameState,
|
|
preRenderCallback
|
|
);
|
|
this.hasSymbol_ && this.renderInternal_(
|
|
buffers.pointBuffers[0],
|
|
buffers.pointBuffers[1],
|
|
this.symbolProgram_,
|
|
this.pointAttributesDesc_,
|
|
frameState,
|
|
preRenderCallback
|
|
);
|
|
}
|
|
/**
|
|
* @param {WebGLArrayBuffer} indicesBuffer Indices buffer
|
|
* @param {WebGLArrayBuffer} verticesBuffer Vertices buffer
|
|
* @param {WebGLProgram} program Program
|
|
* @param {Array<import('../../webgl/Helper.js').AttributeDescription>} attributes Attribute descriptions
|
|
* @param {import("../../Map.js").FrameState} frameState Frame state.
|
|
* @param {function(): void} preRenderCallback This callback will be called right before drawing, and can be used to set uniforms
|
|
* @private
|
|
*/
|
|
renderInternal_(indicesBuffer, verticesBuffer, program, attributes, frameState, preRenderCallback) {
|
|
const renderCount = indicesBuffer.getSize();
|
|
if (renderCount === 0) {
|
|
return;
|
|
}
|
|
this.helper_.useProgram(program, frameState);
|
|
this.helper_.bindBuffer(verticesBuffer);
|
|
this.helper_.bindBuffer(indicesBuffer);
|
|
this.helper_.enableAttributes(attributes);
|
|
preRenderCallback();
|
|
this.helper_.drawElements(0, renderCount);
|
|
}
|
|
/**
|
|
* @param {import('../../webgl/Helper.js').default} helper Helper
|
|
* @param {WebGLBuffers} buffers WebGL Buffers to reload if any
|
|
*/
|
|
setHelper(helper, buffers = null) {
|
|
this.helper_ = helper;
|
|
if (this.hasFill_) {
|
|
this.fillProgram_ = this.helper_.getProgram(
|
|
this.fillFragmentShader_,
|
|
this.fillVertexShader_
|
|
);
|
|
}
|
|
if (this.hasStroke_) {
|
|
this.strokeProgram_ = this.helper_.getProgram(
|
|
this.strokeFragmentShader_,
|
|
this.strokeVertexShader_
|
|
);
|
|
}
|
|
if (this.hasSymbol_) {
|
|
this.symbolProgram_ = this.helper_.getProgram(
|
|
this.symbolFragmentShader_,
|
|
this.symbolVertexShader_
|
|
);
|
|
}
|
|
this.helper_.addUniforms(this.uniforms_);
|
|
if (buffers) {
|
|
if (buffers.polygonBuffers) {
|
|
this.helper_.flushBufferData(buffers.polygonBuffers[0]);
|
|
this.helper_.flushBufferData(buffers.polygonBuffers[1]);
|
|
}
|
|
if (buffers.lineStringBuffers) {
|
|
this.helper_.flushBufferData(buffers.lineStringBuffers[0]);
|
|
this.helper_.flushBufferData(buffers.lineStringBuffers[1]);
|
|
}
|
|
if (buffers.pointBuffers) {
|
|
this.helper_.flushBufferData(buffers.pointBuffers[0]);
|
|
this.helper_.flushBufferData(buffers.pointBuffers[1]);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
var VectorStyleRenderer_default = VectorStyleRenderer;
|
|
|
|
// node_modules/ol/webgl/RenderTarget.js
|
|
var tmpArray4 = new Uint8Array(4);
|
|
var WebGLRenderTarget = class {
|
|
/**
|
|
* @param {import("./Helper.js").default} helper WebGL helper; mandatory.
|
|
* @param {Array<number>} [size] Expected size of the render target texture; note: this can be changed later on.
|
|
*/
|
|
constructor(helper, size) {
|
|
this.helper_ = helper;
|
|
const gl = helper.getGL();
|
|
this.texture_ = gl.createTexture();
|
|
this.framebuffer_ = gl.createFramebuffer();
|
|
this.depthbuffer_ = gl.createRenderbuffer();
|
|
this.size_ = size || [1, 1];
|
|
this.data_ = new Uint8Array(0);
|
|
this.dataCacheDirty_ = true;
|
|
this.updateSize_();
|
|
}
|
|
/**
|
|
* Changes the size of the render target texture. Note: will do nothing if the size
|
|
* is already the same.
|
|
* @param {Array<number>} size Expected size of the render target texture
|
|
*/
|
|
setSize(size) {
|
|
if (equals(size, this.size_)) {
|
|
return;
|
|
}
|
|
this.size_[0] = size[0];
|
|
this.size_[1] = size[1];
|
|
this.updateSize_();
|
|
}
|
|
/**
|
|
* Returns the size of the render target texture
|
|
* @return {Array<number>} Size of the render target texture
|
|
*/
|
|
getSize() {
|
|
return this.size_;
|
|
}
|
|
/**
|
|
* This will cause following calls to `#readAll` or `#readPixel` to download the content of the
|
|
* render target into memory, which is an expensive operation.
|
|
* This content will be kept in cache but should be cleared after each new render.
|
|
*/
|
|
clearCachedData() {
|
|
this.dataCacheDirty_ = true;
|
|
}
|
|
/**
|
|
* Returns the full content of the frame buffer as a series of r, g, b, a components
|
|
* in the 0-255 range (unsigned byte).
|
|
* @return {Uint8Array} Integer array of color values
|
|
*/
|
|
readAll() {
|
|
if (this.dataCacheDirty_) {
|
|
const size = this.size_;
|
|
const gl = this.helper_.getGL();
|
|
gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer_);
|
|
gl.readPixels(
|
|
0,
|
|
0,
|
|
size[0],
|
|
size[1],
|
|
gl.RGBA,
|
|
gl.UNSIGNED_BYTE,
|
|
this.data_
|
|
);
|
|
this.dataCacheDirty_ = false;
|
|
}
|
|
return this.data_;
|
|
}
|
|
/**
|
|
* Reads one pixel of the frame buffer as an array of r, g, b, a components
|
|
* in the 0-255 range (unsigned byte).
|
|
* If x and/or y are outside of existing data, an array filled with 0 is returned.
|
|
* @param {number} x Pixel coordinate
|
|
* @param {number} y Pixel coordinate
|
|
* @return {Uint8Array} Integer array with one color value (4 components)
|
|
*/
|
|
readPixel(x, y) {
|
|
if (x < 0 || y < 0 || x > this.size_[0] || y >= this.size_[1]) {
|
|
tmpArray4[0] = 0;
|
|
tmpArray4[1] = 0;
|
|
tmpArray4[2] = 0;
|
|
tmpArray4[3] = 0;
|
|
return tmpArray4;
|
|
}
|
|
this.readAll();
|
|
const index = Math.floor(x) + (this.size_[1] - Math.floor(y) - 1) * this.size_[0];
|
|
tmpArray4[0] = this.data_[index * 4];
|
|
tmpArray4[1] = this.data_[index * 4 + 1];
|
|
tmpArray4[2] = this.data_[index * 4 + 2];
|
|
tmpArray4[3] = this.data_[index * 4 + 3];
|
|
return tmpArray4;
|
|
}
|
|
/**
|
|
* @return {WebGLTexture} Texture to render to
|
|
*/
|
|
getTexture() {
|
|
return this.texture_;
|
|
}
|
|
/**
|
|
* @return {WebGLFramebuffer} Frame buffer of the render target
|
|
*/
|
|
getFramebuffer() {
|
|
return this.framebuffer_;
|
|
}
|
|
/**
|
|
* @return {WebGLRenderbuffer} Depth buffer of the render target
|
|
*/
|
|
getDepthbuffer() {
|
|
return this.depthbuffer_;
|
|
}
|
|
/**
|
|
* @private
|
|
*/
|
|
updateSize_() {
|
|
const size = this.size_;
|
|
const gl = this.helper_.getGL();
|
|
this.texture_ = this.helper_.createTexture(size, null, this.texture_);
|
|
gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer_);
|
|
gl.viewport(0, 0, size[0], size[1]);
|
|
gl.framebufferTexture2D(
|
|
gl.FRAMEBUFFER,
|
|
gl.COLOR_ATTACHMENT0,
|
|
gl.TEXTURE_2D,
|
|
this.texture_,
|
|
0
|
|
);
|
|
gl.bindRenderbuffer(gl.RENDERBUFFER, this.depthbuffer_);
|
|
gl.renderbufferStorage(
|
|
gl.RENDERBUFFER,
|
|
gl.DEPTH_COMPONENT16,
|
|
size[0],
|
|
size[1]
|
|
);
|
|
gl.framebufferRenderbuffer(
|
|
gl.FRAMEBUFFER,
|
|
gl.DEPTH_ATTACHMENT,
|
|
gl.RENDERBUFFER,
|
|
this.depthbuffer_
|
|
);
|
|
this.data_ = new Uint8Array(size[0] * size[1] * 4);
|
|
}
|
|
};
|
|
var RenderTarget_default = WebGLRenderTarget;
|
|
|
|
// node_modules/ol/renderer/webgl/worldUtil.js
|
|
function getWorldParameters(frameState, layer) {
|
|
const projection = frameState.viewState.projection;
|
|
const vectorSource = layer.getSource();
|
|
const multiWorld = vectorSource.getWrapX() && projection.canWrapX();
|
|
const projectionExtent = projection.getExtent();
|
|
const extent = frameState.extent;
|
|
const worldWidth = multiWorld ? getWidth(projectionExtent) : null;
|
|
const endWorld = multiWorld ? Math.ceil((extent[2] - projectionExtent[2]) / worldWidth) + 1 : 1;
|
|
const startWorld = multiWorld ? Math.floor((extent[0] - projectionExtent[0]) / worldWidth) : 0;
|
|
return [startWorld, endWorld, worldWidth];
|
|
}
|
|
|
|
// node_modules/ol/renderer/webgl/VectorLayer.js
|
|
var Uniforms = {
|
|
...DefaultUniform,
|
|
RENDER_EXTENT: "u_renderExtent",
|
|
// intersection of layer, source, and view extent
|
|
PATTERN_ORIGIN: "u_patternOrigin",
|
|
GLOBAL_ALPHA: "u_globalAlpha"
|
|
};
|
|
var WebGLVectorLayerRenderer = class extends Layer_default {
|
|
/**
|
|
* @param {import("../../layer/Layer.js").default} layer Layer.
|
|
* @param {Options} options Options.
|
|
*/
|
|
constructor(layer, options) {
|
|
const uniforms = {
|
|
[Uniforms.RENDER_EXTENT]: [0, 0, 0, 0],
|
|
[Uniforms.PATTERN_ORIGIN]: [0, 0],
|
|
[Uniforms.GLOBAL_ALPHA]: 1
|
|
};
|
|
super(layer, {
|
|
uniforms,
|
|
postProcesses: options.postProcesses
|
|
});
|
|
this.hitDetectionEnabled_ = !options.disableHitDetection;
|
|
this.hitRenderTarget_;
|
|
this.sourceRevision_ = -1;
|
|
this.previousExtent_ = createEmpty();
|
|
this.currentTransform_ = create();
|
|
this.tmpCoords_ = [0, 0];
|
|
this.tmpTransform_ = create();
|
|
this.tmpMat4_ = create2();
|
|
this.currentFrameStateTransform_ = create();
|
|
this.styleVariables_ = {};
|
|
this.styles_ = [];
|
|
this.styleRenderers_ = [];
|
|
this.buffers_ = [];
|
|
this.applyOptions_(options);
|
|
this.batch_ = new MixedGeometryBatch_default();
|
|
this.initialFeaturesAdded_ = false;
|
|
this.sourceListenKeys_ = null;
|
|
}
|
|
/**
|
|
* @private
|
|
* @param {import("../../Map.js").FrameState} frameState Frame state.
|
|
*/
|
|
addInitialFeatures_(frameState) {
|
|
const source = this.getLayer().getSource();
|
|
const userProjection = getUserProjection();
|
|
let projectionTransform;
|
|
if (userProjection) {
|
|
projectionTransform = getTransformFromProjections(
|
|
userProjection,
|
|
frameState.viewState.projection
|
|
);
|
|
}
|
|
this.batch_.addFeatures(source.getFeatures(), projectionTransform);
|
|
this.sourceListenKeys_ = [
|
|
listen(
|
|
source,
|
|
VectorEventType_default.ADDFEATURE,
|
|
this.handleSourceFeatureAdded_.bind(this, projectionTransform)
|
|
),
|
|
listen(
|
|
source,
|
|
VectorEventType_default.CHANGEFEATURE,
|
|
this.handleSourceFeatureChanged_.bind(this, projectionTransform),
|
|
this
|
|
),
|
|
listen(
|
|
source,
|
|
VectorEventType_default.REMOVEFEATURE,
|
|
this.handleSourceFeatureDelete_,
|
|
this
|
|
),
|
|
listen(
|
|
source,
|
|
VectorEventType_default.CLEAR,
|
|
this.handleSourceFeatureClear_,
|
|
this
|
|
)
|
|
];
|
|
}
|
|
/**
|
|
* @param {Options} options Options.
|
|
* @private
|
|
*/
|
|
applyOptions_(options) {
|
|
this.styleVariables_ = options.variables;
|
|
this.styles_ = breakDownFlatStyle(options.style);
|
|
}
|
|
/**
|
|
* @private
|
|
*/
|
|
createRenderers_() {
|
|
this.buffers_ = [];
|
|
this.styleRenderers_ = this.styles_.map(
|
|
(style) => new VectorStyleRenderer_default(
|
|
style,
|
|
this.styleVariables_,
|
|
this.helper,
|
|
this.hitDetectionEnabled_,
|
|
"filter" in style ? style.filter : null
|
|
)
|
|
);
|
|
}
|
|
/**
|
|
* @override
|
|
*/
|
|
reset(options) {
|
|
this.applyOptions_(options);
|
|
if (this.helper) {
|
|
this.createRenderers_();
|
|
}
|
|
super.reset(options);
|
|
}
|
|
/**
|
|
* @override
|
|
*/
|
|
afterHelperCreated() {
|
|
if (this.styleRenderers_.length) {
|
|
this.styleRenderers_.forEach(
|
|
(renderer, i) => renderer.setHelper(this.helper, this.buffers_[i])
|
|
);
|
|
} else {
|
|
this.createRenderers_();
|
|
}
|
|
if (this.hitDetectionEnabled_) {
|
|
this.hitRenderTarget_ = new RenderTarget_default(this.helper);
|
|
}
|
|
}
|
|
/**
|
|
* @param {import("../../proj.js").TransformFunction} projectionTransform Transform function.
|
|
* @param {import("../../source/Vector.js").VectorSourceEvent} event Event.
|
|
* @private
|
|
*/
|
|
handleSourceFeatureAdded_(projectionTransform, event) {
|
|
const feature = event.feature;
|
|
this.batch_.addFeature(feature, projectionTransform);
|
|
}
|
|
/**
|
|
* @param {import("../../proj.js").TransformFunction} projectionTransform Transform function.
|
|
* @param {import("../../source/Vector.js").VectorSourceEvent} event Event.
|
|
* @private
|
|
*/
|
|
handleSourceFeatureChanged_(projectionTransform, event) {
|
|
const feature = event.feature;
|
|
this.batch_.changeFeature(feature, projectionTransform);
|
|
}
|
|
/**
|
|
* @param {import("../../source/Vector.js").VectorSourceEvent} event Event.
|
|
* @private
|
|
*/
|
|
handleSourceFeatureDelete_(event) {
|
|
const feature = event.feature;
|
|
this.batch_.removeFeature(feature);
|
|
}
|
|
/**
|
|
* @private
|
|
*/
|
|
handleSourceFeatureClear_() {
|
|
this.batch_.clear();
|
|
}
|
|
/**
|
|
* @param {import("../../transform.js").Transform} batchInvertTransform Inverse of the transformation in which geometries are expressed
|
|
* @private
|
|
*/
|
|
applyUniforms_(batchInvertTransform) {
|
|
setFromArray(this.tmpTransform_, this.currentFrameStateTransform_);
|
|
multiply(this.tmpTransform_, batchInvertTransform);
|
|
this.helper.setUniformMatrixValue(
|
|
Uniforms.PROJECTION_MATRIX,
|
|
fromTransform(this.tmpMat4_, this.tmpTransform_)
|
|
);
|
|
makeInverse(this.tmpTransform_, this.tmpTransform_);
|
|
this.helper.setUniformMatrixValue(
|
|
Uniforms.SCREEN_TO_WORLD_MATRIX,
|
|
fromTransform(this.tmpMat4_, this.tmpTransform_)
|
|
);
|
|
this.tmpCoords_[0] = 0;
|
|
this.tmpCoords_[1] = 0;
|
|
makeInverse(this.tmpTransform_, batchInvertTransform);
|
|
apply(this.tmpTransform_, this.tmpCoords_);
|
|
this.helper.setUniformFloatVec2(Uniforms.PATTERN_ORIGIN, this.tmpCoords_);
|
|
}
|
|
/**
|
|
* Render the layer.
|
|
* @param {import("../../Map.js").FrameState} frameState Frame state.
|
|
* @return {HTMLElement} The rendered element.
|
|
* @override
|
|
*/
|
|
renderFrame(frameState) {
|
|
const gl = this.helper.getGL();
|
|
this.preRender(gl, frameState);
|
|
const [startWorld, endWorld, worldWidth] = getWorldParameters(
|
|
frameState,
|
|
this.getLayer()
|
|
);
|
|
this.helper.prepareDraw(frameState);
|
|
this.renderWorlds(frameState, false, startWorld, endWorld, worldWidth);
|
|
this.helper.finalizeDraw(
|
|
frameState,
|
|
this.dispatchPreComposeEvent,
|
|
this.dispatchPostComposeEvent
|
|
);
|
|
const canvas = this.helper.getCanvas();
|
|
if (this.hitDetectionEnabled_) {
|
|
this.renderWorlds(frameState, true, startWorld, endWorld, worldWidth);
|
|
this.hitRenderTarget_.clearCachedData();
|
|
}
|
|
this.postRender(gl, frameState);
|
|
return canvas;
|
|
}
|
|
/**
|
|
* Determine whether renderFrame should be called.
|
|
* @param {import("../../Map.js").FrameState} frameState Frame state.
|
|
* @return {boolean} Layer is ready to be rendered.
|
|
* @override
|
|
*/
|
|
prepareFrameInternal(frameState) {
|
|
if (!this.initialFeaturesAdded_) {
|
|
this.addInitialFeatures_(frameState);
|
|
this.initialFeaturesAdded_ = true;
|
|
}
|
|
const layer = this.getLayer();
|
|
const vectorSource = layer.getSource();
|
|
const viewState = frameState.viewState;
|
|
const viewNotMoving = !frameState.viewHints[ViewHint_default.ANIMATING] && !frameState.viewHints[ViewHint_default.INTERACTING];
|
|
const extentChanged = !equals2(this.previousExtent_, frameState.extent);
|
|
const sourceChanged = this.sourceRevision_ < vectorSource.getRevision();
|
|
if (sourceChanged) {
|
|
this.sourceRevision_ = vectorSource.getRevision();
|
|
}
|
|
if (viewNotMoving && (extentChanged || sourceChanged)) {
|
|
const projection = viewState.projection;
|
|
const resolution = viewState.resolution;
|
|
const renderBuffer = layer instanceof BaseVector_default ? layer.getRenderBuffer() : 0;
|
|
const extent = buffer(frameState.extent, renderBuffer * resolution);
|
|
const userProjection = getUserProjection();
|
|
if (userProjection) {
|
|
vectorSource.loadFeatures(
|
|
toUserExtent(extent, userProjection),
|
|
toUserResolution(resolution, projection),
|
|
userProjection
|
|
);
|
|
} else {
|
|
vectorSource.loadFeatures(extent, resolution, projection);
|
|
}
|
|
this.ready = false;
|
|
const transform = this.helper.makeProjectionTransform(
|
|
frameState,
|
|
create()
|
|
);
|
|
const generatePromises = this.styleRenderers_.map(
|
|
(renderer, i) => renderer.generateBuffers(this.batch_, transform).then((buffers) => {
|
|
if (this.buffers_[i]) {
|
|
this.disposeBuffers(this.buffers_[i]);
|
|
}
|
|
this.buffers_[i] = buffers;
|
|
})
|
|
);
|
|
Promise.all(generatePromises).then(() => {
|
|
this.ready = true;
|
|
this.getLayer().changed();
|
|
});
|
|
this.previousExtent_ = frameState.extent.slice();
|
|
}
|
|
return true;
|
|
}
|
|
/**
|
|
* Render the world, either to the main framebuffer or to the hit framebuffer
|
|
* @param {import("../../Map.js").FrameState} frameState current frame state
|
|
* @param {boolean} forHitDetection whether the rendering is for hit detection
|
|
* @param {number} startWorld the world to render in the first iteration
|
|
* @param {number} endWorld the last world to render
|
|
* @param {number} worldWidth the width of the worlds being rendered
|
|
*/
|
|
renderWorlds(frameState, forHitDetection, startWorld, endWorld, worldWidth) {
|
|
let world = startWorld;
|
|
if (forHitDetection) {
|
|
this.hitRenderTarget_.setSize([
|
|
Math.floor(frameState.size[0] / 2),
|
|
Math.floor(frameState.size[1] / 2)
|
|
]);
|
|
this.helper.prepareDrawToRenderTarget(
|
|
frameState,
|
|
this.hitRenderTarget_,
|
|
true
|
|
);
|
|
}
|
|
do {
|
|
this.helper.makeProjectionTransform(
|
|
frameState,
|
|
this.currentFrameStateTransform_
|
|
);
|
|
translate(
|
|
this.currentFrameStateTransform_,
|
|
world * worldWidth,
|
|
0
|
|
);
|
|
for (let i = 0, ii = this.styleRenderers_.length; i < ii; i++) {
|
|
const renderer = this.styleRenderers_[i];
|
|
const buffers = this.buffers_[i];
|
|
if (!buffers) {
|
|
continue;
|
|
}
|
|
renderer.render(buffers, frameState, () => {
|
|
this.applyUniforms_(buffers.invertVerticesTransform);
|
|
this.helper.applyHitDetectionUniform(forHitDetection);
|
|
});
|
|
}
|
|
} while (++world < endWorld);
|
|
}
|
|
/**
|
|
* @param {import("../../coordinate.js").Coordinate} coordinate Coordinate.
|
|
* @param {import("../../Map.js").FrameState} frameState Frame state.
|
|
* @param {number} hitTolerance Hit tolerance in pixels.
|
|
* @param {import("../vector.js").FeatureCallback<T>} callback Feature callback.
|
|
* @param {Array<import("../Map.js").HitMatch<T>>} matches The hit detected matches with tolerance.
|
|
* @return {T|undefined} Callback result.
|
|
* @template T
|
|
* @override
|
|
*/
|
|
forEachFeatureAtCoordinate(coordinate, frameState, hitTolerance, callback, matches) {
|
|
assert(
|
|
this.hitDetectionEnabled_,
|
|
"`forEachFeatureAtCoordinate` cannot be used on a WebGL layer if the hit detection logic has been disabled using the `disableHitDetection: true` option."
|
|
);
|
|
if (!this.styleRenderers_.length || !this.hitDetectionEnabled_) {
|
|
return void 0;
|
|
}
|
|
const pixel = apply(
|
|
frameState.coordinateToPixelTransform,
|
|
coordinate.slice()
|
|
);
|
|
const data = this.hitRenderTarget_.readPixel(pixel[0] / 2, pixel[1] / 2);
|
|
const color = [data[0] / 255, data[1] / 255, data[2] / 255, data[3] / 255];
|
|
const ref = colorDecodeId(color);
|
|
const feature = this.batch_.getFeatureFromRef(ref);
|
|
if (feature) {
|
|
return callback(feature, this.getLayer(), null);
|
|
}
|
|
return void 0;
|
|
}
|
|
/**
|
|
* Will release a set of Webgl buffers
|
|
* @param {import('../../render/webgl/VectorStyleRenderer.js').WebGLBuffers} buffers Buffers
|
|
*/
|
|
disposeBuffers(buffers) {
|
|
const disposeBuffersOfType = (typeBuffers) => {
|
|
for (const buffer2 of typeBuffers) {
|
|
if (buffer2) {
|
|
this.helper.deleteBuffer(buffer2);
|
|
}
|
|
}
|
|
};
|
|
if (buffers.pointBuffers) {
|
|
disposeBuffersOfType(buffers.pointBuffers);
|
|
}
|
|
if (buffers.lineStringBuffers) {
|
|
disposeBuffersOfType(buffers.lineStringBuffers);
|
|
}
|
|
if (buffers.polygonBuffers) {
|
|
disposeBuffersOfType(buffers.polygonBuffers);
|
|
}
|
|
}
|
|
/**
|
|
* Clean up.
|
|
* @override
|
|
*/
|
|
disposeInternal() {
|
|
this.buffers_.forEach((buffers) => {
|
|
if (buffers) {
|
|
this.disposeBuffers(buffers);
|
|
}
|
|
});
|
|
if (this.sourceListenKeys_) {
|
|
this.sourceListenKeys_.forEach(function(key) {
|
|
unlistenByKey(key);
|
|
});
|
|
this.sourceListenKeys_ = null;
|
|
}
|
|
super.disposeInternal();
|
|
}
|
|
renderDeclutter() {
|
|
}
|
|
};
|
|
var VectorLayer_default = WebGLVectorLayerRenderer;
|
|
|
|
// node_modules/ol/layer/Heatmap.js
|
|
var Property = {
|
|
BLUR: "blur",
|
|
GRADIENT: "gradient",
|
|
RADIUS: "radius"
|
|
};
|
|
var DEFAULT_GRADIENT = ["#00f", "#0ff", "#0f0", "#ff0", "#f00"];
|
|
var Heatmap = class extends BaseVector_default {
|
|
/**
|
|
* @param {Options<FeatureType, VectorSourceType>} [options] Options.
|
|
*/
|
|
constructor(options) {
|
|
options = options ? options : {};
|
|
const baseOptions = Object.assign({}, options);
|
|
delete baseOptions.gradient;
|
|
delete baseOptions.radius;
|
|
delete baseOptions.blur;
|
|
delete baseOptions.weight;
|
|
super(baseOptions);
|
|
this.filter_ = options.filter ?? true;
|
|
this.styleVariables_ = options.variables || {};
|
|
this.gradient_ = null;
|
|
this.addChangeListener(Property.GRADIENT, this.handleGradientChanged_);
|
|
this.setGradient(options.gradient ? options.gradient : DEFAULT_GRADIENT);
|
|
this.setBlur(options.blur !== void 0 ? options.blur : 15);
|
|
this.setRadius(options.radius !== void 0 ? options.radius : 8);
|
|
const weight = options.weight ? options.weight : "weight";
|
|
this.weight_ = weight;
|
|
this.setRenderOrder(null);
|
|
}
|
|
/**
|
|
* Return the blur size in pixels.
|
|
* @return {import("../style/flat.js").NumberExpression} Blur size in pixels.
|
|
* @api
|
|
* @observable
|
|
*/
|
|
getBlur() {
|
|
return (
|
|
/** @type {import("../style/flat.js").NumberExpression} */
|
|
this.get(Property.BLUR)
|
|
);
|
|
}
|
|
/**
|
|
* Return the gradient colors as array of strings.
|
|
* @return {Array<string>} Colors.
|
|
* @api
|
|
* @observable
|
|
*/
|
|
getGradient() {
|
|
return (
|
|
/** @type {Array<string>} */
|
|
this.get(Property.GRADIENT)
|
|
);
|
|
}
|
|
/**
|
|
* Return the size of the radius in pixels.
|
|
* @return {import("../style/flat.js").NumberExpression} Radius size in pixel.
|
|
* @api
|
|
* @observable
|
|
*/
|
|
getRadius() {
|
|
return (
|
|
/** @type {import("../style/flat.js").NumberExpression} */
|
|
this.get(Property.RADIUS)
|
|
);
|
|
}
|
|
/**
|
|
* @private
|
|
*/
|
|
handleGradientChanged_() {
|
|
this.gradient_ = createGradient(this.getGradient());
|
|
}
|
|
/**
|
|
* Set the blur size in pixels.
|
|
* @param {import("../style/flat.js").NumberExpression} blur Blur size in pixels (supports expressions).
|
|
* @api
|
|
* @observable
|
|
*/
|
|
setBlur(blur) {
|
|
const previousValue = this.get(Property.BLUR);
|
|
this.set(Property.BLUR, blur);
|
|
if (typeof blur === "number" && typeof previousValue === "number") {
|
|
this.changed();
|
|
return;
|
|
}
|
|
this.clearRenderer();
|
|
}
|
|
/**
|
|
* Set the gradient colors as array of strings.
|
|
* @param {Array<string>} colors Gradient.
|
|
* @api
|
|
* @observable
|
|
*/
|
|
setGradient(colors) {
|
|
this.set(Property.GRADIENT, colors);
|
|
}
|
|
/**
|
|
* Set the size of the radius in pixels.
|
|
* @param {import("../style/flat.js").NumberExpression} radius Radius size in pixel (supports expressions).
|
|
* @api
|
|
* @observable
|
|
*/
|
|
setRadius(radius) {
|
|
const previousValue = this.get(Property.RADIUS);
|
|
this.set(Property.RADIUS, radius);
|
|
if (typeof radius === "number" && typeof previousValue === "number") {
|
|
this.changed();
|
|
return;
|
|
}
|
|
this.clearRenderer();
|
|
}
|
|
/**
|
|
* Set the filter expression
|
|
* @param {import("../style/flat.js").BooleanExpression} filter Filter expression
|
|
* @api
|
|
*/
|
|
setFilter(filter) {
|
|
this.filter_ = filter;
|
|
this.changed();
|
|
this.clearRenderer();
|
|
}
|
|
/**
|
|
* Set the weight expression
|
|
* @param {WeightExpression} weight Weight expression
|
|
* @api
|
|
*/
|
|
setWeight(weight) {
|
|
this.weight_ = weight;
|
|
this.changed();
|
|
this.clearRenderer();
|
|
}
|
|
/**
|
|
* @override
|
|
*/
|
|
createRenderer() {
|
|
const builder = new ShaderBuilder();
|
|
const context = newCompilationContext();
|
|
const filterCompiled = expressionToGlsl(context, this.filter_, BooleanType);
|
|
let radiusCompiled = expressionToGlsl(
|
|
context,
|
|
this.getRadius(),
|
|
NumberType
|
|
);
|
|
let blurCompiled = expressionToGlsl(context, this.getBlur(), NumberType);
|
|
const blurRadiusUniforms = {};
|
|
if (typeof this.getBlur() === "number") {
|
|
blurCompiled = "a_blur";
|
|
blurRadiusUniforms["a_blur"] = () => this.getBlur();
|
|
builder.addUniform("a_blur", "float");
|
|
}
|
|
if (typeof this.getRadius() === "number") {
|
|
radiusCompiled = "a_radius";
|
|
blurRadiusUniforms["a_radius"] = () => this.getRadius();
|
|
builder.addUniform("a_radius", "float");
|
|
}
|
|
const weightAttribute = {};
|
|
let weightExpression = null;
|
|
if (typeof this.weight_ === "string" || typeof this.weight_ === "function") {
|
|
const weightFunction = typeof this.weight_ === "string" ? (feature) => feature.get(this.weight_) : this.weight_;
|
|
weightAttribute["prop_weight"] = {
|
|
size: 1,
|
|
callback: (feature) => {
|
|
const weightValue = weightFunction(feature);
|
|
return weightValue !== void 0 ? clamp(weightValue, 0, 1) : 1;
|
|
}
|
|
};
|
|
weightExpression = "a_prop_weight";
|
|
builder.addAttribute("a_prop_weight", "float");
|
|
} else {
|
|
const clampedWeight = ["clamp", this.weight_, 0, 1];
|
|
weightExpression = expressionToGlsl(context, clampedWeight, NumberType);
|
|
}
|
|
builder.addFragmentShaderFunction(
|
|
`float getBlurSlope() {
|
|
float blur = max(1., ${blurCompiled});
|
|
float radius = ${radiusCompiled};
|
|
return radius / blur;
|
|
}`
|
|
).setSymbolSizeExpression(`vec2(${radiusCompiled} + ${blurCompiled}) * 2.`).setSymbolColorExpression(
|
|
`vec4(smoothstep(0., 1., (1. - length(coordsPx * 2. / v_quadSizePx)) * getBlurSlope()) * ${weightExpression})`
|
|
).setStrokeColorExpression(
|
|
`vec4(smoothstep(0., 1., (1. - length(currentRadiusPx * 2. / v_width)) * getBlurSlope()) * ${weightExpression})`
|
|
).setStrokeWidthExpression(`(${radiusCompiled} + ${blurCompiled}) * 2.`).setFillColorExpression(`vec4(${weightExpression})`).setFragmentDiscardExpression(`!${filterCompiled}`);
|
|
applyContextToBuilder(builder, context);
|
|
const attributes = generateAttributesFromContext(context);
|
|
const uniforms = generateUniformsFromContext(context, this.styleVariables_);
|
|
return new VectorLayer_default(this, {
|
|
className: this.getClassName(),
|
|
variables: this.styleVariables_,
|
|
style: {
|
|
builder,
|
|
attributes: {
|
|
...attributes,
|
|
...weightAttribute
|
|
},
|
|
uniforms: {
|
|
...uniforms,
|
|
...blurRadiusUniforms
|
|
}
|
|
},
|
|
disableHitDetection: false,
|
|
postProcesses: [
|
|
{
|
|
fragmentShader: `
|
|
precision mediump float;
|
|
|
|
uniform sampler2D u_image;
|
|
uniform sampler2D u_gradientTexture;
|
|
uniform float u_opacity;
|
|
|
|
varying vec2 v_texCoord;
|
|
|
|
void main() {
|
|
vec4 color = texture2D(u_image, v_texCoord);
|
|
gl_FragColor.a = color.a * u_opacity;
|
|
gl_FragColor.rgb = texture2D(u_gradientTexture, vec2(0.5, color.a)).rgb;
|
|
gl_FragColor.rgb *= gl_FragColor.a;
|
|
}`,
|
|
uniforms: {
|
|
u_gradientTexture: () => this.gradient_,
|
|
u_opacity: () => this.getOpacity()
|
|
}
|
|
}
|
|
]
|
|
});
|
|
}
|
|
/**
|
|
* Update any variables used by the layer style and trigger a re-render.
|
|
* @param {import('../style/flat.js').StyleVariables} variables Variables to update.
|
|
*/
|
|
updateStyleVariables(variables) {
|
|
Object.assign(this.styleVariables_, variables);
|
|
this.changed();
|
|
}
|
|
/**
|
|
* @override
|
|
*/
|
|
renderDeclutter() {
|
|
}
|
|
};
|
|
function createGradient(colors) {
|
|
const width = 1;
|
|
const height = 256;
|
|
const context = createCanvasContext2D(width, height);
|
|
const gradient = context.createLinearGradient(0, 0, width, height);
|
|
const step = 1 / (colors.length - 1);
|
|
for (let i = 0, ii = colors.length; i < ii; ++i) {
|
|
gradient.addColorStop(i * step, colors[i]);
|
|
}
|
|
context.fillStyle = gradient;
|
|
context.fillRect(0, 0, width, height);
|
|
return context.canvas;
|
|
}
|
|
var Heatmap_default = Heatmap;
|
|
|
|
export {
|
|
create3 as create,
|
|
WebGLWorkerMessageType,
|
|
colorEncodeId,
|
|
colorDecodeId,
|
|
parseLiteralStyle,
|
|
RenderTarget_default,
|
|
getWorldParameters,
|
|
VectorLayer_default,
|
|
Heatmap_default
|
|
};
|
|
//# sourceMappingURL=chunk-MY3Y56DQ.js.map
|