类名 common/base/geometry/Geometry.js
import * as T from '@turf/turf'
import Evented from '../Evented'
import { defaultValue, defined, isNull, Log } from '../../util'
import Zondy from '../Zondy'
import { GeometryType, IGSGeometryType } from '../enum'
import { createGeometry, createGeometryByGeoJSON } from './Utiles'
import SpatialReference from './SpatialReference'
import { Polygon, GeometryEngine } from './index'

/**
 * 几何图形对象基类
 * <br><br>[ES5引入方式]:<br/>
 * Zondy.Geometry.Geometry() <br/>
 * [ES6引入方式]:<br/>
 * import { Geometry } from "@mapgis/webclient-common" <br/>
 * <br/>
 * @class Geometry
 * @extends Evented
 * @moduleEX GeometryModule
 * @param {Object} options 构造参数
 * @param {SpatialReference} [options.spatialReference = new Zondy.SpatialReference('EPSG:4326')] 几何点的空间参考系,默认4326
 */
class Geometry extends Evented {
  constructor(options) {
    super(options)
    // 构造几何对象
    options = defaultValue(options, {})
    /**
     * 几何点的空间参考系
     * @member {SpatialReference} Geometry.prototype.spatialReference
     */
    this.spatialReference = defaultValue(
      SpatialReference.fromJSON(options.spatialReference),
      new SpatialReference('EPSG:4326')
    )
    /**
     * 是否含有z坐标
     * @readonly
     * @member {Boolean} Geometry.prototype.hasZ
     */
    this.hasZ = false
    /**
     * 几何类型
     * @readonly
     * @member {GeometryType} GeometryType.prototype.type
     */
    this.type = GeometryType.geometry
  }

  static fromGeoJSON(json) {
    return createGeometryByGeoJSON(json)
  }

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

  /**
   * 通过一个JSON对象创建一个几何对象
   * @param {Object} json JSON对象
   * @return {Geometry} 几何对象
   *
   * @example <caption><h5 id='fromJSON'>通过一个JSON对象创建一个几何对象</h7></caption>
   * // ES5引入方式
   * const { Geometry } = Zondy
   * const { GeometryType } = Zondy.Enum
   * // ES6引入方式
   * import { Geometry, GeometryType } from "@mapgis/webclient-common"
   * // 通过json构造一个几何对象
   * const polygon = Geometry.fromJSON({
   *   // 注意必须传入一个几何类型
   *   type: GeometryType.polygon,
   *   coordinates: [
   *       [
   *         [113, 29],
   *         [114, 29],
   *         [114, 30],
   *         [113, 30],
   *         [113, 29]
   *       ]
   *   ]
   * })
   */
  static fromJSON(json) {
    json = defaultValue(json, {})
    return createGeometry(json)
  }

  /**
   * 导出为json对象
   * @return {Object} json对象
   */
  toJSON() {
    return {
      spatialReference:
        this.spatialReference instanceof SpatialReference
          ? this.spatialReference.toJSON()
          : this.spatialReference,
      hasZ: this.hasZ,
      type: this.type
    }
  }

  /**
   * 导出为GeoJSON
   * @return {Object} GeoJSON对象
   * */
  toGeoJSON() {
    return {
      type: this.type,
      coordinates: this.coordinates
    }
  }

  /**
   * 导入GeoJSON
   * @param {Object} GeoJSON Object
   * */
  fromGeoJSON(geoJSON) {
    if (geoJSON || geoJSON.type !== this.type) {
      new Error('GeoJSON类型和几何类型不一致')
    }
    if (defined(this.coordinates)) {
      this.coordinates = geoJSON.coordinates
    }
  }

  /**
   * 导出为OGC服务要求的xml字符串,子类实现
   * @return {String} 字符串
   */
  toXML() {
    return ''
  }

  /**
   * 返回所对应的GeometryModule型
   * @returns {String} GeometryModule型
   */
  getType() {
    return this.type
  }

  /**
   * 返回IGS所对应的GeometryModule型
   * @returns {String} GeometryModule型
   */
  getIGSType() {
    return IGSGeometryType.geometry
  }

  /**
   * 获取GeometryModule型
   * @return {String} GeometryModule型
   */
  getGeometryType() {
    return this.type
  }

  /**
   * 导出为字符串,子类实现,这里必须是igs要求的字符串
   * @return {String} 字符串
   */
  toString() {
    return ''
  }

