From 2f874593f7d7ab6effca6c354794ed094be796af Mon Sep 17 00:00:00 2001 From: Andreas Hocevar Date: Sun, 28 Jan 2024 21:48:43 +0100 Subject: [PATCH] Add support for text-ignore-placement and text-allow-overlap --- package-lock.json | 2 +- package.json | 2 +- src/stylefunction.js | 36 ++++-- test/stylefunction.test.js | 226 +++++++++++++++++++++++++------------ 4 files changed, 184 insertions(+), 82 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8c072327..286a626b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -60,7 +60,7 @@ "webpack-dev-server": "^4.4.0" }, "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" } }, "node_modules/@aashutoshrathi/word-wrap": { diff --git a/package.json b/package.json index db314bb1..885b65f1 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/stylefunction.js b/src/stylefunction.js index 4d4f3bbb..9a86c1c7 100644 --- a/src/stylefunction.js +++ b/src/stylefunction.js @@ -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, @@ -164,7 +165,7 @@ function getIconDeclutterMode(layer, zoom, feature, functionCache) { const ignorePlacement = getValue( layer, 'layout', - 'icon-ignore-placement', + `${prefix}-ignore-placement`, zoom, feature, functionCache, @@ -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; @@ -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', diff --git a/test/stylefunction.test.js b/test/stylefunction.test.js index bb0b87c3..4dd1fdfe 100644 --- a/test/stylefunction.test.js +++ b/test/stylefunction.test.js @@ -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); + }); + }); + } }); });