import * as T from '@turf/turf'
import Geometry from './Geometry'
import {defaultValue, Log, isNumber, toJSON} from '../../util'
import Zondy from '../Zondy'
import Point from './Point'
import { GeometryType, IGSGeometryType } from '../enum'
import Projection from '../Projection'
import SpatialReference from './SpatialReference'
import GeometryEngine from './GeometryEngine'
/**
* 几何范围对象,即左下角和右上角组成的矩形范围几何对象,参考示例:<a href='#Extent'>[几何范围对象]</a>
* <br><br>[ES5引入方式]:<br/>
* Zondy.Geometry.Extent() <br/>
* [ES6引入方式]:<br/>
* import { Extent } from "@mapgis/webclient-common" <br/>
* <br/>
* @class Extent
* @moduleEX GeometryModule
* @extends Geometry
* @param {Object} options 构造参数
* @param {Number} [options.xmin = 0] x轴最小坐标
* @param {Number} [options.ymin = 0] y轴最小坐标
* @param {Number} [options.xmax = 0] x轴最大坐标
* @param {Number} [options.ymax = 0] y轴最大坐标
* @param {Number} [options.zmin0] Z轴最小坐标
* @param {Number} [options.zmax0] Z轴最大坐标
* @param {SpatialReference} [options.spatialReference = new Zondy.SpatialReference('EPSG:4326')] 几何点的空间参考系,默认4326,当不是4326时请指定坐标系,方便进行投影转换,参考示例:<a href='#SpatialReference'>[指定坐标系]</a>
*
* @summary <h5>支持如下方法:</h5>
* <a href='#centerAt'>[1、根据中心点生成新的范围]</a><br/>
* <a href='#contains'>[2、输入的几何是否包含在范围内]</a><br/>
* <a href='#intersection'>[3、将原始范围与输入范围求交]</a><br/>
* <a href='#intersects'>[4、输入的几何图形是否与范围相交]</a><br/>
* <a href='#equals'>[5、是否和输入范围相等]</a><br/>
* <a href='#expand'>[6、按给定的因子扩大范围]</a><br/>
* <a href='#offset'>[7、平移extent]</a><br/>
* <a href='#union'>[8、求并]</a><br/>
* <a href='#toString'>[9、返回字符串]</a><br/>
* <a href='#toIGSOldString'>[10、返回IGS1.0的字符串]</a><br/>
* <a href='#getIGSType'>[11、返回IGS所对应的GeometryModule型]</a><br/>
* <a href='#fromJSON'>[12、通过传入的json构造并返回一个新的几何对象]</a><br/>
* <a href='#toJSON'>[13、导出为json对象]</a><br/>
* [14、克隆几何对象]{@link Geometry#clone}
*
* @example <caption><h7 id='Extent'>创建几何对象</h7></caption>
* // ES5引入方式
* const { Extent } = Zondy.Geometry
* // ES6引入方式
* import { Extent } from "@mapgis/webclient-common"
* new Extent({
* xmin: 10,
* xmax: 210,
* ymin: 0,
* ymax: 100
* })
*
* @example <caption><h7 id='spatialReference'>指定坐标系</h7></caption>
* // ES5引入方式
* const { Extent } = Zondy.Geometry
* const { SpatialReference } = Zondy
* // ES6引入方式
* import { Extent, SpatialReference } from "@mapgis/webclient-common"
* new Extent({
* // 3857坐标系的点
* xmin: 12060733.232006868,
* xmax: 12929863.44711455,
* ymin: 3377247.5680546067,
* ymax: 3934286.575385226,
* // 当不是4326时请指定坐标系,方便进行投影转换
* spatialReference: new SpatialReference('EPSG:3857')
* })
*/
class Extent extends Geometry {
constructor(options) {
super(options)
options = defaultValue(options, {})
/**
* x坐标最小值
* @default 0
* @member {Number} Extent.prototype.xmin
*/
this.xmin = defaultValue(options.xmin, 0)
/**
* y坐标最小值
* @default 0
* @member {Number} Extent.prototype.ymin
*/
this.ymin = defaultValue(options.ymin, 0)
/**
* x坐标最大值
* @default 0
* @member {Number} Extent.prototype.xmax
*/
this.xmax = defaultValue(options.xmax, 0)
/**
* y坐标最大值
* @default 0
* @member {Number} Extent.prototype.ymax
*/
this.ymax = defaultValue(options.ymax, 0)
/**
* z坐标最小值
* @member {Number} Extent.prototype.zmin
*/
this.zmin = defaultValue(options.zmin, undefined)
/**
* z坐标最大值
* @member {Number} Extent.prototype.zmax
*/
this.zmax = defaultValue(options.zmax, undefined)
this.type = GeometryType.extent
// 判断hasZ
this.hasZ = !(!isNumber(this.zmin) || !isNumber(this.zmax))
// 判断坐标是否为数字
if (
!isNumber(this.xmin) ||
!isNumber(this.ymin) ||
!isNumber(this.xmax) ||
!isNumber(this.ymax) ||
(typeof this.zmin !== 'undefined' && !isNumber(this.zmin)) ||
(typeof this.zmax !== 'undefined' && !isNumber(this.zmax))
) {
throw new Error('坐标必须为数字')
}
}
/**
* 根据中心点生成新的范围,新范围的宽高为当前范围的宽高
* @param {Point} point 中心点
* @returns {Extent} 以点为中心的新范围。
* @example <caption><h7 id='centerAt'>根据中心点生成新的范围</h7></caption>
* // ES5引入方式
* const { Extent, Point } = Zondy.Geometry
* // ES6引入方式
* import { Extent, Point } from "@mapgis/webclient-common"
* const extent = new Extent({
* xmin: 10,
* xmax: 210,
* ymin: 0,
* ymax: 100
* })
* const point = new Point({
* coordinates: [100.0, 0.0]
* })
* const newConst = extent.centerAt(point)
*/
centerAt(point) {
if (!point) throw new Error('传入参数错误')
// 对传入参数为Number[]或者Point的处理
const coordinates = Point.toCoordinates(point)
const { width } = this
const { height } = this
this.xmin = coordinates[0] - width / 2
this.xmax = coordinates[0] + width / 2
this.ymin = coordinates[1] - height / 2
this.ymax = coordinates[1] + height / 2
if (this.hasZ && point.hasZ) {
const zlength = this.zmax - this.zmin
this.zmin = coordinates[2] - zlength / 2
this.zmax = coordinates[2] + zlength / 2
}
return this
}
/**
* 输入的几何是否包含在范围内
* @param {Point|Extent} geometry 输入的几何图形
* @returns {Boolean} 如果输入几何图形包含在范围内,则返回true
* @example <caption><h7 id='contains'>输入的几何是否包含在范围内</h7></caption>
* // ES5引入方式
* const { Extent, Point } = Zondy.Geometry
* // ES6引入方式
* import { Extent, Point } from "@mapgis/webclient-common"
* const extent = new Extent({
* xmin: 10,
* xmax: 210,
* ymin: 0,
* ymax: 100
* })
* const point = new Point({
* coordinates: [100.0, 0.0]
* })
* const isIn = extent.contains(point)
*/
contains(geometry) {
// 使用turf判断面与面,面与点的包含关系
if (!(geometry instanceof Point) && !(geometry instanceof Extent)) {
return false
}
// 因为turf的精度问题,将几何转为4326进行比较
let _geometry = geometry
if (!_geometry.isGeographic) {
_geometry = Projection.project(
geometry,
new SpatialReference({ wkid: 4326 })
)
}
let _extent = this
if (!_extent.isGeographic) {
_extent = Projection.project(this, new SpatialReference({ wkid: 4326 }))
}
const extentPolygonOri = T.polygon([
[
[_extent.xmin, _extent.ymin],
[_extent.xmin, _extent.ymax],
[_extent.xmax, _extent.ymax],
[_extent.xmax, _extent.ymin],
[_extent.xmin, _extent.ymin]
]
])
let currentGeometry
if (_geometry instanceof Point) {
currentGeometry = T.point(_geometry._cloneCoordinates())
} else {
currentGeometry = T.polygon([
[
[_geometry.xmin, _geometry.ymin],
[_geometry.xmin, _geometry.ymax],
[_geometry.xmax, _geometry.ymax],
[_geometry.xmax, _geometry.ymin],
[_geometry.xmin, _geometry.ymin]
]
])
}
return T.booleanContains(extentPolygonOri, currentGeometry)
}
/**
* 将原始范围与输入范围求交
* @param {Extent} extent 输入的范围,用于相交。
* @returns {Extent} 返回输入Extent与原始Extent的交集,没有交集返回 null
* @example <caption><h7 id='intersection'>将原始范围与输入范围求交</h7></caption>
* // ES5引入方式
* const { Extent } = Zondy.Geometry
* // ES6引入方式
* import { Extent } from "@mapgis/webclient-common"
* const extent = new Extent({
* xmin: 10,
* xmax: 210,
* ymin: 0,
* ymax: 100
* })
* const extent2 = new Extent({
* xmin: 10,
* xmax: 210,
* ymin: 0,
* ymax: 100
* })
* const newExtent = extent.intersection(extent2)
*/
intersection(extent) {
if (!extent) Log.error('传入参数错误!')
if (!(extent instanceof Extent)) Log.error('extent不是Extent几何!')
if (!this.spatialReference.equals(extent.spatialReference)) {
Log.error('不是同一个坐标系!')
}
// 因为turf的精度问题,将几何转为4326进行比较
let _geometry = extent
if (!_geometry.isGeographic) {
_geometry = Projection.project(
extent,
new SpatialReference({ wkid: 4326 })
)
}
let _extent = this
if (!_extent.isGeographic) {
_extent = Projection.project(this, new SpatialReference({ wkid: 4326 }))
}
const extentPolygonOri = T.polygon([
[
[_extent.xmin, _extent.ymin],
[_extent.xmin, _extent.ymax],
[_extent.xmax, _extent.ymax],
[_extent.xmax, _extent.ymin],
[_extent.xmin, _extent.ymin]
]
])
const extentPolygonAdd = T.polygon([
[
[_geometry.xmin, _geometry.ymin],
[_geometry.xmin, _geometry.ymax],
[_geometry.xmax, _geometry.ymax],
[_geometry.xmax, _geometry.ymin],
[_geometry.xmin, _geometry.ymin]
]
])
let result = null
const geoJSON = T.intersect(extentPolygonOri, extentPolygonAdd)
if (geoJSON && geoJSON.geometry) {
let bbox
if (geoJSON.geometry.type === 'Polygon') {
bbox = T.bbox(T.polygon(geoJSON.geometry.coordinates))
} else if (geoJSON.geometry.type === 'MultiPolygon') {
bbox = T.bbox(T.multiPolygon(geoJSON.geometry.coordinates))
}
if (bbox) {
result = new Extent({
xmin: bbox[0],
ymin: bbox[1],
xmax: bbox[2],
ymax: bbox[3]
})
if (!this.spatialReference.isGeographic) {
result = Projection.project(
result,
SpatialReference.fromJSON(this.spatialReference)
)
}
}
}
return result
}
/**
* 输入的几何图形是否与范围相交
* @param {Geometry} geometry 用于测试相交的几何图形。可以是 Point, Polyline, Polygon, Extent or Multipoint
* @returns {Boolean} 如果输入几何图形与区段相交,则返回true
* @example <caption><h7 id='intersects'>输入的几何图形是否与范围相交</h7></caption>
* // ES5引入方式
* const { Extent, Point } = Zondy.Geometry
* // ES6引入方式
* import { Extent, Point } from "@mapgis/webclient-common"
* const extent = new Extent({
* xmin: 10,
* xmax: 210,
* ymin: 0,
* ymax: 100
* })
* const point = new Point({
* coordinates: [100.0, 0.0]
* })
* const isIntersect = extent.intersects(point)
*/
intersects(geometry) {
return GeometryEngine.intersects(this, geometry)
}
/**
* 是否和输入范围相等
* @param {Extent} extent 输入的外包范围
* @returns {Boolean} 如果输入范围等于调用equals()的范围,则返回true
* @example <caption><h7 id='equals'>是否和输入范围相等</h7></caption>
* // ES5引入方式
* const { Extent } = Zondy.Geometry
* // ES6引入方式
* import { Extent } from "@mapgis/webclient-common"
* const extent = new Extent({
* xmin: 10,
* xmax: 210,
* ymin: 0,
* ymax: 100
* })
* const extent2 = new Extent({
* xmin: 10,
* xmax: 210,
* ymin: 0,
* ymax: 100
* })
* const isEqual = extent.equals(extent2)
*/
equals(extent) {
if (!extent) throw new Error('传入参数错误')
const condition1 = !!Math.abs(this.xmax - extent.xmax) <= 10e-8
const condition2 = !!Math.abs(this.xmin - extent.xmin) <= 10e-8
const condition3 = !!Math.abs(this.ymax - extent.ymax) <= 10e-8
const condition4 = !!Math.abs(this.ymin - extent.ymin) <= 10e-8
if (!this.hasZ) {
return condition1 && condition2 && condition3 && condition4
}
const condition5 = !!Math.abs(this.zmax - extent.zmax) <= 10e-8
const condition6 = !!Math.abs(this.zmin - extent.zmin) <= 10e-8
return (
condition1 &&
condition2 &&
condition3 &&
condition4 &&
condition5 &&
condition6
)
}
/**
* 按给定的因子扩大范围。例如,值为1.5将将范围扩展到比原始范围大50%。
* @param {Number} factor 乘数的值,必须大于等于1
* @returns {Extent} 返回扩大的范围
* @example <caption><h7 id='expand'>按给定的因子扩大范围</h7></caption>
* // ES5引入方式
* const { Extent } = Zondy.Geometry
* // ES6引入方式
* import { Extent } from "@mapgis/webclient-common"
* const extent = new Extent({
* xmin: 10,
* xmax: 210,
* ymin: 0,
* ymax: 100
* })
* extent.expand(1.5)
*/
expand(factor) {
if (typeof factor === 'number') {
if (factor >= 1) {
factor = Math.abs(factor)
const width = this.width * factor
const height = this.height * factor
const widthSub = width - this.width
const heightSub = height - this.height
this.xmin -= widthSub / 2
this.xmax += widthSub / 2
this.ymin -= heightSub / 2
this.ymax += heightSub / 2
}
return this
} else {
throw new Error('按照给定的因子扩大范围,需要的是一个num值')
}
}
/**
* 根据输入的dx, dy, dz值,平移extent
* @param {Number} dx 要平移的x值
* @param {Number} dy 要平移的y值
* @param {Number} dz 要平移的z值
* @returns {Extent} 偏移后的范围
* @example <caption><h7 id='offset'>平移extent</h7></caption>
* // ES5引入方式
* const { Extent } = Zondy.Geometry
* // ES6引入方式
* import { Extent } from "@mapgis/webclient-common"
* const extent = new Extent({
* xmin: 10,
* xmax: 210,
* ymin: 0,
* ymax: 100
* })
* extent.offset(0, 1, 0)
*/
offset(dx, dy, dz) {
if (!isNumber(dx) || !isNumber(dy) || (this.hasZ && !isNumber(dz))) {
throw new Error('传入参数错误')
}
this.xmax += dx
this.xmin += dx
this.ymax += dy
this.ymin += dy
if (this.hasZ) {
this.zmax += dz
this.zmin += dz
}
return this
}
/**
* 求并
* @param {Extent} extent 输入的范围,用于并集
* @returns {Extent} 返回输入Extent与原始Extent的并集
* @example <caption><h7 id='union'>求并</h7></caption>
* // ES5引入方式
* const { Extent } = Zondy.Geometry
* // ES6引入方式
* import { Extent } from "@mapgis/webclient-common"
* const extent = new Extent({
* xmin: 10,
* xmax: 210,
* ymin: 0,
* ymax: 100
* })
* const extent2 = new Extent({
* xmin: 10,
* xmax: 210,
* ymin: 0,
* ymax: 100
* })
* const newExtent = extent.union(extent2)
*/
union(extent) {
if (!extent) throw new Error('传入参数错误')
if (!this.hasZ) {
return new Extent({
xmin: extent.xmin < this.xmin ? extent.xmin : this.xmin,
xmax: extent.xmax > this.xmax ? extent.xmax : this.xmax,
ymin: extent.ymin < this.ymin ? extent.ymin : this.ymin,
ymax: extent.ymax > this.ymax ? extent.ymax : this.ymax
})
}
return new Extent({
xmin: extent.xmin < this.xmin ? extent.xmin : this.xmin,
xmax: extent.xmax > this.xmax ? extent.xmax : this.xmax,
ymin: extent.ymin < this.ymin ? extent.ymin : this.ymin,
ymax: extent.ymax > this.ymax ? extent.ymax : this.ymax,
zmin: extent.zmin < this.zmin ? extent.zmin : this.zmin,
zmax: extent.zmax > this.zmax ? extent.zmax : this.zmax
})
}
/**
* 返回如下格式的字符串:"xmin,ymin,xmax,ymax"<a id='toString'></a>
* @returns string
*/
toString() {
if (this.hasZ) {
return `${this.xmin},${this.ymin},${this.zmin},${this.xmax},${this.ymax},${this.zmax}`
}
return `${this.xmin},${this.ymin},${this.xmax},${this.ymax}`
}
/**
* 通过传入的json构造并返回一个新的几何对象
* @param {Object} [json] JSON对象
* @example <caption><h7 id='fromJSON'>通过传入的json构造并返回一个新的几何对象</h7></caption>
* // ES5引入方式
* const { Extent } = Zondy.Geometry
* // ES6引入方式
* import { Extent } from "@mapgis/webclient-common"
* const json = {
* xmin: 10,
* xmax: 210,
* ymin: 0,
* ymax: 100
* }
* const extent = Extent.fromJSON(json)
*/
static fromJSON(json) {
json = defaultValue(json, {})
return new Extent(json)
}
/**
* <a id='toJSON'></a>
* 导出为json对象
* @return {Object} json对象
*/
toJSON() {
const json = super.toJSON()
json.xmin = this.xmin
json.ymin = this.ymin
json.xmax = this.xmax
json.ymax = this.ymax
json.zmin = this.zmin
json.zmax = this.zmax
json.width = this.width
json.height = this.height
return json
}
/**
* 返回如下格式的字符串:"xmin$ymin$xmax$ymax"<a id='toIGSOldString'></a>
* @returns string
*/
toIGSOldString() {
return `${this.xmin}$${this.ymin}$${this.xmax}$${this.ymax}`
}
/**
* 返回IGS所对应的GeometryModule型<a id='getIGSType'></a>
* @returns string GeometryModule型
*/
getIGSType() {
if (this.hasZ) {
return IGSGeometryType.extent3D
}
return IGSGeometryType.extent
}
/**
* 归一化计算
* @returns {Extent} 归一化后的点对象
* @example <caption><h7 id='normalize'>归一化计算</h7></caption>
* // ES5引入方式
* const { Extent } = Zondy.Geometry
* // ES6引入方式
* import { Extent } from "@mapgis/webclient-common"
* const extent = new Extent({
* xmin: 10,
* xmax: 210,
* ymin: 0,
* ymax: 100
* })
* const normalize = extent.normalize()
*/
normalize() {
let pointMin = new Point({
coordinates: [this.xmin, this.ymin]
})
let pointMax = new Point({
coordinates: [this.xmax, this.ymax]
})
pointMin = pointMin.normalize()
pointMax = pointMax.normalize()
const extent = new Extent({
xmin: pointMin.coordinates[0],
ymin: pointMin.coordinates[1],
xmax: pointMax.coordinates[0],
ymax: pointMax.coordinates[1]
})
return extent
}
/**
* 克隆几何对象
* @return {Geometry} 克隆后的几何对象
*/
clone() {
return new Extent(this.toJSON())
}
}
Object.defineProperties(Extent.prototype, {
/**
* 宽度
* @member {Number} Extent.prototype.width
* */
width: {
configurable: false,
get() {
return this.xmax - this.xmin
}
},
/**
* 高度
* @member {Number} Extent.prototype.height
* */
height: {
configurable: false,
get() {
return this.ymax - this.ymin
}
},
/**
* 中心点
* @member {Number[]} Extent.prototype.center
* */
center: {
configurable: false,
set(center) {
this.centerAt(center)
},
get() {
if (!this.hasZ) {
return new Point({
coordinates: [
(this.xmax - this.xmin) / 2 + this.xmin,
(this.ymax - this.ymin) / 2 + this.ymin
]
})
}
return new Point({
coordinates: [
(this.xmax - this.xmin) / 2 + this.xmin,
(this.ymax - this.ymin) / 2 + this.ymin,
(this.zmax - this.zmin) / 2 + this.zmin
]
})
}
}
})
Zondy.Geometry.Extent = Extent
export default Extent