import {
Zondy,
defaultValue,
BaseView,
ViewEventType,
LayerType,
Point,
Extent,
T,
showLayerByScale,
SpatialReference,
Projection,
MapEventType,
Geometry,
Config,
cloneObject,
TileInfoUtil,
// Utils,
Map,
isNull,
toJSON
} from '@mapgis/webclient-common'
import * as L from '@mapgis/leaflet'
import UI from './ui/UI'
import MapViewUtil from './utils/MapViewUtil'
import { getLayerView } from '../document/index'
import CRS from './utils/support/CRS'
import Popup from './utils/Popup'
import Screenshot from './utils/Screenshot'
/**
* 二维场景视图(leaflet引擎),对地图引擎进行管理,如果要对地图图层进行管理请参考[Map]{@link Map},<br/>
* 参考示例:
* <a href='#MapView'>[初始化二维场景视图]</a>
* <br>[ES5引入方式]:<br/>
* Zondy.MapView() <br/>
* [ES6引入方式]:<br/>
* import { MapView } from '@mapgis/webclient-leaflet-plugin' <br/>
* @class MapView
* @extends Evented
* @moduleEX ViewModule
* @fires BaseView#地图视图加载完毕事件
* @fires BaseView#鼠标点击事件
* @fires BaseView#鼠标双击事件
* @fires BaseView#鼠标按下事件
* @fires BaseView#鼠标抬起事件
* @fires BaseView#鼠标右键点击事件
* @fires BaseView#鼠标移动事件
* @fires BaseView#鼠标移出视图事件
* @fires BaseView#鼠标移入视图事件
* @fires BaseView#zoom变化事件
* @fires BaseView#地图移动事件
* @fires BaseView#地图大小变化事件
* @fires BaseView#键盘输入事件
* @fires BaseView#键盘按下事件
* @fires BaseView#键盘抬起事件
* @fires BaseView#地图视图改变事件
* @param {Object} options 构造参数
* @param {Map} [options.Map] 图层管理容器对象
* @param {String} [options.viewId] 二维场景视图的容器(html的div标签)ID
* @param {Number} [options.minZoom = 0] 最小缩放级数,在此设置则所有图层都不会浏览小于该级数的图片
* @param {Number} [options.maxZoom = 19] 最大缩放级数,在此设置则所有图层都不会浏览大于该级数的图片
* @param {Number} [options.zoom = 1] 初始化二维场景视图时显示级数
* @param {Boolean} [options.attributionControl = false] 是否显示右下角水印
* @param {Boolean} [options.zoomControl = true] 是否显示缩放控件
* @param {Boolean | String} [options.doubleClickZoom = true] 是否允许双击鼠标左键缩放或者缩放至图层中心点
* @param {Boolean} [options.dragging = true] 是否允许拖拽
* @param {Number} [options.zoomSnap = 1] 当使用flyTo缩放至中心点时,缩放级数乘以的系数
* @param {Number} [options.zoomDelta = 1] 当触发zoomIn或者zoomOut操作时,缩放级数乘以的系数
* @param {Boolean} [options.trackResize = true] 是否允许图层大小随视窗变化
* @param {Boolean} [options.keyboard = true] 是否允使用键盘的+/-号,来缩放地图
* @param {Number} [options.keyboardPanDelta = 80] 使用键盘来平移或缩放地图时的系数,单位px
* @param {Boolean | String} [options.scrollWheelZoom = true] 使用键盘来平移或缩放地图时的系数,单位px
* @param {Number} [options.wheelDebounceTime = 40] 滚轮事件的触发事件,单位毫秒
* @param {Number} [options.wheelPxPerZoomLevel = 60] 滚轮缩放时,地图缩放的像素单位,单位像素
* @param {Boolean} [options.tapHold = true] 是否开启移动端,手指按压不放事件
* @param {Number} [options.tapTolerance = 15] 手指有效触发范围,单位像素
* @param {Boolean | String} [options.touchZoom = true] 是否启用手指两指缩放,当值为center,表示两只滑动,缩放至地图中心
* @param {Boolean} [options.bounceAtZoomLimits = true] 当过最大或最小级数后不再缩放
* @param {Boolean} [options.animation = true] 是否启用动画
* @param {Point} [options.center = new Point({coordinates:[0,0]})] 地图视图中心点
* @param {Extent} [options.extent = undefined] 地图视图可视范围
* @param {Number} [options.scale = undefined] 地图视图比例尺
* @param {Number} [options.maxScale = undefined] 最大比例尺
* @param {Number} [options.minScale = undefined] 最小比例尺
* @param {Object} [options.popup = undefined] 地图弹框
* @param {Number} [options.rotation = 0] 地图视图旋转选项。单位为度,默认为0,表示不进行旋转
*
* @summary <h5>支持如下方法:</h5>
* <a href='#flyTo'>[1、视点跳转]</a><br/>
* <a href='#destroy'>[2、销毁视图对象]</a><br/>
* <a href='#getCenter'>[3、获取当前视图的中心点]</a><br/>
* <a href='#getPixelCenter'>[3、获取当前视图的像素中心点]</a><br/>
* <a href='#getZoom'>[4、获取当前缩放级数]</a><br/>
* <a href='#getExtent'>[5、获取当前视图的地理范围]</a><br/>
* <a href='#getPixelWorldExtent'>[6、获取当前视图的像素范围]</a><br/>
* <a href='#getMinZoom'>[7、获取最小缩放级数]</a><br/>
* <a href='#getMaxZoom'>[8、获取最大缩放级数]</a><br/>
* <a href='#getSize'>[9、获取当前视图容器的宽高]</a><br/>
* <a href='#toJSON'>[10、导出场景视图的配置文件]</a><br/>
* <a href='#clone'>[11、克隆并返回一个新的场景视图对象]</a><br/>
* <a href='#fromJSON'>[12、通过json构造并返回一个新的场景视图对象]</a><br/>
* <a href='#takeScreenshot'>[13、屏幕快照]</a><br/>
* [14、注册事件]{@link Evented#on}<br/>
* [15、移除事件]{@link Evented#off}<br/>
* <a href='#toMap'>[15、屏幕像素坐标点转地理坐标点]</a><br/>
* <a href='#toScreen'>[16、地理坐标点转屏幕像素坐标点]</a><br/>
* <a href='#hitTest'>[17、穿透检测]</a><br/>
* <a href='#getLayer'>[18、根据实际图层对象查询并返回基础图层]</a><br/>
* <a href='#getScale'>[19、获取当前比例尺]</a><br/>
*
* @example <caption><h7 id='MapView'>初始化一个二维场景视图</h7></caption>
* // ES5引入方式
* const { Map, MapView } = Zondy
* // ES6引入方式
* import { Map, MapView } from "@mapgis/webclient-leaflet-plugin"
* // 初始化图层管理容器
* const map = new Map();
* // 初始化地图视图对象
* const mapView = new MapView({
* // 二维场景视图的容器(html的div标签)ID
* viewId: "二维场景视图的容器的id",
* // 图层管理容器
* map: map
* });
*/
class MapView extends BaseView {
constructor(options) {
super(options)
// eslint-disable-next-line no-param-reassign
options = defaultValue(options, {})
this._options = options
/**
* 地图视图中心点
* @member {Point} MapView.prototype.center
*/
// 统一center的类型
this._center = defaultValue(
options.center,
new Point({ coordinates: [0, 0] })
)
if (
this._center &&
Array.isArray(this._center) &&
this._center.length > 1
) {
this._center = new Point({
coordinates: [this._center[0], this._center[1]]
})
}
/**
* 地图坐标系
* @member {Number} MapView.prototype.zoom
*/
this.crs = undefined // defaultValue(options.crs, 'EPSG3857')
/**
* 地图视图可视范围
* @member {Extent} MapView.prototype.extent
*/
this._extent = defaultValue(options.extent, undefined)
/**
* 是否启用视角跳转动画
* @member {Boolean} MapView.prototype.animation
*/
this.animation = defaultValue(options.animation, true)
/**
* 地图视图宽度
* @readonly
* @member {Number} MapView.prototype.width
*/
this.width = undefined
/**
* 地图视图高度
* @readonly
* @member {Number} MapView.prototype.height
*/
this.height = undefined
/**
* 地图层级
* @member {Number} MapView.prototype.zoom
*/
this._zoom = defaultValue(options.zoom, 0)
this._resolution = 0 // defaultValue(options.resolution, 0)
/**
* 地图视图比例尺
* @member {Number} MapView.prototype.scale
*/
this._scale = defaultValue(options.scale, undefined)
/**
* 地图视图范围
* @member {Extent} MapView.prototype.extent
*/
this._extent = defaultValue(options.extent, undefined)
/**
* 地图视图最大比例尺
* @member {Number} MapView.prototype.maxScale
*/
this.maxScale = defaultValue(options.maxScale, undefined)
/**
* 地图视图最小比例尺
* @member {Number} MapView.prototype.minScale
*/
this.minScale = defaultValue(options.minScale, undefined)
/**
* 地图弹框popup
* @member {Popup} MapView.prototype.popup
*/
this.popup = defaultValue(options.popup, undefined)
/**
* 视图渲染方式是否为canvas
* @member {Boolean} MapView.prototype.preferCanvas
*/
this.preferCanvas = defaultValue(options.preferCanvas, false)
// if (typeof this.crs === 'string') {
// if (L.CRS[this.crs]) {
// this.crs = L.CRS[this.crs]
// } else {
// this.crs = L.CRS.EPSG4326
// }
// }
/**
* 视图空间参考系
* @readonly
* @member {SpatialReference} MapView.prototype._spatialReference
*/
this._spatialReference = undefined
this._referenceLayer = undefined
/**
* 是否锁定视图空间参考系
* @member {Boolean} MapView.prototype.spatialReferenceLocked
*/
this.spatialReferenceLocked = defaultValue(
options.spatialReferenceLocked,
false
)
/**
* 地图视图旋转选项。单位为度,默认为0,表示不进行旋转
* @member {Boolean} MapView.prototype.rotation
*/
this._rotation = defaultValue(options.rotation, 0)
// 是否重置地图视图的坐标系
this._isCustomCrs = false
// 是否绑定Leaflet
this._map._initLeafletView = true
// 屏幕打印对象
this._screenshot = null
// 初始化地图引擎
// this._initView(false, this._options)
}
/**
* 视点跳转<a id='flyTo'></a>
* @param options 跳转参数
* @param {Array} [options.center] 跳转中心点
* @param {Number} [options.zoom = 1] 地图层级
* @param {Extent} [options.extent] 按范围跳转
* @example <caption><h7>中心点跳转示例</h7></caption>
* // ES5引入方式
* const { Map, MapView } = Zondy
* // ES6引入方式
* import { Map, MapView } from "@mapgis/webclient-leaflet-plugin"
* // 初始化图层管理容器
* map = new .Map();
* // 初始化地图视图对象
* mapView = new MapView({
* // 视图id
* viewId: "view-id",
* // 图层管理容器
* map: map
* });
* // 视点跳转
* mapView.flyTo({
* // 跳转中心点
* center: [{x}, {y}],
* // 地图层级
* zoom: {zoom}
* });
*
* @example <caption><h7>按范围跳转示例</h7></caption>
* // ES5引入方式
* const { Map, MapView } = Zondy
* const { Extent } = Zondy.Geometry
* // ES6引入方式
* import { Map, MapView, Extent } from "@mapgis/webclient-leaflet-plugin"
* // 初始化图层管理容器
* map = new Map();
* // 初始化地图视图对象
* mapView = new MapView({
* // 视图id
* viewId: "view-id",
* // 图层管理容器
* map: map
* });
* mapView.flyTo({
* // 范围几何
* extent: new Extent({
* "xmin":10,
* "xmax":210,
* "ymin":0,
* "ymax":100,
* })
* });
*
* @example <caption><h7>按范围跳转示例-拿到图层信息后跳转</h7></caption>
* // ES5引入方式
* const { IGSMapImageLayer } = Zondy.Layer
* // ES6引入方式
* import { IGSMapImageLayer } from "@mapgis/webclient-leaflet-plugin"
* const igsMapImageLayer = new IGSMapImageLayer({
* url: 'http://192.168.82.89:8089/igs/rest/services/Map/Hubei4326/MapServer'
* });
* map.add(igsMapImageLayer);
* // 图层加载完毕
* igsMapImageLayer.on('layer-view-created', function (result) {
* console.log("加载完毕:", result.layer)
* //视点跳转
* mapView.flyTo({
* extent: result.layer.extent
* });
* })
*/
flyTo(options) {
options = defaultValue(options, {})
const zoom = defaultValue(options.zoom, 1)
// 获取center
const center = this._getFlyToCenter(options)
// 获取extent
const extent = this._getFlyToExtent(options)
if (this._innerView) {
if (center) {
this._innerView.setView(
[center.coordinates[1], center.coordinates[0]],
zoom
)
} else if (extent) {
this._innerView.fitBounds([
[extent.ymin, extent.xmin],
[extent.ymax, extent.xmax]
])
}
}
}
/**
* 从extent获取经纬度范围
* @private
* @param {Extent} extent 范围对象
* @return {Object} 经纬度范围
* */
_getLatLngFromExtent(extent) {
const spatialReference = new SpatialReference('EPSG:4326')
const newExtent = Projection.project(extent, spatialReference)
const northEastLatlng = new L.latLng(newExtent.ymax, newExtent.xmax)
const southWestLatlng = new L.latLng(newExtent.ymin, newExtent.xmin)
return {
northEastLatlng,
southWestLatlng
}
}
/**
* 从center获取经纬度范围
* @private
* @param {Point} point 范围对象
* @return {Object} 地理坐标
* */
_getLatLngFromPoint(point) {
const spatialReference = new SpatialReference('EPSG:4326')
const newCenter = Projection.project(point, spatialReference)
const centerLatlng = new L.latLng(
newCenter.coordinates[1],
newCenter.coordinates[0]
)
return centerLatlng
}
/**
* 初始化地图视图事件
* @private
* */
_initViewEvent() {
const self = this
// 定义一个变量存储单击触发事件
this._clickStore = null
// 定义一个变量存储拖拽事件参数
this._dragParams = { isDrag: false, origin: null, action: '', button: null }
// 地图视图加载完毕事件
self.fire(ViewEventType.loaded, {})
// leaflet注册点击事件
this._innerView.on('click', (event) => {
// 单击触发事件,300ms延迟
self._clickStore = setTimeout(function () {
// 发送左键单击点击事件
if (self._clickStore) {
self.fire(ViewEventType.click, MapViewUtil.getMouseEvent(event, self))
}
}, 300)
// 发送立即点击事件
self.fire(
ViewEventType.immediateClick,
MapViewUtil.getMouseEvent(event, self)
)
})
// 注册鼠标右键按下事件
this._innerView.on('contextmenu', (event) => {
// 单击触发事件,300ms延迟
self._clickStore = setTimeout(function () {
// 发送鼠标右键按下事件
if (self._clickStore) {
self.fire(ViewEventType.click, MapViewUtil.getMouseEvent(event, self))
}
}, 300)
// 发送立即点击事件
self.fire(
ViewEventType.immediateClick,
MapViewUtil.getMouseEvent(event, self)
)
})
// 注册双击事件
this._innerView.on('dblclick', (event) => {
// 单击事件清理
if (self._clickStore) {
clearTimeout(this._clickStore)
self._clickStore = null
}
// 发送双击事件
self.fire(
ViewEventType.doubleClick,
MapViewUtil.getMouseEvent(event, self)
)
})
// 注册鼠标按下事件
this._innerView.on('mousedown', (event) => {
// 发送鼠标按下事件
self.fire(
ViewEventType.pointerDown,
MapViewUtil.getMouseEvent(event, self)
)
// 发送鼠标拖拽开始事件
if (!this._dragParams.isDrag) {
this._dragParams.origin = {
x: event.originalEvent.layerX,
y: event.originalEvent.layerY
}
this._dragParams.button = event.originalEvent.button
this._dragParams.action = 'mouse-down'
this._dragParams.isDrag = false
}
})
// 注册鼠标抬起事件
this._innerView.on('mouseup', (event) => {
// 发送鼠标抬起事件
self.fire(ViewEventType.pointerUp, MapViewUtil.getMouseEvent(event, self))
// 发送鼠标拖拽结束事件
if (self._dragParams.isDrag && this._dragParams.action === 'update') {
this._dragParams.action = 'end'
self.fire(
ViewEventType.drag,
MapViewUtil.getDragEvent(
event,
self,
self._dragParams.origin,
this._dragParams.action
)
)
}
self._dragParams = {
isDrag: false,
origin: null,
action: '',
button: null
}
})
// 注册鼠标移动事件
this._innerView.on('mousemove', (event) => {
// 发送鼠标移动视图事件
self.fire(
ViewEventType.pointerMove,
MapViewUtil.getMouseEvent(event, self)
)
// 发送鼠标拖拽中事件
if (self._dragParams.isDrag) {
self.fire(
ViewEventType.drag,
MapViewUtil.getDragEvent(
event,
self,
self._dragParams.origin,
self._dragParams.action,
self._dragParams.button
)
)
self._dragParams.action = 'update'
} else if (this._dragParams.action === 'mouse-down') {
this._dragParams.action = 'start'
self._dragParams.isDrag = true
}
})
// 注册键盘按下事件
this._innerView.on('keydown', (event) => {
// 发送键盘按下事件
self.fire(
ViewEventType.keyDown,
MapViewUtil.getKeyEvent(event, self, ViewEventType.keyDown)
)
})
// 注册键盘抬起事件
this._innerView.on('keyup', (event) => {
// 发送键盘抬起事件
self.fire(
ViewEventType.keyUp,
MapViewUtil.getKeyEvent(event, self, ViewEventType.keyUp)
)
})
// 注册视图开始改变事件
this._innerView.on('movestart', () => {
self.stationary = false
})
// 注册zoom变化事件
this._innerView.on('zoom', (event) => {
self._map.layers.forEach(function (layer) {
switch (layer.type) {
case LayerType.igsFeature:
case LayerType.graphics:
showLayerByScale(self, layer)
break
default:
break
}
})
// 发送zoom变化事件
self.fire(ViewEventType.zoom, {
zoom: event.target._zoom,
event
})
})
// 注册zoom end事件
this._innerView.on('zoomend', (event) => {
// 更新地图视野范围,比例尺
self._zoomEndHandler()
// 发送地图视图改变事件
self.fire(
ViewEventType.viewChange,
MapViewUtil.getViewChangeEvent(event, self)
)
})
// 注册地图移动完成事件
this._innerView.on('moveend', (event) => {
// 更新地图视中心
self._moveEndHandler()
// 发送地图视图改变事件
self.fire(
ViewEventType.viewChange,
MapViewUtil.getViewChangeEvent(event, self)
)
// self.fire(ViewEventType.mapMove, event)
self.stationary = true
})
// 注册地图大小变化事件
this._innerView.on('resize', (event) => {
// 发送地图初始化后触发的事件
self.fire(ViewEventType.resize, {
oldWidth: event.oldSize.x,
oldHeight: event.oldSize.y,
width: event.newSize.x,
height: event.newSize.y
})
// 更新地图窗口宽高
self._initSize()
})
}
/**
* 设置(鼠标)操作地图事件是否可用
* @private
* @param {String} actionType 操作类型
* @param {Boolean} isEnable 是否可用
* */
_mapActionControl(actionType, isEnable) {
if (!this._innerView) return
if (actionType === 'drag-pan') {
if (isEnable) {
this._innerView.dragging.enable()
} else {
this._innerView.dragging.disable()
}
} else if (actionType === 'double-click-zoom') {
if (isEnable) {
this._innerView.doubleClickZoom.enable()
} else {
this._innerView.doubleClickZoom.disable()
}
}
}
/**
* 初始化一个地图视图对象
* @param isReset 是否是重置视图
* @private
* */
_initView(isReset, options) {
this._innerView = L.map(this._viewId, {
// 是否使用Canvas渲染方式
preferCanvas: this.preferCanvas,
// 是否显示右下角水印
attributionControl: defaultValue(options.attributionControl, false),
// 是否显示缩放控件
zoomControl: defaultValue(options.zoomControl, true),
// 是否允许双击鼠标左键缩放或者缩放至图层中心点
doubleClickZoom: defaultValue(options.doubleClickZoom, true),
// 是否允许拖拽
dragging: defaultValue(options.dragging, true),
// 当使用flyTo缩放至中心点时,缩放级数乘以的系数
zoomSnap: defaultValue(options.zoomSnap, 1),
// 当触发zoomIn或者zoomOut操作时,缩放级数乘以的系数
zoomDelta: defaultValue(options.zoomDelta, 1),
// 是否允许图层大小随视窗变化
trackResize: defaultValue(options.trackResize, true),
// 是否允使用键盘的+/-号,来缩放地图或者使用方向键来平移地图
keyboard: defaultValue(options.keyboard, true),
// 使用键盘来平移或缩放地图时的系数,单位px
keyboardPanDelta: defaultValue(options.keyboardPanDelta, 80),
// 是否允许使用鼠标滚轮来缩放地图,当值为center时,表示使用滚轮缩放至地图中心点
scrollWheelZoom: defaultValue(options.scrollWheelZoom, true),
// 滚轮事件的触发事件,单位毫秒
wheelDebounceTime: defaultValue(options.wheelDebounceTime, 40),
// 滚轮缩放时,地图缩放的像素单位,单位像素
wheelPxPerZoomLevel: defaultValue(options.wheelDebounceTime, 60),
// 是否开启移动端,手指按压不放事件
tapHold: defaultValue(options.tapHold, true),
// 手指有效触发范围,单位像素
tapTolerance: defaultValue(options.tapTolerance, 15),
// 是否启用手指两指缩放,当值为center,表示两只滑动,缩放至地图中心
touchZoom: defaultValue(options.touchZoom, true),
// 当过最大或最小级数后不再缩放
bounceAtZoomLimits: defaultValue(options.bounceAtZoomLimits, true),
// 图层空间参考系
crs: this.crs,
// 图层中心点
center:
this._center && this._center.coordinates
? [this._center.coordinates[1], this._center.coordinates[0]]
: [0, 0],
// 可视范围
extent: defaultValue(this._extent, undefined),
// 最大缩放级数
maxZoom: this.maxZoom,
// 最小缩放级数
minZoom: this.minZoom,
// 初始化时显示级数
zoom: this._zoom,
// 是否启用视点跳转动画
zoomAnimation: this.animation,
// Config
config: Config,
// 是否进行旋转,目前对外没有constraints,默认设置为true
rotate: true,
// 旋转bearing,单位为度
bearing: this._rotation
})
// 初始化ui容器
this.ui = new UI({
view: this
})
// 初始化视图事件
this._initViewEvent()
// 初始化可视范围
if (this._extent && !isReset) {
this.flyTo({ extent: this._extent })
}
// 初始化比例尺,extent定位优先于scale,center定位
if (!this._extent && this._scale && this._center && !isReset) {
this._initScale()
}
// 获取视图大小
this._initSize()
// 初始化最大比例尺和最小比例尺
this._initScaleLimit()
// 初始化地图视图弹框,目前仅支持location方式打开弹框
this._initPopup()
}
// 设置spatialReference
_setSpatialReference() {
// 如果basemap有坐标系,则view坐标系取basemap坐标系的值。
// 没有,则view根据加载的图层动态坐标系的值。
let spatialReference = null
let referenceLayer = null
if (this._map.basemap && this._map.basemap.spatialReference) {
spatialReference = this._map.basemap.spatialReference
referenceLayer = this._map.basemap._referenceLayer
} else if (this._map.layers) {
// const target = Utils.getSpatialReference(this._map.layers)
const target = TileInfoUtil.getSpatialReference(this._map.layers)
spatialReference = target.spatialReference
referenceLayer = target.referenceLayer
}
// 没有获取到图层参考系,则直接返回
if (!spatialReference) return
// 当view上未设置空间参考系锁定,
// 或空间参考系锁定但没有spatialReference时,则获取新的spatialReference
if (
!this.spatialReferenceLocked ||
(this.spatialReferenceLocked && !this._spatialReference)
) {
this._spatialReference = spatialReference
this._referenceLayer = referenceLayer
// 如果是MapImageLayer,则重置坐标系为当前坐标系
if (this._spatialReference) {
const isBasemapLayer = referenceLayer._isBasemapLayer
TileInfoUtil.setLayerSpatialReference(
this._map.layers,
this._spatialReference,
isBasemapLayer
)
}
// 设置crs
this.setCrs(referenceLayer)
}
}
setCrs(referenceLayer) {
const crs = this.getCrsByReferenceLayer(referenceLayer)
let isNew = false
let isFirst = false
if (this.crs) {
// 比较被设置的新Crs和当前this.crs是否相同。
// 判断标准 crs.option的resolutions,origin,bounds是否相等。
if (crs !== null) {
const newCrsInfo = {
wkid: crs.code.split(':')[1]
}
const currentCrsInfo = {
wkid: this.crs.code.split(':')[1]
}
if (newCrsInfo.wkid === currentCrsInfo.wkid) {
// ArcgisMapImage图层临时处理,服务数据extent有问题,暂为解决。4326、3857参考系构建对应的Leaflet参考系对象,
// 因此没有options,不参与对比bounds、resolutions,暂时组织比较wkid
// 问题解决后,统一比较 crs.options,该if条件可移除
if (
(newCrsInfo.wkid === '4326' || newCrsInfo.wkid === '3857') &&
!crs.options
) {
isNew = false
}
if (crs.options) {
const newCrsOptions = crs.options
newCrsInfo.resolutions = newCrsOptions.resolutions
newCrsInfo.origin = newCrsOptions.origin
newCrsInfo.bounds = newCrsOptions.bounds
currentCrsInfo.resolutions = this.crs.options.resolutions
currentCrsInfo.origin = this.crs.options.origin
currentCrsInfo.bounds = this.crs.options.bounds
if (
JSON.stringify(newCrsInfo.resolutions) !==
JSON.stringify(currentCrsInfo.resolutions) ||
JSON.stringify(newCrsInfo.origin) !==
JSON.stringify(currentCrsInfo.origin) ||
JSON.stringify(newCrsInfo.bounds) !==
JSON.stringify(currentCrsInfo.bounds)
) {
isNew = true
}
}
} else {
isNew = true
}
}
} else if (!this.crs && crs) {
isNew = true
isFirst = true
}
if (isNew) {
this.crs = crs
if (referenceLayer.extent) {
const extent = new Extent({
xmin: referenceLayer.extent.xmin,
xmax: referenceLayer.extent.xmax,
ymin: referenceLayer.extent.ymin,
ymax: referenceLayer.extent.ymax,
spatialReference: referenceLayer.spatialReference
})
const latLng = this._getLatLngFromExtent(extent)
this._center = new Point({
coordinates: [
(latLng.southWestLatlng.lng + latLng.northEastLatlng.lng) / 2,
(latLng.southWestLatlng.lat + latLng.northEastLatlng.lat) / 2
]
})
}
const layers = []
const baseLayers = []
this._map.layers.items.forEach((layer) => {
// 如果是当前图层load完之后触发的重置方法,则不需要重载图层
// 图层信息已加载完全并且不是第一次设置坐标系,则需要重新加载
// 图层信息没加载完全并且是第一次设置坐标系,则需要重新加载
// 图层是第一次设置坐标系,并且是graphicsLayer(不管graphicsLayer是否loaded),则需要重新加载
// 图层是参考图层,extent没变,则不需要重新加载。图层是参考图层,extent变了,则需要重新加载
if (this._loadedLayer && layer.id === this._loadedLayer.id) {
return
}
if (
(layer.loaded && !isFirst) ||
(!layer.loaded && isFirst) ||
(isFirst && layer.type === LayerType.graphics)
) {
layers.push(layer)
if (layer._isBasemapLayer) {
layer._isBasemapLayer = false
baseLayers.push(layer)
}
}
})
this._map._canResetView = false
this._map.removeMany(layers)
baseLayers.forEach((layer) => {
layer._isBasemapLayer = true
})
this._reCreateView()
this._map.addMany(layers)
this._map._canResetView = true
if (isFirst) {
referenceLayer.on('layerview-created', () => {
// 初始化可视范围
if (this._extent) {
this.flyTo({ extent: this._extent })
}
// 初始化比例尺,extent定位优先于scale,center定位
if (!this._extent && this._scale && this._center) {
this._initScale()
}
// 获取视图大小
this._initSize()
// 初始化最大比例尺和最小比例尺
this._initScaleLimit()
// 初始化地图视图弹框,目前仅支持location方式打开弹框
this._initPopup()
})
}
}
}
/**
* 删除当前视图,重新创建一个视图
* @private
* */
_reCreateView() {
if (this._innerView) {
this._innerView.remove()
this._resetExtent()
}
this._initView(true, this._options)
}
/**
* 根据crs重新创建extent,center等属性
* @private
* */
_resetExtent() {
if (this._extent) {
this._extent = undefined
this._zoom = 0
}
}
/**
* 根据图层的spatialReference对象,生成一个坐标系,在leaflet上一定要resolutions、origin、extent以及椭球体参数,
* 才可以创建一个坐标系,瓦片或者WMTS自带resolutions、origin、extent,
* 矢量或者WMS需要从extent上计算出resolutions和origin,计算操作在基础图层中完成
* @private
* @param {Layer} layer 图层
* @return {CRS} 地图视图的坐标系对象
* */
_getCrs(layer) {
const spatialReference = layer.spatialReference
// 投影范围
const layerExtent = layer.spatialReference.extent || layer.extent
const extent = Extent.fromJSON(layerExtent.toJSON())
const isSameSpatialReference = function (
spatialReference1,
spatialReference2
) {
let isSame = false
if (
[3857, 102113, 102100, 900913].indexOf(spatialReference1.wkid) > -1 &&
[3857, 102113, 102100, 900913].indexOf(spatialReference2.wkid) > -1
) {
isSame = true
} else if (
[4326, 4490, 4610, 4214].indexOf(spatialReference1.wkid) > -1 &&
[4326, 4490, 4610, 4214].indexOf(spatialReference2.wkid) > -1
) {
isSame = true
} else if (spatialReference1.wkid === spatialReference2.wkid) {
isSame = true
}
return isSame
}
this._map.layers.forEach((layerItem) => {
// 如果当前layer是基础底图,则不受普通图层的参考系范围影响,但也会受其他基础底图图层的参考系范围影响
if (layer._isBasemapLayer && !layerItem._isBasemapLayer) {
return
}
if (
layerItem.spatialReference &&
layer.spatialReference &&
isSameSpatialReference(
layerItem.spatialReference,
layer.spatialReference
) &&
layerItem.loaded
) {
if (extent.xmin > layerItem.extent.xmin) {
extent.xmin = layerItem.extent.xmin
}
if (extent.ymin > layerItem.extent.ymin) {
extent.ymin = layerItem.extent.ymin
}
if (extent.xmax < layerItem.extent.xmax) {
extent.xmax = layerItem.extent.xmax
}
if (extent.ymax < layerItem.extent.ymax) {
extent.ymax = layerItem.extent.ymax
}
}
})
let tileInfo = layer.tileInfo
// 处理 瓦片图层、arcgis瓦片图层、矢量瓦片图层
if (layer.type === LayerType.wmts) {
tileInfo = TileInfoUtil.parseWMTSTileInfo(layer)
} else if (layer.type === LayerType.wms) {
tileInfo = TileInfoUtil.parseWMSTileInfo(layer, extent)
} else if (
// igsFeature、wfs、geojson图层是矢量图形,图层没有tile size概念,
// 但Leaflet引擎需要构造分辨率级,给定一个256默认tile size,用于计算分辨率级。
// igsMapImage图层,如果是图像渲染模式则和上序逻辑相同。如果是瓦片渲染模式,tileSize取图层上设置的瓦片宽高。
// arcgisMapImage服务对接有暂时问题,暂不自定义计算,直接取arcgisMapImage的tileInfo的分辨率
layer.type === LayerType.igsMapImage ||
// layer.type === LayerType.arcgisMapImage ||
layer.type === LayerType.igsFeature ||
layer.type === LayerType.wfs ||
layer.type === LayerType.geojson
) {
tileInfo = TileInfoUtil.parseMapImageTileInfo(layer, extent)
}
// igsMapImage图层临时处理,目前根据arcgisMapImage的extent、origin和tileInfo.lods构造出来的参考系有问题。
// 问题解决后,统一走 tileInfo = TileInfoUtil.parseMapImageTileInfo(layer)逻辑。
if (layer.type === LayerType.arcgisMapImage) {
if (
[3857, 102113, 102100, 900913].indexOf(layer.spatialReference.wkid) > -1
) {
return L.CRS.EPSG3857
} else if (
[4326, 4490, 4610, 4214].indexOf(layer.spatialReference.wkid) > -1
) {
return L.CRS.EPSG4326
} else {
tileInfo = TileInfoUtil.parseMapImageTileInfo(layer)
}
}
// 原点
const origin = Point.toCoordinates(tileInfo.origin)
// 获取分辨率数组
const resolutions = tileInfo.lods.map((lod) => lod.resolution)
const length = resolutions.length
// 如果数组长度小于20,则将数组最后一个除以2,将数组扩充为20,否则地图视图无法继续缩放
if (length < 20) {
let lastResolutions = resolutions[length - 1]
let lastLod = tileInfo.lods[length - 1]
for (let i = 0; i < 20 - length; i++) {
resolutions.push(lastResolutions / 2)
lastLod = {
resolution: lastResolutions / 2,
scale: lastLod.scale / 2,
level: tileInfo.lods.length
}
tileInfo.lods.push(lastLod)
lastResolutions /= 2
}
}
// 创建一个坐标系对象
return new CRS({
// 指定wkid,wkid的优先级比wkt高,和igs保持一致
wkid: spatialReference.wkid,
// 指定wkt
wkt: spatialReference.wkt,
resolutions,
lods: tileInfo.lods,
origin,
extent,
// 加载矢量瓦片特殊逻辑,需要传入矢量瓦片tileSize(mapboxgl对于初级瓦片的要求),仅影响矢量瓦片显示
mapboxTileSize: spatialReference._mapboxTileSize
})
}
/**
* @description 创建图层视图
* @private
* @param {Layer} layer 图层
* @return {LayerView| null}
*/
_createLayerView(layer) {
const layerView = getLayerView(this, layer) || null
return layerView
}
/**
* @description 事件处理器,重载基类方法
* @private
*/
_processEvent(event) {
super._processEvent(event)
if (event.type === MapEventType.layerRemove) {
if (
!this._map._isRemoveMany &&
!this._map._isRemoveAll &&
this._map._canResetView
) {
this._setSpatialReference()
}
}
if (
event.type === MapEventType.layerRemoveMany &&
this._map._canResetView
) {
if (event.layers) {
this._setSpatialReference()
}
}
}
/**
* @description 通过参考系图层获取CRS对象
* @private
* @param {Layer} referenceLayer 图层加载完毕后的对象
* @return {CRS} CRS对象
*/
getCrsByReferenceLayer(referenceLayer) {
let crs = null
if (
!referenceLayer ||
!referenceLayer.spatialReference ||
!referenceLayer.loaded
) {
return crs
}
// 根据基础图层对象上的spatialReference,创建一个自定义坐标系
// (4326、3857也更具图层信息构造Crs,而不是定义为Leaflet的默认4326、3857Crs)
crs = this._getCrs(referenceLayer)
return crs
}
/**
* @description 初始化获取视图尺寸大小
* @private
*/
_initSize() {
const size = this._innerView.getSize()
this.width = size.x
this.height = size.y
}
/**
* 等待图层加载完毕,重写BaseView类的该方法
* @param {Layer} layer 基础图层对象
* @param {Object} result 图层加载完毕后的对象
* @param {Function} fireCreatedError 创建失败回调
* */
_waitLayerLoaded(layer, result, fireCreatedError) {
const self = this
// 是提供坐标系的图层,则加载图层
if (layer._isSRLayer) {
const promise = self._addLayer(result, event)
self._layerLoaded(promise, layer, result, fireCreatedError)
}
// 不是的则等待图层加载完毕
else {
if (layer._interval) {
clearInterval(layer._interval)
layer._interval = undefined
}
layer._interval = setInterval(function () {
const srLayer = self._map._getSRLayer()
if (!srLayer || (srLayer && srLayer.loaded)) {
clearInterval(layer._interval)
layer._interval = undefined
const promise = self._addLayer(result, event)
self._layerLoaded(promise, layer, result, fireCreatedError)
}
}, 50)
}
}
/**
* 销毁视图对象<a id='destroy'></a>
* */
destroy() {
this._innerView.remove()
}
/**
* 获取当前视图的中心点(经纬度中心点)<a id='getCenter'></a>
* @return {Object} 中心点对象
* */
getCenter() {
const center = this._innerView.getCenter()
let centerCrs = new Point({ coordinates: [center.lng, center.lat] })
this._center = centerCrs
const spatialReference = new SpatialReference(this.crs.code)
if (!spatialReference.isGeographic) {
centerCrs = Projection.project(centerCrs, spatialReference)
}
return centerCrs
}
/**
* <a id='setCenter'></a>
* 设置缩视野中心<a id='setCenter'></a>
* @private
* @param {Point} value 视图中心
* */
setCenter(value) {
if (!this._innerView) return
if (Array.isArray(value) && value.length > 1) {
this._center = new Point({
coordinates: [value[0], value[1]],
spatialReference: new SpatialReference(this.crs.code)
})
this.flyTo({ zoom: this._zoom, center: this._center })
} else if (value instanceof Point) {
this._center = value
this.flyTo({ zoom: this._zoom, center: this._center })
}
}
/**
* 获取当前缩放级数<a id='getZoom'></a>
* @return {Number} 当前缩放级数
* */
getZoom() {
this._zoom = this._innerView.getZoom()
return this._zoom
}
/**
* <a id='setZoom'></a>
* 设置缩放级数
* @private
* @param {Number} value 视图层级
* */
setZoom(value) {
if (!this._innerView) return
this._zoom = value
this.flyTo({ zoom: value, center: this._center })
}
/**
* 获取最小缩放级数<a id='getMinZoom'></a>
* @return {Number} 最小缩放级数
* */
getMinZoom() {
return this._innerView.getMinZoom()
}
/**
* @description 获取当前视图容器的宽高,单位像素<a id='getSize'></a>
* @return {Object} 当前视图容器的宽高对象
* */
getSize() {
return this._innerView.getSize()
}
/**
* 获取最大缩放级数<a id='getMaxZoom'></a>
* @return {Number} 最大缩放级数
* */
getMaxZoom() {
return this._innerView.getMaxZoom()
}
/**
* 获取当前视图的宽高范围,单位像素<a id='getPixelExtent'></a>
* @return {Object} 视图宽高对象
* */
getPixelExtent() {
return this._innerView.getPixelBounds()
}
/**
* 获取当前视图的像素中心点<a id='getPixelCenter'></a>
* @return {Object} 像素中心点对象
* */
getPixelCenter() {
return this._innerView.getPixelOrigin()
}
/**
* 获取当前视图的像素范围<a id='getPixelWorldExtent'></a>
* @return {Object} 当前视图的像素范围
* */
getPixelWorldExtent() {
return this._innerView.getPixelWorldBounds()
}
/**
* 导出场景视图的配置文件<a id='toJSON'></a>
* @return {Object} 导出的配置文件
* */
toJSON() {
const _json = {}
_json.extent = toJSON(this._extent, Extent)
_json.center = toJSON(this._center, Point)
_json.map = toJSON(this._map, Map)
_json.viewId = this._viewId
_json.animation = this.animation
_json.width = this.width
_json.height = this.height
_json.zoom = this._zoom
_json.scale = this._scale
_json.maxScale = this.maxScale
_json.minScale = this.minScale
_json.preferCanvas = this.preferCanvas
_json.spatialReferenceLocked = this.spatialReferenceLocked
return _json
}
/**
* 克隆并返回一个新的场景视图对象<a id='clone'></a>
* @return {MapView} 一个新的场景视图对象
* */
clone() {
return cloneObject(this)
}
/**
* 通过json构造并返回一个新的场景视图对象<a id='fromJSON'></a>
* @param {Object} json json对象
* @return {MapView} 一个新的场景视图对象
* */
static fromJSON(json) {
return new MapView(json)
}
/**
* <a id='toMap'></a>
* 屏幕像素坐标点转地理坐标点
* @param {Object} screenPoint 屏幕像素坐标点,例如{ x: 900, y: 500 }
* @return {Point} 地理坐标点
* @example <caption><h5>屏幕像素坐标点转地理坐标示例</h5></caption>
* // ES6引入方式
* import { MapView } from "@mapgis/webclient-leaflet-plugin"
* import { Map, Point, Extent } from "@mapgis/webclient-common"
* // 初始化图层管理容器
* const map = new Map();
* // 初始化地图视图对象
* const mapView = new MapView({
* // 视图id
* viewId: "mapgis-2d-viewer",
* // 图层管理容器
* map: map
* });
* const screenPoint = { x: 900, y: 500 }
* mapView.toMap(screenPoint)
* */
toMap(screenPoint) {
const latLngPoint = this._innerView.containerPointToLatLng(screenPoint)
let geoPoint = new Point({
coordinates: [latLngPoint.lng, latLngPoint.lat]
})
const spatialReference = new SpatialReference(this.crs.code)
if (!spatialReference.isGeographic) {
geoPoint = Projection.project(geoPoint, spatialReference)
}
return geoPoint
}
/**
* <a id='toScreen'></a>
* 地理坐标点转屏幕像素坐标点
* @param {Point}point 地理坐标点
* @return {Object} 屏幕像素坐标点
* @example <caption><h5>地理坐标点转屏幕像素坐标示例</h5></caption>
* // ES6引入方式
* import { MapView } from "@mapgis/webclient-leaflet-plugin"
* import { Map, Point, Extent } from "@mapgis/webclient-common"
* // 初始化图层管理容器
* const map = new Map();
* // 初始化地图视图对象
* const mapView = new MapView({
* // 视图id
* viewId: "mapgis-2d-viewer",
* // 图层管理容器
* map: map
* });
* const geoPoint = new Point({ coordinates: [123, 23, 0] })
* mapView.toScreen(geoPoint)
* */
toScreen(point) {
let _point = Geometry.fromJSON(point)
if (!_point.spatialReference.isGeographic) {
_point = Projection.project(
_point,
new SpatialReference({
wkid: 4326
})
)
}
const screenPoint = this._innerView.latLngToContainerPoint([
_point.coordinates[1],
_point.coordinates[0]
])
return screenPoint
}
/**
* <a id='takeScreenshot'></a>
* @description 屏幕快照
* @param {Object} [options = {}] 屏幕快照配置配置
* @param {PictureFormat} [options.format = PictureFormat.png] 照片格式,支持png,jpeg格式
* @param {String} [options.filename = 'screenshotFile'] 下载文件名
* @param {Number} [options.width = undefined] 图片宽度
* @param {Number} [options.height = undefined] 图片高度
* @param {Number} [options.x = undefined] 图片原点x
* @param {Number} [options.y = undefined] 图片原点y
* @param {Boolean} [options.isDownload = true] 是否下载图片
* @return {Object} 屏幕快照 {dataUrl String },且浏览器会下载图片
* @example <caption><h7>屏幕快照</h7></caption>
* // ES5引入方式
* const { Map, MapView } = Zondy
* const { PictureFormat } = Zondy.Enum
* // ES6引入方式
* import { Map, MapView } from "@mapgis/webclient-leaflet-plugin"
* import { PictureFormat } from "@mapgis/webclient-common"
* // 初始化图层管理容器
* const map = new Map();
* // 初始化地图视图对象
* const mapView = new MapView({
* // 二维场景视图的容器(html的div标签)ID
* viewId: "二维场景视图的容器的id",
* // 图层管理容器
* map: map
* })
* // 设置屏幕快照参数
* const screenshotOptions: {
* format: PictureFormat.png
* }
* // 开始屏幕快照
* mapView.takeScreenshot(screenshotOptions).then((result) => {
* // 获取base64格式的url字符串
* console.log("dataUrl:", result.dataUrl)
* })
* */
takeScreenshot(options) {
options.view = this
let screenshotResult = null
if (!this._screenshot) {
this._screenshot = new Screenshot(options)
screenshotResult = this._screenshot._addView()
} else {
screenshotResult = this._screenshot._addView()
}
return screenshotResult
}
/**
* 获取当前视图的地理范围<a id='getExtent'></a>
* @return {Extent} 获取当前视图的地理范围
* */
getExtent() {
if (!this._innerView) return
const latLngBounds = this._innerView.getBounds()
let extent = new Extent({
xmin: latLngBounds._southWest.lng,
ymin: latLngBounds._southWest.lat,
xmax: latLngBounds._northEast.lng,
ymax: latLngBounds._northEast.lat
})
const spatialReference = new SpatialReference(this.crs.code)
if (!spatialReference.isGeographic) {
extent = Projection.project(extent, spatialReference)
}
this._extent = extent
return extent
}
/**
* <a id='setExtent'></a>
* 设置视野范围
* @private
* @param {Extent} value 范围对象
* */
setExtent(value) {
if (!this._innerView) return
this._extent = value
this.flyTo({ extent: value })
}
/**
* zoom end事件处理器
* @private
* */
_zoomEndHandler() {
// 更新地图视野范围
this.getExtent()
// 更新地图层级
this.getZoom()
// 更新地图比例尺
this.getScale()
this.getResolution()
}
/**
* move end事件处理器
* @private
* */
_moveEndHandler() {
// 更新地图视野中心
this.getCenter()
// 更新地图视野范围
this.getExtent()
}
/**
* 初始化最大比例尺和最小比例尺
* @private
* */
_initScaleLimit() {
if (this.minScale) {
const result = this._getScaleZoom(this.minScale)
if (result.zoom) {
this.maxZoom = result.zoom
this._innerView.setMaxZoom(this.maxZoom)
}
}
if (this.maxScale) {
const result = this._getScaleZoom(this.maxScale)
if (result.zoom) {
this.minZoom = result.zoom
this._innerView.setMinZoom(this.minZoom)
}
}
if (
!this._scale ||
this._scale <= this.minScale ||
this._scale >= this.minScale
) {
this._scale = this.maxScale || this.minScale
this.setScale(this._scale)
}
}
/**
* 初始化弹窗
* @private
* */
_initPopup() {
this.popup = new Popup({ view: this })
}
/**
* <a id='hitTest'></a>
* @description 穿透检测,图元拾取。目前支持graphic类型拾取结果,支持图层类型GraphicLayer,FeatureLayer。
* @param {Object} screenPoint 屏幕像素坐标点,例如{ x: 900, y: 500 }
* @return {Array} 图元检测结果
* @example <caption><h7>根据基础图层对象或者图层id查询并返回实际图层</h7></caption>
* // ES6引入方式
* import { MapView } from "@mapgis/webclient-leaflet-plugin";
* import { Map, Point, Polygon, MultiPolygon ,Extent, GraphicsLayer, Feature, Circle, IGSFeatureLayer, IGSTileLayer } from "@mapgis/webclient-common";
* // 初始化图层管理容器
* const map = new Map();
* // 初始化地图视图对象
* this.mapView = new MapView({
* // 视图id
* viewId: "mapgis-2d-viewer",
* // 图层管理容器
* map: map,
* })
* // 创建一个要素
* const feature = [
* new Feature({
* id: '11113',
* geometry: new Circle({
* center: [113, 35],
* radius: 10000,
* radiusUnit: 'kilometers',
* })
* }),
* new Feature({
* id: '11114',
* geometry: new Polygon({
* coordinates: [
* // 外圈
* [
* [113.0, 29.0],
* [116.0, 29.0],
* [116.0, 35.0],
* [113.0, 35.0],
* [113.0, 29.0]
* ]
* ]
* })
* }),
* new Feature({
* id: '11115',
* geometry:new MultiPolygon({
* coordinates: [
* [
* // 外圈
* [
* [112.0, 28.0],
* [115.0, 28.0],
* [115.0, 30.0],
* [112.0, 30.0],
* [112.0, 28.0]
* ],
* // 第一个内圈
* [
* [112.2, 28.2],
* [112.2, 29.8],
* [114.8, 29.8],
* [114.8, 28.2],
* [112.2, 28.2]
* ]
* ]
* ]
* })
* })
* ]
* // 初始化几何图层
* const graphicsLayer = new GraphicsLayer({
* graphics:feature
* })
* map.add(this.graphicsLayer)
* const result = this.mapView.hitTest({x:1100,y:600})
* */
hitTest(screenPoint) {
let hitTestResult = []
if (this.preferCanvas) {
hitTestResult = this._hitTestCanvas(screenPoint)
} else {
hitTestResult = this._hitTestSVG(screenPoint)
}
return hitTestResult
}
/**
* SVG渲染方式下图元拾取
* @private
* @param {Object} screenPoint 屏幕像素坐标点,例如{ x: 900, y: 500 }
* @return {Array} SVG渲染方式下图元检测结果
* */
_hitTestSVG(screenPoint) {
const self = this
const pickedInnerLayer = []
const clientRect = this._innerView._container.getBoundingClientRect()
const elements = document.elementsFromPoint(
clientRect.x + screenPoint.x,
clientRect.y + screenPoint.y
)
elements.forEach((el) => {
if (el.classList.contains('leaflet-interactive') && el._leaflet_id) {
pickedInnerLayer.push(self._innerView._targets[el._leaflet_id])
}
})
const hitTestResult = []
pickedInnerLayer.forEach((innerLayer) => {
const layer = this.getLayer(innerLayer.commonLayerId)
let features = null
if (layer.type === LayerType.igsFeature) {
const layerView = this._getLayerView(layer)
features = layerView._featureSetCache.filter((item) => {
if (item.id === innerLayer.commonFeatureId) {
return item
}
})
} else if (layer.type === LayerType.graphics) {
features = layer.graphics.filter((item) => {
if (item.id === innerLayer.commonFeatureId) {
return item
}
})
}
const feature =
features && features.items.length > 0 ? features.items[0] : null
const graphicHit = {
graphic: feature,
layer,
mapPoint: this.toMap(screenPoint),
type: 'graphic'
}
hitTestResult.push(graphicHit)
})
return hitTestResult
}
/**
* Canvas渲染方式下图元拾取
* @private
* @param {Object} screenPoint 屏幕像素坐标点,例如{ x: 900, y: 500 }
* @return {Array} Canvas渲染方式下图元检测结果
* */
_hitTestCanvas(screenPoint) {
const pickedInnerLayer = []
const renderer = this._innerView._renderer
const e = { clientX: screenPoint.x, clientY: screenPoint.y }
const point = renderer._map.mouseEventToContainerPoint(e)
for (let order = renderer._dragFirst; order; order = order.next) {
const layer = order.layer
if (layer.options.interactive && layer._containsPoint(point)) {
if (
!(e.type === 'click' || e.type === 'preclick') ||
!renderer._map._draggableMoved(layer)
) {
if (layer.feature) {
pickedInnerLayer.push(layer)
}
}
}
}
const hitTestResult = []
pickedInnerLayer.forEach((innerLayer) => {
const layer = this.getLayer(innerLayer.commonLayerId)
let features = null
if (layer.type === 'igs-feature') {
const layerView = this.getLayerView(layer)
features = layerView._featureSetCache.filter((item) => {
if (item.id === innerLayer.commonFeatureId) {
return item
}
})
} else {
features = layer.graphics.filter((item) => {
if (item.id === innerLayer.commonFeatureId) {
return item
}
})
}
const feature = features.items.length > 0 ? features.items[0] : null
const graphicHit = {
graphic: feature,
layer,
mapPoint: this.toMap(screenPoint),
type: 'graphic'
}
hitTestResult.push(graphicHit)
})
return hitTestResult
}
/**
* 初始化地图视图比例尺
* @private
* */
_initScale() {
if (this._scale) {
if (this.minScale && this._scale > this.minScale) {
this.setScale()
}
if (this.maxScale && this._scale < this.maxScale) {
this.setScale()
}
}
}
_getScaleZoom(value) {
let zoom = undefined
let scale = undefined
if (this.crs && this.crs.options && this.crs.options.lods) {
for (let i = 0; i < this.crs.options.lods.length; i++) {
const item = this.crs.options.lods[i]
const lastItem = this.crs.options.lods[i - 1]
if (i === 0) {
if (value >= item.scale) {
zoom = item.level
scale = item.scale
break
}
} else if (i === this.crs.options.lods.length - 1) {
if (value <= item.scale) {
zoom = item.level
scale = item.scale
break
}
} else if (value >= item.scale && value <= lastItem.scale) {
zoom = item.level
scale = item.scale
break
}
}
}
return { zoom, scale }
}
/**
* 地图比例尺转视野范围
* @private
* @param {Number} scale 比例尺
* @return {Extent} 视野范围
* */
_getScaleExtent(scale) {
const extent = this._getScaleDefaultExtent(scale)
return extent
}
/**
* 地图比例尺转默认坐标系的视野范围
* @private
* @param {Number} scale 比例尺
* @return {Extent} 视野范围
* */
_getScaleDefaultExtent(scale) {
const ppi = 96 // 1 inch=2.54 厘米(cm)
const size = this.getSize()
const lengthX = (size.x / ppi) * 0.0254 * scale // 米
const lengthY = (size.y / ppi) * 0.0254 * scale
const length = Math.sqrt(lengthX * lengthX + lengthY * lengthY)
const absAngle = (180 / Math.PI) * Math.atan2(lengthX, lengthY)
const center = this._center.coordinates
const getTargetPoint = (point, distance, bearing) => {
point = T.point(point)
const options = { units: 'kilometers' }
const destination = T.destination(point, distance, bearing, options)
return destination.geometry.coordinates
}
const leftTopPoint = getTargetPoint(center, length / 2000, -absAngle)
const rightBottomPoint = getTargetPoint(
center,
length / 2000,
180 - absAngle
)
const extent = new Extent({
xmin: leftTopPoint[0],
xmax: rightBottomPoint[0],
ymin: rightBottomPoint[1],
ymax: leftTopPoint[1]
})
return extent
}
/**
* <a id='getScale'></a>
* 获取当前比例尺
* @return {Number} 比例尺 实际10000米:地图1米
* */
getScale0() {
const ppi = 96 // 1 inch=2.54 厘米(cm)
const size = this.getSize()
const screenX = (size.x / ppi) * 0.0254
const screenY = (size.y / ppi) * 0.0254
const length = Math.sqrt(screenX * screenX + screenY * screenY)
const latLngBounds = this._innerView.getBounds()
const southWestPoint = T.point([
latLngBounds._southWest.lng,
latLngBounds._southWest.lat
])
const northEastPoint = T.point([
latLngBounds._northEast.lng,
latLngBounds._northEast.lat
])
const options = { units: 'kilometers' }
const distance = T.distance(southWestPoint, northEastPoint, options)
const scale = (distance * 1000) / length
this._scale = scale
return scale
}
getScale() {
let scale = 0
if (this.crs && this.crs.options && this.crs.options.lods) {
if (!isNull(this._zoom)) {
const lod = this.crs.options.lods[this._zoom]
scale = lod.scale
}
}
this._scale = scale
return scale
}
/**
* 通过视野范围参数 获取当前比例尺
* @param {Extent} extent 比例尺
* @return {Number} 比例尺 实际10000米:地图1米
* @private
* */
_getScaleByExtent(extent) {
const ppi = 96 // 1 inch=2.54 厘米(cm)
const size = this.getSize()
const screenX = (size.x / ppi) * 0.0254
const screenY = (size.y / ppi) * 0.0254
const length = Math.sqrt(screenX * screenX + screenY * screenY)
const latLngBounds = this._getLatLngFromExtent(extent)
const southWestPoint = T.point([
latLngBounds.southWestLatlng.lng,
latLngBounds.southWestLatlng.lat
])
const northEastPoint = T.point([
latLngBounds.northEastLatlng.lng,
latLngBounds.northEastLatlng.lat
])
const options = { units: 'kilometers' }
const distance = T.distance(southWestPoint, northEastPoint, options)
const scale = (distance * 1000) / length
this._scale = scale
return scale
}
/**
* <a id='setScale'></a>
* 设置当前比例尺
* @private
* @param {Number} value 比例尺 实际10000米:地图1米
* */
setScale(value) {
if (!this._innerView) return
const result = this._getScaleZoom(value)
// 视图定位
if (result.zoom) {
this.flyTo({ center: this._center, zoom: result.zoom + 1 })
}
}
/**
* 获取视图范围的比例尺
* @private
* @param {Extent} extent 范围对象
* @return {Object} 比例尺
* */
getExtentScale(extent) {
const ppi = 96 // 1 inch=2.54 厘米(cm)
const size = this.getSize()
const screenX = (size.x / ppi) * 0.0254
const screenY = (size.y / ppi) * 0.0254
const length = Math.sqrt(screenX * screenX + screenY * screenY)
const leftTopPoint = T.point([extent.xmin, extent.ymax])
const rightBottomPoint = T.point([extent.xmax, extent.ymin])
const options = { units: 'kilometers' }
const distance = T.distance(leftTopPoint, rightBottomPoint, options)
const scale = (distance * 1000) / length
this._scale = scale
return scale
}
getResolutionByScale(scale) {
const ppi = 96
this._resolution = (0.0254 / ppi) * scale
return this._resolution
}
getResolution() {
const size = this.getSize()
const length = Math.sqrt(size.x * size.x + size.y * size.y)
const latLngBounds = this._innerView.getBounds()
const southWestPoint = T.point([
latLngBounds._southWest.lng,
latLngBounds._southWest.lat
])
const northEastPoint = T.point([
latLngBounds._northEast.lng,
latLngBounds._northEast.lat
])
const options = { units: 'kilometers' }
const distance = T.distance(southWestPoint, northEastPoint, options)
this._resolution = (distance * 1000) / length
return this._resolution
}
}
/**
* 通过一个配置生成一个场景视图对象
* @param {Object} json 场景视图配置
* @return {MapView}
* */
MapView.fromJSON = function (json) {
json = defaultValue(json, {})
return new MapView(json)
}
Object.defineProperties(MapView.prototype, {
scale: {
get() {
return this._scale
},
set(value) {
this.setScale(value)
}
},
zoom: {
get() {
return this._zoom
},
set(value) {
this.setZoom(value)
}
},
extent: {
get() {
return this._extent
},
set(value) {
this.setExtent(value)
}
},
center: {
get() {
let center = this._center
if (this.crs) {
const spatialReference = new SpatialReference(this.crs.code)
if (!spatialReference.isGeographic) {
center = Projection.project(this._center, spatialReference)
}
}
return center
},
set(value) {
this.setCenter(value)
}
},
spatialReference: {
get() {
return this._spatialReference
},
set(value) {
this._spatialReference = value
}
},
rotation: {
get() {
return this._rotation
},
set(value) {
this._rotation = value
if (this._innerView) {
this._innerView.setBearing(value)
}
}
}
})
Zondy.PluginVersion = '16.5.2'
Zondy.MapView = MapView
export default MapView