Merge pull request #8926 from gberaudo/replay_refactoring
Uncouple replay creation and rendering
This commit is contained in:
+13
-13
@@ -65,9 +65,9 @@ class VectorTile extends Tile {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
* @type {Object<string, import("./render/ReplayGroup.js").default>}
|
* @type {Object<string, import("./render/ExecutorGroup.js").default>}
|
||||||
*/
|
*/
|
||||||
this.replayGroups_ = {};
|
this.executorGroups_ = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
@@ -88,7 +88,7 @@ class VectorTile extends Tile {
|
|||||||
*/
|
*/
|
||||||
disposeInternal() {
|
disposeInternal() {
|
||||||
this.features_ = null;
|
this.features_ = null;
|
||||||
this.replayGroups_ = {};
|
this.executorGroups_ = {};
|
||||||
this.state = TileState.ABORT;
|
this.state = TileState.ABORT;
|
||||||
this.changed();
|
this.changed();
|
||||||
super.disposeInternal();
|
super.disposeInternal();
|
||||||
@@ -142,10 +142,10 @@ class VectorTile extends Tile {
|
|||||||
/**
|
/**
|
||||||
* @param {import("./layer/Layer.js").default} layer Layer.
|
* @param {import("./layer/Layer.js").default} layer Layer.
|
||||||
* @param {string} key Key.
|
* @param {string} key Key.
|
||||||
* @return {import("./render/ReplayGroup.js").default} Replay group.
|
* @return {import("./render/ExecutorGroup.js").default} Executor group.
|
||||||
*/
|
*/
|
||||||
getReplayGroup(layer, key) {
|
getExecutorGroup(layer, key) {
|
||||||
return this.replayGroups_[getUid(layer) + ',' + key];
|
return this.executorGroups_[getUid(layer) + ',' + key];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -153,17 +153,17 @@ class VectorTile extends Tile {
|
|||||||
* @param {import("./layer/Layer").default} layer Layer.
|
* @param {import("./layer/Layer").default} layer Layer.
|
||||||
* @param {number} zoom Zoom.
|
* @param {number} zoom Zoom.
|
||||||
* @param {import("./extent").Extent} extent Extent.
|
* @param {import("./extent").Extent} extent Extent.
|
||||||
* @return {import("./render/ReplayGroup.js").default} Replay groups.
|
* @return {import("./render/ExecutorGroup.js").default} Executor groups.
|
||||||
*/
|
*/
|
||||||
getLowResReplayGroup(layer, zoom, extent) {
|
getLowResExecutorGroup(layer, zoom, extent) {
|
||||||
const layerId = getUid(layer);
|
const layerId = getUid(layer);
|
||||||
let bestZoom = 0;
|
let bestZoom = 0;
|
||||||
let replayGroup = null;
|
let replayGroup = null;
|
||||||
for (const key in this.replayGroups_) {
|
for (const key in this.executorGroups_) {
|
||||||
const keyData = key.split(',');
|
const keyData = key.split(',');
|
||||||
const candidateZoom = Number(keyData[1]);
|
const candidateZoom = Number(keyData[1]);
|
||||||
if (keyData[0] === layerId && candidateZoom <= zoom) {
|
if (keyData[0] === layerId && candidateZoom <= zoom) {
|
||||||
const candidate = this.replayGroups_[key];
|
const candidate = this.executorGroups_[key];
|
||||||
if (containsExtent(candidate.getMaxExtent(), extent) && candidateZoom > bestZoom) {
|
if (containsExtent(candidate.getMaxExtent(), extent) && candidateZoom > bestZoom) {
|
||||||
replayGroup = candidate;
|
replayGroup = candidate;
|
||||||
bestZoom = candidateZoom;
|
bestZoom = candidateZoom;
|
||||||
@@ -244,10 +244,10 @@ class VectorTile extends Tile {
|
|||||||
/**
|
/**
|
||||||
* @param {import("./layer/Layer.js").default} layer Layer.
|
* @param {import("./layer/Layer.js").default} layer Layer.
|
||||||
* @param {string} key Key.
|
* @param {string} key Key.
|
||||||
* @param {import("./render/ReplayGroup.js").default} replayGroup Replay group.
|
* @param {import("./render/ExecutorGroup.js").default} executorGroup Executor group.
|
||||||
*/
|
*/
|
||||||
setReplayGroup(layer, key, replayGroup) {
|
setExecutorGroup(layer, key, executorGroup) {
|
||||||
this.replayGroups_[getUid(layer) + ',' + key] = replayGroup;
|
this.executorGroups_[getUid(layer) + ',' + key] = executorGroup;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
/**
|
/**
|
||||||
* @module ol/render/ReplayGroup
|
* @module ol/render/BuilderGroup
|
||||||
*/
|
*/
|
||||||
import {abstract} from '../util.js';
|
import {abstract} from '../util.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base class for replay groups.
|
* Base class for builder groups.
|
||||||
*/
|
*/
|
||||||
class ReplayGroup {
|
class BuilderGroup {
|
||||||
/**
|
/**
|
||||||
* @abstract
|
* @abstract
|
||||||
* @param {number|undefined} zIndex Z index.
|
* @param {number|undefined} zIndex Z index.
|
||||||
* @param {import("./ReplayType.js").default} replayType Replay type.
|
* @param {import("./ReplayType.js").default} replayType Replay type.
|
||||||
* @return {import("./VectorContext.js").default} Replay.
|
* @return {import("./VectorContext.js").default} Replay.
|
||||||
*/
|
*/
|
||||||
getReplay(zIndex, replayType) {
|
getBuilder(zIndex, replayType) {
|
||||||
return abstract();
|
return abstract();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@ class ReplayGroup {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @abstract
|
* @abstract
|
||||||
* @param {boolean} group Group with previous replay
|
* @param {boolean} group Group with previous builder
|
||||||
* @return {Array<*>} The resulting instruction group
|
* @return {Array<*>} The resulting instruction group
|
||||||
*/
|
*/
|
||||||
addDeclutter(group) {
|
addDeclutter(group) {
|
||||||
@@ -35,4 +35,4 @@ class ReplayGroup {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ReplayGroup;
|
export default BuilderGroup;
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
/**
|
||||||
|
* @module ol/render/ExecutorGroup
|
||||||
|
*/
|
||||||
|
import {abstract} from '../util.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for replay groups.
|
||||||
|
*/
|
||||||
|
class ExecutorGroup {
|
||||||
|
/**
|
||||||
|
* @abstract
|
||||||
|
* @param {number|undefined} zIndex Z index.
|
||||||
|
* @param {import("./ReplayType.js").default} replayType Replay type.
|
||||||
|
* @return {import("./VectorContext.js").default} Executor.
|
||||||
|
*/
|
||||||
|
getExecutor(zIndex, replayType) {
|
||||||
|
return abstract();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @abstract
|
||||||
|
* @return {boolean} Is empty.
|
||||||
|
*/
|
||||||
|
isEmpty() {
|
||||||
|
return abstract();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {import("../extent.js").Extent} The extent of the group.
|
||||||
|
*/
|
||||||
|
getMaxExtent() {
|
||||||
|
return abstract();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @abstract
|
||||||
|
* @param {boolean} group Group with previous executor
|
||||||
|
* @return {Array<*>} The resulting instruction group
|
||||||
|
*/
|
||||||
|
addDeclutter(group) {
|
||||||
|
return abstract();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ExecutorGroup;
|
||||||
@@ -324,6 +324,25 @@ export function measureTextWidth(font, text) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} font Font to use for measuring.
|
||||||
|
* @param {Array<string>} lines Lines to measure.
|
||||||
|
* @param {Array<number>} widths Array will be populated with the widths of
|
||||||
|
* each line.
|
||||||
|
* @return {number} Width of the whole text.
|
||||||
|
*/
|
||||||
|
export function measureTextWidths(font, lines, widths) {
|
||||||
|
const numLines = lines.length;
|
||||||
|
let width = 0;
|
||||||
|
for (let i = 0; i < numLines; ++i) {
|
||||||
|
const currentWidth = measureTextWidth(font, lines[i]);
|
||||||
|
width = Math.max(width, currentWidth);
|
||||||
|
widths.push(currentWidth);
|
||||||
|
}
|
||||||
|
return width;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {CanvasRenderingContext2D} context Context.
|
* @param {CanvasRenderingContext2D} context Context.
|
||||||
* @param {number} rotation Rotation.
|
* @param {number} rotation Rotation.
|
||||||
|
|||||||
@@ -0,0 +1,546 @@
|
|||||||
|
/**
|
||||||
|
* @module ol/render/canvas/Builder
|
||||||
|
*/
|
||||||
|
import {equals, reverseSubArray} from '../../array.js';
|
||||||
|
import {asColorLike} from '../../colorlike.js';
|
||||||
|
import {buffer, clone, coordinateRelationship} from '../../extent.js';
|
||||||
|
import Relationship from '../../extent/Relationship.js';
|
||||||
|
import GeometryType from '../../geom/GeometryType.js';
|
||||||
|
import {inflateCoordinates, inflateCoordinatesArray, inflateMultiCoordinatesArray} from '../../geom/flat/inflate.js';
|
||||||
|
import {CANVAS_LINE_DASH} from '../../has.js';
|
||||||
|
import VectorContext from '../VectorContext.js';
|
||||||
|
import {resetTransform, defaultFillStyle, defaultStrokeStyle,
|
||||||
|
defaultMiterLimit, defaultLineWidth, defaultLineJoin, defaultLineDashOffset,
|
||||||
|
defaultLineDash, defaultLineCap} from '../canvas.js';
|
||||||
|
import CanvasInstruction from './Instruction.js';
|
||||||
|
import {
|
||||||
|
create as createTransform,
|
||||||
|
apply as applyTransform
|
||||||
|
} from '../../transform.js';
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} SerializableInstructions
|
||||||
|
* @property {Array<*>} instructions The rendering instructions.
|
||||||
|
* @property {Array<*>} hitDetectionInstructions The rendering hit detection instructions.
|
||||||
|
* @property {Array<number>} coordinates The array of all coordinates.
|
||||||
|
* @property {!Object<string, import("../canvas.js").TextState>} [textStates] The text states (decluttering).
|
||||||
|
* @property {!Object<string, import("../canvas.js").FillState>} [fillStates] The fill states (decluttering).
|
||||||
|
* @property {!Object<string, import("../canvas.js").StrokeState>} [strokeStates] The stroke states (decluttering).
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
class CanvasBuilder extends VectorContext {
|
||||||
|
/**
|
||||||
|
* @param {number} tolerance Tolerance.
|
||||||
|
* @param {import("../../extent.js").Extent} maxExtent Maximum extent.
|
||||||
|
* @param {number} resolution Resolution.
|
||||||
|
* @param {number} pixelRatio Pixel ratio.
|
||||||
|
* @param {boolean} overlaps The builder can have overlapping geometries.
|
||||||
|
* @param {?} declutterTree Declutter tree.
|
||||||
|
*/
|
||||||
|
constructor(tolerance, maxExtent, resolution, pixelRatio, overlaps, declutterTree) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {?}
|
||||||
|
*/
|
||||||
|
this.declutterTree = declutterTree;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @protected
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
this.tolerance = tolerance;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @protected
|
||||||
|
* @const
|
||||||
|
* @type {import("../../extent.js").Extent}
|
||||||
|
*/
|
||||||
|
this.maxExtent = maxExtent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @protected
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
this.overlaps = overlaps;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @protected
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
this.pixelRatio = pixelRatio;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @protected
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
this.maxLineWidth = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @protected
|
||||||
|
* @const
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
this.resolution = resolution;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
this.alignFill_;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {Array<*>}
|
||||||
|
*/
|
||||||
|
this.beginGeometryInstruction1_ = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {Array<*>}
|
||||||
|
*/
|
||||||
|
this.beginGeometryInstruction2_ = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {import("../../extent.js").Extent}
|
||||||
|
*/
|
||||||
|
this.bufferedMaxExtent_ = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @protected
|
||||||
|
* @type {Array<*>}
|
||||||
|
*/
|
||||||
|
this.instructions = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @protected
|
||||||
|
* @type {Array<number>}
|
||||||
|
*/
|
||||||
|
this.coordinates = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {!Object<number,import("../../coordinate.js").Coordinate|Array<import("../../coordinate.js").Coordinate>|Array<Array<import("../../coordinate.js").Coordinate>>>}
|
||||||
|
*/
|
||||||
|
this.coordinateCache_ = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {!import("../../transform.js").Transform}
|
||||||
|
*/
|
||||||
|
this.renderedTransform_ = createTransform();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @protected
|
||||||
|
* @type {Array<*>}
|
||||||
|
*/
|
||||||
|
this.hitDetectionInstructions = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {Array<number>}
|
||||||
|
*/
|
||||||
|
this.pixelCoordinates_ = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @protected
|
||||||
|
* @type {import("../canvas.js").FillStrokeState}
|
||||||
|
*/
|
||||||
|
this.state = /** @type {import("../canvas.js").FillStrokeState} */ ({});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
this.viewRotation_ = 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @protected
|
||||||
|
* @param {Array<number>} dashArray Dash array.
|
||||||
|
* @return {Array<number>} Dash array with pixel ratio applied
|
||||||
|
*/
|
||||||
|
applyPixelRatio(dashArray) {
|
||||||
|
const pixelRatio = this.pixelRatio;
|
||||||
|
return pixelRatio == 1 ? dashArray : dashArray.map(function(dash) {
|
||||||
|
return dash * pixelRatio;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Array<number>} flatCoordinates Flat coordinates.
|
||||||
|
* @param {number} offset Offset.
|
||||||
|
* @param {number} end End.
|
||||||
|
* @param {number} stride Stride.
|
||||||
|
* @param {boolean} closed Last input coordinate equals first.
|
||||||
|
* @param {boolean} skipFirst Skip first coordinate.
|
||||||
|
* @protected
|
||||||
|
* @return {number} My end.
|
||||||
|
*/
|
||||||
|
appendFlatCoordinates(flatCoordinates, offset, end, stride, closed, skipFirst) {
|
||||||
|
|
||||||
|
let myEnd = this.coordinates.length;
|
||||||
|
const extent = this.getBufferedMaxExtent();
|
||||||
|
if (skipFirst) {
|
||||||
|
offset += stride;
|
||||||
|
}
|
||||||
|
const lastCoord = [flatCoordinates[offset], flatCoordinates[offset + 1]];
|
||||||
|
const nextCoord = [NaN, NaN];
|
||||||
|
let skipped = true;
|
||||||
|
|
||||||
|
let i, lastRel, nextRel;
|
||||||
|
for (i = offset + stride; i < end; i += stride) {
|
||||||
|
nextCoord[0] = flatCoordinates[i];
|
||||||
|
nextCoord[1] = flatCoordinates[i + 1];
|
||||||
|
nextRel = coordinateRelationship(extent, nextCoord);
|
||||||
|
if (nextRel !== lastRel) {
|
||||||
|
if (skipped) {
|
||||||
|
this.coordinates[myEnd++] = lastCoord[0];
|
||||||
|
this.coordinates[myEnd++] = lastCoord[1];
|
||||||
|
}
|
||||||
|
this.coordinates[myEnd++] = nextCoord[0];
|
||||||
|
this.coordinates[myEnd++] = nextCoord[1];
|
||||||
|
skipped = false;
|
||||||
|
} else if (nextRel === Relationship.INTERSECTING) {
|
||||||
|
this.coordinates[myEnd++] = nextCoord[0];
|
||||||
|
this.coordinates[myEnd++] = nextCoord[1];
|
||||||
|
skipped = false;
|
||||||
|
} else {
|
||||||
|
skipped = true;
|
||||||
|
}
|
||||||
|
lastCoord[0] = nextCoord[0];
|
||||||
|
lastCoord[1] = nextCoord[1];
|
||||||
|
lastRel = nextRel;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Last coordinate equals first or only one point to append:
|
||||||
|
if ((closed && skipped) || i === offset + stride) {
|
||||||
|
this.coordinates[myEnd++] = lastCoord[0];
|
||||||
|
this.coordinates[myEnd++] = lastCoord[1];
|
||||||
|
}
|
||||||
|
return myEnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Array<number>} flatCoordinates Flat coordinates.
|
||||||
|
* @param {number} offset Offset.
|
||||||
|
* @param {Array<number>} ends Ends.
|
||||||
|
* @param {number} stride Stride.
|
||||||
|
* @param {Array<number>} builderEnds Builder ends.
|
||||||
|
* @return {number} Offset.
|
||||||
|
*/
|
||||||
|
drawCustomCoordinates_(flatCoordinates, offset, ends, stride, builderEnds) {
|
||||||
|
for (let i = 0, ii = ends.length; i < ii; ++i) {
|
||||||
|
const end = ends[i];
|
||||||
|
const builderEnd = this.appendFlatCoordinates(flatCoordinates, offset, end, stride, false, false);
|
||||||
|
builderEnds.push(builderEnd);
|
||||||
|
offset = end;
|
||||||
|
}
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc.
|
||||||
|
*/
|
||||||
|
drawCustom(geometry, feature, renderer) {
|
||||||
|
this.beginGeometry(geometry, feature);
|
||||||
|
const type = geometry.getType();
|
||||||
|
const stride = geometry.getStride();
|
||||||
|
const builderBegin = this.coordinates.length;
|
||||||
|
let flatCoordinates, builderEnd, builderEnds, builderEndss;
|
||||||
|
let offset;
|
||||||
|
if (type == GeometryType.MULTI_POLYGON) {
|
||||||
|
geometry = /** @type {import("../../geom/MultiPolygon.js").default} */ (geometry);
|
||||||
|
flatCoordinates = geometry.getOrientedFlatCoordinates();
|
||||||
|
builderEndss = [];
|
||||||
|
const endss = geometry.getEndss();
|
||||||
|
offset = 0;
|
||||||
|
for (let i = 0, ii = endss.length; i < ii; ++i) {
|
||||||
|
const myEnds = [];
|
||||||
|
offset = this.drawCustomCoordinates_(flatCoordinates, offset, endss[i], stride, myEnds);
|
||||||
|
builderEndss.push(myEnds);
|
||||||
|
}
|
||||||
|
this.instructions.push([CanvasInstruction.CUSTOM,
|
||||||
|
builderBegin, builderEndss, geometry, renderer, inflateMultiCoordinatesArray]);
|
||||||
|
} else if (type == GeometryType.POLYGON || type == GeometryType.MULTI_LINE_STRING) {
|
||||||
|
builderEnds = [];
|
||||||
|
flatCoordinates = (type == GeometryType.POLYGON) ?
|
||||||
|
/** @type {import("../../geom/Polygon.js").default} */ (geometry).getOrientedFlatCoordinates() :
|
||||||
|
geometry.getFlatCoordinates();
|
||||||
|
offset = this.drawCustomCoordinates_(flatCoordinates, 0,
|
||||||
|
/** @type {import("../../geom/Polygon.js").default|import("../../geom/MultiLineString.js").default} */ (geometry).getEnds(),
|
||||||
|
stride, builderEnds);
|
||||||
|
this.instructions.push([CanvasInstruction.CUSTOM,
|
||||||
|
builderBegin, builderEnds, geometry, renderer, inflateCoordinatesArray]);
|
||||||
|
} else if (type == GeometryType.LINE_STRING || type == GeometryType.MULTI_POINT) {
|
||||||
|
flatCoordinates = geometry.getFlatCoordinates();
|
||||||
|
builderEnd = this.appendFlatCoordinates(
|
||||||
|
flatCoordinates, 0, flatCoordinates.length, stride, false, false);
|
||||||
|
this.instructions.push([CanvasInstruction.CUSTOM,
|
||||||
|
builderBegin, builderEnd, geometry, renderer, inflateCoordinates]);
|
||||||
|
} else if (type == GeometryType.POINT) {
|
||||||
|
flatCoordinates = geometry.getFlatCoordinates();
|
||||||
|
this.coordinates.push(flatCoordinates[0], flatCoordinates[1]);
|
||||||
|
builderEnd = this.coordinates.length;
|
||||||
|
this.instructions.push([CanvasInstruction.CUSTOM,
|
||||||
|
builderBegin, builderEnd, geometry, renderer]);
|
||||||
|
}
|
||||||
|
this.endGeometry(geometry, feature);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @protected
|
||||||
|
* @param {import("../../geom/Geometry.js").default|import("../Feature.js").default} geometry Geometry.
|
||||||
|
* @param {import("../../Feature.js").default|import("../Feature.js").default} feature Feature.
|
||||||
|
*/
|
||||||
|
beginGeometry(geometry, feature) {
|
||||||
|
this.beginGeometryInstruction1_ = [CanvasInstruction.BEGIN_GEOMETRY, feature, 0];
|
||||||
|
this.instructions.push(this.beginGeometryInstruction1_);
|
||||||
|
this.beginGeometryInstruction2_ = [CanvasInstruction.BEGIN_GEOMETRY, feature, 0];
|
||||||
|
this.hitDetectionInstructions.push(this.beginGeometryInstruction2_);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {SerializableInstructions} the serializable instructions.
|
||||||
|
*/
|
||||||
|
finish() {
|
||||||
|
return {
|
||||||
|
instructions: this.instructions,
|
||||||
|
hitDetectionInstructions: this.hitDetectionInstructions,
|
||||||
|
coordinates: this.coordinates
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @param {CanvasRenderingContext2D} context Context.
|
||||||
|
*/
|
||||||
|
fill_(context) {
|
||||||
|
if (this.alignFill_) {
|
||||||
|
const origin = applyTransform(this.renderedTransform_, [0, 0]);
|
||||||
|
const repeatSize = 512 * this.pixelRatio;
|
||||||
|
context.translate(origin[0] % repeatSize, origin[1] % repeatSize);
|
||||||
|
context.rotate(this.viewRotation_);
|
||||||
|
}
|
||||||
|
context.fill();
|
||||||
|
if (this.alignFill_) {
|
||||||
|
context.setTransform.apply(context, resetTransform);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @param {CanvasRenderingContext2D} context Context.
|
||||||
|
* @param {Array<*>} instruction Instruction.
|
||||||
|
*/
|
||||||
|
setStrokeStyle_(context, instruction) {
|
||||||
|
context.strokeStyle = /** @type {import("../../colorlike.js").ColorLike} */ (instruction[1]);
|
||||||
|
context.lineWidth = /** @type {number} */ (instruction[2]);
|
||||||
|
context.lineCap = /** @type {CanvasLineCap} */ (instruction[3]);
|
||||||
|
context.lineJoin = /** @type {CanvasLineJoin} */ (instruction[4]);
|
||||||
|
context.miterLimit = /** @type {number} */ (instruction[5]);
|
||||||
|
if (CANVAS_LINE_DASH) {
|
||||||
|
context.lineDashOffset = /** @type {number} */ (instruction[7]);
|
||||||
|
context.setLineDash(/** @type {Array<number>} */ (instruction[6]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the hit detection instructions.
|
||||||
|
*/
|
||||||
|
reverseHitDetectionInstructions() {
|
||||||
|
const hitDetectionInstructions = this.hitDetectionInstructions;
|
||||||
|
// step 1 - reverse array
|
||||||
|
hitDetectionInstructions.reverse();
|
||||||
|
// step 2 - reverse instructions within geometry blocks
|
||||||
|
let i;
|
||||||
|
const n = hitDetectionInstructions.length;
|
||||||
|
let instruction;
|
||||||
|
let type;
|
||||||
|
let begin = -1;
|
||||||
|
for (i = 0; i < n; ++i) {
|
||||||
|
instruction = hitDetectionInstructions[i];
|
||||||
|
type = /** @type {CanvasInstruction} */ (instruction[0]);
|
||||||
|
if (type == CanvasInstruction.END_GEOMETRY) {
|
||||||
|
begin = i;
|
||||||
|
} else if (type == CanvasInstruction.BEGIN_GEOMETRY) {
|
||||||
|
instruction[2] = i;
|
||||||
|
reverseSubArray(this.hitDetectionInstructions, begin, i);
|
||||||
|
begin = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
setFillStrokeStyle(fillStyle, strokeStyle) {
|
||||||
|
const state = this.state;
|
||||||
|
if (fillStyle) {
|
||||||
|
const fillStyleColor = fillStyle.getColor();
|
||||||
|
state.fillStyle = asColorLike(fillStyleColor ?
|
||||||
|
fillStyleColor : defaultFillStyle);
|
||||||
|
} else {
|
||||||
|
state.fillStyle = undefined;
|
||||||
|
}
|
||||||
|
if (strokeStyle) {
|
||||||
|
const strokeStyleColor = strokeStyle.getColor();
|
||||||
|
state.strokeStyle = asColorLike(strokeStyleColor ?
|
||||||
|
strokeStyleColor : defaultStrokeStyle);
|
||||||
|
const strokeStyleLineCap = strokeStyle.getLineCap();
|
||||||
|
state.lineCap = strokeStyleLineCap !== undefined ?
|
||||||
|
strokeStyleLineCap : defaultLineCap;
|
||||||
|
const strokeStyleLineDash = strokeStyle.getLineDash();
|
||||||
|
state.lineDash = strokeStyleLineDash ?
|
||||||
|
strokeStyleLineDash.slice() : defaultLineDash;
|
||||||
|
const strokeStyleLineDashOffset = strokeStyle.getLineDashOffset();
|
||||||
|
state.lineDashOffset = strokeStyleLineDashOffset ?
|
||||||
|
strokeStyleLineDashOffset : defaultLineDashOffset;
|
||||||
|
const strokeStyleLineJoin = strokeStyle.getLineJoin();
|
||||||
|
state.lineJoin = strokeStyleLineJoin !== undefined ?
|
||||||
|
strokeStyleLineJoin : defaultLineJoin;
|
||||||
|
const strokeStyleWidth = strokeStyle.getWidth();
|
||||||
|
state.lineWidth = strokeStyleWidth !== undefined ?
|
||||||
|
strokeStyleWidth : defaultLineWidth;
|
||||||
|
const strokeStyleMiterLimit = strokeStyle.getMiterLimit();
|
||||||
|
state.miterLimit = strokeStyleMiterLimit !== undefined ?
|
||||||
|
strokeStyleMiterLimit : defaultMiterLimit;
|
||||||
|
|
||||||
|
if (state.lineWidth > this.maxLineWidth) {
|
||||||
|
this.maxLineWidth = state.lineWidth;
|
||||||
|
// invalidate the buffered max extent cache
|
||||||
|
this.bufferedMaxExtent_ = null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
state.strokeStyle = undefined;
|
||||||
|
state.lineCap = undefined;
|
||||||
|
state.lineDash = null;
|
||||||
|
state.lineDashOffset = undefined;
|
||||||
|
state.lineJoin = undefined;
|
||||||
|
state.lineWidth = undefined;
|
||||||
|
state.miterLimit = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import("../canvas.js").FillStrokeState} state State.
|
||||||
|
* @param {import("../../geom/Geometry.js").default|import("../Feature.js").default} geometry Geometry.
|
||||||
|
* @return {Array<*>} Fill instruction.
|
||||||
|
*/
|
||||||
|
createFill(state, geometry) {
|
||||||
|
const fillStyle = state.fillStyle;
|
||||||
|
/** @type {Array<*>} */
|
||||||
|
const fillInstruction = [CanvasInstruction.SET_FILL_STYLE, fillStyle];
|
||||||
|
if (typeof fillStyle !== 'string') {
|
||||||
|
// Fill is a pattern or gradient - align it!
|
||||||
|
fillInstruction.push(true);
|
||||||
|
}
|
||||||
|
return fillInstruction;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import("../canvas.js").FillStrokeState} state State.
|
||||||
|
*/
|
||||||
|
applyStroke(state) {
|
||||||
|
this.instructions.push(this.createStroke(state));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import("../canvas.js").FillStrokeState} state State.
|
||||||
|
* @return {Array<*>} Stroke instruction.
|
||||||
|
*/
|
||||||
|
createStroke(state) {
|
||||||
|
return [
|
||||||
|
CanvasInstruction.SET_STROKE_STYLE,
|
||||||
|
state.strokeStyle, state.lineWidth * this.pixelRatio, state.lineCap,
|
||||||
|
state.lineJoin, state.miterLimit,
|
||||||
|
this.applyPixelRatio(state.lineDash), state.lineDashOffset * this.pixelRatio
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import("../canvas.js").FillStrokeState} state State.
|
||||||
|
* @param {function(this:CanvasBuilder, import("../canvas.js").FillStrokeState, (import("../../geom/Geometry.js").default|import("../Feature.js").default)):Array<*>} createFill Create fill.
|
||||||
|
* @param {import("../../geom/Geometry.js").default|import("../Feature.js").default} geometry Geometry.
|
||||||
|
*/
|
||||||
|
updateFillStyle(state, createFill, geometry) {
|
||||||
|
const fillStyle = state.fillStyle;
|
||||||
|
if (typeof fillStyle !== 'string' || state.currentFillStyle != fillStyle) {
|
||||||
|
if (fillStyle !== undefined) {
|
||||||
|
this.instructions.push(createFill.call(this, state, geometry));
|
||||||
|
}
|
||||||
|
state.currentFillStyle = fillStyle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import("../canvas.js").FillStrokeState} state State.
|
||||||
|
* @param {function(this:CanvasBuilder, import("../canvas.js").FillStrokeState)} applyStroke Apply stroke.
|
||||||
|
*/
|
||||||
|
updateStrokeStyle(state, applyStroke) {
|
||||||
|
const strokeStyle = state.strokeStyle;
|
||||||
|
const lineCap = state.lineCap;
|
||||||
|
const lineDash = state.lineDash;
|
||||||
|
const lineDashOffset = state.lineDashOffset;
|
||||||
|
const lineJoin = state.lineJoin;
|
||||||
|
const lineWidth = state.lineWidth;
|
||||||
|
const miterLimit = state.miterLimit;
|
||||||
|
if (state.currentStrokeStyle != strokeStyle ||
|
||||||
|
state.currentLineCap != lineCap ||
|
||||||
|
(lineDash != state.currentLineDash && !equals(state.currentLineDash, lineDash)) ||
|
||||||
|
state.currentLineDashOffset != lineDashOffset ||
|
||||||
|
state.currentLineJoin != lineJoin ||
|
||||||
|
state.currentLineWidth != lineWidth ||
|
||||||
|
state.currentMiterLimit != miterLimit) {
|
||||||
|
if (strokeStyle !== undefined) {
|
||||||
|
applyStroke.call(this, state);
|
||||||
|
}
|
||||||
|
state.currentStrokeStyle = strokeStyle;
|
||||||
|
state.currentLineCap = lineCap;
|
||||||
|
state.currentLineDash = lineDash;
|
||||||
|
state.currentLineDashOffset = lineDashOffset;
|
||||||
|
state.currentLineJoin = lineJoin;
|
||||||
|
state.currentLineWidth = lineWidth;
|
||||||
|
state.currentMiterLimit = miterLimit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import("../../geom/Geometry.js").default|import("../Feature.js").default} geometry Geometry.
|
||||||
|
* @param {import("../../Feature.js").default|import("../Feature.js").default} feature Feature.
|
||||||
|
*/
|
||||||
|
endGeometry(geometry, feature) {
|
||||||
|
this.beginGeometryInstruction1_[2] = this.instructions.length;
|
||||||
|
this.beginGeometryInstruction1_ = null;
|
||||||
|
this.beginGeometryInstruction2_[2] = this.hitDetectionInstructions.length;
|
||||||
|
this.beginGeometryInstruction2_ = null;
|
||||||
|
const endGeometryInstruction = [CanvasInstruction.END_GEOMETRY, feature];
|
||||||
|
this.instructions.push(endGeometryInstruction);
|
||||||
|
this.hitDetectionInstructions.push(endGeometryInstruction);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the buffered rendering extent. Rendering will be clipped to the extent
|
||||||
|
* provided to the constructor. To account for symbolizers that may intersect
|
||||||
|
* this extent, we calculate a buffered extent (e.g. based on stroke width).
|
||||||
|
* @return {import("../../extent.js").Extent} The buffered rendering extent.
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
getBufferedMaxExtent() {
|
||||||
|
if (!this.bufferedMaxExtent_) {
|
||||||
|
this.bufferedMaxExtent_ = clone(this.maxExtent);
|
||||||
|
if (this.maxLineWidth > 0) {
|
||||||
|
const width = this.resolution * (this.maxLineWidth + 1) / 2;
|
||||||
|
buffer(this.bufferedMaxExtent_, width, this.bufferedMaxExtent_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this.bufferedMaxExtent_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default CanvasBuilder;
|
||||||
@@ -0,0 +1,409 @@
|
|||||||
|
/**
|
||||||
|
* @module ol/render/canvas/BuilderGroup
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {numberSafeCompareFunction} from '../../array.js';
|
||||||
|
import {createCanvasContext2D} from '../../dom.js';
|
||||||
|
import {buffer, createEmpty, extendCoordinate} from '../../extent.js';
|
||||||
|
import {transform2D} from '../../geom/flat/transform.js';
|
||||||
|
import {isEmpty} from '../../obj.js';
|
||||||
|
import BuilderGroup from '../BuilderGroup.js';
|
||||||
|
import ReplayType from '../ReplayType.js';
|
||||||
|
import CanvasBuilder from './Builder.js';
|
||||||
|
import CanvasImageBuilder from './ImageBuilder.js';
|
||||||
|
import CanvasLineStringBuilder from './LineStringBuilder.js';
|
||||||
|
import CanvasPolygonBuilder from './PolygonBuilder.js';
|
||||||
|
import CanvasTextBuilder from './TextBuilder.js';
|
||||||
|
import {ORDER} from '../replay.js';
|
||||||
|
import {create as createTransform, compose as composeTransform} from '../../transform.js';
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {Object<ReplayType, typeof CanvasBuilder>}
|
||||||
|
*/
|
||||||
|
const BATCH_CONSTRUCTORS = {
|
||||||
|
'Circle': CanvasPolygonBuilder,
|
||||||
|
'Default': CanvasBuilder,
|
||||||
|
'Image': CanvasImageBuilder,
|
||||||
|
'LineString': CanvasLineStringBuilder,
|
||||||
|
'Polygon': CanvasPolygonBuilder,
|
||||||
|
'Text': CanvasTextBuilder
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class CanvasBuilderGroup extends BuilderGroup {
|
||||||
|
/**
|
||||||
|
* @param {number} tolerance Tolerance.
|
||||||
|
* @param {import("../../extent.js").Extent} maxExtent Max extent.
|
||||||
|
* @param {number} resolution Resolution.
|
||||||
|
* @param {number} pixelRatio Pixel ratio.
|
||||||
|
* @param {boolean} overlaps The builder group can have overlapping geometries.
|
||||||
|
* @param {?} declutterTree Declutter tree for declutter processing in postrender.
|
||||||
|
* @param {number=} opt_renderBuffer Optional rendering buffer.
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
tolerance,
|
||||||
|
maxExtent,
|
||||||
|
resolution,
|
||||||
|
pixelRatio,
|
||||||
|
overlaps,
|
||||||
|
declutterTree,
|
||||||
|
opt_renderBuffer
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Declutter tree.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.declutterTree_ = declutterTree;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {import("../canvas.js").DeclutterGroup}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.declutterGroup_ = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
this.tolerance_ = tolerance;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {import("../../extent.js").Extent}
|
||||||
|
*/
|
||||||
|
this.maxExtent_ = maxExtent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
this.overlaps_ = overlaps;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
this.pixelRatio_ = pixelRatio;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
this.resolution_ = resolution;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {number|undefined}
|
||||||
|
*/
|
||||||
|
this.renderBuffer_ = opt_renderBuffer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {!Object<string, !Object<ReplayType, CanvasBuilder>>}
|
||||||
|
*/
|
||||||
|
this.buildersByZIndex_ = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {CanvasRenderingContext2D}
|
||||||
|
*/
|
||||||
|
this.hitDetectionContext_ = createCanvasContext2D(1, 1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {import("../../transform.js").Transform}
|
||||||
|
*/
|
||||||
|
this.hitDetectionTransform_ = createTransform();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
addDeclutter(group) {
|
||||||
|
let declutter = null;
|
||||||
|
if (this.declutterTree_) {
|
||||||
|
if (group) {
|
||||||
|
declutter = this.declutterGroup_;
|
||||||
|
/** @type {number} */ (declutter[4])++;
|
||||||
|
} else {
|
||||||
|
declutter = this.declutterGroup_ = createEmpty();
|
||||||
|
declutter.push(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return declutter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {CanvasRenderingContext2D} context Context.
|
||||||
|
* @param {import("../../transform.js").Transform} transform Transform.
|
||||||
|
*/
|
||||||
|
clip(context, transform) {
|
||||||
|
const flatClipCoords = this.getClipCoords(transform);
|
||||||
|
context.beginPath();
|
||||||
|
context.moveTo(flatClipCoords[0], flatClipCoords[1]);
|
||||||
|
context.lineTo(flatClipCoords[2], flatClipCoords[3]);
|
||||||
|
context.lineTo(flatClipCoords[4], flatClipCoords[5]);
|
||||||
|
context.lineTo(flatClipCoords[6], flatClipCoords[7]);
|
||||||
|
context.clip();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {!Object<string, !Object<ReplayType, import("./Builder.js").SerializableInstructions>>} The serializable instructions
|
||||||
|
*/
|
||||||
|
finish() {
|
||||||
|
const builderInstructions = {};
|
||||||
|
for (const zKey in this.buildersByZIndex_) {
|
||||||
|
builderInstructions[zKey] = builderInstructions[zKey] || {};
|
||||||
|
const builders = this.buildersByZIndex_[zKey];
|
||||||
|
for (const builderKey in builders) {
|
||||||
|
const builderInstruction = builders[builderKey].finish();
|
||||||
|
builderInstructions[zKey][builderKey] = builderInstruction;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return builderInstructions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import("../../coordinate.js").Coordinate} coordinate Coordinate.
|
||||||
|
* @param {number} resolution Resolution.
|
||||||
|
* @param {number} rotation Rotation.
|
||||||
|
* @param {number} hitTolerance Hit tolerance in pixels.
|
||||||
|
* @param {Object<string, boolean>} skippedFeaturesHash Ids of features to skip.
|
||||||
|
* @param {function((import("../../Feature.js").default|import("../Feature.js").default)): T} callback Feature callback.
|
||||||
|
* @param {Object<string, import("../canvas.js").DeclutterGroup>} declutterReplays Declutter replays.
|
||||||
|
* @return {T|undefined} Callback result.
|
||||||
|
* @template T
|
||||||
|
*/
|
||||||
|
forEachFeatureAtCoordinate(
|
||||||
|
coordinate,
|
||||||
|
resolution,
|
||||||
|
rotation,
|
||||||
|
hitTolerance,
|
||||||
|
skippedFeaturesHash,
|
||||||
|
callback,
|
||||||
|
declutterReplays
|
||||||
|
) {
|
||||||
|
|
||||||
|
hitTolerance = Math.round(hitTolerance);
|
||||||
|
const contextSize = hitTolerance * 2 + 1;
|
||||||
|
const transform = composeTransform(this.hitDetectionTransform_,
|
||||||
|
hitTolerance + 0.5, hitTolerance + 0.5,
|
||||||
|
1 / resolution, -1 / resolution,
|
||||||
|
-rotation,
|
||||||
|
-coordinate[0], -coordinate[1]);
|
||||||
|
const context = this.hitDetectionContext_;
|
||||||
|
|
||||||
|
if (context.canvas.width !== contextSize || context.canvas.height !== contextSize) {
|
||||||
|
context.canvas.width = contextSize;
|
||||||
|
context.canvas.height = contextSize;
|
||||||
|
} else {
|
||||||
|
context.clearRect(0, 0, contextSize, contextSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {import("../../extent.js").Extent}
|
||||||
|
*/
|
||||||
|
let hitExtent;
|
||||||
|
if (this.renderBuffer_ !== undefined) {
|
||||||
|
hitExtent = createEmpty();
|
||||||
|
extendCoordinate(hitExtent, coordinate);
|
||||||
|
buffer(hitExtent, resolution * (this.renderBuffer_ + hitTolerance), hitExtent);
|
||||||
|
}
|
||||||
|
|
||||||
|
const mask = getCircleArray(hitTolerance);
|
||||||
|
let declutteredFeatures;
|
||||||
|
if (this.declutterTree_) {
|
||||||
|
declutteredFeatures = this.declutterTree_.all().map(function(entry) {
|
||||||
|
return entry.value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let replayType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import("../../Feature.js").default|import("../Feature.js").default} feature Feature.
|
||||||
|
* @return {?} Callback result.
|
||||||
|
*/
|
||||||
|
function featureCallback(feature) {
|
||||||
|
const imageData = context.getImageData(0, 0, contextSize, contextSize).data;
|
||||||
|
for (let i = 0; i < contextSize; i++) {
|
||||||
|
for (let j = 0; j < contextSize; j++) {
|
||||||
|
if (mask[i][j]) {
|
||||||
|
if (imageData[(j * contextSize + i) * 4 + 3] > 0) {
|
||||||
|
let result;
|
||||||
|
if (!(declutteredFeatures && (replayType == ReplayType.IMAGE || replayType == ReplayType.TEXT)) ||
|
||||||
|
declutteredFeatures.indexOf(feature) !== -1) {
|
||||||
|
result = callback(feature);
|
||||||
|
}
|
||||||
|
if (result) {
|
||||||
|
return result;
|
||||||
|
} else {
|
||||||
|
context.clearRect(0, 0, contextSize, contextSize);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {Array<number>} */
|
||||||
|
const zs = Object.keys(this.buildersByZIndex_).map(Number);
|
||||||
|
zs.sort(numberSafeCompareFunction);
|
||||||
|
|
||||||
|
let i, j, builders, builder, result;
|
||||||
|
for (i = zs.length - 1; i >= 0; --i) {
|
||||||
|
const zIndexKey = zs[i].toString();
|
||||||
|
builders = this.buildersByZIndex_[zIndexKey];
|
||||||
|
for (j = ORDER.length - 1; j >= 0; --j) {
|
||||||
|
replayType = ORDER[j];
|
||||||
|
builder = builders[replayType];
|
||||||
|
if (builder !== undefined) {
|
||||||
|
if (declutterReplays &&
|
||||||
|
(replayType == ReplayType.IMAGE || replayType == ReplayType.TEXT)) {
|
||||||
|
const declutter = declutterReplays[zIndexKey];
|
||||||
|
if (!declutter) {
|
||||||
|
declutterReplays[zIndexKey] = [builder, transform.slice(0)];
|
||||||
|
} else {
|
||||||
|
declutter.push(builder, transform.slice(0));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result = builder.executeHitDetection(context, transform, rotation,
|
||||||
|
skippedFeaturesHash, featureCallback, hitExtent);
|
||||||
|
if (result) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import("../../transform.js").Transform} transform Transform.
|
||||||
|
* @return {Array<number>} Clip coordinates.
|
||||||
|
*/
|
||||||
|
getClipCoords(transform) {
|
||||||
|
const maxExtent = this.maxExtent_;
|
||||||
|
const minX = maxExtent[0];
|
||||||
|
const minY = maxExtent[1];
|
||||||
|
const maxX = maxExtent[2];
|
||||||
|
const maxY = maxExtent[3];
|
||||||
|
const flatClipCoords = [minX, minY, minX, maxY, maxX, maxY, maxX, minY];
|
||||||
|
transform2D(
|
||||||
|
flatClipCoords, 0, 8, 2, transform, flatClipCoords);
|
||||||
|
return flatClipCoords;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
getBuilder(zIndex, replayType) {
|
||||||
|
const zIndexKey = zIndex !== undefined ? zIndex.toString() : '0';
|
||||||
|
let replays = this.buildersByZIndex_[zIndexKey];
|
||||||
|
if (replays === undefined) {
|
||||||
|
replays = {};
|
||||||
|
this.buildersByZIndex_[zIndexKey] = replays;
|
||||||
|
}
|
||||||
|
let replay = replays[replayType];
|
||||||
|
if (replay === undefined) {
|
||||||
|
const Constructor = BATCH_CONSTRUCTORS[replayType];
|
||||||
|
replay = new Constructor(this.tolerance_, this.maxExtent_,
|
||||||
|
this.resolution_, this.pixelRatio_, this.overlaps_, this.declutterTree_);
|
||||||
|
replays[replayType] = replay;
|
||||||
|
}
|
||||||
|
return replay;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
isEmpty() {
|
||||||
|
return isEmpty(this.buildersByZIndex_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This cache is used for storing calculated pixel circles for increasing performance.
|
||||||
|
* It is a static property to allow each Replaygroup to access it.
|
||||||
|
* @type {Object<number, Array<Array<(boolean|undefined)>>>}
|
||||||
|
*/
|
||||||
|
const circleArrayCache = {
|
||||||
|
0: [[true]]
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method fills a row in the array from the given coordinate to the
|
||||||
|
* middle with `true`.
|
||||||
|
* @param {Array<Array<(boolean|undefined)>>} array The array that will be altered.
|
||||||
|
* @param {number} x X coordinate.
|
||||||
|
* @param {number} y Y coordinate.
|
||||||
|
*/
|
||||||
|
function fillCircleArrayRowToMiddle(array, x, y) {
|
||||||
|
let i;
|
||||||
|
const radius = Math.floor(array.length / 2);
|
||||||
|
if (x >= radius) {
|
||||||
|
for (i = radius; i < x; i++) {
|
||||||
|
array[i][y] = true;
|
||||||
|
}
|
||||||
|
} else if (x < radius) {
|
||||||
|
for (i = x + 1; i < radius; i++) {
|
||||||
|
array[i][y] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This methods creates a circle inside a fitting array. Points inside the
|
||||||
|
* circle are marked by true, points on the outside are undefined.
|
||||||
|
* It uses the midpoint circle algorithm.
|
||||||
|
* A cache is used to increase performance.
|
||||||
|
* @param {number} radius Radius.
|
||||||
|
* @returns {Array<Array<(boolean|undefined)>>} An array with marked circle points.
|
||||||
|
*/
|
||||||
|
export function getCircleArray(radius) {
|
||||||
|
if (circleArrayCache[radius] !== undefined) {
|
||||||
|
return circleArrayCache[radius];
|
||||||
|
}
|
||||||
|
|
||||||
|
const arraySize = radius * 2 + 1;
|
||||||
|
const arr = new Array(arraySize);
|
||||||
|
for (let i = 0; i < arraySize; i++) {
|
||||||
|
arr[i] = new Array(arraySize);
|
||||||
|
}
|
||||||
|
|
||||||
|
let x = radius;
|
||||||
|
let y = 0;
|
||||||
|
let error = 0;
|
||||||
|
|
||||||
|
while (x >= y) {
|
||||||
|
fillCircleArrayRowToMiddle(arr, radius + x, radius + y);
|
||||||
|
fillCircleArrayRowToMiddle(arr, radius + y, radius + x);
|
||||||
|
fillCircleArrayRowToMiddle(arr, radius - y, radius + x);
|
||||||
|
fillCircleArrayRowToMiddle(arr, radius - x, radius + y);
|
||||||
|
fillCircleArrayRowToMiddle(arr, radius - x, radius - y);
|
||||||
|
fillCircleArrayRowToMiddle(arr, radius - y, radius - x);
|
||||||
|
fillCircleArrayRowToMiddle(arr, radius + y, radius - x);
|
||||||
|
fillCircleArrayRowToMiddle(arr, radius + x, radius - y);
|
||||||
|
|
||||||
|
y++;
|
||||||
|
error += 1 + 2 * y;
|
||||||
|
if (2 * (error - x) + 1 > 0) {
|
||||||
|
x -= 1;
|
||||||
|
error += 1 - 2 * x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
circleArrayCache[radius] = arr;
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CanvasBuilderGroup;
|
||||||
@@ -1,23 +1,16 @@
|
|||||||
/**
|
/**
|
||||||
* @module ol/render/canvas/Replay
|
* @module ol/render/canvas/Executor
|
||||||
*/
|
*/
|
||||||
import {getUid} from '../../util.js';
|
import {getUid} from '../../util.js';
|
||||||
import {equals, reverseSubArray} from '../../array.js';
|
import {equals, reverseSubArray} from '../../array.js';
|
||||||
import {asColorLike} from '../../colorlike.js';
|
import {buffer, clone, createEmpty, createOrUpdate,
|
||||||
import {buffer, clone, coordinateRelationship, createEmpty, createOrUpdate,
|
|
||||||
createOrUpdateEmpty, extend, extendCoordinate, intersects} from '../../extent.js';
|
createOrUpdateEmpty, extend, extendCoordinate, intersects} from '../../extent.js';
|
||||||
import Relationship from '../../extent/Relationship.js';
|
|
||||||
import GeometryType from '../../geom/GeometryType.js';
|
|
||||||
import {inflateCoordinates, inflateCoordinatesArray, inflateMultiCoordinatesArray} from '../../geom/flat/inflate.js';
|
|
||||||
import {lineStringLength} from '../../geom/flat/length.js';
|
import {lineStringLength} from '../../geom/flat/length.js';
|
||||||
import {drawTextOnPath} from '../../geom/flat/textpath.js';
|
import {drawTextOnPath} from '../../geom/flat/textpath.js';
|
||||||
import {transform2D} from '../../geom/flat/transform.js';
|
import {transform2D} from '../../geom/flat/transform.js';
|
||||||
import {CANVAS_LINE_DASH} from '../../has.js';
|
import {CANVAS_LINE_DASH} from '../../has.js';
|
||||||
import {isEmpty} from '../../obj.js';
|
import {isEmpty} from '../../obj.js';
|
||||||
import VectorContext from '../VectorContext.js';
|
import {drawImage, resetTransform, defaultPadding, defaultTextBaseline} from '../canvas.js';
|
||||||
import {drawImage, resetTransform, defaultPadding, defaultFillStyle, defaultStrokeStyle,
|
|
||||||
defaultMiterLimit, defaultLineWidth, defaultLineJoin, defaultLineDashOffset,
|
|
||||||
defaultLineDash, defaultLineCap} from '../canvas.js';
|
|
||||||
import CanvasInstruction from './Instruction.js';
|
import CanvasInstruction from './Instruction.js';
|
||||||
import {TEXT_ALIGN} from '../replay.js';
|
import {TEXT_ALIGN} from '../replay.js';
|
||||||
import {
|
import {
|
||||||
@@ -28,6 +21,20 @@ import {
|
|||||||
} from '../../transform.js';
|
} from '../../transform.js';
|
||||||
|
|
||||||
|
|
||||||
|
import {createCanvasContext2D} from '../../dom.js';
|
||||||
|
import {labelCache, defaultTextAlign, measureTextHeight, measureTextWidth, measureTextWidths} from '../canvas.js';
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} SerializableInstructions
|
||||||
|
* @property {Array<*>} instructions The rendering instructions.
|
||||||
|
* @property {Array<*>} hitDetectionInstructions The rendering hit detection instructions.
|
||||||
|
* @property {Array<number>} coordinates The array of all coordinates.
|
||||||
|
* @property {!Object<string, import("../canvas.js").TextState>} textStates The text states (decluttering).
|
||||||
|
* @property {!Object<string, import("../canvas.js").FillState>} fillStates The fill states (decluttering).
|
||||||
|
* @property {!Object<string, import("../canvas.js").StrokeState>} strokeStates The stroke states (decluttering).
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {import("../../extent.js").Extent}
|
* @type {import("../../extent.js").Extent}
|
||||||
*/
|
*/
|
||||||
@@ -40,7 +47,7 @@ const tmpExtent = createEmpty();
|
|||||||
const tmpTransform = createTransform();
|
const tmpTransform = createTransform();
|
||||||
|
|
||||||
|
|
||||||
class CanvasReplay extends VectorContext {
|
class CanvasExecutor {
|
||||||
/**
|
/**
|
||||||
* @param {number} tolerance Tolerance.
|
* @param {number} tolerance Tolerance.
|
||||||
* @param {import("../../extent.js").Extent} maxExtent Maximum extent.
|
* @param {import("../../extent.js").Extent} maxExtent Maximum extent.
|
||||||
@@ -48,10 +55,9 @@ class CanvasReplay extends VectorContext {
|
|||||||
* @param {number} pixelRatio Pixel ratio.
|
* @param {number} pixelRatio Pixel ratio.
|
||||||
* @param {boolean} overlaps The replay can have overlapping geometries.
|
* @param {boolean} overlaps The replay can have overlapping geometries.
|
||||||
* @param {?} declutterTree Declutter tree.
|
* @param {?} declutterTree Declutter tree.
|
||||||
|
* @param {SerializableInstructions} instructions The serializable instructions
|
||||||
*/
|
*/
|
||||||
constructor(tolerance, maxExtent, resolution, pixelRatio, overlaps, declutterTree) {
|
constructor(tolerance, maxExtent, resolution, pixelRatio, overlaps, declutterTree, instructions) {
|
||||||
super();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {?}
|
* @type {?}
|
||||||
*/
|
*/
|
||||||
@@ -123,13 +129,13 @@ class CanvasReplay extends VectorContext {
|
|||||||
* @protected
|
* @protected
|
||||||
* @type {Array<*>}
|
* @type {Array<*>}
|
||||||
*/
|
*/
|
||||||
this.instructions = [];
|
this.instructions = instructions.instructions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @protected
|
* @protected
|
||||||
* @type {Array<number>}
|
* @type {Array<number>}
|
||||||
*/
|
*/
|
||||||
this.coordinates = [];
|
this.coordinates = instructions.coordinates;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
@@ -147,7 +153,7 @@ class CanvasReplay extends VectorContext {
|
|||||||
* @protected
|
* @protected
|
||||||
* @type {Array<*>}
|
* @type {Array<*>}
|
||||||
*/
|
*/
|
||||||
this.hitDetectionInstructions = [];
|
this.hitDetectionInstructions = instructions.hitDetectionInstructions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
@@ -167,6 +173,98 @@ class CanvasReplay extends VectorContext {
|
|||||||
*/
|
*/
|
||||||
this.viewRotation_ = 0;
|
this.viewRotation_ = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {!Object<string, import("../canvas.js").FillState>}
|
||||||
|
*/
|
||||||
|
this.fillStates = instructions.fillStates || {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {!Object<string, import("../canvas.js").StrokeState>}
|
||||||
|
*/
|
||||||
|
this.strokeStates = instructions.strokeStates || {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {!Object<string, import("../canvas.js").TextState>}
|
||||||
|
*/
|
||||||
|
this.textStates = instructions.textStates || {};
|
||||||
|
|
||||||
|
// Adaptations
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {Object<string, Object<string, number>>}
|
||||||
|
*/
|
||||||
|
this.widths_ = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} text Text.
|
||||||
|
* @param {string} textKey Text style key.
|
||||||
|
* @param {string} fillKey Fill style key.
|
||||||
|
* @param {string} strokeKey Stroke style key.
|
||||||
|
* @return {HTMLCanvasElement} Image.
|
||||||
|
*/
|
||||||
|
getTextImage(text, textKey, fillKey, strokeKey) {
|
||||||
|
let label;
|
||||||
|
const key = strokeKey + textKey + text + fillKey + this.pixelRatio;
|
||||||
|
|
||||||
|
if (!labelCache.containsKey(key)) {
|
||||||
|
const strokeState = strokeKey ? this.strokeStates[strokeKey] : null;
|
||||||
|
const fillState = fillKey ? this.fillStates[fillKey] : null;
|
||||||
|
const textState = this.textStates[textKey];
|
||||||
|
const pixelRatio = this.pixelRatio;
|
||||||
|
const scale = textState.scale * pixelRatio;
|
||||||
|
const align = TEXT_ALIGN[textState.textAlign || defaultTextAlign];
|
||||||
|
const strokeWidth = strokeKey && strokeState.lineWidth ? strokeState.lineWidth : 0;
|
||||||
|
|
||||||
|
const lines = text.split('\n');
|
||||||
|
const numLines = lines.length;
|
||||||
|
const widths = [];
|
||||||
|
const width = measureTextWidths(textState.font, lines, widths);
|
||||||
|
const lineHeight = measureTextHeight(textState.font);
|
||||||
|
const height = lineHeight * numLines;
|
||||||
|
const renderWidth = (width + strokeWidth);
|
||||||
|
const context = createCanvasContext2D(
|
||||||
|
Math.ceil(renderWidth * scale),
|
||||||
|
Math.ceil((height + strokeWidth) * scale));
|
||||||
|
label = context.canvas;
|
||||||
|
labelCache.set(key, label);
|
||||||
|
if (scale != 1) {
|
||||||
|
context.scale(scale, scale);
|
||||||
|
}
|
||||||
|
context.font = textState.font;
|
||||||
|
if (strokeKey) {
|
||||||
|
context.strokeStyle = strokeState.strokeStyle;
|
||||||
|
context.lineWidth = strokeWidth;
|
||||||
|
context.lineCap = /** @type {CanvasLineCap} */ (strokeState.lineCap);
|
||||||
|
context.lineJoin = /** @type {CanvasLineJoin} */ (strokeState.lineJoin);
|
||||||
|
context.miterLimit = strokeState.miterLimit;
|
||||||
|
if (CANVAS_LINE_DASH && strokeState.lineDash.length) {
|
||||||
|
context.setLineDash(strokeState.lineDash);
|
||||||
|
context.lineDashOffset = strokeState.lineDashOffset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fillKey) {
|
||||||
|
context.fillStyle = fillState.fillStyle;
|
||||||
|
}
|
||||||
|
context.textBaseline = 'middle';
|
||||||
|
context.textAlign = 'center';
|
||||||
|
const leftRight = (0.5 - align);
|
||||||
|
const x = align * label.width / scale + leftRight * strokeWidth;
|
||||||
|
let i;
|
||||||
|
if (strokeKey) {
|
||||||
|
for (i = 0; i < numLines; ++i) {
|
||||||
|
context.strokeText(lines[i], x + leftRight * widths[i], 0.5 * (strokeWidth + lineHeight) + i * lineHeight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fillKey) {
|
||||||
|
for (i = 0; i < numLines; ++i) {
|
||||||
|
context.fillText(lines[i], x + leftRight * widths[i], 0.5 * (strokeWidth + lineHeight) + i * lineHeight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return labelCache.get(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -322,144 +420,6 @@ class CanvasReplay extends VectorContext {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Array<number>} flatCoordinates Flat coordinates.
|
|
||||||
* @param {number} offset Offset.
|
|
||||||
* @param {number} end End.
|
|
||||||
* @param {number} stride Stride.
|
|
||||||
* @param {boolean} closed Last input coordinate equals first.
|
|
||||||
* @param {boolean} skipFirst Skip first coordinate.
|
|
||||||
* @protected
|
|
||||||
* @return {number} My end.
|
|
||||||
*/
|
|
||||||
appendFlatCoordinates(flatCoordinates, offset, end, stride, closed, skipFirst) {
|
|
||||||
|
|
||||||
let myEnd = this.coordinates.length;
|
|
||||||
const extent = this.getBufferedMaxExtent();
|
|
||||||
if (skipFirst) {
|
|
||||||
offset += stride;
|
|
||||||
}
|
|
||||||
const lastCoord = [flatCoordinates[offset], flatCoordinates[offset + 1]];
|
|
||||||
const nextCoord = [NaN, NaN];
|
|
||||||
let skipped = true;
|
|
||||||
|
|
||||||
let i, lastRel, nextRel;
|
|
||||||
for (i = offset + stride; i < end; i += stride) {
|
|
||||||
nextCoord[0] = flatCoordinates[i];
|
|
||||||
nextCoord[1] = flatCoordinates[i + 1];
|
|
||||||
nextRel = coordinateRelationship(extent, nextCoord);
|
|
||||||
if (nextRel !== lastRel) {
|
|
||||||
if (skipped) {
|
|
||||||
this.coordinates[myEnd++] = lastCoord[0];
|
|
||||||
this.coordinates[myEnd++] = lastCoord[1];
|
|
||||||
}
|
|
||||||
this.coordinates[myEnd++] = nextCoord[0];
|
|
||||||
this.coordinates[myEnd++] = nextCoord[1];
|
|
||||||
skipped = false;
|
|
||||||
} else if (nextRel === Relationship.INTERSECTING) {
|
|
||||||
this.coordinates[myEnd++] = nextCoord[0];
|
|
||||||
this.coordinates[myEnd++] = nextCoord[1];
|
|
||||||
skipped = false;
|
|
||||||
} else {
|
|
||||||
skipped = true;
|
|
||||||
}
|
|
||||||
lastCoord[0] = nextCoord[0];
|
|
||||||
lastCoord[1] = nextCoord[1];
|
|
||||||
lastRel = nextRel;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Last coordinate equals first or only one point to append:
|
|
||||||
if ((closed && skipped) || i === offset + stride) {
|
|
||||||
this.coordinates[myEnd++] = lastCoord[0];
|
|
||||||
this.coordinates[myEnd++] = lastCoord[1];
|
|
||||||
}
|
|
||||||
return myEnd;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Array<number>} flatCoordinates Flat coordinates.
|
|
||||||
* @param {number} offset Offset.
|
|
||||||
* @param {Array<number>} ends Ends.
|
|
||||||
* @param {number} stride Stride.
|
|
||||||
* @param {Array<number>} replayEnds Replay ends.
|
|
||||||
* @return {number} Offset.
|
|
||||||
*/
|
|
||||||
drawCustomCoordinates_(flatCoordinates, offset, ends, stride, replayEnds) {
|
|
||||||
for (let i = 0, ii = ends.length; i < ii; ++i) {
|
|
||||||
const end = ends[i];
|
|
||||||
const replayEnd = this.appendFlatCoordinates(flatCoordinates, offset, end, stride, false, false);
|
|
||||||
replayEnds.push(replayEnd);
|
|
||||||
offset = end;
|
|
||||||
}
|
|
||||||
return offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritDoc.
|
|
||||||
*/
|
|
||||||
drawCustom(geometry, feature, renderer) {
|
|
||||||
this.beginGeometry(geometry, feature);
|
|
||||||
const type = geometry.getType();
|
|
||||||
const stride = geometry.getStride();
|
|
||||||
const replayBegin = this.coordinates.length;
|
|
||||||
let flatCoordinates, replayEnd, replayEnds, replayEndss;
|
|
||||||
let offset;
|
|
||||||
if (type == GeometryType.MULTI_POLYGON) {
|
|
||||||
geometry = /** @type {import("../../geom/MultiPolygon.js").default} */ (geometry);
|
|
||||||
flatCoordinates = geometry.getOrientedFlatCoordinates();
|
|
||||||
replayEndss = [];
|
|
||||||
const endss = geometry.getEndss();
|
|
||||||
offset = 0;
|
|
||||||
for (let i = 0, ii = endss.length; i < ii; ++i) {
|
|
||||||
const myEnds = [];
|
|
||||||
offset = this.drawCustomCoordinates_(flatCoordinates, offset, endss[i], stride, myEnds);
|
|
||||||
replayEndss.push(myEnds);
|
|
||||||
}
|
|
||||||
this.instructions.push([CanvasInstruction.CUSTOM,
|
|
||||||
replayBegin, replayEndss, geometry, renderer, inflateMultiCoordinatesArray]);
|
|
||||||
} else if (type == GeometryType.POLYGON || type == GeometryType.MULTI_LINE_STRING) {
|
|
||||||
replayEnds = [];
|
|
||||||
flatCoordinates = (type == GeometryType.POLYGON) ?
|
|
||||||
/** @type {import("../../geom/Polygon.js").default} */ (geometry).getOrientedFlatCoordinates() :
|
|
||||||
geometry.getFlatCoordinates();
|
|
||||||
offset = this.drawCustomCoordinates_(flatCoordinates, 0,
|
|
||||||
/** @type {import("../../geom/Polygon.js").default|import("../../geom/MultiLineString.js").default} */ (geometry).getEnds(),
|
|
||||||
stride, replayEnds);
|
|
||||||
this.instructions.push([CanvasInstruction.CUSTOM,
|
|
||||||
replayBegin, replayEnds, geometry, renderer, inflateCoordinatesArray]);
|
|
||||||
} else if (type == GeometryType.LINE_STRING || type == GeometryType.MULTI_POINT) {
|
|
||||||
flatCoordinates = geometry.getFlatCoordinates();
|
|
||||||
replayEnd = this.appendFlatCoordinates(
|
|
||||||
flatCoordinates, 0, flatCoordinates.length, stride, false, false);
|
|
||||||
this.instructions.push([CanvasInstruction.CUSTOM,
|
|
||||||
replayBegin, replayEnd, geometry, renderer, inflateCoordinates]);
|
|
||||||
} else if (type == GeometryType.POINT) {
|
|
||||||
flatCoordinates = geometry.getFlatCoordinates();
|
|
||||||
this.coordinates.push(flatCoordinates[0], flatCoordinates[1]);
|
|
||||||
replayEnd = this.coordinates.length;
|
|
||||||
this.instructions.push([CanvasInstruction.CUSTOM,
|
|
||||||
replayBegin, replayEnd, geometry, renderer]);
|
|
||||||
}
|
|
||||||
this.endGeometry(geometry, feature);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @protected
|
|
||||||
* @param {import("../../geom/Geometry.js").default|import("../Feature.js").default} geometry Geometry.
|
|
||||||
* @param {import("../../Feature.js").default|import("../Feature.js").default} feature Feature.
|
|
||||||
*/
|
|
||||||
beginGeometry(geometry, feature) {
|
|
||||||
this.beginGeometryInstruction1_ = [CanvasInstruction.BEGIN_GEOMETRY, feature, 0];
|
|
||||||
this.instructions.push(this.beginGeometryInstruction1_);
|
|
||||||
this.beginGeometryInstruction2_ = [CanvasInstruction.BEGIN_GEOMETRY, feature, 0];
|
|
||||||
this.hitDetectionInstructions.push(this.beginGeometryInstruction2_);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* FIXME empty description for jsdoc
|
|
||||||
*/
|
|
||||||
finish() {}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
* @param {CanvasRenderingContext2D} context Context.
|
* @param {CanvasRenderingContext2D} context Context.
|
||||||
@@ -530,6 +490,35 @@ class CanvasReplay extends VectorContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @param {string} text The text to draw.
|
||||||
|
* @param {string} textKey The key of the text state.
|
||||||
|
* @param {string} strokeKey The key for the stroke state.
|
||||||
|
* @param {string} fillKey The key for the fill state.
|
||||||
|
* @return {{label: HTMLCanvasElement, anchorX: number, anchorY: number}} The text image and its anchor.
|
||||||
|
*/
|
||||||
|
drawTextImageWithPointPlacement_(text, textKey, strokeKey, fillKey) {
|
||||||
|
const textState = this.textStates[textKey];
|
||||||
|
|
||||||
|
const label = this.getTextImage(text, textKey, fillKey, strokeKey);
|
||||||
|
|
||||||
|
const strokeState = this.strokeStates[strokeKey];
|
||||||
|
const pixelRatio = this.pixelRatio;
|
||||||
|
const align = TEXT_ALIGN[textState.textAlign || defaultTextAlign];
|
||||||
|
const baseline = TEXT_ALIGN[textState.textBaseline || defaultTextBaseline];
|
||||||
|
const strokeWidth = strokeState && strokeState.lineWidth ? strokeState.lineWidth : 0;
|
||||||
|
|
||||||
|
const anchorX = align * label.width / pixelRatio + 2 * (0.5 - align) * strokeWidth;
|
||||||
|
const anchorY = baseline * label.height / pixelRatio + 2 * (0.5 - baseline) * strokeWidth;
|
||||||
|
|
||||||
|
return {
|
||||||
|
label: label,
|
||||||
|
anchorX: anchorX,
|
||||||
|
anchorY: anchorY
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
* @param {CanvasRenderingContext2D} context Context.
|
* @param {CanvasRenderingContext2D} context Context.
|
||||||
@@ -544,7 +533,7 @@ class CanvasReplay extends VectorContext {
|
|||||||
* @return {T|undefined} Callback result.
|
* @return {T|undefined} Callback result.
|
||||||
* @template T
|
* @template T
|
||||||
*/
|
*/
|
||||||
replay_(
|
execute_(
|
||||||
context,
|
context,
|
||||||
transform,
|
transform,
|
||||||
skippedFeaturesHash,
|
skippedFeaturesHash,
|
||||||
@@ -571,7 +560,8 @@ class CanvasReplay extends VectorContext {
|
|||||||
const ii = instructions.length; // end of instructions
|
const ii = instructions.length; // end of instructions
|
||||||
let d = 0; // data index
|
let d = 0; // data index
|
||||||
let dd; // end of per-instruction data
|
let dd; // end of per-instruction data
|
||||||
let anchorX, anchorY, prevX, prevY, roundX, roundY, declutterGroup, image;
|
let anchorX, anchorY, prevX, prevY, roundX, roundY, declutterGroup, image, text, textKey;
|
||||||
|
let strokeKey, fillKey;
|
||||||
let pendingFill = 0;
|
let pendingFill = 0;
|
||||||
let pendingStroke = 0;
|
let pendingStroke = 0;
|
||||||
let lastFillInstruction = null;
|
let lastFillInstruction = null;
|
||||||
@@ -663,20 +653,42 @@ class CanvasReplay extends VectorContext {
|
|||||||
case CanvasInstruction.DRAW_IMAGE:
|
case CanvasInstruction.DRAW_IMAGE:
|
||||||
d = /** @type {number} */ (instruction[1]);
|
d = /** @type {number} */ (instruction[1]);
|
||||||
dd = /** @type {number} */ (instruction[2]);
|
dd = /** @type {number} */ (instruction[2]);
|
||||||
image = /** @type {HTMLCanvasElement|HTMLVideoElement|HTMLImageElement} */
|
image = /** @type {HTMLCanvasElement|HTMLVideoElement|HTMLImageElement} */ (instruction[3]);
|
||||||
(instruction[3]);
|
|
||||||
// Remaining arguments in DRAW_IMAGE are in alphabetical order
|
// Remaining arguments in DRAW_IMAGE are in alphabetical order
|
||||||
anchorX = /** @type {number} */ (instruction[4]);
|
anchorX = /** @type {number} */ (instruction[4]);
|
||||||
anchorY = /** @type {number} */ (instruction[5]);
|
anchorY = /** @type {number} */ (instruction[5]);
|
||||||
declutterGroup = featureCallback ? null : /** @type {import("../canvas.js").DeclutterGroup} */ (instruction[6]);
|
declutterGroup = featureCallback ? null : /** @type {import("../canvas.js").DeclutterGroup} */ (instruction[6]);
|
||||||
const height = /** @type {number} */ (instruction[7]);
|
let height = /** @type {number} */ (instruction[7]);
|
||||||
const opacity = /** @type {number} */ (instruction[8]);
|
const opacity = /** @type {number} */ (instruction[8]);
|
||||||
const originX = /** @type {number} */ (instruction[9]);
|
const originX = /** @type {number} */ (instruction[9]);
|
||||||
const originY = /** @type {number} */ (instruction[10]);
|
const originY = /** @type {number} */ (instruction[10]);
|
||||||
const rotateWithView = /** @type {boolean} */ (instruction[11]);
|
const rotateWithView = /** @type {boolean} */ (instruction[11]);
|
||||||
let rotation = /** @type {number} */ (instruction[12]);
|
let rotation = /** @type {number} */ (instruction[12]);
|
||||||
const scale = /** @type {number} */ (instruction[13]);
|
const scale = /** @type {number} */ (instruction[13]);
|
||||||
const width = /** @type {number} */ (instruction[14]);
|
let width = /** @type {number} */ (instruction[14]);
|
||||||
|
|
||||||
|
|
||||||
|
if (!image && instruction.length >= 19) {
|
||||||
|
// create label images
|
||||||
|
text = /** @type {string} */ (instruction[18]);
|
||||||
|
textKey = /** @type {string} */ (instruction[19]);
|
||||||
|
strokeKey = /** @type {string} */ (instruction[20]);
|
||||||
|
fillKey = /** @type {string} */ (instruction[21]);
|
||||||
|
const labelWithAnchor = this.drawTextImageWithPointPlacement_(text, textKey, strokeKey, fillKey);
|
||||||
|
image = instruction[3] = labelWithAnchor.label;
|
||||||
|
const textOffsetX = /** @type {number} */ (instruction[22]);
|
||||||
|
anchorX = instruction[4] = (labelWithAnchor.anchorX - textOffsetX) * this.pixelRatio;
|
||||||
|
const textOffsetY = /** @type {number} */ (instruction[23]);
|
||||||
|
anchorY = instruction[5] = (labelWithAnchor.anchorY - textOffsetY) * this.pixelRatio;
|
||||||
|
height = instruction[7] = image.height;
|
||||||
|
width = instruction[14] = image.width;
|
||||||
|
}
|
||||||
|
|
||||||
|
let geometryWidths;
|
||||||
|
if (instruction.length > 24) {
|
||||||
|
geometryWidths = /** @type {number} */ (instruction[24]);
|
||||||
|
}
|
||||||
|
|
||||||
let padding, backgroundFill, backgroundStroke;
|
let padding, backgroundFill, backgroundStroke;
|
||||||
if (instruction.length > 16) {
|
if (instruction.length > 16) {
|
||||||
@@ -691,7 +703,11 @@ class CanvasReplay extends VectorContext {
|
|||||||
if (rotateWithView) {
|
if (rotateWithView) {
|
||||||
rotation += viewRotation;
|
rotation += viewRotation;
|
||||||
}
|
}
|
||||||
|
let widthIndex = 0;
|
||||||
for (; d < dd; d += 2) {
|
for (; d < dd; d += 2) {
|
||||||
|
if (geometryWidths && geometryWidths[widthIndex++] < width / this.pixelRatio) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
this.replayImage_(context,
|
this.replayImage_(context,
|
||||||
pixelCoordinates[d], pixelCoordinates[d + 1], image, anchorX, anchorY,
|
pixelCoordinates[d], pixelCoordinates[d + 1], image, anchorX, anchorY,
|
||||||
declutterGroup, height, opacity, originX, originY, rotation, scale,
|
declutterGroup, height, opacity, originX, originY, rotation, scale,
|
||||||
@@ -708,22 +724,38 @@ class CanvasReplay extends VectorContext {
|
|||||||
const baseline = /** @type {number} */ (instruction[3]);
|
const baseline = /** @type {number} */ (instruction[3]);
|
||||||
declutterGroup = featureCallback ? null : /** @type {import("../canvas.js").DeclutterGroup} */ (instruction[4]);
|
declutterGroup = featureCallback ? null : /** @type {import("../canvas.js").DeclutterGroup} */ (instruction[4]);
|
||||||
const overflow = /** @type {number} */ (instruction[5]);
|
const overflow = /** @type {number} */ (instruction[5]);
|
||||||
const fillKey = /** @type {string} */ (instruction[6]);
|
fillKey = /** @type {string} */ (instruction[6]);
|
||||||
const maxAngle = /** @type {number} */ (instruction[7]);
|
const maxAngle = /** @type {number} */ (instruction[7]);
|
||||||
const measure = /** @type {function(string):number} */ (instruction[8]);
|
const measurePixelRatio = /** @type {number} */ (instruction[8]);
|
||||||
const offsetY = /** @type {number} */ (instruction[9]);
|
const offsetY = /** @type {number} */ (instruction[9]);
|
||||||
const strokeKey = /** @type {string} */ (instruction[10]);
|
strokeKey = /** @type {string} */ (instruction[10]);
|
||||||
const strokeWidth = /** @type {number} */ (instruction[11]);
|
const strokeWidth = /** @type {number} */ (instruction[11]);
|
||||||
const text = /** @type {string} */ (instruction[12]);
|
text = /** @type {string} */ (instruction[12]);
|
||||||
const textKey = /** @type {string} */ (instruction[13]);
|
textKey = /** @type {string} */ (instruction[13]);
|
||||||
const textScale = /** @type {number} */ (instruction[14]);
|
const pixelRatioScale = /** @type {number} */ (instruction[14]);
|
||||||
|
|
||||||
|
const textState = this.textStates[textKey];
|
||||||
|
const font = textState.font;
|
||||||
|
const textScale = textState.scale;
|
||||||
|
|
||||||
|
let widths = this.widths_[font];
|
||||||
|
if (!widths) {
|
||||||
|
this.widths_[font] = widths = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
//FIXME Do not create this function on every call
|
||||||
|
const measure = function(text) {
|
||||||
|
let width = widths[text];
|
||||||
|
if (!width) {
|
||||||
|
width = widths[text] = measureTextWidth(font, text);
|
||||||
|
}
|
||||||
|
return width * textScale * measurePixelRatio;
|
||||||
|
};
|
||||||
|
|
||||||
const pathLength = lineStringLength(pixelCoordinates, begin, end, 2);
|
const pathLength = lineStringLength(pixelCoordinates, begin, end, 2);
|
||||||
const textLength = measure(text);
|
const textLength = measure(text);
|
||||||
if (overflow || textLength <= pathLength) {
|
if (overflow || textLength <= pathLength) {
|
||||||
/** @type {import("./TextReplay.js").default} */
|
const textAlign = this.textStates[textKey].textAlign;
|
||||||
const textReplay = /** @type {?} */ (this);
|
|
||||||
const textAlign = textReplay.textStates[textKey].textAlign;
|
|
||||||
const startM = (pathLength - textLength) * TEXT_ALIGN[textAlign];
|
const startM = (pathLength - textLength) * TEXT_ALIGN[textAlign];
|
||||||
const parts = drawTextOnPath(
|
const parts = drawTextOnPath(
|
||||||
pixelCoordinates, begin, end, 2, text, measure, startM, maxAngle);
|
pixelCoordinates, begin, end, 2, text, measure, startM, maxAngle);
|
||||||
@@ -733,13 +765,13 @@ class CanvasReplay extends VectorContext {
|
|||||||
for (c = 0, cc = parts.length; c < cc; ++c) {
|
for (c = 0, cc = parts.length; c < cc; ++c) {
|
||||||
part = parts[c]; // x, y, anchorX, rotation, chunk
|
part = parts[c]; // x, y, anchorX, rotation, chunk
|
||||||
chars = /** @type {string} */ (part[4]);
|
chars = /** @type {string} */ (part[4]);
|
||||||
label = textReplay.getImage(chars, textKey, '', strokeKey);
|
label = this.getTextImage(chars, textKey, '', strokeKey);
|
||||||
anchorX = /** @type {number} */ (part[2]) + strokeWidth;
|
anchorX = /** @type {number} */ (part[2]) + strokeWidth;
|
||||||
anchorY = baseline * label.height + (0.5 - baseline) * 2 * strokeWidth - offsetY;
|
anchorY = baseline * label.height + (0.5 - baseline) * 2 * strokeWidth - offsetY;
|
||||||
this.replayImage_(context,
|
this.replayImage_(context,
|
||||||
/** @type {number} */ (part[0]), /** @type {number} */ (part[1]), label,
|
/** @type {number} */ (part[0]), /** @type {number} */ (part[1]), label,
|
||||||
anchorX, anchorY, declutterGroup, label.height, 1, 0, 0,
|
anchorX, anchorY, declutterGroup, label.height, 1, 0, 0,
|
||||||
/** @type {number} */ (part[3]), textScale, false, label.width,
|
/** @type {number} */ (part[3]), pixelRatioScale, false, label.width,
|
||||||
defaultPadding, null, null);
|
defaultPadding, null, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -747,13 +779,13 @@ class CanvasReplay extends VectorContext {
|
|||||||
for (c = 0, cc = parts.length; c < cc; ++c) {
|
for (c = 0, cc = parts.length; c < cc; ++c) {
|
||||||
part = parts[c]; // x, y, anchorX, rotation, chunk
|
part = parts[c]; // x, y, anchorX, rotation, chunk
|
||||||
chars = /** @type {string} */ (part[4]);
|
chars = /** @type {string} */ (part[4]);
|
||||||
label = textReplay.getImage(chars, textKey, fillKey, '');
|
label = this.getTextImage(chars, textKey, fillKey, '');
|
||||||
anchorX = /** @type {number} */ (part[2]);
|
anchorX = /** @type {number} */ (part[2]);
|
||||||
anchorY = baseline * label.height - offsetY;
|
anchorY = baseline * label.height - offsetY;
|
||||||
this.replayImage_(context,
|
this.replayImage_(context,
|
||||||
/** @type {number} */ (part[0]), /** @type {number} */ (part[1]), label,
|
/** @type {number} */ (part[0]), /** @type {number} */ (part[1]), label,
|
||||||
anchorX, anchorY, declutterGroup, label.height, 1, 0, 0,
|
anchorX, anchorY, declutterGroup, label.height, 1, 0, 0,
|
||||||
/** @type {number} */ (part[3]), textScale, false, label.width,
|
/** @type {number} */ (part[3]), pixelRatioScale, false, label.width,
|
||||||
defaultPadding, null, null);
|
defaultPadding, null, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -860,9 +892,9 @@ class CanvasReplay extends VectorContext {
|
|||||||
* to skip.
|
* to skip.
|
||||||
* @param {boolean} snapToPixel Snap point symbols and text to integer pixels.
|
* @param {boolean} snapToPixel Snap point symbols and text to integer pixels.
|
||||||
*/
|
*/
|
||||||
replay(context, transform, viewRotation, skippedFeaturesHash, snapToPixel) {
|
execute(context, transform, viewRotation, skippedFeaturesHash, snapToPixel) {
|
||||||
this.viewRotation_ = viewRotation;
|
this.viewRotation_ = viewRotation;
|
||||||
this.replay_(context, transform,
|
this.execute_(context, transform,
|
||||||
skippedFeaturesHash, this.instructions, snapToPixel, undefined, undefined);
|
skippedFeaturesHash, this.instructions, snapToPixel, undefined, undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -879,7 +911,7 @@ class CanvasReplay extends VectorContext {
|
|||||||
* @return {T|undefined} Callback result.
|
* @return {T|undefined} Callback result.
|
||||||
* @template T
|
* @template T
|
||||||
*/
|
*/
|
||||||
replayHitDetection(
|
executeHitDetection(
|
||||||
context,
|
context,
|
||||||
transform,
|
transform,
|
||||||
viewRotation,
|
viewRotation,
|
||||||
@@ -888,7 +920,7 @@ class CanvasReplay extends VectorContext {
|
|||||||
opt_hitExtent
|
opt_hitExtent
|
||||||
) {
|
) {
|
||||||
this.viewRotation_ = viewRotation;
|
this.viewRotation_ = viewRotation;
|
||||||
return this.replay_(context, transform, skippedFeaturesHash,
|
return this.execute_(context, transform, skippedFeaturesHash,
|
||||||
this.hitDetectionInstructions, true, opt_featureCallback, opt_hitExtent);
|
this.hitDetectionInstructions, true, opt_featureCallback, opt_hitExtent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -918,56 +950,6 @@ class CanvasReplay extends VectorContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
setFillStrokeStyle(fillStyle, strokeStyle) {
|
|
||||||
const state = this.state;
|
|
||||||
if (fillStyle) {
|
|
||||||
const fillStyleColor = fillStyle.getColor();
|
|
||||||
state.fillStyle = asColorLike(fillStyleColor ?
|
|
||||||
fillStyleColor : defaultFillStyle);
|
|
||||||
} else {
|
|
||||||
state.fillStyle = undefined;
|
|
||||||
}
|
|
||||||
if (strokeStyle) {
|
|
||||||
const strokeStyleColor = strokeStyle.getColor();
|
|
||||||
state.strokeStyle = asColorLike(strokeStyleColor ?
|
|
||||||
strokeStyleColor : defaultStrokeStyle);
|
|
||||||
const strokeStyleLineCap = strokeStyle.getLineCap();
|
|
||||||
state.lineCap = strokeStyleLineCap !== undefined ?
|
|
||||||
strokeStyleLineCap : defaultLineCap;
|
|
||||||
const strokeStyleLineDash = strokeStyle.getLineDash();
|
|
||||||
state.lineDash = strokeStyleLineDash ?
|
|
||||||
strokeStyleLineDash.slice() : defaultLineDash;
|
|
||||||
const strokeStyleLineDashOffset = strokeStyle.getLineDashOffset();
|
|
||||||
state.lineDashOffset = strokeStyleLineDashOffset ?
|
|
||||||
strokeStyleLineDashOffset : defaultLineDashOffset;
|
|
||||||
const strokeStyleLineJoin = strokeStyle.getLineJoin();
|
|
||||||
state.lineJoin = strokeStyleLineJoin !== undefined ?
|
|
||||||
strokeStyleLineJoin : defaultLineJoin;
|
|
||||||
const strokeStyleWidth = strokeStyle.getWidth();
|
|
||||||
state.lineWidth = strokeStyleWidth !== undefined ?
|
|
||||||
strokeStyleWidth : defaultLineWidth;
|
|
||||||
const strokeStyleMiterLimit = strokeStyle.getMiterLimit();
|
|
||||||
state.miterLimit = strokeStyleMiterLimit !== undefined ?
|
|
||||||
strokeStyleMiterLimit : defaultMiterLimit;
|
|
||||||
|
|
||||||
if (state.lineWidth > this.maxLineWidth) {
|
|
||||||
this.maxLineWidth = state.lineWidth;
|
|
||||||
// invalidate the buffered max extent cache
|
|
||||||
this.bufferedMaxExtent_ = null;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
state.strokeStyle = undefined;
|
|
||||||
state.lineCap = undefined;
|
|
||||||
state.lineDash = null;
|
|
||||||
state.lineDashOffset = undefined;
|
|
||||||
state.lineJoin = undefined;
|
|
||||||
state.lineWidth = undefined;
|
|
||||||
state.miterLimit = undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {import("../canvas.js").FillStrokeState} state State.
|
* @param {import("../canvas.js").FillStrokeState} state State.
|
||||||
@@ -985,12 +967,6 @@ class CanvasReplay extends VectorContext {
|
|||||||
return fillInstruction;
|
return fillInstruction;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import("../canvas.js").FillStrokeState} state State.
|
|
||||||
*/
|
|
||||||
applyStroke(state) {
|
|
||||||
this.instructions.push(this.createStroke(state));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {import("../canvas.js").FillStrokeState} state State.
|
* @param {import("../canvas.js").FillStrokeState} state State.
|
||||||
@@ -1005,66 +981,6 @@ class CanvasReplay extends VectorContext {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import("../canvas.js").FillStrokeState} state State.
|
|
||||||
* @param {function(this:CanvasReplay, import("../canvas.js").FillStrokeState, (import("../../geom/Geometry.js").default|import("../Feature.js").default)):Array<*>} createFill Create fill.
|
|
||||||
* @param {import("../../geom/Geometry.js").default|import("../Feature.js").default} geometry Geometry.
|
|
||||||
*/
|
|
||||||
updateFillStyle(state, createFill, geometry) {
|
|
||||||
const fillStyle = state.fillStyle;
|
|
||||||
if (typeof fillStyle !== 'string' || state.currentFillStyle != fillStyle) {
|
|
||||||
if (fillStyle !== undefined) {
|
|
||||||
this.instructions.push(createFill.call(this, state, geometry));
|
|
||||||
}
|
|
||||||
state.currentFillStyle = fillStyle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import("../canvas.js").FillStrokeState} state State.
|
|
||||||
* @param {function(this:CanvasReplay, import("../canvas.js").FillStrokeState)} applyStroke Apply stroke.
|
|
||||||
*/
|
|
||||||
updateStrokeStyle(state, applyStroke) {
|
|
||||||
const strokeStyle = state.strokeStyle;
|
|
||||||
const lineCap = state.lineCap;
|
|
||||||
const lineDash = state.lineDash;
|
|
||||||
const lineDashOffset = state.lineDashOffset;
|
|
||||||
const lineJoin = state.lineJoin;
|
|
||||||
const lineWidth = state.lineWidth;
|
|
||||||
const miterLimit = state.miterLimit;
|
|
||||||
if (state.currentStrokeStyle != strokeStyle ||
|
|
||||||
state.currentLineCap != lineCap ||
|
|
||||||
(lineDash != state.currentLineDash && !equals(state.currentLineDash, lineDash)) ||
|
|
||||||
state.currentLineDashOffset != lineDashOffset ||
|
|
||||||
state.currentLineJoin != lineJoin ||
|
|
||||||
state.currentLineWidth != lineWidth ||
|
|
||||||
state.currentMiterLimit != miterLimit) {
|
|
||||||
if (strokeStyle !== undefined) {
|
|
||||||
applyStroke.call(this, state);
|
|
||||||
}
|
|
||||||
state.currentStrokeStyle = strokeStyle;
|
|
||||||
state.currentLineCap = lineCap;
|
|
||||||
state.currentLineDash = lineDash;
|
|
||||||
state.currentLineDashOffset = lineDashOffset;
|
|
||||||
state.currentLineJoin = lineJoin;
|
|
||||||
state.currentLineWidth = lineWidth;
|
|
||||||
state.currentMiterLimit = miterLimit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import("../../geom/Geometry.js").default|import("../Feature.js").default} geometry Geometry.
|
|
||||||
* @param {import("../../Feature.js").default|import("../Feature.js").default} feature Feature.
|
|
||||||
*/
|
|
||||||
endGeometry(geometry, feature) {
|
|
||||||
this.beginGeometryInstruction1_[2] = this.instructions.length;
|
|
||||||
this.beginGeometryInstruction1_ = null;
|
|
||||||
this.beginGeometryInstruction2_[2] = this.hitDetectionInstructions.length;
|
|
||||||
this.beginGeometryInstruction2_ = null;
|
|
||||||
const endGeometryInstruction = [CanvasInstruction.END_GEOMETRY, feature];
|
|
||||||
this.instructions.push(endGeometryInstruction);
|
|
||||||
this.hitDetectionInstructions.push(endGeometryInstruction);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the buffered rendering extent. Rendering will be clipped to the extent
|
* Get the buffered rendering extent. Rendering will be clipped to the extent
|
||||||
@@ -1086,4 +1002,4 @@ class CanvasReplay extends VectorContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export default CanvasReplay;
|
export default CanvasExecutor;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* @module ol/render/canvas/ReplayGroup
|
* @module ol/render/canvas/ExecutorGroup
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {numberSafeCompareFunction} from '../../array.js';
|
import {numberSafeCompareFunction} from '../../array.js';
|
||||||
@@ -7,38 +7,23 @@ import {createCanvasContext2D} from '../../dom.js';
|
|||||||
import {buffer, createEmpty, extendCoordinate} from '../../extent.js';
|
import {buffer, createEmpty, extendCoordinate} from '../../extent.js';
|
||||||
import {transform2D} from '../../geom/flat/transform.js';
|
import {transform2D} from '../../geom/flat/transform.js';
|
||||||
import {isEmpty} from '../../obj.js';
|
import {isEmpty} from '../../obj.js';
|
||||||
import ReplayGroup from '../ReplayGroup.js';
|
import BaseExecutorGroup from '../ExecutorGroup.js';
|
||||||
import ReplayType from '../ReplayType.js';
|
import ReplayType from '../ReplayType.js';
|
||||||
import CanvasReplay from './Replay.js';
|
|
||||||
import CanvasImageReplay from './ImageReplay.js';
|
|
||||||
import CanvasLineStringReplay from './LineStringReplay.js';
|
|
||||||
import CanvasPolygonReplay from './PolygonReplay.js';
|
|
||||||
import CanvasTextReplay from './TextReplay.js';
|
|
||||||
import {ORDER} from '../replay.js';
|
import {ORDER} from '../replay.js';
|
||||||
import {create as createTransform, compose as composeTransform} from '../../transform.js';
|
import {create as createTransform, compose as composeTransform} from '../../transform.js';
|
||||||
|
import CanvasExecutor from './Executor.js';
|
||||||
|
|
||||||
|
|
||||||
/**
|
class ExecutorGroup extends BaseExecutorGroup {
|
||||||
* @type {Object<ReplayType, typeof CanvasReplay>}
|
|
||||||
*/
|
|
||||||
const BATCH_CONSTRUCTORS = {
|
|
||||||
'Circle': CanvasPolygonReplay,
|
|
||||||
'Default': CanvasReplay,
|
|
||||||
'Image': CanvasImageReplay,
|
|
||||||
'LineString': CanvasLineStringReplay,
|
|
||||||
'Polygon': CanvasPolygonReplay,
|
|
||||||
'Text': CanvasTextReplay
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
class CanvasReplayGroup extends ReplayGroup {
|
|
||||||
/**
|
/**
|
||||||
* @param {number} tolerance Tolerance.
|
* @param {number} tolerance Tolerance.
|
||||||
* @param {import("../../extent.js").Extent} maxExtent Max extent.
|
* @param {import("../../extent.js").Extent} maxExtent Max extent.
|
||||||
* @param {number} resolution Resolution.
|
* @param {number} resolution Resolution.
|
||||||
* @param {number} pixelRatio Pixel ratio.
|
* @param {number} pixelRatio Pixel ratio.
|
||||||
* @param {boolean} overlaps The replay group can have overlapping geometries.
|
* @param {boolean} overlaps The executor group can have overlapping geometries.
|
||||||
* @param {?} declutterTree Declutter tree for declutter processing in postrender.
|
* @param {?} declutterTree Declutter tree for declutter processing in postrender.
|
||||||
|
* @param {!Object<string, !Object<ReplayType, import("./Builder.js").SerializableInstructions>>} allInstructions
|
||||||
|
* The serializable instructions.
|
||||||
* @param {number=} opt_renderBuffer Optional rendering buffer.
|
* @param {number=} opt_renderBuffer Optional rendering buffer.
|
||||||
*/
|
*/
|
||||||
constructor(
|
constructor(
|
||||||
@@ -48,6 +33,7 @@ class CanvasReplayGroup extends ReplayGroup {
|
|||||||
pixelRatio,
|
pixelRatio,
|
||||||
overlaps,
|
overlaps,
|
||||||
declutterTree,
|
declutterTree,
|
||||||
|
allInstructions,
|
||||||
opt_renderBuffer
|
opt_renderBuffer
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
@@ -102,9 +88,9 @@ class CanvasReplayGroup extends ReplayGroup {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
* @type {!Object<string, !Object<ReplayType, CanvasReplay>>}
|
* @type {!Object<string, !Object<ReplayType, import("./Executor").default>>}
|
||||||
*/
|
*/
|
||||||
this.replaysByZIndex_ = {};
|
this.executorsByZIndex_ = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
@@ -117,23 +103,8 @@ class CanvasReplayGroup extends ReplayGroup {
|
|||||||
* @type {import("../../transform.js").Transform}
|
* @type {import("../../transform.js").Transform}
|
||||||
*/
|
*/
|
||||||
this.hitDetectionTransform_ = createTransform();
|
this.hitDetectionTransform_ = createTransform();
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
this.createExectutors_(allInstructions);
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
addDeclutter(group) {
|
|
||||||
let declutter = null;
|
|
||||||
if (this.declutterTree_) {
|
|
||||||
if (group) {
|
|
||||||
declutter = this.declutterGroup_;
|
|
||||||
/** @type {number} */ (declutter[4])++;
|
|
||||||
} else {
|
|
||||||
declutter = this.declutterGroup_ = createEmpty();
|
|
||||||
declutter.push(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return declutter;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -151,14 +122,34 @@ class CanvasReplayGroup extends ReplayGroup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Array<ReplayType>} replays Replays.
|
* Create executors and populate them using the provided instructions.
|
||||||
* @return {boolean} Has replays of the provided types.
|
* @private
|
||||||
|
* @param {!Object<string, !Object<ReplayType, import("./Builder.js").SerializableInstructions>>} allInstructions The serializable instructions
|
||||||
*/
|
*/
|
||||||
hasReplays(replays) {
|
createExectutors_(allInstructions) {
|
||||||
for (const zIndex in this.replaysByZIndex_) {
|
for (const zIndex in allInstructions) {
|
||||||
const candidates = this.replaysByZIndex_[zIndex];
|
let executors = this.executorsByZIndex_[zIndex];
|
||||||
for (let i = 0, ii = replays.length; i < ii; ++i) {
|
if (executors === undefined) {
|
||||||
if (replays[i] in candidates) {
|
this.executorsByZIndex_[zIndex] = executors = {};
|
||||||
|
}
|
||||||
|
const instructionByZindex = allInstructions[zIndex];
|
||||||
|
for (const replayType in instructionByZindex) {
|
||||||
|
const instructions = instructionByZindex[replayType];
|
||||||
|
executors[replayType] = new CanvasExecutor(this.tolerance_, this.maxExtent_,
|
||||||
|
this.resolution_, this.pixelRatio_, this.overlaps_, this.declutterTree_, instructions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Array<ReplayType>} executors Executors.
|
||||||
|
* @return {boolean} Has executors of the provided types.
|
||||||
|
*/
|
||||||
|
hasExecutors(executors) {
|
||||||
|
for (const zIndex in this.executorsByZIndex_) {
|
||||||
|
const candidates = this.executorsByZIndex_[zIndex];
|
||||||
|
for (let i = 0, ii = executors.length; i < ii; ++i) {
|
||||||
|
if (executors[i] in candidates) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -166,17 +157,6 @@ class CanvasReplayGroup extends ReplayGroup {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* FIXME empty description for jsdoc
|
|
||||||
*/
|
|
||||||
finish() {
|
|
||||||
for (const zKey in this.replaysByZIndex_) {
|
|
||||||
const replays = this.replaysByZIndex_[zKey];
|
|
||||||
for (const replayKey in replays) {
|
|
||||||
replays[replayKey].finish();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {import("../../coordinate.js").Coordinate} coordinate Coordinate.
|
* @param {import("../../coordinate.js").Coordinate} coordinate Coordinate.
|
||||||
@@ -263,27 +243,27 @@ class CanvasReplayGroup extends ReplayGroup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** @type {Array<number>} */
|
/** @type {Array<number>} */
|
||||||
const zs = Object.keys(this.replaysByZIndex_).map(Number);
|
const zs = Object.keys(this.executorsByZIndex_).map(Number);
|
||||||
zs.sort(numberSafeCompareFunction);
|
zs.sort(numberSafeCompareFunction);
|
||||||
|
|
||||||
let i, j, replays, replay, result;
|
let i, j, executors, executor, result;
|
||||||
for (i = zs.length - 1; i >= 0; --i) {
|
for (i = zs.length - 1; i >= 0; --i) {
|
||||||
const zIndexKey = zs[i].toString();
|
const zIndexKey = zs[i].toString();
|
||||||
replays = this.replaysByZIndex_[zIndexKey];
|
executors = this.executorsByZIndex_[zIndexKey];
|
||||||
for (j = ORDER.length - 1; j >= 0; --j) {
|
for (j = ORDER.length - 1; j >= 0; --j) {
|
||||||
replayType = ORDER[j];
|
replayType = ORDER[j];
|
||||||
replay = replays[replayType];
|
executor = executors[replayType];
|
||||||
if (replay !== undefined) {
|
if (executor !== undefined) {
|
||||||
if (declutterReplays &&
|
if (declutterReplays &&
|
||||||
(replayType == ReplayType.IMAGE || replayType == ReplayType.TEXT)) {
|
(replayType == ReplayType.IMAGE || replayType == ReplayType.TEXT)) {
|
||||||
const declutter = declutterReplays[zIndexKey];
|
const declutter = declutterReplays[zIndexKey];
|
||||||
if (!declutter) {
|
if (!declutter) {
|
||||||
declutterReplays[zIndexKey] = [replay, transform.slice(0)];
|
declutterReplays[zIndexKey] = [executor, transform.slice(0)];
|
||||||
} else {
|
} else {
|
||||||
declutter.push(replay, transform.slice(0));
|
declutter.push(executor, transform.slice(0));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
result = replay.replayHitDetection(context, transform, rotation,
|
result = executor.executeHitDetection(context, transform, rotation,
|
||||||
skippedFeaturesHash, featureCallback, hitExtent);
|
skippedFeaturesHash, featureCallback, hitExtent);
|
||||||
if (result) {
|
if (result) {
|
||||||
return result;
|
return result;
|
||||||
@@ -321,35 +301,40 @@ class CanvasReplayGroup extends ReplayGroup {
|
|||||||
/**
|
/**
|
||||||
* @inheritDoc
|
* @inheritDoc
|
||||||
*/
|
*/
|
||||||
getReplay(zIndex, replayType) {
|
getExecutor(zIndex, replayType) {
|
||||||
const zIndexKey = zIndex !== undefined ? zIndex.toString() : '0';
|
const zIndexKey = zIndex !== undefined ? zIndex.toString() : '0';
|
||||||
let replays = this.replaysByZIndex_[zIndexKey];
|
let executors = this.executorsByZIndex_[zIndexKey];
|
||||||
if (replays === undefined) {
|
if (executors === undefined) {
|
||||||
replays = {};
|
executors = {};
|
||||||
this.replaysByZIndex_[zIndexKey] = replays;
|
this.executorsByZIndex_[zIndexKey] = executors;
|
||||||
}
|
}
|
||||||
let replay = replays[replayType];
|
let executor = executors[replayType];
|
||||||
if (replay === undefined) {
|
if (executor === undefined) {
|
||||||
const Constructor = BATCH_CONSTRUCTORS[replayType];
|
// FIXME: it should not be possible to ask for an executor that does not exist
|
||||||
replay = new Constructor(this.tolerance_, this.maxExtent_,
|
executor = new CanvasExecutor(this.tolerance_, this.maxExtent_,
|
||||||
this.resolution_, this.pixelRatio_, this.overlaps_, this.declutterTree_);
|
this.resolution_, this.pixelRatio_, this.overlaps_, {
|
||||||
replays[replayType] = replay;
|
instructions: [],
|
||||||
|
hitDetectionInstructions: [],
|
||||||
|
coordinates: []
|
||||||
|
},
|
||||||
|
this.declutterTree_);
|
||||||
|
executors[replayType] = executor;
|
||||||
}
|
}
|
||||||
return replay;
|
return executor;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return {Object<string, Object<ReplayType, CanvasReplay>>} Replays.
|
* @return {Object<string, Object<ReplayType, CanvasReplay>>} Replays.
|
||||||
*/
|
*/
|
||||||
getReplays() {
|
getExecutors() {
|
||||||
return this.replaysByZIndex_;
|
return this.executorsByZIndex_;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritDoc
|
* @inheritDoc
|
||||||
*/
|
*/
|
||||||
isEmpty() {
|
isEmpty() {
|
||||||
return isEmpty(this.replaysByZIndex_);
|
return isEmpty(this.executorsByZIndex_);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -362,7 +347,7 @@ class CanvasReplayGroup extends ReplayGroup {
|
|||||||
* Default is {@link module:ol/render/replay~ORDER}
|
* Default is {@link module:ol/render/replay~ORDER}
|
||||||
* @param {Object<string, import("../canvas.js").DeclutterGroup>=} opt_declutterReplays Declutter replays.
|
* @param {Object<string, import("../canvas.js").DeclutterGroup>=} opt_declutterReplays Declutter replays.
|
||||||
*/
|
*/
|
||||||
replay(
|
execute(
|
||||||
context,
|
context,
|
||||||
transform,
|
transform,
|
||||||
viewRotation,
|
viewRotation,
|
||||||
@@ -373,7 +358,7 @@ class CanvasReplayGroup extends ReplayGroup {
|
|||||||
) {
|
) {
|
||||||
|
|
||||||
/** @type {Array<number>} */
|
/** @type {Array<number>} */
|
||||||
const zs = Object.keys(this.replaysByZIndex_).map(Number);
|
const zs = Object.keys(this.executorsByZIndex_).map(Number);
|
||||||
zs.sort(numberSafeCompareFunction);
|
zs.sort(numberSafeCompareFunction);
|
||||||
|
|
||||||
// setup clipping so that the parts of over-simplified geometries are not
|
// setup clipping so that the parts of over-simplified geometries are not
|
||||||
@@ -385,7 +370,7 @@ class CanvasReplayGroup extends ReplayGroup {
|
|||||||
let i, ii, j, jj, replays, replay;
|
let i, ii, j, jj, replays, replay;
|
||||||
for (i = 0, ii = zs.length; i < ii; ++i) {
|
for (i = 0, ii = zs.length; i < ii; ++i) {
|
||||||
const zIndexKey = zs[i].toString();
|
const zIndexKey = zs[i].toString();
|
||||||
replays = this.replaysByZIndex_[zIndexKey];
|
replays = this.executorsByZIndex_[zIndexKey];
|
||||||
for (j = 0, jj = replayTypes.length; j < jj; ++j) {
|
for (j = 0, jj = replayTypes.length; j < jj; ++j) {
|
||||||
const replayType = replayTypes[j];
|
const replayType = replayTypes[j];
|
||||||
replay = replays[replayType];
|
replay = replays[replayType];
|
||||||
@@ -399,7 +384,7 @@ class CanvasReplayGroup extends ReplayGroup {
|
|||||||
declutter.push(replay, transform.slice(0));
|
declutter.push(replay, transform.slice(0));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
replay.replay(context, transform, viewRotation, skippedFeaturesHash, snapToPixel);
|
replay.execute(context, transform, viewRotation, skippedFeaturesHash, snapToPixel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -498,14 +483,14 @@ export function replayDeclutter(declutterReplays, context, rotation, snapToPixel
|
|||||||
const zs = Object.keys(declutterReplays).map(Number).sort(numberSafeCompareFunction);
|
const zs = Object.keys(declutterReplays).map(Number).sort(numberSafeCompareFunction);
|
||||||
const skippedFeatureUids = {};
|
const skippedFeatureUids = {};
|
||||||
for (let z = 0, zz = zs.length; z < zz; ++z) {
|
for (let z = 0, zz = zs.length; z < zz; ++z) {
|
||||||
const replayData = declutterReplays[zs[z].toString()];
|
const executorData = declutterReplays[zs[z].toString()];
|
||||||
for (let i = 0, ii = replayData.length; i < ii;) {
|
for (let i = 0, ii = executorData.length; i < ii;) {
|
||||||
const replay = replayData[i++];
|
const executor = executorData[i++];
|
||||||
const transform = replayData[i++];
|
const transform = executorData[i++];
|
||||||
replay.replay(context, transform, rotation, skippedFeatureUids, snapToPixel);
|
executor.execute(context, transform, rotation, skippedFeatureUids, snapToPixel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export default CanvasReplayGroup;
|
export default ExecutorGroup;
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
/**
|
/**
|
||||||
* @module ol/render/canvas/ImageReplay
|
* @module ol/render/canvas/ImageBuilder
|
||||||
*/
|
*/
|
||||||
import CanvasInstruction from './Instruction.js';
|
import CanvasInstruction from './Instruction.js';
|
||||||
import CanvasReplay from './Replay.js';
|
import CanvasBuilder from './Builder.js';
|
||||||
|
|
||||||
class CanvasImageReplay extends CanvasReplay {
|
class CanvasImageBuilder extends CanvasBuilder {
|
||||||
/**
|
/**
|
||||||
* @param {number} tolerance Tolerance.
|
* @param {number} tolerance Tolerance.
|
||||||
* @param {import("../../extent.js").Extent} maxExtent Maximum extent.
|
* @param {import("../../extent.js").Extent} maxExtent Maximum extent.
|
||||||
* @param {number} resolution Resolution.
|
* @param {number} resolution Resolution.
|
||||||
* @param {number} pixelRatio Pixel ratio.
|
* @param {number} pixelRatio Pixel ratio.
|
||||||
* @param {boolean} overlaps The replay can have overlapping geometries.
|
* @param {boolean} overlaps The builder can have overlapping geometries.
|
||||||
* @param {?} declutterTree Declutter tree.
|
* @param {?} declutterTree Declutter tree.
|
||||||
*/
|
*/
|
||||||
constructor(tolerance, maxExtent, resolution, pixelRatio, overlaps, declutterTree) {
|
constructor(tolerance, maxExtent, resolution, pixelRatio, overlaps, declutterTree) {
|
||||||
@@ -185,6 +185,7 @@ class CanvasImageReplay extends CanvasReplay {
|
|||||||
this.rotateWithView_ = undefined;
|
this.rotateWithView_ = undefined;
|
||||||
this.rotation_ = undefined;
|
this.rotation_ = undefined;
|
||||||
this.width_ = undefined;
|
this.width_ = undefined;
|
||||||
|
return super.finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -213,4 +214,4 @@ class CanvasImageReplay extends CanvasReplay {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export default CanvasImageReplay;
|
export default CanvasImageBuilder;
|
||||||
+5
-4
@@ -1,10 +1,10 @@
|
|||||||
/**
|
/**
|
||||||
* @module ol/render/canvas/LineStringReplay
|
* @module ol/render/canvas/LineStringBuilder
|
||||||
*/
|
*/
|
||||||
import CanvasInstruction, {strokeInstruction, beginPathInstruction} from './Instruction.js';
|
import CanvasInstruction, {strokeInstruction, beginPathInstruction} from './Instruction.js';
|
||||||
import CanvasReplay from './Replay.js';
|
import CanvasBuilder from './Builder.js';
|
||||||
|
|
||||||
class CanvasLineStringReplay extends CanvasReplay {
|
class CanvasLineStringBuilder extends CanvasBuilder {
|
||||||
/**
|
/**
|
||||||
* @param {number} tolerance Tolerance.
|
* @param {number} tolerance Tolerance.
|
||||||
* @param {import("../../extent.js").Extent} maxExtent Maximum extent.
|
* @param {import("../../extent.js").Extent} maxExtent Maximum extent.
|
||||||
@@ -97,6 +97,7 @@ class CanvasLineStringReplay extends CanvasReplay {
|
|||||||
}
|
}
|
||||||
this.reverseHitDetectionInstructions();
|
this.reverseHitDetectionInstructions();
|
||||||
this.state = null;
|
this.state = null;
|
||||||
|
return super.finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -114,4 +115,4 @@ class CanvasLineStringReplay extends CanvasReplay {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export default CanvasLineStringReplay;
|
export default CanvasLineStringBuilder;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* @module ol/render/canvas/PolygonReplay
|
* @module ol/render/canvas/PolygonBuilder
|
||||||
*/
|
*/
|
||||||
import {asString} from '../../color.js';
|
import {asString} from '../../color.js';
|
||||||
import {snap} from '../../geom/flat/simplify.js';
|
import {snap} from '../../geom/flat/simplify.js';
|
||||||
@@ -7,10 +7,10 @@ import {defaultFillStyle} from '../canvas.js';
|
|||||||
import CanvasInstruction, {
|
import CanvasInstruction, {
|
||||||
fillInstruction, strokeInstruction, beginPathInstruction, closePathInstruction
|
fillInstruction, strokeInstruction, beginPathInstruction, closePathInstruction
|
||||||
} from './Instruction.js';
|
} from './Instruction.js';
|
||||||
import CanvasReplay from './Replay.js';
|
import CanvasBuilder from './Builder.js';
|
||||||
|
|
||||||
|
|
||||||
class CanvasPolygonReplay extends CanvasReplay {
|
class CanvasPolygonBuilder extends CanvasBuilder {
|
||||||
/**
|
/**
|
||||||
* @param {number} tolerance Tolerance.
|
* @param {number} tolerance Tolerance.
|
||||||
* @param {import("../../extent.js").Extent} maxExtent Maximum extent.
|
* @param {import("../../extent.js").Extent} maxExtent Maximum extent.
|
||||||
@@ -192,6 +192,7 @@ class CanvasPolygonReplay extends CanvasReplay {
|
|||||||
coordinates[i] = snap(coordinates[i], tolerance);
|
coordinates[i] = snap(coordinates[i], tolerance);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return super.finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -211,4 +212,4 @@ class CanvasPolygonReplay extends CanvasReplay {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export default CanvasPolygonReplay;
|
export default CanvasPolygonBuilder;
|
||||||
@@ -1,20 +1,18 @@
|
|||||||
/**
|
/**
|
||||||
* @module ol/render/canvas/TextReplay
|
* @module ol/render/canvas/TextBuilder
|
||||||
*/
|
*/
|
||||||
import {getUid} from '../../util.js';
|
import {getUid} from '../../util.js';
|
||||||
import {asColorLike} from '../../colorlike.js';
|
import {asColorLike} from '../../colorlike.js';
|
||||||
import {createCanvasContext2D} from '../../dom.js';
|
|
||||||
import {intersects} from '../../extent.js';
|
import {intersects} from '../../extent.js';
|
||||||
import {matchingChunk} from '../../geom/flat/straightchunk.js';
|
import {matchingChunk} from '../../geom/flat/straightchunk.js';
|
||||||
import GeometryType from '../../geom/GeometryType.js';
|
import GeometryType from '../../geom/GeometryType.js';
|
||||||
import {CANVAS_LINE_DASH} from '../../has.js';
|
import {labelCache, defaultTextAlign, defaultPadding, defaultLineCap, defaultLineDashOffset, defaultLineDash, defaultLineJoin, defaultFillStyle, checkFont, defaultFont, defaultLineWidth, defaultMiterLimit, defaultStrokeStyle, defaultTextBaseline} from '../canvas.js';
|
||||||
import {labelCache, measureTextWidth, defaultTextAlign, measureTextHeight, defaultPadding, defaultLineCap, defaultLineDashOffset, defaultLineDash, defaultLineJoin, defaultFillStyle, checkFont, defaultFont, defaultLineWidth, defaultMiterLimit, defaultStrokeStyle, defaultTextBaseline} from '../canvas.js';
|
|
||||||
import CanvasInstruction from './Instruction.js';
|
import CanvasInstruction from './Instruction.js';
|
||||||
import CanvasReplay from './Replay.js';
|
import CanvasBuilder from './Builder.js';
|
||||||
import {TEXT_ALIGN} from '../replay.js';
|
import {TEXT_ALIGN} from '../replay.js';
|
||||||
import TextPlacement from '../../style/TextPlacement.js';
|
import TextPlacement from '../../style/TextPlacement.js';
|
||||||
|
|
||||||
class CanvasTextReplay extends CanvasReplay {
|
class CanvasTextBuilder extends CanvasBuilder {
|
||||||
/**
|
/**
|
||||||
* @param {number} tolerance Tolerance.
|
* @param {number} tolerance Tolerance.
|
||||||
* @param {import("../../extent.js").Extent} maxExtent Maximum extent.
|
* @param {import("../../extent.js").Extent} maxExtent Maximum extent.
|
||||||
@@ -119,14 +117,18 @@ class CanvasTextReplay extends CanvasReplay {
|
|||||||
*/
|
*/
|
||||||
this.strokeKey_ = '';
|
this.strokeKey_ = '';
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* @type {Object<string, Object<string, number>>}
|
|
||||||
*/
|
|
||||||
this.widths_ = {};
|
|
||||||
|
|
||||||
labelCache.prune();
|
labelCache.prune();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
finish() {
|
||||||
|
const instructions = super.finish();
|
||||||
|
instructions.textStates = this.textStates;
|
||||||
|
instructions.fillStates = this.fillStates;
|
||||||
|
instructions.strokeStates = this.strokeStates;
|
||||||
|
return instructions;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -191,8 +193,11 @@ class CanvasTextReplay extends CanvasReplay {
|
|||||||
this.endGeometry(geometry, feature);
|
this.endGeometry(geometry, feature);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
const label = this.getImage(this.text_, this.textKey_, this.fillKey_, this.strokeKey_);
|
|
||||||
const width = label.width / this.pixelRatio;
|
let geometryWidths = null;
|
||||||
|
if (!textState.overflow) {
|
||||||
|
geometryWidths = [];
|
||||||
|
}
|
||||||
switch (geometryType) {
|
switch (geometryType) {
|
||||||
case GeometryType.POINT:
|
case GeometryType.POINT:
|
||||||
case GeometryType.MULTI_POINT:
|
case GeometryType.MULTI_POINT:
|
||||||
@@ -211,8 +216,8 @@ class CanvasTextReplay extends CanvasReplay {
|
|||||||
break;
|
break;
|
||||||
case GeometryType.POLYGON:
|
case GeometryType.POLYGON:
|
||||||
flatCoordinates = /** @type {import("../../geom/Polygon.js").default} */ (geometry).getFlatInteriorPoint();
|
flatCoordinates = /** @type {import("../../geom/Polygon.js").default} */ (geometry).getFlatInteriorPoint();
|
||||||
if (!textState.overflow && flatCoordinates[2] / this.resolution < width) {
|
if (!textState.overflow) {
|
||||||
return;
|
geometryWidths.push(flatCoordinates[2] / this.resolution);
|
||||||
}
|
}
|
||||||
stride = 3;
|
stride = 3;
|
||||||
break;
|
break;
|
||||||
@@ -220,9 +225,10 @@ class CanvasTextReplay extends CanvasReplay {
|
|||||||
const interiorPoints = /** @type {import("../../geom/MultiPolygon.js").default} */ (geometry).getFlatInteriorPoints();
|
const interiorPoints = /** @type {import("../../geom/MultiPolygon.js").default} */ (geometry).getFlatInteriorPoints();
|
||||||
flatCoordinates = [];
|
flatCoordinates = [];
|
||||||
for (i = 0, ii = interiorPoints.length; i < ii; i += 3) {
|
for (i = 0, ii = interiorPoints.length; i < ii; i += 3) {
|
||||||
if (textState.overflow || interiorPoints[i + 2] / this.resolution >= width) {
|
if (!textState.overflow) {
|
||||||
flatCoordinates.push(interiorPoints[i], interiorPoints[i + 1]);
|
geometryWidths.push(interiorPoints[i + 2] / this.resolution);
|
||||||
}
|
}
|
||||||
|
flatCoordinates.push(interiorPoints[i], interiorPoints[i + 1]);
|
||||||
}
|
}
|
||||||
end = flatCoordinates.length;
|
end = flatCoordinates.length;
|
||||||
if (end == 0) {
|
if (end == 0) {
|
||||||
@@ -232,6 +238,9 @@ class CanvasTextReplay extends CanvasReplay {
|
|||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
end = this.appendFlatCoordinates(flatCoordinates, 0, end, stride, false, false);
|
end = this.appendFlatCoordinates(flatCoordinates, 0, end, stride, false, false);
|
||||||
|
|
||||||
|
this.saveTextStates_();
|
||||||
|
|
||||||
if (textState.backgroundFill || textState.backgroundStroke) {
|
if (textState.backgroundFill || textState.backgroundStroke) {
|
||||||
this.setFillStrokeStyle(textState.backgroundFill, textState.backgroundStroke);
|
this.setFillStrokeStyle(textState.backgroundFill, textState.backgroundStroke);
|
||||||
if (textState.backgroundFill) {
|
if (textState.backgroundFill) {
|
||||||
@@ -243,122 +252,41 @@ class CanvasTextReplay extends CanvasReplay {
|
|||||||
this.hitDetectionInstructions.push(this.createStroke(this.state));
|
this.hitDetectionInstructions.push(this.createStroke(this.state));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.beginGeometry(geometry, feature);
|
this.beginGeometry(geometry, feature);
|
||||||
this.drawTextImage_(label, begin, end);
|
|
||||||
|
// The image is unknown at this stage so we pass null; it will be computed at render time.
|
||||||
|
// For clarity, we pass NaN for offsetX, offsetY, width and height, which will be computed at
|
||||||
|
// render time.
|
||||||
|
const pixelRatio = this.pixelRatio;
|
||||||
|
this.instructions.push([CanvasInstruction.DRAW_IMAGE, begin, end,
|
||||||
|
null, NaN, NaN, this.declutterGroup_, NaN, 1, 0, 0,
|
||||||
|
this.textRotateWithView_, this.textRotation_, 1, NaN,
|
||||||
|
textState.padding == defaultPadding ?
|
||||||
|
defaultPadding : textState.padding.map(function(p) {
|
||||||
|
return p * pixelRatio;
|
||||||
|
}),
|
||||||
|
!!textState.backgroundFill, !!textState.backgroundStroke,
|
||||||
|
this.text_, this.textKey_, this.strokeKey_, this.fillKey_,
|
||||||
|
this.textOffsetX_, this.textOffsetY_, geometryWidths
|
||||||
|
]);
|
||||||
|
this.hitDetectionInstructions.push([CanvasInstruction.DRAW_IMAGE, begin, end,
|
||||||
|
null, NaN, NaN, this.declutterGroup_, NaN, 1, 0, 0,
|
||||||
|
this.textRotateWithView_, this.textRotation_, 1 / this.pixelRatio, NaN,
|
||||||
|
textState.padding,
|
||||||
|
!!textState.backgroundFill, !!textState.backgroundStroke,
|
||||||
|
this.text_, this.textKey_, this.strokeKey_, this.fillKey_,
|
||||||
|
this.textOffsetX_, this.textOffsetY_, geometryWidths
|
||||||
|
]);
|
||||||
|
|
||||||
this.endGeometry(geometry, feature);
|
this.endGeometry(geometry, feature);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} text Text.
|
|
||||||
* @param {string} textKey Text style key.
|
|
||||||
* @param {string} fillKey Fill style key.
|
|
||||||
* @param {string} strokeKey Stroke style key.
|
|
||||||
* @return {HTMLCanvasElement} Image.
|
|
||||||
*/
|
|
||||||
getImage(text, textKey, fillKey, strokeKey) {
|
|
||||||
let label;
|
|
||||||
const key = strokeKey + textKey + text + fillKey + this.pixelRatio;
|
|
||||||
|
|
||||||
if (!labelCache.containsKey(key)) {
|
|
||||||
const strokeState = strokeKey ? this.strokeStates[strokeKey] || this.textStrokeState_ : null;
|
|
||||||
const fillState = fillKey ? this.fillStates[fillKey] || this.textFillState_ : null;
|
|
||||||
const textState = this.textStates[textKey] || this.textState_;
|
|
||||||
const pixelRatio = this.pixelRatio;
|
|
||||||
const scale = textState.scale * pixelRatio;
|
|
||||||
const align = TEXT_ALIGN[textState.textAlign || defaultTextAlign];
|
|
||||||
const strokeWidth = strokeKey && strokeState.lineWidth ? strokeState.lineWidth : 0;
|
|
||||||
|
|
||||||
const lines = text.split('\n');
|
|
||||||
const numLines = lines.length;
|
|
||||||
const widths = [];
|
|
||||||
const width = measureTextWidths(textState.font, lines, widths);
|
|
||||||
const lineHeight = measureTextHeight(textState.font);
|
|
||||||
const height = lineHeight * numLines;
|
|
||||||
const renderWidth = (width + strokeWidth);
|
|
||||||
const context = createCanvasContext2D(
|
|
||||||
Math.ceil(renderWidth * scale),
|
|
||||||
Math.ceil((height + strokeWidth) * scale));
|
|
||||||
label = context.canvas;
|
|
||||||
labelCache.set(key, label);
|
|
||||||
if (scale != 1) {
|
|
||||||
context.scale(scale, scale);
|
|
||||||
}
|
|
||||||
context.font = textState.font;
|
|
||||||
if (strokeKey) {
|
|
||||||
context.strokeStyle = strokeState.strokeStyle;
|
|
||||||
context.lineWidth = strokeWidth;
|
|
||||||
context.lineCap = /** @type {CanvasLineCap} */ (strokeState.lineCap);
|
|
||||||
context.lineJoin = /** @type {CanvasLineJoin} */ (strokeState.lineJoin);
|
|
||||||
context.miterLimit = strokeState.miterLimit;
|
|
||||||
if (CANVAS_LINE_DASH && strokeState.lineDash.length) {
|
|
||||||
context.setLineDash(strokeState.lineDash);
|
|
||||||
context.lineDashOffset = strokeState.lineDashOffset;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (fillKey) {
|
|
||||||
context.fillStyle = fillState.fillStyle;
|
|
||||||
}
|
|
||||||
context.textBaseline = 'middle';
|
|
||||||
context.textAlign = 'center';
|
|
||||||
const leftRight = (0.5 - align);
|
|
||||||
const x = align * label.width / scale + leftRight * strokeWidth;
|
|
||||||
let i;
|
|
||||||
if (strokeKey) {
|
|
||||||
for (i = 0; i < numLines; ++i) {
|
|
||||||
context.strokeText(lines[i], x + leftRight * widths[i], 0.5 * (strokeWidth + lineHeight) + i * lineHeight);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (fillKey) {
|
|
||||||
for (i = 0; i < numLines; ++i) {
|
|
||||||
context.fillText(lines[i], x + leftRight * widths[i], 0.5 * (strokeWidth + lineHeight) + i * lineHeight);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return labelCache.get(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
* @param {HTMLCanvasElement} label Label.
|
|
||||||
* @param {number} begin Begin.
|
|
||||||
* @param {number} end End.
|
|
||||||
*/
|
*/
|
||||||
drawTextImage_(label, begin, end) {
|
saveTextStates_() {
|
||||||
const textState = this.textState_;
|
|
||||||
const strokeState = this.textStrokeState_;
|
|
||||||
const pixelRatio = this.pixelRatio;
|
|
||||||
const align = TEXT_ALIGN[textState.textAlign || defaultTextAlign];
|
|
||||||
const baseline = TEXT_ALIGN[textState.textBaseline];
|
|
||||||
const strokeWidth = strokeState && strokeState.lineWidth ? strokeState.lineWidth : 0;
|
|
||||||
|
|
||||||
const anchorX = align * label.width / pixelRatio + 2 * (0.5 - align) * strokeWidth;
|
|
||||||
const anchorY = baseline * label.height / pixelRatio + 2 * (0.5 - baseline) * strokeWidth;
|
|
||||||
this.instructions.push([CanvasInstruction.DRAW_IMAGE, begin, end,
|
|
||||||
label, (anchorX - this.textOffsetX_) * pixelRatio, (anchorY - this.textOffsetY_) * pixelRatio,
|
|
||||||
this.declutterGroup_, label.height, 1, 0, 0, this.textRotateWithView_, this.textRotation_,
|
|
||||||
1, label.width,
|
|
||||||
textState.padding == defaultPadding ?
|
|
||||||
defaultPadding : textState.padding.map(function(p) {
|
|
||||||
return p * pixelRatio;
|
|
||||||
}),
|
|
||||||
!!textState.backgroundFill, !!textState.backgroundStroke
|
|
||||||
]);
|
|
||||||
this.hitDetectionInstructions.push([CanvasInstruction.DRAW_IMAGE, begin, end,
|
|
||||||
label, (anchorX - this.textOffsetX_) * pixelRatio, (anchorY - this.textOffsetY_) * pixelRatio,
|
|
||||||
this.declutterGroup_, label.height, 1, 0, 0, this.textRotateWithView_, this.textRotation_,
|
|
||||||
1 / pixelRatio, label.width, textState.padding,
|
|
||||||
!!textState.backgroundFill, !!textState.backgroundStroke
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* @param {number} begin Begin.
|
|
||||||
* @param {number} end End.
|
|
||||||
* @param {import("../canvas.js").DeclutterGroup} declutterGroup Declutter group.
|
|
||||||
*/
|
|
||||||
drawChars_(begin, end, declutterGroup) {
|
|
||||||
const strokeState = this.textStrokeState_;
|
const strokeState = this.textStrokeState_;
|
||||||
const textState = this.textState_;
|
const textState = this.textState_;
|
||||||
const fillState = this.textFillState_;
|
const fillState = this.textFillState_;
|
||||||
@@ -378,10 +306,11 @@ class CanvasTextReplay extends CanvasReplay {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const textKey = this.textKey_;
|
const textKey = this.textKey_;
|
||||||
if (!(this.textKey_ in this.textStates)) {
|
if (!(textKey in this.textStates)) {
|
||||||
this.textStates[this.textKey_] = /** @type {import("../canvas.js").TextState} */ ({
|
this.textStates[textKey] = /** @type {import("../canvas.js").TextState} */ ({
|
||||||
font: textState.font,
|
font: textState.font,
|
||||||
textAlign: textState.textAlign || defaultTextAlign,
|
textAlign: textState.textAlign || defaultTextAlign,
|
||||||
|
textBaseline: textState.textBaseline || defaultTextBaseline,
|
||||||
scale: textState.scale
|
scale: textState.scale
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -393,41 +322,42 @@ class CanvasTextReplay extends CanvasReplay {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @param {number} begin Begin.
|
||||||
|
* @param {number} end End.
|
||||||
|
* @param {import("../canvas.js").DeclutterGroup} declutterGroup Declutter group.
|
||||||
|
*/
|
||||||
|
drawChars_(begin, end, declutterGroup) {
|
||||||
|
const strokeState = this.textStrokeState_;
|
||||||
|
const textState = this.textState_;
|
||||||
|
|
||||||
|
const strokeKey = this.strokeKey_;
|
||||||
|
const textKey = this.textKey_;
|
||||||
|
const fillKey = this.fillKey_;
|
||||||
|
this.saveTextStates_();
|
||||||
|
|
||||||
|
|
||||||
const pixelRatio = this.pixelRatio;
|
const pixelRatio = this.pixelRatio;
|
||||||
const baseline = TEXT_ALIGN[textState.textBaseline];
|
const baseline = TEXT_ALIGN[textState.textBaseline];
|
||||||
|
|
||||||
const offsetY = this.textOffsetY_ * pixelRatio;
|
const offsetY = this.textOffsetY_ * pixelRatio;
|
||||||
const text = this.text_;
|
const text = this.text_;
|
||||||
const font = textState.font;
|
|
||||||
const textScale = textState.scale;
|
const textScale = textState.scale;
|
||||||
const strokeWidth = strokeState ? strokeState.lineWidth * textScale / 2 : 0;
|
const strokeWidth = strokeState ? strokeState.lineWidth * textScale / 2 : 0;
|
||||||
let widths = this.widths_[font];
|
|
||||||
if (!widths) {
|
|
||||||
this.widths_[font] = widths = {};
|
|
||||||
}
|
|
||||||
this.instructions.push([CanvasInstruction.DRAW_CHARS,
|
this.instructions.push([CanvasInstruction.DRAW_CHARS,
|
||||||
begin, end, baseline, declutterGroup,
|
begin, end, baseline, declutterGroup,
|
||||||
textState.overflow, fillKey, textState.maxAngle,
|
textState.overflow, fillKey, textState.maxAngle,
|
||||||
function(text) {
|
pixelRatio,
|
||||||
let width = widths[text];
|
|
||||||
if (!width) {
|
|
||||||
width = widths[text] = measureTextWidth(font, text);
|
|
||||||
}
|
|
||||||
return width * textScale * pixelRatio;
|
|
||||||
},
|
|
||||||
offsetY, strokeKey, strokeWidth * pixelRatio, text, textKey, 1
|
offsetY, strokeKey, strokeWidth * pixelRatio, text, textKey, 1
|
||||||
]);
|
]);
|
||||||
this.hitDetectionInstructions.push([CanvasInstruction.DRAW_CHARS,
|
this.hitDetectionInstructions.push([CanvasInstruction.DRAW_CHARS,
|
||||||
begin, end, baseline, declutterGroup,
|
begin, end, baseline, declutterGroup,
|
||||||
textState.overflow, fillKey, textState.maxAngle,
|
textState.overflow, fillKey, textState.maxAngle,
|
||||||
function(text) {
|
1,
|
||||||
let width = widths[text];
|
|
||||||
if (!width) {
|
|
||||||
width = widths[text] = measureTextWidth(font, text);
|
|
||||||
}
|
|
||||||
return width * textScale;
|
|
||||||
},
|
|
||||||
offsetY, strokeKey, strokeWidth, text, textKey, 1 / pixelRatio
|
offsetY, strokeKey, strokeWidth, text, textKey, 1 / pixelRatio
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@@ -518,23 +448,4 @@ class CanvasTextReplay extends CanvasReplay {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
export default CanvasTextBuilder;
|
||||||
* @param {string} font Font to use for measuring.
|
|
||||||
* @param {Array<string>} lines Lines to measure.
|
|
||||||
* @param {Array<number>} widths Array will be populated with the widths of
|
|
||||||
* each line.
|
|
||||||
* @return {number} Width of the whole text.
|
|
||||||
*/
|
|
||||||
export function measureTextWidths(font, lines, widths) {
|
|
||||||
const numLines = lines.length;
|
|
||||||
let width = 0;
|
|
||||||
for (let i = 0; i < numLines; ++i) {
|
|
||||||
const currentWidth = measureTextWidth(font, lines[i]);
|
|
||||||
width = Math.max(width, currentWidth);
|
|
||||||
widths.push(currentWidth);
|
|
||||||
}
|
|
||||||
return width;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export default CanvasTextReplay;
|
|
||||||
@@ -90,7 +90,7 @@ class WebGLImmediateRenderer extends VectorContext {
|
|||||||
drawText_(replayGroup, geometry) {
|
drawText_(replayGroup, geometry) {
|
||||||
const context = this.context_;
|
const context = this.context_;
|
||||||
const replay = /** @type {import("./TextReplay.js").default} */ (
|
const replay = /** @type {import("./TextReplay.js").default} */ (
|
||||||
replayGroup.getReplay(0, ReplayType.TEXT));
|
replayGroup.getBuilder(0, ReplayType.TEXT));
|
||||||
replay.setTextStyle(this.textStyle_);
|
replay.setTextStyle(this.textStyle_);
|
||||||
replay.drawText(geometry, null);
|
replay.drawText(geometry, null);
|
||||||
replay.finish(context);
|
replay.finish(context);
|
||||||
@@ -191,7 +191,7 @@ class WebGLImmediateRenderer extends VectorContext {
|
|||||||
const context = this.context_;
|
const context = this.context_;
|
||||||
const replayGroup = new WebGLReplayGroup(1, this.extent_);
|
const replayGroup = new WebGLReplayGroup(1, this.extent_);
|
||||||
const replay = /** @type {import("./ImageReplay.js").default} */ (
|
const replay = /** @type {import("./ImageReplay.js").default} */ (
|
||||||
replayGroup.getReplay(0, ReplayType.IMAGE));
|
replayGroup.getBuilder(0, ReplayType.IMAGE));
|
||||||
replay.setImageStyle(this.imageStyle_);
|
replay.setImageStyle(this.imageStyle_);
|
||||||
replay.drawPoint(geometry, data);
|
replay.drawPoint(geometry, data);
|
||||||
replay.finish(context);
|
replay.finish(context);
|
||||||
@@ -218,7 +218,7 @@ class WebGLImmediateRenderer extends VectorContext {
|
|||||||
const context = this.context_;
|
const context = this.context_;
|
||||||
const replayGroup = new WebGLReplayGroup(1, this.extent_);
|
const replayGroup = new WebGLReplayGroup(1, this.extent_);
|
||||||
const replay = /** @type {import("./ImageReplay.js").default} */ (
|
const replay = /** @type {import("./ImageReplay.js").default} */ (
|
||||||
replayGroup.getReplay(0, ReplayType.IMAGE));
|
replayGroup.getBuilder(0, ReplayType.IMAGE));
|
||||||
replay.setImageStyle(this.imageStyle_);
|
replay.setImageStyle(this.imageStyle_);
|
||||||
replay.drawMultiPoint(geometry, data);
|
replay.drawMultiPoint(geometry, data);
|
||||||
replay.finish(context);
|
replay.finish(context);
|
||||||
@@ -244,7 +244,7 @@ class WebGLImmediateRenderer extends VectorContext {
|
|||||||
const context = this.context_;
|
const context = this.context_;
|
||||||
const replayGroup = new WebGLReplayGroup(1, this.extent_);
|
const replayGroup = new WebGLReplayGroup(1, this.extent_);
|
||||||
const replay = /** @type {import("./LineStringReplay.js").default} */ (
|
const replay = /** @type {import("./LineStringReplay.js").default} */ (
|
||||||
replayGroup.getReplay(0, ReplayType.LINE_STRING));
|
replayGroup.getBuilder(0, ReplayType.LINE_STRING));
|
||||||
replay.setFillStrokeStyle(null, this.strokeStyle_);
|
replay.setFillStrokeStyle(null, this.strokeStyle_);
|
||||||
replay.drawLineString(geometry, data);
|
replay.drawLineString(geometry, data);
|
||||||
replay.finish(context);
|
replay.finish(context);
|
||||||
@@ -270,7 +270,7 @@ class WebGLImmediateRenderer extends VectorContext {
|
|||||||
const context = this.context_;
|
const context = this.context_;
|
||||||
const replayGroup = new WebGLReplayGroup(1, this.extent_);
|
const replayGroup = new WebGLReplayGroup(1, this.extent_);
|
||||||
const replay = /** @type {import("./LineStringReplay.js").default} */ (
|
const replay = /** @type {import("./LineStringReplay.js").default} */ (
|
||||||
replayGroup.getReplay(0, ReplayType.LINE_STRING));
|
replayGroup.getBuilder(0, ReplayType.LINE_STRING));
|
||||||
replay.setFillStrokeStyle(null, this.strokeStyle_);
|
replay.setFillStrokeStyle(null, this.strokeStyle_);
|
||||||
replay.drawMultiLineString(geometry, data);
|
replay.drawMultiLineString(geometry, data);
|
||||||
replay.finish(context);
|
replay.finish(context);
|
||||||
@@ -296,7 +296,7 @@ class WebGLImmediateRenderer extends VectorContext {
|
|||||||
const context = this.context_;
|
const context = this.context_;
|
||||||
const replayGroup = new WebGLReplayGroup(1, this.extent_);
|
const replayGroup = new WebGLReplayGroup(1, this.extent_);
|
||||||
const replay = /** @type {import("./PolygonReplay.js").default} */ (
|
const replay = /** @type {import("./PolygonReplay.js").default} */ (
|
||||||
replayGroup.getReplay(0, ReplayType.POLYGON));
|
replayGroup.getBuilder(0, ReplayType.POLYGON));
|
||||||
replay.setFillStrokeStyle(this.fillStyle_, this.strokeStyle_);
|
replay.setFillStrokeStyle(this.fillStyle_, this.strokeStyle_);
|
||||||
replay.drawPolygon(geometry, data);
|
replay.drawPolygon(geometry, data);
|
||||||
replay.finish(context);
|
replay.finish(context);
|
||||||
@@ -322,7 +322,7 @@ class WebGLImmediateRenderer extends VectorContext {
|
|||||||
const context = this.context_;
|
const context = this.context_;
|
||||||
const replayGroup = new WebGLReplayGroup(1, this.extent_);
|
const replayGroup = new WebGLReplayGroup(1, this.extent_);
|
||||||
const replay = /** @type {import("./PolygonReplay.js").default} */ (
|
const replay = /** @type {import("./PolygonReplay.js").default} */ (
|
||||||
replayGroup.getReplay(0, ReplayType.POLYGON));
|
replayGroup.getBuilder(0, ReplayType.POLYGON));
|
||||||
replay.setFillStrokeStyle(this.fillStyle_, this.strokeStyle_);
|
replay.setFillStrokeStyle(this.fillStyle_, this.strokeStyle_);
|
||||||
replay.drawMultiPolygon(geometry, data);
|
replay.drawMultiPolygon(geometry, data);
|
||||||
replay.finish(context);
|
replay.finish(context);
|
||||||
@@ -348,7 +348,7 @@ class WebGLImmediateRenderer extends VectorContext {
|
|||||||
const context = this.context_;
|
const context = this.context_;
|
||||||
const replayGroup = new WebGLReplayGroup(1, this.extent_);
|
const replayGroup = new WebGLReplayGroup(1, this.extent_);
|
||||||
const replay = /** @type {import("./CircleReplay.js").default} */ (
|
const replay = /** @type {import("./CircleReplay.js").default} */ (
|
||||||
replayGroup.getReplay(0, ReplayType.CIRCLE));
|
replayGroup.getBuilder(0, ReplayType.CIRCLE));
|
||||||
replay.setFillStrokeStyle(this.fillStyle_, this.strokeStyle_);
|
replay.setFillStrokeStyle(this.fillStyle_, this.strokeStyle_);
|
||||||
replay.drawCircle(geometry, data);
|
replay.drawCircle(geometry, data);
|
||||||
replay.finish(context);
|
replay.finish(context);
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {numberSafeCompareFunction} from '../../array.js';
|
|||||||
import {buffer, createOrUpdateFromCoordinate} from '../../extent.js';
|
import {buffer, createOrUpdateFromCoordinate} from '../../extent.js';
|
||||||
import {isEmpty} from '../../obj.js';
|
import {isEmpty} from '../../obj.js';
|
||||||
import {ORDER} from '../replay.js';
|
import {ORDER} from '../replay.js';
|
||||||
import ReplayGroup from '../ReplayGroup.js';
|
import ReplayGroup from '../BuilderGroup.js';
|
||||||
import WebGLCircleReplay from './CircleReplay.js';
|
import WebGLCircleReplay from './CircleReplay.js';
|
||||||
import WebGLImageReplay from './ImageReplay.js';
|
import WebGLImageReplay from './ImageReplay.js';
|
||||||
import WebGLLineStringReplay from './LineStringReplay.js';
|
import WebGLLineStringReplay from './LineStringReplay.js';
|
||||||
@@ -113,7 +113,7 @@ class WebGLReplayGroup extends ReplayGroup {
|
|||||||
/**
|
/**
|
||||||
* @inheritDoc
|
* @inheritDoc
|
||||||
*/
|
*/
|
||||||
getReplay(zIndex, replayType) {
|
getBuilder(zIndex, replayType) {
|
||||||
const zIndexKey = zIndex !== undefined ? zIndex.toString() : '0';
|
const zIndexKey = zIndex !== undefined ? zIndex.toString() : '0';
|
||||||
let replays = this.replaysByZIndex_[zIndexKey];
|
let replays = this.replaysByZIndex_[zIndexKey];
|
||||||
if (replays === undefined) {
|
if (replays === undefined) {
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ import rbush from 'rbush';
|
|||||||
import {buffer, createEmpty, containsExtent, getWidth} from '../../extent.js';
|
import {buffer, createEmpty, containsExtent, getWidth} from '../../extent.js';
|
||||||
import RenderEventType from '../../render/EventType.js';
|
import RenderEventType from '../../render/EventType.js';
|
||||||
import {labelCache, rotateAtOffset} from '../../render/canvas.js';
|
import {labelCache, rotateAtOffset} from '../../render/canvas.js';
|
||||||
import CanvasReplayGroup from '../../render/canvas/ReplayGroup.js';
|
import CanvasBuilderGroup from '../../render/canvas/BuilderGroup.js';
|
||||||
|
import InstructionsGroupExecutor from '../../render/canvas/ExecutorGroup.js';
|
||||||
import CanvasLayerRenderer from './Layer.js';
|
import CanvasLayerRenderer from './Layer.js';
|
||||||
import {defaultOrder as defaultRenderOrder, getTolerance as getRenderTolerance, getSquaredTolerance as getSquaredRenderTolerance, renderFeature} from '../vector.js';
|
import {defaultOrder as defaultRenderOrder, getTolerance as getRenderTolerance, getSquaredTolerance as getSquaredRenderTolerance, renderFeature} from '../vector.js';
|
||||||
|
|
||||||
@@ -66,7 +67,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
* @type {import("../../render/canvas/ReplayGroup.js").default}
|
* @type {import("../../render/canvas/ExecutorGroup").default}
|
||||||
*/
|
*/
|
||||||
this.replayGroup_ = null;
|
this.replayGroup_ = null;
|
||||||
|
|
||||||
@@ -165,7 +166,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
|
|||||||
const halfWidth = (frameState.size[0] * pixelRatio) / 2;
|
const halfWidth = (frameState.size[0] * pixelRatio) / 2;
|
||||||
const halfHeight = (frameState.size[1] * pixelRatio) / 2;
|
const halfHeight = (frameState.size[1] * pixelRatio) / 2;
|
||||||
rotateAtOffset(replayContext, -rotation, halfWidth, halfHeight);
|
rotateAtOffset(replayContext, -rotation, halfWidth, halfHeight);
|
||||||
replayGroup.replay(replayContext, transform, rotation, skippedFeatureUids, snapToPixel);
|
replayGroup.execute(replayContext, transform, rotation, skippedFeatureUids, snapToPixel);
|
||||||
if (vectorSource.getWrapX() && projection.canWrapX() &&
|
if (vectorSource.getWrapX() && projection.canWrapX() &&
|
||||||
!containsExtent(projectionExtent, extent)) {
|
!containsExtent(projectionExtent, extent)) {
|
||||||
let startX = extent[0];
|
let startX = extent[0];
|
||||||
@@ -176,7 +177,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
|
|||||||
--world;
|
--world;
|
||||||
offsetX = worldWidth * world;
|
offsetX = worldWidth * world;
|
||||||
transform = this.getTransform(frameState, offsetX);
|
transform = this.getTransform(frameState, offsetX);
|
||||||
replayGroup.replay(replayContext, transform, rotation, skippedFeatureUids, snapToPixel);
|
replayGroup.execute(replayContext, transform, rotation, skippedFeatureUids, snapToPixel);
|
||||||
startX += worldWidth;
|
startX += worldWidth;
|
||||||
}
|
}
|
||||||
world = 0;
|
world = 0;
|
||||||
@@ -185,7 +186,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
|
|||||||
++world;
|
++world;
|
||||||
offsetX = worldWidth * world;
|
offsetX = worldWidth * world;
|
||||||
transform = this.getTransform(frameState, offsetX);
|
transform = this.getTransform(frameState, offsetX);
|
||||||
replayGroup.replay(replayContext, transform, rotation, skippedFeatureUids, snapToPixel);
|
replayGroup.execute(replayContext, transform, rotation, skippedFeatureUids, snapToPixel);
|
||||||
startX -= worldWidth;
|
startX -= worldWidth;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -279,7 +280,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
|
|||||||
|
|
||||||
let transform = this.getRenderTransform(frameState, width, height, 0);
|
let transform = this.getRenderTransform(frameState, width, height, 0);
|
||||||
const skippedFeatureUids = layerState.managed ? frameState.skippedFeatureUids : {};
|
const skippedFeatureUids = layerState.managed ? frameState.skippedFeatureUids : {};
|
||||||
replayGroup.replay(context, transform, rotation, skippedFeatureUids, snapToPixel);
|
replayGroup.execute(context, transform, rotation, skippedFeatureUids, snapToPixel);
|
||||||
|
|
||||||
if (vectorSource.getWrapX() && projection.canWrapX() && !containsExtent(projectionExtent, extent)) {
|
if (vectorSource.getWrapX() && projection.canWrapX() && !containsExtent(projectionExtent, extent)) {
|
||||||
let startX = extent[0];
|
let startX = extent[0];
|
||||||
@@ -290,7 +291,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
|
|||||||
--world;
|
--world;
|
||||||
offsetX = worldWidth * world;
|
offsetX = worldWidth * world;
|
||||||
transform = this.getRenderTransform(frameState, width, height, offsetX);
|
transform = this.getRenderTransform(frameState, width, height, offsetX);
|
||||||
replayGroup.replay(context, transform, rotation, skippedFeatureUids, snapToPixel);
|
replayGroup.execute(context, transform, rotation, skippedFeatureUids, snapToPixel);
|
||||||
startX += worldWidth;
|
startX += worldWidth;
|
||||||
}
|
}
|
||||||
world = 0;
|
world = 0;
|
||||||
@@ -299,7 +300,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
|
|||||||
++world;
|
++world;
|
||||||
offsetX = worldWidth * world;
|
offsetX = worldWidth * world;
|
||||||
transform = this.getRenderTransform(frameState, width, height, offsetX);
|
transform = this.getRenderTransform(frameState, width, height, offsetX);
|
||||||
replayGroup.replay(context, transform, rotation, skippedFeatureUids, snapToPixel);
|
replayGroup.execute(context, transform, rotation, skippedFeatureUids, snapToPixel);
|
||||||
startX -= worldWidth;
|
startX -= worldWidth;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -443,7 +444,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
|
|||||||
|
|
||||||
this.dirty_ = false;
|
this.dirty_ = false;
|
||||||
|
|
||||||
const replayGroup = new CanvasReplayGroup(
|
const replayGroup = new CanvasBuilderGroup(
|
||||||
getRenderTolerance(resolution, pixelRatio), extent, resolution,
|
getRenderTolerance(resolution, pixelRatio), extent, resolution,
|
||||||
pixelRatio, vectorSource.getOverlaps(), this.declutterTree_, vectorLayer.getRenderBuffer());
|
pixelRatio, vectorSource.getOverlaps(), this.declutterTree_, vectorLayer.getRenderBuffer());
|
||||||
vectorSource.loadFeatures(extent, resolution, projection);
|
vectorSource.loadFeatures(extent, resolution, projection);
|
||||||
@@ -480,13 +481,18 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
|
|||||||
} else {
|
} else {
|
||||||
vectorSource.forEachFeatureInExtent(extent, render);
|
vectorSource.forEachFeatureInExtent(extent, render);
|
||||||
}
|
}
|
||||||
replayGroup.finish();
|
|
||||||
|
const replayGroupInstructions = replayGroup.finish();
|
||||||
|
const renderingExecutorGroup = new InstructionsGroupExecutor(
|
||||||
|
getRenderTolerance(resolution, pixelRatio), extent, resolution,
|
||||||
|
pixelRatio, vectorSource.getOverlaps(), this.declutterTree_,
|
||||||
|
replayGroupInstructions, vectorLayer.getRenderBuffer());
|
||||||
|
|
||||||
this.renderedResolution_ = resolution;
|
this.renderedResolution_ = resolution;
|
||||||
this.renderedRevision_ = vectorLayerRevision;
|
this.renderedRevision_ = vectorLayerRevision;
|
||||||
this.renderedRenderOrder_ = vectorLayerRenderOrder;
|
this.renderedRenderOrder_ = vectorLayerRenderOrder;
|
||||||
this.renderedExtent_ = extent;
|
this.renderedExtent_ = extent;
|
||||||
this.replayGroup_ = replayGroup;
|
this.replayGroup_ = renderingExecutorGroup;
|
||||||
|
|
||||||
this.replayGroupChanged = true;
|
this.replayGroupChanged = true;
|
||||||
return true;
|
return true;
|
||||||
@@ -497,10 +503,10 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
|
|||||||
* @param {number} resolution Resolution.
|
* @param {number} resolution Resolution.
|
||||||
* @param {number} pixelRatio Pixel ratio.
|
* @param {number} pixelRatio Pixel ratio.
|
||||||
* @param {import("../../style/Style.js").default|Array<import("../../style/Style.js").default>} styles The style or array of styles.
|
* @param {import("../../style/Style.js").default|Array<import("../../style/Style.js").default>} styles The style or array of styles.
|
||||||
* @param {import("../../render/canvas/ReplayGroup.js").default} replayGroup Replay group.
|
* @param {import("../../render/canvas/BuilderGroup.js").default} builderGroup Builder group.
|
||||||
* @return {boolean} `true` if an image is loading.
|
* @return {boolean} `true` if an image is loading.
|
||||||
*/
|
*/
|
||||||
renderFeature(feature, resolution, pixelRatio, styles, replayGroup) {
|
renderFeature(feature, resolution, pixelRatio, styles, builderGroup) {
|
||||||
if (!styles) {
|
if (!styles) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -508,13 +514,13 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
|
|||||||
if (Array.isArray(styles)) {
|
if (Array.isArray(styles)) {
|
||||||
for (let i = 0, ii = styles.length; i < ii; ++i) {
|
for (let i = 0, ii = styles.length; i < ii; ++i) {
|
||||||
loading = renderFeature(
|
loading = renderFeature(
|
||||||
replayGroup, feature, styles[i],
|
builderGroup, feature, styles[i],
|
||||||
getSquaredRenderTolerance(resolution, pixelRatio),
|
getSquaredRenderTolerance(resolution, pixelRatio),
|
||||||
this.handleStyleImageChange_, this) || loading;
|
this.handleStyleImageChange_, this) || loading;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
loading = renderFeature(
|
loading = renderFeature(
|
||||||
replayGroup, feature, styles,
|
builderGroup, feature, styles,
|
||||||
getSquaredRenderTolerance(resolution, pixelRatio),
|
getSquaredRenderTolerance(resolution, pixelRatio),
|
||||||
this.handleStyleImageChange_, this);
|
this.handleStyleImageChange_, this);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import {equivalent as equivalentProjection} from '../../proj.js';
|
|||||||
import Units from '../../proj/Units.js';
|
import Units from '../../proj/Units.js';
|
||||||
import ReplayType from '../../render/ReplayType.js';
|
import ReplayType from '../../render/ReplayType.js';
|
||||||
import {labelCache, rotateAtOffset} from '../../render/canvas.js';
|
import {labelCache, rotateAtOffset} from '../../render/canvas.js';
|
||||||
import CanvasReplayGroup, {replayDeclutter} from '../../render/canvas/ReplayGroup.js';
|
import CanvasBuilderGroup from '../../render/canvas/BuilderGroup.js';
|
||||||
import {ORDER} from '../../render/replay.js';
|
import {ORDER} from '../../render/replay.js';
|
||||||
import CanvasTileLayerRenderer from './TileLayer.js';
|
import CanvasTileLayerRenderer from './TileLayer.js';
|
||||||
import {getSquaredTolerance as getSquaredRenderTolerance, renderFeature} from '../vector.js';
|
import {getSquaredTolerance as getSquaredRenderTolerance, renderFeature} from '../vector.js';
|
||||||
@@ -24,6 +24,7 @@ import {
|
|||||||
scale as scaleTransform,
|
scale as scaleTransform,
|
||||||
translate as translateTransform
|
translate as translateTransform
|
||||||
} from '../../transform.js';
|
} from '../../transform.js';
|
||||||
|
import CanvasExecutorGroup, {replayDeclutter} from '../../render/canvas/ExecutorGroup.js';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -107,7 +108,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
|||||||
getTile(z, x, y, pixelRatio, projection) {
|
getTile(z, x, y, pixelRatio, projection) {
|
||||||
const tile = super.getTile(z, x, y, pixelRatio, projection);
|
const tile = super.getTile(z, x, y, pixelRatio, projection);
|
||||||
if (tile.getState() === TileState.LOADED) {
|
if (tile.getState() === TileState.LOADED) {
|
||||||
this.createReplayGroup_(/** @type {import("../../VectorImageTile.js").default} */ (tile), pixelRatio, projection);
|
this.createExecutorGroup_(/** @type {import("../../VectorImageTile.js").default} */ (tile), pixelRatio, projection);
|
||||||
if (this.context) {
|
if (this.context) {
|
||||||
this.renderTileImage_(/** @type {import("../../VectorImageTile.js").default} */ (tile), pixelRatio, projection);
|
this.renderTileImage_(/** @type {import("../../VectorImageTile.js").default} */ (tile), pixelRatio, projection);
|
||||||
}
|
}
|
||||||
@@ -142,14 +143,14 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
|||||||
* @param {import("../../proj/Projection.js").default} projection Projection.
|
* @param {import("../../proj/Projection.js").default} projection Projection.
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
createReplayGroup_(tile, pixelRatio, projection) {
|
createExecutorGroup_(tile, pixelRatio, projection) {
|
||||||
const layer = /** @type {import("../../layer/Vector.js").default} */ (this.getLayer());
|
const layer = /** @type {import("../../layer/Vector.js").default} */ (this.getLayer());
|
||||||
const revision = layer.getRevision();
|
const revision = layer.getRevision();
|
||||||
const renderOrder = /** @type {import("../../render.js").OrderFunction} */ (layer.getRenderOrder()) || null;
|
const renderOrder = /** @type {import("../../render.js").OrderFunction} */ (layer.getRenderOrder()) || null;
|
||||||
|
|
||||||
const replayState = tile.getReplayState(layer);
|
const builderState = tile.getReplayState(layer);
|
||||||
if (!replayState.dirty && replayState.renderedRevision == revision &&
|
if (!builderState.dirty && builderState.renderedRevision == revision &&
|
||||||
replayState.renderedRenderOrder == renderOrder) {
|
builderState.renderedRenderOrder == renderOrder) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,10 +167,10 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (tile.useLoadedOnly) {
|
if (tile.useLoadedOnly) {
|
||||||
const lowResReplayGroup = sourceTile.getLowResReplayGroup(layer, zoom, tileExtent);
|
const lowResExecutorGroup = sourceTile.getLowResExecutorGroup(layer, zoom, tileExtent);
|
||||||
if (lowResReplayGroup) {
|
if (lowResExecutorGroup) {
|
||||||
// reuse existing replay if we're rendering an interim tile
|
// reuse existing replay if we're rendering an interim tile
|
||||||
sourceTile.setReplayGroup(layer, tile.tileCoord.toString(), lowResReplayGroup);
|
sourceTile.setExecutorGroup(layer, tile.tileCoord.toString(), lowResExecutorGroup);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -185,8 +186,8 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
|||||||
reproject = true;
|
reproject = true;
|
||||||
sourceTile.setProjection(projection);
|
sourceTile.setProjection(projection);
|
||||||
}
|
}
|
||||||
replayState.dirty = false;
|
builderState.dirty = false;
|
||||||
const replayGroup = new CanvasReplayGroup(0, sharedExtent, resolution,
|
const builderGroup = new CanvasBuilderGroup(0, sharedExtent, resolution,
|
||||||
pixelRatio, source.getOverlaps(), this.declutterTree_, layer.getRenderBuffer());
|
pixelRatio, source.getOverlaps(), this.declutterTree_, layer.getRenderBuffer());
|
||||||
const squaredTolerance = getSquaredRenderTolerance(resolution, pixelRatio);
|
const squaredTolerance = getSquaredRenderTolerance(resolution, pixelRatio);
|
||||||
|
|
||||||
@@ -201,14 +202,14 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
|||||||
styles = styleFunction(feature, resolution);
|
styles = styleFunction(feature, resolution);
|
||||||
}
|
}
|
||||||
if (styles) {
|
if (styles) {
|
||||||
const dirty = this.renderFeature(feature, squaredTolerance, styles, replayGroup);
|
const dirty = this.renderFeature(feature, squaredTolerance, styles, builderGroup);
|
||||||
this.dirty_ = this.dirty_ || dirty;
|
this.dirty_ = this.dirty_ || dirty;
|
||||||
replayState.dirty = replayState.dirty || dirty;
|
builderState.dirty = builderState.dirty || dirty;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const features = sourceTile.getFeatures();
|
const features = sourceTile.getFeatures();
|
||||||
if (renderOrder && renderOrder !== replayState.renderedRenderOrder) {
|
if (renderOrder && renderOrder !== builderState.renderedRenderOrder) {
|
||||||
features.sort(renderOrder);
|
features.sort(renderOrder);
|
||||||
}
|
}
|
||||||
for (let i = 0, ii = features.length; i < ii; ++i) {
|
for (let i = 0, ii = features.length; i < ii; ++i) {
|
||||||
@@ -226,11 +227,13 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
|||||||
render.call(this, feature);
|
render.call(this, feature);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
replayGroup.finish();
|
const replayGroupInstructions = builderGroup.finish();
|
||||||
sourceTile.setReplayGroup(layer, tile.tileCoord.toString(), replayGroup);
|
const renderingReplayGroup = new CanvasExecutorGroup(0, sharedExtent, resolution,
|
||||||
|
pixelRatio, source.getOverlaps(), this.declutterTree_, replayGroupInstructions, layer.getRenderBuffer());
|
||||||
|
sourceTile.setExecutorGroup(layer, tile.tileCoord.toString(), renderingReplayGroup);
|
||||||
}
|
}
|
||||||
replayState.renderedRevision = revision;
|
builderState.renderedRevision = revision;
|
||||||
replayState.renderedRenderOrder = renderOrder;
|
builderState.renderedRenderOrder = renderOrder;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -259,9 +262,9 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
|||||||
if (sourceTile.getState() != TileState.LOADED) {
|
if (sourceTile.getState() != TileState.LOADED) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const replayGroup = /** @type {CanvasReplayGroup} */ (sourceTile.getReplayGroup(layer,
|
const executorGroup = /** @type {CanvasExecutorGroup} */ (sourceTile.getExecutorGroup(layer,
|
||||||
tile.tileCoord.toString()));
|
tile.tileCoord.toString()));
|
||||||
found = found || replayGroup.forEachFeatureAtCoordinate(coordinate, resolution, rotation, hitTolerance, {},
|
found = found || executorGroup.forEachFeatureAtCoordinate(coordinate, resolution, rotation, hitTolerance, {},
|
||||||
/**
|
/**
|
||||||
* @param {import("../../Feature.js").FeatureLike} feature Feature.
|
* @param {import("../../Feature.js").FeatureLike} feature Feature.
|
||||||
* @return {?} Callback result.
|
* @return {?} Callback result.
|
||||||
@@ -367,8 +370,8 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
|||||||
if (sourceTile.getState() != TileState.LOADED) {
|
if (sourceTile.getState() != TileState.LOADED) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const replayGroup = /** @type {CanvasReplayGroup} */ (sourceTile.getReplayGroup(layer, tileCoord.toString()));
|
const executorGroup = /** @type {CanvasExecutorGroup} */ (sourceTile.getExecutorGroup(layer, tileCoord.toString()));
|
||||||
if (!replayGroup || !replayGroup.hasReplays(replayTypes)) {
|
if (!executorGroup || !executorGroup.hasExecutors(replayTypes)) {
|
||||||
// sourceTile was not yet loaded when this.createReplayGroup_() was
|
// sourceTile was not yet loaded when this.createReplayGroup_() was
|
||||||
// called, or it has no replays of the types we want to render
|
// called, or it has no replays of the types we want to render
|
||||||
continue;
|
continue;
|
||||||
@@ -377,7 +380,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
|||||||
transform = this.getTransform(frameState, worldOffset);
|
transform = this.getTransform(frameState, worldOffset);
|
||||||
}
|
}
|
||||||
const currentZ = sourceTile.tileCoord[0];
|
const currentZ = sourceTile.tileCoord[0];
|
||||||
const currentClip = replayGroup.getClipCoords(transform);
|
const currentClip = executorGroup.getClipCoords(transform);
|
||||||
context.save();
|
context.save();
|
||||||
context.globalAlpha = layerState.opacity;
|
context.globalAlpha = layerState.opacity;
|
||||||
// Create a clip mask for regions in this low resolution tile that are
|
// Create a clip mask for regions in this low resolution tile that are
|
||||||
@@ -399,7 +402,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
|||||||
context.clip();
|
context.clip();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
replayGroup.replay(context, transform, rotation, {}, snapToPixel, replayTypes, declutterReplays);
|
executorGroup.execute(context, transform, rotation, {}, snapToPixel, replayTypes, declutterReplays);
|
||||||
context.restore();
|
context.restore();
|
||||||
clips.push(currentClip);
|
clips.push(currentClip);
|
||||||
zs.push(currentZ);
|
zs.push(currentZ);
|
||||||
@@ -420,7 +423,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
|||||||
* @param {import("../../Feature.js").FeatureLike} feature Feature.
|
* @param {import("../../Feature.js").FeatureLike} feature Feature.
|
||||||
* @param {number} squaredTolerance Squared tolerance.
|
* @param {number} squaredTolerance Squared tolerance.
|
||||||
* @param {import("../../style/Style.js").default|Array<import("../../style/Style.js").default>} styles The style or array of styles.
|
* @param {import("../../style/Style.js").default|Array<import("../../style/Style.js").default>} styles The style or array of styles.
|
||||||
* @param {import("../../render/canvas/ReplayGroup.js").default} replayGroup Replay group.
|
* @param {import("../../render/canvas/BuilderGroup.js").default} replayGroup Replay group.
|
||||||
* @return {boolean} `true` if an image is loading.
|
* @return {boolean} `true` if an image is loading.
|
||||||
*/
|
*/
|
||||||
renderFeature(feature, squaredTolerance, styles, replayGroup) {
|
renderFeature(feature, squaredTolerance, styles, replayGroup) {
|
||||||
@@ -474,9 +477,9 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
|||||||
const transform = resetTransform(this.tmpTransform_);
|
const transform = resetTransform(this.tmpTransform_);
|
||||||
scaleTransform(transform, pixelScale, -pixelScale);
|
scaleTransform(transform, pixelScale, -pixelScale);
|
||||||
translateTransform(transform, -tileExtent[0], -tileExtent[3]);
|
translateTransform(transform, -tileExtent[0], -tileExtent[3]);
|
||||||
const replayGroup = /** @type {CanvasReplayGroup} */ (sourceTile.getReplayGroup(layer,
|
const executorGroup = /** @type {CanvasExecutorGroup} */ (sourceTile.getExecutorGroup(layer,
|
||||||
tile.tileCoord.toString()));
|
tile.tileCoord.toString()));
|
||||||
replayGroup.replay(context, transform, 0, {}, true, replays);
|
executorGroup.execute(context, transform, 0, {}, true, replays);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+44
-44
@@ -4,7 +4,7 @@
|
|||||||
import {getUid} from '../util.js';
|
import {getUid} from '../util.js';
|
||||||
import ImageState from '../ImageState.js';
|
import ImageState from '../ImageState.js';
|
||||||
import GeometryType from '../geom/GeometryType.js';
|
import GeometryType from '../geom/GeometryType.js';
|
||||||
import ReplayType from '../render/ReplayType.js';
|
import BuilderType from '../render/ReplayType.js';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -17,7 +17,7 @@ const SIMPLIFY_TOLERANCE = 0.5;
|
|||||||
/**
|
/**
|
||||||
* @const
|
* @const
|
||||||
* @type {Object<import("../geom/GeometryType.js").default,
|
* @type {Object<import("../geom/GeometryType.js").default,
|
||||||
* function(import("../render/ReplayGroup.js").default, import("../geom/Geometry.js").default,
|
* function(import("../render/BuilderGroup.js").default, import("../geom/Geometry.js").default,
|
||||||
* import("../style/Style.js").default, Object)>}
|
* import("../style/Style.js").default, Object)>}
|
||||||
*/
|
*/
|
||||||
const GEOMETRY_RENDERERS = {
|
const GEOMETRY_RENDERERS = {
|
||||||
@@ -64,30 +64,30 @@ export function getTolerance(resolution, pixelRatio) {
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {import("../render/ReplayGroup.js").default} replayGroup Replay group.
|
* @param {import("../render/BuilderGroup.js").default} builderGroup Builder group.
|
||||||
* @param {import("../geom/Circle.js").default} geometry Geometry.
|
* @param {import("../geom/Circle.js").default} geometry Geometry.
|
||||||
* @param {import("../style/Style.js").default} style Style.
|
* @param {import("../style/Style.js").default} style Style.
|
||||||
* @param {import("../Feature.js").default} feature Feature.
|
* @param {import("../Feature.js").default} feature Feature.
|
||||||
*/
|
*/
|
||||||
function renderCircleGeometry(replayGroup, geometry, style, feature) {
|
function renderCircleGeometry(builderGroup, geometry, style, feature) {
|
||||||
const fillStyle = style.getFill();
|
const fillStyle = style.getFill();
|
||||||
const strokeStyle = style.getStroke();
|
const strokeStyle = style.getStroke();
|
||||||
if (fillStyle || strokeStyle) {
|
if (fillStyle || strokeStyle) {
|
||||||
const circleReplay = replayGroup.getReplay(style.getZIndex(), ReplayType.CIRCLE);
|
const circleReplay = builderGroup.getBuilder(style.getZIndex(), BuilderType.CIRCLE);
|
||||||
circleReplay.setFillStrokeStyle(fillStyle, strokeStyle);
|
circleReplay.setFillStrokeStyle(fillStyle, strokeStyle);
|
||||||
circleReplay.drawCircle(geometry, feature);
|
circleReplay.drawCircle(geometry, feature);
|
||||||
}
|
}
|
||||||
const textStyle = style.getText();
|
const textStyle = style.getText();
|
||||||
if (textStyle) {
|
if (textStyle) {
|
||||||
const textReplay = replayGroup.getReplay(style.getZIndex(), ReplayType.TEXT);
|
const textReplay = builderGroup.getBuilder(style.getZIndex(), BuilderType.TEXT);
|
||||||
textReplay.setTextStyle(textStyle, replayGroup.addDeclutter(false));
|
textReplay.setTextStyle(textStyle, builderGroup.addDeclutter(false));
|
||||||
textReplay.drawText(geometry, feature);
|
textReplay.drawText(geometry, feature);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {import("../render/ReplayGroup.js").default} replayGroup Replay group.
|
* @param {import("../render/BuilderGroup.js").default} replayGroup Replay group.
|
||||||
* @param {import("../Feature.js").FeatureLike} feature Feature.
|
* @param {import("../Feature.js").FeatureLike} feature Feature.
|
||||||
* @param {import("../style/Style.js").default} style Style.
|
* @param {import("../style/Style.js").default} style Style.
|
||||||
* @param {number} squaredTolerance Squared tolerance.
|
* @param {number} squaredTolerance Squared tolerance.
|
||||||
@@ -119,7 +119,7 @@ export function renderFeature(replayGroup, feature, style, squaredTolerance, lis
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {import("../render/ReplayGroup.js").default} replayGroup Replay group.
|
* @param {import("../render/BuilderGroup.js").default} replayGroup Replay group.
|
||||||
* @param {import("../Feature.js").FeatureLike} feature Feature.
|
* @param {import("../Feature.js").FeatureLike} feature Feature.
|
||||||
* @param {import("../style/Style.js").default} style Style.
|
* @param {import("../style/Style.js").default} style Style.
|
||||||
* @param {number} squaredTolerance Squared tolerance.
|
* @param {number} squaredTolerance Squared tolerance.
|
||||||
@@ -141,7 +141,7 @@ function renderFeatureInternal(replayGroup, feature, style, squaredTolerance) {
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {import("../render/ReplayGroup.js").default} replayGroup Replay group.
|
* @param {import("../render/BuilderGroup.js").default} replayGroup Replay group.
|
||||||
* @param {import("../geom/Geometry.js").default|import("../render/Feature.js").default} geometry Geometry.
|
* @param {import("../geom/Geometry.js").default|import("../render/Feature.js").default} geometry Geometry.
|
||||||
* @param {import("../style/Style.js").default} style Style.
|
* @param {import("../style/Style.js").default} style Style.
|
||||||
* @param {import("../Feature.js").FeatureLike} feature Feature.
|
* @param {import("../Feature.js").FeatureLike} feature Feature.
|
||||||
@@ -154,13 +154,13 @@ function renderGeometry(replayGroup, geometry, style, feature) {
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const replay = replayGroup.getReplay(style.getZIndex(), ReplayType.DEFAULT);
|
const replay = replayGroup.getBuilder(style.getZIndex(), BuilderType.DEFAULT);
|
||||||
replay.drawCustom(/** @type {import("../geom/SimpleGeometry.js").default} */ (geometry), feature, style.getRenderer());
|
replay.drawCustom(/** @type {import("../geom/SimpleGeometry.js").default} */ (geometry), feature, style.getRenderer());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {import("../render/ReplayGroup.js").default} replayGroup Replay group.
|
* @param {import("../render/BuilderGroup.js").default} replayGroup Replay group.
|
||||||
* @param {import("../geom/GeometryCollection.js").default} geometry Geometry.
|
* @param {import("../geom/GeometryCollection.js").default} geometry Geometry.
|
||||||
* @param {import("../style/Style.js").default} style Style.
|
* @param {import("../style/Style.js").default} style Style.
|
||||||
* @param {import("../Feature.js").default} feature Feature.
|
* @param {import("../Feature.js").default} feature Feature.
|
||||||
@@ -177,140 +177,140 @@ function renderGeometryCollectionGeometry(replayGroup, geometry, style, feature)
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {import("../render/ReplayGroup.js").default} replayGroup Replay group.
|
* @param {import("../render/BuilderGroup.js").default} builderGroup Replay group.
|
||||||
* @param {import("../geom/LineString.js").default|import("../render/Feature.js").default} geometry Geometry.
|
* @param {import("../geom/LineString.js").default|import("../render/Feature.js").default} geometry Geometry.
|
||||||
* @param {import("../style/Style.js").default} style Style.
|
* @param {import("../style/Style.js").default} style Style.
|
||||||
* @param {import("../Feature.js").FeatureLike} feature Feature.
|
* @param {import("../Feature.js").FeatureLike} feature Feature.
|
||||||
*/
|
*/
|
||||||
function renderLineStringGeometry(replayGroup, geometry, style, feature) {
|
function renderLineStringGeometry(builderGroup, geometry, style, feature) {
|
||||||
const strokeStyle = style.getStroke();
|
const strokeStyle = style.getStroke();
|
||||||
if (strokeStyle) {
|
if (strokeStyle) {
|
||||||
const lineStringReplay = replayGroup.getReplay(style.getZIndex(), ReplayType.LINE_STRING);
|
const lineStringReplay = builderGroup.getBuilder(style.getZIndex(), BuilderType.LINE_STRING);
|
||||||
lineStringReplay.setFillStrokeStyle(null, strokeStyle);
|
lineStringReplay.setFillStrokeStyle(null, strokeStyle);
|
||||||
lineStringReplay.drawLineString(geometry, feature);
|
lineStringReplay.drawLineString(geometry, feature);
|
||||||
}
|
}
|
||||||
const textStyle = style.getText();
|
const textStyle = style.getText();
|
||||||
if (textStyle) {
|
if (textStyle) {
|
||||||
const textReplay = replayGroup.getReplay(style.getZIndex(), ReplayType.TEXT);
|
const textReplay = builderGroup.getBuilder(style.getZIndex(), BuilderType.TEXT);
|
||||||
textReplay.setTextStyle(textStyle, replayGroup.addDeclutter(false));
|
textReplay.setTextStyle(textStyle, builderGroup.addDeclutter(false));
|
||||||
textReplay.drawText(geometry, feature);
|
textReplay.drawText(geometry, feature);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {import("../render/ReplayGroup.js").default} replayGroup Replay group.
|
* @param {import("../render/BuilderGroup.js").default} builderGroup Replay group.
|
||||||
* @param {import("../geom/MultiLineString.js").default|import("../render/Feature.js").default} geometry Geometry.
|
* @param {import("../geom/MultiLineString.js").default|import("../render/Feature.js").default} geometry Geometry.
|
||||||
* @param {import("../style/Style.js").default} style Style.
|
* @param {import("../style/Style.js").default} style Style.
|
||||||
* @param {import("../Feature.js").FeatureLike} feature Feature.
|
* @param {import("../Feature.js").FeatureLike} feature Feature.
|
||||||
*/
|
*/
|
||||||
function renderMultiLineStringGeometry(replayGroup, geometry, style, feature) {
|
function renderMultiLineStringGeometry(builderGroup, geometry, style, feature) {
|
||||||
const strokeStyle = style.getStroke();
|
const strokeStyle = style.getStroke();
|
||||||
if (strokeStyle) {
|
if (strokeStyle) {
|
||||||
const lineStringReplay = replayGroup.getReplay(style.getZIndex(), ReplayType.LINE_STRING);
|
const lineStringReplay = builderGroup.getBuilder(style.getZIndex(), BuilderType.LINE_STRING);
|
||||||
lineStringReplay.setFillStrokeStyle(null, strokeStyle);
|
lineStringReplay.setFillStrokeStyle(null, strokeStyle);
|
||||||
lineStringReplay.drawMultiLineString(geometry, feature);
|
lineStringReplay.drawMultiLineString(geometry, feature);
|
||||||
}
|
}
|
||||||
const textStyle = style.getText();
|
const textStyle = style.getText();
|
||||||
if (textStyle) {
|
if (textStyle) {
|
||||||
const textReplay = replayGroup.getReplay(style.getZIndex(), ReplayType.TEXT);
|
const textReplay = builderGroup.getBuilder(style.getZIndex(), BuilderType.TEXT);
|
||||||
textReplay.setTextStyle(textStyle, replayGroup.addDeclutter(false));
|
textReplay.setTextStyle(textStyle, builderGroup.addDeclutter(false));
|
||||||
textReplay.drawText(geometry, feature);
|
textReplay.drawText(geometry, feature);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {import("../render/ReplayGroup.js").default} replayGroup Replay group.
|
* @param {import("../render/BuilderGroup.js").default} builderGroup Replay group.
|
||||||
* @param {import("../geom/MultiPolygon.js").default} geometry Geometry.
|
* @param {import("../geom/MultiPolygon.js").default} geometry Geometry.
|
||||||
* @param {import("../style/Style.js").default} style Style.
|
* @param {import("../style/Style.js").default} style Style.
|
||||||
* @param {import("../Feature.js").default} feature Feature.
|
* @param {import("../Feature.js").default} feature Feature.
|
||||||
*/
|
*/
|
||||||
function renderMultiPolygonGeometry(replayGroup, geometry, style, feature) {
|
function renderMultiPolygonGeometry(builderGroup, geometry, style, feature) {
|
||||||
const fillStyle = style.getFill();
|
const fillStyle = style.getFill();
|
||||||
const strokeStyle = style.getStroke();
|
const strokeStyle = style.getStroke();
|
||||||
if (strokeStyle || fillStyle) {
|
if (strokeStyle || fillStyle) {
|
||||||
const polygonReplay = replayGroup.getReplay(style.getZIndex(), ReplayType.POLYGON);
|
const polygonReplay = builderGroup.getBuilder(style.getZIndex(), BuilderType.POLYGON);
|
||||||
polygonReplay.setFillStrokeStyle(fillStyle, strokeStyle);
|
polygonReplay.setFillStrokeStyle(fillStyle, strokeStyle);
|
||||||
polygonReplay.drawMultiPolygon(geometry, feature);
|
polygonReplay.drawMultiPolygon(geometry, feature);
|
||||||
}
|
}
|
||||||
const textStyle = style.getText();
|
const textStyle = style.getText();
|
||||||
if (textStyle) {
|
if (textStyle) {
|
||||||
const textReplay = replayGroup.getReplay(style.getZIndex(), ReplayType.TEXT);
|
const textReplay = builderGroup.getBuilder(style.getZIndex(), BuilderType.TEXT);
|
||||||
textReplay.setTextStyle(textStyle, replayGroup.addDeclutter(false));
|
textReplay.setTextStyle(textStyle, builderGroup.addDeclutter(false));
|
||||||
textReplay.drawText(geometry, feature);
|
textReplay.drawText(geometry, feature);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {import("../render/ReplayGroup.js").default} replayGroup Replay group.
|
* @param {import("../render/BuilderGroup.js").default} builderGroup Replay group.
|
||||||
* @param {import("../geom/Point.js").default|import("../render/Feature.js").default} geometry Geometry.
|
* @param {import("../geom/Point.js").default|import("../render/Feature.js").default} geometry Geometry.
|
||||||
* @param {import("../style/Style.js").default} style Style.
|
* @param {import("../style/Style.js").default} style Style.
|
||||||
* @param {import("../Feature.js").FeatureLike} feature Feature.
|
* @param {import("../Feature.js").FeatureLike} feature Feature.
|
||||||
*/
|
*/
|
||||||
function renderPointGeometry(replayGroup, geometry, style, feature) {
|
function renderPointGeometry(builderGroup, geometry, style, feature) {
|
||||||
const imageStyle = style.getImage();
|
const imageStyle = style.getImage();
|
||||||
if (imageStyle) {
|
if (imageStyle) {
|
||||||
if (imageStyle.getImageState() != ImageState.LOADED) {
|
if (imageStyle.getImageState() != ImageState.LOADED) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const imageReplay = replayGroup.getReplay(style.getZIndex(), ReplayType.IMAGE);
|
const imageReplay = builderGroup.getBuilder(style.getZIndex(), BuilderType.IMAGE);
|
||||||
imageReplay.setImageStyle(imageStyle, replayGroup.addDeclutter(false));
|
imageReplay.setImageStyle(imageStyle, builderGroup.addDeclutter(false));
|
||||||
imageReplay.drawPoint(geometry, feature);
|
imageReplay.drawPoint(geometry, feature);
|
||||||
}
|
}
|
||||||
const textStyle = style.getText();
|
const textStyle = style.getText();
|
||||||
if (textStyle) {
|
if (textStyle) {
|
||||||
const textReplay = replayGroup.getReplay(style.getZIndex(), ReplayType.TEXT);
|
const textReplay = builderGroup.getBuilder(style.getZIndex(), BuilderType.TEXT);
|
||||||
textReplay.setTextStyle(textStyle, replayGroup.addDeclutter(!!imageStyle));
|
textReplay.setTextStyle(textStyle, builderGroup.addDeclutter(!!imageStyle));
|
||||||
textReplay.drawText(geometry, feature);
|
textReplay.drawText(geometry, feature);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {import("../render/ReplayGroup.js").default} replayGroup Replay group.
|
* @param {import("../render/BuilderGroup.js").default} builderGroup Replay group.
|
||||||
* @param {import("../geom/MultiPoint.js").default|import("../render/Feature.js").default} geometry Geometry.
|
* @param {import("../geom/MultiPoint.js").default|import("../render/Feature.js").default} geometry Geometry.
|
||||||
* @param {import("../style/Style.js").default} style Style.
|
* @param {import("../style/Style.js").default} style Style.
|
||||||
* @param {import("../Feature.js").FeatureLike} feature Feature.
|
* @param {import("../Feature.js").FeatureLike} feature Feature.
|
||||||
*/
|
*/
|
||||||
function renderMultiPointGeometry(replayGroup, geometry, style, feature) {
|
function renderMultiPointGeometry(builderGroup, geometry, style, feature) {
|
||||||
const imageStyle = style.getImage();
|
const imageStyle = style.getImage();
|
||||||
if (imageStyle) {
|
if (imageStyle) {
|
||||||
if (imageStyle.getImageState() != ImageState.LOADED) {
|
if (imageStyle.getImageState() != ImageState.LOADED) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const imageReplay = replayGroup.getReplay(style.getZIndex(), ReplayType.IMAGE);
|
const imageReplay = builderGroup.getBuilder(style.getZIndex(), BuilderType.IMAGE);
|
||||||
imageReplay.setImageStyle(imageStyle, replayGroup.addDeclutter(false));
|
imageReplay.setImageStyle(imageStyle, builderGroup.addDeclutter(false));
|
||||||
imageReplay.drawMultiPoint(geometry, feature);
|
imageReplay.drawMultiPoint(geometry, feature);
|
||||||
}
|
}
|
||||||
const textStyle = style.getText();
|
const textStyle = style.getText();
|
||||||
if (textStyle) {
|
if (textStyle) {
|
||||||
const textReplay = replayGroup.getReplay(style.getZIndex(), ReplayType.TEXT);
|
const textReplay = builderGroup.getBuilder(style.getZIndex(), BuilderType.TEXT);
|
||||||
textReplay.setTextStyle(textStyle, replayGroup.addDeclutter(!!imageStyle));
|
textReplay.setTextStyle(textStyle, builderGroup.addDeclutter(!!imageStyle));
|
||||||
textReplay.drawText(geometry, feature);
|
textReplay.drawText(geometry, feature);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {import("../render/ReplayGroup.js").default} replayGroup Replay group.
|
* @param {import("../render/BuilderGroup.js").default} builderGroup Replay group.
|
||||||
* @param {import("../geom/Polygon.js").default|import("../render/Feature.js").default} geometry Geometry.
|
* @param {import("../geom/Polygon.js").default|import("../render/Feature.js").default} geometry Geometry.
|
||||||
* @param {import("../style/Style.js").default} style Style.
|
* @param {import("../style/Style.js").default} style Style.
|
||||||
* @param {import("../Feature.js").FeatureLike} feature Feature.
|
* @param {import("../Feature.js").FeatureLike} feature Feature.
|
||||||
*/
|
*/
|
||||||
function renderPolygonGeometry(replayGroup, geometry, style, feature) {
|
function renderPolygonGeometry(builderGroup, geometry, style, feature) {
|
||||||
const fillStyle = style.getFill();
|
const fillStyle = style.getFill();
|
||||||
const strokeStyle = style.getStroke();
|
const strokeStyle = style.getStroke();
|
||||||
if (fillStyle || strokeStyle) {
|
if (fillStyle || strokeStyle) {
|
||||||
const polygonReplay = replayGroup.getReplay(style.getZIndex(), ReplayType.POLYGON);
|
const polygonReplay = builderGroup.getBuilder(style.getZIndex(), BuilderType.POLYGON);
|
||||||
polygonReplay.setFillStrokeStyle(fillStyle, strokeStyle);
|
polygonReplay.setFillStrokeStyle(fillStyle, strokeStyle);
|
||||||
polygonReplay.drawPolygon(geometry, feature);
|
polygonReplay.drawPolygon(geometry, feature);
|
||||||
}
|
}
|
||||||
const textStyle = style.getText();
|
const textStyle = style.getText();
|
||||||
if (textStyle) {
|
if (textStyle) {
|
||||||
const textReplay = replayGroup.getReplay(style.getZIndex(), ReplayType.TEXT);
|
const textReplay = builderGroup.getBuilder(style.getZIndex(), BuilderType.TEXT);
|
||||||
textReplay.setTextStyle(textStyle, replayGroup.addDeclutter(false));
|
textReplay.setTextStyle(textStyle, builderGroup.addDeclutter(false));
|
||||||
textReplay.drawText(geometry, feature);
|
textReplay.drawText(geometry, feature);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import {getCircleArray} from '../../../../../src/ol/render/canvas/ReplayGroup.js';
|
import {getCircleArray} from '../../../../../src/ol/render/canvas/BuilderGroup.js';
|
||||||
|
|
||||||
|
|
||||||
describe('ol.render.canvas.ReplayGroup', function() {
|
describe('ol.render.canvas.ReplayGroup', function() {
|
||||||
|
|||||||
@@ -1,48 +1,87 @@
|
|||||||
import Feature from '../../../../../src/ol/Feature.js';
|
import Feature from '../../../../../src/ol/Feature.js';
|
||||||
import MultiPolygon from '../../../../../src/ol/geom/MultiPolygon.js';
|
import MultiPolygon from '../../../../../src/ol/geom/MultiPolygon.js';
|
||||||
import Polygon from '../../../../../src/ol/geom/Polygon.js';
|
import Polygon from '../../../../../src/ol/geom/Polygon.js';
|
||||||
import CanvasTextReplay from '../../../../../src/ol/render/canvas/TextReplay.js';
|
import CanvasTextReplay from '../../../../../src/ol/render/canvas/TextBuilder.js';
|
||||||
|
import InstructionExecutor from '../../../../../src/ol/render/canvas/Executor.js';
|
||||||
import Text from '../../../../../src/ol/style/Text.js';
|
import Text from '../../../../../src/ol/style/Text.js';
|
||||||
|
import {create as createTransform} from '../../../../../src/ol/transform.js';
|
||||||
|
|
||||||
|
function createBuilder() {
|
||||||
|
return new CanvasTextReplay(1, [-180, -90, 180, 90], 0.02, 1, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createContext() {
|
||||||
|
return {
|
||||||
|
fill: function() {},
|
||||||
|
stroke: function() {},
|
||||||
|
beginPath: function() {},
|
||||||
|
clip: function() {},
|
||||||
|
moveTo: function() {},
|
||||||
|
lineTo: function() {},
|
||||||
|
closePath: function() {},
|
||||||
|
setLineDash: function() {},
|
||||||
|
save: function() {},
|
||||||
|
restore: function() {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function executeInstructions(builder, expectedDrawTextImageCalls, expectedReplayImageCalls) {
|
||||||
|
const transform = createTransform();
|
||||||
|
const context = createContext();
|
||||||
|
const executor = new InstructionExecutor(1, [-180, -90, 180, 90], 0.02, 1, false, null, builder.finish());
|
||||||
|
sinon.spy(executor, 'drawTextImageWithPointPlacement_');
|
||||||
|
const replayImageStub = sinon.stub(executor, 'replayImage_');
|
||||||
|
executor.execute(context, transform);
|
||||||
|
expect(executor.drawTextImageWithPointPlacement_.callCount).to.be(expectedDrawTextImageCalls);
|
||||||
|
expect(replayImageStub.callCount).to.be(expectedReplayImageCalls);
|
||||||
|
}
|
||||||
|
|
||||||
describe('ol.render.canvas.TextReplay', function() {
|
describe('ol.render.canvas.TextReplay', function() {
|
||||||
|
|
||||||
it('renders polygon labels only when they fit', function() {
|
it('renders polygon labels only when they fit', function() {
|
||||||
const replay = new CanvasTextReplay(1, [-180, -90, 180, 90], 0.02, 1, true);
|
let builder = createBuilder();
|
||||||
const geometry = new Polygon([[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]]);
|
const geometry = new Polygon([[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]]);
|
||||||
const feature = new Feature(geometry);
|
const feature = new Feature(geometry);
|
||||||
|
|
||||||
replay.setTextStyle(new Text({
|
builder.setTextStyle(new Text({
|
||||||
text: 'This is a long text'
|
text: 'This is a long text'
|
||||||
}));
|
}));
|
||||||
replay.drawText(geometry, feature);
|
builder.drawText(geometry, feature);
|
||||||
expect(replay.instructions.length).to.be(0);
|
expect(builder.instructions.length).to.be(3);
|
||||||
|
executeInstructions(builder, 1, 0);
|
||||||
|
|
||||||
replay.setTextStyle(new Text({
|
|
||||||
|
builder = createBuilder();
|
||||||
|
builder.setTextStyle(new Text({
|
||||||
text: 'short'
|
text: 'short'
|
||||||
}));
|
}));
|
||||||
replay.drawText(geometry, feature);
|
builder.drawText(geometry, feature);
|
||||||
expect(replay.instructions.length).to.be(3);
|
expect(builder.instructions.length).to.be(3);
|
||||||
|
executeInstructions(builder, 1, 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders multipolygon labels only when they fit', function() {
|
it('renders multipolygon labels only when they fit', function() {
|
||||||
const replay = new CanvasTextReplay(1, [-180, -90, 180, 90], 0.02, 1, true);
|
|
||||||
const geometry = new MultiPolygon([
|
const geometry = new MultiPolygon([
|
||||||
[[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]],
|
[[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]],
|
||||||
[[[1, 1], [1, 2], [2, 2], [2, 1], [1, 1]]]
|
[[[1, 1], [1, 2], [2, 2], [2, 1], [1, 1]]]
|
||||||
]);
|
]);
|
||||||
const feature = new Feature(geometry);
|
const feature = new Feature(geometry);
|
||||||
|
|
||||||
replay.setTextStyle(new Text({
|
let builder = createBuilder();
|
||||||
|
builder.setTextStyle(new Text({
|
||||||
text: 'This is a long text'
|
text: 'This is a long text'
|
||||||
}));
|
}));
|
||||||
replay.drawText(geometry, feature);
|
builder.drawText(geometry, feature);
|
||||||
expect(replay.instructions.length).to.be(0);
|
expect(builder.instructions.length).to.be(3);
|
||||||
|
executeInstructions(builder, 1, 0);
|
||||||
|
|
||||||
replay.setTextStyle(new Text({
|
builder = createBuilder();
|
||||||
|
builder.setTextStyle(new Text({
|
||||||
text: 'short'
|
text: 'short'
|
||||||
}));
|
}));
|
||||||
replay.drawText(geometry, feature);
|
builder.drawText(geometry, feature);
|
||||||
expect(replay.instructions.length).to.be(3);
|
expect(builder.instructions.length).to.be(3);
|
||||||
|
executeInstructions(builder, 1, 2);
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,10 +7,11 @@ import MultiPoint from '../../../../../src/ol/geom/MultiPoint.js';
|
|||||||
import MultiPolygon from '../../../../../src/ol/geom/MultiPolygon.js';
|
import MultiPolygon from '../../../../../src/ol/geom/MultiPolygon.js';
|
||||||
import Point from '../../../../../src/ol/geom/Point.js';
|
import Point from '../../../../../src/ol/geom/Point.js';
|
||||||
import Polygon from '../../../../../src/ol/geom/Polygon.js';
|
import Polygon from '../../../../../src/ol/geom/Polygon.js';
|
||||||
import CanvasLineStringReplay from '../../../../../src/ol/render/canvas/LineStringReplay.js';
|
import CanvasLineStringBuilder from '../../../../../src/ol/render/canvas/LineStringBuilder.js';
|
||||||
import CanvasPolygonReplay from '../../../../../src/ol/render/canvas/PolygonReplay.js';
|
import CanvasPolygonBuilder from '../../../../../src/ol/render/canvas/PolygonBuilder.js';
|
||||||
import CanvasReplay from '../../../../../src/ol/render/canvas/Replay.js';
|
import CanvasReplay from '../../../../../src/ol/render/canvas/Builder.js';
|
||||||
import CanvasReplayGroup from '../../../../../src/ol/render/canvas/ReplayGroup.js';
|
import CanvasInstructionsGroupBuilder from '../../../../../src/ol/render/canvas/BuilderGroup.js';
|
||||||
|
import CanvasInstructionsGroupExecutor from '../../../../../src/ol/render/canvas/ExecutorGroup.js';
|
||||||
import {renderFeature} from '../../../../../src/ol/renderer/vector.js';
|
import {renderFeature} from '../../../../../src/ol/renderer/vector.js';
|
||||||
import Fill from '../../../../../src/ol/style/Fill.js';
|
import Fill from '../../../../../src/ol/style/Fill.js';
|
||||||
import Stroke from '../../../../../src/ol/style/Stroke.js';
|
import Stroke from '../../../../../src/ol/style/Stroke.js';
|
||||||
@@ -21,14 +22,26 @@ describe('ol.render.canvas.ReplayGroup', function() {
|
|||||||
|
|
||||||
describe('#replay', function() {
|
describe('#replay', function() {
|
||||||
|
|
||||||
let context, replay, fillCount, transform;
|
let context, builder, fillCount, transform;
|
||||||
let strokeCount, beginPathCount, moveToCount, lineToCount;
|
let strokeCount, beginPathCount, moveToCount, lineToCount;
|
||||||
let feature0, feature1, feature2, feature3;
|
let feature0, feature1, feature2, feature3;
|
||||||
let fill0, fill1, style1, style2;
|
let fill0, fill1, style1, style2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {CanvasInstructionsGroupBuilder} builder The builder to get instructions from.
|
||||||
|
* @param {Object=} skippedUids The ids to skip.
|
||||||
|
* @param {number=} pixelRatio The pixel ratio.
|
||||||
|
* @param {boolean=} overlaps Whether there is overlaps.
|
||||||
|
*/
|
||||||
|
function execute(builder, skippedUids, pixelRatio, overlaps) {
|
||||||
|
const executor = new CanvasInstructionsGroupExecutor(1, [-180, -90, 180, 90], 1,
|
||||||
|
pixelRatio || 1, !!overlaps, null, builder.finish());
|
||||||
|
executor.execute(context, transform, 0, skippedUids || {});
|
||||||
|
}
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
transform = createTransform();
|
transform = createTransform();
|
||||||
replay = new CanvasReplayGroup(1, [-180, -90, 180, 90], 1, 1, false);
|
builder = new CanvasInstructionsGroupBuilder(1, [-180, -90, 180, 90], 1, 1, false);
|
||||||
feature0 = new Feature(new Polygon(
|
feature0 = new Feature(new Polygon(
|
||||||
[[[-90, 0], [-45, 45], [0, 0], [1, 1], [0, -45], [-90, 0]]]));
|
[[[-90, 0], [-45, 45], [0, 0], [1, 1], [0, -45], [-90, 0]]]));
|
||||||
feature1 = new Feature(new Polygon(
|
feature1 = new Feature(new Polygon(
|
||||||
@@ -88,95 +101,95 @@ describe('ol.render.canvas.ReplayGroup', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('omits lineTo for repeated coordinates', function() {
|
it('omits lineTo for repeated coordinates', function() {
|
||||||
renderFeature(replay, feature0, fill0, 1);
|
renderFeature(builder, feature0, fill0, 1);
|
||||||
replay.replay(context, transform, 0, {});
|
execute(builder);
|
||||||
expect(lineToCount).to.be(4);
|
expect(lineToCount).to.be(4);
|
||||||
lineToCount = 0;
|
lineToCount = 0;
|
||||||
scaleTransform(transform, 0.25, 0.25);
|
scaleTransform(transform, 0.25, 0.25);
|
||||||
replay.replay(context, transform, 0, {});
|
execute(builder);
|
||||||
expect(lineToCount).to.be(3);
|
expect(lineToCount).to.be(3);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not omit moveTo for repeated coordinates', function() {
|
it('does not omit moveTo for repeated coordinates', function() {
|
||||||
renderFeature(replay, feature0, fill0, 1);
|
renderFeature(builder, feature0, fill0, 1);
|
||||||
renderFeature(replay, feature1, fill1, 1);
|
renderFeature(builder, feature1, fill1, 1);
|
||||||
replay.replay(context, transform, 0, {});
|
execute(builder);
|
||||||
expect(moveToCount).to.be(2);
|
expect(moveToCount).to.be(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('batches fill and stroke instructions for same style', function() {
|
it('batches fill and stroke instructions for same style', function() {
|
||||||
renderFeature(replay, feature1, style1, 1);
|
renderFeature(builder, feature1, style1, 1);
|
||||||
renderFeature(replay, feature2, style1, 1);
|
renderFeature(builder, feature2, style1, 1);
|
||||||
renderFeature(replay, feature3, style1, 1);
|
renderFeature(builder, feature3, style1, 1);
|
||||||
replay.replay(context, transform, 0, {});
|
execute(builder);
|
||||||
expect(fillCount).to.be(1);
|
expect(fillCount).to.be(1);
|
||||||
expect(strokeCount).to.be(1);
|
expect(strokeCount).to.be(1);
|
||||||
expect(beginPathCount).to.be(1);
|
expect(beginPathCount).to.be(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('batches fill and stroke instructions for different styles', function() {
|
it('batches fill and stroke instructions for different styles', function() {
|
||||||
renderFeature(replay, feature1, style1, 1);
|
renderFeature(builder, feature1, style1, 1);
|
||||||
renderFeature(replay, feature2, style1, 1);
|
renderFeature(builder, feature2, style1, 1);
|
||||||
renderFeature(replay, feature3, style2, 1);
|
renderFeature(builder, feature3, style2, 1);
|
||||||
replay.replay(context, transform, 0, {});
|
execute(builder);
|
||||||
expect(fillCount).to.be(2);
|
expect(fillCount).to.be(2);
|
||||||
expect(strokeCount).to.be(2);
|
expect(strokeCount).to.be(2);
|
||||||
expect(beginPathCount).to.be(2);
|
expect(beginPathCount).to.be(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('batches fill and stroke instructions for changing styles', function() {
|
it('batches fill and stroke instructions for changing styles', function() {
|
||||||
renderFeature(replay, feature1, style1, 1);
|
renderFeature(builder, feature1, style1, 1);
|
||||||
renderFeature(replay, feature2, style2, 1);
|
renderFeature(builder, feature2, style2, 1);
|
||||||
renderFeature(replay, feature3, style1, 1);
|
renderFeature(builder, feature3, style1, 1);
|
||||||
replay.replay(context, transform, 0, {});
|
execute(builder);
|
||||||
expect(fillCount).to.be(3);
|
expect(fillCount).to.be(3);
|
||||||
expect(strokeCount).to.be(3);
|
expect(strokeCount).to.be(3);
|
||||||
expect(beginPathCount).to.be(3);
|
expect(beginPathCount).to.be(3);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('batches fill and stroke instructions for skipped feature at the beginning', function() {
|
it('batches fill and stroke instructions for skipped feature at the beginning', function() {
|
||||||
renderFeature(replay, feature1, style1, 1);
|
renderFeature(builder, feature1, style1, 1);
|
||||||
renderFeature(replay, feature2, style2, 1);
|
renderFeature(builder, feature2, style2, 1);
|
||||||
renderFeature(replay, feature3, style2, 1);
|
renderFeature(builder, feature3, style2, 1);
|
||||||
const skippedUids = {};
|
const skippedUids = {};
|
||||||
skippedUids[getUid(feature1)] = true;
|
skippedUids[getUid(feature1)] = true;
|
||||||
replay.replay(context, transform, 0, skippedUids);
|
execute(builder, skippedUids);
|
||||||
expect(fillCount).to.be(1);
|
expect(fillCount).to.be(1);
|
||||||
expect(strokeCount).to.be(1);
|
expect(strokeCount).to.be(1);
|
||||||
expect(beginPathCount).to.be(1);
|
expect(beginPathCount).to.be(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('batches fill and stroke instructions for skipped feature at the end', function() {
|
it('batches fill and stroke instructions for skipped feature at the end', function() {
|
||||||
renderFeature(replay, feature1, style1, 1);
|
renderFeature(builder, feature1, style1, 1);
|
||||||
renderFeature(replay, feature2, style1, 1);
|
renderFeature(builder, feature2, style1, 1);
|
||||||
renderFeature(replay, feature3, style2, 1);
|
renderFeature(builder, feature3, style2, 1);
|
||||||
const skippedUids = {};
|
const skippedUids = {};
|
||||||
skippedUids[getUid(feature3)] = true;
|
skippedUids[getUid(feature3)] = true;
|
||||||
replay.replay(context, transform, 0, skippedUids);
|
execute(builder, skippedUids);
|
||||||
expect(fillCount).to.be(1);
|
expect(fillCount).to.be(1);
|
||||||
expect(strokeCount).to.be(1);
|
expect(strokeCount).to.be(1);
|
||||||
expect(beginPathCount).to.be(1);
|
expect(beginPathCount).to.be(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('batches fill and stroke instructions for skipped features', function() {
|
it('batches fill and stroke instructions for skipped features', function() {
|
||||||
renderFeature(replay, feature1, style1, 1);
|
renderFeature(builder, feature1, style1, 1);
|
||||||
renderFeature(replay, feature2, style1, 1);
|
renderFeature(builder, feature2, style1, 1);
|
||||||
renderFeature(replay, feature3, style2, 1);
|
renderFeature(builder, feature3, style2, 1);
|
||||||
const skippedUids = {};
|
const skippedUids = {};
|
||||||
skippedUids[getUid(feature1)] = true;
|
skippedUids[getUid(feature1)] = true;
|
||||||
skippedUids[getUid(feature2)] = true;
|
skippedUids[getUid(feature2)] = true;
|
||||||
replay.replay(context, transform, 0, skippedUids);
|
execute(builder, skippedUids);
|
||||||
expect(fillCount).to.be(1);
|
expect(fillCount).to.be(1);
|
||||||
expect(strokeCount).to.be(1);
|
expect(strokeCount).to.be(1);
|
||||||
expect(beginPathCount).to.be(1);
|
expect(beginPathCount).to.be(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not batch when overlaps is set to true', function() {
|
it('does not batch when overlaps is set to true', function() {
|
||||||
replay = new CanvasReplayGroup(1, [-180, -90, 180, 90], 1, 1, true);
|
builder = new CanvasInstructionsGroupBuilder(1, [-180, -90, 180, 90], 1, 1, true);
|
||||||
renderFeature(replay, feature1, style1, 1);
|
renderFeature(builder, feature1, style1, 1);
|
||||||
renderFeature(replay, feature2, style1, 1);
|
renderFeature(builder, feature2, style1, 1);
|
||||||
renderFeature(replay, feature3, style1, 1);
|
renderFeature(builder, feature3, style1, 1);
|
||||||
replay.replay(context, transform, 0, {});
|
execute(builder, {}, 1, true);
|
||||||
expect(fillCount).to.be(3);
|
expect(fillCount).to.be(3);
|
||||||
expect(strokeCount).to.be(3);
|
expect(strokeCount).to.be(3);
|
||||||
expect(beginPathCount).to.be(3);
|
expect(beginPathCount).to.be(3);
|
||||||
@@ -184,7 +197,7 @@ describe('ol.render.canvas.ReplayGroup', function() {
|
|||||||
|
|
||||||
it('applies the pixelRatio to the linedash array and offset', function() {
|
it('applies the pixelRatio to the linedash array and offset', function() {
|
||||||
// replay with a pixelRatio of 2
|
// replay with a pixelRatio of 2
|
||||||
replay = new CanvasReplayGroup(1, [-180, -90, 180, 90], 1, 2, true);
|
builder = new CanvasInstructionsGroupBuilder(1, [-180, -90, 180, 90], 1, 2, true);
|
||||||
|
|
||||||
let lineDash, lineDashCount = 0,
|
let lineDash, lineDashCount = 0,
|
||||||
lineDashOffset, lineDashOffsetCount = 0;
|
lineDashOffset, lineDashOffsetCount = 0;
|
||||||
@@ -201,9 +214,9 @@ describe('ol.render.canvas.ReplayGroup', function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
renderFeature(replay, feature1, style2, 1);
|
renderFeature(builder, feature1, style2, 1);
|
||||||
renderFeature(replay, feature2, style2, 1);
|
renderFeature(builder, feature2, style2, 1);
|
||||||
replay.replay(context, transform, 0, {});
|
execute(builder, {}, 2, true);
|
||||||
|
|
||||||
expect(lineDashCount).to.be(1);
|
expect(lineDashCount).to.be(1);
|
||||||
expect(style2.getStroke().getLineDash()).to.eql([3, 6]);
|
expect(style2.getStroke().getLineDash()).to.eql([3, 6]);
|
||||||
@@ -241,16 +254,16 @@ describe('ol.render.canvas.ReplayGroup', function() {
|
|||||||
[polygon.getGeometry().getCoordinates(), polygon.getGeometry().getCoordinates()]));
|
[polygon.getGeometry().getCoordinates(), polygon.getGeometry().getCoordinates()]));
|
||||||
const geometrycollection = new Feature(new GeometryCollection(
|
const geometrycollection = new Feature(new GeometryCollection(
|
||||||
[point.getGeometry(), linestring.getGeometry(), polygon.getGeometry()]));
|
[point.getGeometry(), linestring.getGeometry(), polygon.getGeometry()]));
|
||||||
replay = new CanvasReplayGroup(1, [-180, -90, 180, 90], 1, 1, true);
|
builder = new CanvasInstructionsGroupBuilder(1, [-180, -90, 180, 90], 1, 1, true);
|
||||||
renderFeature(replay, point, style, 1);
|
renderFeature(builder, point, style, 1);
|
||||||
renderFeature(replay, multipoint, style, 1);
|
renderFeature(builder, multipoint, style, 1);
|
||||||
renderFeature(replay, linestring, style, 1);
|
renderFeature(builder, linestring, style, 1);
|
||||||
renderFeature(replay, multilinestring, style, 1);
|
renderFeature(builder, multilinestring, style, 1);
|
||||||
renderFeature(replay, polygon, style, 1);
|
renderFeature(builder, polygon, style, 1);
|
||||||
renderFeature(replay, multipolygon, style, 1);
|
renderFeature(builder, multipolygon, style, 1);
|
||||||
renderFeature(replay, geometrycollection, style, 1);
|
renderFeature(builder, geometrycollection, style, 1);
|
||||||
scaleTransform(transform, 0.1, 0.1);
|
scaleTransform(transform, 0.1, 0.1);
|
||||||
replay.replay(context, transform, 0, {});
|
execute(builder, {}, 1, true);
|
||||||
expect(calls.length).to.be(9);
|
expect(calls.length).to.be(9);
|
||||||
expect(calls[0].geometry).to.be(point.getGeometry());
|
expect(calls[0].geometry).to.be(point.getGeometry());
|
||||||
expect(calls[0].feature).to.be(point);
|
expect(calls[0].feature).to.be(point);
|
||||||
@@ -446,7 +459,7 @@ describe('ol.render.canvas.LineStringReplay', function() {
|
|||||||
const tolerance = 1;
|
const tolerance = 1;
|
||||||
const extent = [-180, -90, 180, 90];
|
const extent = [-180, -90, 180, 90];
|
||||||
const resolution = 10;
|
const resolution = 10;
|
||||||
const replay = new CanvasLineStringReplay(tolerance, extent,
|
const replay = new CanvasLineStringBuilder(tolerance, extent,
|
||||||
resolution);
|
resolution);
|
||||||
const stroke = new Stroke({
|
const stroke = new Stroke({
|
||||||
width: 2
|
width: 2
|
||||||
@@ -468,7 +481,7 @@ describe('ol.render.canvas.PolygonReplay', function() {
|
|||||||
const tolerance = 1;
|
const tolerance = 1;
|
||||||
const extent = [-180, -90, 180, 90];
|
const extent = [-180, -90, 180, 90];
|
||||||
const resolution = 10;
|
const resolution = 10;
|
||||||
replay = new CanvasPolygonReplay(tolerance, extent,
|
replay = new CanvasPolygonBuilder(tolerance, extent,
|
||||||
resolution);
|
resolution);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -241,10 +241,8 @@ describe('ol.renderer.canvas.VectorTileLayer', function() {
|
|||||||
});
|
});
|
||||||
map.addLayer(layer2);
|
map.addLayer(layer2);
|
||||||
|
|
||||||
const spy1 = sinon.spy(VectorTile.prototype,
|
const spy1 = sinon.spy(VectorTile.prototype, 'getExecutorGroup');
|
||||||
'getReplayGroup');
|
const spy2 = sinon.spy(VectorTile.prototype, 'setExecutorGroup');
|
||||||
const spy2 = sinon.spy(VectorTile.prototype,
|
|
||||||
'setReplayGroup');
|
|
||||||
map.renderSync();
|
map.renderSync();
|
||||||
expect(spy1.callCount).to.be(4);
|
expect(spy1.callCount).to.be(4);
|
||||||
expect(spy2.callCount).to.be(2);
|
expect(spy2.callCount).to.be(2);
|
||||||
@@ -308,7 +306,7 @@ describe('ol.renderer.canvas.VectorTileLayer', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('#forEachFeatureAtCoordinate', function() {
|
describe('#forEachFeatureAtCoordinate', function() {
|
||||||
let layer, renderer, replayGroup;
|
let layer, renderer, executorGroup;
|
||||||
class TileClass extends VectorImageTile {
|
class TileClass extends VectorImageTile {
|
||||||
constructor() {
|
constructor() {
|
||||||
super(...arguments);
|
super(...arguments);
|
||||||
@@ -317,8 +315,8 @@ describe('ol.renderer.canvas.VectorTileLayer', function() {
|
|||||||
const sourceTile = new VectorTile([0, 0, 0]);
|
const sourceTile = new VectorTile([0, 0, 0]);
|
||||||
sourceTile.setState(TileState.LOADED);
|
sourceTile.setState(TileState.LOADED);
|
||||||
sourceTile.setProjection(getProjection('EPSG:3857'));
|
sourceTile.setProjection(getProjection('EPSG:3857'));
|
||||||
sourceTile.getReplayGroup = function() {
|
sourceTile.getExecutorGroup = function() {
|
||||||
return replayGroup;
|
return executorGroup;
|
||||||
};
|
};
|
||||||
const key = sourceTile.tileCoord.toString();
|
const key = sourceTile.tileCoord.toString();
|
||||||
this.tileKeys = [key];
|
this.tileKeys = [key];
|
||||||
@@ -329,7 +327,7 @@ describe('ol.renderer.canvas.VectorTileLayer', function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
replayGroup = {};
|
executorGroup = {};
|
||||||
layer = new VectorTileLayer({
|
layer = new VectorTileLayer({
|
||||||
source: new VectorTileSource({
|
source: new VectorTileSource({
|
||||||
tileClass: TileClass,
|
tileClass: TileClass,
|
||||||
@@ -337,7 +335,7 @@ describe('ol.renderer.canvas.VectorTileLayer', function() {
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
renderer = new CanvasVectorTileLayerRenderer(layer);
|
renderer = new CanvasVectorTileLayerRenderer(layer);
|
||||||
replayGroup.forEachFeatureAtCoordinate = function(coordinate,
|
executorGroup.forEachFeatureAtCoordinate = function(coordinate,
|
||||||
resolution, rotation, hitTolerance, skippedFeaturesUids, callback) {
|
resolution, rotation, hitTolerance, skippedFeaturesUids, callback) {
|
||||||
const feature = new Feature();
|
const feature = new Feature();
|
||||||
callback(feature);
|
callback(feature);
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import Polygon from '../../../../src/ol/geom/Polygon.js';
|
|||||||
import MultiLineString from '../../../../src/ol/geom/MultiLineString.js';
|
import MultiLineString from '../../../../src/ol/geom/MultiLineString.js';
|
||||||
import MultiPoint from '../../../../src/ol/geom/MultiPoint.js';
|
import MultiPoint from '../../../../src/ol/geom/MultiPoint.js';
|
||||||
import MultiPolygon from '../../../../src/ol/geom/MultiPolygon.js';
|
import MultiPolygon from '../../../../src/ol/geom/MultiPolygon.js';
|
||||||
import CanvasReplayGroup from '../../../../src/ol/render/canvas/ReplayGroup.js';
|
import CanvasBuilderGroup from '../../../../src/ol/render/canvas/BuilderGroup.js';
|
||||||
import {renderFeature} from '../../../../src/ol/renderer/vector.js';
|
import {renderFeature} from '../../../../src/ol/renderer/vector.js';
|
||||||
import Fill from '../../../../src/ol/style/Fill.js';
|
import Fill from '../../../../src/ol/style/Fill.js';
|
||||||
import Icon from '../../../../src/ol/style/Icon.js';
|
import Icon from '../../../../src/ol/style/Icon.js';
|
||||||
@@ -17,12 +17,12 @@ import Feature from '../../../../src/ol/Feature.js';
|
|||||||
|
|
||||||
describe('ol.renderer.vector', function() {
|
describe('ol.renderer.vector', function() {
|
||||||
describe('#renderFeature', function() {
|
describe('#renderFeature', function() {
|
||||||
let replayGroup;
|
let builderGroup;
|
||||||
let feature, iconStyle, style, squaredTolerance, listener, listenerThis;
|
let feature, iconStyle, style, squaredTolerance, listener, listenerThis;
|
||||||
let iconStyleLoadSpy;
|
let iconStyleLoadSpy;
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
replayGroup = new CanvasReplayGroup(1);
|
builderGroup = new CanvasBuilderGroup(1);
|
||||||
feature = new Feature();
|
feature = new Feature();
|
||||||
iconStyle = new Icon({
|
iconStyle = new Icon({
|
||||||
src: 'http://example.com/icon.png'
|
src: 'http://example.com/icon.png'
|
||||||
@@ -50,7 +50,7 @@ describe('ol.renderer.vector', function() {
|
|||||||
let listeners;
|
let listeners;
|
||||||
|
|
||||||
// call #1
|
// call #1
|
||||||
renderFeature(replayGroup, feature,
|
renderFeature(builderGroup, feature,
|
||||||
style, squaredTolerance, listener, listenerThis);
|
style, squaredTolerance, listener, listenerThis);
|
||||||
|
|
||||||
expect(iconStyleLoadSpy.calledOnce).to.be.ok();
|
expect(iconStyleLoadSpy.calledOnce).to.be.ok();
|
||||||
@@ -59,7 +59,7 @@ describe('ol.renderer.vector', function() {
|
|||||||
expect(listeners.length).to.eql(1);
|
expect(listeners.length).to.eql(1);
|
||||||
|
|
||||||
// call #2
|
// call #2
|
||||||
renderFeature(replayGroup, feature,
|
renderFeature(builderGroup, feature,
|
||||||
style, squaredTolerance, listener, listenerThis);
|
style, squaredTolerance, listener, listenerThis);
|
||||||
|
|
||||||
expect(iconStyleLoadSpy.calledOnce).to.be.ok();
|
expect(iconStyleLoadSpy.calledOnce).to.be.ok();
|
||||||
@@ -74,11 +74,11 @@ describe('ol.renderer.vector', function() {
|
|||||||
|
|
||||||
it('does not render the point', function() {
|
it('does not render the point', function() {
|
||||||
feature.setGeometry(new Point([0, 0]));
|
feature.setGeometry(new Point([0, 0]));
|
||||||
const imageReplay = replayGroup.getReplay(
|
const imageReplay = builderGroup.getBuilder(
|
||||||
style.getZIndex(), 'Image');
|
style.getZIndex(), 'Image');
|
||||||
const setImageStyleSpy = sinon.spy(imageReplay, 'setImageStyle');
|
const setImageStyleSpy = sinon.spy(imageReplay, 'setImageStyle');
|
||||||
const drawPointSpy = sinon.stub(imageReplay, 'drawPoint').callsFake(VOID);
|
const drawPointSpy = sinon.stub(imageReplay, 'drawPoint').callsFake(VOID);
|
||||||
renderFeature(replayGroup, feature,
|
renderFeature(builderGroup, feature,
|
||||||
style, squaredTolerance, listener, listenerThis);
|
style, squaredTolerance, listener, listenerThis);
|
||||||
expect(setImageStyleSpy.called).to.be(false);
|
expect(setImageStyleSpy.called).to.be(false);
|
||||||
setImageStyleSpy.restore();
|
setImageStyleSpy.restore();
|
||||||
@@ -87,11 +87,11 @@ describe('ol.renderer.vector', function() {
|
|||||||
|
|
||||||
it('does not render the multipoint', function() {
|
it('does not render the multipoint', function() {
|
||||||
feature.setGeometry(new MultiPoint([[0, 0], [1, 1]]));
|
feature.setGeometry(new MultiPoint([[0, 0], [1, 1]]));
|
||||||
const imageReplay = replayGroup.getReplay(
|
const imageReplay = builderGroup.getBuilder(
|
||||||
style.getZIndex(), 'Image');
|
style.getZIndex(), 'Image');
|
||||||
const setImageStyleSpy = sinon.spy(imageReplay, 'setImageStyle');
|
const setImageStyleSpy = sinon.spy(imageReplay, 'setImageStyle');
|
||||||
const drawMultiPointSpy = sinon.stub(imageReplay, 'drawMultiPoint').callsFake(VOID);
|
const drawMultiPointSpy = sinon.stub(imageReplay, 'drawMultiPoint').callsFake(VOID);
|
||||||
renderFeature(replayGroup, feature,
|
renderFeature(builderGroup, feature,
|
||||||
style, squaredTolerance, listener, listenerThis);
|
style, squaredTolerance, listener, listenerThis);
|
||||||
expect(setImageStyleSpy.called).to.be(false);
|
expect(setImageStyleSpy.called).to.be(false);
|
||||||
setImageStyleSpy.restore();
|
setImageStyleSpy.restore();
|
||||||
@@ -100,12 +100,12 @@ describe('ol.renderer.vector', function() {
|
|||||||
|
|
||||||
it('does render the linestring', function() {
|
it('does render the linestring', function() {
|
||||||
feature.setGeometry(new LineString([[0, 0], [1, 1]]));
|
feature.setGeometry(new LineString([[0, 0], [1, 1]]));
|
||||||
const lineStringReplay = replayGroup.getReplay(
|
const lineStringReplay = builderGroup.getBuilder(
|
||||||
style.getZIndex(), 'LineString');
|
style.getZIndex(), 'LineString');
|
||||||
const setFillStrokeStyleSpy = sinon.spy(lineStringReplay,
|
const setFillStrokeStyleSpy = sinon.spy(lineStringReplay,
|
||||||
'setFillStrokeStyle');
|
'setFillStrokeStyle');
|
||||||
const drawLineStringSpy = sinon.stub(lineStringReplay, 'drawLineString').callsFake(VOID);
|
const drawLineStringSpy = sinon.stub(lineStringReplay, 'drawLineString').callsFake(VOID);
|
||||||
renderFeature(replayGroup, feature,
|
renderFeature(builderGroup, feature,
|
||||||
style, squaredTolerance, listener, listenerThis);
|
style, squaredTolerance, listener, listenerThis);
|
||||||
expect(setFillStrokeStyleSpy.called).to.be(true);
|
expect(setFillStrokeStyleSpy.called).to.be(true);
|
||||||
expect(drawLineStringSpy.called).to.be(true);
|
expect(drawLineStringSpy.called).to.be(true);
|
||||||
@@ -115,12 +115,12 @@ describe('ol.renderer.vector', function() {
|
|||||||
|
|
||||||
it('does render the multilinestring', function() {
|
it('does render the multilinestring', function() {
|
||||||
feature.setGeometry(new MultiLineString([[[0, 0], [1, 1]]]));
|
feature.setGeometry(new MultiLineString([[[0, 0], [1, 1]]]));
|
||||||
const lineStringReplay = replayGroup.getReplay(
|
const lineStringReplay = builderGroup.getBuilder(
|
||||||
style.getZIndex(), 'LineString');
|
style.getZIndex(), 'LineString');
|
||||||
const setFillStrokeStyleSpy = sinon.spy(lineStringReplay,
|
const setFillStrokeStyleSpy = sinon.spy(lineStringReplay,
|
||||||
'setFillStrokeStyle');
|
'setFillStrokeStyle');
|
||||||
const drawMultiLineStringSpy = sinon.stub(lineStringReplay, 'drawMultiLineString').callsFake(VOID);
|
const drawMultiLineStringSpy = sinon.stub(lineStringReplay, 'drawMultiLineString').callsFake(VOID);
|
||||||
renderFeature(replayGroup, feature,
|
renderFeature(builderGroup, feature,
|
||||||
style, squaredTolerance, listener, listenerThis);
|
style, squaredTolerance, listener, listenerThis);
|
||||||
expect(setFillStrokeStyleSpy.called).to.be(true);
|
expect(setFillStrokeStyleSpy.called).to.be(true);
|
||||||
expect(drawMultiLineStringSpy.called).to.be(true);
|
expect(drawMultiLineStringSpy.called).to.be(true);
|
||||||
@@ -131,12 +131,12 @@ describe('ol.renderer.vector', function() {
|
|||||||
it('does render the polygon', function() {
|
it('does render the polygon', function() {
|
||||||
feature.setGeometry(new Polygon(
|
feature.setGeometry(new Polygon(
|
||||||
[[[0, 0], [1, 1], [1, 0], [0, 0]]]));
|
[[[0, 0], [1, 1], [1, 0], [0, 0]]]));
|
||||||
const polygonReplay = replayGroup.getReplay(
|
const polygonReplay = builderGroup.getBuilder(
|
||||||
style.getZIndex(), 'Polygon');
|
style.getZIndex(), 'Polygon');
|
||||||
const setFillStrokeStyleSpy = sinon.spy(polygonReplay,
|
const setFillStrokeStyleSpy = sinon.spy(polygonReplay,
|
||||||
'setFillStrokeStyle');
|
'setFillStrokeStyle');
|
||||||
const drawPolygonSpy = sinon.stub(polygonReplay, 'drawPolygon').callsFake(VOID);
|
const drawPolygonSpy = sinon.stub(polygonReplay, 'drawPolygon').callsFake(VOID);
|
||||||
renderFeature(replayGroup, feature,
|
renderFeature(builderGroup, feature,
|
||||||
style, squaredTolerance, listener, listenerThis);
|
style, squaredTolerance, listener, listenerThis);
|
||||||
expect(setFillStrokeStyleSpy.called).to.be(true);
|
expect(setFillStrokeStyleSpy.called).to.be(true);
|
||||||
expect(drawPolygonSpy.called).to.be(true);
|
expect(drawPolygonSpy.called).to.be(true);
|
||||||
@@ -147,12 +147,12 @@ describe('ol.renderer.vector', function() {
|
|||||||
it('does render the multipolygon', function() {
|
it('does render the multipolygon', function() {
|
||||||
feature.setGeometry(new MultiPolygon(
|
feature.setGeometry(new MultiPolygon(
|
||||||
[[[[0, 0], [1, 1], [1, 0], [0, 0]]]]));
|
[[[[0, 0], [1, 1], [1, 0], [0, 0]]]]));
|
||||||
const polygonReplay = replayGroup.getReplay(
|
const polygonReplay = builderGroup.getBuilder(
|
||||||
style.getZIndex(), 'Polygon');
|
style.getZIndex(), 'Polygon');
|
||||||
const setFillStrokeStyleSpy = sinon.spy(polygonReplay,
|
const setFillStrokeStyleSpy = sinon.spy(polygonReplay,
|
||||||
'setFillStrokeStyle');
|
'setFillStrokeStyle');
|
||||||
const drawMultiPolygonSpy = sinon.stub(polygonReplay, 'drawMultiPolygon').callsFake(VOID);
|
const drawMultiPolygonSpy = sinon.stub(polygonReplay, 'drawMultiPolygon').callsFake(VOID);
|
||||||
renderFeature(replayGroup, feature,
|
renderFeature(builderGroup, feature,
|
||||||
style, squaredTolerance, listener, listenerThis);
|
style, squaredTolerance, listener, listenerThis);
|
||||||
expect(setFillStrokeStyleSpy.called).to.be(true);
|
expect(setFillStrokeStyleSpy.called).to.be(true);
|
||||||
expect(drawMultiPolygonSpy.called).to.be(true);
|
expect(drawMultiPolygonSpy.called).to.be(true);
|
||||||
|
|||||||
Reference in New Issue
Block a user