作为一个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

1.1、计算两经纬度点之间的距离

适用场景:测量

/**
* 计算两经纬度点之间的距离(单位:米)
* @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 * dy
var u = cross / d2
return [(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 * dy
if (cross >= d2) {
return p2
}
// 垂足
var u = cross / d2
return [(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]
}

1.6、点和面关系

该方法采用射线法思路实现。(了解射线法可参考:https://blog.csdn.net/qq_27161673/article/details/52973866)

这里已经考虑到环状多边形的情况。

适用场景:判断点是否在面内

/**
* 点和面关系
* @param point 点;[经度,纬度];例:[116.353455, 40.080173]
* @param polygon 面;geojson格式中的coordinates;例:[[[116.1,39.5],[116.1,40.5],[116.9,40.5],[116.9,39.5]],[[116.3,39.7],[116.3,40.3],[116.7,40.3],[116.7,39.7]]]
*
* @return inside 点和面关系;0:多边形外,1:多边形内,2:多边形边上
*/
function pointInPolygon(point, polygon) {
var isInNum = 0;
for (var i = 0; i < polygon.length; i++) {
var inside = pointInRing(point, polygon[i])
if (inside === 2) {
return 2;
} else if (inside === 1) {
isInNum++;
}
}
if (isInNum % 2 == 0) {
return 0;
} else if (isInNum % 2 == 1) {
return 1;
}
} /**
* 点和面关系
* @param point 点
* @param ring 单个闭合面的坐标
*
* @return inside 点和面关系;0:多边形外,1:多边形内,2:多边形边上
*/
function pointInRing(point, ring) {
var inside = false,
x = point[0],
y = point[1],
intersects, i, j; for (i = 0, j = ring.length - 1; i < ring.length; j = i++) {
var xi = ring[i][0],
yi = ring[i][1],
xj = ring[j][0],
yj = ring[j][1]; if (xi == xj && yi == yj) {
continue
}
// 判断点与线段的相对位置,0为在线段上,>0 点在左侧,<0 点在右侧
if (isLeft(point, [ring[i], ring[j]]) === 0) {
return 2; // 点在多边形边上
} else {
if ((yi > y) !== (yj > y)) { // 垂直方向目标点在yi、yj之间
// 求目标点在当前线段上的x坐标。 由于JS小数运算后会转换为精确15位的float,因此需要去一下精度
var xx = Number(((xj - xi) * (y - yi) / (yj - yi) + xi).toFixed(10))
if (x <= xx) { // 目标点水平射线与当前线段有交点
inside = !inside;
}
}
}
}
return Number(inside);
} /**
* 判断点与线段的相对位置
* @param point 目标点
* @param line 线段
*
* @return isLeft,点与线段的相对位置,0为在线段上,>0 p在左侧,<0 p在右侧
*/
function isLeft(point, line) {
var isLeft = ((line[0][0] - point[0]) * (line[1][1] - point[1]) - (line[1][0] - point[0]) * (line[0][1] - point[1]))
// 由于JS小数运算后会转换为精确15位的float,因此需要去一下精度
return Number(isLeft.toFixed(10))
}

1.7、线段与线段的关系

适用场景:判断线和线的关系