  /**
   * 对点数组进行是否为顺时针判断;<br/>
   * 支持的参数类型为闭合的二维点数组;<br/>
   * 示例如下:<br/>
   * <a href='#isClockwise'>[对点数组进行顺时针判断]</a><br/>
   * @param  {Array} pointArr 点数组
   * @return {Boolean} 是否为顺时针
   *
   * @example <caption><h5 id='isClockwise'>对点数组进行顺时针判断</h5></caption>
   * // ES5引入方式
   * const { Geometry } = Zondy.Geometry
   * // ES6引入方式
   * import { Geometry } from "@mapgis/webclient-common"
   * // 构造线几何对象
   * const pointArr = [
   *      [112, 30],
   *      [114, 30],
   *      [114, 32],
   *      [112, 30],
   *    ];
   * // 对点数组进行顺时针判断
   * const isClockwise = Geometry.isClockwise(pointArr)
   * console.log("是否为顺时针:", isClockwise)
   * */
  static isClockwise(pointArr) {
    pointArr = defaultValue(pointArr, undefined)
    // 1.先判断入参非空
    if (isNull(pointArr)) {
      Log.error('geometry1不能为空!')
    }

    // 2.入参类型检查,必须是数组
    if (!(pointArr instanceof Array)) {
      Log.error('输入参数类型错误,需要输入点数组!')
    }

    // 3.入参类型检查,不能是一维数组三维数组,必须是二维数组
    for (let i = 0; i < pointArr.length; i++) {
      if (!(pointArr[i] instanceof Array)) {
        Log.error('输入参数类型错误,需要输入二维数组!')
      } else {
        for (let j = 0; j < pointArr[i].length; j++) {
          if (pointArr[i][j] instanceof Array) {
            Log.error('输入参数类型错误,需要输入二维数组!')
          }
        }
      }
    }
    // 4. 是否为闭合的点检查
    if (
      !(
        pointArr[0].length === pointArr[pointArr.length - 1].length &&
        JSON.stringify(pointArr[0]) ===
          JSON.stringify(pointArr[pointArr.length - 1])
      )
    ) {
      Log.error('输入的点数组必须首尾闭合!')
    }

    // 6.通过判断点数组
    if (!isNull(pointArr)) {
      const _clockwiseRing = T.lineString(pointArr)
      return T.booleanClockwise(_clockwiseRing)
    }
  }

