UNPKG

ol

Version:

OpenLayers mapping library

330 lines (317 loc) • 10.2 kB
/** * @module ol/render/canvas/PolygonBuilder */ import {snap} from '../../geom/flat/simplify.js'; import { defaultFillStyle, defaultLineDash, defaultLineDashOffset, } from '../canvas.js'; import CanvasBuilder from './Builder.js'; import CanvasInstruction, { beginPathInstruction, closePathInstruction, fillInstruction, strokeInstruction, } from './Instruction.js'; class CanvasPolygonBuilder extends CanvasBuilder { /** * @param {number} tolerance Tolerance. * @param {import("../../extent.js").Extent} maxExtent Maximum extent. * @param {number} resolution Resolution. * @param {number} pixelRatio Pixel ratio. */ constructor(tolerance, maxExtent, resolution, pixelRatio) { super(tolerance, maxExtent, resolution, pixelRatio); } /** * @param {Array<number>} flatCoordinates Flat coordinates. * @param {number} offset Offset. * @param {Array<number>} ends Ends. * @param {number} stride Stride. * @param {number} [strokeOffset] Stroke Offset in pixels. * @private * @return {number} End. */ drawFlatCoordinatess_(flatCoordinates, offset, ends, stride, strokeOffset) { const state = this.state; const fill = state.fillStyle !== undefined; const stroke = state.strokeStyle !== undefined; const numEnds = ends.length; this.instructions.push(beginPathInstruction); this.hitDetectionInstructions.push(beginPathInstruction); for (let i = 0; i < numEnds; ++i) { const end = ends[i]; const myBegin = this.coordinates.length; const myEnd = this.appendFlatLineCoordinates( flatCoordinates, offset, end, stride, true, !stroke, ); this.instructions.push([ CanvasInstruction.MOVE_TO_LINE_TO, myBegin, myEnd, strokeOffset * this.pixelRatio, true, ]); this.hitDetectionInstructions.push([ CanvasInstruction.MOVE_TO_LINE_TO, myBegin, myEnd, strokeOffset, true, ]); if (stroke) { // Performance optimization: only call closePath() when we have a stroke. // Otherwise the ring is closed already (see appendFlatLineCoordinates above). this.instructions.push(closePathInstruction); this.hitDetectionInstructions.push(closePathInstruction); } offset = end; } if (fill) { this.instructions.push(fillInstruction); this.hitDetectionInstructions.push(fillInstruction); } if (stroke) { this.instructions.push(strokeInstruction); this.hitDetectionInstructions.push(strokeInstruction); } return offset; } /** * @param {import("../../geom/Circle.js").default} circleGeometry Circle geometry. * @param {import("../../Feature.js").default} feature Feature. * @param {number} [index] Render order index. * @override */ drawCircle(circleGeometry, feature, index) { const state = this.state; const fillStyle = state.fillStyle; const strokeStyle = state.strokeStyle; const strokeOffset = state.strokeOffset; if (fillStyle === undefined && strokeStyle === undefined) { return; } if ( this.handleStrokeOffset_(() => this.drawCircle(circleGeometry, feature, index), ) ) { return; } this.setFillStrokeStyles_(); this.beginGeometry(circleGeometry, feature, index); if (state.fillStyle !== undefined) { this.hitDetectionInstructions.push([ CanvasInstruction.SET_FILL_STYLE, defaultFillStyle, ]); } if (state.strokeStyle !== undefined) { this.hitDetectionInstructions.push([ CanvasInstruction.SET_STROKE_STYLE, state.strokeStyle, state.lineWidth, state.lineCap, state.lineJoin, state.miterLimit, defaultLineDash, defaultLineDashOffset, ]); } const flatCoordinates = circleGeometry.getFlatCoordinates(); const stride = circleGeometry.getStride(); const myBegin = this.coordinates.length; this.appendFlatLineCoordinates( flatCoordinates, 0, flatCoordinates.length, stride, false, false, ); const circleInstruction = [CanvasInstruction.CIRCLE, myBegin, strokeOffset]; this.instructions.push(beginPathInstruction, circleInstruction); this.hitDetectionInstructions.push(beginPathInstruction, circleInstruction); if (state.fillStyle !== undefined) { this.instructions.push(fillInstruction); this.hitDetectionInstructions.push(fillInstruction); } if (state.strokeStyle !== undefined) { this.instructions.push(strokeInstruction); this.hitDetectionInstructions.push(strokeInstruction); } this.endGeometry(feature); } /** * @param {import("../../geom/Polygon.js").default|import("../Feature.js").default} polygonGeometry Polygon geometry. * @param {import("../../Feature.js").FeatureLike} feature Feature. * @param {number} [index] Render order index. * @override */ drawPolygon(polygonGeometry, feature, index) { const state = this.state; const fillStyle = state.fillStyle; const strokeStyle = state.strokeStyle; const strokeOffset = state.strokeOffset; if (fillStyle === undefined && strokeStyle === undefined) { return; } if ( this.handleStrokeOffset_(() => this.drawPolygon(polygonGeometry, feature, index), ) ) { return; } this.setFillStrokeStyles_(); this.beginGeometry(polygonGeometry, feature, index); if (state.fillStyle !== undefined) { this.hitDetectionInstructions.push([ CanvasInstruction.SET_FILL_STYLE, defaultFillStyle, ]); } if (state.strokeStyle !== undefined) { this.hitDetectionInstructions.push([ CanvasInstruction.SET_STROKE_STYLE, state.strokeStyle, state.lineWidth, state.lineCap, state.lineJoin, state.miterLimit, defaultLineDash, defaultLineDashOffset, ]); } const ends = polygonGeometry.getEnds(); const flatCoordinates = polygonGeometry.getOrientedFlatCoordinates(); const stride = polygonGeometry.getStride(); this.drawFlatCoordinatess_( flatCoordinates, 0, /** @type {Array<number>} */ (ends), stride, strokeOffset, ); this.endGeometry(feature); } /** * @param {import("../../geom/MultiPolygon.js").default} multiPolygonGeometry MultiPolygon geometry. * @param {import("../../Feature.js").FeatureLike} feature Feature. * @param {number} [index] Render order index. * @override */ drawMultiPolygon(multiPolygonGeometry, feature, index) { const state = this.state; const fillStyle = state.fillStyle; const strokeStyle = state.strokeStyle; const strokeOffset = state.strokeOffset; if (fillStyle === undefined && strokeStyle === undefined) { return; } if ( this.handleStrokeOffset_(() => this.drawMultiPolygon(multiPolygonGeometry, feature, index), ) ) { return; } this.setFillStrokeStyles_(); this.beginGeometry(multiPolygonGeometry, feature, index); if (state.fillStyle !== undefined) { this.hitDetectionInstructions.push([ CanvasInstruction.SET_FILL_STYLE, defaultFillStyle, ]); } if (state.strokeStyle !== undefined) { this.hitDetectionInstructions.push([ CanvasInstruction.SET_STROKE_STYLE, state.strokeStyle, state.lineWidth, state.lineCap, state.lineJoin, state.miterLimit, defaultLineDash, defaultLineDashOffset, ]); } const endss = multiPolygonGeometry.getEndss(); const flatCoordinates = multiPolygonGeometry.getOrientedFlatCoordinates(); const stride = multiPolygonGeometry.getStride(); let offset = 0; for (let i = 0, ii = endss.length; i < ii; ++i) { offset = this.drawFlatCoordinatess_( flatCoordinates, offset, endss[i], stride, strokeOffset, ); } this.endGeometry(feature); } /** * @return {import("../canvas.js").SerializableInstructions} the serializable instructions. * @override */ finish() { this.reverseHitDetectionInstructions(); this.state = null; // We want to preserve topology when drawing polygons. Polygons are // simplified using quantization and point elimination. However, we might // have received a mix of quantized and non-quantized geometries, so ensure // that all are quantized by quantizing all coordinates in the batch. const tolerance = this.tolerance; if (tolerance !== 0) { const coordinates = this.coordinates; for (let i = 0, ii = coordinates.length; i < ii; ++i) { coordinates[i] = snap(coordinates[i], tolerance); } } return super.finish(); } /** * @private */ setFillStrokeStyles_() { const state = this.state; this.updateFillStyle(state, this.createFill); this.updateStrokeStyle(state, this.applyStroke); } handleStrokeOffset_(drawGeometryCallback) { const state = this.state; const fillStyle = state.fillStyle; const strokeStyle = state.strokeStyle; const strokeOffset = state.strokeOffset; // In case both fill style and stroke style are defined and the stroke style has an offset, // the stroke and fill should be done in separate steps, because offset stroke shape will // be different from the original shape used for the fill. if ( Math.abs(strokeOffset) > 0 && fillStyle !== undefined && strokeStyle !== undefined ) { // First do only fill state.strokeStyle = undefined; state.strokeOffset = 0; drawGeometryCallback(); // Now separately do the stroke state.fillStyle = undefined; state.strokeStyle = strokeStyle; state.strokeOffset = strokeOffset; drawGeometryCallback(); // Reset the state to the original state.fillStyle = fillStyle; return true; } return false; } } export default CanvasPolygonBuilder;