diff --git a/package.json b/package.json index 391c22ba3c..973df08ec1 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "test-rendering": "node rendering/test.js", "test": "npm run karma -- --single-run --log-level error && npm run test-rendering -- --force", "karma": "karma start test/karma.config.js", + "start": "npm run serve-examples", "serve-examples": "webpack-dev-server --config examples/webpack/config.js --mode development --watch", "build-examples": "webpack --config examples/webpack/config.js --mode production", "build-package": "npm run transpile && npm run copy-css && node tasks/prepare-package && cp README.md build/ol", diff --git a/rendering/cases/linestring-style-rotation/expected.png b/rendering/cases/linestring-style-rotation/expected.png new file mode 100644 index 0000000000..795e69ffb3 Binary files /dev/null and b/rendering/cases/linestring-style-rotation/expected.png differ diff --git a/rendering/cases/linestring-style-rotation/main.js b/rendering/cases/linestring-style-rotation/main.js new file mode 100644 index 0000000000..f69bd1cf74 --- /dev/null +++ b/rendering/cases/linestring-style-rotation/main.js @@ -0,0 +1,70 @@ +import Map from '../../../src/ol/CompositeMap.js'; +import View from '../../../src/ol/View.js'; +import Feature from '../../../src/ol/Feature.js'; +import LineString from '../../../src/ol/geom/LineString.js'; +import VectorLayer from '../../../src/ol/layer/Vector.js'; +import VectorSource from '../../../src/ol/source/Vector.js'; +import Style from '../../../src/ol/style/Style.js'; +import Stroke from '../../../src/ol/style/Stroke.js'; + + +const vectorSource = new VectorSource(); +let feature; + +feature = new Feature({ + geometry: new LineString([[-60, 60], [45, 60]]) +}); +vectorSource.addFeature(feature); + +feature = new Feature({ + geometry: new LineString([[-60, -50], [30, 10]]) +}); +feature.setStyle(new Style({ + stroke: new Stroke({color: '#f00', width: 3}) +})); +vectorSource.addFeature(feature); + +feature = new Feature({ + geometry: new LineString([[-110, -100], [0, 100], [100, -90]]) +}); +feature.setStyle(new Style({ + stroke: new Stroke({ + color: 'rgba(55, 55, 55, 0.75)', + width: 5, + lineCap: 'square', + lineDash: [4, 8], + lineJoin: 'round' + }) +})); +vectorSource.addFeature(feature); + +feature = new Feature({ + geometry: new LineString([[-80, 80], [80, 80], [-40, -90]]) +}); +feature.setStyle([ + new Style({ + stroke: new Stroke({color: '#F2F211', width: 5}) + }), + new Style({ + stroke: new Stroke({color: '#292921', width: 1}) + }) +]); +vectorSource.addFeature(feature); + + +new Map({ + pixelRatio: 1, + layers: [ + new VectorLayer({ + source: vectorSource + }) + ], + target: 'map', + view: new View({ + center: [0, 0], + resolution: 1, + rotation: Math.PI / 4 + }) +}); + +render(); diff --git a/src/ol/renderer/Composite.js b/src/ol/renderer/Composite.js index f1a6686131..4beda7874e 100644 --- a/src/ol/renderer/Composite.js +++ b/src/ol/renderer/Composite.js @@ -30,9 +30,13 @@ class CompositeMapRenderer extends MapRenderer { * @type {HTMLDivElement} */ this.element_ = document.createElement('div'); + const style = this.element_.style; + style.display = 'flex'; + style.alignItems = 'center'; + style.justifyContent = 'center'; + style.width = '100%'; + style.height = '100%'; - this.element_.style.width = '100%'; - this.element_.style.height = '100%'; this.element_.className = CLASS_UNSELECTABLE; const container = map.getViewport(); @@ -82,9 +86,6 @@ class CompositeMapRenderer extends MapRenderer { stableSort(layerStatesArray, sortByZIndex); const rotation = frameState.viewState.rotation; - if (rotation) { - // TODO: apply rotation - } const viewResolution = frameState.viewState.resolution; @@ -99,10 +100,17 @@ class CompositeMapRenderer extends MapRenderer { const layerRenderer = this.getLayerRenderer(layer); if (layerRenderer.prepareFrame(frameState, layerState)) { const element = layerRenderer.renderFrame(frameState, layerState); + const opacity = layerState.opacity; if (opacity !== element.style.opacity) { element.style.opacity = opacity; } + + const transform = 'rotate(' + rotation + 'rad)'; + if (transform !== element.style.transform) { + element.style.transform = transform; + } + this.children_.push(element); } } diff --git a/src/ol/renderer/canvas/Layer.js b/src/ol/renderer/canvas/Layer.js index 47b0a582ae..182ed8c653 100644 --- a/src/ol/renderer/canvas/Layer.js +++ b/src/ol/renderer/canvas/Layer.js @@ -184,6 +184,27 @@ class CanvasLayerRenderer extends LayerRenderer { return composeTransform(this.transform_, dx1, dy1, sx, sy, angle, dx2, dy2); } + /** + * Creates a transform for rendering to an element that will be rotated after rendering. + * @param {import("../../PluggableMap.js").FrameState} frameState Frame state. + * @param {number} width Width of the rendered element (in pixels). + * @param {number} height Height of the rendered element (in pixels). + * @param {number} offsetX Offset on the x-axis in view coordinates. + * @protected + * @return {!import("../../transform.js").Transform} Transform. + */ + getRenderTransform(frameState, width, height, offsetX) { + const viewState = frameState.viewState; + const pixelRatio = frameState.pixelRatio; + const dx1 = width / 2; + const dy1 = height / 2; + const sx = pixelRatio / viewState.resolution; + const sy = -sx; + const dx2 = -viewState.center[0] + offsetX; + const dy2 = -viewState.center[1]; + return composeTransform(this.transform_, dx1, dy1, sx, sy, 0, dx2, dy2); + } + /** * @abstract * @param {import("../../PluggableMap.js").FrameState} frameState Frame state. diff --git a/src/ol/renderer/canvas/VectorLayer.js b/src/ol/renderer/canvas/VectorLayer.js index dcabe4fcbc..c54276bd69 100644 --- a/src/ol/renderer/canvas/VectorLayer.js +++ b/src/ol/renderer/canvas/VectorLayer.js @@ -81,6 +81,9 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer { */ this.context = createCanvasContext2D(); + const canvas = this.context.canvas; + canvas.style.position = 'absolute'; + listen(labelCache, EventType.CLEAR, this.handleFontsChanged_, this); } @@ -275,12 +278,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer { const viewHints = frameState.viewHints; const snapToPixel = !(viewHints[ViewHint.ANIMATING] || viewHints[ViewHint.INTERACTING]); - // TODO: deal with rotation (this should not be necessary) - if (rotation) { - rotateAtOffset(context, -rotation, width / 2, height / 2); - } - - let transform = this.getTransform(frameState, 0); + let transform = this.getRenderTransform(frameState, width, height, 0); const skippedFeatureUids = layerState.managed ? frameState.skippedFeatureUids : {}; replayGroup.replay(context, transform, rotation, skippedFeatureUids, snapToPixel); @@ -292,7 +290,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer { while (startX < projectionExtent[0]) { --world; offsetX = worldWidth * world; - transform = this.getTransform(frameState, offsetX); + transform = this.getRenderTransform(frameState, width, height, offsetX); replayGroup.replay(context, transform, rotation, skippedFeatureUids, snapToPixel); startX += worldWidth; } @@ -301,17 +299,12 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer { while (startX > projectionExtent[2]) { ++world; offsetX = worldWidth * world; - transform = this.getTransform(frameState, offsetX); + transform = this.getRenderTransform(frameState, width, height, offsetX); replayGroup.replay(context, transform, rotation, skippedFeatureUids, snapToPixel); startX -= worldWidth; } } - // TODO: deal with rotation (this should not be necessary) - if (rotation) { - rotateAtOffset(context, rotation, width / 2, height / 2); - } - if (this.getLayer().hasListener(RenderEventType.RENDER)) { this.dispatchRenderEvent(context, frameState, transform); }