类名 common/base/geometry/Arc.js
import * as T from '@turf/turf'
import Geometry from './Geometry'
import { defaultValue, isNumber } from '../../util'
import Zondy from '../Zondy'
import { GeometryType } from '../enum'
import Polygon from './Polygon'
import Point from './Point'
import Extent from './Extent'
import LineString from './LineString'
import GeometryEngine from './GeometryEngine'
import SpatialReference from './SpatialReference'
import Projection from '../Projection'

/**
 * 几何弧段,三个点确定一个弧段,参考示例:<a href='#Arc'>[几何圆对象]</a>
 * <br><br>[ES5引入方式]:<br/>
 * Zondy.Geometry.Arc() <br/>
 * [ES6引入方式]:<br/>
 * import { Arc } from "@mapgis/webclient-common" <br/>
 * <br/>
 * @class Arc
 * @moduleEX GeometryModule
 * @private
 * @extends Geometry
 * @param {Object} options 构造参数
 * @param {Point|Number[]} [options.point1] 弧段上的第一个点
 * @param {Point|Number[]} [options.point2] 弧段上的第二个点
 * @param {Point|Number[]} [options.point3] 弧段上的第三个点
 * @param {Number} [options.numberOfPoints = 120] 圆转换为区插值点个数
 * @param {SpatialReference} [options.spatialReference = new Zondy.SpatialReference('EPSG:4326')] 几何点的空间参考系,默认4326,当不是4326时请指定坐标系,方便进行投影转换,参考示例:<a href='#SpatialReference'>[指定坐标系]</a>
 * @summary <h5>支持如下方法:</h5>
 * <a href='#toPolygon'>[1、导出为区]</a><br/>
 * <a href='#toString'>[2、返回字符串]</a><br/>
 * <a href='#toXMl'>[3、导出为OGC服务要求的xml字符串]</a><br/>
 * <a href='#getIGSType'>[4、返回IGS所对应的GeometryModule型]</a><br/>
 * <a href='#fromJSON'>[5、通过传入的json构造并返回一个新的几何对象]</a><br/>
 * <a href='#toJSON'>[6、导出为json对象]</a><br/>
 * [7、克隆几何对象]{@link Geometry#clone}
 *
 * @example <caption><h7 id='Arc'>创建几何对象</h7></caption>
 * // ES5引入方式
 * const { Arc } = Zondy.Geometry
 * // ES6引入方式
 * import { Arc } from "@mapgis/webclient-common"
 * new Arc({
 *   // 弧段上的第一个点
 *   point1: [113.5, 30],
 *   // 弧段上的第二个点
 *   point2: [114, 30.5],
 *   // 弧段上的第三个点
 *   point3: [113.5, 31]
 * })
 *
 * @example <caption><h7 id='spatialReference'>指定坐标系</h7></caption>
 * // ES5引入方式
 * const { Arc } = Zondy.Geometry
 * const { SpatialReference } = Zondy
 * // ES6引入方式
 * import { Arc, SpatialReference } from "@mapgis/webclient-common"
 * new Arc({
 *   // 弧段上的第一个点
 *   point1: [],
 *   // 弧段上的第二个点
 *   point2: [],
 *   // 弧段上的第三个点
 *   point3: []
 *   // 当不是4326时请指定坐标系,方便进行投影转换
 *   spatialReference: new SpatialReference('EPSG:3857')
 * })
 */
class Arc extends Geometry {
  constructor(options) {
    super(options)
    options = defaultValue(options, {})
    /**
     * 表示GeometryModule型的字符串值
     * @member {GeometryType} Arc.prototype.type
     */
    this.type = GeometryType.arc

    /**
     * 几何点的坐标数组
     * @member {Array} Arc.prototype.coordinates
     */
    this.coordinates = defaultValue(options.coordinates, [])

    /**
     * 弧段上的第一个点
     * @member {Number[]} Arc.prototype.point1
     */
    this.point1 = this.coordinates[0]
    if (!this.point1) {
      throw new Error('缺少弧段上的第一个点坐标')
    }
    this.point1 = Point.toCoordinates(this.point1)

    /**
     * 弧段上的第二个点
     * @member {Number[]} Arc.prototype.point2
     */
    this.point2 = this.coordinates[1]
    if (!this.point2) {
      throw new Error('缺少弧段上的第二个点坐标')
    }
    this.point2 = Point.toCoordinates(this.point2)

    /**
     * 弧段上的第三个点
     * @member {Number[]} Arc.prototype.point3
     */
    this.point3 = this.coordinates[2]
    if (!this.point3) {
      throw new Error('缺少弧段上的第三个点坐标')
    }
    this.point3 = Point.toCoordinates(this.point3)

    /**
     * 定义弧段上点的数量
     * @member {Number} Arc.prototype.numberOfPoints
     */
    this.numberOfPoints = defaultValue(options.numberOfPoints, 120)
    if (!isNumber(this.numberOfPoints)) {
      throw new Error('圆曲线上点的数量必须为数字')
    }

    // 计算是否为三维
    if (
      this.point1.length === 3 &&
      this.point1.length === 3 &&
      this.point1.length === 3
    ) {
      this.hasZ = true
    }

    // 获取圆心
    this._getCenter()
    // 设置第一个点
    this._pointBegan = this.point1
    // 设置第最后一个点
    this._pointEnd = this.point3
  }

