Skip to content

Commit

Permalink
Merge pull request #1078 from openlayers/text-decluttermode
Browse files Browse the repository at this point in the history
Add support for text-ignore-placement and text-allow-overlap
  • Loading branch information
ahocevar authored Jan 29, 2024
2 parents 42170c5 + 2f87459 commit df32938
Show file tree
Hide file tree
Showing 4 changed files with 184 additions and 82 deletions.
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
"mapbox-to-css-font": "^2.4.1"
},
"peerDependencies": {
"ol": ">=9.0.0 || >=9.0.0-dev.0 <9.0.0 || >=8.0.0 <=8.2.0 || >=7.0.0 <=7.5.2"
"ol": ">=9.0.0 || >=9.0.0-dev.0 <9.0.0 || =9.0.0-dev || >=8.0.0 <=8.2.0 || >=7.0.0 <=7.5.2"
},
"devDependencies": {
"@mapbox/flow-remove-types": "^2.0.0",
Expand Down
36 changes: 26 additions & 10 deletions src/stylefunction.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,14 +146,15 @@ export function getValue(
* @param {Object} layer Gl object layer.
* @param {number} zoom Zoom.
* @param {Object} feature Gl feature.
* @param {"icon"|"text"} prefix Style property prefix.
* @param {Object} [functionCache] Function cache.
* @return {"declutter"|"obstacle"|"none"} Value.
*/
function getIconDeclutterMode(layer, zoom, feature, functionCache) {
function getDeclutterMode(layer, zoom, feature, prefix, functionCache) {
const allowOverlap = getValue(
layer,
'layout',
'icon-allow-overlap',
`${prefix}-allow-overlap`,
zoom,
feature,
functionCache,
Expand All @@ -164,7 +165,7 @@ function getIconDeclutterMode(layer, zoom, feature, functionCache) {
const ignorePlacement = getValue(
layer,
'layout',
'icon-ignore-placement',
`${prefix}-ignore-placement`,
zoom,
feature,
functionCache,
Expand Down Expand Up @@ -895,10 +896,11 @@ export function stylefunction(
}
iconImg = iconImageCache[iconCacheKey];
if (!iconImg) {
const declutterMode = getIconDeclutterMode(
const declutterMode = getDeclutterMode(
layer,
zoom,
f,
'icon',
functionCache,
);
let displacement;
Expand Down Expand Up @@ -1334,15 +1336,29 @@ export function stylefunction(
style.setImage(undefined);
style.setGeometry(undefined);
}
const declutterMode = getDeclutterMode(
layer,
zoom,
f,
'text',
functionCache,
);
if (!style.getText()) {
style.setText(
text ||
new Text({
padding: [2, 2, 2, 2],
}),
);
style.setText(text);
}
text = style.getText();
if (
!text ||
('getDeclutterMode' in text &&
text.getDeclutterMode() !== declutterMode)
) {
text = new Text({
padding: [2, 2, 2, 2],
// @ts-ignore
declutterMode: declutterMode,
});
style.setText(text);
}
const textTransform = getValue(
layer,
'layout',
Expand Down
226 changes: 156 additions & 70 deletions test/stylefunction.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -424,84 +424,170 @@ describe('stylefunction', function () {
};
});

it('sets the declutter-mode "declutter" if not allow-overlap', function (done) {
style.layers[0].layout['icon-allow-overlap'] = false;
style.layers[0].layout['icon-ignore-placement'] = false;
apply(document.createElement('div'), style)
.then(function (map) {
const layer = map.getLayers().item(0);
layer.once('change', () => {
const styleFunction = layer.getStyle();
const feature = layer.getSource().getFeatures()[0];
const styles = styleFunction(feature, 1);
const image = styles[0].getImage();
should(image.getDeclutterMode()).eql('declutter');
done();
describe('icon decluttering', function () {
it('sets the declutter-mode "declutter" if not allow-overlap', function (done) {
style.layers[0].layout['icon-allow-overlap'] = false;
style.layers[0].layout['icon-ignore-placement'] = false;
apply(document.createElement('div'), style)
.then(function (map) {
const layer = map.getLayers().item(0);
layer.once('change', () => {
const styleFunction = layer.getStyle();
const feature = layer.getSource().getFeatures()[0];
const styles = styleFunction(feature, 1);
const image = styles[0].getImage();
should(image.getDeclutterMode()).eql('declutter');
done();
});
})
.catch(function (err) {
done(err);
});
})
.catch(function (err) {
done(err);
});
});
});

it('sets the declutter-mode "declutter" if not allow-overlap even if ignore-placement', function (done) {
style.layers[0].layout['icon-allow-overlap'] = false;
style.layers[0].layout['icon-ignore-placement'] = true;
apply(document.createElement('div'), style)
.then(function (map) {
const layer = map.getLayers().item(0);
layer.once('change', () => {
const styleFunction = layer.getStyle();
const feature = layer.getSource().getFeatures()[0];
const styles = styleFunction(feature, 1);
const image = styles[0].getImage();
should(image.getDeclutterMode()).eql('declutter');
done();
it('sets the declutter-mode "declutter" if not allow-overlap even if ignore-placement', function (done) {
style.layers[0].layout['icon-allow-overlap'] = false;
style.layers[0].layout['icon-ignore-placement'] = true;
apply(document.createElement('div'), style)
.then(function (map) {
const layer = map.getLayers().item(0);
layer.once('change', () => {
const styleFunction = layer.getStyle();
const feature = layer.getSource().getFeatures()[0];
const styles = styleFunction(feature, 1);
const image = styles[0].getImage();
should(image.getDeclutterMode()).eql('declutter');
done();
});
})
.catch(function (err) {
done(err);
});
})
.catch(function (err) {
done(err);
});
});
});

it('sets the declutter-mode "obstacle" if allow-overlap and not ignore-placement', function (done) {
style.layers[0].layout['icon-allow-overlap'] = true;
style.layers[0].layout['icon-ignore-placement'] = false;
apply(document.createElement('div'), style)
.then(function (map) {
const layer = map.getLayers().item(0);
layer.once('change', () => {
const styleFunction = layer.getStyle();
const feature = layer.getSource().getFeatures()[0];
const styles = styleFunction(feature, 1);
const image = styles[0].getImage();
should(image.getDeclutterMode()).eql('obstacle');
done();
it('sets the declutter-mode "obstacle" if allow-overlap and not ignore-placement', function (done) {
style.layers[0].layout['icon-allow-overlap'] = true;
style.layers[0].layout['icon-ignore-placement'] = false;
apply(document.createElement('div'), style)
.then(function (map) {
const layer = map.getLayers().item(0);
layer.once('change', () => {
const styleFunction = layer.getStyle();
const feature = layer.getSource().getFeatures()[0];
const styles = styleFunction(feature, 1);
const image = styles[0].getImage();
should(image.getDeclutterMode()).eql('obstacle');
done();
});
})
.catch(function (err) {
done(err);
});
})
.catch(function (err) {
done(err);
});
});
});

it('sets the declutter-mode "none" if allow-overlap and ignore-placement', function (done) {
style.layers[0].layout['icon-allow-overlap'] = true;
style.layers[0].layout['icon-ignore-placement'] = true;
apply(document.createElement('div'), style)
.then(function (map) {
const layer = map.getLayers().item(0);
layer.once('change', () => {
const styleFunction = layer.getStyle();
const feature = layer.getSource().getFeatures()[0];
const styles = styleFunction(feature, 1);
const image = styles[0].getImage();
should(image.getDeclutterMode()).eql('none');
done();
it('sets the declutter-mode "none" if allow-overlap and ignore-placement', function (done) {
style.layers[0].layout['icon-allow-overlap'] = true;
style.layers[0].layout['icon-ignore-placement'] = true;
apply(document.createElement('div'), style)
.then(function (map) {
const layer = map.getLayers().item(0);
layer.once('change', () => {
const styleFunction = layer.getStyle();
const feature = layer.getSource().getFeatures()[0];
const styles = styleFunction(feature, 1);
const image = styles[0].getImage();
should(image.getDeclutterMode()).eql('none');
done();
});
})
.catch(function (err) {
done(err);
});
})
.catch(function (err) {
done(err);
});
});

describe('text decluttering', function () {
if ('getDeclutterMode' in Text.prototype) {
it('sets the declutter-mode "declutter" if not allow-overlap', function (done) {
style.layers[0].layout['text-allow-overlap'] = false;
style.layers[0].layout['text-ignore-placement'] = false;
apply(document.createElement('div'), style)
.then(function (map) {
const layer = map.getLayers().item(0);
layer.once('change', () => {
const styleFunction = layer.getStyle();
const feature = layer.getSource().getFeatures()[0];
const styles = styleFunction(feature, 1);
const text = styles[0].getText();
should(text.getDeclutterMode()).eql('declutter');
done();
});
})
.catch(function (err) {
done(err);
});
});

it('sets the declutter-mode "declutter" if not allow-overlap even if ignore-placement', function (done) {
style.layers[0].layout['text-allow-overlap'] = false;
style.layers[0].layout['text-ignore-placement'] = true;
apply(document.createElement('div'), style)
.then(function (map) {
const layer = map.getLayers().item(0);
layer.once('change', () => {
const styleFunction = layer.getStyle();
const feature = layer.getSource().getFeatures()[0];
const styles = styleFunction(feature, 1);
const text = styles[0].getText();
should(text.getDeclutterMode()).eql('declutter');
done();
});
})
.catch(function (err) {
done(err);
});
});

it('sets the declutter-mode "obstacle" if allow-overlap and not ignore-placement', function (done) {
style.layers[0].layout['text-allow-overlap'] = true;
style.layers[0].layout['text-ignore-placement'] = false;
apply(document.createElement('div'), style)
.then(function (map) {
const layer = map.getLayers().item(0);
layer.once('change', () => {
const styleFunction = layer.getStyle();
const feature = layer.getSource().getFeatures()[0];
const styles = styleFunction(feature, 1);
const text = styles[0].getText();
should(text.getDeclutterMode()).eql('obstacle');
done();
});
})
.catch(function (err) {
done(err);
});
});

it('sets the declutter-mode "none" if allow-overlap and ignore-placement', function (done) {
style.layers[0].layout['text-allow-overlap'] = true;
style.layers[0].layout['text-ignore-placement'] = true;
apply(document.createElement('div'), style)
.then(function (map) {
const layer = map.getLayers().item(0);
layer.once('change', () => {
const styleFunction = layer.getStyle();
const feature = layer.getSource().getFeatures()[0];
const styles = styleFunction(feature, 1);
const text = styles[0].getText();
should(text.getDeclutterMode()).eql('none');
done();
});
})
.catch(function (err) {
done(err);
});
});
}
});
});

Expand Down

0 comments on commit df32938

Please sign in to comment.