diff --git a/examples/immediate-geographic.html b/examples/immediate-geographic.html new file mode 100644 index 0000000000..2683857e27 --- /dev/null +++ b/examples/immediate-geographic.html @@ -0,0 +1,14 @@ +--- +layout: example.html +title: Immediate Rendering (Geographic) +shortdesc: Using the immediate rendering API with geometries in geographic coordinates. +docs: > + This example uses the "immediate" rendering API with geometries in geographic coordinates. + The immediate rendering API lets you draw styled geometries without adding them to a layer first. + Use the `getVectorContext` function to create a rendering context from a render event. Using the + `context.drawGeometry()` and `context.setStyle()` methods on this rendering context, you can draw + any geometry on each render frame. The `useGeographic` function is used in this example so that + geometries can be in geographic coordinates. +tags: "immediate, geographic" +--- +
diff --git a/examples/immediate-geographic.js b/examples/immediate-geographic.js new file mode 100644 index 0000000000..7c92cf0459 --- /dev/null +++ b/examples/immediate-geographic.js @@ -0,0 +1,64 @@ +import {Map, View} from '../src/ol/index.js'; +import {Point} from '../src/ol/geom.js'; +import TileLayer from '../src/ol/layer/Tile.js'; +import Stamen from '../src/ol/source/Stamen.js'; +import {Circle, Fill, Style} from '../src/ol/style.js'; +import {getVectorContext} from '../src/ol/render.js'; +import {useGeographic} from '../src/ol/proj.js'; +import {upAndDown} from '../src/ol/easing.js'; + +useGeographic(); + +const layer = new TileLayer({ + source: new Stamen({ + layer: 'toner' + }) +}); + +const map = new Map({ + layers: [layer], + target: 'map', + view: new View({ + center: [0, 0], + zoom: 2 + }) +}); + +const image = new Circle({ + radius: 8, + fill: new Fill({color: 'rgb(255, 153, 0)'}) +}); + +const style = new Style({ + image: image +}); + +const n = 1000; +const geometries = new Array(n); + +for (let i = 0; i < n; ++i) { + const lon = 360 * Math.random() - 180; + const lat = 180 * Math.random() - 90; + geometries[i] = new Point([lon, lat]); +} + +layer.on('postrender', function(event) { + const vectorContext = getVectorContext(event); + + for (let i = 0; i < n; ++i) { + const importance = upAndDown(Math.pow((n - i) / n, 0.15)); + if (importance < 0.1) { + continue; + } + image.setOpacity(importance); + image.setScale(importance); + vectorContext.setStyle(style); + vectorContext.drawGeometry(geometries[i]); + } + + const lon = 360 * Math.random() - 180; + const lat = 180 * Math.random() - 90; + geometries.push(new Point([lon, lat])); + geometries.shift(); + map.render(); +}); diff --git a/src/ol/geom/Geometry.js b/src/ol/geom/Geometry.js index 00c18a7c04..bab5f4f68b 100644 --- a/src/ol/geom/Geometry.js +++ b/src/ol/geom/Geometry.js @@ -5,7 +5,7 @@ import {abstract} from '../util.js'; import BaseObject from '../Object.js'; import {createEmpty, getHeight, returnOrUpdate} from '../extent.js'; import {transform2D} from './flat/transform.js'; -import {get as getProjection, getTransform, getTransformFromProjections} from '../proj.js'; +import {get as getProjection, getTransform} from '../proj.js'; import Units from '../proj/Units.js'; import {create as createTransform, compose as composeTransform} from '../transform.js'; import {memoizeOne} from '../functions.js'; @@ -62,17 +62,15 @@ class Geometry extends BaseObject { * @abstract * @param {number} revision The geometry revision. * @param {number} squaredTolerance Squared tolerance. - * @param {import("../proj/Projection.js").default} [sourceProjection] The source projection. - * @param {import("../proj/Projection.js").default} [destProjection] The destination projection. + * @param {import("../proj.js").TransformFunction} [opt_transform] Optional transform function. * @return {Geometry} Simplified geometry. */ - this.simplifyTransformedInternal = memoizeOne(function(revision, squaredTolerance, sourceProjection, destProjection) { - if (!sourceProjection || !destProjection) { + this.simplifyTransformedInternal = memoizeOne(function(revision, squaredTolerance, opt_transform) { + if (!opt_transform) { return this.getSimplifiedGeometry(squaredTolerance); } - const transform = getTransformFromProjections(sourceProjection, destProjection); const clone = this.clone(); - clone.applyTransform(transform); + clone.applyTransform(opt_transform); return clone.getSimplifiedGeometry(squaredTolerance); }); @@ -82,12 +80,11 @@ class Geometry extends BaseObject { * Get a transformed and simplified version of the geometry. * @abstract * @param {number} squaredTolerance Squared tolerance. - * @param {import("../proj/Projection.js").default} sourceProjection The source projection. - * @param {import("../proj/Projection.js").default} destProjection The destination projection. + * @param {import("../proj.js").TransformFunction} [opt_transform] Optional transform function. * @return {Geometry} Simplified geometry. */ - simplifyTransformed(squaredTolerance, sourceProjection, destProjection) { - return this.simplifyTransformedInternal(this.getRevision(), squaredTolerance, sourceProjection, destProjection); + simplifyTransformed(squaredTolerance, opt_transform) { + return this.simplifyTransformedInternal(this.getRevision(), squaredTolerance, opt_transform); } /** diff --git a/src/ol/render.js b/src/ol/render.js index fb48e303bd..9b62d933d1 100644 --- a/src/ol/render.js +++ b/src/ol/render.js @@ -9,6 +9,8 @@ import { scale as scaleTransform } from './transform.js'; import CanvasImmediateRenderer from './render/canvas/Immediate.js'; +import {getSquaredTolerance} from './renderer/vector.js'; +import {getUserProjection, getTransformFromProjections} from './proj.js'; /** @@ -92,9 +94,15 @@ export function toContext(context, opt_options) { export function getVectorContext(event) { const frameState = event.frameState; const transform = multiplyTransform(event.inversePixelTransform.slice(), frameState.coordinateToPixelTransform); + const squaredTolerance = getSquaredTolerance(frameState.viewState.resolution, frameState.pixelRatio); + let userTransform; + const userProjection = getUserProjection(); + if (userProjection) { + userTransform = getTransformFromProjections(userProjection, frameState.viewState.projection); + } return new CanvasImmediateRenderer( event.context, frameState.pixelRatio, frameState.extent, transform, - frameState.viewState.rotation); + frameState.viewState.rotation, squaredTolerance, userTransform); } /** diff --git a/src/ol/render/Feature.js b/src/ol/render/Feature.js index 322d4a9b0e..7d758130bf 100644 --- a/src/ol/render/Feature.js +++ b/src/ol/render/Feature.js @@ -204,11 +204,10 @@ class RenderFeature { * Get a transformed and simplified version of the geometry. * @abstract * @param {number} squaredTolerance Squared tolerance. - * @param {import("../proj/Projection.js").default} sourceProjection The source projection. - * @param {import("../proj/Projection.js").default} destProjection The destination projection. + * @param {import("../proj.js").TransformFunction} [opt_transform] Optional transform function. * @return {RenderFeature} Simplified geometry. */ - simplifyTransformed(squaredTolerance, sourceProjection, destProjection) { + simplifyTransformed(squaredTolerance, opt_transform) { return this; } diff --git a/src/ol/render/canvas/Immediate.js b/src/ol/render/canvas/Immediate.js index 871b41adb6..2f0894b4f1 100644 --- a/src/ol/render/canvas/Immediate.js +++ b/src/ol/render/canvas/Immediate.js @@ -31,8 +31,10 @@ class CanvasImmediateRenderer extends VectorContext { * @param {import("../../extent.js").Extent} extent Extent. * @param {import("../../transform.js").Transform} transform Transform. * @param {number} viewRotation View rotation. + * @param {number=} opt_squaredTolerance Optional squared tolerance for simplification. + * @param {import("../../proj.js").TransformFunction=} opt_userTransform Transform from user to view projection. */ - constructor(context, pixelRatio, extent, transform, viewRotation) { + constructor(context, pixelRatio, extent, transform, viewRotation, opt_squaredTolerance, opt_userTransform) { super(); /** @@ -65,6 +67,18 @@ class CanvasImmediateRenderer extends VectorContext { */ this.viewRotation_ = viewRotation; + /** + * @private + * @type {number} + */ + this.squaredTolerance_ = opt_squaredTolerance; + + /** + * @private + * @type {import("../../proj.js").TransformFunction} + */ + this.userTransform_ = opt_userTransform; + /** * @private * @type {?import("../canvas.js").FillState} @@ -505,6 +519,9 @@ class CanvasImmediateRenderer extends VectorContext { * @override */ drawPoint(geometry) { + if (this.squaredTolerance_) { + geometry = /** @type {import("../../geom/Point.js").default} */ (geometry.simplifyTransformed(this.squaredTolerance_, this.userTransform_)); + } const flatCoordinates = geometry.getFlatCoordinates(); const stride = geometry.getStride(); if (this.image_) { @@ -523,6 +540,9 @@ class CanvasImmediateRenderer extends VectorContext { * @override */ drawMultiPoint(geometry) { + if (this.squaredTolerance_) { + geometry = /** @type {import("../../geom/MultiPoint.js").default} */ (geometry.simplifyTransformed(this.squaredTolerance_, this.userTransform_)); + } const flatCoordinates = geometry.getFlatCoordinates(); const stride = geometry.getStride(); if (this.image_) { @@ -541,6 +561,9 @@ class CanvasImmediateRenderer extends VectorContext { * @override */ drawLineString(geometry) { + if (this.squaredTolerance_) { + geometry = /** @type {import("../../geom/LineString.js").default} */ (geometry.simplifyTransformed(this.squaredTolerance_, this.userTransform_)); + } if (!intersects(this.extent_, geometry.getExtent())) { return; } @@ -567,6 +590,9 @@ class CanvasImmediateRenderer extends VectorContext { * @override */ drawMultiLineString(geometry) { + if (this.squaredTolerance_) { + geometry = /** @type {import("../../geom/MultiLineString.js").default} */ (geometry.simplifyTransformed(this.squaredTolerance_, this.userTransform_)); + } const geometryExtent = geometry.getExtent(); if (!intersects(this.extent_, geometryExtent)) { return; @@ -598,6 +624,9 @@ class CanvasImmediateRenderer extends VectorContext { * @override */ drawPolygon(geometry) { + if (this.squaredTolerance_) { + geometry = /** @type {import("../../geom/Polygon.js").default} */ (geometry.simplifyTransformed(this.squaredTolerance_, this.userTransform_)); + } if (!intersects(this.extent_, geometry.getExtent())) { return; } @@ -632,6 +661,9 @@ class CanvasImmediateRenderer extends VectorContext { * @override */ drawMultiPolygon(geometry) { + if (this.squaredTolerance_) { + geometry = /** @type {import("../../geom/MultiPolygon.js").default} */ (geometry.simplifyTransformed(this.squaredTolerance_, this.userTransform_)); + } if (!intersects(this.extent_, geometry.getExtent())) { return; } diff --git a/src/ol/renderer/canvas/VectorLayer.js b/src/ol/renderer/canvas/VectorLayer.js index ee9110e5e1..4cb11e29da 100644 --- a/src/ol/renderer/canvas/VectorLayer.js +++ b/src/ol/renderer/canvas/VectorLayer.js @@ -4,7 +4,7 @@ import {getUid} from '../../util.js'; import ViewHint from '../../ViewHint.js'; import {buffer, createEmpty, containsExtent, getWidth, intersects as intersectsExtent} from '../../extent.js'; -import {fromUserExtent, toUserExtent, getUserProjection} from '../../proj.js'; +import {fromUserExtent, toUserExtent, getUserProjection, getTransformFromProjections} from '../../proj.js'; import CanvasBuilderGroup from '../../render/canvas/BuilderGroup.js'; import ExecutorGroup, {replayDeclutter} from '../../render/canvas/ExecutorGroup.js'; import CanvasLayerRenderer from './Layer.js'; @@ -307,8 +307,10 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer { pixelRatio, vectorLayer.getDeclutter()); const userProjection = getUserProjection(); + let userTransform; if (userProjection) { vectorSource.loadFeatures(toUserExtent(extent, projection), resolution, userProjection); + userTransform = getTransformFromProjections(userProjection, projection); } else { vectorSource.loadFeatures(extent, resolution, projection); } @@ -326,7 +328,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer { styles = styleFunction(feature, resolution); } if (styles) { - const dirty = this.renderFeature(feature, squaredTolerance, styles, replayGroup, projection); + const dirty = this.renderFeature(feature, squaredTolerance, styles, replayGroup, userTransform); this.dirty_ = this.dirty_ || dirty; } }.bind(this); @@ -370,10 +372,10 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer { * @param {number} squaredTolerance Squared render tolerance. * @param {import("../../style/Style.js").default|Array