diff --git a/src/ol/format/KML.js b/src/ol/format/KML.js index 27c00fd9ee..fbef902363 100644 --- a/src/ol/format/KML.js +++ b/src/ol/format/KML.js @@ -9,6 +9,7 @@ import GeometryType from '../geom/GeometryType.js'; import Icon from '../style/Icon.js'; import IconAnchorUnits from '../style/IconAnchorUnits.js'; import IconOrigin from '../style/IconOrigin.js'; +import ImageState from '../ImageState.js'; import LineString from '../geom/LineString.js'; import MultiLineString from '../geom/MultiLineString.js'; import MultiPoint from '../geom/MultiPoint.js'; @@ -231,11 +232,6 @@ let DEFAULT_IMAGE_STYLE_SIZE; */ let DEFAULT_IMAGE_STYLE_SRC; -/** - * @type {number} - */ -let DEFAULT_IMAGE_SCALE_MULTIPLIER; - /** * @type {import("../style/Image.js").default} */ @@ -311,6 +307,15 @@ export function getDefaultStyleArray() { return DEFAULT_STYLE_ARRAY; } +/** + * Function that returns the scale needed to normalize an icon image to 32 pixels. + * @param {import("../size.js").Size} size Image size. + * @return {number} Scale. + */ +function scaleForSize(size) { + return 32 / Math.min(size[0], size[1]); +} + function createStyleDefaults() { DEFAULT_COLOR = [255, 255, 255, 1]; @@ -318,7 +323,7 @@ function createStyleDefaults() { color: DEFAULT_COLOR, }); - DEFAULT_IMAGE_STYLE_ANCHOR = [20, 2]; // FIXME maybe [8, 32] ? + DEFAULT_IMAGE_STYLE_ANCHOR = [20, 2]; DEFAULT_IMAGE_STYLE_ANCHOR_X_UNITS = IconAnchorUnits.PIXELS; @@ -329,8 +334,6 @@ function createStyleDefaults() { DEFAULT_IMAGE_STYLE_SRC = 'https://maps.google.com/mapfiles/kml/pushpin/ylw-pushpin.png'; - DEFAULT_IMAGE_SCALE_MULTIPLIER = 0.5; - DEFAULT_IMAGE_STYLE = new Icon({ anchor: DEFAULT_IMAGE_STYLE_ANCHOR, anchorOrigin: IconOrigin.BOTTOM_LEFT, @@ -338,7 +341,7 @@ function createStyleDefaults() { anchorYUnits: DEFAULT_IMAGE_STYLE_ANCHOR_Y_UNITS, crossOrigin: 'anonymous', rotation: 0, - scale: DEFAULT_IMAGE_SCALE_MULTIPLIER, + scale: scaleForSize(DEFAULT_IMAGE_STYLE_SIZE), size: DEFAULT_IMAGE_STYLE_SIZE, src: DEFAULT_IMAGE_STYLE_SRC, }); @@ -941,16 +944,14 @@ function createNameStyleFunction(foundStyle, name) { let textAlign = 'start'; const imageStyle = foundStyle.getImage(); if (imageStyle) { - let imageSize = imageStyle.getImageSize(); - if (imageSize === null) { - imageSize = DEFAULT_IMAGE_STYLE_SIZE; - } - if (imageSize.length == 2) { + const imageSize = imageStyle.getSize(); + if (imageSize && imageSize.length == 2) { const imageScale = imageStyle.getScaleArray(); + const anchor = imageStyle.getAnchor(); // Offset the label to be centered to the right of the icon, // if there is one. - textOffset[0] = (imageScale[0] * imageSize[0]) / 2; - textOffset[1] = (-imageScale[1] * imageSize[1]) / 2; + textOffset[0] = imageScale[0] * (imageSize[0] - anchor[0]); + textOffset[1] = imageScale[1] * (imageSize[1] / 2 - anchor[1]); textAlign = 'left'; } } @@ -1278,14 +1279,21 @@ function iconStyleParser(node, objectStack) { anchorXUnits = hotSpot.xunits; anchorYUnits = hotSpot.yunits; anchorOrigin = hotSpot.origin; - } else if (src === DEFAULT_IMAGE_STYLE_SRC) { - anchor = DEFAULT_IMAGE_STYLE_ANCHOR; - anchorXUnits = DEFAULT_IMAGE_STYLE_ANCHOR_X_UNITS; - anchorYUnits = DEFAULT_IMAGE_STYLE_ANCHOR_Y_UNITS; } else if (/^http:\/\/maps\.(?:google|gstatic)\.com\//.test(src)) { - anchor = [0.5, 0]; - anchorXUnits = IconAnchorUnits.FRACTION; - anchorYUnits = IconAnchorUnits.FRACTION; + // Google hotspots from https://kml4earth.appspot.com/icons.html#notes + if (/pushpin/.test(src)) { + anchor = DEFAULT_IMAGE_STYLE_ANCHOR; + anchorXUnits = DEFAULT_IMAGE_STYLE_ANCHOR_X_UNITS; + anchorYUnits = DEFAULT_IMAGE_STYLE_ANCHOR_Y_UNITS; + } else if (/arrow-reverse/.test(src)) { + anchor = [54, 42]; + anchorXUnits = DEFAULT_IMAGE_STYLE_ANCHOR_X_UNITS; + anchorYUnits = DEFAULT_IMAGE_STYLE_ANCHOR_Y_UNITS; + } else if (/paddle/.test(src)) { + anchor = [32, 1]; + anchorXUnits = DEFAULT_IMAGE_STYLE_ANCHOR_X_UNITS; + anchorYUnits = DEFAULT_IMAGE_STYLE_ANCHOR_Y_UNITS; + } } let offset; @@ -1308,16 +1316,13 @@ function iconStyleParser(node, objectStack) { rotation = toRadians(heading); } - let scale = /** @type {number|undefined} */ (object['scale']); + const scale = /** @type {number|undefined} */ (object['scale']); const color = /** @type {Array|undefined} */ (object['color']); if (drawIcon) { if (src == DEFAULT_IMAGE_STYLE_SRC) { size = DEFAULT_IMAGE_STYLE_SIZE; - if (scale === undefined) { - scale = DEFAULT_IMAGE_SCALE_MULTIPLIER; - } } const imageStyle = new Icon({ @@ -1334,6 +1339,37 @@ function iconStyleParser(node, objectStack) { src: this.iconUrlFunction_(src), color: color, }); + + const imageScale = imageStyle.getScaleArray()[0]; + const imageSize = imageStyle.getSize(); + if (imageSize === null) { + const imageState = imageStyle.getImageState(); + if (imageState === ImageState.IDLE || imageState === ImageState.LOADING) { + const listener = function () { + const imageState = imageStyle.getImageState(); + if ( + !( + imageState === ImageState.IDLE || + imageState === ImageState.LOADING + ) + ) { + const imageSize = imageStyle.getSize(); + if (imageSize && imageSize.length == 2) { + const resizeScale = scaleForSize(imageSize); + imageStyle.setScale(imageScale * resizeScale); + } + imageStyle.unlistenImageChange(listener); + } + }; + imageStyle.listenImageChange(listener); + if (imageState === ImageState.IDLE) { + imageStyle.load(); + } + } + } else if (imageSize.length == 2) { + const resizeScale = scaleForSize(imageSize); + imageStyle.setScale(imageScale * resizeScale); + } styleObject['imageStyle'] = imageStyle; } else { // handle the case when we explicitly want to draw no icon. @@ -2619,7 +2655,15 @@ function writeIconStyle(node, style, objectStack) { properties['Icon'] = iconProperties; - const scale = style.getScale(); + let scale = style.getScaleArray()[0]; + let imageSize = size; + if (imageSize === null) { + imageSize = DEFAULT_IMAGE_STYLE_SIZE; + } + if (imageSize.length == 2) { + const resizeScale = scaleForSize(imageSize); + scale = scale / resizeScale; + } if (scale !== 1) { properties['scale'] = scale; } diff --git a/test/browser/spec/ol/format/kml.test.js b/test/browser/spec/ol/format/kml.test.js index a1115ae986..fc44e39305 100644 --- a/test/browser/spec/ol/format/kml.test.js +++ b/test/browser/spec/ol/format/kml.test.js @@ -2299,6 +2299,56 @@ describe('ol.format.KML', function () { expect(style.getZIndex()).to.be(undefined); }); + it("can read a feature's IconStyle, load the image and reset the scale", function (done) { + format = new KML({ + iconUrlFunction: function (href) { + return href.replace('http://foo/', 'spec/ol/data/'); + }, + }); + const text = + '' + + ' ' + + ' ' + + ' ' + + ''; + const fs = format.readFeatures(text); + expect(fs).to.have.length(1); + const f = fs[0]; + expect(f).to.be.an(Feature); + const styleFunction = f.getStyleFunction(); + expect(styleFunction).not.to.be(undefined); + const styleArray = styleFunction(f, 0); + expect(styleArray).to.be.an(Array); + expect(styleArray).to.have.length(1); + const style = styleArray[0]; + expect(style).to.be.an(Style); + expect(style.getFill()).to.be(getDefaultFillStyle()); + expect(style.getStroke()).to.be(getDefaultStrokeStyle()); + const imageStyle = style.getImage(); + expect(imageStyle).to.be.an(Icon); + expect(imageStyle.getSrc()).to.eql('spec/ol/data/dot.png'); + expect(imageStyle.getAnchor()).to.be(null); + expect(imageStyle.getOrigin()).to.be(null); + expect(imageStyle.getRotation()).to.eql(0); + expect(imageStyle.getSize()).to.be(null); + expect(imageStyle.getScale()).to.be(1); + expect(imageStyle.getImage().crossOrigin).to.eql('anonymous'); + expect(style.getText()).to.be(getDefaultTextStyle()); + expect(style.getZIndex()).to.be(undefined); + + setTimeout(function () { + expect(imageStyle.getSize()).to.eql([20, 20]); + expect(imageStyle.getScale()).to.be(1.6); // 32 / 20 + done(); + }, 200); + }); + it("can read a IconStyle's hotspot", function () { const text = '' + @@ -2446,7 +2496,7 @@ describe('ol.format.KML', function () { expect(imageStyle.getAnchor()).to.eql([24, 36]); expect(imageStyle.getOrigin()).to.eql([24, 108]); expect(imageStyle.getRotation()).to.eql(0); - expect(imageStyle.getScale()).to.eql(3.0); + expect(imageStyle.getScale()).to.eql(2.0); // 3.0 * 32 / 48 expect(style.getText()).to.be(getDefaultTextStyle()); expect(style.getZIndex()).to.be(undefined); }); @@ -3027,7 +3077,7 @@ describe('ol.format.KML', function () { ' ' + '