作为一个GISer,在日常WebGIS开发中,会常用到的turf.js,这是一个地理空间分析的JavaScript库,经常搭配各种GIS JS API使用,如leafletmapboxglopenlayers等;在后台Java开发中,也有个比较强大的GIS库,geotools,里面包含构建一个完整的地理信息系统所需要的全部工具类;数据库端常用是postgis扩展,需要在postgres库中引入使用。

然而在开发某一些业务系统的时候,有些需求只需要调用某一个GIS算法,简单的几行代码即可完成,没有必要去引用一个GIS类库。

而且有些算法在这些常用的GIS类库中没有对应接口,就比如在下文记录的这几种常用算法中,求垂足、判断线和面的关系,在turf.js就没有对应接口。

下面文章中是我总结的一些常用GIS算法,这里统一用JavaScript语言实现,因为JS代码相对比较简洁,方便理解其中算法逻辑,也方便在浏览器下预览效果。在具体应用时可以根据具体需求,翻译成JavaC#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. cytoscape-d3-force api

    { animate:true,//是否在布局运行时显示布局:特殊的"结束"值使布局具有离散布局的动画效果 maxIterations:0,//布局退出前的最大迭代次数 maxSim ...

  2. SpringCloud升级之路2020.0.x版-5.所有项目的parent与spring-framework-common说明

    本系列代码地址:https://github.com/HashZhang/spring-cloud-scaffold/tree/master/spring-cloud-iiford 源代码文件:htt ...

  3. 在屏幕上搜索图片并返回图片所在位置的坐标的AutoHotkey脚本源代码(类似大漠插件)

    ;~  在屏幕上搜索图片并返回图片所在位置的坐标的AutoHotkey脚本源代码(类似大漠插件) ; https://www.autohotkey.com/boards/viewtopic.php?t ...

  4. 使用jwt来保护你的接口服务

    以前写过一篇关于接口服务规范的文章,原文在此,里面关于安全性问题重点讲述了通过appid,appkey,timestamp,nonce以及sign来获取token,使用token来保障接口服务的安全. ...

  5. C++ 继承方式 与 普通方式 对比

    1 //C++ 继承 2 //继承是面向对象三大特性之一 3 4 #include <iostream> 5 #include <string> 6 using namespa ...

  6. git的实用命令(撤回,合并)

    前言 在用开发项目的时候,经常会写着写着会发现写错的时候,人生没有后悔药,但是git有啊,大不了从头再来嘛. git的一些撤销操作 代码还没有存到暂存区 当我们修改了一个文件,还没有执行git add ...

  7. Spring Security中实现微信网页授权

    微信公众号提供了微信支付.微信优惠券.微信H5红包.微信红包封面等等促销工具来帮助我们的应用拉新保活.但是这些福利要想正确地发放到用户的手里就必须拿到用户特定的(微信应用)微信标识openid甚至是用 ...

  8. Java代码操作zookeeper

    .personSunflowerP { background: rgba(51, 153, 0, 0.66); border-bottom: 1px solid rgba(0, 102, 0, 1); ...

  9. STM32—SPI详解

    目录 一.什么是SPI 二.SPI协议 物理层 协议层 1.通讯时序图 2.起始和停止信号 3.数据有效性 4.通讯模式 三.STM32中的SPI 简介 功能框图 1.通讯引脚 2.时钟控制逻辑 3. ...

  10. 个人笔记-----Vue中多个router-view应用

    单个 <router-view/> 和多个 <router-view/> 的区别,单个 <router-view/> 只是一个区域的变化,不需要设置name属性,在 ...