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