diff --git a/src/ol/parser/wkt.exports b/src/ol/parser/wkt.exports new file mode 100644 index 0000000000..27763ba19d --- /dev/null +++ b/src/ol/parser/wkt.exports @@ -0,0 +1,3 @@ +@exportSymbol ol.parser.WKT +@exportProperty ol.parser.WKT.prototype.read +@exportProperty ol.parser.WKT.prototype.write diff --git a/src/ol/parser/wkt.js b/src/ol/parser/wkt.js new file mode 100644 index 0000000000..5720bb819b --- /dev/null +++ b/src/ol/parser/wkt.js @@ -0,0 +1,345 @@ +goog.provide('ol.parser.WKT'); + +goog.require('goog.array'); +goog.require('goog.string'); +goog.require('ol.geom.Geometry'); +goog.require('ol.geom.GeometryCollection'); +goog.require('ol.geom.LineString'); +goog.require('ol.geom.MultiLineString'); +goog.require('ol.geom.MultiPoint'); +goog.require('ol.geom.MultiPolygon'); +goog.require('ol.geom.Point'); +goog.require('ol.geom.Polygon'); +goog.require('ol.parser.Parser'); + + + +/** + * @constructor + * @extends {ol.parser.Parser} + */ +ol.parser.WKT = function() { +}; +goog.inherits(ol.parser.WKT, ol.parser.Parser); + + +/** + * Constants for regExes. + * @enum {RegExp} + */ +ol.parser.WKT.regExes = { + typeStr: /^\s*(\w+)\s*\(\s*(.*)\s*\)\s*$/, + spaces: /\s+/, + parenComma: /\)\s*,\s*\(/, + doubleParenComma: /\)\s*\)\s*,\s*\(\s*\(/, + trimParens: /^\s*\(?(.*?)\)?\s*$/, + geomCollection: /,\s*([A-Za-z])/g, + removeNewLine: /[\n\r]/g +}; + + +/** + * @param {string} str WKT point. + * @return {ol.geom.Point} Parsed point. + * @private + */ +ol.parser.WKT.prototype.parsePoint_ = function(str) { + var coords = goog.string.trim(str).split(ol.parser.WKT.regExes.spaces); + return new ol.geom.Point(goog.array.map(coords, parseFloat)); +}; + + +/** + * @param {string} str WKT linestring. + * @return {ol.geom.LineString} Parsed linestring. + * @private + */ +ol.parser.WKT.prototype.parseLineString_ = function(str) { + var points = goog.string.trim(str).split(','); + var coordinates = []; + for (var i = 0, ii = points.length; i < ii; ++i) { + coordinates.push(this.parsePoint_.apply(this, + [points[i]]).getCoordinates()); + } + return new ol.geom.LineString(coordinates); +}; + + +/** + * @param {string} str WKT multipoint. + * @return {ol.geom.MultiPoint} Parsed multipoint. + * @private + */ +ol.parser.WKT.prototype.parseMultiPoint_ = function(str) { + var point; + var points = goog.string.trim(str).split(','); + var parts = []; + for (var i = 0, ii = points.length; i < ii; ++i) { + point = points[i].replace(ol.parser.WKT.regExes.trimParens, '$1'); + parts.push(this.parsePoint_.apply(this, [point])); + } + return ol.geom.MultiPoint.fromParts(parts); +}; + + +/** + * @param {string} str WKT multilinestring. + * @return {ol.geom.MultiLineString} Parsed multilinestring. + * @private + */ +ol.parser.WKT.prototype.parseMultiLineString_ = function(str) { + var line; + var lines = goog.string.trim(str).split(ol.parser.WKT.regExes.parenComma); + var parts = []; + for (var i = 0, ii = lines.length; i < ii; ++i) { + line = lines[i].replace(ol.parser.WKT.regExes.trimParens, '$1'); + parts.push(this.parseLineString_.apply(this, [line])); + } + return ol.geom.MultiLineString.fromParts(parts); +}; + + +/** + * @param {string} str WKT polygon. + * @return {ol.geom.Polygon} Parsed polygon. + * @private + */ +ol.parser.WKT.prototype.parsePolygon_ = function(str) { + var ring, linestring, linearring; + var rings = goog.string.trim(str).split(ol.parser.WKT.regExes.parenComma); + var coordinates = []; + for (var i = 0, ii = rings.length; i < ii; ++i) { + ring = rings[i].replace(ol.parser.WKT.regExes.trimParens, '$1'); + linestring = this.parseLineString_.apply(this, [ring]).getCoordinates(); + coordinates.push(linestring); + } + return new ol.geom.Polygon(coordinates); +}; + + +/** + * @param {string} str WKT multipolygon. + * @return {ol.geom.MultiPolygon} Parsed multipolygon. + * @private + */ +ol.parser.WKT.prototype.parseMultiPolygon_ = function(str) { + var polygon; + var polygons = goog.string.trim(str).split( + ol.parser.WKT.regExes.doubleParenComma); + var parts = []; + for (var i = 0, ii = polygons.length; i < ii; ++i) { + polygon = polygons[i].replace(ol.parser.WKT.regExes.trimParens, '$1'); + parts.push(this.parsePolygon_.apply(this, [polygon])); + } + return ol.geom.MultiPolygon.fromParts(parts); +}; + + +/** + * @param {string} str WKT geometrycollection. + * @return {ol.geom.GeometryCollection} Parsed geometrycollection. + * @private + */ +ol.parser.WKT.prototype.parseGeometryCollection_ = function(str) { + // separate components of the collection with | + str = str.replace(ol.parser.WKT.regExes.geomCollection, '|$1'); + var wktArray = goog.string.trim(str).split('|'); + var components = []; + for (var i = 0, ii = wktArray.length; i < ii; ++i) { + components.push(this.parse_.apply(this, [wktArray[i]])); + } + return new ol.geom.GeometryCollection(components); +}; + + +/** + * @param {ol.geom.Point} geom Point geometry. + * @return {string} Coordinates part of Point as WKT. + * @private + */ +ol.parser.WKT.prototype.encodePoint_ = function(geom) { + var coordinates = geom.getCoordinates(); + return coordinates[0] + ' ' + coordinates[1]; +}; + + +/** + * @param {ol.geom.MultiPoint} geom MultiPoint geometry. + * @return {string} Coordinates part of MultiPoint as WKT. + * @private + */ +ol.parser.WKT.prototype.encodeMultiPoint_ = function(geom) { + var array = []; + for (var i = 0, ii = geom.components.length; i < ii; ++i) { + array.push('(' + this.encodePoint_.apply(this, [geom.components[i]]) + ')'); + } + return array.join(','); +}; + + +/** + * @param {ol.geom.GeometryCollection} geom GeometryCollection geometry. + * @return {string} Coordinates part of GeometryCollection as WKT. + * @private + */ +ol.parser.WKT.prototype.encodeGeometryCollection_ = function(geom) { + var array = []; + for (var i = 0, ii = geom.components.length; i < ii; ++i) { + array.push(this.encode_.apply(this, [geom.components[i]])); + } + return array.join(','); +}; + + +/** + * @param {ol.geom.LineString} geom LineString geometry. + * @return {string} Coordinates part of LineString as WKT. + * @private + */ +ol.parser.WKT.prototype.encodeLineString_ = function(geom) { + var coordinates = geom.getCoordinates(); + var array = []; + for (var i = 0, ii = coordinates.length; i < ii; ++i) { + array.push(coordinates[i][0] + ' ' + coordinates[i][1]); + } + return array.join(','); +}; + + +/** + * @param {ol.geom.MultiLineString} geom MultiLineString geometry. + * @return {string} Coordinates part of MultiLineString as WKT. + * @private + */ +ol.parser.WKT.prototype.encodeMultiLineString_ = function(geom) { + var array = []; + for (var i = 0, ii = geom.components.length; i < ii; ++i) { + array.push('(' + this.encodeLineString_.apply(this, + [geom.components[i]]) + ')'); + } + return array.join(','); +}; + + +/** + * @param {ol.geom.Polygon} geom Polygon geometry. + * @return {string} Coordinates part of Polygon as WKT. + * @private + */ +ol.parser.WKT.prototype.encodePolygon_ = function(geom) { + var array = []; + for (var i = 0, ii = geom.rings.length; i < ii; ++i) { + array.push('(' + this.encodeLineString_.apply(this, + [geom.rings[i]]) + ')'); + } + return array.join(','); +}; + + +/** + * @param {ol.geom.MultiPolygon} geom MultiPolygon geometry. + * @return {string} Coordinates part of MultiPolygon as WKT. + * @private + */ +ol.parser.WKT.prototype.encodeMultiPolygon_ = function(geom) { + var array = []; + for (var i = 0, ii = geom.components.length; i < ii; ++i) { + array.push('(' + this.encodePolygon_.apply(this, + [geom.components[i]]) + ')'); + } + return array.join(','); +}; + + +/** + * Parse a WKT string. + * @param {string} wkt WKT string. + * @return {ol.geom.Geometry|ol.geom.GeometryCollection|undefined} + * The geometry created. + * @private + */ +ol.parser.WKT.prototype.parse_ = function(wkt) { + wkt = wkt.replace(ol.parser.WKT.regExes.removeNewLine, ' '); + var matches = ol.parser.WKT.regExes.typeStr.exec(wkt); + var geometry; + if (matches) { + var type = matches[1].toLowerCase(); + var str = matches[2]; + switch (type) { + case 'point': + geometry = this.parsePoint_(str); + break; + case 'multipoint': + geometry = this.parseMultiPoint_(str); + break; + case 'linestring': + geometry = this.parseLineString_(str); + break; + case 'multilinestring': + geometry = this.parseMultiLineString_(str); + break; + case 'polygon': + geometry = this.parsePolygon_(str); + break; + case 'multipolygon': + geometry = this.parseMultiPolygon_(str); + break; + case 'geometrycollection': + geometry = this.parseGeometryCollection_(str); + break; + default: + throw new Error('Bad geometry type: ' + type); + } + } + return geometry; +}; + + +/** + * Encode a geometry as WKT. + * @param {ol.geom.Geometry} geom The geometry to encode. + * @return {string} WKT string for the geometry. + * @private + */ +ol.parser.WKT.prototype.encode_ = function(geom) { + var type = geom.getType(); + var result = type.toUpperCase() + '('; + if (geom instanceof ol.geom.Point) { + result += this.encodePoint_(geom); + } else if (geom instanceof ol.geom.MultiPoint) { + result += this.encodeMultiPoint_(geom); + } else if (geom instanceof ol.geom.LineString) { + result += this.encodeLineString_(geom); + } else if (geom instanceof ol.geom.MultiLineString) { + result += this.encodeMultiLineString_(geom); + } else if (geom instanceof ol.geom.Polygon) { + result += this.encodePolygon_(geom); + } else if (geom instanceof ol.geom.MultiPolygon) { + result += this.encodeMultiPolygon_(geom); + } else if (geom instanceof ol.geom.GeometryCollection) { + result += this.encodeGeometryCollection_(geom); + } else { + throw new Error('Bad geometry type: ' + type); + } + return result + ')'; +}; + + +/** + * Parse a WKT string. + * @param {string} str WKT string. + * @return {ol.geom.Geometry|undefined} Parsed geometry. + */ +ol.parser.WKT.prototype.read = function(str) { + return this.parse_(str); +}; + + +/** + * Write out a geometry as a WKT string. + * @param {ol.geom.Geometry} geom The geometry to encode. + * @return {string} WKT for the geometry. + */ +ol.parser.WKT.prototype.write = function(geom) { + return this.encode_(geom); +}; diff --git a/test/spec/ol/parser/wkt.test.js b/test/spec/ol/parser/wkt.test.js new file mode 100644 index 0000000000..37751b4698 --- /dev/null +++ b/test/spec/ol/parser/wkt.test.js @@ -0,0 +1,166 @@ +goog.provide('ol.test.parser.WKT'); + +describe('ol.parser.WKT', function() { + + var parser = new ol.parser.WKT(); + + it('Point read / written correctly', function() { + var wkt = 'POINT(30 10)'; + var geom = parser.read(wkt); + expect(geom.getCoordinates()).to.eql([30, 10]); + expect(parser.write(geom)).to.eql(wkt); + // test whitespace when reading + wkt = 'POINT (30 10)'; + geom = parser.read(wkt); + expect(geom.getCoordinates()).to.eql([30, 10]); + }); + + it('MultiPoint read / written correctly', function() { + // there are two forms to test + var wkt = 'MULTIPOINT((10 40),(40 30),(20 20),(30 10))'; + var geom = parser.read(wkt); + expect(geom.components.length).to.eql(4); + expect(geom.components[0].getCoordinates()).to.eql([10, 40]); + expect(geom.components[1].getCoordinates()).to.eql([40, 30]); + expect(geom.components[2].getCoordinates()).to.eql([20, 20]); + expect(geom.components[3].getCoordinates()).to.eql([30, 10]); + expect(parser.write(geom)).to.eql(wkt); + // this has whitespace + wkt = 'MULTIPOINT (10 40, 40 30, 20 20, 30 10)'; + geom = parser.read(wkt); + expect(geom.components.length).to.eql(4); + expect(geom.components[0].getCoordinates()).to.eql([10, 40]); + expect(geom.components[1].getCoordinates()).to.eql([40, 30]); + expect(geom.components[2].getCoordinates()).to.eql([20, 20]); + expect(geom.components[3].getCoordinates()).to.eql([30, 10]); + }); + + it('LineString read / written correctly', function() { + var wkt = 'LINESTRING(30 10,10 30,40 40)'; + var geom = parser.read(wkt); + expect(geom.getType()).to.eql(ol.geom.GeometryType.LINESTRING); + expect(geom.getCoordinates()).to.eql([[30, 10], [10, 30], [40, 40]]); + expect(parser.write(geom)).to.eql(wkt); + // test whitespace when reading + wkt = 'LINESTRING (30 10, 10 30, 40 40)'; + geom = parser.read(wkt); + expect(geom.getType()).to.eql(ol.geom.GeometryType.LINESTRING); + expect(geom.getCoordinates()).to.eql([[30, 10], [10, 30], [40, 40]]); + }); + + it('MultiLineString read / written correctly', function() { + var wkt = 'MULTILINESTRING((10 10,20 20,10 40),' + + '(40 40,30 30,40 20,30 10))'; + var geom = parser.read(wkt); + expect(geom.getType()).to.eql(ol.geom.GeometryType.MULTILINESTRING); + expect(geom.components.length).to.eql(2); + expect(geom.components[0].getType()).to.eql( + ol.geom.GeometryType.LINESTRING); + expect(geom.components[0].getCoordinates()).to.eql( + [[10, 10], [20, 20], [10, 40]]); + expect(parser.write(geom)).to.eql(wkt); + // test whitespace when reading + wkt = 'MULTILINESTRING ( (10 10, 20 20, 10 40), ' + + '(40 40, 30 30, 40 20, 30 10) )'; + geom = parser.read(wkt); + expect(geom.getType()).to.eql(ol.geom.GeometryType.MULTILINESTRING); + expect(geom.components.length).to.eql(2); + expect(geom.components[0].getType()).to.eql( + ol.geom.GeometryType.LINESTRING); + expect(geom.components[0].getCoordinates()).to.eql( + [[10, 10], [20, 20], [10, 40]]); + }); + + it('Polygon read / written correctly', function() { + var wkt = 'POLYGON((30 10,10 20,20 40,40 40,30 10))'; + var geom = parser.read(wkt); + expect(geom.getType()).to.eql(ol.geom.GeometryType.POLYGON); + expect(geom.rings.length).to.eql(1); + expect(geom.rings[0].getType()).to.eql(ol.geom.GeometryType.LINEARRING); + 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))'; + geom = parser.read(wkt); + expect(geom.getType()).to.eql(ol.geom.GeometryType.POLYGON); + expect(geom.rings.length).to.eql(2); + expect(geom.rings[0].getType()).to.eql(ol.geom.GeometryType.LINEARRING); + expect(geom.rings[1].getType()).to.eql(ol.geom.GeometryType.LINEARRING); + 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]]); + 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); + expect(geom.getType()).to.eql(ol.geom.GeometryType.POLYGON); + expect(geom.rings.length).to.eql(1); + expect(geom.rings[0].getType()).to.eql(ol.geom.GeometryType.LINEARRING); + expect(geom.rings[0].getCoordinates()).to.eql( + [[30, 10], [10, 20], [20, 40], [40, 40], [30, 10]]); + }); + + it('MultiPolygon read / written correctly', function() { + var 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)))'; + var geom = parser.read(wkt); + expect(geom.getType()).to.eql(ol.geom.GeometryType.MULTIPOLYGON); + expect(geom.components.length).to.eql(2); + expect(geom.components[0].getType()).to.eql(ol.geom.GeometryType.POLYGON); + expect(geom.components[1].getType()).to.eql(ol.geom.GeometryType.POLYGON); + 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]]); + 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 ) ) )'; + geom = parser.read(wkt); + expect(geom.getType()).to.eql(ol.geom.GeometryType.MULTIPOLYGON); + expect(geom.components.length).to.eql(2); + expect(geom.components[0].getType()).to.eql(ol.geom.GeometryType.POLYGON); + expect(geom.components[1].getType()).to.eql(ol.geom.GeometryType.POLYGON); + 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]]); + 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]]); + }); + + it('GeometryCollection read / written correctly', function() { + var wkt = 'GEOMETRYCOLLECTION(POINT(4 6),LINESTRING(4 6,7 10))'; + var geom = parser.read(wkt); + expect(geom.components.length).to.eql(2); + expect(geom.getType()).to.eql(ol.geom.GeometryType.GEOMETRYCOLLECTION); + expect(geom.components[0].getType()).to.eql(ol.geom.GeometryType.POINT); + expect(geom.components[1].getType()).to.eql( + ol.geom.GeometryType.LINESTRING); + expect(geom.components[0].getCoordinates()).to.eql([4, 6]); + expect(geom.components[1].getCoordinates()).to.eql([[4, 6], [7, 10]]); + expect(parser.write(geom)).to.eql(wkt); + // test whitespace when reading + wkt = 'GEOMETRYCOLLECTION ( POINT (4 6), LINESTRING (4 6, 7 10) )'; + geom = parser.read(wkt); + expect(geom.components.length).to.eql(2); + expect(geom.getType()).to.eql(ol.geom.GeometryType.GEOMETRYCOLLECTION); + expect(geom.components[0].getType()).to.eql(ol.geom.GeometryType.POINT); + expect(geom.components[1].getType()).to.eql( + ol.geom.GeometryType.LINESTRING); + expect(geom.components[0].getCoordinates()).to.eql([4, 6]); + expect(geom.components[1].getCoordinates()).to.eql([[4, 6], [7, 10]]); + }); + +}); + +goog.require('ol.geom.GeometryType'); +goog.require('ol.parser.WKT');