类名 common/base/geometry/MultiPolygon.js
import * as T from '@turf/turf'
import Geometry from './Geometry'
import { defaultValue, isNumber } from '../../util'
import Zondy from '../Zondy'
import { GeometryType, IGSGeometryType } from '../enum'
import Polygon from './Polygon'
import Point from './Point'
import SpatialReference from './SpatialReference'
import Log from '../../util/Log'
import { calcExtent } from './Utiles'

/**
 * 多区几何
 * <br><br>[ES5引入方式]:<br/>
 * Zondy.Geometry.MultiPolygon() <br/>
 * [ES6引入方式]:<br/>
 * import { MultiPolygon } from "@mapgis/webclient-common" <br/>
 * <br/>
 * @class MultiPolygon
 * @moduleEX GeometryModule
 * @extends Geometry
 * @param {Object} options 构造参数
 * @param {Array} [options.coordinates = []] 几何点的坐标数组,参考示例:<a href='#MultiPolygon'>[多区几何对象]</a>
 * @param {SpatialReference} [options.spatialReference = new Zondy.SpatialReference('EPSG:4326')] 几何点的空间参考系,默认4326,当不是4326时请指定坐标系,方便进行投影转换,参考示例:<a href='#SpatialReference'>[指定坐标系]</a>
 * @summary <h5>支持如下方法:</h5>
 * <a href='#addPolygon'>[1、在多区末尾添加区]</a><br/>
 * <a href='#removePolygon'>[2、根据区几何的下标删除的区对象]</a><br/>
 * <a href='#toXMl'>[3、导出为OGC服务要求的xml字符串]</a><br/>
 * <a href='#contains'>[4、检查输入点是否在多边形内]</a><br/>
 * <a href='#toString'>[5、返回字符串]</a><br/>
 * <a href='#toOldIGSGeometry'>[6、返回igs1.0的几何对象]</a><br/>
 * <a href='#getIGSType'>[7、返回IGS所对应的GeometryModule型]</a><br/>
 * <a href='#fromJSON'>[8、通过传入的json构造并返回一个新的几何对象]</a><br/>
 * <a href='#toJSON'>[9、导出为json对象]</a><br/>
 * [10、克隆几何对象]{@link Geometry#clone}
 *
 * @example <caption><h7 id='MultiPolygon'>创建几何对象</h7></caption>
 * // ES5引入方式
 * const { MultiPolygon } = Zondy.Geometry
 * // ES6引入方式
 * import { MultiPolygon } from "@mapgis/webclient-common"
 * new MultiPolygon({
 *   coordinates:  [
 *     // 第一个区
 *     [
 *       // 外圈
 *       [
 *         // 区要首尾相连
 *         [102.0, 2.0],
 *         [103.0, 2.0],
 *         [103.0, 3.0],
 *         [102.0, 3.0],
 *         [102.0, 2.0]
 *       ],
 *       // 第一个内圈
 *       [],
 *       // 第二个内圈
 *       [],
 *       ...
 *     ],
 *     // 第二个区
 *     [
 *       // 外圈
 *       [
 *         [100.0, 0.0],
 *         [101.0, 0.0],
 *         [101.0, 1.0],
 *         [100.0, 1.0],
 *         [100.0, 0.0]
 *       ],
 *       // 第一个内圈
 *       [
 *         [100.2, 0.2],
 *         [100.2, 0.8],
 *         [100.8, 0.8],
 *         [100.8, 0.2],
 *         [100.2, 0.2]
 *       ]
 *     ]
 *   ]
 * })
 *
 * @example <caption><h7 id='spatialReference'>指定坐标系</h7></caption>
 * // ES5引入方式
 * const { MultiPolygon } = Zondy.Geometry
 * const { SpatialReference } = Zondy.SpatialReference
 * // ES6引入方式
 * import { MultiPolygon, SpatialReference } from "@mapgis/webclient-common"
 * new MultiPolygon({
 *   coordinates:  [
 *     // 第一个区
 *     [
 *       // 外圈
 *       [
 *         // 区要首尾相连
 *         [12060733.232006868, 3377247.5680546067],
 *         [12929863.44711455, 3377247.5680546067],
 *         [12929863.44711455, 3934286.575385226],
 *         [12060733.232006868, 3934286.575385226],
 *         [12060733.232006868, 3377247.5680546067]
 *       ]
 *     ],
 *     // 第二个区
 *     [
 *       // 外圈
 *       [
 *         [12060733.232006868, 3377247.5680546067],
 *         [12929863.44711455, 3377247.5680546067],
 *         [12929863.44711455, 3934286.575385226],
 *         [12060733.232006868, 3934286.575385226],
 *         [12060733.232006868, 3377247.5680546067]
 *       ]
 *     ]
 *   ],
 *   // 当不是4326时请指定坐标系,方便进行投影转换
 *   spatialReference: new SpatialReference('EPSG:3857')
 * })
 */