  /**
   * 对入参进行判断处理,构建polygon;<br/>
   * 支持的参数类型为闭合的三维点数组(多个ring构成的数组),不区分顺逆时针;<br/>
   * 示例如下:<br/>
   * <a href='#fromRings1'>[1、根据入参构建polygon]</a><br/>
   * <a href='#fromRings2'>[1、根据入参构建polygon,指定rings坐标系]</a><br/>
   * @param  {Array} pointArr 点数组
   * @param  {SpatialReference} spatialReference 几何点的空间参考系,默认4326,非必填,当不是4326时请指定坐标系,方便进行投影转换
   * @return {Array} 构建的polygon组成的对象数组
   *
   * @example <caption><h5 id='fromRings1'>点数组构建polygon</h5></caption>
   * // ES5引入方式
   * const { Geometry} = Zondy.Geometry
   * // ES6引入方式
   * import { Geometry } from "@mapgis/webclient-common"
   * // 输入三维点数组
   * const pointArr = [
   *      [
   *        [108, 30],
   *        [108, 33],
   *        [115, 33],
   *        [115, 30],
   *        [108, 30],
   *      ],
   *      [
   *        [110, 31],
   *        [113, 31],
   *        [113, 32],
   *        [110, 32],
   *        [110, 31],
   *      ],
   *      [
   *        [110, 20],
   *        [110, 25],
   *        [115, 25],
   *        [115, 20],
   *        [110, 20],
   *      ],
   *    ];
   * // 点数组构建polygon
   * const fromRings = Geometry.fromRings(pointArr)
   * console.log("构建的多边形对象:", fromRings)
   *
   * @example <caption><h5 id='fromRings2'>指定坐标系的点数组构建polygon</h5></caption>
   * // ES5引入方式
   * const { Geometry,SpatialReference} = Zondy.Geometry
   * // ES6引入方式
   * import { Geometry,SpatialReference } from "@mapgis/webclient-common"
   * // 输入指定坐标系的点数组
   *  const pointArr_4547 = [
   *    [
   *      [-79450.434008356, 3335318.50110312],
   *      [-61138.8507993389, 3668785.80194059],
   *      [593455.16027313, 3653192.25094895],
   *      [596488.74806744, 3320534.43645214],
   *      [-79450.434008356, 3335318.50110312],
   *    ],
   *    [
   *      [22192.1178133016, 3441729.02787479],
   *      [213425.289259696, 3434840.60529103],
   *      [216463.35881388, 3545788.2250497],
   *      [27269.5496056064, 3552800.06686639],
   *      [22192.1178133016, 3441729.02787479],
   *    ],
   *    [
   *      [307007.697900227, 3441729.02787479],
   *      [500000.000000819, 3441729.02787479],
   *      [500000.00000081, 3545788.2250497],
   *      [308973.125268913, 3545788.2250497],
   *      [307007.697900227, 3441729.02787479],
   *    ],
   *    [
   *      [81149.2212748193, 2217372.5132106],
   *      [81149.2212748193, 2766426.51455527],
   *      [600953.408059909, 2766426.51455527],
   *      [600953.408059909, 2217372.5132106],
   *      [81149.2212748193, 2217372.5132106],
   *    ],
   *  ];
   *
   * // 创建坐标系
   * const spatialReference = new SpatialReference({
   *    wkid: 4547,
   *  });
   * // 点数组构建polygon
   * const fromRings = Geometry.fromRings(pointArr_4547, spatialReference)
   * console.log("构建的指定坐标系的多边形对象:", fromRings)
   * */
  static fromRings(geometry, spatialReference) {
    geometry = defaultValue(geometry, undefined)
    const _spatialReference = defaultValue(
      SpatialReference.fromJSON(spatialReference),
      new SpatialReference('EPSG:4326')
    )

    // 1.先判断入参非空
    if (isNull(geometry)) {
      Log.error('geometry1不能为空!')
    }

    // 2.入参类型检查,必须是数组,字符串也要转数组
    if (!(geometry instanceof Array)) {
      Log.error('输入参数类型错误,需要输入点数组!')
    }

    // 3.入参类型检查,不能是一维数组四维数组,必须是三维数组
    const a = this._multiarr(geometry)
    if (a !== 3) {
      Log.error('输入参数错误,需要输入三维数组!')
    }

    let _polygon
    // 4.三维数组处理
    //  4.1 判断三维数组长度是否为1
    if (geometry.length === 1) {
      // 多了一层数组的二维数组[[[1,2],[3,4],[5,6],[1,2]]]
      const _geometry = geometry[0]
      // 检查闭合点
      if (
        !(
          _geometry[0].length === _geometry[_geometry.length - 1].length &&
          JSON.stringify(_geometry[0]) ===
            JSON.stringify(_geometry[_geometry.length - 1])
        )
      ) {
        _geometry.push(_geometry[0])
      }
      _polygon = new Polygon({
        coordinates: [_geometry],
        spatialReference: _spatialReference
      })
      return _polygon
    } else {
      // 先遍历每个ring
      // 用来记录所有ring的数组
      const _ringsObjArr = []
      // 有多个圈,先遍历每个圈
      for (let i = 0; i < geometry.length; i++) {
        // 每个圈对象化
        const _pointObj = {
          geometry: undefined,
          area: null,
          inner: []
        }
        // 5.闭合点检查
        if (
          !(
            JSON.stringify(geometry[i][0]) ===
            JSON.stringify(geometry[i][geometry[i].length - 1])
          )
        ) {
          geometry[i].push(geometry[i][0])
        }
        // 先 生成polygon
        const _geometry = new Polygon({
          coordinates: [geometry[i]],
          spatialReference: _spatialReference
        })
        _pointObj.geometry = _geometry
        // 计算面积
        _pointObj.area = GeometryEngine.planarArea(_geometry)
        _ringsObjArr.push(_pointObj)
      }
      // 在这里得到ring对象的数组 _ringsObjArr

      // 用来存放处理好的ring以及他的inner
      const stateObj = []
      const ringArr = this._processArr(stateObj, _ringsObjArr)

      // 开始new
      const coords = []
      for (let i = 0; i < ringArr.length; i++) {
        const coord = [ringArr[i].geometry.coordinates[0]]
        // 检查有无内圈
        if (ringArr[i].inner.length >= 1) {
          ringArr[i].inner.forEach((item) => {
            coord.push(item.geometry.coordinates[0])
          })
        }
        coords.push(coord)
      }
      const polygonObj = []
      // 返回polygon组成的Object
      for (let i = 0; i < ringArr.length; i++) {
        // new polygon
        _polygon = new Polygon({
          coordinates: coords[i],
          spatialReference: _spatialReference
        })
        polygonObj.push(_polygon)
      }
      return polygonObj
    }
  }