/**
* 线段与线段的关系
* @param line1 线段;[[经度,纬度],[经度,纬度]];例:[[116.01,40.01],[116.52,40.01]]
* @param line2 线段;[[经度,纬度],[经度,纬度]];例:[[116.33,40.21],[116.36,39.76]]
*
* @return intersect 线段与线段的关系;0:相离,1:相交,2:相切
*/
function intersectLineAndLine(line1, line2) {
var x1 = line1[0][0],
y1 = line1[0][1],
x2 = line1[1][0],
y2 = line1[1][1],
x3 = line2[0][0],
y3 = line2[0][1],
x4 = line2[1][0],
y4 = line2[1][1] //快速排斥:
//两个线段为对角线组成的矩形,如果这两个矩形没有重叠的部分,那么两条线段是不可能出现重叠的 //这里的确如此,这一步是判定两矩形是否相交
//1.线段ab的低点低于cd的最高点(可能重合)
//2.cd的最左端小于ab的最右端(可能重合)
//3.cd的最低点低于ab的最高点(加上条件1,两线段在竖直方向上重合)
//4.ab的最左端小于cd的最右端(加上条件2,两直线在水平方向上重合)
//综上4个条件,两条线段组成的矩形是重合的
//特别要注意一个矩形含于另一个矩形之内的情况
if (!(Math.min(x1, x2) <= Math.max(x3, x4) && Math.min(y3, y4) <= Math.max(y1, y2) &&
Math.min(x3, x4) <= Math.max(x1, x2) && Math.min(y1, y2) <= Math.max(y3, y4))) {
return 0
} // 判断点与线段的相对位置,0为在线段上,>0 点在左侧,<0 点在右侧
if (isLeft(line1[0], line2) === 0 || isLeft(line1[1], line2) === 0) {
return 2
} //跨立实验:
//如果两条线段相交,那么必须跨立,就是以一条线段为标准,另一条线段的两端点一定在这条线段的两段
//也就是说a b两点在线段cd的两端,c d两点在线段ab的两端
var kuaili1 = ((x3 - x1) * (y2 - y1) - (x2 - x1) * (y3 - y1)) * ((x4 - x1) * (y2 - y1) - (x2 - x1) * (y4 - y1))
var kuaili2 = ((x1 - x3) * (y4 - y3) - (x4 - x3) * (y1 - y3)) * ((x2 - x3) * (y4 - y3) - (x4 - x3) * (y2 - y3))
return Number(Number(kuaili1.toFixed(10)) <= 0 && Number(kuaili2.toFixed(10)) <= 0)
}

1.8、线和面关系

适用场景:判断线与面的关系

该方法考虑到环状多边形的情况,且把相切情况分为了内切和外切。

参考链接:https://www.cnblogs.com/xiaozhi_5638/p/4165353.html

/**
* 线和面关系
* @param line 线段;[[经度,纬度],[经度,纬度]];例:[[116.01,40.01],[116.52,40.01]]
* @param polygon 面;geojson格式中的coordinates;例:[[[116.1,39.5],[116.1,40.5],[116.9,40.5],[116.9,39.5]],[[116.3,39.7],[116.3,40.3],[116.7,40.3],[116.7,39.7]]]
*
* @return intersect 线和面关系;0:相离,1:相交,2:包含,3:内切,4:外切
*/
function intersectLineAndPolygon(line, polygon) {
var isTangent = false
var isInNum = 0
var intersect = 0
for (var i = 0; i < polygon.length; i++) {
// 线和面关系;0:相离,1:相交,2:包含,3:内切,4:外切
intersect = intersectLineAndRing(line, polygon[i])
if (intersect === 1) {
return 1
} else if (intersect === 2) {
isInNum++
} else if (intersect === 3) {
isInNum++
isTangent = true
} else if (intersect === 4) {
isTangent = true
}
}
if (isInNum % 2 == 0) {
if (isTangent) {
return 4 // 外切
} else {
return 0 // 相离
}
} else if (isInNum % 2 == 1) {
if (isTangent) {
return 3 // 内切
} else {
return 2 // 包含
}
}
} /**
* 线和面关系
* @param line 线段
* @param ring 单面
*
* @return intersect 线和面关系;0:相离,1:相交,2:包含,3:内切,4:外切
*/
function intersectLineAndRing(line, ring) {
var inserset = 0
var isTangent = false
var inserset1 = pointInRing(line[0], ring) // 点和面关系;0:多边形外,1:多边形内,2:多边形边上
var inserset2 = pointInRing(line[1], ring) // 点和面关系;0:多边形外,1:多边形内,2:多边形边上
if (inserset1 === inserset2 === 0) {
inserset = 0
} else if ((inserset1 * inserset2) === 1) {
inserset = 2
} else if ((inserset1 * inserset2) === 2) {
inserset = 3
} else if ((inserset1 === 2 || inserset2 === 2) && (inserset1 === 0 || inserset2 === 0)) {
inserset = 4
} else if ((inserset1 === 1 || inserset2 === 1) && (inserset1 === 0 || inserset2 === 0)) {
return 1 // 相交
}
for (var i = 0, j = ring.length - 1; i < ring.length; j = i++) {
var line2 = [ring[j], ring[i]]
// 目标线段与当前线段的关系;0:相离,1:相交,2:相切
var intersectLine = intersectLineAndLine(line, line2)
if (intersectLine == 1) {
return 1 // 相交
}
}
return inserset
}

