heli-agri
Version:
HeliAgri is a high-performance, feature-packed library for creating interactive maps on the web. It can display map tiles, vector data and markers loaded from any source on any web page. OpenLayers has been developed to further the use of geographic infor
372 lines (327 loc) • 11.4 kB
JavaScript
/**
* @module ol/renderer/webgl/VectorLayer
*/
import BaseVector from '../../layer/BaseVector.js';
import MixedGeometryBatch from '../../render/webgl/MixedGeometryBatch.js';
import VectorEventType from '../../source/VectorEventType.js';
import VectorStyleRenderer from '../../render/webgl/VectorStyleRenderer.js';
import ViewHint from '../../ViewHint.js';
import WebGLLayerRenderer from './Layer.js';
import {DefaultUniform} from '../../webgl/Helper.js';
import {buffer, createEmpty, equals, getWidth} from '../../extent.js';
import {
create as createMat4,
fromTransform as mat4FromTransform,
} from '../../vec/mat4.js';
import {
create as createTransform,
multiply as multiplyTransform,
setFromArray as setFromTransform,
translate as translateTransform,
} from '../../transform.js';
import {listen, unlistenByKey} from '../../events.js';
export const Uniforms = {
...DefaultUniform,
RENDER_EXTENT: 'u_renderExtent', // intersection of layer, source, and view extent
GLOBAL_ALPHA: 'u_globalAlpha',
};
/**
* @typedef {import('../../render/webgl/VectorStyleRenderer.js').VectorStyle} VectorStyle
*/
/**
* @typedef {Object} Options
* @property {string} [className='ol-layer'] A CSS class name to set to the canvas element.
* @property {VectorStyle|Array<VectorStyle>} style Vector style as literal style or shaders; can also accept an array of styles
* @property {Array<import("./Layer").PostProcessesOptions>} [postProcesses] Post-processes definitions
*/
/**
* @classdesc
* Experimental WebGL vector renderer. Supports polygons, lines and points:
* * Polygons are broken down into triangles
* * Lines are rendered as strips of quads
* * Points are rendered as quads
*
* You need to provide vertex and fragment shaders as well as custom attributes for each type of geometry. All shaders
* can access the uniforms in the {@link module:ol/webgl/Helper~DefaultUniform} enum.
* The vertex shaders can access the following attributes depending on the geometry type:
* * For polygons: {@link module:ol/render/webgl/PolygonBatchRenderer~Attributes}
* * For line strings: {@link module:ol/render/webgl/LineStringBatchRenderer~Attributes}
* * For points: {@link module:ol/render/webgl/PointBatchRenderer~Attributes}
*
* Please note that the fragment shaders output should have premultiplied alpha, otherwise visual anomalies may occur.
*
* Note: this uses {@link module:ol/webgl/Helper~WebGLHelper} internally.
*/
class WebGLVectorLayerRenderer extends WebGLLayerRenderer {
/**
* @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.GLOBAL_ALPHA]: 1,
};
super(layer, {
uniforms: uniforms,
postProcesses: options.postProcesses,
});
this.sourceRevision_ = -1;
this.previousExtent_ = createEmpty();
/**
* This transform is updated on every frame and is the composition of:
* - invert of the world->screen transform that was used when rebuilding buffers (see `this.renderTransform_`)
* - current world->screen transform
* @type {import("../../transform.js").Transform}
* @private
*/
this.currentTransform_ = createTransform();
this.tmpTransform_ = createTransform();
this.tmpMat4_ = createMat4();
/**
* @type {import("../../transform.js").Transform}
* @private
*/
this.currentFrameStateTransform_ = createTransform();
/**
* @type {Array<VectorStyle>}
* @private
*/
this.styles_ = [];
/**
* @type {Array<VectorStyleRenderer>}
* @private
*/
this.styleRenderers_ = [];
/**
* @type {Array<import('../../render/webgl/VectorStyleRenderer.js').WebGLBuffers>}
* @private
*/
this.buffers_ = [];
this.applyOptions_(options);
/**
* @private
*/
this.batch_ = new MixedGeometryBatch();
const source = this.getLayer().getSource();
this.batch_.addFeatures(source.getFeatures());
this.sourceListenKeys_ = [
listen(
source,
VectorEventType.ADDFEATURE,
this.handleSourceFeatureAdded_,
this
),
listen(
source,
VectorEventType.CHANGEFEATURE,
this.handleSourceFeatureChanged_,
this
),
listen(
source,
VectorEventType.REMOVEFEATURE,
this.handleSourceFeatureDelete_,
this
),
listen(
source,
VectorEventType.CLEAR,
this.handleSourceFeatureClear_,
this
),
];
}
/**
* @param {Options} options Options.
* @private
*/
applyOptions_(options) {
this.styles_ = Array.isArray(options.style)
? options.style
: [options.style];
}
/**
* @private
*/
createRenderers_() {
this.buffers_ = [];
this.styleRenderers_ = this.styles_.map(
(style) => new VectorStyleRenderer(style, this.helper)
);
}
reset(options) {
this.applyOptions_(options);
if (this.helper) {
this.createRenderers_();
}
super.reset(options);
}
afterHelperCreated() {
this.createRenderers_();
}
/**
* @param {import("../../source/Vector.js").VectorSourceEvent} event Event.
* @private
*/
handleSourceFeatureAdded_(event) {
const feature = event.feature;
this.batch_.addFeature(feature);
}
/**
* @param {import("../../source/Vector.js").VectorSourceEvent} event Event.
* @private
*/
handleSourceFeatureChanged_(event) {
const feature = event.feature;
this.batch_.changeFeature(feature);
}
/**
* @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) {
// world to screen matrix
setFromTransform(this.tmpTransform_, this.currentFrameStateTransform_);
multiplyTransform(this.tmpTransform_, batchInvertTransform);
this.helper.setUniformMatrixValue(
Uniforms.PROJECTION_MATRIX,
mat4FromTransform(this.tmpMat4_, this.tmpTransform_)
);
}
/**
* Render the layer.
* @param {import("../../Map.js").FrameState} frameState Frame state.
* @return {HTMLElement} The rendered element.
*/
renderFrame(frameState) {
const gl = this.helper.getGL();
this.preRender(gl, frameState);
this.helper.prepareDraw(frameState);
this.currentFrameStateTransform_ = this.helper.makeProjectionTransform(
frameState,
this.currentFrameStateTransform_
);
const layer = this.getLayer();
const vectorSource = layer.getSource();
const projection = frameState.viewState.projection;
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;
let world = multiWorld
? Math.floor((extent[0] - projectionExtent[0]) / worldWidth)
: 0;
translateTransform(this.currentFrameStateTransform_, world * worldWidth, 0);
do {
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);
});
}
translateTransform(this.currentFrameStateTransform_, worldWidth, 0);
} while (++world < endWorld);
this.helper.finalizeDraw(frameState);
const canvas = this.helper.getCanvas();
const layerState = frameState.layerStatesArray[frameState.layerIndex];
const opacity = layerState.opacity;
if (opacity !== parseFloat(canvas.style.opacity)) {
canvas.style.opacity = String(opacity);
}
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.
*/
prepareFrameInternal(frameState) {
const layer = this.getLayer();
const vectorSource = layer.getSource();
const viewState = frameState.viewState;
const viewNotMoving =
!frameState.viewHints[ViewHint.ANIMATING] &&
!frameState.viewHints[ViewHint.INTERACTING];
const extentChanged = !equals(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 ? layer.getRenderBuffer() : 0;
const extent = buffer(frameState.extent, renderBuffer * resolution);
vectorSource.loadFeatures(extent, resolution, projection);
this.ready = false;
const transform = this.helper.makeProjectionTransform(
frameState,
createTransform()
);
const generatePromises = this.styleRenderers_.map((renderer, i) =>
renderer.generateBuffers(this.batch_, transform).then((buffers) => {
this.buffers_[i] = buffers;
})
);
Promise.all(generatePromises).then(() => {
this.ready = true;
this.getLayer().changed();
});
this.previousExtent_ = frameState.extent.slice();
}
return true;
}
/**
* @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
*/
forEachFeatureAtCoordinate(
coordinate,
frameState,
hitTolerance,
callback,
matches
) {
return undefined;
}
/**
* Clean up.
*/
disposeInternal() {
this.sourceListenKeys_.forEach(function (key) {
unlistenByKey(key);
});
this.sourceListenKeys_ = null;
super.disposeInternal();
}
}
export default WebGLVectorLayerRenderer;