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