diff --git a/src/ol/format/WKT.js b/src/ol/format/WKT.js index 504e9451d2..62b90c35a9 100644 --- a/src/ol/format/WKT.js +++ b/src/ol/format/WKT.js @@ -16,6 +16,509 @@ import Point from '../geom/Point.js'; import Polygon from '../geom/Polygon.js'; import SimpleGeometry from '../geom/SimpleGeometry.js'; + +/** + * @const + * @type {string} + */ +const EMPTY = 'EMPTY'; + + +/** + * @const + * @type {string} + */ +const Z = 'Z'; + + +/** + * @const + * @type {string} + */ +const M = 'M'; + + +/** + * @const + * @type {string} + */ +const ZM = 'ZM'; + + +/** + * @const + * @enum {number} + */ +const TokenType = { + TEXT: 1, + LEFT_PAREN: 2, + RIGHT_PAREN: 3, + NUMBER: 4, + COMMA: 5, + EOF: 6 +}; + + +/** + * Class to tokenize a WKT string. + * @param {string} wkt WKT string. + * @constructor + */ +const Lexer = function(wkt) { + + /** + * @type {string} + */ + this.wkt = wkt; + + /** + * @type {number} + * @private + */ + this.index_ = -1; +}; + + +/** + * @param {string} c Character. + * @return {boolean} Whether the character is alphabetic. + * @private + */ +Lexer.prototype.isAlpha_ = function(c) { + return c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z'; +}; + + +/** + * @param {string} c Character. + * @param {boolean=} opt_decimal Whether the string number + * contains a dot, i.e. is a decimal number. + * @return {boolean} Whether the character is numeric. + * @private + */ +Lexer.prototype.isNumeric_ = function(c, opt_decimal) { + const decimal = opt_decimal !== undefined ? opt_decimal : false; + return c >= '0' && c <= '9' || c == '.' && !decimal; +}; + + +/** + * @param {string} c Character. + * @return {boolean} Whether the character is whitespace. + * @private + */ +Lexer.prototype.isWhiteSpace_ = function(c) { + return c == ' ' || c == '\t' || c == '\r' || c == '\n'; +}; + + +/** + * @return {string} Next string character. + * @private + */ +Lexer.prototype.nextChar_ = function() { + return this.wkt.charAt(++this.index_); +}; + + +/** + * Fetch and return the next token. + * @return {!ol.WKTToken} Next string token. + */ +Lexer.prototype.nextToken = function() { + const c = this.nextChar_(); + const token = {position: this.index_, value: c}; + + if (c == '(') { + token.type = TokenType.LEFT_PAREN; + } else if (c == ',') { + token.type = TokenType.COMMA; + } else if (c == ')') { + token.type = TokenType.RIGHT_PAREN; + } else if (this.isNumeric_(c) || c == '-') { + token.type = TokenType.NUMBER; + token.value = this.readNumber_(); + } else if (this.isAlpha_(c)) { + token.type = TokenType.TEXT; + token.value = this.readText_(); + } else if (this.isWhiteSpace_(c)) { + return this.nextToken(); + } else if (c === '') { + token.type = TokenType.EOF; + } else { + throw new Error('Unexpected character: ' + c); + } + + return token; +}; + + +/** + * @return {number} Numeric token value. + * @private + */ +Lexer.prototype.readNumber_ = function() { + let c; + const index = this.index_; + let decimal = false; + let scientificNotation = false; + do { + if (c == '.') { + decimal = true; + } else if (c == 'e' || c == 'E') { + scientificNotation = true; + } + c = this.nextChar_(); + } while ( + this.isNumeric_(c, decimal) || + // if we haven't detected a scientific number before, 'e' or 'E' + // hint that we should continue to read + !scientificNotation && (c == 'e' || c == 'E') || + // once we know that we have a scientific number, both '-' and '+' + // are allowed + scientificNotation && (c == '-' || c == '+') + ); + return parseFloat(this.wkt.substring(index, this.index_--)); +}; + + +/** + * @return {string} String token value. + * @private + */ +Lexer.prototype.readText_ = function() { + let c; + const index = this.index_; + do { + c = this.nextChar_(); + } while (this.isAlpha_(c)); + return this.wkt.substring(index, this.index_--).toUpperCase(); +}; + + +/** + * Class to parse the tokens from the WKT string. + * @param {ol.format.Lexer} lexer The lexer. + * @constructor + */ +const Parser = function(lexer) { + + /** + * @type {ol.format.Lexer} + * @private + */ + this.lexer_ = lexer; + + /** + * @type {ol.WKTToken} + * @private + */ + this.token_; + + /** + * @type {ol.geom.GeometryLayout} + * @private + */ + this.layout_ = GeometryLayout.XY; +}; + + +/** + * Fetch the next token form the lexer and replace the active token. + * @private + */ +Parser.prototype.consume_ = function() { + this.token_ = this.lexer_.nextToken(); +}; + +/** + * Tests if the given type matches the type of the current token. + * @param {ol.format.TokenType} type Token type. + * @return {boolean} Whether the token matches the given type. + */ +Parser.prototype.isTokenType = function(type) { + const isMatch = this.token_.type == type; + return isMatch; +}; + + +/** + * If the given type matches the current token, consume it. + * @param {ol.format.TokenType} type Token type. + * @return {boolean} Whether the token matches the given type. + */ +Parser.prototype.match = function(type) { + const isMatch = this.isTokenType(type); + if (isMatch) { + this.consume_(); + } + return isMatch; +}; + + +/** + * Try to parse the tokens provided by the lexer. + * @return {ol.geom.Geometry} The geometry. + */ +Parser.prototype.parse = function() { + this.consume_(); + const geometry = this.parseGeometry_(); + return geometry; +}; + + +/** + * Try to parse the dimensional info. + * @return {ol.geom.GeometryLayout} The layout. + * @private + */ +Parser.prototype.parseGeometryLayout_ = function() { + let layout = GeometryLayout.XY; + const dimToken = this.token_; + if (this.isTokenType(TokenType.TEXT)) { + const dimInfo = dimToken.value; + if (dimInfo === Z) { + layout = GeometryLayout.XYZ; + } else if (dimInfo === M) { + layout = GeometryLayout.XYM; + } else if (dimInfo === ZM) { + layout = GeometryLayout.XYZM; + } + if (layout !== GeometryLayout.XY) { + this.consume_(); + } + } + return layout; +}; + + +/** + * @return {!Array.} A collection of geometries. + * @private + */ +Parser.prototype.parseGeometryCollectionText_ = function() { + if (this.match(TokenType.LEFT_PAREN)) { + const geometries = []; + do { + geometries.push(this.parseGeometry_()); + } while (this.match(TokenType.COMMA)); + if (this.match(TokenType.RIGHT_PAREN)) { + return geometries; + } + } else if (this.isEmptyGeometry_()) { + return []; + } + throw new Error(this.formatErrorMessage_()); +}; + + +/** + * @return {Array.} All values in a point. + * @private + */ +Parser.prototype.parsePointText_ = function() { + if (this.match(TokenType.LEFT_PAREN)) { + const coordinates = this.parsePoint_(); + if (this.match(TokenType.RIGHT_PAREN)) { + return coordinates; + } + } else if (this.isEmptyGeometry_()) { + return null; + } + throw new Error(this.formatErrorMessage_()); +}; + + +/** + * @return {!Array.>} All points in a linestring. + * @private + */ +Parser.prototype.parseLineStringText_ = function() { + if (this.match(TokenType.LEFT_PAREN)) { + const coordinates = this.parsePointList_(); + if (this.match(TokenType.RIGHT_PAREN)) { + return coordinates; + } + } else if (this.isEmptyGeometry_()) { + return []; + } + throw new Error(this.formatErrorMessage_()); +}; + + +/** + * @return {!Array.>} All points in a polygon. + * @private + */ +Parser.prototype.parsePolygonText_ = function() { + if (this.match(TokenType.LEFT_PAREN)) { + const coordinates = this.parseLineStringTextList_(); + if (this.match(TokenType.RIGHT_PAREN)) { + return coordinates; + } + } else if (this.isEmptyGeometry_()) { + return []; + } + throw new Error(this.formatErrorMessage_()); +}; + + +/** + * @return {!Array.>} All points in a multipoint. + * @private + */ +Parser.prototype.parseMultiPointText_ = function() { + if (this.match(TokenType.LEFT_PAREN)) { + let coordinates; + if (this.token_.type == TokenType.LEFT_PAREN) { + coordinates = this.parsePointTextList_(); + } else { + coordinates = this.parsePointList_(); + } + if (this.match(TokenType.RIGHT_PAREN)) { + return coordinates; + } + } else if (this.isEmptyGeometry_()) { + return []; + } + throw new Error(this.formatErrorMessage_()); +}; + + +/** + * @return {!Array.>} All linestring points + * in a multilinestring. + * @private + */ +Parser.prototype.parseMultiLineStringText_ = function() { + if (this.match(TokenType.LEFT_PAREN)) { + const coordinates = this.parseLineStringTextList_(); + if (this.match(TokenType.RIGHT_PAREN)) { + return coordinates; + } + } else if (this.isEmptyGeometry_()) { + return []; + } + throw new Error(this.formatErrorMessage_()); +}; + + +/** + * @return {!Array.>} All polygon points in a multipolygon. + * @private + */ +Parser.prototype.parseMultiPolygonText_ = function() { + if (this.match(TokenType.LEFT_PAREN)) { + const coordinates = this.parsePolygonTextList_(); + if (this.match(TokenType.RIGHT_PAREN)) { + return coordinates; + } + } else if (this.isEmptyGeometry_()) { + return []; + } + throw new Error(this.formatErrorMessage_()); +}; + + +/** + * @return {!Array.} A point. + * @private + */ +Parser.prototype.parsePoint_ = function() { + const coordinates = []; + const dimensions = this.layout_.length; + for (let i = 0; i < dimensions; ++i) { + const token = this.token_; + if (this.match(TokenType.NUMBER)) { + coordinates.push(token.value); + } else { + break; + } + } + if (coordinates.length == dimensions) { + return coordinates; + } + throw new Error(this.formatErrorMessage_()); +}; + + +/** + * @return {!Array.>} An array of points. + * @private + */ +Parser.prototype.parsePointList_ = function() { + const coordinates = [this.parsePoint_()]; + while (this.match(TokenType.COMMA)) { + coordinates.push(this.parsePoint_()); + } + return coordinates; +}; + + +/** + * @return {!Array.>} An array of points. + * @private + */ +Parser.prototype.parsePointTextList_ = function() { + const coordinates = [this.parsePointText_()]; + while (this.match(TokenType.COMMA)) { + coordinates.push(this.parsePointText_()); + } + return coordinates; +}; + + +/** + * @return {!Array.>} An array of points. + * @private + */ +Parser.prototype.parseLineStringTextList_ = function() { + const coordinates = [this.parseLineStringText_()]; + while (this.match(TokenType.COMMA)) { + coordinates.push(this.parseLineStringText_()); + } + return coordinates; +}; + + +/** + * @return {!Array.>} An array of points. + * @private + */ +Parser.prototype.parsePolygonTextList_ = function() { + const coordinates = [this.parsePolygonText_()]; + while (this.match(TokenType.COMMA)) { + coordinates.push(this.parsePolygonText_()); + } + return coordinates; +}; + + +/** + * @return {boolean} Whether the token implies an empty geometry. + * @private + */ +Parser.prototype.isEmptyGeometry_ = function() { + const isEmpty = this.isTokenType(TokenType.TEXT) && + this.token_.value == EMPTY; + if (isEmpty) { + this.consume_(); + } + return isEmpty; +}; + + +/** + * Create an error message for an unexpected token error. + * @return {string} Error message. + * @private + */ +Parser.prototype.formatErrorMessage_ = function() { + return 'Unexpected `' + this.token_.value + '` at position ' + + this.token_.position + ' in `' + this.lexer_.wkt + '`'; +}; + + /** * @classdesc * Geometry format for reading and writing data in the `WellKnownText` (WKT) @@ -45,155 +548,134 @@ const WKT = function(opt_options) { inherits(WKT, TextFeature); -/** - * @const - * @type {string} - */ -WKT.EMPTY = 'EMPTY'; - - -/** - * @const - * @type {string} - */ -WKT.Z = 'Z'; - - -/** - * @const - * @type {string} - */ -WKT.M = 'M'; - - -/** - * @const - * @type {string} - */ -WKT.ZM = 'ZM'; - - /** * @param {ol.geom.Point} geom Point geometry. * @return {string} Coordinates part of Point as WKT. - * @private */ -WKT.encodePointGeometry_ = function(geom) { +function encodePointGeometry(geom) { const coordinates = geom.getCoordinates(); if (coordinates.length === 0) { return ''; } return coordinates.join(' '); -}; +} /** * @param {ol.geom.MultiPoint} geom MultiPoint geometry. * @return {string} Coordinates part of MultiPoint as WKT. - * @private */ -WKT.encodeMultiPointGeometry_ = function(geom) { +function encodeMultiPointGeometry(geom) { const array = []; const components = geom.getPoints(); for (let i = 0, ii = components.length; i < ii; ++i) { - array.push('(' + WKT.encodePointGeometry_(components[i]) + ')'); + array.push('(' + encodePointGeometry(components[i]) + ')'); } return array.join(','); -}; +} /** * @param {ol.geom.GeometryCollection} geom GeometryCollection geometry. * @return {string} Coordinates part of GeometryCollection as WKT. - * @private */ -WKT.encodeGeometryCollectionGeometry_ = function(geom) { +function encodeGeometryCollectionGeometry(geom) { const array = []; const geoms = geom.getGeometries(); for (let i = 0, ii = geoms.length; i < ii; ++i) { - array.push(WKT.encode_(geoms[i])); + array.push(encode(geoms[i])); } return array.join(','); -}; +} /** * @param {ol.geom.LineString|ol.geom.LinearRing} geom LineString geometry. * @return {string} Coordinates part of LineString as WKT. - * @private */ -WKT.encodeLineStringGeometry_ = function(geom) { +function encodeLineStringGeometry(geom) { const coordinates = geom.getCoordinates(); const array = []; for (let i = 0, ii = coordinates.length; i < ii; ++i) { array.push(coordinates[i].join(' ')); } return array.join(','); -}; +} /** * @param {ol.geom.MultiLineString} geom MultiLineString geometry. * @return {string} Coordinates part of MultiLineString as WKT. - * @private */ -WKT.encodeMultiLineStringGeometry_ = function(geom) { +function encodeMultiLineStringGeometry(geom) { const array = []; const components = geom.getLineStrings(); for (let i = 0, ii = components.length; i < ii; ++i) { - array.push('(' + WKT.encodeLineStringGeometry_( + array.push('(' + encodeLineStringGeometry( components[i]) + ')'); } return array.join(','); -}; +} /** * @param {ol.geom.Polygon} geom Polygon geometry. * @return {string} Coordinates part of Polygon as WKT. - * @private */ -WKT.encodePolygonGeometry_ = function(geom) { +function encodePolygonGeometry(geom) { const array = []; const rings = geom.getLinearRings(); for (let i = 0, ii = rings.length; i < ii; ++i) { - array.push('(' + WKT.encodeLineStringGeometry_( + array.push('(' + encodeLineStringGeometry( rings[i]) + ')'); } return array.join(','); -}; +} /** * @param {ol.geom.MultiPolygon} geom MultiPolygon geometry. * @return {string} Coordinates part of MultiPolygon as WKT. - * @private */ -WKT.encodeMultiPolygonGeometry_ = function(geom) { +function encodeMultiPolygonGeometry(geom) { const array = []; const components = geom.getPolygons(); for (let i = 0, ii = components.length; i < ii; ++i) { - array.push('(' + WKT.encodePolygonGeometry_( + array.push('(' + encodePolygonGeometry( components[i]) + ')'); } return array.join(','); -}; +} /** * @param {ol.geom.SimpleGeometry} geom SimpleGeometry geometry. * @return {string} Potential dimensional information for WKT type. - * @private */ -WKT.encodeGeometryLayout_ = function(geom) { +function encodeGeometryLayout(geom) { const layout = geom.getLayout(); let dimInfo = ''; if (layout === GeometryLayout.XYZ || layout === GeometryLayout.XYZM) { - dimInfo += WKT.Z; + dimInfo += Z; } if (layout === GeometryLayout.XYM || layout === GeometryLayout.XYZM) { - dimInfo += WKT.M; + dimInfo += M; } return dimInfo; +} + + +/** + * @const + * @type {Object.} + */ +const GeometryEncoder = { + 'Point': encodePointGeometry, + 'LineString': encodeLineStringGeometry, + 'Polygon': encodePolygonGeometry, + 'MultiPoint': encodeMultiPointGeometry, + 'MultiLineString': encodeMultiLineStringGeometry, + 'MultiPolygon': encodeMultiPolygonGeometry, + 'GeometryCollection': encodeGeometryCollectionGeometry }; @@ -201,40 +683,23 @@ WKT.encodeGeometryLayout_ = function(geom) { * Encode a geometry as WKT. * @param {ol.geom.Geometry} geom The geometry to encode. * @return {string} WKT string for the geometry. - * @private */ -WKT.encode_ = function(geom) { +function encode(geom) { let type = geom.getType(); - const geometryEncoder = WKT.GeometryEncoder_[type]; + const geometryEncoder = GeometryEncoder[type]; const enc = geometryEncoder(geom); type = type.toUpperCase(); if (geom instanceof SimpleGeometry) { - const dimInfo = WKT.encodeGeometryLayout_(geom); + const dimInfo = encodeGeometryLayout(geom); if (dimInfo.length > 0) { type += ' ' + dimInfo; } } if (enc.length === 0) { - return type + ' ' + WKT.EMPTY; + return type + ' ' + EMPTY; } return type + '(' + enc + ')'; -}; - - -/** - * @const - * @type {Object.} - * @private - */ -WKT.GeometryEncoder_ = { - 'Point': WKT.encodePointGeometry_, - 'LineString': WKT.encodeLineStringGeometry_, - 'Polygon': WKT.encodePolygonGeometry_, - 'MultiPoint': WKT.encodeMultiPointGeometry_, - 'MultiLineString': WKT.encodeMultiLineStringGeometry_, - 'MultiPolygon': WKT.encodeMultiPolygonGeometry_, - 'GeometryCollection': WKT.encodeGeometryCollectionGeometry_ -}; +} /** @@ -245,8 +710,8 @@ WKT.GeometryEncoder_ = { * @private */ WKT.prototype.parse_ = function(wkt) { - const lexer = new WKT.Lexer(wkt); - const parser = new WKT.Parser(lexer); + const lexer = new Lexer(wkt); + const parser = new Parser(lexer); return parser.parse(); }; @@ -340,6 +805,58 @@ WKT.prototype.readGeometryFromText = function(text, opt_options) { }; +/** + * @enum {function (new:ol.geom.Geometry, Array, ol.geom.GeometryLayout)} + */ +const GeometryConstructor = { + 'POINT': Point, + 'LINESTRING': LineString, + 'POLYGON': Polygon, + 'MULTIPOINT': MultiPoint, + 'MULTILINESTRING': MultiLineString, + 'MULTIPOLYGON': MultiPolygon +}; + + +/** + * @enum {(function(): Array)} + */ +const GeometryParser = { + 'POINT': Parser.prototype.parsePointText_, + 'LINESTRING': Parser.prototype.parseLineStringText_, + 'POLYGON': Parser.prototype.parsePolygonText_, + 'MULTIPOINT': Parser.prototype.parseMultiPointText_, + 'MULTILINESTRING': Parser.prototype.parseMultiLineStringText_, + 'MULTIPOLYGON': Parser.prototype.parseMultiPolygonText_ +}; + + +/** + * @return {!ol.geom.Geometry} The geometry. + * @private + */ +Parser.prototype.parseGeometry_ = function() { + const token = this.token_; + if (this.match(TokenType.TEXT)) { + const geomType = token.value; + this.layout_ = this.parseGeometryLayout_(); + if (geomType == GeometryType.GEOMETRY_COLLECTION.toUpperCase()) { + const geometries = this.parseGeometryCollectionText_(); + return new GeometryCollection(geometries); + } else { + const parser = GeometryParser[geomType]; + const ctor = GeometryConstructor[geomType]; + if (!parser || !ctor) { + throw new Error('Invalid geometry type: ' + geomType); + } + const coordinates = parser.call(this); + return new ctor(coordinates, this.layout_); + } + } + throw new Error(this.formatErrorMessage_()); +}; + + /** * Encode a feature as a WKT string. * @@ -408,538 +925,9 @@ WKT.prototype.writeGeometry; * @inheritDoc */ WKT.prototype.writeGeometryText = function(geometry, opt_options) { - return WKT.encode_(/** @type {ol.geom.Geometry} */ ( + return encode(/** @type {ol.geom.Geometry} */ ( transformWithOptions(geometry, true, opt_options))); }; -/** - * @const - * @enum {number} - * @private - */ -WKT.TokenType_ = { - TEXT: 1, - LEFT_PAREN: 2, - RIGHT_PAREN: 3, - NUMBER: 4, - COMMA: 5, - EOF: 6 -}; - - -/** - * Class to tokenize a WKT string. - * @param {string} wkt WKT string. - * @constructor - * @protected - */ -WKT.Lexer = function(wkt) { - - /** - * @type {string} - */ - this.wkt = wkt; - - /** - * @type {number} - * @private - */ - this.index_ = -1; -}; - - -/** - * @param {string} c Character. - * @return {boolean} Whether the character is alphabetic. - * @private - */ -WKT.Lexer.prototype.isAlpha_ = function(c) { - return c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z'; -}; - - -/** - * @param {string} c Character. - * @param {boolean=} opt_decimal Whether the string number - * contains a dot, i.e. is a decimal number. - * @return {boolean} Whether the character is numeric. - * @private - */ -WKT.Lexer.prototype.isNumeric_ = function(c, opt_decimal) { - const decimal = opt_decimal !== undefined ? opt_decimal : false; - return c >= '0' && c <= '9' || c == '.' && !decimal; -}; - - -/** - * @param {string} c Character. - * @return {boolean} Whether the character is whitespace. - * @private - */ -WKT.Lexer.prototype.isWhiteSpace_ = function(c) { - return c == ' ' || c == '\t' || c == '\r' || c == '\n'; -}; - - -/** - * @return {string} Next string character. - * @private - */ -WKT.Lexer.prototype.nextChar_ = function() { - return this.wkt.charAt(++this.index_); -}; - - -/** - * Fetch and return the next token. - * @return {!ol.WKTToken} Next string token. - */ -WKT.Lexer.prototype.nextToken = function() { - const c = this.nextChar_(); - const token = {position: this.index_, value: c}; - - if (c == '(') { - token.type = WKT.TokenType_.LEFT_PAREN; - } else if (c == ',') { - token.type = WKT.TokenType_.COMMA; - } else if (c == ')') { - token.type = WKT.TokenType_.RIGHT_PAREN; - } else if (this.isNumeric_(c) || c == '-') { - token.type = WKT.TokenType_.NUMBER; - token.value = this.readNumber_(); - } else if (this.isAlpha_(c)) { - token.type = WKT.TokenType_.TEXT; - token.value = this.readText_(); - } else if (this.isWhiteSpace_(c)) { - return this.nextToken(); - } else if (c === '') { - token.type = WKT.TokenType_.EOF; - } else { - throw new Error('Unexpected character: ' + c); - } - - return token; -}; - - -/** - * @return {number} Numeric token value. - * @private - */ -WKT.Lexer.prototype.readNumber_ = function() { - let c; - const index = this.index_; - let decimal = false; - let scientificNotation = false; - do { - if (c == '.') { - decimal = true; - } else if (c == 'e' || c == 'E') { - scientificNotation = true; - } - c = this.nextChar_(); - } while ( - this.isNumeric_(c, decimal) || - // if we haven't detected a scientific number before, 'e' or 'E' - // hint that we should continue to read - !scientificNotation && (c == 'e' || c == 'E') || - // once we know that we have a scientific number, both '-' and '+' - // are allowed - scientificNotation && (c == '-' || c == '+') - ); - return parseFloat(this.wkt.substring(index, this.index_--)); -}; - - -/** - * @return {string} String token value. - * @private - */ -WKT.Lexer.prototype.readText_ = function() { - let c; - const index = this.index_; - do { - c = this.nextChar_(); - } while (this.isAlpha_(c)); - return this.wkt.substring(index, this.index_--).toUpperCase(); -}; - - -/** - * Class to parse the tokens from the WKT string. - * @param {ol.format.WKT.Lexer} lexer The lexer. - * @constructor - * @protected - */ -WKT.Parser = function(lexer) { - - /** - * @type {ol.format.WKT.Lexer} - * @private - */ - this.lexer_ = lexer; - - /** - * @type {ol.WKTToken} - * @private - */ - this.token_; - - /** - * @type {ol.geom.GeometryLayout} - * @private - */ - this.layout_ = GeometryLayout.XY; -}; - - -/** - * Fetch the next token form the lexer and replace the active token. - * @private - */ -WKT.Parser.prototype.consume_ = function() { - this.token_ = this.lexer_.nextToken(); -}; - -/** - * Tests if the given type matches the type of the current token. - * @param {ol.format.WKT.TokenType_} type Token type. - * @return {boolean} Whether the token matches the given type. - */ -WKT.Parser.prototype.isTokenType = function(type) { - const isMatch = this.token_.type == type; - return isMatch; -}; - - -/** - * If the given type matches the current token, consume it. - * @param {ol.format.WKT.TokenType_} type Token type. - * @return {boolean} Whether the token matches the given type. - */ -WKT.Parser.prototype.match = function(type) { - const isMatch = this.isTokenType(type); - if (isMatch) { - this.consume_(); - } - return isMatch; -}; - - -/** - * Try to parse the tokens provided by the lexer. - * @return {ol.geom.Geometry} The geometry. - */ -WKT.Parser.prototype.parse = function() { - this.consume_(); - const geometry = this.parseGeometry_(); - return geometry; -}; - - -/** - * Try to parse the dimensional info. - * @return {ol.geom.GeometryLayout} The layout. - * @private - */ -WKT.Parser.prototype.parseGeometryLayout_ = function() { - let layout = GeometryLayout.XY; - const dimToken = this.token_; - if (this.isTokenType(WKT.TokenType_.TEXT)) { - const dimInfo = dimToken.value; - if (dimInfo === WKT.Z) { - layout = GeometryLayout.XYZ; - } else if (dimInfo === WKT.M) { - layout = GeometryLayout.XYM; - } else if (dimInfo === WKT.ZM) { - layout = GeometryLayout.XYZM; - } - if (layout !== GeometryLayout.XY) { - this.consume_(); - } - } - return layout; -}; - - -/** - * @return {!ol.geom.Geometry} The geometry. - * @private - */ -WKT.Parser.prototype.parseGeometry_ = function() { - const token = this.token_; - if (this.match(WKT.TokenType_.TEXT)) { - const geomType = token.value; - this.layout_ = this.parseGeometryLayout_(); - if (geomType == GeometryType.GEOMETRY_COLLECTION.toUpperCase()) { - const geometries = this.parseGeometryCollectionText_(); - return new GeometryCollection(geometries); - } else { - const parser = WKT.Parser.GeometryParser_[geomType]; - const ctor = WKT.Parser.GeometryConstructor_[geomType]; - if (!parser || !ctor) { - throw new Error('Invalid geometry type: ' + geomType); - } - const coordinates = parser.call(this); - return new ctor(coordinates, this.layout_); - } - } - throw new Error(this.formatErrorMessage_()); -}; - - -/** - * @return {!Array.} A collection of geometries. - * @private - */ -WKT.Parser.prototype.parseGeometryCollectionText_ = function() { - if (this.match(WKT.TokenType_.LEFT_PAREN)) { - const geometries = []; - do { - geometries.push(this.parseGeometry_()); - } while (this.match(WKT.TokenType_.COMMA)); - if (this.match(WKT.TokenType_.RIGHT_PAREN)) { - return geometries; - } - } else if (this.isEmptyGeometry_()) { - return []; - } - throw new Error(this.formatErrorMessage_()); -}; - - -/** - * @return {Array.} All values in a point. - * @private - */ -WKT.Parser.prototype.parsePointText_ = function() { - if (this.match(WKT.TokenType_.LEFT_PAREN)) { - const coordinates = this.parsePoint_(); - if (this.match(WKT.TokenType_.RIGHT_PAREN)) { - return coordinates; - } - } else if (this.isEmptyGeometry_()) { - return null; - } - throw new Error(this.formatErrorMessage_()); -}; - - -/** - * @return {!Array.>} All points in a linestring. - * @private - */ -WKT.Parser.prototype.parseLineStringText_ = function() { - if (this.match(WKT.TokenType_.LEFT_PAREN)) { - const coordinates = this.parsePointList_(); - if (this.match(WKT.TokenType_.RIGHT_PAREN)) { - return coordinates; - } - } else if (this.isEmptyGeometry_()) { - return []; - } - throw new Error(this.formatErrorMessage_()); -}; - - -/** - * @return {!Array.>} All points in a polygon. - * @private - */ -WKT.Parser.prototype.parsePolygonText_ = function() { - if (this.match(WKT.TokenType_.LEFT_PAREN)) { - const coordinates = this.parseLineStringTextList_(); - if (this.match(WKT.TokenType_.RIGHT_PAREN)) { - return coordinates; - } - } else if (this.isEmptyGeometry_()) { - return []; - } - throw new Error(this.formatErrorMessage_()); -}; - - -/** - * @return {!Array.>} All points in a multipoint. - * @private - */ -WKT.Parser.prototype.parseMultiPointText_ = function() { - if (this.match(WKT.TokenType_.LEFT_PAREN)) { - let coordinates; - if (this.token_.type == WKT.TokenType_.LEFT_PAREN) { - coordinates = this.parsePointTextList_(); - } else { - coordinates = this.parsePointList_(); - } - if (this.match(WKT.TokenType_.RIGHT_PAREN)) { - return coordinates; - } - } else if (this.isEmptyGeometry_()) { - return []; - } - throw new Error(this.formatErrorMessage_()); -}; - - -/** - * @return {!Array.>} All linestring points - * in a multilinestring. - * @private - */ -WKT.Parser.prototype.parseMultiLineStringText_ = function() { - if (this.match(WKT.TokenType_.LEFT_PAREN)) { - const coordinates = this.parseLineStringTextList_(); - if (this.match(WKT.TokenType_.RIGHT_PAREN)) { - return coordinates; - } - } else if (this.isEmptyGeometry_()) { - return []; - } - throw new Error(this.formatErrorMessage_()); -}; - - -/** - * @return {!Array.>} All polygon points in a multipolygon. - * @private - */ -WKT.Parser.prototype.parseMultiPolygonText_ = function() { - if (this.match(WKT.TokenType_.LEFT_PAREN)) { - const coordinates = this.parsePolygonTextList_(); - if (this.match(WKT.TokenType_.RIGHT_PAREN)) { - return coordinates; - } - } else if (this.isEmptyGeometry_()) { - return []; - } - throw new Error(this.formatErrorMessage_()); -}; - - -/** - * @return {!Array.} A point. - * @private - */ -WKT.Parser.prototype.parsePoint_ = function() { - const coordinates = []; - const dimensions = this.layout_.length; - for (let i = 0; i < dimensions; ++i) { - const token = this.token_; - if (this.match(WKT.TokenType_.NUMBER)) { - coordinates.push(token.value); - } else { - break; - } - } - if (coordinates.length == dimensions) { - return coordinates; - } - throw new Error(this.formatErrorMessage_()); -}; - - -/** - * @return {!Array.>} An array of points. - * @private - */ -WKT.Parser.prototype.parsePointList_ = function() { - const coordinates = [this.parsePoint_()]; - while (this.match(WKT.TokenType_.COMMA)) { - coordinates.push(this.parsePoint_()); - } - return coordinates; -}; - - -/** - * @return {!Array.>} An array of points. - * @private - */ -WKT.Parser.prototype.parsePointTextList_ = function() { - const coordinates = [this.parsePointText_()]; - while (this.match(WKT.TokenType_.COMMA)) { - coordinates.push(this.parsePointText_()); - } - return coordinates; -}; - - -/** - * @return {!Array.>} An array of points. - * @private - */ -WKT.Parser.prototype.parseLineStringTextList_ = function() { - const coordinates = [this.parseLineStringText_()]; - while (this.match(WKT.TokenType_.COMMA)) { - coordinates.push(this.parseLineStringText_()); - } - return coordinates; -}; - - -/** - * @return {!Array.>} An array of points. - * @private - */ -WKT.Parser.prototype.parsePolygonTextList_ = function() { - const coordinates = [this.parsePolygonText_()]; - while (this.match(WKT.TokenType_.COMMA)) { - coordinates.push(this.parsePolygonText_()); - } - return coordinates; -}; - - -/** - * @return {boolean} Whether the token implies an empty geometry. - * @private - */ -WKT.Parser.prototype.isEmptyGeometry_ = function() { - const isEmpty = this.isTokenType(WKT.TokenType_.TEXT) && - this.token_.value == WKT.EMPTY; - if (isEmpty) { - this.consume_(); - } - return isEmpty; -}; - - -/** - * Create an error message for an unexpected token error. - * @return {string} Error message. - * @private - */ -WKT.Parser.prototype.formatErrorMessage_ = function() { - return 'Unexpected `' + this.token_.value + '` at position ' + - this.token_.position + ' in `' + this.lexer_.wkt + '`'; -}; - - -/** - * @enum {function (new:ol.geom.Geometry, Array, ol.geom.GeometryLayout)} - * @private - */ -WKT.Parser.GeometryConstructor_ = { - 'POINT': Point, - 'LINESTRING': LineString, - 'POLYGON': Polygon, - 'MULTIPOINT': MultiPoint, - 'MULTILINESTRING': MultiLineString, - 'MULTIPOLYGON': MultiPolygon -}; - - -/** - * @enum {(function(): Array)} - * @private - */ -WKT.Parser.GeometryParser_ = { - 'POINT': WKT.Parser.prototype.parsePointText_, - 'LINESTRING': WKT.Parser.prototype.parseLineStringText_, - 'POLYGON': WKT.Parser.prototype.parsePolygonText_, - 'MULTIPOINT': WKT.Parser.prototype.parseMultiPointText_, - 'MULTILINESTRING': WKT.Parser.prototype.parseMultiLineStringText_, - 'MULTIPOLYGON': WKT.Parser.prototype.parseMultiPolygonText_ -}; export default WKT;