1.9、geojson 面转线

适用场景:只有geojson面数据,获取线的边界

/**
* 面转线
* @param geojson 面geojson
*
* @return geojson 线geojson
*/
function convertPolygonToPolyline(polygonGeoJson) {
var polylineGeoJson = JSON.parse(JSON.stringify(polygonGeoJson)) for (var i = 0; i < polylineGeoJson.features.length; i++) {
var MultiLineString = []
if (polylineGeoJson.features[i].geometry.type === 'Polygon') {
var Polygon = polylineGeoJson.features[i].geometry.coordinates
Polygon.forEach(LinearRing => {
var LineString = LinearRing
MultiLineString.push(LineString)
})
} else if (polylineGeoJson.features[i].geometry.type === 'MultiPolygon') {
var MultiPolygon = polylineGeoJson.features[i].geometry.coordinates
MultiPolygon.forEach(Polygon => {
Polygon.forEach(LinearRing => {
var LineString = LinearRing
MultiLineString.push(LineString)
})
})
} else {
console.error('请确认输入参数为geojson格式面数据!')
return null
}
polylineGeoJson.features[i].geometry.type = 'MultiLineString' //面转线
polylineGeoJson.features[i].geometry.coordinates = MultiLineString
} return polylineGeoJson
}

2、在线示例

在线示例:http://gisarmory.xyz/blog/index.html?demo=GISAlgorithm

代码地址:http://gisarmory.xyz/blog/index.html?source=GISAlgorithm


原文地址:http://gisarmory.xyz/blog/index.html?blog=GISAlgorithm

关注《GIS兵器库》, 只给你网上搜不到的GIS知识技能。