  /**
   * 通过弧段上的三个点计算圆心坐标,参考资料https://www.jianshu.com/p/f99246170561和桌面逻辑
   * @private
   * */
  _getCenter() {
    const x1 = this.point1[0]
    const x2 = this.point2[0]
    const x3 = this.point3[0]
    const y1 = this.point1[1]
    const y2 = this.point2[1]
    const y3 = this.point3[1]
    const z1 = 0
    const z2 = 0
    const z3 = 0
    const a1 = y1 * z2 - y2 * z1 - y1 * z3 + y3 * z1 + y2 * z3 - y3 * z2
    const b1 = -(x1 * z2 - x2 * z1 - x1 * z3 + x3 * z1 + x2 * z3 - x3 * z2)
    const c1 = x1 * y2 - x2 * y1 - x1 * y3 + x3 * y1 + x2 * y3 - x3 * y2
    const d1 = -(
      x1 * y2 * z3 -
      x1 * y3 * z2 -
      x2 * y1 * z3 +
      x2 * y3 * z1 +
      x3 * y1 * z2 -
      x3 * y2 * z1
    )
    const a2 = 2 * (x2 - x1)
    const b2 = 2 * (y2 - y1)
    const c2 = 2 * (z2 - z1)
    const d2 = x1 * x1 + y1 * y1 + z1 * z1 - x2 * x2 - y2 * y2 - z2 * z2

    const a3 = 2 * (x3 - x1)
    const b3 = 2 * (y3 - y1)
    const c3 = 2 * (z3 - z1)
    const d3 = x1 * x1 + y1 * y1 + z1 * z1 - x3 * x3 - y3 * y3 - z3 * z3

    const cx =
      -(
        b1 * c2 * d3 -
        b1 * c3 * d2 -
        b2 * c1 * d3 +
        b2 * c3 * d1 +
        b3 * c1 * d2 -
        b3 * c2 * d1
      ) /
      (a1 * b2 * c3 -
        a1 * b3 * c2 -
        a2 * b1 * c3 +
        a2 * b3 * c1 +
        a3 * b1 * c2 -
        a3 * b2 * c1)
    const cy =
      (a1 * c2 * d3 -
        a1 * c3 * d2 -
        a2 * c1 * d3 +
        a2 * c3 * d1 +
        a3 * c1 * d2 -
        a3 * c2 * d1) /
      (a1 * b2 * c3 -
        a1 * b3 * c2 -
        a2 * b1 * c3 +
        a2 * b3 * c1 +
        a3 * b1 * c2 -
        a3 * b2 * c1)
    const cz =
      -(
        a1 * b2 * d3 -
        a1 * b3 * d2 -
        a2 * b1 * d3 +
        a2 * b3 * d1 +
        a3 * b1 * d2 -
        a3 * b2 * d1
      ) /
      (a1 * b2 * c3 -
        a1 * b3 * c2 -
        a2 * b1 * c3 +
        a2 * b3 * c1 +
        a3 * b1 * c2 -
        a3 * b2 * c1)

    if (this.hasZ) {
      this.center = [cx, cy, this.point1[2]]
    } else {
      this.center = [cx, cy]
    }
  }

  /**
   * 通过传入的json构造并返回一个新的几何对象
   * @param {Object} [json] JSON对象
   * @example <caption><h7 id='fromJSON'>通过传入的json构造并返回一个新的几何对象</h7></caption>
   * // ES5引入方式
   * const { Arc } = Zondy.Geometry
   * // ES6引入方式
   * import { Arc } from "@mapgis/webclient-common"
   * const json = {
   *   point1: [113.5, 30],
   *   point2: [114, 30.5],
   *   point3: [113.5, 31]
   * }
   * const circle = Arc.fromJSON(json)
   */
  static fromJSON(json) {
    json = defaultValue(json, {})
    return new Arc(json)
  }

  /**
   * <a id='toJSON'></a>
   * 导出为json对象
   * @return {Object} json对象
   */
  toJSON() {
    const json = super.toJSON()
    json.point1 =
      this.point1 instanceof Point ? this.point1.toJSON() : this.point1
    json.point2 =
      this.point2 instanceof Point ? this.point2.toJSON() : this.point2
    json.point3 =
      this.point3 instanceof Point ? this.point3.toJSON() : this.point3
    json.numberOfPoints = this.numberOfPoints
    json.extent = this.extent.toJSON()

    return json
  }

