GIS常用算法

作为一个GISer,在日常WebGIS开发中,会常用到的turf.js,这是一个地理空间分析的JAVAScript库,经常搭配各种GIS JS API使用,如leaflet、mapboxgl、openlayers等;在后台Java开发中,也有个比较强大的GIS库,geotools,里面包含构建一个完整的地理信息系统所需要的全部工具类;数据库端常用是postgis扩展,需要在postgres库中引入使用 。
然而在开发某一些业务系统的时候,有些需求只需要调用某一个GIS算法,简单的几行代码即可完成,没有必要去引用一个GIS类库 。
而且有些算法在这些常用的GIS类库中没有对应接口,就比如在下文记录的这几种常用算法中,求垂足、判断线和面的关系,在turf.js就没有对应接口 。
下面文章中是我总结的一些常用GIS算法,这里统一用JavaScript语言实现,因为JS代码相对比较简洁,方便理解其中算法逻辑,也方便在浏览器下预览效果 。在具体应用时可以根据具体需求,翻译成Java、C#、Python等语言来使用 。
文中代码大部分为之前遇到需求时在网上搜索得到,然后自己根据具体需要做了优化修改,通过这篇文章做个总结收集,也方便后续使用时查找 。
1、常用算法以下方法中传参的点、线、面都是对应geojson格式中coordinates,方便统一调用 。geojson标准参考:
https://www.oschina.net/translate/geojson-spec

GIS常用算法

文章插图
 
1.1、计算两经纬度点之间的距离【GIS常用算法】适用场景:测量
/*** 计算两经纬度点之间的距离(单位:米)* @param p1 起点的坐标;[经度,纬度];例:[116.35,40.08]* @param p2 终点的坐标;[经度,纬度];例:[116.72,40.18]** @return d 返回距离*/function getDistance(p1, p2) {var rlat1 = p1[1] * Math.PI / 180.0;var rlat2 = p2[1] * Math.PI / 180.0;var a = rlat1 - rlat2;var b = p1[0] * Math.PI / 180.0 - p2[0] * Math.PI / 180.0;var d = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2) + Math.cos(rlat1) * Math.cos(rlat2) * Math.pow(Math.sin(b / 2), 2)));d = d * 6378.137;d = Math.round(d * 10000) / 10;return d}1.2、根据已知线段以及到起点距离,求目标点坐标适用场景:封闭管段定位问题点
/*** 根据已知线段以及到起点距离(单位:米),求目标点坐标* @param line 线段;[[经度,纬度],[经度,纬度]];例:[[116.01,40.01],[116.52,40.01]]* @param dis 到起点距离(米);Number;例:500** @return point 返回坐标*/function getLinePoint(line, dis) {var p1 = line[0]var p2 = line[1]var d = getDistance(p1, p2) // 计算两经纬度点之间的距离(单位:米)var dx = p2[0] - p1[0]var dy = p2[1] - p1[1]return [p1[0] + dx * (dis / d), p1[1] + dy * (dis / d)]}1.3、已知点、线段,求垂足垂足可能在线段上,也可能在线段延长线上 。
适用场景:求垂足
/*** 已知点、线段,求垂足* @param line 线段;[[经度,纬度],[经度,纬度]];例:[[116.01,40.01],[116.52,40.01]]* @param p 点;[经度,纬度];例:[116.35,40.08]** @return point 返回垂足坐标*/function getFootPoint(line, p) {var p1 = line[0]var p2 = line[1]var dx = p2[0] - p1[0];var dy = p2[1] - p1[1];var cross = dx * (p[0] - p1[0]) + dy * (p[1] - p1[1])var d2 = dx * dx + dy * dyvar u = cross / d2return [(p1[0] + u * dx), (p1[1] + u * dy)]}1.4、线段上距离目标点最近的点不同于上面求垂足方法,该方法求出的点肯定在线段上 。
如果垂足在线段上,则最近的点就是垂足,如果垂足在线段延长线上,则最近的点就是线段某一个端点 。
适用场景:根据求出最近的点计算点到线段的最短距离
/*** 线段上距离目标点最近的点* @param line 线段;[[经度,纬度],[经度,纬度]];例:[[116.01,40.01],[116.52,40.01]]* @param p 点;[经度,纬度];例:[116.35,40.08]** @return point 最近的点坐标*/function getShortestPointInLine(line, p) {var p1 = line[0]var p2 = line[1]var dx = p2[0] - p1[0];var dy = p2[1] - p1[1];var cross = dx * (p[0] - p1[0]) + dy * (p[1] - p1[1])if (cross <= 0) {return p1}var d2 = dx * dx + dy * dyif (cross >= d2) {return p2}// 垂足var u = cross / d2return [(p1[0] + u * dx), (p1[1] + u * dy)]}1.5、点缓冲这里缓冲属于测地线方法,由于这里并没有严格的投影转换体系,所以与标准的测地线缓冲还有些许误差,不过经测试,半径100KM内,误差基本可以忽略 。具体缓冲类型可看下之前的文章你真的会用PostGIS中的buffer缓冲吗?
适用场景:根据点和半径画圆
/*** 点缓冲* @param center 中心点;[经度,纬度];例:[116.35,40.08]* @param radius 半径(米);Number;例:5000* @param vertices 返回圆面点的个数;默认64;Number;例:32** @return coords 面的坐标*/function bufferPoint(center, radius, vertices) {if (!vertices) vertices = 64;var coords = []// 111319.55:在赤道上1经度差对应的距离,111133.33:在经线上1纬度差对应的距离var distanceX = radius / (111319.55 * Math.cos(center[1] * Math.PI / 180));var distanceY = radius / 111133.33;var theta, x, y;for (var i = 0; i < vertices; i++) {theta = (i / vertices) * (2 * Math.PI);x = distanceX * Math.cos(theta);y = distanceY * Math.sin(theta);coords.push([center[0] + x, center[1] + y]);}return [coords]}


推荐阅读