  /**
   * ring 生成 polygon拓扑关系检查
   * @param {Array} arr 多维数组
   * @return {Array} 返回整理好的对象数组
   * */
  static _processArr(stateObj, ringsObjArr) {
    // 根据面积排序
    ringsObjArr.sort((a, b) => {
      return b.area - a.area
    })
    // 得到面积从大到小排序的对象数组 ringsObjArr

    // 取第一个
    const firstObj = ringsObjArr[0]
    // 用来存新的、与第一个ring相离的ring对象
    const _newDisjointObjArr = []
    // 遍历剩下的
    for (let i = 1; i < ringsObjArr.length; i++) {
      // 判断第一个和剩下的是否相离
      if (GeometryEngine.disjoint(firstObj.geometry, ringsObjArr[i].geometry)) {
        // 若相离,作为新数组存入
        _newDisjointObjArr.push(ringsObjArr[i])
      }
      // 若不相离,判断第一个ring是否包含第i个ring
      else if (
        GeometryEngine.contains(firstObj.geometry, ringsObjArr[i].geometry)
      ) {
        // 若包含,则第一个ring的inner暂存第i个ring
        firstObj.inner.push(ringsObjArr[i])
      } else {
        // 若不相离也不包含,说明存在相交的情况,将其作为外圈放入新数组
        _newDisjointObjArr.push(ringsObjArr[i])
      }
    }
    // 第一遍结束,得到第一个ring和他互不相交的n个内圈,以及和第一个ring相离或者相交的其他外圈
    // 开始处理第一个ring的所有内圈
    if (firstObj.inner.length > 1) {
      for (let i = 0; i < firstObj.inner.length; i++) {
        // 取第一个
        const _one = firstObj.inner[i]
        // 在这里,第一个ring的内圈,要做到不相交,不包含,但面和面的包含也是一种相交,因此在判断完相交后要继续判断是否包含
        for (let j = i + 1; j < firstObj.inner.length; j++) {
          if (
            GeometryEngine.intersects(_one.geometry, firstObj.inner[j].geometry)
          ) {
            if (
              GeometryEngine.contains(_one.geometry, firstObj.inner[j].geometry)
            ) {
              // 若包含,则需要把这个ring作为一个单独的polygon加入到新数组中,从第一个ring的inner中踢出去
              _newDisjointObjArr.push(firstObj.inner[j])
              firstObj.inner.splice(j, 1)
              // 删除后inner的长度-1,因此j也要-1
              j--
            } else {
              Log.error('输入的几何存在拓扑错误,请检查')
            }
          }
        }
      }
    }
    stateObj.push(firstObj)
    if (_newDisjointObjArr.length > 1) {
      this._processArr(stateObj, _newDisjointObjArr)
    } else if (_newDisjointObjArr.length === 1) {
      stateObj.push(_newDisjointObjArr[0])
    }

    return stateObj
  }

  /**
   * 判断输入数组的维度
   * @param {Array} arr 多维数组
   * @return {Number} 返回输入数组的维度
   * */
  static _multiarr(arr) {
    const list = []
    let num = 1
    for (let i = 0; i < arr.length; i++) {
      if (arr[i] instanceof Array) {
        for (let j = 0; j < arr[i].length; j++) {
          list.push(arr[i][j])
        }
      }
    }
    if (list.length) {
      num = 1
      num += this._multiarr(list)
    }
    return num
  }

  /**
   * fromRings输入参数为字符串时解析字符串
   * @param {String} pointStr 多维数组
   * @return {Number} 返回输入数组的维度
   * */
  static _parseString(pointStr) {
    const _geometry = []
    // *分割每个圈,先把圈剥出来
    const _ringsAll = pointStr.split('*')
    // 空格分隔每个点
    for (let i = 0; i < _ringsAll.length; i++) {
      // _ring得到的是以,分割的每个圈的所有坐标点
      const _ring = _ringsAll[i].split(' ')
      // 在这里还要进行是否是弧段的判断
      // 遍历每个ring中的每个元素,如果有#
      const _pointsArrStr = []
      for (let j = 0; j < _ring.length; j++) {
        _pointsArrStr.push(_ring[j].split(','))
        if (_ring[j].indexOf('#') > -1) {
          _ring[j] = _ring[j].replace('#', '')
          _pointsArrStr.splice(j - 1, 2)
          // 拼接弧段
          const _arc = [
            _ring[j - 1].split(','),
            _ring[j].split(','),
            _ring[j + 1].split(',')
          ]
          _pointsArrStr.push(_arc)
          j++
        }
      }
      // _pointsArrStr 用来存放转成number的每圈的坐标
      const _pointsArr = []
      for (let j = 0; j < _pointsArrStr.length; j++) {
        let _pointsLonely
        if (_pointsArrStr[j][0] instanceof Array) {
          // 有弧段
          const _temp = []
          for (let k = 0; k < _pointsArrStr[j].length; k++) {
            _pointsLonely = _pointsArrStr[j][k].map((item) => {
              return Number(item)
            })
            // 这样可以弧度单独为一个数组
            _temp.push(_pointsLonely)
            // 这样可以弧度转点
            // _pointsArr.push(_pointsLonely)
          }
          _pointsArr.push(_temp)
        } else {
          _pointsLonely = _pointsArrStr[j].map((item) => {
            return Number(item)
          })
          _pointsArr.push(_pointsLonely)
        }
      }
      _geometry.push(_pointsArr)
    }
    return _geometry
  }
}
Object.defineProperties(Geometry.prototype, {
  /**
   * 几何的范围
   * @member {Number} Geometry.prototype.extent
   * */
  extent: {
    configurable: false,
    get() {
      return undefined
    }
  }
})

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