Home Reference Source

src/Line.js

import Point from './Point'
import {EPSILON} from './utils'
// noinspection UnterminatedStatementJS

const lineDefaultOptions = {
  stroke: '#E1E1E1',
  strokeWidth: 2
}

/**
 * Class representing a line segment in 2 dimension cartesian space
 * and helpers function to get other representations like toWKT, toGeoJSON etc
 * the line equation y = m*x + b can be derived with m=getSlope() and b=getYintercept() methods
 */
export default class Line {
  /**
   * Creates a line
   * @param {Point} startPoint of the Line (will be copied by value)
   * @param {Point} endPoint of the Line (will be copied by value)
   * @param {Object} params
   */
  constructor (startPoint = new Point(), endPoint = new Point(1, 1), params = null) {
    this._options = Object.assign({}, lineDefaultOptions, params)
    // using property setters above to check for valid values
    this.startPoint = startPoint
    this.endPoint = endPoint
  }

  /**
   * get a new Line that is a copy (clone) of the otherLine passed has parameter
   * @param {Line} otherLine is the Line you want to copy
   * @returns {Line} a new Line located at the same cartesian coordinates as otherLine
   */
  static fromLine (otherLine) {
    if (otherLine instanceof Line) {
      return new Line(otherLine.startPoint, otherLine.endPoint)
    } else {
      throw new TypeError('fromLine needs parameter otherLine of type Line')
    }
  }

  /**
   * Get the starting Point.
   * @return {Point} The starting Point of this line.
   */
  get startPoint () {
    return this._startPoint
  }

  /**
   * Set the starting Point.
   * @param {Point} otherPoint is the starting Point
   */
  set startPoint (otherPoint) {
    if (otherPoint instanceof Point) {
      if (otherPoint.isInvalid) {
        throw new TypeError(`Class Line needs a valid startPoint : ${otherPoint.InvalidReason}`)
      } else {
        if (this._endPoint !== undefined && otherPoint.equal(this._endPoint)) {
          throw new TypeError(`Class Line needs a startPoint different from endPoint : ${otherPoint.toString()} == ${this._endPoint.toString()}`)
        } else {
          // make a copy of otherPoint values we don't want keep a reference
          this._startPoint = Point.fromPoint(otherPoint)
        }
      }
    } else {
      throw new TypeError('startPoint setter needs parameter otherPoint of type Point')
    }
  }

  /**
   * Get the endPoint value.
   * @return {Point} The endPoint value.
   */
  get endPoint () {
    return this._endPoint
  }

  /**
   * Set the endPoint value
   * @param {Point} otherPoint is the endPoint
   */
  set endPoint (otherPoint) {
    if (otherPoint instanceof Point) {
      if (otherPoint.isInvalid) {
        throw new TypeError(`Class Line needs a valid endPoint : ${otherPoint.InvalidReason}`)
      } else {
        if (this._startPoint !== undefined && otherPoint.equal(this._startPoint)) {
          throw new TypeError(`Class Line needs a endPoint different from startPoint: ${otherPoint.toString()} == ${this._startPoint.toString()}`)
        } else {
          // make a copy of otherPoint values we don't want keep a reference
          this._endPoint = Point.fromPoint(otherPoint)
        }
      }
    } else {
      throw new TypeError('endPoint setter needs parameter otherPoint of type Point')
    }
  }

  /**
   * give a string representation of this Line class instance
   * @returns {string}
   */
  toString () {
    return `Line(${this.startPoint.toString()} -> ${this.endPoint.toString()})`
  }

  /**
   * give an array representation of this Line class instance,
   * you can use Array.flatten()  to convert to [x1, y1, x2, y2]
   * if flatten is not implemented use reduce instead : arrResult.reduce((acc, val) => acc.concat(val), []);
   * @returns {Array} an Array giving the coord of the line like this [[x1, y1], [x2, y2]]
   */
  toArray () {
    let tmpArray = []
    tmpArray.push(this.startPoint.toArray())
    tmpArray.push(this.endPoint.toArray())
    return tmpArray
  }

  /**
   * give an OGC Well-known text (WKT) representation of this class instance
   * https://en.wikipedia.org/wiki/Well-known_text
   * @returns {string}
   */
  toWKT () {
    return `LINESTRING(${this.startPoint.x} ${this.startPoint.y}, ${this.endPoint.x} ${this.endPoint.y})`
  }

  /**
   * give an Postgis Extended Well-known text (EWKT) representation of this class instance
   * https://postgis.net/docs/using_postgis_dbmanagement.html#EWKB_EWKT
   * @param {number} srid is the Spatial reference systems identifier EPSG code default is 21781 for Switzerland MN03
   * @returns {string}
   */
  toEWKT (srid = 21781) {
    return `SRID=${srid};LINESTRING(${this.startPoint.x} ${this.startPoint.y}, ${this.endPoint.x} ${this.endPoint.y})`
  }

  // TO implement toEWKB I can maybe use this lib : https://github.com/cschwarz/wkx

  /**
   * give a GeoJSON geometry (http://geojson.org/) representation of this class instance geometry
   * @returns {string}
   */
  toGeoJSON () {
    return `{"type":"LineString","coordinates":[[${this.startPoint.x},${this.startPoint.y}],[${this.endPoint.x},${this.endPoint.y}]]}`
  }

  /**
   * give the length of the line
   * @returns {number} the length
   */
  getLength () {
    return this.startPoint.distance(this.endPoint)
  }

  /**
   * give the array [deltaX, deltaY] corresponding to difference in cartesian coordinates of this line
   * @returns {number} the length
   */
  getVectorArray () {
    const arrDelta = []
    arrDelta.push(this.endPoint.x - this.startPoint.x)
    arrDelta.push(this.endPoint.y - this.startPoint.y)
    return arrDelta
  }

  /**
   * gives the slope of the line or m defined as Δy/Δx in the line equation y = m*x + b (b is y-intercept)
   * @returns {number} a number the slope of the Line and Infinity if line is vertical
   */
  getSlope () {
    const delta = this.getVectorArray()
    if (delta[0] < EPSILON) {
      return Infinity
    } else {
      return delta[1] / delta[0] //  Δy/Δx
    }
  }

  /**
   * gives the y-intercept of the line or b in the line equation y = m*x + b (m is the Slope defined as Δy/Δx)
   * @returns {number} a number the y-intercept of the Line and NaN if line is vertical
   */
  getYIntercept () {
    const slope = this.getSlope()
    if (slope === Infinity) {
      return NaN
    } else {
      return this.startPoint.y - (slope * this.startPoint.x)
    }
  }
}