diff --git a/externs/olx.js b/externs/olx.js index 5b3edcd18f..2c4f13a943 100644 --- a/externs/olx.js +++ b/externs/olx.js @@ -1538,7 +1538,8 @@ olx.format; /** * @typedef {{dataProjection: ol.proj.ProjectionLike, - * featureProjection: ol.proj.ProjectionLike}} + * featureProjection: ol.proj.ProjectionLike, + * rightHanded: (boolean|undefined)}} * @api */ olx.format.ReadOptions; @@ -1567,7 +1568,8 @@ olx.format.ReadOptions.prototype.featureProjection; /** * @typedef {{dataProjection: ol.proj.ProjectionLike, - * featureProjection: ol.proj.ProjectionLike}} + * featureProjection: ol.proj.ProjectionLike, + * rightHanded: (boolean|undefined)}} * @api */ olx.format.WriteOptions; @@ -1593,6 +1595,22 @@ olx.format.WriteOptions.prototype.dataProjection; olx.format.WriteOptions.prototype.featureProjection; +/** + * When writing geometries, follow the right-hand rule for linear ring + * orientation. This means that polygons will have counter-clockwise exterior + * rings and clockwise interior rings. By default, coordinates are serialized + * as they are provided at construction. If `true`, the right-hand rule will + * be applied. If `false`, the left-hand rule will be applied (clockwise for + * exterior and counter-clockwise for interior rings). Note that not all + * formats support this. The GeoJSON format does use this property when writing + * geometries. + * + * @type {boolean|undefined} + * @api + */ +olx.format.WriteOptions.prototype.rightHanded; + + /** * @typedef {{defaultDataProjection: ol.proj.ProjectionLike, * geometryName: (string|undefined)}} diff --git a/src/ol/format/featureformat.js b/src/ol/format/featureformat.js index a553dc1a70..d06f7a1b51 100644 --- a/src/ol/format/featureformat.js +++ b/src/ol/format/featureformat.js @@ -68,7 +68,8 @@ ol.format.Feature.prototype.adaptOptions = function(options) { updatedOptions = { featureProjection: options.featureProjection, dataProjection: goog.isDefAndNotNull(options.dataProjection) ? - options.dataProjection : this.defaultDataProjection + options.dataProjection : this.defaultDataProjection, + rightHanded: options.rightHanded }; } return updatedOptions; diff --git a/src/ol/format/geojsonformat.js b/src/ol/format/geojsonformat.js index ffe142ede1..0a0d86689d 100644 --- a/src/ol/format/geojsonformat.js +++ b/src/ol/format/geojsonformat.js @@ -178,7 +178,8 @@ ol.format.GeoJSON.writeGeometry_ = function(geometry, opt_options) { var geometryWriter = ol.format.GeoJSON.GEOMETRY_WRITERS_[geometry.getType()]; goog.asserts.assert(goog.isDef(geometryWriter)); return geometryWriter(/** @type {ol.geom.Geometry} */ ( - ol.format.Feature.transformWithOptions(geometry, true, opt_options))); + ol.format.Feature.transformWithOptions(geometry, true, opt_options)), + opt_options); }; @@ -217,10 +218,11 @@ ol.format.GeoJSON.writeGeometryCollectionGeometry_ = function( /** * @param {ol.geom.Geometry} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. * @private * @return {GeoJSONGeometry} GeoJSON geometry. */ -ol.format.GeoJSON.writeLineStringGeometry_ = function(geometry) { +ol.format.GeoJSON.writeLineStringGeometry_ = function(geometry, opt_options) { goog.asserts.assertInstanceof(geometry, ol.geom.LineString); return /** @type {GeoJSONGeometry} */ ({ 'type': 'LineString', @@ -231,10 +233,12 @@ ol.format.GeoJSON.writeLineStringGeometry_ = function(geometry) { /** * @param {ol.geom.Geometry} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. * @private * @return {GeoJSONGeometry} GeoJSON geometry. */ -ol.format.GeoJSON.writeMultiLineStringGeometry_ = function(geometry) { +ol.format.GeoJSON.writeMultiLineStringGeometry_ = + function(geometry, opt_options) { goog.asserts.assertInstanceof(geometry, ol.geom.MultiLineString); goog.asserts.assert( geometry.getType() == ol.geom.GeometryType.MULTI_LINE_STRING); @@ -247,10 +251,11 @@ ol.format.GeoJSON.writeMultiLineStringGeometry_ = function(geometry) { /** * @param {ol.geom.Geometry} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. * @private * @return {GeoJSONGeometry} GeoJSON geometry. */ -ol.format.GeoJSON.writeMultiPointGeometry_ = function(geometry) { +ol.format.GeoJSON.writeMultiPointGeometry_ = function(geometry, opt_options) { goog.asserts.assertInstanceof(geometry, ol.geom.MultiPoint); return /** @type {GeoJSONGeometry} */ ({ 'type': 'MultiPoint', @@ -261,24 +266,30 @@ ol.format.GeoJSON.writeMultiPointGeometry_ = function(geometry) { /** * @param {ol.geom.Geometry} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. * @private * @return {GeoJSONGeometry} GeoJSON geometry. */ -ol.format.GeoJSON.writeMultiPolygonGeometry_ = function(geometry) { +ol.format.GeoJSON.writeMultiPolygonGeometry_ = function(geometry, opt_options) { goog.asserts.assertInstanceof(geometry, ol.geom.MultiPolygon); + var right; + if (goog.isDef(opt_options)) { + right = opt_options.rightHanded; + } return /** @type {GeoJSONGeometry} */ ({ 'type': 'MultiPolygon', - 'coordinates': geometry.getCoordinates() + 'coordinates': geometry.getCoordinates(right) }); }; /** * @param {ol.geom.Geometry} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. * @private * @return {GeoJSONGeometry} GeoJSON geometry. */ -ol.format.GeoJSON.writePointGeometry_ = function(geometry) { +ol.format.GeoJSON.writePointGeometry_ = function(geometry, opt_options) { goog.asserts.assertInstanceof(geometry, ol.geom.Point); return /** @type {GeoJSONGeometry} */ ({ 'type': 'Point', @@ -289,14 +300,19 @@ ol.format.GeoJSON.writePointGeometry_ = function(geometry) { /** * @param {ol.geom.Geometry} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. * @private * @return {GeoJSONGeometry} GeoJSON geometry. */ -ol.format.GeoJSON.writePolygonGeometry_ = function(geometry) { +ol.format.GeoJSON.writePolygonGeometry_ = function(geometry, opt_options) { goog.asserts.assertInstanceof(geometry, ol.geom.Polygon); + var right; + if (goog.isDef(opt_options)) { + right = opt_options.rightHanded; + } return /** @type {GeoJSONGeometry} */ ({ 'type': 'Polygon', - 'coordinates': geometry.getCoordinates() + 'coordinates': geometry.getCoordinates(right) }); }; @@ -320,7 +336,7 @@ ol.format.GeoJSON.GEOMETRY_READERS_ = { /** * @const * @private - * @type {Object.} + * @type {Object.} */ ol.format.GeoJSON.GEOMETRY_WRITERS_ = { 'Point': ol.format.GeoJSON.writePointGeometry_, diff --git a/test/spec/ol/format/geojsonformat.test.js b/test/spec/ol/format/geojsonformat.test.js index bf3356825c..df3c46342a 100644 --- a/test/spec/ol/format/geojsonformat.test.js +++ b/test/spec/ol/format/geojsonformat.test.js @@ -558,6 +558,106 @@ describe('ol.format.GeoJSON', function() { format.readGeometry(geojson).getCoordinates()); }); + it('maintains coordinate order by default', function() { + + var cw = [[-180, -90], [-180, 90], [180, 90], [180, -90], [-180, -90]]; + var ccw = [[-180, -90], [180, -90], [180, 90], [-180, 90], [-180, -90]]; + + var right = new ol.geom.Polygon([ccw, cw]); + var rightMulti = new ol.geom.MultiPolygon([[ccw, cw]]); + var left = new ol.geom.Polygon([cw, ccw]); + var leftMulti = new ol.geom.MultiPolygon([[cw, ccw]]); + + var rightObj = { + type: 'Polygon', + coordinates: [ccw, cw] + }; + + var rightMultiObj = { + type: 'MultiPolygon', + coordinates: [[ccw, cw]] + }; + + var leftObj = { + type: 'Polygon', + coordinates: [cw, ccw] + }; + + var leftMultiObj = { + type: 'MultiPolygon', + coordinates: [[cw, ccw]] + }; + + expect(JSON.parse(format.writeGeometry(right))).to.eql(rightObj); + expect( + JSON.parse(format.writeGeometry(rightMulti))).to.eql(rightMultiObj); + expect(JSON.parse(format.writeGeometry(left))).to.eql(leftObj); + expect(JSON.parse(format.writeGeometry(leftMulti))).to.eql(leftMultiObj); + + }); + + it('allows serializing following the right-hand rule', function() { + + var cw = [[-180, -90], [-180, 90], [180, 90], [180, -90], [-180, -90]]; + var ccw = [[-180, -90], [180, -90], [180, 90], [-180, 90], [-180, -90]]; + var right = new ol.geom.Polygon([ccw, cw]); + var rightMulti = new ol.geom.MultiPolygon([[ccw, cw]]); + var left = new ol.geom.Polygon([cw, ccw]); + var leftMulti = new ol.geom.MultiPolygon([[cw, ccw]]); + + var rightObj = { + type: 'Polygon', + coordinates: [ccw, cw] + }; + + var rightMultiObj = { + type: 'MultiPolygon', + coordinates: [[ccw, cw]] + }; + + var json = format.writeGeometry(right, {rightHanded: true}); + expect(JSON.parse(json)).to.eql(rightObj); + json = format.writeGeometry(rightMulti, {rightHanded: true}); + expect(JSON.parse(json)).to.eql(rightMultiObj); + + json = format.writeGeometry(left, {rightHanded: true}); + expect(JSON.parse(json)).to.eql(rightObj); + json = format.writeGeometry(leftMulti, {rightHanded: true}); + expect(JSON.parse(json)).to.eql(rightMultiObj); + + }); + + it('allows serializing following the left-hand rule', function() { + + var cw = [[-180, -90], [-180, 90], [180, 90], [180, -90], [-180, -90]]; + var ccw = [[-180, -90], [180, -90], [180, 90], [-180, 90], [-180, -90]]; + var right = new ol.geom.Polygon([ccw, cw]); + var rightMulti = new ol.geom.MultiPolygon([[ccw, cw]]); + var left = new ol.geom.Polygon([cw, ccw]); + var leftMulti = new ol.geom.MultiPolygon([[cw, ccw]]); + + var leftObj = { + type: 'Polygon', + coordinates: [cw, ccw] + }; + + var leftMultiObj = { + type: 'MultiPolygon', + coordinates: [[cw, ccw]] + }; + + var json = format.writeGeometry(right, {rightHanded: false}); + expect(JSON.parse(json)).to.eql(leftObj); + json = format.writeGeometry(rightMulti, {rightHanded: false}); + expect(JSON.parse(json)).to.eql(leftMultiObj); + + json = format.writeGeometry(left, {rightHanded: false}); + expect(JSON.parse(json)).to.eql(leftObj); + json = format.writeGeometry(leftMulti, {rightHanded: false}); + expect(JSON.parse(json)).to.eql(leftMultiObj); + + }); + it('encodes geometry collection', function() { var collection = new ol.geom.GeometryCollection([ new ol.geom.Point([10, 20]), @@ -611,6 +711,7 @@ goog.require('ol.geom.Circle'); goog.require('ol.geom.GeometryCollection'); goog.require('ol.geom.LineString'); goog.require('ol.geom.LinearRing'); +goog.require('ol.geom.MultiPolygon'); goog.require('ol.geom.Point'); goog.require('ol.geom.Polygon'); goog.require('ol.proj');