From 99ba5a0da80be8b93ff9d9cd74e0d0c2d896a8d2 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Mon, 24 Jun 2013 17:46:36 -0600 Subject: [PATCH] Store rings so exerior is clockwise and interior is counter-clockwise KML and WKT don't specify a winding order, so we write those out in CW/CCW order (for exterior/interior). GML references ISO 19107 that specifies CCW/CW, so we serialize in that winding order. Having hand generated all this GML data the first time around, I reserve the right to modify it for the tests. --- src/ol/geom/polygon.js | 21 +++++++- src/ol/parser/kml.js | 4 ++ src/ol/parser/ogc/gml_v2.js | 12 ++++- src/ol/parser/ogc/gml_v3.js | 16 ++++-- test/spec/ol/geom/polygon.test.js | 22 ++++++++ test/spec/ol/parser/kml/polygon.kml | 53 +++++++------------ test/spec/ol/parser/ogc/gml_v2.test.js | 8 --- test/spec/ol/parser/ogc/gml_v3.test.js | 16 ------ .../gml_v2/geometrycollection-coordinates.xml | 6 +-- .../ogc/xml/gml_v2/multipolygon-coord.xml | 24 ++++----- .../xml/gml_v2/multipolygon-coordinates.xml | 8 +-- .../ogc/xml/gml_v2/polygon-coordinates.xml | 6 +-- .../ogc/xml/gml_v3/multipolygon-plural.xml | 8 +-- .../ogc/xml/gml_v3/multipolygon-singular.xml | 8 +-- .../ogc/xml/gml_v3/multisurface-plural.xml | 8 +-- .../ogc/xml/gml_v3/multisurface-singular.xml | 8 +-- .../ogc/xml/gml_v3/multisurface-surface.xml | 8 +-- .../spec/ol/parser/ogc/xml/gml_v3/polygon.xml | 8 +-- .../spec/ol/parser/ogc/xml/gml_v3/surface.xml | 6 +-- test/spec/ol/parser/wkt.test.js | 21 +++++--- 20 files changed, 150 insertions(+), 121 deletions(-) diff --git a/src/ol/geom/polygon.js b/src/ol/geom/polygon.js index e9b6d7c600..c888d67da5 100644 --- a/src/ol/geom/polygon.js +++ b/src/ol/geom/polygon.js @@ -10,6 +10,12 @@ goog.require('ol.geom.VertexArray'); /** + * Create a polygon from an array of vertex arrays. Coordinates for the + * exterior ring will be forced to clockwise order. Coordinates for any + * interior rings will be forced to counter-clockwise order. In cases where + * the opposite winding order occurs in the passed vertex arrays, they will + * be modified in place. + * * @constructor * @extends {ol.geom.Geometry} * @param {Array.} coordinates Array of rings. First @@ -40,8 +46,21 @@ ol.geom.Polygon = function(coordinates, opt_shared) { * @type {Array.} */ this.rings = new Array(numRings); + var ringCoords; for (var i = 0; i < numRings; ++i) { - this.rings[i] = new ol.geom.LinearRing(coordinates[i], vertices); + ringCoords = coordinates[i]; + if (i === 0) { + // force exterior ring to be clockwise + if (!ol.geom.LinearRing.isClockwise(ringCoords)) { + ringCoords.reverse(); + } + } else { + // force interior rings to be counter-clockwise + if (ol.geom.LinearRing.isClockwise(ringCoords)) { + ringCoords.reverse(); + } + } + this.rings[i] = new ol.geom.LinearRing(ringCoords, vertices); } /** diff --git a/src/ol/parser/kml.js b/src/ol/parser/kml.js index 54b4478a1f..003349bfb1 100644 --- a/src/ol/parser/kml.js +++ b/src/ol/parser/kml.js @@ -755,6 +755,10 @@ ol.parser.KML = function(opt_options) { return node; }, 'Polygon': function(geometry) { + /** + * KML doesn't specify the winding order of coordinates in linear + * rings. So we keep them as they are in the geometries. + */ var node = this.createElementNS('Polygon'); var coordinates = geometry.getCoordinates(); this.writeNode('outerBoundaryIs', coordinates[0], null, node); diff --git a/src/ol/parser/ogc/gml_v2.js b/src/ol/parser/ogc/gml_v2.js index feea6d3b22..e6a9f0a62e 100644 --- a/src/ol/parser/ogc/gml_v2.js +++ b/src/ol/parser/ogc/gml_v2.js @@ -67,9 +67,17 @@ ol.parser.ogc.GML_v2 = function(opt_options) { 'Polygon': function(geometry) { var node = this.createElementNS('gml:Polygon'); var coordinates = geometry.getCoordinates(); - this.writeNode('outerBoundaryIs', coordinates[0], null, node); + /** + * Though there continues to be ambiguity around this, GML references + * ISO 19107, which says polygons have counter-clockwise exterior rings + * and clockwise interior rings. The ambiguity comes because the + * the Simple Feature Access - SQL spec (ISO 19125-2) says that no + * winding order is enforced. Anyway, we write out counter-clockwise + * exterior and clockwise interior here but accept either when reading. + */ + this.writeNode('outerBoundaryIs', coordinates[0].reverse(), null, node); for (var i = 1; i < coordinates.length; ++i) { - this.writeNode('innerBoundaryIs', coordinates[i], null, node); + this.writeNode('innerBoundaryIs', coordinates[i].reverse(), null, node); } return node; }, diff --git a/src/ol/parser/ogc/gml_v3.js b/src/ol/parser/ogc/gml_v3.js index 744d75afc8..b8e6f433b4 100644 --- a/src/ol/parser/ogc/gml_v3.js +++ b/src/ol/parser/ogc/gml_v3.js @@ -298,18 +298,26 @@ ol.parser.ogc.GML_v3 = function(opt_options) { var node = this.createElementNS('gml:PolygonPatch'); node.setAttribute('interpolation', 'planar'); var coordinates = geometry.getCoordinates(); - this.writeNode('exterior', coordinates[0], null, node); + this.writeNode('exterior', coordinates[0].reverse(), null, node); for (var i = 1, len = coordinates.length; i < len; ++i) { - this.writeNode('interior', coordinates[i], null, node); + this.writeNode('interior', coordinates[i].reverse(), null, node); } return node; }, 'Polygon': function(geometry) { var node = this.createElementNS('gml:Polygon'); var coordinates = geometry.getCoordinates(); - this.writeNode('exterior', coordinates[0], null, node); + /** + * Though there continues to be ambiguity around this, GML references + * ISO 19107, which says polygons have counter-clockwise exterior rings + * and clockwise interior rings. The ambiguity comes because the + * the Simple Feature Access - SQL spec (ISO 19125-2) says that no + * winding order is enforced. Anyway, we write out counter-clockwise + * exterior and clockwise interior here but accept either when reading. + */ + this.writeNode('exterior', coordinates[0].reverse(), null, node); for (var i = 1, len = coordinates.length; i < len; ++i) { - this.writeNode('interior', coordinates[i], null, node); + this.writeNode('interior', coordinates[i].reverse(), null, node); } return node; }, diff --git a/test/spec/ol/geom/polygon.test.js b/test/spec/ol/geom/polygon.test.js index 9973e15eaa..355105ff80 100644 --- a/test/spec/ol/geom/polygon.test.js +++ b/test/spec/ol/geom/polygon.test.js @@ -44,6 +44,28 @@ describe('ol.geom.Polygon', function() { expect(poly.rings[2]).to.be.a(ol.geom.LinearRing); }); + var isClockwise = ol.geom.LinearRing.isClockwise; + + it('forces exterior ring to be clockwise', function() { + var outer = [[0, 0], [10, 0], [10, 10], [0, 10], [0, 0]]; + expect(isClockwise(outer)).to.be(false); + + var poly = new ol.geom.Polygon([outer]); + var ring = poly.rings[0]; + expect(isClockwise(ring.getCoordinates())).to.be(true); + }); + + it('forces interior ring to be counter-clockwise', function() { + var outer = [[0, 0], [10, 0], [10, 10], [0, 10], [0, 0]]; + var inner = [[2, 2], [2, 8], [8, 8], [8, 2], [2, 2]]; + expect(isClockwise(inner)).to.be(true); + + var poly = new ol.geom.Polygon([outer, inner]); + var ring = poly.rings[1]; + expect(isClockwise(ring.getCoordinates())).to.be(false); + }); + + }); describe('#dimension', function() { diff --git a/test/spec/ol/parser/kml/polygon.kml b/test/spec/ol/parser/kml/polygon.kml index 7d31fbe6e9..96cc19b20b 100644 --- a/test/spec/ol/parser/kml/polygon.kml +++ b/test/spec/ol/parser/kml/polygon.kml @@ -1,34 +1,21 @@ - - - Polygon.kml - 0 - - hollow box - - - - - -122.366278,37.818844,30 - -122.365248,37.819267,30 - -122.36564,37.819861,30 - -122.366669,37.819429,30 - -122.366278,37.818844,30 - - - - - - - -122.366212,37.818977,30 - -122.365424,37.819294,30 - -122.365704,37.819731,30 - -122.366488,37.819402,30 - -122.366212,37.818977,30 - - - - - - - + + Polygon.kml + 0 + + hollow box + + + + -30,-20,0 -30,20,0 30,20,0 30,-20,0 -30,-20,0 + + + + + -15,-10,0 15,-10,0 15,10,0 -15,10,0 -15,-10,0 + + + + + + \ No newline at end of file diff --git a/test/spec/ol/parser/ogc/gml_v2.test.js b/test/spec/ol/parser/ogc/gml_v2.test.js index cfa4d82859..baaae89bed 100644 --- a/test/spec/ol/parser/ogc/gml_v2.test.js +++ b/test/spec/ol/parser/ogc/gml_v2.test.js @@ -145,14 +145,6 @@ describe('ol.parser.gml_v2', function() { delete parser.srsName; expect(obj.geometry.type).to.eql('polygon'); done(); - expect(obj.geometry.coordinates.length).to.eql(3); - expect(obj.geometry.coordinates[0].length).to.eql(4); - expect(obj.geometry.coordinates[0]).to.eql([[1, 2], [3, 4], - [5, 6], [1, 2]]); - expect(obj.geometry.coordinates[1]).to.eql([[2, 3], [4, 5], - [6, 7], [2, 3]]); - expect(obj.geometry.coordinates[2]).to.eql([[3, 4], [5, 6], - [7, 8], [3, 4]]); }); }); it('MultiPolygon read correctly from coord', function(done) { diff --git a/test/spec/ol/parser/ogc/gml_v3.test.js b/test/spec/ol/parser/ogc/gml_v3.test.js index b58878b0c4..599e173f47 100644 --- a/test/spec/ol/parser/ogc/gml_v3.test.js +++ b/test/spec/ol/parser/ogc/gml_v3.test.js @@ -247,14 +247,6 @@ describe('ol.parser.gml_v3', function() { delete parser.srsName; expect(goog.dom.xml.loadXml(parser.serialize(node))).to.xmleql(xml); expect(obj.geometry.type).to.eql('polygon'); - expect(obj.geometry.coordinates.length).to.eql(3); - expect(obj.geometry.coordinates[0].length).to.eql(4); - expect(obj.geometry.coordinates[0]).to.eql([[1, 2], [3, 4], - [5, 6], [1, 2]]); - expect(obj.geometry.coordinates[1]).to.eql([[2, 3], [4, 5], - [6, 7], [2, 3]]); - expect(obj.geometry.coordinates[2]).to.eql([[3, 4], [5, 6], - [7, 8], [3, 4]]); done(); }); }); @@ -268,14 +260,6 @@ describe('ol.parser.gml_v3', function() { [geom]).firstChild; expect(goog.dom.xml.loadXml(p.serialize(node))).to.xmleql(xml); expect(obj.geometry.type).to.eql('polygon'); - expect(obj.geometry.coordinates.length).to.eql(3); - expect(obj.geometry.coordinates[0].length).to.eql(4); - expect(obj.geometry.coordinates[0]).to.eql([[1, 2], [3, 4], - [5, 6], [1, 2]]); - expect(obj.geometry.coordinates[1]).to.eql([[2, 3], [4, 5], - [6, 7], [2, 3]]); - expect(obj.geometry.coordinates[2]).to.eql([[3, 4], [5, 6], - [7, 8], [3, 4]]); done(); }); }); diff --git a/test/spec/ol/parser/ogc/xml/gml_v2/geometrycollection-coordinates.xml b/test/spec/ol/parser/ogc/xml/gml_v2/geometrycollection-coordinates.xml index 35de3b1475..bec4cf48c4 100644 --- a/test/spec/ol/parser/ogc/xml/gml_v2/geometrycollection-coordinates.xml +++ b/test/spec/ol/parser/ogc/xml/gml_v2/geometrycollection-coordinates.xml @@ -13,17 +13,17 @@ - 1,2 3,4 5,6 1,2 + 1,2 3,2 3,4 1,2 - 2,3 4,5 6,7 2,3 + 2,3 2,5 4,5 2,3 - 3,4 5,6 7,8 3,4 + 3,4 3,6 5,6 3,4 diff --git a/test/spec/ol/parser/ogc/xml/gml_v2/multipolygon-coord.xml b/test/spec/ol/parser/ogc/xml/gml_v2/multipolygon-coord.xml index 7fc02f57e3..4ea443a91f 100644 --- a/test/spec/ol/parser/ogc/xml/gml_v2/multipolygon-coord.xml +++ b/test/spec/ol/parser/ogc/xml/gml_v2/multipolygon-coord.xml @@ -9,11 +9,11 @@ 3 - 4 + 2 - 5 - 6 + 3 + 4 1 @@ -28,12 +28,12 @@ 3 - 4 + 2 5 - 6 - 7 + 4 + 5 2 @@ -48,12 +48,12 @@ 4 - 5 + 3 6 - 7 - 8 + 5 + 6 3 @@ -73,11 +73,11 @@ 3 - 4 + 2 - 5 - 6 + 3 + 4 1 diff --git a/test/spec/ol/parser/ogc/xml/gml_v2/multipolygon-coordinates.xml b/test/spec/ol/parser/ogc/xml/gml_v2/multipolygon-coordinates.xml index ebe53c761a..26de3d3422 100644 --- a/test/spec/ol/parser/ogc/xml/gml_v2/multipolygon-coordinates.xml +++ b/test/spec/ol/parser/ogc/xml/gml_v2/multipolygon-coordinates.xml @@ -3,17 +3,17 @@ - 1,2 3,4 5,6 1,2 + 1,2 3,2 3,4 1,2 - 2,3 4,5 6,7 2,3 + 2,3 2,5 4,5 2,3 - 3,4 5,6 7,8 3,4 + 3,4 3,6 5,6 3,4 @@ -22,7 +22,7 @@ - 1,2 3,4 5,6 1,2 + 1,2 3,2 3,4 1,2 diff --git a/test/spec/ol/parser/ogc/xml/gml_v2/polygon-coordinates.xml b/test/spec/ol/parser/ogc/xml/gml_v2/polygon-coordinates.xml index 24966f2851..5a5085e3cb 100644 --- a/test/spec/ol/parser/ogc/xml/gml_v2/polygon-coordinates.xml +++ b/test/spec/ol/parser/ogc/xml/gml_v2/polygon-coordinates.xml @@ -1,17 +1,17 @@ - 1,2 3,4 5,6 1,2 + 1,2 5,2 5,6 1,2 - 2,3 4,5 6,7 2,3 + 2,3 2,5 4,5 2,3 - 3,4 5,6 7,8 3,4 + 3,4 3,6 5,6 3,4 diff --git a/test/spec/ol/parser/ogc/xml/gml_v3/multipolygon-plural.xml b/test/spec/ol/parser/ogc/xml/gml_v3/multipolygon-plural.xml index f03149f732..6118ff7724 100644 --- a/test/spec/ol/parser/ogc/xml/gml_v3/multipolygon-plural.xml +++ b/test/spec/ol/parser/ogc/xml/gml_v3/multipolygon-plural.xml @@ -3,24 +3,24 @@ - 1 2 3 4 5 6 1 2 + 1 2 3 2 3 4 1 2 - 2 3 4 5 6 7 2 3 + 2 3 2 5 4 5 2 3 - 3 4 5 6 7 8 3 4 + 3 4 3 6 5 6 3 4 - 1 2 3 4 5 6 1 2 + 1 2 3 2 3 4 1 2 diff --git a/test/spec/ol/parser/ogc/xml/gml_v3/multipolygon-singular.xml b/test/spec/ol/parser/ogc/xml/gml_v3/multipolygon-singular.xml index e9199b77d5..9ea5339d87 100644 --- a/test/spec/ol/parser/ogc/xml/gml_v3/multipolygon-singular.xml +++ b/test/spec/ol/parser/ogc/xml/gml_v3/multipolygon-singular.xml @@ -3,17 +3,17 @@ - 1 2 3 4 5 6 1 2 + 1 2 3 2 3 4 1 2 - 2 3 4 5 6 7 2 3 + 2 3 2 5 4 5 2 3 - 3 4 5 6 7 8 3 4 + 3 4 3 6 5 6 3 4 @@ -22,7 +22,7 @@ - 1 2 3 4 5 6 1 2 + 1 2 3 2 3 4 1 2 diff --git a/test/spec/ol/parser/ogc/xml/gml_v3/multisurface-plural.xml b/test/spec/ol/parser/ogc/xml/gml_v3/multisurface-plural.xml index 0ce914b658..ed65e0fd56 100644 --- a/test/spec/ol/parser/ogc/xml/gml_v3/multisurface-plural.xml +++ b/test/spec/ol/parser/ogc/xml/gml_v3/multisurface-plural.xml @@ -3,24 +3,24 @@ - 1 2 3 4 5 6 1 2 + 1 2 3 2 3 4 1 2 - 2 3 4 5 6 7 2 3 + 2 3 2 5 4 5 2 3 - 3 4 5 6 7 8 3 4 + 3 4 3 6 5 6 3 4 - 1 2 3 4 5 6 1 2 + 1 2 3 2 3 4 1 2 diff --git a/test/spec/ol/parser/ogc/xml/gml_v3/multisurface-singular.xml b/test/spec/ol/parser/ogc/xml/gml_v3/multisurface-singular.xml index f96d33adf3..bf6eb737af 100644 --- a/test/spec/ol/parser/ogc/xml/gml_v3/multisurface-singular.xml +++ b/test/spec/ol/parser/ogc/xml/gml_v3/multisurface-singular.xml @@ -3,17 +3,17 @@ - 1 2 3 4 5 6 1 2 + 1 2 3 2 3 4 1 2 - 2 3 4 5 6 7 2 3 + 2 3 2 5 4 5 2 3 - 3 4 5 6 7 8 3 4 + 3 4 3 6 5 6 3 4 @@ -22,7 +22,7 @@ - 1 2 3 4 5 6 1 2 + 1 2 3 2 3 4 1 2 diff --git a/test/spec/ol/parser/ogc/xml/gml_v3/multisurface-surface.xml b/test/spec/ol/parser/ogc/xml/gml_v3/multisurface-surface.xml index 31ee061e9d..7fe394769f 100644 --- a/test/spec/ol/parser/ogc/xml/gml_v3/multisurface-surface.xml +++ b/test/spec/ol/parser/ogc/xml/gml_v3/multisurface-surface.xml @@ -5,17 +5,17 @@ - 1 2 3 4 5 6 1 2 + 1 2 3 2 3 4 1 2 - 2 3 4 5 6 7 2 3 + 2 3 2 5 4 5 2 3 - 3 4 5 6 7 8 3 4 + 3 4 3 6 5 6 3 4 @@ -28,7 +28,7 @@ - 1 2 3 4 5 6 1 2 + 1 2 3 2 3 4 1 2 diff --git a/test/spec/ol/parser/ogc/xml/gml_v3/polygon.xml b/test/spec/ol/parser/ogc/xml/gml_v3/polygon.xml index 9f9895245a..e9c467e02b 100644 --- a/test/spec/ol/parser/ogc/xml/gml_v3/polygon.xml +++ b/test/spec/ol/parser/ogc/xml/gml_v3/polygon.xml @@ -1,17 +1,17 @@ - 1 2 3 4 5 6 1 2 + 1 2 3 2 3 4 1 2 - 2 3 4 5 6 7 2 3 + 2 3 2 5 4 5 2 3 - 3 4 5 6 7 8 3 4 + 3 4 3 6 5 6 3 4 - + diff --git a/test/spec/ol/parser/ogc/xml/gml_v3/surface.xml b/test/spec/ol/parser/ogc/xml/gml_v3/surface.xml index 017f68a0d7..4bfea73303 100644 --- a/test/spec/ol/parser/ogc/xml/gml_v3/surface.xml +++ b/test/spec/ol/parser/ogc/xml/gml_v3/surface.xml @@ -3,17 +3,17 @@ - 1 2 3 4 5 6 1 2 + 1 2 3 2 3 4 1 2 - 2 3 4 5 6 7 2 3 + 2 3 2 5 4 5 2 3 - 3 4 5 6 7 8 3 4 + 3 4 3 6 5 6 3 4 diff --git a/test/spec/ol/parser/wkt.test.js b/test/spec/ol/parser/wkt.test.js index 37751b4698..c49d7c9cb3 100644 --- a/test/spec/ol/parser/wkt.test.js +++ b/test/spec/ol/parser/wkt.test.js @@ -80,7 +80,9 @@ describe('ol.parser.WKT', function() { expect(geom.rings[0].getCoordinates()).to.eql( [[30, 10], [10, 20], [20, 40], [40, 40], [30, 10]]); expect(parser.write(geom)).to.eql(wkt); - wkt = 'POLYGON((35 10,10 20,15 40,45 45,35 10),(20 30,35 35,30 20,20 30))'; + + // note that WKT doesn't care about winding order, we do + wkt = 'POLYGON((35 10,10 20,15 40,45 45,35 10),(20 30,30 20,35 35,20 30))'; geom = parser.read(wkt); expect(geom.getType()).to.eql(ol.geom.GeometryType.POLYGON); expect(geom.rings.length).to.eql(2); @@ -89,8 +91,9 @@ describe('ol.parser.WKT', function() { expect(geom.rings[0].getCoordinates()).to.eql( [[35, 10], [10, 20], [15, 40], [45, 45], [35, 10]]); expect(geom.rings[1].getCoordinates()).to.eql( - [[20, 30], [35, 35], [30, 20], [20, 30]]); + [[20, 30], [30, 20], [35, 35], [20, 30]]); expect(parser.write(geom)).to.eql(wkt); + // test whitespace when reading wkt = 'POLYGON ( (30 10, 10 20, 20 40, 40 40, 30 10) )'; geom = parser.read(wkt); @@ -102,7 +105,8 @@ describe('ol.parser.WKT', function() { }); it('MultiPolygon read / written correctly', function() { - var wkt = 'MULTIPOLYGON(((40 40,20 45,45 30,40 40)),' + + // note that WKT doesn't care about winding order, we do + var wkt = 'MULTIPOLYGON(((40 40,45 30,20 45,40 40)),' + '((20 35,45 20,30 5,10 10,10 30,20 35),(30 20,20 25,20 15,30 20)))'; var geom = parser.read(wkt); expect(geom.getType()).to.eql(ol.geom.GeometryType.MULTIPOLYGON); @@ -112,16 +116,17 @@ describe('ol.parser.WKT', function() { expect(geom.components[0].rings.length).to.eql(1); expect(geom.components[1].rings.length).to.eql(2); expect(geom.components[0].rings[0].getCoordinates()).to.eql( - [[40, 40], [20, 45], [45, 30], [40, 40]]); + [[40, 40], [45, 30], [20, 45], [40, 40]]); expect(geom.components[1].rings[0].getCoordinates()).to.eql( [[20, 35], [45, 20], [30, 5], [10, 10], [10, 30], [20, 35]]); expect(geom.components[1].rings[1].getCoordinates()).to.eql( [[30, 20], [20, 25], [20, 15], [30, 20]]); expect(parser.write(geom)).to.eql(wkt); + // test whitespace when reading - wkt = 'MULTIPOLYGON( ( (40 40, 20 45, 45 30, 40 40) ), ' + - '( (20 35, 45 20, 30 5, 10 10, 10 30, 20 35 ), ( 30 20, 20 25, ' + - '20 15, 30 20 ) ) )'; + wkt = 'MULTIPOLYGON( ( ( 40 40,45 30, 20 45 ,40 40 )) ,' + + '( (20 35, 45 20,30 5,10 10,10 30,20 35), ' + + '( 30 20, 20 25,20 15 ,30 20 ) ))'; geom = parser.read(wkt); expect(geom.getType()).to.eql(ol.geom.GeometryType.MULTIPOLYGON); expect(geom.components.length).to.eql(2); @@ -130,7 +135,7 @@ describe('ol.parser.WKT', function() { expect(geom.components[0].rings.length).to.eql(1); expect(geom.components[1].rings.length).to.eql(2); expect(geom.components[0].rings[0].getCoordinates()).to.eql( - [[40, 40], [20, 45], [45, 30], [40, 40]]); + [[40, 40], [45, 30], [20, 45], [40, 40]]); expect(geom.components[1].rings[0].getCoordinates()).to.eql( [[20, 35], [45, 20], [30, 5], [10, 10], [10, 30], [20, 35]]); expect(geom.components[1].rings[1].getCoordinates()).to.eql(