class MultiPolygon extends Geometry {
  constructor(options) {
    super(options)
    options = defaultValue(options, {})
    /**
     * 几何点的坐标
     * @member {Array} MultiPolygon.prototype.coordinates
     */
    this.coordinates = defaultValue(options.coordinates, [])
    this.isSelfIntersecting = defaultValue(options.isSelfIntersecting, false)
    this.type = GeometryType.multiPolygon

    if (!Array.isArray(this.coordinates)) {
      throw new Error('坐标必须为数组')
    }

    for (const polygon of this.coordinates) {
      for (const ring of polygon) {
        if (ring.length < 4) {
          throw new Error('每个环至少有四个点')
        }

        if (ring[ring.length - 1].length !== ring[0].length) {
          throw new Error('首尾点维度不相等')
        }

        for (let j = 0; j < ring[ring.length - 1].length; j++) {
          // Check if first point of Polygon contains two numbers
          if (ring[ring.length - 1][j] !== ring[0][j]) {
            throw new Error('第一个位置和最后一个位置不相等')
          }
        }
      }
    }

    if (this.coordinates[0][0][0].length === 3) {
      this.hasZ = true
    }
  }

  /**
   * 在多区末尾添加区
   * @param {Polygon} polygon 要添加区几何对象
   * @param {String} [mode = 'add'] 添加模式,add:正常添加,deduplication:去除重复点,即当新加的区几何和现有的一个或多个几何相交时,会合并几何对象
   * @return {MultiPolygon} 表示给定范围的多边形实例
   * @example <caption><h7 id='addPolygon'>在多区末尾添加区</h7></caption>
   * // ES5引入方式
   * const { MultiPolygon, Polygon } = Zondy.Geometry
   * // ES6引入方式
   * import { MultiPolygon, Polygon } from "@mapgis/webclient-common"
   * const multiPolygon = new MultiPolygon({
   *   coordinates:  [
   *     // 第一个区
   *     [
   *       // 外圈
   *       [
   *         // 区要首尾相连
   *         [102.0, 2.0],
   *         [103.0, 2.0],
   *         [103.0, 3.0],
   *         [102.0, 3.0],
   *         [102.0, 2.0]
   *       ]
   *     ]
   *   ]
   * })
   * const polygon = new Polygon({
   *   coordinates:[
   *     // 外圈
   *     [
   *       [100.0, 0.0],
   *       [101.0, 0.0],
   *       [101.0, 1.0],
   *       [100.0, 1.0],
   *       [100.0, 0.0]
   *     ]
   *   ]
   * })
   * multiPolygon.addPolygon(polygon)
   */
  addPolygon(polygon, mode) {
    mode = defaultValue(mode, 'add')

    if (!(polygon instanceof Polygon)) {
      Log.error('polygon类型不是Polygon对象类型!')
    }

    // 添加模式
    if (mode === 'add') {
      this.coordinates.push(polygon._cloneCoordinates())
    }
    // 去重添加模式
    else {
      // 获取所有与polygon相交的几何
      const intersectPolygons = []
      for (let i = 0; i < this.coordinates.length; i++) {
        const _polygon = T.polygon(this.coordinates[i])
        const _polygon2 = T.polygon(polygon.coordinates)
        if (!T.booleanDisjoint(_polygon, _polygon2)) {
          intersectPolygons.push(_polygon)
          this.coordinates[i] = undefined
        }
      }
      // 删除以相交的几何
      for (let i = 0; i < this.coordinates.length; i++) {
        if (!this.coordinates[i]) {
          this.coordinates.splice(i, 1)
        }
      }
      let unionGeometry = T.polygon(polygon.coordinates)
      // 几何求并
      for (let i = 0; i < intersectPolygons.length; i++) {
        unionGeometry = T.union(unionGeometry, intersectPolygons[i]).geometry
      }
      // 将相交几何放到末尾
      this.coordinates.push(unionGeometry.coordinates)
    }
    this.coordinates = JSON.parse(JSON.stringify(this.coordinates))
    return this
  }

