本文最后更新于:2021年9月9日 晚上
KML 中圆形的实现
原先我以为,KML 应该是有一些什么方法或者标记,可以让我们通过设置一个圆形和半径来画出一个园
但是当我在 Google Earth 随便画了一个圆,导出成 kml ,并用 vs code 打开之后发现,并不是这样的。在 KML 里面,圆形定义如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <LineString> <tessellate>1</tessellate> <coordinates> 113.10147288586933,22.714294933272082,0 113.10164312458863,22.71428807687225,0 113.10181206768848,22.714267559854086,0 113.10197842940988,22.714233538364496,0 113.1021409436397,22.714186271327552,0 113.10229837354649,22.714126118473953,0 113.1024495209936,22.714053537603228,0 113.10259323565772,22.71396908109963,0 113.10272842378347,22.713873391728153,0 113.10285405650757,22.71376719774269,0...... </coordinates> </LineString>
|
在关键的 coordinates 里面,有 70 多个坐标点
所以事实上,我们在 Google Earth 里面看到的圆,是一个多边形
那我们要怎么画多边形呢
这个问题可以简化成,我们要怎么求出来相对于某一个点,特定方向特定距离的另一个点。
假如我们可以求出来距离某个经纬度 20 m,且与点所在水平线方向角为 0°, 60°,120°,180°,240°,300°,360° 的点,那么就可以围绕这个经纬度的位置画一个六边形了
而这个需求可以使用以下的代码来实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
function getLongLat(distance, longitude, latitude, angle) { let newLng = longitude + (distance * Math.sin(angle * Math.PI / 180)) / (111 * Math.cos(latitude * Math.PI / 180)); let newLat = latitude + (distance * Math.cos(angle * Math.PI / 180)) / 111; return { "lat": newLat, "lng": newLng, }; }
|
当我们调用以上函数 6 次,就可以得到 6 个经纬度了
而当我们调用次数越多,形成的多边形就越趋近于圆
我这里取了一个 72 次,相当于 72 边形,每 5° 就画一个点
KML 模板
有了这一堆的经纬度之后,我们要基于这一堆的经纬度来生成一个 kml 文件,下面这份是我自己从 Google Earth 自己绘制的图形导出得到的 kml 模板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| <?xml version="1.0" encoding="UTF-8"?> <kml xmlns="http://www.opengis.net/kml/2.2" xmlns:gx="http://www.google.com/kml/ext/2.2" xmlns:kml="http://www.opengis.net/kml/2.2" xmlns:atom="http://www.w3.org/2005/Atom"> <Document> <name>Test.kml</name> <Style id="inline"> <LineStyle> <color>ff0000ff</color> <width>5</width> </LineStyle> </Style> <StyleMap id="inline0"> <Pair> <key>normal</key> <styleUrl>#inline</styleUrl> </Pair> <Pair> <key>highlight</key> <styleUrl>#inline1</styleUrl> </Pair> </StyleMap> <Style id="inline1"> <LineStyle> <color>ff0000ff</color> <width>5</width> </LineStyle> </Style> <Folder id="{fenceName}"> <name>围栏 {fenceName}</name> <Snippet></Snippet> <description></description> <Placemark> <name>{fenceName}_point</name> <Snippet></Snippet> <description> {fenceLog} </description> <Style> <IconStyle> <color> FFFFFFFF </color> </IconStyle> <LabelStyle> <color> FFFFFFFF </color> </LabelStyle> </Style> <Point> <altitudeMode>clampToGround</altitudeMode> <coordinates> {fenceCenterLngLat} </coordinates> </Point> </Placemark> <Placemark> <name>{fenceName}_cycle</name> <styleUrl>#inline0</styleUrl> <LineString> <tessellate>1</tessellate> <coordinates> {fenceCycleList} </coordinates> </LineString> </Placemark> </Folder> </Document> </kml>
|
xml 里面,我用 Folder 来括组一组围栏的信息,其中包含了一个多边形(其实就是圆)以及点(圆心)
当我们需要绘制多个围栏的时候,只需要在 Document 标签里面多加几个 Folder 就可以了
模板文件里面有一些待填充的数据,其含义如下
{fenceName} 围栏的名字:随意字符串
{fenceLog} 围栏的描述,会显示在围栏中心点的描述位置
{fenceCenterLngLat} 经纬度:”longute, latute”, 比如 113.1100, 22.2200
{fenceCycleList} 组成圆(多边形)的点的经纬度每一组由 “纬度, 经度, 高度” 组成,每组之间用空格隔开
如 113.90767872659711,22.517658949676875,0 113.9078474283604,22.51763843265871,0 113.90801355243275,22.51760441116912,0
最终的 JS 代码
最终实现的 JavaScript 代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
| const fenceKmlModel = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + "<kml xmlns=\"http://www.opengis.net/kml/2.2\" xmlns:gx=\"http://www.google.com/kml/ext/2.2\" xmlns:kml=\"http://www.opengis.net/kml/2.2\" xmlns:atom=\"http://www.w3.org/2005/Atom\">\n" + "<Document>\n" + " <name>FencesExport</name>\n" + " <Style id=\"inline\">\n" + " <LineStyle>\n" + " <color>ff0000ff</color>\n" + " <width>5</width>\n" + " </LineStyle>\n" + " </Style>\n" + " <StyleMap id=\"inline0\">\n" + " <Pair>\n" + " <key>normal</key>\n" + " <styleUrl>#inline</styleUrl>\n" + " </Pair>\n" + " <Pair>\n" + " <key>highlight</key>\n" + " <styleUrl>#inline1</styleUrl>\n" + " </Pair>\n" + " </StyleMap>\n" + " <Style id=\"inline1\">\n" + " <LineStyle>\n" + " <color>ff0000ff</color>\n" + " <width>5</width>\n" + " </LineStyle>\n" + " </Style>\n" + " \n" + " {folderList}\n" + "</Document>\n" + "</kml>" let fenceFolderModel = " <Folder id=\"{fenceName}\">\n" + " <name>围栏 {fenceName}</name>\n" + " <Snippet></Snippet>\n" + " <description></description>\n" + " <Placemark>\n" + " <name>{fenceName}_point</name>\n" + " <Snippet></Snippet>\n" + " <description> {fenceLog} </description>\n" + " <Style>\n" + " <IconStyle>\n" + " <color> FFFFFFFF </color>\n" + " </IconStyle>\n" + " <LabelStyle>\n" + " <color> FFFFFFFF </color>\n" + " </LabelStyle>\n" + " </Style>\n" + " <Point>\n" + " <altitudeMode>clampToGround</altitudeMode>\n" + " <coordinates> {fenceCenterLngLat} </coordinates>\n" + " </Point>\n" + " </Placemark>\n" + " <Placemark>\n" + " <name>{fenceName}_cycle</name>\n" + " <styleUrl>#inline0</styleUrl>\n" + " <LineString>\n" + " <tessellate>1</tessellate>\n" + " <coordinates>\n" + " {fenceCycleList}\n" + " </coordinates>\n" + " </LineString>\n" + " </Placemark>\n" + " </Folder>" if(String.prototype.replaceAll == undefined) { console.log("注入 replaceAll 方法") String.prototype.replaceAll = function(s1, s2) { return this.replace(new RegExp(s1, "gm"), s2); } }
function getLongLat(distance, longitude, latitude, angle) { let newLng = longitude + (distance * Math.sin(angle * Math.PI / 180)) / (111 * Math.cos(latitude * Math.PI / 180)); let newLat = latitude + (distance * Math.cos(angle * Math.PI / 180)) / 111; return { "lat": newLat, "lng": newLng, }; }
const cyclePointCount = 72;
function parseFenceToKml(fenceList) { let folderList = ""; for (let i =0;i<fenceList.length;i++){ let fence = fenceList[i]; let folderNow = fenceFolderModel; folderNow = folderNow.replaceAll("{fenceName}", fence.id) folderNow = folderNow.replace("{fenceCenterLngLat}", "" + fence.lng + "," + fence.lat) folderNow = folderNow.replace("{fenceLog}", fence.log) let fenceCycleList = ""; for(let degree = 0.0;degree<=360;degree+= (360/cyclePointCount) ){ let temp = getLongLat(1.0 * fence.radius / 1000, fence.lng, fence.lat, degree) fenceCycleList += temp.lng+","+temp.lat+",0 \n" } folderNow = folderNow.replace("{fenceCycleList}", fenceCycleList) folderList += folderNow; } let resKml = fenceKmlModel; resKml = resKml.replace("{folderList}", folderList); return resKml; }
let kmlResult = parseFenceToKml([ { id: "Fence_ID_1", lat: 22.712493131470283, lng: 113.10147288586933, radius: 200, log: "围栏备注", } ])
|