Skip to content

Commit fc1955d

Browse files
authored
Merge pull request #229 from NieuwlandGeo/fix-displacement-rotation
Fix combination of rotation and displacement
2 parents fef2c09 + 0236b8e commit fc1955d

File tree

3 files changed

+37
-15
lines changed

3 files changed

+37
-15
lines changed

docs/assets/sldreader-standalone.js

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* Version: 0.7.3 - March 27, 2026 10:15:35 */
1+
/* Version: 0.7.3 - March 27, 2026 16:12:19 */
22
var SLDReader = (function (exports, RenderFeature, has, Style, Icon, Fill, Stroke, Circle, RegularShape, render, Point, color, colorlike, IconImageCache, ImageStyle, dom, IconImage, LineString, extent, Polygon, MultiPolygon, Text, MultiPoint) {
33
'use strict';
44

@@ -3603,11 +3603,11 @@ var SLDReader = (function (exports, RenderFeature, has, Style, Icon, Fill, Strok
36033603
} = graphic;
36043604
const sizeValue = Number(evaluate(size, feature, context)) || DEFAULT_MARK_SIZE;
36053605
const rotationDegrees = Number(evaluate(rotation, feature, context)) || 0.0;
3606+
const rotationRadians = Math.PI * rotationDegrees / 180.0;
36063607

36073608
// --- Update dynamic rotation ---
36083609
if (isDynamicExpression(rotation)) {
3609-
// Note: OL angles are in radians.
3610-
const rotationRadians = Math.PI * rotationDegrees / 180.0;
3610+
// Note: OL expects angles in radians.
36113611
olImage.setRotation(rotationRadians);
36123612
}
36133613

@@ -3654,7 +3654,12 @@ var SLDReader = (function (exports, RenderFeature, has, Style, Icon, Fill, Strok
36543654
const dx = evaluate(displacementx, feature, context) || 0.0;
36553655
const dy = evaluate(displacementy, feature, context) || 0.0;
36563656
if (dx !== 0.0 || dy !== 0.0) {
3657-
olImage.setDisplacement([dx, dy]);
3657+
// The SLD spec says that rotation must be independent of displacement,
3658+
// but because OpenLayers applies rotation after displacement, the displaced center will be rotated too.
3659+
// Compensate for this by rotating displaced center back in counter-clockwise direction.
3660+
const dx2 = Math.cos(rotationRadians) * dx - Math.sin(rotationRadians) * dy;
3661+
const dy2 = Math.sin(rotationRadians) * dx + Math.cos(rotationRadians) * dy;
3662+
olImage.setDisplacement([dx2, dy2]);
36583663
}
36593664
}
36603665
}

src/styles/pointStyle.js

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -127,11 +127,11 @@ function getPointStyle(symbolizer, feature, context) {
127127
const sizeValue =
128128
Number(evaluate(size, feature, context)) || DEFAULT_MARK_SIZE;
129129
const rotationDegrees = Number(evaluate(rotation, feature, context)) || 0.0;
130+
const rotationRadians = (Math.PI * rotationDegrees) / 180.0;
130131

131132
// --- Update dynamic rotation ---
132133
if (isDynamicExpression(rotation)) {
133-
// Note: OL angles are in radians.
134-
const rotationRadians = (Math.PI * rotationDegrees) / 180.0;
134+
// Note: OL expects angles in radians.
135135
olImage.setRotation(rotationRadians);
136136
}
137137

@@ -192,7 +192,12 @@ function getPointStyle(symbolizer, feature, context) {
192192
const dx = evaluate(displacementx, feature, context) || 0.0;
193193
const dy = evaluate(displacementy, feature, context) || 0.0;
194194
if (dx !== 0.0 || dy !== 0.0) {
195-
olImage.setDisplacement([dx, dy]);
195+
// The SLD spec says that rotation must be independent of displacement,
196+
// but because OpenLayers applies rotation after displacement, the displaced center will be rotated too.
197+
// Compensate for this by rotating displaced center back in counter-clockwise direction.
198+
const dx2 = Math.cos(rotationRadians) * dx - Math.sin(rotationRadians) * dy;
199+
const dy2 = Math.sin(rotationRadians) * dx + Math.cos(rotationRadians) * dy;
200+
olImage.setDisplacement([dx2, dy2]);
196201
}
197202
}
198203
}

test/OlStyler.test.js

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -263,8 +263,8 @@ describe('SLD with external graphics', () => {
263263
).to.be.undefined;
264264
expect(
265265
getImageLoadingState(
266-
featureTypeStyle.elseFilterRules[0].symbolizers[0].graphic.externalgraphic
267-
.onlineresource
266+
featureTypeStyle.elseFilterRules[0].symbolizers[0].graphic
267+
.externalgraphic.onlineresource
268268
)
269269
).to.be.undefined;
270270

@@ -451,8 +451,8 @@ describe('SLD with external graphics', () => {
451451
.true;
452452
// The pointsymbolizer of the second style object should also be properly invalidated,
453453
// even if it uses the same image for which the first style function triggered the loading.
454-
expect(featureTypeStyle2.rules[0].symbolizers[0].__invalidated).to
455-
.be.true;
454+
expect(featureTypeStyle2.rules[0].symbolizers[0].__invalidated).to.be
455+
.true;
456456
done();
457457
},
458458
});
@@ -595,9 +595,15 @@ describe('Dynamic style properties', () => {
595595
);
596596
});
597597

598-
it('Reads displacement from feature', () => {
598+
it('Reads displacement and compensates for rotation', () => {
599599
const style = styleFunction(pointFeature)[0];
600-
expect(style.getImage().getDisplacement()).to.deep.equal([10, 20]);
600+
const [dx, dy] = style.getImage().getDisplacement();
601+
const rotation = style.getImage().getRotation();
602+
// OriDx and oriDy are the original, uncompensated, displacement values from SLD.
603+
const oriDx = Math.cos(-rotation) * dx - Math.sin(-rotation) * dy;
604+
expect(oriDx).to.be.closeTo(10, 1e-6);
605+
const oriDy = Math.sin(-rotation) * dx + Math.cos(-rotation) * dy;
606+
expect(oriDy).to.be.closeTo(20, 1e-6);
601607
});
602608

603609
it('Reads text for label from feature', () => {
@@ -1227,8 +1233,14 @@ describe('Styling with dynamic SVG Parameters', () => {
12271233
expect(imageRotationDegrees).to.equal(42);
12281234
});
12291235

1230-
it('Dynamic displacement', () => {
1231-
expect(olStyle.getImage().getDisplacement()).to.deep.equal([15, 45]);
1236+
it('Dynamic displacement (compensated for dynamic rotation)', () => {
1237+
const [dx, dy] = olStyle.getImage().getDisplacement();
1238+
const rotation = olStyle.getImage().getRotation();
1239+
// OriDx and oriDy are the original, uncompensated, displacement values from SLD.
1240+
const oriDx = Math.cos(-rotation) * dx - Math.sin(-rotation) * dy;
1241+
expect(oriDx).to.be.closeTo(15, 1e-6);
1242+
const oriDy = Math.sin(-rotation) * dx + Math.cos(-rotation) * dy;
1243+
expect(oriDy).to.be.closeTo(45, 1e-6);
12321244
});
12331245
});
12341246

0 commit comments

Comments
 (0)