本文章采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。欢迎转载、使用、重新发布,但务必保留文章署名《GIS兵器库》(包含链接:  http://gisarmory.xyz/blog/),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。

GIS常用算法的更多相关文章

  1. 总结Objective-c常用算法

          今天是星期天,想睡到10点起床,结果认为自己太奢侈了,不能这么做,于是把闹钟设置成了6:30:结果终于9:36醒了,起床,无缘无故迟了,好吧,就算太累了吧,周天就原谅自己一回.终于到了中午 ...

  2. Atitit 编程语言常用算法attilax总结

    Atitit 编程语言常用算法attilax总结 1. 编译算法分类and   数据操作算法.1 1.1. Tab driver stat  状态转换表格算法1 1.2. Nest case 词法分析 ...

  3. GJM : 数据结构 - 轻松看懂机器学习十大常用算法 [转载]

     转载请联系原文作者 需要获得授权,非法转载 原文作者将享受侵权诉讼 文/不会停的蜗牛(简书作者)原文链接:http://www.jianshu.com/p/55a67c12d3e9 通过本篇文章可以 ...

  4. ACM常用算法及练习(2)

    ACM常用算法及练习 知识类型 重要度 容易度 应掌握度 典型题 其他           数据结构(5) 链表 ★★☆ ★★★ ★★☆     栈 stack ★★★ ★★★ ★★★ HLoj120 ...

  5. ACM常用算法及练习(1)

    ACM常用算法及练习 第一阶段:练经典常用算法,下面的每个算法给我打上十到二十遍,同时自己精简代码,因为太常用,所以要练到写时不用想,10-15分钟内打完,甚至关掉显示器都可以把程序打出来. 1.最短 ...

  6. C/C++常用算法【C语言顺序查找(随机数)】【1】

    这是我学习唐峻,李淳的<C/C++常用算法第一天> 1.8.1. 查找数字: 程序随机生成一个拥有20个整数数据的数组,然后输入要查找的数据.接着,可以采用醉简单的逐个对比的方法进行查找, ...

  7. Python之路,Day21 - 常用算法学习

    Python之路,Day21 - 常用算法学习   本节内容 算法定义 时间复杂度 空间复杂度 常用算法实例 1.算法定义 算法(Algorithm)是指解题方案的准确而完整的描述,是一系列解决问题的 ...

  8. python 下的数据结构与算法---2:大O符号与常用算法和数据结构的复杂度速查表

    目录: 一:大O记法 二:各函数高阶比较 三:常用算法和数据结构的复杂度速查表 四:常见的logn是怎么来的 一:大O记法 算法复杂度记法有很多种,其中最常用的就是Big O notation(大O记 ...

  9. 剑指Offer——知识点储备-常用算法

    剑指Offer--知识点储备-常用算法 快速排序 注:若排序是有序的,采用快排,则退化为冒泡排序. 解决这个问题,采用两个选取基准的方法 (1)随机选取基数(在这个区间内随机取一个数) 出现的恶劣情况 ...

随机推荐

  1. nuxt服务部署到云上全程记录

    首先,在使用脚手架nuxt-app中创建项目时,箭头选用不起作用,这是因为git bash在windows中交互问题,临时的解决办法是换用cmd 登录云服务器后,首先安装nodejs yum inst ...

  2. vue传值 ---- >> 子传父,$emit()

    划重点: $emit 绑定一个自定义事件event,当这个这个语句被执行到的时候,就会将参数arg传递给父组件,父组件通过@event监听并接收参数.   子组件:   1 <template& ...

  3. python3.7安装和pycharm安装教程. 以及pycharm的破解教程

    前言: 供对python初学者查阅, pycharm破解教程参阅了: https://blog.csdn.net/u014044812/article/details/78727496 1. 安装py ...

  4. Asp.Net Core Razor页面中使用echarts展示图形

    Asp.Net Core Razor页面中使用echarts展示图形 要在Razor页面中使用echarts显示图形,主要问题点在于如何将数据传递给js文件. 1,下载安装echarts库文件 首先引 ...

  5. 30 个极大提高开发效率超级实用的 VSCode 插件

    Visual Studio Code 的插件对于在提升编程效率和加快工作速度非常重要.这里有 30 个最受欢迎的 VSCode 插件,它们将使你成为更高效的搬砖摸鱼大师.这些插件主要适用于前端开发人员 ...

  6. NOIP 模拟 $31\; \rm Cover$

    题解 \(by\;zj\varphi\) 因为对于所有区间,都只有包含和被包含关系,这就是一个树形结构. 设 \(\rm f_{i,j}\) 表示在第 \(\rm i\) 个节点,最多被覆盖 \(\r ...

  7. MySQL指定ip和端口连接数据库,并修改数据库密码

    一.指定ip和端口连接数据库 命令 mysql -u root -h (ip) -P (端口)-p 假设ip是:127.0.0.1:端口是:13326,连接的命令: mysql -u root -h ...

  8. com.github.ulisesbocchio:jasypt-spring-boot-starter:2.0.0引用了sping cloud Finchley.M8版本,一直报错说不能从阿里云下载

    解决方法: 1.找到idea或者eclipase中maven插件引向得settings.xml文件 2.修改文件中<mirror/>标签(配置仓库镜像用得)中<mirrorOf/&g ...

  9. python的GUI框架tkinter,实现程序员的流氓式表白逻辑

    导入依赖 '''导入依赖''' import tkinter as tk import tkinter.messagebox as msg 创建并隐藏根窗口 '''创建并隐藏根窗口''' root_w ...

  10. 8、二进制安装K8s之部署CIN网络

    二进制安装K8s之部署CIN网络 部署CIN网络可以使用flannel或者calico,这里介绍使用calico ecd 方式部署. 1.下载calico二进制安装包 创建所需目录 mkdir -p ...