import proj4 from 'proj4'
import epsg from './EPSG'
import { Log, defaultValue } from '../util'
import Zondy from './Zondy'
import {
Circle,
Extent,
LineString,
MultiLineString,
MultiPoint,
MultiPolygon,
Point,
Polygon,
SpatialReference
} from './geometry'
import GeometryType from './enum/GeometryType'
/**
* 投影转换工具
* @class Projection
* @moduleEX GeometryModule
* */
class Projection {}
/**
* 将墨卡托的点转为经纬度
* @private
* @param {Object} mercator 墨卡托点坐标,{x: 0, y: 0}
* @return {Object} 经纬度坐标, { longitude: 0, latitude: 0 }
* */
Projection.projectMercatorToLonLat = function (mercator) {
const lonlat = { longitude: 0, latitude: 0 }
const x = (mercator.x / 20037508.34) * 180
let y = (mercator.y / 20037508.34) * 180
y =
(180 / Math.PI) *
(2 * Math.atan(Math.exp((y * Math.PI) / 180)) - Math.PI / 2)
lonlat.longitude = x
lonlat.latitude = y
return lonlat
}
/**
* 将任意坐标系的点坐标转为经纬度(EPSG:4326)坐标
* @private
* @param {Object} options 投影参数
* @param {Array} [options.points = []] 要投影的点数组。[[x, y], [x, y],...]
* @param {String | Number} [options.wkid] wkid号
* @param {String} [options.proj4Defs] 可选参数,EPSG的椭球体参数,本工具内置了部分EPSG号,如果有EPSG号未收录,请在EPSG官网:https://epsg.io,查找并通过该参数传入
* @return {Array} 投影后的点坐标数组
* @example <caption><h7>将4547坐标系的点转为4326坐标系的点</h7></caption>
* const points = Projection.projectToGeographyPoints({
* points: [[-45257.10778559791, 3212885.1836444484]],
* wkid: 4547
* })
* @example <caption><h7>将未收录的坐标系的点转为4326坐标系的点</h7></caption>
* const points = Projection.projectToGeographyPoints({
* points: [[-45257.10778559791, 3212885.1836444484]],
* // 定义了椭球体参数,请在WPSG官网查询
* proj4Defs: '+proj=tmerc +lat_0=0 +lon_0=114 +k=1 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs'
* })
* */
Projection.projectToGeographyPoints = function (options) {
options = defaultValue(options, {})
// 要投影的点数组
const points = defaultValue(options.points, [])
// 目的坐标系的wkid
const wkid = defaultValue(options.wkid, -1)
// 目的坐标系的椭球体参数
const proj4Defs = defaultValue(options.proj4Defs, undefined)
// 投影后的点数组
const projPoints = []
let defs = undefined
if (proj4Defs) {
defs = proj4Defs
} else {
const epsgObj = Projection.getEPSGByWKID(wkid)
if (epsgObj) {
defs = epsgObj.strProject
}
}
if (defs) {
for (let i = 0; i < points.length; i++) {
projPoints.push(
proj4(defs, '+proj=longlat +datum=WGS84 +no_defs +type=crs', points[i])
)
}
}
return projPoints
}
/**
* 将点数组从一个坐标系投影到另一个坐标系
* @private
* @param {Object} options 投影参数
* @param {String | Number} [options.originWKID] 点数组所在的原始坐标系的wkid
* @param {String | Number} [options.targetWKID] 目的坐标系的wkid
* @param {Array} [options.points = []] 要投影的点数组
* @param {String} [options.originDefs] 可选参数,若一个坐标系未收录,则请在EPSG官网:https://epsg.io查询原始坐标系的Defs
* @param {String} [options.targetDefs] 可选参数,若一个坐标系未收录,则请在EPSG官网:https://epsg.io查询目的坐标系的Defs
* @example <caption><h7>将3857坐标系的点转为4326坐标系的点</h7></caption>
* const points = Projection.projectPoints({
* originWKID: 3857,
* targetWKID: 4326,
* points: [[12062959.621822732, 3379793.1381245172]]
* })
* @example <caption><h7>将未收录的坐标系的点转为4326坐标系的点</h7></caption>
* const points = Projection.projectPoints({
* // 定义了椭球体参数,请在WPSG官网查询
* originDefs: '+proj=tmerc +lat_0=0 +lon_0=114 +k=1 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs',
* targetWKID: 4326,
* points: [[-45257.10778559791, 3212885.1836444484]]
* })
* */
Projection.projectPoints = function (options) {
options = defaultValue(options, {})
// 要投影的点数组
const points = defaultValue(options.points, [])
// 原始坐标系的wkid
const originWKID = defaultValue(options.originWKID, -1)
// 原始坐标系的椭球体参数
const originDefs = defaultValue(options.originDefs, undefined)
// 目的坐标系的wkid
const targetWKID = defaultValue(options.targetWKID, -1)
// 目的坐标系的椭球体参数
const targetDefs = defaultValue(options.targetDefs, undefined)
// 要获取的原始坐标系椭球体参数
let _originDefs = undefined
// 要获取的目的坐标系椭球体参数
let _targetDefs = undefined
// 投影后的点数组
const projPoints = []
if (originDefs) {
_originDefs = originDefs
} else {
const epsgObj = Projection.getEPSGByWKID(originWKID)
if (epsgObj) {
_originDefs = epsgObj.strProject
}
}
if (targetDefs) {
_targetDefs = targetDefs
} else {
const epsgObj = Projection.getEPSGByWKID(targetWKID)
if (epsgObj) {
_targetDefs = epsgObj.strProject
}
}
if (_originDefs && _targetDefs) {
for (let i = 0; i < points.length; i++) {
projPoints.push(proj4(_originDefs, _targetDefs, points[i]))
}
}
return projPoints
}
/**
* 根据wkid获取epsg对象
* @private
* @param {Number | String} wkid wkid号
* @return {Object} EPSG参数对象
* */
Projection.getEPSGByWKID = function (wkid) {
let _epsg = undefined
for (let i = 0; i < epsg.length; i++) {
if (String(epsg[i].id) === String(wkid)) {
_epsg = epsg[i]
break
}
}
if (!_epsg) {
Log.error(`epsg未定义wkid为${wkid}的坐标系信息`)
}
return _epsg
}
/**
* 将几何对象投影到指定坐标系中
* @private
* @param {Geometry | Array<geometry>} geometry 要投影的几何或几何数组
* @param {SpatialReference} outSpatialReference 目标参考系
* @return {Geometry | Array<Geometry>} 投影后的几何对象
* */
Projection.projectGeometry = function (geometry, outSpatialReference) {
let coordinates = []
const projectOptions = {
originWKID: geometry.spatialReference.wkid,
originDefs: geometry.spatialReference.wkt,
targetWKID: outSpatialReference.wkid,
targetDefs: outSpatialReference.wkt
}
switch (geometry.type) {
case GeometryType.point:
coordinates = [geometry.coordinates]
projectOptions.points = coordinates
coordinates = Projection.projectPoints(projectOptions)
return new Point({
coordinates: coordinates[0],
spatialReference: new SpatialReference(outSpatialReference)
})
case GeometryType.multiPoint:
coordinates = geometry.coordinates
projectOptions.points = coordinates
coordinates = Projection.projectPoints(projectOptions)
return new MultiPoint({
coordinates,
spatialReference: new SpatialReference(outSpatialReference)
})
case GeometryType.lineString:
coordinates = geometry.coordinates
projectOptions.points = coordinates
coordinates = Projection.projectPoints(projectOptions)
return new LineString({
coordinates,
spatialReference: new SpatialReference(outSpatialReference)
})
case GeometryType.multiLineString:
coordinates = geometry.coordinates
const multiLines = []
for (let i = 0; i < coordinates.length; i++) {
projectOptions.points = coordinates[i]
multiLines.push(Projection.projectPoints(projectOptions))
}
return new MultiLineString({
coordinates: multiLines,
spatialReference: new SpatialReference(outSpatialReference)
})
case GeometryType.polygon:
coordinates = geometry.coordinates
const polygonRings = []
for (let i = 0; i < coordinates.length; i++) {
projectOptions.points = coordinates[i]
polygonRings.push(Projection.projectPoints(projectOptions))
}
return new Polygon({
coordinates: polygonRings,
spatialReference: new SpatialReference(outSpatialReference)
})
case GeometryType.multiPolygon:
coordinates = geometry.coordinates
const multiPolygons = []
for (let i = 0; i < coordinates.length; i++) {
const polygonRings = []
for (let j = 0; j < coordinates[i].length; j++) {
projectOptions.points = coordinates[i][j]
polygonRings.push(Projection.projectPoints(projectOptions))
}
multiPolygons.push(polygonRings)
}
return new MultiPolygon({
coordinates: multiPolygons,
spatialReference: new SpatialReference(outSpatialReference)
})
case GeometryType.circle:
coordinates = [
[geometry.center[0], geometry.center[1], geometry.center[2]],
[
geometry.center[0] + geometry.radius,
geometry.center[1] + geometry.radius,
geometry.center[2]
]
]
projectOptions.points = coordinates
coordinates = Projection.projectPoints(projectOptions)
const radius = coordinates[1][0] - coordinates[0][0]
return new Circle({
center: coordinates[0],
radius
})
case GeometryType.extent:
coordinates = [
[geometry.xmin, geometry.ymin, geometry.zmin],
[geometry.xmax, geometry.ymax, geometry.zmax]
]
projectOptions.points = coordinates
coordinates = Projection.projectPoints(projectOptions)
return new Extent({
xmin: coordinates[0][0],
ymin: coordinates[0][1],
zmin: coordinates[0][2],
xmax: coordinates[1][0],
ymax: coordinates[1][1],
zmax: coordinates[1][2],
spatialReference: new SpatialReference(outSpatialReference)
})
default:
return geometry
}
}
/**
* 将几何对象投影到指定坐标系中
* @param {Geometry | Array<geometry>} geometry 要投影的几何或几何数组
* @param {SpatialReference} outSpatialReference 目标参考系
* @return {Geometry | Array<Geometry>} 投影后的几何对象
* @example <caption><h7>将3857坐标系的点转为4326坐标系的点</h7></caption>
* const projectedGeometry = Zondy.Geometry.Projection.project(
* new Zondy.Geometry.Extent({
* xmin: 12062959.621822732,
* ymin: 3379793.138124517,
* xmax: 12927637.10614421,
* ymax: 3931626.287996913,
* spatialReference: new Zondy.SpatialReference({
* wkid: 3857
* })
* }),
* new Zondy.SpatialReference({
* wkid: 4326
* })
* )
* @example <caption><h7>将未收录的坐标系的点转为4326坐标系的点</h7></caption>
* const projectedGeometry = Zondy.Geometry.Projection.project(
* new Zondy.Geometry.Extent({
* xmin: -45257.10778559791,
* ymin: 3212885.1836444484,
* xmax: 705989.8953363781,
* ymax: 3691623.86404564,
* spatialReference: new Zondy.SpatialReference({
* wkid: 4547,
* wkt: '+proj=tmerc +lat_0=0 +lon_0=114 +k=1 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs'
* })
* }),
* new Zondy.SpatialReference({
* wkid: 4326
* })
* )
* */
Projection.project = function (geometry, outSpatialReference) {
if (geometry instanceof Array) {
const projectGeometries = []
for (let i = 0; i < geometry.length; i++) {
projectGeometries.push(
Projection.projectGeometry(geometry[i], outSpatialReference)
)
}
return projectGeometries
} else {
// 4326坐标系下,获取有效范围内经度。proj纬度最大为 89.99999999
if (geometry.spatialReference.isGeographic) {
const _validLat = (lat) => {
const projLimit = 89.99999999
if (lat > projLimit) {
lat = projLimit
} else if (lat < -projLimit) {
lat = -projLimit
}
return lat
}
if (geometry.coordinates && geometry.coordinates.length > 1) {
geometry.coordinates[1] = _validLat(geometry.coordinates[1])
}
geometry.ymax = _validLat(geometry.ymax)
geometry.ymin = _validLat(geometry.ymin)
}
return Projection.projectGeometry(geometry, outSpatialReference)
}
}
Zondy.Geometry.Projection = Projection
Zondy.Geometry.Proj4 = proj4
export default Projection