UNPKG

@mapgis/webclient-leaflet-plugin

Version:

646 lines (552 loc) 23.6 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title> document/support/mapbox/leafletMapbox.js</title> <!--[if lt IE 9]> <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script> <![endif]--> <link type="text/css" rel="stylesheet" href="styles/app.min.css"> <link type="text/css" rel="stylesheet" href="styles/iframe.css"> <link type="text/css" rel="stylesheet" href=""> </head> <body class="layout small-header"> <div id="stickyNavbarOverlay"></div> <div class="top-nav"> <div class="inner"> <a id="hamburger" role="button" class="navbar-burger" aria-label="menu" aria-expanded="false"> <span aria-hidden="true"></span> <span aria-hidden="true"></span> <span aria-hidden="true"></span> </a> <div class="logo"> <h1> MapGIS Client for JavaScript API</h1> </div> <div class="menu"> <div class="navigation"> <a class="link user-link " href="/docs/cesium/index.html" > Cesium </a> <a class="link user-link " href="/docs/mapboxgl/index.html" > MapboxGL </a> <a class="link user-link " href="/docs/leaflet/index.html" > Leaflet </a> <a class="link user-link " href="/docs/openlayers/index.html" > OpenLayers </a> </div> </div> </div> </div> <div id="main"> <div class="sidebar " id="sidebarNav" > <div> <span class="mapgis-api-document-span"><a href="index.html">API文档</a></span> </div> <div class="search-wrapper"> <input id="search" type="text" placeholder="搜索文档..." class="input"> </div> <nav> <div class="category"><div style="font-weight: bold;vertical-align: middle;padding: 0.4rem 0;" class="mapgis-menu-span"><img style="width: 20px;margin-right: 6px;vertical-align: middle;" src="./styles/m.png" alt="">视图模块</div><ul class="mapgis-sidebar-menus"><li><img style="width: 20px;margin-right: 6px;vertical-align: middle;" src="./styles/c.png" alt=""><a href="DrawControl.html">DrawControl</a><span style="display: none;"><p>绘制工具</p></span></li><li><img style="width: 20px;margin-right: 6px;vertical-align: middle;" src="./styles/c.png" alt=""><a href="MapView.html">MapView</a><span style="display: none;"><p>二维场景视图(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/></p></span></li><li><img style="width: 20px;margin-right: 6px;vertical-align: middle;" src="./styles/c.png" alt=""><a href="Popup.html">Popup</a><span style="display: none;"><p>二维场景信息弹窗(leaflet引擎) 参考示例: <a href='#MapView'>[初始化二维场景视图]</a> [ES6引入方式]:<br/> import { Popup } from '@mapgis/webclient-leaflet-plugin' <br/> 自定义样式说明:<br/> zondy-popup__content 弹窗容器样式<br/> zondy-popup__tip 弹窗对话框箭头样式<br/> zondy-popup__header 弹窗头部样式<br/> zondy-popup__content 弹窗主体样式<br/> zondy-popup__footer 弹窗底部样式<br/></p></span></li><li><img style="width: 20px;margin-right: 6px;vertical-align: middle;" src="./styles/c.png" alt=""><a href="Screenshot.html">Screenshot</a><span style="display: none;"><p>屏幕打印工具</p></span></li></ul><div style="font-weight: bold;vertical-align: middle;padding: 0.4rem 0;" class="mapgis-menu-span"><img style="width: 20px;margin-right: 6px;vertical-align: middle;" src="./styles/m.png" alt="">草图编辑模块</div><ul class="mapgis-sidebar-menus"><li><img style="width: 20px;margin-right: 6px;vertical-align: middle;" src="./styles/c.png" alt=""><a href="SketchEditorLeaflet.html">SketchEditorLeaflet</a><span style="display: none;"><p>二维场景草图编辑类<br/> <br>[ES5引入方式]:<br/> const { SketchEditorLeaflet } = Zondy <br/> [ES6引入方式]:<br/> import { SketchEditorLeaflet } from &quot;@mapgis/webclient-leaflet-plugin&quot; <br/></p></span></li></ul><div style="font-weight: bold;vertical-align: middle;padding: 0.4rem 0;" class="mapgis-menu-span"><img style="width: 20px;margin-right: 6px;vertical-align: middle;" src="./styles/m.png" alt="">渲染器模块</div><ul class="mapgis-sidebar-menus"><li><img style="width: 20px;margin-right: 6px;vertical-align: middle;" src="./styles/c.png" alt=""><a href="Zondy.ThemeLayer.GraphThemeLayer.html">GraphThemeLayer</a><span style="display: none;"><p>统计专题图通过为每个要素绘制统计图表来反映其对应的专题值的大小。它可同时表示多个字段属性信息,在区域本身与各区域之间形成横向和纵向的对比。<br>统计专题图多用于具有相关数量特征的地图上,比如表示不同地区多年的粮食产量、GDP、人口等,不同时段客运量、地铁流量等。目前提供的统计图类型有:柱状图(Bar),折线图(Line),饼图(Pie),三维柱状图(Bar3D),点状图(Point),环状图(Ring)。</p></span></li></ul></div> </nav> </div> <div class="core" id="main-content-wrapper"> <div class="content"> <header id="page-title" class="page-title"> <span class="page-title-main">类名</span> <span class="page-title-sub">document/support/mapbox/leafletMapbox.js</span> </header> <section> <article> <pre class="prettyprint source linenums"><code>import * as T from '@turf/turf' /** * @description: leaflet内使用mapboxgl插件,copy from https://github.com/mapbox/mapbox-gl-leaflet * 初始版本:0.0.16 * 改写后支持接入zondy mapboxgl内核库 * @param {*} L * @param {*} mapboxgl * @return {*} */ export default function leafletMapbox(L, mapboxgl) { L.MapboxGL = L.Layer.extend({ options: { updateInterval: 32, // How much to extend the overlay view (relative to map size) // e.g. 0.1 would be 10% of map view in each direction // 内边距设置为1,在旋转时可以遮住当前屏幕范围 padding: 1, // whether or not to register the mouse and keyboard // events on the mapbox overlay interactive: false, // set the tilepane as the default pane to draw gl tiles pane: 'tilePane', // 裁剪区域 clippingArea: null, // 级数偏移 zoomOffset: 0 }, initialize(options) { L.setOptions(this, options) if (options.accessToken) { mapboxgl.accessToken = options.accessToken } // setup throttling the update event when panning this._throttledUpdate = L.Util.throttle( this._update, this.options.updateInterval, this ) }, onAdd(map) { if (!this._container) { this._initContainer() } const paneName = this.getPaneName() map.getPane(paneName).appendChild(this._container) this._initGL() this._offset = this._calcRotateOffset() // work around https://github.com/mapbox/mapbox-gl-leaflet/issues/47 if (map.options.zoomAnimation) { L.DomEvent.on( map._proxy, L.DomUtil.TRANSITION_END, this._transitionEnd, this ) } map._addZoomLimit(this) }, onRemove(map) { if (this._map._proxy &amp;&amp; this._map.options.zoomAnimation) { L.DomEvent.off( this._map._proxy, L.DomUtil.TRANSITION_END, this._transitionEnd, this ) } const paneName = this.getPaneName() map.getPane(paneName).removeChild(this._container) }, getEvents() { return { move: this._throttledUpdate, // sensibly throttle updating while panning zoomanim: this._animateZoom, // applys the zoom animation to the &lt;canvas> zoom: this._pinchZoom, // animate every zoom event for smoother pinch-zooming zoomstart: this._zoomStart, // flag starting a zoom to disable panning zoomend: this._zoomEnd, resize: this._resize } }, setClippingArea(data) { this.options.clippingArea = data this.clippingAreaBounds = null this._update() }, getClippingArea() { return this.options.clippingArea }, getMapboxMap() { return this._glMap }, getCanvas() { return this._glMap.getCanvas() }, getSize() { return this._map.getSize().multiplyBy(1 + this.options.padding * 2) }, getBounds() { const halfSize = this.getSize().multiplyBy(0.5) const center = this._map.latLngToContainerPoint(this._map.getCenter()) return L.latLngBounds( this._map.containerPointToLatLng(center.subtract(halfSize)), this._map.containerPointToLatLng(center.add(halfSize)) ) }, getContainer() { return this._container }, // returns the pane name set in options if it is a valid pane, defaults to tilePane getPaneName() { return this._map.getPane(this.options.pane) ? this.options.pane : 'tilePane' }, _initContainer() { const container = (this._container = L.DomUtil.create( 'div', 'leaflet-gl-layer' )) const size = this.getSize() container.style.width = `${size.x}px` container.style.height = `${size.y}px` const topLeft = this._calcRotateOffset() L.DomUtil.setPosition(container, topLeft) }, _initGL() { const center = this._map.getCenter() const crs = this._map.options.crs this._offsetZoom = 0 let projCRS const crsOptions = crs.options if (crsOptions) { const mapboxTileSize = crsOptions.mapboxTileSize || 256 const projCRSOptions = { resolutions: crsOptions.resolutions, origin: crsOptions.origin, tileSize: mapboxTileSize } if (mapboxgl.Proj &amp;&amp; mapboxgl.Proj.CRS) { projCRS = new mapboxgl.Proj.CRS( crs.code, crs.def || crs.code, projCRSOptions ) // projCRS getZoomOffset计算投影坐标系的偏移(针对超出投影系范围进行偏移处理) // 计算当前使用的size,因为本质上是mapbox在渲染,并没有依赖于leaflet的网格图层,我们需要知道对应leaflet层级的瓦片是否正确,如果瓦片大小是512,需要偏移下mapbox的zoom层级 // Math.floor(mapboxTileSize/256 -1 ) this._offsetZoom = projCRS.getZoomOffset() - Math.floor(mapboxTileSize / 256 - 1) - this.options.zoomOffset } else { if (crsOptions.bounds) { const max = crsOptions.bounds.max const min = crsOptions.bounds.min projCRSOptions.bounds = [min.x, min.y, max.x, max.y] } projCRS = new mapboxgl.CRS(crs.code, projCRSOptions) } } else { projCRS = crs.code } const options = L.extend({}, this.options, { container: this._container, center: [center.lng, center.lat], zoom: this._map.getZoom() - 1, attributionControl: false, crs: projCRS, minZoom: 0, renderWorldCopies: false }) if (!this._glMap) this._glMap = new mapboxgl.Map(options) else { this._glMap.setCenter(options.center) this._glMap.setZoom(options.zoom) } // allow GL base map to pan beyond min/max latitudes this._glMap.transform.latRange = null this._transformGL(this._glMap) if (this._glMap._canvas.canvas) { // older versions of mapbox-gl surfaced the canvas differently this._glMap._actualCanvas = this._glMap._canvas.canvas } else { this._glMap._actualCanvas = this._glMap._canvas } // treat child &lt;canvas> element like L.ImageOverlay const canvas = this._glMap._actualCanvas L.DomUtil.addClass(canvas, 'leaflet-image-layer') L.DomUtil.addClass(canvas, 'leaflet-zoom-animated') if (this.options.interactive) { L.DomUtil.addClass(canvas, 'leaflet-interactive') } if (this.options.className) { L.DomUtil.addClass(canvas, this.options.className) } }, /** * @description: 计算旋转实际的偏移值 * @return {*} */ _calcRotateOffset() { // 需要注意的是,mapboxgl容器实际上是以中心点作为和leaflet覆盖层关联的点 // 也就是说,mapboxgl容器和leaflet容器中心点是一致的 // 旋转时如果还是根据左上角点计算,则在旋转后会出现偏移,因此以中心点作为偏移的基准点 const offset = this._map.getSize().multiplyBy(this.options.padding) const viewHalf = this._map.getSize()._divideBy(2) const topLeft = this._map ._latLngToNewLayerPoint( this._map.getCenter(), this._map.getZoom(), this._map.getCenter() ) .subtract(viewHalf) .subtract(offset) return topLeft }, _update(e) { if (!this._map) { return } // update the offset so we can correct for it later when we zoom this._offset = this._calcRotateOffset() if (this._zooming) { return } const gl = this._glMap const size = this.getSize() const container = this._container this._transformGL(gl) const x_round = Math.round(size.x) const y_round = Math.round(size.y) if ( Math.round(gl.transform.width) !== x_round || Math.round(gl.transform.height) !== y_round ) { container.style.width = `${x_round}px` container.style.height = `${y_round}px` gl.transform.width = x_round gl.transform.height = y_round if (gl._resize !== null &amp;&amp; gl._resize !== undefined) { gl._resize() } else { gl.resize() } } else { // older versions of mapbox-gl surfaced update publicly if (gl._update !== null &amp;&amp; gl._update !== undefined) { gl._update() } else { gl.update() } } }, _transformGL(gl) { const center = this._clampCenter(this._map.getCenter()) // gl.setView([center.lat, center.lng], this._map.getZoom() - 1, 0); // calling setView directly causes sync issues because it uses requestAnimFrame const tr = gl.transform tr.center = mapboxgl.LngLat.convert([center.lng, center.lat]) // 设置不进行自动重算zoom center tr.resetZoomScale = false // transfrom zoom等级为整数,否则会出现瓦片和实际位置偏移 // offsetZoom定义为mapboxgl内部层级偏移值,非0情况下表示,由于此投影坐标系超出投影系范围,故进行偏移 tr.zoom = Math.floor(this._map.getZoom() - this._offsetZoom - 1) const currentCenter = tr.center const centerOffset = [0, 0] if ( Math.abs(currentCenter.lng - center.lng) > 10e-8 || Math.abs(currentCenter.lat - center.lat) > 10e-8 ) { const p1 = tr.coordinatePoint(tr.locationCoordinate(currentCenter)) const p2 = tr.coordinatePoint(tr.locationCoordinate(center)) centerOffset[0] = p1.x - p2.x centerOffset[1] = p1.y - p2.y } // 偏移container 需要对照mapboxgl内核库偏移 const container = this._container const topLeft = this._calcRotateOffset().add(centerOffset) // 矢量瓦片裁剪处理程序 const polygon = this.options.clippingArea if (polygon) { // 计算裁剪区缓存四至 if (!this.clippingAreaBounds) { const [x1, y1, x2, y2] = T.bbox(T.polygon(polygon)) this.clippingAreaBounds = L.latLngBounds( L.latLng(y1, x1), L.latLng(y2, x2) ) } // 先进行四至边界判断 if (this.clippingAreaBounds.intersects(this._map.getBounds())) { const clipPxs = polygon.map((coords) => { return coords.map((v) => { const point = this._map.latLngToContainerPoint( L.latLng(v[1], v[0]) ) return [point.x, point.y] }) }) const polygon1 = T.polygon(clipPxs) const size = this._map.getSize() const polygon2 = T.polygon([ [ [0, 0], [size.x, 0], [size.x, size.y], [0, size.y], [0, 0] ] ]) const intersection = T.intersect(polygon1, polygon2) if (intersection) { const coords = T.coordAll(intersection) if (coords &amp;&amp; coords.length > 3) { let clipStr = '' coords.forEach((coord) => { clipStr += `${coord[0]}px ${coord[1]}px,` }) clipStr = clipStr.substring(0, clipStr.length - 1) container.style.clipPath = `polygon(${clipStr})` } } } else { container.style.clipPath = 'none' } } else { container.style.clipPath = 'none' } L.DomUtil.setPosition(container, topLeft) }, _clampCenter(center) { if (!center) return center const lng = center.lng const lat = Math.min(Math.max(center.lat, -90), 90) return L.latLng(lat, lng) }, // update the map constantly during a pinch zoom _pinchZoom(e) { this._glMap.jumpTo({ zoom: Math.floor(this._map.getZoom() - this._offsetZoom - 1), center: this._clampCenter(this._map.getCenter()) }) }, // borrowed from L.ImageOverlay // https://github.com/Leaflet/Leaflet/blob/master/src/layer/ImageOverlay.js#L139-L144 _animateZoom(e) { const scale = this._map.getZoomScale(e.zoom) const padding = this._map .getSize() .multiplyBy(this.options.padding * scale) const viewHalf = this.getSize()._divideBy(2) // corrections for padding (scaled), adapted from // https://github.com/Leaflet/Leaflet/blob/master/src/map/Map.js#L1490-L1508 const topLeft = this._map .project(e.center, e.zoom) ._subtract(viewHalf) ._add(this._map._getMapPanePos().add(padding)) ._round() const offset = this._map .project(this._map.getBounds().getNorthWest(), e.zoom) ._subtract(topLeft) L.DomUtil.setTransform( this._glMap._actualCanvas, offset.subtract(this._offset), scale ) }, _zoomStart(e) { this._zooming = true // 处理动画效果异常问题 if (this._container) { this._container.style.visibility = 'hidden' } this._offset = this._calcRotateOffset() }, _zoomEnd() { const scale = this._map.getZoomScale(this._map.getZoom()) L.DomUtil.setTransform( this._glMap._actualCanvas, // https://github.com/mapbox/mapbox-gl-leaflet/pull/130 null, scale ) this._zooming = false this._update() // 处理动画效果异常问题 if (this._container) { this._container.style.visibility = 'visible' } }, _transitionEnd(e) { L.Util.requestAnimFrame(function () { const zoom = this._map.getZoom() const center = this._map.getCenter() const offset = this._map.latLngToContainerPoint( this._map.getBounds().getNorthWest() ) // reset the scale and offset L.DomUtil.setTransform(this._glMap._actualCanvas, offset, 1) // enable panning once the gl map is ready again this._glMap.once( 'moveend', L.Util.bind(function () { this._zoomEnd() }, this) ) // update the map position this._glMap.jumpTo({ center: this._clampCenter(center), zoom: Math.floor(zoom - this._offsetZoom - 1) }) }, this) }, _resize(e) { this._transitionEnd(e) } }) L.mapboxGL = function (options) { return new L.MapboxGL(options) } } </code></pre> </article> </section> </div> <footer class="footer"> <div class="content has-text-centered"> <p>文档生成<a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.6.11</a></p> <p class="sidebar-created-by"> <a href="http://www.smaryun.com" target="_blank">司马云</a> <span>© 2023 云生态圈</span> <a href="http://192.168.82.89:8086/#/index" target="_blank">MapGIS Client for JavaScript</a> </p> </div> </footer> </div> <div id="side-nav" class="side-nav"> <div style="margin-bottom: 10px;"> <img style="vertical-align: middle;margin-right: 10px;width: 30px;" src="./styles/anchor.png"/><a href="#page-title">构造函数</a> </div> <div style="margin-bottom: 10px;"> <img style="vertical-align: middle;margin-right: 10px;width: 30px;" src="./styles/anchor.png"/><a href="#member">成员变量</a> </div> <div style="margin-bottom: 10px;"> <img style="vertical-align: middle;margin-right: 10px;width: 30px;" src="./styles/anchor.png"/><a href="#function">方法</a> </div> <div> <img style="vertical-align: middle;margin-right: 10px;width: 30px;" src="./styles/anchor.png"/><a href="#event">事件</a> </div> </div> </div> <script src="scripts/linenumber.js"> </script> <script src="scripts/search.js"> </script> </body> </html>