  /**
   * 根据区几何的下标删除的区对象
   * @param {Number} polygonIndex
   * @return {Polygon | null} 返回删除的区对象
   * @example <caption><h7 id='removePolygon'>根据区几何的下标删除的区对象</h7></caption>
   * // ES5引入方式
   * const { MultiPolygon } = Zondy.Geometry
   * // ES6引入方式
   * import { MultiPolygon } from "@mapgis/webclient-common"
   * const multiPolygon = new MultiPolygon({
   *   coordinates:  [
   *     // 第一个区
   *     [
   *       // 外圈
   *       [
   *         // 区要首尾相连
   *         [102.0, 2.0],
   *         [103.0, 2.0],
   *         [103.0, 3.0],
   *         [102.0, 3.0],
   *         [102.0, 2.0]
   *       ]
   *     ]
   *   ]
   * })
   * multiPolygon.removePolygon(0)
   */
  removePolygon(polygonIndex) {
    if (!this._isValidate(polygonIndex)) return null
    const coords = JSON.parse(
      JSON.stringify(this._cloneCoordinates()[polygonIndex])
    )
    this.coordinates.splice(polygonIndex, 1)
    this.coordinates = JSON.parse(JSON.stringify(this.coordinates))
    return new Polygon({
      coordinates: coords
    })
  }

  /**
   * 导出为OGC服务要求的xml字符串<a id='toXMl'></a>
   * @return {String} 字符串
   */
  toXMl() {
    return ''
  }

  /**
   * 检查输入点是否在多边形内。多边形线上的一个点被认为是内部
   * @param {Point} point 用于测试是否包含在测试多边形中的点
   * @return {Boolean} 如果该点位于多边形内,则返回true
   * @example <caption><h7 id='contains'>检查输入点是否在多边形内</h7></caption>
   * // ES5引入方式
   * const { MultiPolygon, Point } = Zondy.Geometry
   * // ES6引入方式
   * import { MultiPolygon, Point } from "@mapgis/webclient-common"
   * const multiPolygon = new MultiPolygon({
   *   coordinates:  [
   *     // 第一个区
   *     [
   *       // 外圈
   *       [
   *         // 区要首尾相连
   *         [102.0, 2.0],
   *         [103.0, 2.0],
   *         [103.0, 3.0],
   *         [102.0, 3.0],
   *         [102.0, 2.0]
   *       ]
   *     ]
   *   ]
   * })
   * const point = new Point({
   *   coordinates: [100.0, 0.0]
   * })
   * const isIn = multiPolygon.contains(point)
   */
  contains(point) {
    if (!point) throw new Error('传入参数错误')
    const mutiPolygon = T.multiPolygon(this._cloneCoordinates())
    const p = T.point(Point.toCoordinates(point))
    return T.booleanPointInPolygon(p, mutiPolygon)
  }

  /**
   * 通过传入的json构造并返回一个新的几何对象
   * @param {Object} [json] JSON对象
   * @example <caption><h7 id='fromJSON'>通过传入的json构造并返回一个新的几何对象</h7></caption>
   * // ES5引入方式
   * const { MultiPolygon } = Zondy.Geometry
   * // ES6引入方式
   * import { MultiPolygon } from "@mapgis/webclient-common"
   * const json = {
   *   coordinates:  [
   *     // 第一个区
   *     [
   *       // 外圈
   *       [
   *         // 区要首尾相连
   *         [102.0, 2.0],
   *         [103.0, 2.0],
   *         [103.0, 3.0],
   *         [102.0, 3.0],
   *         [102.0, 2.0]
   *       ]
   *     ]
   *   ]
   * }
   * const multiPolygon = new MultiPolygon.fromJSON(json)
   */
  static fromJSON(json) {
    json = defaultValue(json, {})
    return new MultiPolygon(json)
  }

  /**
   * <a id='toJSON'></a>
   * 导出为json对象
   * @return {Object} json对象
   */
  toJSON() {
    const json = super.toJSON()
    json.coordinates = JSON.parse(JSON.stringify(this.coordinates))
    json.extent = this.extent.toJSON()
    return json
  }

