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(