最近项目上遇到一个专题树,遇到打开专题的时间轴后,以前打开的图层会被关闭的bug过程是这样的,先打开增城区规划导则地块
,而后再打开上面含有多个年份的历史图层,规划导则地块
,这个时候增城区规划导则地块
的图层会被关闭。web
通过代码定位和按行注释,发现是这段代码针对图层的关闭和显示有问题:json
// 处理默认年份的显示隐藏
var layer = this.defaultLayer;
if (visible) {
`layer.setVisibleLayers && layer.setVisibleLayers([layerobj.layerindex])`;
layer.setVisibility(true);
} else {
// 关闭
`layer.setVisibleLayers && layer.setVisibleLayers([]);`
layer.setVisibility(false);
}
复制代码
为何当前的 layer
会把其余的图层关闭了呢?下面先理解几个概念。api
本文主要讲述如下几点内容:缓存
图层是 ArcMap、ArcGlobe 和 ArcScene 等Arcgis 产品套件中地理数据集的显示机制。一个图层引用一个数据集,并指定如何利用符号和文本标注绘制该数据集。向地图添加图层时,要指定它的引用数据集并设定地图符号和标注属性。bash
包含一个地图控件的每一个应用程序是经过一系列图层组装的。显示以特定的顺序显示在地图上,列在最底部的显示在地图的最上面显示,也就是先添加的显示在下面显示(原理相似于“栈”)运维
全部的图层都是从Layer类型继承而来的,能够参考下载的API中的对象模型图。测试
Layer
|–TiledMapServiceLayer
|----|–ArcGISTiledMapServiceLayer
|–DynamicLayer
|----|–DynamicMapServiceLayer
|----------|–ArcGISDynamicMapServiceLayer
|----------|–ArcGISImageServiceLayer
|----------|–GPResultImageLayer
|–GraphicsLayer
|----|–FeatureLayer
|–ElementLayer
复制代码
而图层是怎么加载出来的呢,它是经过地图服务加载出来的。优化
地图服务是一种利用 ArcGIS 使地图可经过 web 进行访问的方法。咱们首先在 ArcMap 中制做地图,而后将地图发布到 ArcGIS Server 站点上。当地图服务发布成功后,咱们能够经过网址(xxxx/arcgis/rest/services)来查看地图服务所支持的操做,地图服务所包含的数据,以及咱们还能够经过网址来测试地图服务的功能。ui
以后在Web 应用程序、ArcGIS for Desktop、ArcGIS Online 以及其余客户端应用程序中请求该地址使用此地图服务this
下面说说常见的两种图层加载模式,实例化一个图层对象,须要传入图层的 url
。
原理:切片服务是已经经过比例尺切好地图了,如一般的底图,通常是切片服务加载的,当你经过鼠标放大底图,它会根据当前的比例尺来加载已经切好的图片,加载的方式是经过
export
接口请求已经切好的图片。
因为切片服务已经切好了,因此没法经过相似setVisibleLayers
来控制它的图层显示,只能经过setVisiblity
控制整个图层的显示。导出图片时,export
会把整个当前比例的切片导出来。
一个发布出来的切片以下:
经过ArcGISTiledMapServiceLayer
新建一个切片类实例,而后加载到地图中
var layer = new ArcGISTiledMapServiceLayer(layerobj.url);
map.addLayer(layer);
复制代码
控制切片图层的显示
// 设置图层显示/隐藏
layerVisibleRefreshByName: function(obj) {
if (obj == null) return;
var serviceid = obj.serviceid;
var layername = obj.layername;
var layervisible = obj.layervisible;
// 若是 serviceid 不存在时,运维赋值为 label 名称
if (serviceid == this.guid || serviceid == this.label) {
this.setVisibility(layervisible);
}
}
复制代码
固然,若是切片服务在发布时,勾选了可使用动态服务加载的,那么切片服务也能够经过ArcGISDynamicMapServiceLayer
来加载。
原理:
一个动态服务的信息以下:
经过ArcGISDynamicMapServiceLayer
新建一个动态类实例,而后加载到地图中
var layer = new ArcGISDynamicMapServiceLayer(layerobj.url);
map.addLayer(layer);
复制代码
在咱们执行setVisibleLayers
时,会经过 export
方式来把对应的子图层输出图片,而后加载到地图中。setVisibleLayers(-1)
关闭全部子图层。
控制动态图片的显示
/** * 改变图层的可见性 * @param {Object} dlayer 服务图层 * @param {Number} layerid 子图层 id * @param {Boolean} layervisible 可见性 */
changeLayerVisible: function (dlayer, layerid, layervisible) {
if (dlayer == null) return;
if (layerid < 0) return;
var arrc = dlayer.visibleLayers;
arrc = this.dealWithLayerInfos(arrc, dlayer.layerInfos);
if (arrc == null || (arrc.length == 1 && arrc[0] == -1)) {
arrc = [];
}
if (layervisible) {
if (!this.checkLayerId(arrc, layerid)) {
arrc.push(layerid);
}
} else {
if (this.checkLayerId(arrc, layerid)) {
arrc = this.removeLayerId(arrc, layerid);
}
}
this.setVisibleLayers(arrc, true);
},
checkLayerId: function (arrc, layerid) {
if (arrc == null) return false;
for (var i = 0; i < arrc.length; i++) {
if (arrc[i] == layerid) {
return true;
}
}
return false;
},
复制代码
说到历史时间轴,首先要理解专题树里面的节点信息。
专题树由专题组成,每个叶子节点都是一个专题,那么专题是什么呢?
专题:专题也就是一个图层服务,每个专题layer
都是系统初始化时,经过动态服务或切片服务实例化后添加到地图中的, 每一个专题图层,都有一个图层组 layers
,这个是该专题服务下的默认显示的子图层集合,因此打开或关闭专题时,若是子图层是经过动态服务加载的,也就是关闭对应的子图层。若是是切片服务的,则是关闭整个服务对象。
关于历史时间轴的逻辑是这样的
{
"type": "规划导则地块",
"layers": [
{
"label": "2019",
"layertype": "dynamic",
"layername": "规划导则地块",
"layerindex": 4,
"serviceName": "控制性规划导则",
"serviceUid": "",
"defaultLayer": true,
"url": "xxxxx/arcgis/rest/services/%E6%8E%A7%E5%88%B6%E6%80%A7%E8%A7%84%E5%88%92%E5%AF%BC%E5%88%99/MapServer/4"
},
{
"label": "2018",
"layertype": "dynamic",
"layername": "规划导则地块",
"layerindex": 4,
"serviceName": "控制性规划导则",
"serviceUid": "",
"defaultLayer": false,
"url": "xxxxx/arcgis/rest/services/%E6%8E%A7%E5%88%B6%E6%80%A7%E8%A7%84%E5%88%92%E5%AF%BC%E5%88%992018/MapServer"
},
{
"label": "2017",
"layertype": "dynamic",
"layername": "规划导则地块",
"layerindex": 4,
"serviceName": "控制性规划导则2017",
"defaultLayer": false,
"url": "xxxxx/arcgis/rest/services/%E6%8E%A7%E5%88%B6%E6%80%A7%E8%A7%84%E5%88%92%E5%AF%BC%E5%88%992017/MapServer"
},
{
"label": "2016",
"layertype": "dynamic",
"layername": "规划导则地块2016",
"layerindex": 4,
"serviceName": "控制性规划导则2016",
"defaultLayer": false,
"url": "xxxxx/arcgis/rest/services/%E6%8E%A7%E5%88%B6%E6%80%A7%E8%A7%84%E5%88%92%E5%AF%BC%E5%88%992016/MapServer"
}
]
},
复制代码
/** * _changeLayerVisible() 改变图层的显示状况 * @param {Object} layerobj 图层信息 * @param {Boolean} visible 是否可见 */
_changeLayerVisible: function(layerobj, visible) {
this.map = window._map; // 获取到地图
if (!layerobj) {
return;
}
if (layerobj.label != this.clashHisdata.label) {
// 隔离与默认年份相冲突的年份,把默认年份放在else中处理
var layerId =
this.defaultLayer.label + '_historydataservice' + layerobj.label; // 用于同时添加多个图层 多个图层会出现
topic.publish('history-layerIds', layerId); // 历史图层id传送至专题树,由专题书统一管理图层开关
this.historyLayerIds.push(layerId);
switch (layerobj.layertype) {
case 'tiled':
if (visible) {
var layer = this.map.getLayer(layerId);
if (layer) {
this.map.removeLayer(layer);
layer = new ArcGISTiledMapServiceLayer(layerobj.url);
this._currentLayer = layer;
layer.id = layerId;
this.map.addLayer(layer);
this.map.reorderLayer(layer, 1);
layer.setVisibility(true);
} else {
layer = new ArcGISTiledMapServiceLayer(layerobj.url);
this._currentLayer = layer;
layer.id = layerId;
this.map.addLayer(layer);
this.map.reorderLayer(layer, 1);
}
this._currentLayer.setOpacity(this._opacity);
} else {
var layer = this.map.getLayer(layerId);
if (layer) {
//this.map.removeLayer(layer);
layer.setVisibility(false);
}
}
break;
case 'dynamic':
if (visible) {
var layer = this.map.getLayer(layerId);
if (layer) {
if (layer.url == layerobj.url) {
layer.setVisibleLayers([layerobj.layerindex]);
this._currentLayer = layer;
layer.setVisibility(true);
} else {
this.map.removeLayer(layer);
layer = new ArcGISDynamicMapServiceLayer(layerobj.url);
this._currentLayer = layer;
layer.id = layerId;
this.map.addLayer(layer);
this.map.reorderLayer(layer, 1);
layer.setVisibleLayers([layerobj.layerindex]);
layer.setVisibility(true);
}
} else {
var dlayer = new ArcGISDynamicMapServiceLayer(layerobj.url);
this._currentLayer = dlayer;
dlayer.id = layerId;
this.map.addLayer(dlayer);
this.map.reorderLayer(dlayer, 1);
dlayer.setVisibleLayers([layerobj.layerindex]);
}
this._currentLayer.setOpacity(1);
setTimeout(
lang.hitch(this, function() {
this._currentLayer.setOpacity(this._opacity);
}),
200
);
} else {
var layer = this.map.getLayer(layerId);
if (layer) {
layer.setVisibleLayers([]);
layer.setVisibility(false);
}
}
break;
}
} else {
`// 处理默认年份的显示隐藏 var layer = this.defaultLayer; if (visible) { layer.setVisibleLayers && layer.setVisibleLayers([layerobj.layerindex])`;
layer.setVisibility(true);
} else {
// 关闭
layer.setVisibleLayers && layer.setVisibleLayers([]);
layer.setVisibility(false);
}
}
},
复制代码
Layer: 一个图层服务包含了不少子图层
这个是历史面板初始化时的操做。 时间轴打开后,经过serviceid
去获取添加到地图中的当前 layer
对象。 从专题信息中,获取到当前专题里面的图层,默认显示的子图层。 再经过它的serviceUid
获取到加载到地图中的父图层(专题)。
图层关闭由 setVisibility(isVisible)
和 setVisibleLayers(ids, doNotRefresh?)
组合控制,由切片服务生成的图层只有 setVisibility
属性,动态服务生成的图层则由二者组合控制图层的显示。如图,setVisibility
控制整个图层的显示,而 setVisibleLayers
能够更加细粒度地控制图层里面的子图层。
再看看以前的代码实现,经过断点发现,默认图层 layer
的子图层包含了增城导则地块图层
,所以在打开默认图层的某个子图层时,这行代码layer.setVisibleLayers([layerobj.layerindex])
只赋值了当前子图层的索引id,致使把以前的子图层都关闭了。
// 处理默认年份的显示隐藏
var layer = this.defaultLayer;
if (visible) {
`layer.setVisibleLayers && layer.setVisibleLayers([layerobj.layerindex])`;
layer.setVisibility(true);
} else {
// 关闭
`layer.setVisibleLayers && layer.setVisibleLayers([]);`
layer.setVisibility(false);
}
复制代码
这时候只须要添加对应的图层服务类型判断逻辑,缓存以前的就能够了。
// 处理默认年份的显示隐藏
var layer = this.defaultLayer;
```var visibleLayers = []; // 可见的图层 visibleLayers = visibleLayers.concat(layer.visibleLayers);```
if (visible) {
// 打开
var index = visibleLayers.indexOf(layerobj.layerindex);
if (index === -1 && layerobj.layertype !== 'tiled') {
visibleLayers.push(layerobj.layerindex); // 添加新的图层索引进去,不然传递 -1 会关闭全部图层
layer.setVisibleLayers && layer.setVisibleLayers(visibleLayers); // 注意区分tiled和dynamic
}
layer.setVisibility(true);
} else {
// 知足:
// 1. 须要解决时间轴后面的Layer 被默认的 Layer覆盖问题,
// 2. 可是不能把整个 Layer 关闭了,不然,会影响属于同一个图层服务实例下,其余子图层的显示。
// 3. 当前只能把后面的图层移动 z-index,可是又要知足时间轴的图层做为底图来使用。
`if (layerobj.layertype === 'tiled') { // 判断是否为切片 layer.setVisibility(false); } else { // 动态图片服务的关闭 // 关闭 var index = visibleLayers.indexOf(layerobj.layerindex); if (index > -1) { visibleLayers.splice(index, 1); } layer.setVisibleLayers && layer.setVisibleLayers(visibleLayers); }`
}
}
复制代码
固然,上面的默认图层的显示/隐藏,能够直接把图层对象传递给专题树来统一处理开关,这样就不用写这些判断逻辑了。
(全文完)
arcgis api
是通过优化的,只有地图范围改变了,才会去请求地图服务。因此在切换时间轴时,要注意改变它的范围,才能更好的定位错误。