需求描述
将一张图片叠加到ArcGIS地图上是现在很多项目的一个广泛需求,通过查阅网上资料后发现这种需求目前只有四种方法可以实现,因为ArcGIS JS API官网并没有提供相应的图片类图层来让我们实例化图片图层,但是JS API官网有说明后期会增加这类API,就是不知道是什么时间了。既然目前没有这类API图层,那我们就自己来找找其他的路子吧,刚才提到过实现将图片叠加到地图上的方法目前有四种,分别如下:
- 通过ArcGIS知乎上有人提到过的使用Graphic来实现,其实就是将图片当做一个Symbol;
- 通过类似于ArcGIS JS API 3.X中的MapImage模块来实现;
- 通过扩展MapImageLayer来实现;
- 通过JS API官网上的BaseDynamicLayer这个类来实现
接下来我们就看看各种方法到底能不能做,或者说可以做成什么效果。具体操作步骤开始介绍之前,我们先给大家透露一下的是,本文是通过第四种方法来实现的,即通过BaseDynamicLayer这个类来实现,最终效果如下:
各种方案实现分析及具体操作步骤
一、通过Graphic的形式实现
这种方式是我在网上找到的第一种实现方式,也是最简单的一种,它的原理其实就是将图片作为一个Symbol符号,因为ArcGIS JS API的符号类已经支持图片符号了,所以将图片作为一个符号,然后添加到实例化Graphic图层的构造函数中,最终将实例化后的Graphic图层添加到地图上,具体代码如下所示:
require(["esri/Map",
"esri/views/MapView",
"esri/Graphic"
], function(Map, MapView, Graphic) {
var map = new Map({
basemap: "satellite"
});
var view = new MapView({
container: "viewDiv",
map: map,
zoom: 4,
center: [15, 65]
});
var polyline = {
type: "polyline",
paths: [
[91.0761406150, 29.5803130630]
]
};
var polylineAtt = {
Name: "Keystone Pipeline",
Owner: "TransCanada"
};
var pictureSymbol = {
type: "picture-marker",
url: "./test.jpg",
width: "240px",
height: "240px",
};
var polylineGraphic = new Graphic({
geometry: polyline,
symbol: pictureSymbol,
attributes: polylineAtt
});
view.graphics.add(polylineGraphic);
});
通过以上代码实现的最终效果如下:
由以上效果可看出,这并不是我们想要的结果,虽然通过此方法我们将图片添加到了地图上,但是随着地图的缩放,图片并不会缩放,它还是保持着原来的尺寸大小。虽然可以通过监听view视图层的缩放事件来动态的调整图片的大小,但是这种做法觉得有点蠢,并且后期效果可能并不怎么样。所以这种方法被淘汰,目前暂不考虑。
二、通过类似于ArcGIS JS API 3.X中的MapImage模块来实现
在ArcGIS JS API 3.X和ArcGIS JS API 4.X中都有MapImage模块,在3.X版本中可以通过这个模块来实例化一个图片信息类,然后再将这个图片信息类通过MapImageLayer的addImage方法添加到MapImageLayer图层中,最后将MapImageLayer图层添加到地图上,这就完成了图片和地图的叠加,代码如下所示:
require(["esri/Map",
"esri/views/MapView",
"esri/layers/MapImageLayer",
"esri/layers/support/MapImage"
], function(Map, MapView, MapImageLayer, MapImage) {
var map = new Map({
basemap: "satellite"
});
var view = new MapView({
container: "viewDiv",
map: map,
zoom: 4,
center: [15, 65]
});
//实例化图层
var mapimageLayer = new MapImageLayer({
id: 'imageLayer_xuqw'
});
map.add(mapimageLayer);
var image = new MapImage({
'extent':
{
'xmin': -8864908,
'ymin': 3885443,
'xmax': -8762763,
'ymax': 3976997,
'spatialReference':
{
'wkid': 3857
}
},
'href': './test.jpg'
});
mapimageLayer.addImage(image);
});
以上代码看起来似乎是那么完美,我们只需要在MapImage类实例化的时候传入图片的范围信息和url地址就可以,但是偏偏就这么巧,ArcGIS JS API 4.X版本的MapImageLayer并没有addImage()这个方法,所以我们只能放弃这种方法。但是不死心的同学可能还要会问,既然没有addImage()这个方法,那为什么官网还要将MapImage这个类写出来呢,既然用不了的话干脆不对外公布不就行了吗,这个问题其实在GeoNet上也有官方回复了,说是因为编写失误……
三、通过扩展MapImageLayer来实现
这种思路其实蛮好的,主要是看到了徐磊大佬的文章收到了启发,文章地址如下:
https://www.jianshu.com/p/cc744f1ad6bb?utm_campaign=hugo&utm_medium=reader_share&utm_content=note&utm_source=weixin-timeline&from=timeline
感兴趣的同学可以去这篇文章里好好学习下,文章最后也是放出来了github地址和源码,写的很详细,最后我也是根据扩展的图层类测试成功了,效果如下:
以上截图中,叠加到地图上的图片是会跟随着地图的缩放进行相应的大小调整及绘制,效果还挺不错,但是考虑到目前项目中仅仅这样一个小小的需求就去做扩展图层的操作,未免有点动作幅度太大,太花心思和费精力了,感觉不值,其实就是自己懒啦,哈哈哈,所以我还想再找找其它的方法,这不,就找到了第四种方法。
四、通过JS API官网上的BaseDynamicLayer类来实现
在不懈的努力寻找下,终于找到了GIS之家的文章和BaseDynamicLayer这个类,这个类允许我们自定义扩展图层,所以我们就可以通过这个类简单的扩展一下图片叠加的图层,来实现地图上图片的叠加。其实所用原理就是通过canvas绘制技术,获取到图片的范围后将它的范围坐标信息转换为屏幕坐标,再实例化canvas句柄来绘制图片。
扩展出的叠加图片的自定义类代码如下:
//自定义叠加图片图层
var CustomImageOverlayLayer = BaseDynamicLayer.createSubclass({
properties: {
picUrl: null,
extent: null,
image: null,
canvas: null,
},
// Override the getImageUrl() method to generate URL
// to an image for a given extent, width, and height.
getImageUrl: function (extent, width, height) {
//新Image对象,可以理解为DOM
if (!this.image) {
this.image = new Image();
}
this.image.src = this.picUrl;
// 创建canvas DOM元素,并设置其宽高和图片一样
if (!this.canvas) {
this.canvas = canvas = document.createElement("canvas");
}
this.canvas.width = 2000;
this.canvas.height = 2000;
//左上角地理坐标转换屏幕坐标,为了获取canvas绘制图片的起点
var mapPoint = {
x: this.extent.xmin,
y: this.extent.ymax,
spatialReference: {
wkid: 4326
}
};
var screenPoint = view.toScreen(mapPoint);
//根据extent范围计算canvas绘制图片的宽度以及高度
//左下角
var leftbottom = {
x: this.extent.xmin,
y: this.extent.ymin,
spatialReference: {
wkid: 4326
}
};
var screen_leftbottom = view.toScreen(leftbottom);
//右上角
var righttop = {
x: this.extent.xmax,
y: this.extent.ymax,
spatialReference: {
wkid: 4326
}
};
var screen_righttop = view.toScreen(righttop);
this.canvas.getContext("2d").drawImage(this.image, screenPoint.x, screenPoint.y, Math.abs(screen_righttop.x - screen_leftbottom.x), Math.abs(screen_righttop.y - screen_leftbottom.y));
return this.canvas.toDataURL("image/png");
}
});
实例化这个类的代码如下:
var ImageOverlayLayer = new CustomImageOverlayLayer({
picUrl: "1.jpg",
extent: {
xmin: 91.0761406150,
ymin: 29.5803130630,
xmax: 92.0761406150,
ymax: 30.5803130630
}
});
map.add(ImageOverlayLayer);
最终得到的图片叠加效果如下:
通过这种方法得到的效果是我们所需要的,操作实现简单,并且最终叠加到地图上的图片也会随着地图缩放进行大小调整,所以本文最后采用这种方式来实现。
附:
第四种方法的全部源码:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
<title>地图叠加图片</title>
<link rel="stylesheet" href="http://localhost/4.14/esri/themes/light/main.css" />
<style>
html,
body,
#viewDiv {
padding: 0;
margin: 0;
height: 100%;
width: 100%;
}
</style>
<script src="http://localhost/4.14/init.js"></script>
</head>
<body>
<div id="viewDiv"></div>
<script>
require([
"esri/Map",
"esri/views/MapView",
"esri/layers/BaseDynamicLayer"
], function (Map, MapView, BaseDynamicLayer) {
//自定义叠加图片图层
var CustomImageOverlayLayer = BaseDynamicLayer.createSubclass({
properties: {
picUrl: null,
extent: null,
image: null,
canvas: null,
},
// Override the getImageUrl() method to generate URL
// to an image for a given extent, width, and height.
getImageUrl: function (extent, width, height) {
//新Image对象,可以理解为DOM
if (!this.image) {
this.image = new Image();
}
this.image.src = this.picUrl;
// 创建canvas DOM元素,并设置其宽高和图片一样
if (!this.canvas) {
this.canvas = canvas = document.createElement("canvas");
}
this.canvas.width = 2000;
this.canvas.height = 2000;
//左上角地理坐标转换屏幕坐标,为了获取canvas绘制图片的起点
var mapPoint = {
x: this.extent.xmin,
y: this.extent.ymax,
spatialReference: {
wkid: 4326
}
};
var screenPoint = view.toScreen(mapPoint);
//根据extent范围计算canvas绘制图片的宽度以及高度
//左下角
var leftbottom = {
x: this.extent.xmin,
y: this.extent.ymin,
spatialReference: {
wkid: 4326
}
};
var screen_leftbottom = view.toScreen(leftbottom);
//右上角
var righttop = {
x: this.extent.xmax,
y: this.extent.ymax,
spatialReference: {
wkid: 4326
}
};
var screen_righttop = view.toScreen(righttop);
this.canvas.getContext("2d").drawImage(this.image, screenPoint.x, screenPoint.y, Math.abs(screen_righttop.x - screen_leftbottom.x), Math.abs(screen_righttop.y - screen_leftbottom.y));
return this.canvas.toDataURL("image/png");
}
});
var map = new Map({
basemap: 'osm',
});
var view = new MapView({
container: "viewDiv",
map: map,
center: [107.246152,34.414465],
zoom: 7
});
view.when(function () {
var ImageOverlayLayer = new CustomImageOverlayLayer({
picUrl: "1.jpg",
extent: {
xmin: 91.0761406150,
ymin: 29.5803130630,
xmax: 92.0761406150,
ymax: 30.5803130630
}
});
map.add(ImageOverlayLayer);
//地图移动刷新,防止地图初始化时候,图片叠加图层加载刷新不过来
setTimeout(function () {
var center = view.center.clone();
center.x -= 0.001;//底图是经纬度
view.center = center;
view.goTo(view.center,
{
speedFactor: 0.1,
easing: "linear" //linear, in-cubic, out-cubic, in-out-cubic, in-expo, out-expo, in-out-expo
});
}, 500);
}, function (error) {
console.log("图片叠加失败: ", error);
});
});
</script>
</body>
</html>