  /**
   * 返回如下格式的字符串:"x0,y0,x1,y1,x2,y2,x0,y0|x3,y3,x4,y4,x5,y5,x3,y3;x6,y6,x7,y7,x8,y8,x6,y6|x9,y9,x10,y10,x11,y11,x9,y9"<a id='toString'></a>
   * 多边形内每个的多边形由|号分割,多个多边形由;号分割
   * @returns string
   */
  toString() {
    let _str = ''
    // 开始循环多边形
    for (let i = 0; i < this.coordinates.length; i++) {
      // 取得内部多边行中的一个
      const _singlePolygon = this.coordinates[i]
      // 开始循环其内部的圈
      for (let j = 0; j < _singlePolygon.length; j++) {
        // 获取多边形内圈,第一个为多边形本身
        const _innerPolygon = _singlePolygon[j]
        // 开始循环获取内部点
        for (let k = 0; k < _innerPolygon.length; k++) {
          // 多边形里的点
          const _point = _innerPolygon[k]
          // 有可能是xyz,因此for循环
          for (let m = 0; m < _point.length; m++) {
            // 加,号
            _str += `${_point[m]},`
          }
        }
        // 删除最后一个,号
        _str = _str.substring(0, _str.length - 1)
        // 加上|号
        _str += '|'
      }
      // 删除最后一个|号
      _str = _str.substring(0, _str.length - 1)
      // 加上;号
      _str += ';'
    }
    // 删除最后一个;号
    _str = _str.substring(0, _str.length - 1)
    return _str
  }

  /**
   * 返回igs1.0的几何对象<a id='toOldIGSGeometry'></a>
   * @returns Object igs1.0的几何对象
   */
  toOldIGSGeometry() {
    const RegGeom = []
    let reg = {}
    let ring = {}
    for (let i = 0; i < this.coordinates.length; i++) {
      const polygon = this.coordinates[i]
      reg = {
        Rings: [],
        GID: i
      }
      for (let j = 0; j < polygon.length; j++) {
        ring = {
          Arcs: []
        }
        const Dots = []
        for (let k = 0; k < polygon[j].length; k++) {
          const ringCos = polygon[j][k]
          Dots.push({
            x: ringCos[0],
            y: ringCos[1]
          })
        }
        ring.Arcs.push({
          Dots,
          ArcID: j
        })
        reg.Rings.push(ring)
      }
      RegGeom.push(reg)
    }
    return {
      RegGeom
    }
  }

  /**
   * @private
   * @description 拷贝几何信息
   * @returns {Array}
   */
  _cloneCoordinates() {
    return JSON.parse(JSON.stringify(this.coordinates))
  }

  /**
   * @private
   * @description 判断输入是否合法
   * @param {Number} polygonIndex 路径下标
   * @returns {Boolean} 输入是否合法
   */
  _isValidate(polygonIndex) {
    const len = this.coordinates.length
    if (!isNumber(polygonIndex) || polygonIndex < 0 || polygonIndex >= len) {
      return false
    }
    return true
  }

  /**
   * @private
   * @description 计算外包盒
   * @param {Array} coordinates 坐标点
   * @param {Boolean} hasZ 是否是三维
   * @returns {Extent}
   */
  _calcExtent(coordinates, hasZ) {
    return calcExtent(coordinates, hasZ, GeometryType.multiPolygon)
  }

  /**
   * 返回IGS所对应的GeometryModule型<a id='getIGSType'></a>
   * @returns string GeometryModule型
   */
  getIGSType() {
    return IGSGeometryType.multiPolygon
  }

  /**
   * 多区对象转区对象数组
   * @return {Array<Polygon>} 区对象数组
   * */
  toPolygonArray() {
    const polygonArray = []
    for (let i = 0; i < this.coordinates.length; i++) {
      polygonArray.push(
        new Polygon({
          coordinates: this.coordinates[i],
          spatialReference: SpatialReference.fromJSON(this.spatialReference)
        })
      )
    }
    return polygonArray
  }

  /**
   * 克隆几何对象
   * @return {Geometry} 克隆后的几何对象
   */
  clone() {
    return new MultiPolygon(this.toJSON())
  }
}
Object.defineProperties(MultiPolygon.prototype, {
  /**
   * 外包盒
   * @member {Number} MultiPolygon.prototype.extent
   * */
  extent: {
    configurable: false,
    get() {
      return this._calcExtent(this.coordinates, this.hasZ)
    }
  }
})

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