Source: org/locationtech/jts/io/WKTParser.js

import Coordinate from '../geom/Coordinate'
import GeometryFactory from '../geom/GeometryFactory'

const regExes = {
  typeStr: /^\s*(\w+)\s*\(\s*(.*)\s*\)\s*$/,
  emptyTypeStr: /^\s*(\w+)\s*EMPTY\s*$/,
  spaces: /\s+/,
  parenComma: /\)\s*,\s*\(/,
  doubleParenComma: /\)\s*\)\s*,\s*\(\s*\(/, // can't use {2} here
  trimParens: /^\s*\(?(.*?)\)?\s*$/
}

/**
 * Class for reading and writing Well-Known Text.
 *
 * NOTE: Adapted from OpenLayers 2.11 implementation.
 */

export default class WKTParser {
  /** Create a new parser for WKT
   *
   * @param {GeometryFactory} geometryFactory
   * @return An instance of WKTParser.
   * @private
   */
  constructor(geometryFactory) {
    this.geometryFactory = geometryFactory || new GeometryFactory()
    this.precisionModel = this.geometryFactory.getPrecisionModel()
  }

  /**
   * Deserialize a WKT string and return a geometry. Supports WKT for POINT,
   * MULTIPOINT, LINESTRING, LINEARRING, MULTILINESTRING, POLYGON, MULTIPOLYGON,
   * and GEOMETRYCOLLECTION.
   *
   * @param {String} wkt A WKT string.
   * @return {Geometry} A geometry instance.
   * @private
   */
  read(wkt) {
    let geometry, type, str
    wkt = wkt.replace(/[\n\r]/g, ' ')
    let matches = regExes.typeStr.exec(wkt)
    if (wkt.search('EMPTY') !== -1) {
      matches = regExes.emptyTypeStr.exec(wkt)
      matches[2] = undefined
    }
    if (matches) {
      type = matches[1].toLowerCase()
      str = matches[2]
      if (parse[type]) geometry = parse[type].call(this, str)
    }

    if (geometry === undefined) throw new Error('Could not parse WKT ' + wkt)

    return geometry
  }

  /**
   * Serialize a geometry into a WKT string.
   *
   * @param {Geometry} geometry A feature or array of features.
   * @return {String} The WKT string representation of the input geometries.
   * @private
   */
  write(geometry) {
    return this.extractGeometry(geometry)
  }

  /**
   * Entry point to construct the WKT for a single Geometry object.
   *
   * @param {Geometry} geometry
   * @return {String} A WKT string of representing the geometry.
   * @private
   */
  extractGeometry(geometry) {
    const type = geometry.getGeometryType().toLowerCase()
    if (!extract[type]) return null

    const wktType = type.toUpperCase()
    let data
    if (geometry.isEmpty())
      data = wktType + ' EMPTY'
    else data = wktType + '(' + extract[type].call(this, geometry) + ')'

    return data
  }
}

/**
 * Object with properties corresponding to the geometry types. Property values
 * are functions that do the actual data extraction.
 * @private
 */
const extract = {
  coordinate(coordinate) {
    this.precisionModel.makePrecise(coordinate)
    return coordinate.x + ' ' + coordinate.y
  },

  /**
   * Return a space delimited string of point coordinates.
   *
   * @param {Point}
   *          point
   * @return {String} A string of coordinates representing the point.
   */
  point(point) {
    return extract.coordinate.call(this, point._coordinates._coordinates[0])
  },

  /**
   * Return a comma delimited string of point coordinates from a multipoint.
   *
   * @param {MultiPoint}
   *          multipoint
   * @return {String} A string of point coordinate strings representing the
   *         multipoint.
   */
  multipoint(multipoint) {
    const array = []
    for (let i = 0, len = multipoint._geometries.length; i < len; ++i) array.push('(' + extract.point.call(this, multipoint._geometries[i]) + ')')

    return array.join(',')
  },

  /**
   * Return a comma delimited string of point coordinates from a line.
   *
   * @param {LineString} linestring
   * @return {String} A string of point coordinate strings representing the linestring.
   */
  linestring(linestring) {
    const array = []
    for (let i = 0, len = linestring._points._coordinates.length; i < len; ++i) array.push(extract.coordinate.call(this, linestring._points._coordinates[i]))

    return array.join(',')
  },

  linearring(linearring) {
    const array = []
    for (let i = 0, len = linearring._points._coordinates.length; i < len; ++i) array.push(extract.coordinate.call(this, linearring._points._coordinates[i]))

    return array.join(',')
  },

  /**
   * Return a comma delimited string of linestring strings from a
   * multilinestring.
   *
   * @param {MultiLineString} multilinestring
   * @return {String} A string of of linestring strings representing the multilinestring.
   */
  multilinestring(multilinestring) {
    const array = []
    for (let i = 0, len = multilinestring._geometries.length; i < len; ++i)
      array.push('(' +
        extract.linestring.call(this, multilinestring._geometries[i]) +
        ')')

    return array.join(',')
  },

  /**
   * Return a comma delimited string of linear ring arrays from a polygon.
   *
   * @param {Polygon} polygon
   * @return {String} An array of linear ring arrays representing the polygon.
   */
  polygon(polygon) {
    const array = []
    array.push('(' + extract.linestring.call(this, polygon._shell) + ')')
    for (let i = 0, len = polygon._holes.length; i < len; ++i) array.push('(' + extract.linestring.call(this, polygon._holes[i]) + ')')

    return array.join(',')
  },

  /**
   * Return an array of polygon arrays from a multipolygon.
   *
   * @param {MultiPolygon} multipolygon
   * @return {String} An array of polygon arrays representing the multipolygon.
   */
  multipolygon(multipolygon) {
    const array = []
    for (let i = 0, len = multipolygon._geometries.length; i < len; ++i) array.push('(' + extract.polygon.call(this, multipolygon._geometries[i]) + ')')

    return array.join(',')
  },

  /**
   * Return the WKT portion between 'GEOMETRYCOLLECTION(' and ')' for an
   * geometrycollection.
   *
   * @param {GeometryCollection} collection
   * @return {String} internal WKT representation of the collection.
   */
  geometrycollection(collection) {
    const array = []
    for (let i = 0, len = collection._geometries.length; i < len; ++i) array.push(this.extractGeometry(collection._geometries[i]))

    return array.join(',')
  }
}

/**
 * Object with properties corresponding to the geometry types. Property values
 * are functions that do the actual parsing.
 * @private
 */
const parse = {

  coord(str) {
    const coords = str.trim().split(regExes.spaces)
    const coord = new Coordinate(Number.parseFloat(coords[0]), Number.parseFloat(coords[1]))
    this.precisionModel.makePrecise(coord)
    return coord
  },

  /**
   * Return point geometry given a point WKT fragment.
   *
   * @param {String} str A WKT fragment representing the point.
   * @return {Point} A point geometry.
   * @private
   */
  point(str) {
    if (str === undefined) return this.geometryFactory.createPoint()
    return this.geometryFactory.createPoint(parse.coord.call(this, str))
  },

  /**
   * Return a multipoint geometry given a multipoint WKT fragment.
   *
   * @param {String} str A WKT fragment representing the multipoint.
   * @return {Point} A multipoint feature.
   * @private
   */
  multipoint(str) {
    if (str === undefined) return this.geometryFactory.createMultiPoint()
    let point
    const points = str.trim().split(',')
    const components = []
    for (let i = 0, len = points.length; i < len; ++i) {
      point = points[i].replace(regExes.trimParens, '$1')
      components.push(parse.point.call(this, point))
    }
    return this.geometryFactory.createMultiPoint(components)
  },

  /**
   * Return a linestring geometry given a linestring WKT fragment.
   *
   * @param {String} str A WKT fragment representing the linestring.
   * @return {LineString} A linestring geometry.
   * @private
   */
  linestring(str) {
    if (str === undefined) return this.geometryFactory.createLineString()

    const points = str.trim().split(',')
    const components = []
    let coords
    for (let i = 0, len = points.length; i < len; ++i) components.push(parse.coord.call(this, points[i]))

    return this.geometryFactory.createLineString(components)
  },

  /**
   * Return a linearring geometry given a linearring WKT fragment.
   *
   * @param {String} str A WKT fragment representing the linearring.
   * @return {LinearRing} A linearring geometry.
   * @private
   */
  linearring(str) {
    if (str === undefined) return this.geometryFactory.createLinearRing()

    const points = str.trim().split(',')
    const components = []
    let coords
    for (let i = 0, len = points.length; i < len; ++i) components.push(parse.coord.call(this, points[i]))

    return this.geometryFactory.createLinearRing(components)
  },

  /**
   * Return a multilinestring geometry given a multilinestring WKT fragment.
   *
   * @param {String} str A WKT fragment representing the multilinestring.
   * @return {MultiLineString} A multilinestring geometry.
   * @private
   */
  multilinestring(str) {
    if (str === undefined) return this.geometryFactory.createMultiLineString()

    let line
    const lines = str.trim().split(regExes.parenComma)
    const components = []
    for (let i = 0, len = lines.length; i < len; ++i) {
      line = lines[i].replace(regExes.trimParens, '$1')
      components.push(parse.linestring.call(this, line))
    }
    return this.geometryFactory.createMultiLineString(components)
  },

  /**
   * Return a polygon geometry given a polygon WKT fragment.
   *
   * @param {String} str A WKT fragment representing the polygon.
   * @return {Polygon} A polygon geometry.
   * @private
   */
  polygon(str) {
    if (str === undefined) return this.geometryFactory.createPolygon()

    let ring, linestring, linearring
    const rings = str.trim().split(regExes.parenComma)
    let shell
    const holes = []
    for (let i = 0, len = rings.length; i < len; ++i) {
      ring = rings[i].replace(regExes.trimParens, '$1')
      linestring = parse.linestring.call(this, ring)
      linearring = this.geometryFactory.createLinearRing(linestring._points)
      if (i === 0)
        shell = linearring
      else holes.push(linearring)
    }
    return this.geometryFactory.createPolygon(shell, holes)
  },

  /**
   * Return a multipolygon geometry given a multipolygon WKT fragment.
   *
   * @param {String} str A WKT fragment representing the multipolygon.
   * @return {MultiPolygon} A multipolygon geometry.
   * @private
   */
  multipolygon(str) {
    if (str === undefined) return this.geometryFactory.createMultiPolygon()

    let polygon
    const polygons = str.trim().split(regExes.doubleParenComma)
    const components = []
    for (let i = 0, len = polygons.length; i < len; ++i) {
      polygon = polygons[i].replace(regExes.trimParens, '$1')
      components.push(parse.polygon.call(this, polygon))
    }
    return this.geometryFactory.createMultiPolygon(components)
  },

  /**
   * Return a geometrycollection given a geometrycollection WKT fragment.
   *
   * @param {String} str A WKT fragment representing the geometrycollection.
   * @return {GeometryCollection}
   * @private
   */
  geometrycollection(str) {
    if (str === undefined) return this.geometryFactory.createGeometryCollection()

    // separate components of the collection with |
    str = str.replace(/,\s*([A-Za-z])/g, '|$1')
    const wktArray = str.trim().split('|')
    const components = []
    for (let i = 0, len = wktArray.length; i < len; ++i) components.push(this.read(wktArray[i]))

    return this.geometryFactory.createGeometryCollection(components)
  }
}