目录
前言
一、Leaflet的特种标绘库
1、特种标绘对象的定义
2、Plot基类定义
3、直线箭头的设计与实现
二、在天地图中进行对象绘制
1、引入天地图资源
2、标绘对象的调用时序
3、实际调用过程
三、总结
前言
在博客中介绍过geoman标绘的具体实现,使用Leaflet GeoMan结合天地图进行自由标绘实战,基于GeoMan的标绘只能包含了常见的点、线、面、矩形的空间对象的标绘。在一些面向特种行业的应用中,我们会遇到特种标绘。比如用来表示敌我双方态势的战斗标绘,比如在一场战斗中的敌我接敌,各方的支援力量的介入,战斗的结果等等过程需要进行集中的标绘。如下图所示:
以上对象的绘制比之前的基于Geoman的点、线、面等信息的生成稍微要复杂一些。当然您使用一些开源库进行系统的构建,这样能加快开发的速度。但是如果我们想实现自由业务的封装,就需要进行相应的改造才可以,比如我们需要定义箭头的偏转角,控制斜率,生成集结地等曲面时更加的平滑等等,这些功能的实现都是需要进行自己的定制和改造的。因此我们有必要在Leaflet中实现上述的功能,同时掌握如何来进行自定义的绘制。
一般的态势标绘的类型可以包含以下几种:直箭头、细直箭头、突击方向、进攻方向、进攻方向(尾)、分队战斗行动、分队战斗行动(尾)、钳击、聚集地等。不同的图形绘制过程不一样,根据绘制算法的不同,计算过程也不尽相同。后续会依据不同的标绘对来进行绘制过程解析。本文主要以直线箭头的绘制重点讲解在Leaflet中对上述对象的封装,相关类的功能介绍等,首先介绍一个基于Leaflet的标绘基础库,其次介绍这个库的基本结构,相关类属性和方法的定义,然后基于时序图来介绍相关API的调用,最后生成一个直线箭头的实例。如果对标绘感兴趣的朋友,可以不妨看过来。
一、Leaflet的特种标绘库
目前基于Leaflet的特种标绘库开源的不多,但是有一个基础的开源库可以提供标绘对象的生成服务,leaflet_plot,大家可以下载相关的源代码进行学习。它的开源代码主要包含两个文件,一个是Plots.js和PlotUtil.js,其中PlotUtil.js主要用于相关坐标点的绘制,Plots.js主要用于绘制和展示不同的对象。
1、特种标绘对象的定义
首先,我们来看一下Plots.js,在这个类中,定义了所有的标绘对象,由于对象种类较多,这里仅以直线箭头为例,重点讲解标绘类的设计与实现,其它的对象绘制过程比较复杂,后续再慢慢进行讲解。Plots.js主要包含类型定义、对象工厂的创建、具体对象实现的创建。下面将详细介绍。
在这里,通过javascript代码定义出所有的对象类型,具体有下列几种:
序号 | 类型 | 说明 |
1 | STRAIGHT_ARROW | "straightarrow",//直箭头 |
2 | ASSAULT_DIRECTION | "assaultdirection",//突击方向 |
3 | ATTACK_ARROW | "attackarrow",//进攻方向 |
4 | TAILED_ATTACK_ARROW | "tailedattackarrow",//进攻方向(尾) |
5 | SQUAD_COMBAT | "squadcombat",//分队战斗行动 |
6 | TAILED_SQUAD_COMBAT | "tailedsquadcombat",//分队战斗行动(尾) |
7 | FINE_ARROW | "finearrow",//细直箭头 |
8 | DOUBLE_ARROW | "doublearrow", //钳击 |
9 | GATHERING_PLACE | "gatheringplace",//聚集地 |
2、Plot基类定义
按照后端面向对象的方式将对象的公共父类抽象出来,形成公共的基类。用于基础属性的设置与渲染。其它对象可以在此基础之上进行扩展即可。
L.Plot = {
isPlot: function () {
return true;
},
getBaseType: function () {
let geojson = this.toGeoJSON()
let type = geojson.geometry.type
if (type == 'MultiLineString' || type == 'LineString') {
type = 'Polyline'
}
return type
},
//设置绘制图形需要的点
setPoints: function (latlngs) {
this._bounds = new L.LatLngBounds();
this._setLatLngs([])
this._points = this._convertLatLngs(latlngs) || [];
this._proPoints = L.PlotUtils.proPoints(this._points);
if (this.getPointCount() >= 1)
this.generate();
},
//设置投影点并更新对应的坐标点
setProPoints: function (proPts) {
var latlngs = L.PlotUtils.unProPoints(proPts);
this.setPoints(latlngs);
},
//获取控制点坐标
getCtrlPoints: function() {
let ctrlPts = []
switch (this.type) {
case L.PlotTypes.SECTOR:
case L.PlotTypes.ARC:
ctrlPts = this.getPoints();
if (ctrlPts.length < 3) {
ctrlPts = this._ctrlPnts;
}
break;
case L.PlotTypes.CIRCLE:
ctrlPts = this.getPoints();
if (ctrlPts.length < 2) {
ctrlPts = this._ctrlPnts;
}
break;
default:
ctrlPts = this.getPoints();
}
return ctrlPts;
},
//获取输入点
getPoints: function () {
return this._points;
},
//获取输入点对应的投影点
getProPoints: function () {
return this._proPoints;
},
//获取输入的点个数
getPointCount: function () {
return this._proPoints.length || 0;
},
toPlotJSON: function () {
let setting = {
type: this.type,
points: this.getPoints(),
options: this.options,
}
return setting
},
//结束绘制
finishDrawing: function () {
}
}
Plos类的方法列表如下:
序号 | 方法 | 说明 |
1 | isPlot | 是否特殊标绘,默认true |
2 | getBaseType | 获取基础类型,如:Polyline |
3 | setPoints | 设置绘制图形需要的点 |
4 | setProPoints | 设置投影点并更新对应的坐标点 |
5 | getCtrlPoints | 获取控制点坐标 |
6 | getPoints | 获取输入点 |
7 | getProPoints | 获取输入点对应的投影点 |
8 | getPointCount | 获取输入的点个数 |
9 | toPlotJSON | 转成标绘json |
10 | finishDrawing | 标绘结束事件 |
3、直线箭头的设计与实现
在特种标绘的工厂方法中,有生成直线箭头的方法,为简单起见,将其它的方法进行隐藏说明,关键代码如下所示:。
L.PlotFactory = {};
L.PlotFactory.createPlot = function (type, points, options) {
switch (type) {
case L.PlotTypes.STRAIGHT_ARROW:
return new L.Plot.StraightArrow(points, options);
case L.PlotTypes.ASSAULT_DIRECTION:
return new L.Plot.AssaultDirection(points, options);
}
return null;
}
其次我们来看下具体的直线箭头的具体实现:
/**
* 直箭头
*/
L.Plot.StraightArrow = L.Polyline.extend({
includes: L.Plot,
options: {
fixPointCount: 2,
maxArrowLength: 3000000,
arrowLengthScale: 8
},
initialize: function (latlngs, options) {
L.setOptions(this, options);
this.type = L.PlotTypes.STRAIGHT_ARROW;
this.setPoints(latlngs)
},
//生成图形
generate: function () {
if (this.getPointCount() < 2) {
return;
}
var pnts = this._proPoints;
var pnt1 = pnts[0];
var pnt2 = pnts[1];
var distance = L.PlotUtils.distance(pnt1, pnt2);
var len = distance / this.options.arrowLengthScale;
len = len > this.options.maxArrowLength ? this.options.maxArrowLength : len;
var leftPnt = L.PlotUtils.getThirdPoint(pnt1, pnt2, Math.PI / 8, len, false);
var rightPnt = L.PlotUtils.getThirdPoint(pnt1, pnt2, Math.PI / 8, len, true);
let proPts = [pnt1, pnt2, leftPnt, pnt2, rightPnt]
this._setLatLngs(L.PlotUtils.unProPoints(proPts));
this.redraw();
}
});
L.Plot.straightArrow = function (latlngs, options) {
return new L.Plot.StraightArrow(latlngs, options);
};
通过代码大家可以看到,在StraightArrow这个类中,其是通过继承Polyline来实现对象的扩展的,在extends扩展实现中,扩展了两个属性和两个方法。
序号 | 参数 | 说明 |
1 | includes: L.Plot, | 表示基类包含Plot对象 |
2 | options | 当前对象扩展属性,包含对象独有的箭头倾角等 |
3 | initialize | 初始化方法 |
4 | generate | 最主要的图形绘制,包括各种角度的计算 |
以上就是特种标绘对象的方法和属性的具体介绍。下面将结合天地来重点讲解如何进行标绘对象的静态标绘,即根据指定坐标来动态绘制。
二、在天地图中进行对象绘制
这里将重点讲解如何在天地图中进行特种对象的标绘。通过具体代码的实现和调用过程的重现,让大家对如何进行标绘有更详细的讲解。
1、引入天地图资源
在地图的展示界面中,引入天地图的在线图源非常重要,在加载影像注记的时候,需要请大家注意替换相应的参数。关键代码如下:
<!DOCTYPE >
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Leaflet plot绘制</title>
<link rel="stylesheet" href="./lib/leaflet.css"/>
<style>
.hello {
position: relative;
}
#mapContainer {
width: 100%;
height: 600px;
background-color: #eee;
z-index: 0;
}
#toolbar {
position: absolute;
left: 10px;
bottom: 10px;
list-style: none;
margin: 0;
padding: 10px;
box-shadow: 2px 2px 5px;
background-color: rgb(34 34 34 / 50%);
border-radius: 0 5px 5px 0;
}
#toolbar li {
line-height: 28px;
}
</style>
</head>
<body>
<div class="hello">
<h1>Leaflet的plot标绘</h1>
<div id="mapContainer"></div>
<ul id="toolbar">
<li><button onclick="addStraightArrow();">直箭头</button></li>
<hr>
<li><button onclick="clearPlots();">清空</button></li>
</ul>
</div>
<script src="./lib/leaflet.js"></script>
<!-- // 计算工具 -->
<script src="./lib/PlotUtil.js"></script>
<!-- // 绘制工具 -->
<script src="./lib/Plots.js"></script>
<script>
var tdt_client_key = "xxx";//天地图客户端的key
//影像底图
const tiles = L.tileLayer('http://t0.tianditu.gov.cn/img_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=img&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=' + tdt_client_key, {
maxZoom: 18,
attribution:
'© <a href="https://www.tianditu.gov.cn/">在线图源使用国家天地图</a> contributors',
});
var plotLayer;
var map = L.map('mapContainer').setView([28.170086, 112.957993], 13).addLayer(tiles);
//影像注记
const label_tiles = L.tileLayer('http://t0.tianditu.gov.cn/cia_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cia&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=' + tdt_client_key, {
maxZoom: 18
});
map.addLayer(label_tiles);
plotLayer = L.featureGroup().addTo(map);
function clearPlots() {
this.plotLayer && this.plotLayer.clearLayers()
}
// 直箭头
function addStraightArrow() {
L.Plot.straightArrow([[28.17629, 112.923746],[28.188471, 112.948208]])
.addTo(this.plotLayer);
}
</script>
</body>
</html>
这里需要注意的是,在引入影像注记的时候,需要根据天地图的规范进行相应图层的替换,这里需要注意的点就是:
http://t0.tianditu.gov.cn/cia_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cia&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=
需要注意上述字符串中的cia_w和后面的cia,cia表示图层的名字,这里一定注意,否则地图不能正确加载。
2、标绘对象的调用时序
这里我们使用时序图来进行调用时序的讲解,让大家对标绘对象的绘制和加载过程有更深入的讲解,以下是简单的时序调用视图。
3、实际调用过程
这里以静态标绘为例,未来需要根据绘制事件来进行绘制坐标的获取来进行对象的生成。
绘制对象的关键点生成,这个距离在后面的倾角的计算有很大的作用。
第三点(即倾角坐标点的计算)
最终得到的空间对象如下所示:
最后在在上面的结果中对标注关键参数进行说明。
通过以上四点的计算即完成直线箭头的绘制,将绘制的对象添加到地图即可。
三、总结
以上就是本文的主要内容,本文主要以直线箭头的绘制重点讲解在Leaflet中对上述对象的封装,相关类的功能介绍等,首先介绍一个基于Leaflet的标绘基础库,其次介绍这个库的基本结构,相关类属性和方法的定义,然后基于时序图来介绍相关API的调用,最后生成一个直线箭头的实例。行文仓促,定有不足之处,还请各位专家博友不吝指教,不胜感激。
参考资料:
1、基于Leaflet实现标绘——直箭头。