  /**
   * 导出为区
   * @returns {Polygon} 返回区对象
   * @example <caption><h7 id='toPolygon'>导出为区</h7></caption>
   * // ES5引入方式
   * const { Arc } = Zondy.Geometry
   * // ES6引入方式
   * import { Arc } from "@mapgis/webclient-common"
   * const circle = new Arc({
   *   // 中心点
   *   center:[113, 42],
   *   // 半径
   *   radius:20
   * })
   * const polygon = circle.toPolygon()
   */
  toPolygon() {
    const coordinates = this._tranArcToPath(
      this.center,
      this._pointBegan,
      this._pointEnd,
      this.numberOfPoints
    )
    coordinates[0].push(coordinates[0][0])
    return new Polygon({
      coordinates,
      spatialReference: SpatialReference.fromJSON(this.spatialReference)
    })
  }

  toLineString() {
    const coordinates = this._tranArcToPath(
      this.center,
      this._pointBegan,
      this._pointEnd,
      this.numberOfPoints
    )
    return new LineString({
      coordinates: coordinates[0],
      spatialReference: SpatialReference.fromJSON(this.spatialReference)
    })
  }

  /**
   * @function Arc.prototype._tranArcToPath
   * @private
   * @description 转换三点弧为区
   * @returns {Number[][]} 返回转换后的坐标数组
   */
  _tranArcToPath(center, pointBegan, pointEnd, numberOfPoints) {
    let _center = JSON.parse(JSON.stringify(center))
    let _pointBegan = JSON.parse(JSON.stringify(pointBegan))
    let _pointEnd = JSON.parse(JSON.stringify(pointEnd))

    // 如果不是经纬度坐标系,则投影到经纬度坐标系,方便求角度
    if (!this.spatialReference.isGeographic) {
      const _sr = new SpatialReference({
        wkid: 4326
      })
      _center = Projection.project(
        new Point({
          coordinates: _center,
          spatialReference: this.spatialReference
        }),
        _sr
      ).coordinates
      _pointBegan = Projection.project(
        new Point({
          coordinates: _pointBegan,
          spatialReference: this.spatialReference
        }),
        _sr
      ).coordinates
      _pointEnd = Projection.project(
        new Point({
          coordinates: _pointEnd,
          spatialReference: this.spatialReference
        }),
        _sr
      ).coordinates
    }

    // 构造turf的点
    const _centerT = T.point(_center)
    const _pointBeganT = T.point(_pointBegan)
    const _pointEndT = T.point(_pointEnd)

    // 计算圆心半径
    const line = new LineString({
      coordinates: [center, pointBegan]
    })
    const _radius = GeometryEngine.planarLength(line)

    // 设置起始角度
    let _beganAngle = 90 - T.rhumbBearing(_centerT, _pointBeganT)

    // 设置结束角度
    const _endAngle = 90 - T.rhumbBearing(_centerT, _pointEndT)

    // 色设置弧段旋转的角度
    const _arcAngle = _endAngle - _beganAngle

    // 计算步长
    const _step = (Math.PI * (_arcAngle / 180)) / numberOfPoints

    // 将角度转为弧度
    _beganAngle = Math.PI * (_beganAngle / 180)

    // 开始极端弧段
    let _coordinates = []
    // 外圈点坐标
    const _ringPoints = [pointBegan]
    for (let i = 0; i < numberOfPoints - 1; i++) {
      _beganAngle += _step
      const point = [
        center[0] + _radius * Math.cos(_beganAngle),
        center[1] + _radius * Math.sin(_beganAngle)
      ]
      if (_endAngle <= 180) {
        if (point[0] > _pointEnd[0]) {
          _ringPoints.push(point)
        }
      } else if (point[0] < _pointEnd[0]) {
        _ringPoints.push(point)
      }
    }
    _ringPoints.push(pointEnd)
    _coordinates.push(_ringPoints)

    if (this.hasZ) {
      _coordinates = _coordinates.map((ring) => {
        return ring.map((coordinate) => {
          return [coordinate[0], coordinate[1], center[2]]
        })
      })
    }

    return _coordinates
  }

  /**
   * @function Arc.prototype._calcExtent
   * @private
   * @description 计算外包盒
   * @param {Boolean} hasZ 是否是三维
   * @returns {Extent} 外包盒
   */
  _calcExtent(hasZ) {
    const polygon = this.toPolygon()
    const poly = T.polygon(polygon._cloneCoordinates())
    const bbox = T.bbox(poly)

    if (!hasZ) {
      return new Extent({
        xmin: bbox[0],
        ymin: bbox[1],
        xmax: bbox[2],
        ymax: bbox[3]
      })
    }
    return new Extent({
      xmin: bbox[0],
      ymin: bbox[1],
      zmin: bbox[2],
      xmax: bbox[3],
      ymax: bbox[4],
      zmax: bbox[5]
    })
  }

  /**
   * 克隆几何对象
   * @return {Geometry} 克隆后的几何对象
   */
  clone() {
    return new Arc(this.toJSON())
  }
}

Object.defineProperties(Arc.prototype, {
  /**
   * 外包盒
   * @member {Number} Arc.prototype.extent
   * */
  extent: {
    configurable: false,
    get() {
      return this._calcExtent(this.hasZ)
    }
  }
})

Zondy.Geometry.Arc = Arc
export default Arc
构造函数
成员变量
方法
事件