From 71951b8bbd3f1a4ac7849604516affe8c6cf230f Mon Sep 17 00:00:00 2001 From: oterral Date: Thu, 8 May 2014 14:20:19 +0200 Subject: [PATCH 1/4] Add offset and offsetOrigin in ol.style.Icon --- externs/olx.js | 21 ++++++--- src/ol/style/circlestyle.js | 16 ++++++- src/ol/style/iconstyle.js | 89 +++++++++++++++++++++++++++---------- src/ol/style/imagestyle.js | 22 +++------ 4 files changed, 103 insertions(+), 45 deletions(-) diff --git a/externs/olx.js b/externs/olx.js index 0cb5839951..81bdc6ccff 100644 --- a/externs/olx.js +++ b/externs/olx.js @@ -4491,12 +4491,13 @@ olx.style.FillOptions.prototype.color; /** * @typedef {{anchor: (Array.|undefined), - * anchorOrigin: (ol.style.IconAnchorOrigin|undefined), + * anchorOrigin: (ol.style.IconOrigin|undefined), * anchorXUnits: (ol.style.IconAnchorUnits|undefined), * anchorYUnits: (ol.style.IconAnchorUnits|undefined), * crossOrigin: (null|string|undefined), * img: (Image|undefined), - * origin: (Array.|undefined), + * offset: (Array.|undefined), + * offsetOrigin: (ol.style.IconOrigin|undefined), * scale: (number|undefined), * snapToPixel: (boolean|undefined), * rotateWithView: (boolean|undefined), @@ -4518,7 +4519,7 @@ olx.style.IconOptions.prototype.anchor; /** * Origin of the anchor: `bottom-left`, `bottom-right`, `top-left` or * `top-right`. Default is `top-left`. - * @type {ol.style.IconAnchorOrigin|undefined} + * @type {ol.style.IconOrigin|undefined} */ olx.style.IconOptions.prototype.anchorOrigin; @@ -4557,12 +4558,20 @@ olx.style.IconOptions.prototype.img; /** - * The top left corner, which, together with the size, define the - * sub-rectangle to use from the original icon image. Default value + * Offset, which, together with the size and the offset origin, + * define the sub-rectangle to use from the original icon image. Default value * is `[0, 0]`. * @type {Array.|undefined} */ -olx.style.IconOptions.prototype.origin; +olx.style.IconOptions.prototype.offset; + + +/** + * Origin of the offset: `bottom-left`, `bottom-right`, `top-left` or + * `top-right`. Default is `top-left`. + * @type {ol.style.IconOrigin|undefined} + */ +olx.style.IconOptions.prototype.offsetOrigin; /** diff --git a/src/ol/style/circlestyle.js b/src/ol/style/circlestyle.js index 564dbd5edd..1c25e013f9 100644 --- a/src/ol/style/circlestyle.js +++ b/src/ol/style/circlestyle.js @@ -40,6 +40,12 @@ ol.style.Circle = function(opt_options) { */ this.fill_ = goog.isDef(options.fill) ? options.fill : null; + /** + * @private + * @type {Array.} + */ + this.origin_ = [0, 0]; + /** * @private * @type {number} @@ -74,7 +80,6 @@ ol.style.Circle = function(opt_options) { goog.base(this, { opacity: 1, - origin: [0, 0], rotateWithView: false, rotation: 0, scale: 1, @@ -128,6 +133,15 @@ ol.style.Circle.prototype.getImageState = function() { }; +/** + * @inheritDoc + * @todo api + */ +ol.style.Circle.prototype.getOrigin = function() { + return this.origin_; +}; + + /** * @return {number} Radius. * @todo api diff --git a/src/ol/style/iconstyle.js b/src/ol/style/iconstyle.js index 6de655a9b4..e9581b4e16 100644 --- a/src/ol/style/iconstyle.js +++ b/src/ol/style/iconstyle.js @@ -1,7 +1,7 @@ goog.provide('ol.style.Icon'); -goog.provide('ol.style.IconAnchorOrigin'); goog.provide('ol.style.IconAnchorUnits'); goog.provide('ol.style.IconImageCache'); +goog.provide('ol.style.IconOrigin'); goog.require('goog.array'); goog.require('goog.asserts'); @@ -16,20 +16,20 @@ goog.require('ol.style.ImageState'); /** * @enum {string} */ -ol.style.IconAnchorOrigin = { - BOTTOM_LEFT: 'bottom-left', - BOTTOM_RIGHT: 'bottom-right', - TOP_LEFT: 'top-left', - TOP_RIGHT: 'top-right' +ol.style.IconAnchorUnits = { + FRACTION: 'fraction', + PIXELS: 'pixels' }; /** * @enum {string} */ -ol.style.IconAnchorUnits = { - FRACTION: 'fraction', - PIXELS: 'pixels' +ol.style.IconOrigin = { + BOTTOM_LEFT: 'bottom-left', + BOTTOM_RIGHT: 'bottom-right', + TOP_LEFT: 'top-left', + TOP_RIGHT: 'top-right' }; @@ -58,10 +58,10 @@ ol.style.Icon = function(opt_options) { /** * @private - * @type {ol.style.IconAnchorOrigin} + * @type {ol.style.IconOrigin} */ this.anchorOrigin_ = goog.isDef(options.anchorOrigin) ? - options.anchorOrigin : ol.style.IconAnchorOrigin.TOP_LEFT; + options.anchorOrigin : ol.style.IconOrigin.TOP_LEFT; /** * @private @@ -111,6 +111,25 @@ ol.style.Icon = function(opt_options) { this.iconImage_ = ol.style.IconImage_.get( image, src, crossOrigin, imageState); + /** + * @private + * @type {Array.} + */ + this.offset_ = goog.isDef(options.offset) ? options.offset : [0, 0]; + + /** + * @private + * @type {ol.style.IconOrigin} + */ + this.offsetOrigin_ = goog.isDef(options.offsetOrigin) ? + options.offsetOrigin : ol.style.IconOrigin.TOP_LEFT; + + /** + * @private + * @type {Array.} + */ + this.origin_ = null; + /** * @private * @type {ol.Size} @@ -122,12 +141,6 @@ ol.style.Icon = function(opt_options) { */ var opacity = goog.isDef(options.opacity) ? options.opacity : 1; - /** - * @private - * @type {Array.} - */ - var origin = goog.isDef(options.origin) ? options.origin : [0, 0]; - /** * @type {boolean} */ @@ -152,7 +165,6 @@ ol.style.Icon = function(opt_options) { goog.base(this, { opacity: opacity, - origin: origin, rotation: rotation, scale: scale, snapToPixel: snapToPixel, @@ -187,19 +199,19 @@ ol.style.Icon.prototype.getAnchor = function() { } } - if (this.anchorOrigin_ != ol.style.IconAnchorOrigin.TOP_LEFT) { + if (this.anchorOrigin_ != ol.style.IconOrigin.TOP_LEFT) { if (goog.isNull(size)) { return null; } if (anchor === this.anchor_) { anchor = this.anchor_.slice(); } - if (this.anchorOrigin_ == ol.style.IconAnchorOrigin.TOP_RIGHT || - this.anchorOrigin_ == ol.style.IconAnchorOrigin.BOTTOM_RIGHT) { + if (this.anchorOrigin_ == ol.style.IconOrigin.TOP_RIGHT || + this.anchorOrigin_ == ol.style.IconOrigin.BOTTOM_RIGHT) { anchor[0] = -anchor[0] + size[0]; } - if (this.anchorOrigin_ == ol.style.IconAnchorOrigin.BOTTOM_LEFT || - this.anchorOrigin_ == ol.style.IconAnchorOrigin.BOTTOM_RIGHT) { + if (this.anchorOrigin_ == ol.style.IconOrigin.BOTTOM_LEFT || + this.anchorOrigin_ == ol.style.IconOrigin.BOTTOM_RIGHT) { anchor[1] = -anchor[1] + size[1]; } } @@ -233,6 +245,37 @@ ol.style.Icon.prototype.getHitDetectionImage = function(pixelRatio) { }; +/** + * @inheritDoc + * @todo api + */ +ol.style.Icon.prototype.getOrigin = function() { + if (!goog.isNull(this.origin_)) { + return this.origin_; + } + var offset = this.offset_; + + if (this.offsetOrigin_ != ol.style.IconOrigin.TOP_LEFT) { + var size = this.getSize(); + var iconImageSize = this.iconImage_.getSize(); + if (goog.isNull(size) || goog.isNull(iconImageSize)) { + return null; + } + offset = offset.slice(); + if (this.offsetOrigin_ == ol.style.IconOrigin.TOP_RIGHT || + this.offsetOrigin_ == ol.style.IconOrigin.BOTTOM_RIGHT) { + offset[0] = iconImageSize[0] - size[0] - offset[0]; + } + if (this.offsetOrigin_ == ol.style.IconOrigin.BOTTOM_LEFT || + this.offsetOrigin_ == ol.style.IconOrigin.BOTTOM_RIGHT) { + offset[1] = iconImageSize[1] - size[1] - offset[1]; + } + } + this.origin_ = offset; + return this.origin_; +}; + + /** * @return {string|undefined} Image src. * @todo api diff --git a/src/ol/style/imagestyle.js b/src/ol/style/imagestyle.js index 787f308f4e..db63ef8937 100644 --- a/src/ol/style/imagestyle.js +++ b/src/ol/style/imagestyle.js @@ -15,7 +15,6 @@ ol.style.ImageState = { /** * @typedef {{opacity: number, - * origin: Array., * rotateWithView: boolean, * rotation: number, * scale: number, @@ -37,12 +36,6 @@ ol.style.Image = function(options) { */ this.opacity_ = options.opacity; - /** - * @private - * @type {Array.} - */ - this.origin_ = options.origin; - /** * @private * @type {boolean} @@ -78,14 +71,6 @@ ol.style.Image.prototype.getOpacity = function() { }; -/** - * @return {Array.} Origin. - */ -ol.style.Image.prototype.getOrigin = function() { - return this.origin_; -}; - - /** * @return {boolean} Rotate with map. */ @@ -148,6 +133,13 @@ ol.style.Image.prototype.getImageState = goog.abstractMethod; ol.style.Image.prototype.getHitDetectionImage = goog.abstractMethod; +/** + * @function + * @return {Array.} Origin. + */ +ol.style.Image.prototype.getOrigin = goog.abstractMethod; + + /** * @function * @return {ol.Size} Size. From 0b17d9aeb53d21a1870e45cd4c65055e51c92928 Mon Sep 17 00:00:00 2001 From: oterral Date: Thu, 8 May 2014 14:21:43 +0200 Subject: [PATCH 2/4] Add parsing of gx:x, gx:y, gx:w and gx:h tags in KML format --- src/ol/format/kmlformat.js | 37 +++++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/src/ol/format/kmlformat.js b/src/ol/format/kmlformat.js index af9351e789..5637d6fa19 100644 --- a/src/ol/format/kmlformat.js +++ b/src/ol/format/kmlformat.js @@ -27,8 +27,8 @@ goog.require('ol.geom.Polygon'); goog.require('ol.proj'); goog.require('ol.style.Fill'); goog.require('ol.style.Icon'); -goog.require('ol.style.IconAnchorOrigin'); goog.require('ol.style.IconAnchorUnits'); +goog.require('ol.style.IconOrigin'); goog.require('ol.style.Image'); goog.require('ol.style.Stroke'); goog.require('ol.style.Style'); @@ -413,10 +413,6 @@ ol.format.KML.readStyleMapValue_ = function(node, objectStack) { ol.format.KML.IconStyleParser_ = function(node, objectStack) { goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); goog.asserts.assert(node.localName == 'IconStyle'); - // FIXME gx:x - // FIXME gx:y - // FIXME gx:w - // FIXME gx:h // FIXME refreshMode // FIXME refreshInterval // FIXME viewRefreshTime @@ -456,6 +452,24 @@ ol.format.KML.IconStyleParser_ = function(node, objectStack) { anchorYUnits = ol.style.IconAnchorUnits.FRACTION; } + var offset; + var x = /** @type {number|undefined} */ + (goog.object.get(IconObject, 'x')); + var y = /** @type {number|undefined} */ + (goog.object.get(IconObject, 'y')); + if (goog.isDef(x) && goog.isDef(y)) { + offset = [x, y]; + } + + var size; + var w = /** @type {number|undefined} */ + (goog.object.get(IconObject, 'w')); + var h = /** @type {number|undefined} */ + (goog.object.get(IconObject, 'h')); + if (goog.isDef(w) && goog.isDef(h)) { + size = [w, h]; + } + var rotation; var heading = /** @type {number|undefined} */ (goog.object.get(object, 'heading')); @@ -465,17 +479,18 @@ ol.format.KML.IconStyleParser_ = function(node, objectStack) { var scale = /** @type {number|undefined} */ (goog.object.get(object, 'scale')); - var size; if (src == ol.format.KML.DEFAULT_IMAGE_STYLE_SRC_) { size = ol.format.KML.DEFAULT_IMAGE_STYLE_SIZE_; } var imageStyle = new ol.style.Icon({ anchor: anchor, - anchorOrigin: ol.style.IconAnchorOrigin.BOTTOM_LEFT, + anchorOrigin: ol.style.IconOrigin.BOTTOM_LEFT, anchorXUnits: anchorXUnits, anchorYUnits: anchorYUnits, crossOrigin: 'anonymous', // FIXME should this be configurable? + offset: offset, + offsetOrigin: ol.style.IconOrigin.BOTTOM_LEFT, rotation: rotation, scale: scale, size: size, @@ -1158,7 +1173,13 @@ ol.format.KML.GEOMETRY_FLAT_COORDINATES_PARSERS_ = ol.xml.makeParsersNS( ol.format.KML.ICON_PARSERS_ = ol.xml.makeParsersNS( ol.format.KML.NAMESPACE_URIS_, { 'href': ol.xml.makeObjectPropertySetter(ol.format.KML.readURI_) - }); + }, ol.xml.makeParsersNS( + ol.format.KML.GX_NAMESPACE_URIS_, { + 'x': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), + 'y': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), + 'w': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), + 'h': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal) + })); /** From 2961970f598e3d5f2c356c79267e190b781dde74 Mon Sep 17 00:00:00 2001 From: oterral Date: Wed, 4 Jun 2014 10:40:02 +0200 Subject: [PATCH 3/4] Add ol.style.Icon tests for getAnchor and getOrigin --- test/spec/ol/style/iconstyle.test.js | 111 +++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) diff --git a/test/spec/ol/style/iconstyle.test.js b/test/spec/ol/style/iconstyle.test.js index f6d7dfdafb..e84d55aba1 100644 --- a/test/spec/ol/style/iconstyle.test.js +++ b/test/spec/ol/style/iconstyle.test.js @@ -1,5 +1,116 @@ +goog.provide('ol.test.style.Icon'); goog.provide('ol.test.style.IconImageCache'); +goog.require('ol.style.Icon'); +goog.require('ol.style.IconAnchorUnits'); +goog.require('ol.style.IconOrigin'); + + +describe('ol.style.Icon', function() { + var size = [36, 48]; + + describe('#getAnchor', function() { + var fractionAnchor = [0.25, 0.25]; + + it('uses fractional units by default', function() { + var iconStyle = new ol.style.Icon({ + src: 'test.png', + size: size, + anchor: fractionAnchor + }); + expect(iconStyle.getAnchor()).to.eql([9, 12]); + }); + + it('uses pixels units', function() { + var iconStyle = new ol.style.Icon({ + src: 'test.png', + size: size, + anchor: [2, 18], + anchorXUnits: ol.style.IconAnchorUnits.PIXELS, + anchorYUnits: ol.style.IconAnchorUnits.PIXELS + }); + expect(iconStyle.getAnchor()).to.eql([2, 18]); + }); + + it('uses a bottom left anchor origin', function() { + var iconStyle = new ol.style.Icon({ + src: 'test.png', + size: size, + anchor: fractionAnchor, + anchorOrigin: ol.style.IconOrigin.BOTTOM_LEFT + }); + expect(iconStyle.getAnchor()).to.eql([9, 36]); + }); + + it('uses a bottom right anchor origin', function() { + var iconStyle = new ol.style.Icon({ + src: 'test.png', + size: size, + anchor: fractionAnchor, + anchorOrigin: ol.style.IconOrigin.BOTTOM_RIGHT + }); + expect(iconStyle.getAnchor()).to.eql([27, 36]); + }); + + it('uses a top right anchor origin', function() { + var iconStyle = new ol.style.Icon({ + src: 'test.png', + size: size, + anchor: fractionAnchor, + anchorOrigin: ol.style.IconOrigin.TOP_RIGHT + }); + expect(iconStyle.getAnchor()).to.eql([27, 12]); + }); + }); + + describe('#getOrigin', function() { + var offset = [16, 20]; + var imageSize = [144, 192]; + + it('uses a top left offset origin (default)', function() { + var iconStyle = new ol.style.Icon({ + src: 'test.png', + size: size, + offset: offset + }); + expect(iconStyle.getOrigin()).to.eql([16, 20]); + }); + + it('uses a bottom left offset origin', function() { + var iconStyle = new ol.style.Icon({ + src: 'test.png', + size: size, + offset: offset, + offsetOrigin: ol.style.IconOrigin.BOTTOM_LEFT + }); + iconStyle.iconImage_.size_ = imageSize; + expect(iconStyle.getOrigin()).to.eql([16, 124]); + }); + + it('uses a bottom right offset origin', function() { + var iconStyle = new ol.style.Icon({ + src: 'test.png', + size: size, + offset: offset, + offsetOrigin: ol.style.IconOrigin.BOTTOM_RIGHT + }); + iconStyle.iconImage_.size_ = imageSize; + expect(iconStyle.getOrigin()).to.eql([92, 124]); + }); + + it('uses a top right offset origin', function() { + var iconStyle = new ol.style.Icon({ + src: 'test.png', + size: size, + offset: offset, + offsetOrigin: ol.style.IconOrigin.TOP_RIGHT + }); + iconStyle.iconImage_.size_ = imageSize; + expect(iconStyle.getOrigin()).to.eql([92, 20]); + }); + }); +}); + describe('ol.style.IconImageCache', function() { var originalMaxCacheSize; From b4851e7927d41703bbbb9275c489a3f183743efc Mon Sep 17 00:00:00 2001 From: oterral Date: Wed, 4 Jun 2014 10:46:27 +0200 Subject: [PATCH 4/4] Add ol.format.KML test for complex IconStyle --- test/spec/ol/format/kmlformat.test.js | 44 +++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/test/spec/ol/format/kmlformat.test.js b/test/spec/ol/format/kmlformat.test.js index 25986592bb..c891f2cd35 100644 --- a/test/spec/ol/format/kmlformat.test.js +++ b/test/spec/ol/format/kmlformat.test.js @@ -589,12 +589,56 @@ describe('ol.format.KML', function() { expect(imageStyle).to.be.an(ol.style.Icon); expect(imageStyle.getSrc()).to.eql('http://foo.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(style.getText()).to.be(null); expect(style.getZIndex()).to.be(undefined); }); + it('can read a complex feature\'s IconStyle', function() { + var text = + '' + + ' ' + + ' ' + + ' ' + + ''; + var fs = format.readFeatures(text); + expect(fs).to.have.length(1); + var f = fs[0]; + expect(f).to.be.an(ol.Feature); + var styleFunction = f.getStyleFunction(); + expect(styleFunction).not.to.be(undefined); + var styleArray = styleFunction.call(f, 0); + expect(styleArray).to.be.an(Array); + expect(styleArray).to.have.length(1); + var style = styleArray[0]; + expect(style).to.be.an(ol.style.Style); + expect(style.getFill()).to.be(ol.format.KML.DEFAULT_FILL_STYLE_); + expect(style.getStroke()).to.be(ol.format.KML.DEFAULT_STROKE_STYLE_); + var imageStyle = style.getImage(); + imageStyle.iconImage_.size_ = [144, 192]; + expect(imageStyle.getSize()).to.eql([48, 48]); + expect(imageStyle.getAnchor()).to.eql([24, 36]); + expect(imageStyle.getOrigin()).to.eql([24, 108]); + expect(imageStyle.getRotation()).to.eql(0); + expect(style.getText()).to.be(null); + expect(style.getZIndex()).to.be(undefined); + }); + it('can read a feature\'s LineStyle', function() { var text = '' +