空间搜索(圆范围)中Geohash编码方案和网格编码方案对比探讨
文章版权由作者李晓晖和博客园共有,若转载请于明显处标明出处:http://www.cnblogs.com/naaoveGIS/
1.背景
多个项目中实现范围(圆)搜索的方案为:依赖库表中的X和Y字段构造一个矩形查询范围,再通过几何计算范围中的数据到指定坐标的距离是否在阈值半径中,最后返回阈值中的数据。
该方案有几个优点:
- 无需对数据预处理,仅通过sql就可以实现,实现方式简单。
- 数据库环境中,通过数字搜索比通过字符串搜索效率更高,占用的CPU更少。
但是,该方案在表数据量庞大的情况下,通过X和Y两个字段,并且有四个查询条件,对性能有一定损耗。
在之前我写过一篇关于Geohash编码研究的文章WebGIS中GeoHash编码的研究和扩展,这里提到了一种将X和Y以哈夫曼原理编码成一维字符串的方案。那么这里如果我们使用geohash编码方案来优化查询效率是否有用?
2.基于GeoHash编码的范围查询
2.1需要解决的点
- 基于GeoHash编码原理,将编码对象从经纬度数据扩展到也支持平面坐标数据
- 由于编码值对应的是一个范围,如果查询坐标落入在范围的角落,仅通过相同字符串匹配可能导致查询结果不全,这里需要重构查询范围
- 根据查询的容差范围,可以计算出该范围所对应的geohash字符串位数
2.2解决思路
- 针对平面坐标:将编码范围改变成该地图平面坐标真实范围,基于哈夫曼编码规则进行计算,最后使用base32编码成字符串。
- 针对查询范围:以查询点为中心通过查询范围构造出查询范围矩形,利用目前查询范围所对应的hash编码长度所对应的精度,利用该精度将矩形进行切割,然后对格网分别编码。
- geohash长度所对应的真实精度:基于编码规律,经度的bit长度可以为奇偶,但是纬度的bit长度必须是偶数,反算出经度和纬度的bit长度。然后根据经纬对范围,结合各方向的二分法次数(bit长度),即可算出经纬度此时的精度。
2.3方案实现
这里重点给出查询搜索代码,即通过hash长度对应的精度、查询范围参数,进行网格切分和编码。
/***
* 通过传入指定范围、指定坐标、查询范围和geohash长度,返回查询范围中对应的所有geohash编码
* @param minX
* @param minY
* @param maxX
* @param maxY
* @param X
* @param Y
* @param geohashLength geohash字符串编码长度
* @param searchRange 查询范围,如果是平面坐标系100M则传入100,经纬度坐标系0.0001度则传入0.0001
* @return
*/
public static List<String> GeoHashSearch(double minX, double minY, double maxX, double maxY,
double X, double Y, int geohashLength,double searchRange){
List<Integer> latLngLength = SetHashLength(geohashLength);
double boundMinX = X - searchRange;
double boundMaxX = X + searchRange;
double boundMinY = Y - searchRange;
double boundMaxY = Y + searchRange;
List<Double> range = GetGoeHashRange(minX, minY, maxX, maxY, latLngLength.get(0), latLngLength.get(1));
List<String> searchResult= new ArrayList<String>();
double xrange = range.get(0);
double yrange = range.get(1);
double value = 0.5;
for (int i = 0; boundMinX + (i - value) * xrange <= boundMaxX; i++) {
for (int j = 0; boundMinY + (j - value) * yrange <= boundMaxY; j++) {
String geohashCode = Encode(minX, minY, maxX, maxY,
boundMinX + i* xrange, boundMinY + j * yrange, geohashLength);
if (!searchResult.contains(geohashCode)) {
searchResult.add(geohashCode);
}
}
}
return searchResult;
}
2.4优缺点探讨
2.4.1优点
- geohash编码通过不断的二分,如果有必要可以直接将精度编码至厘米或毫米级别,并且对应的编码长度不会特别长。比如,当经纬度坐标系下,即使坐标范围用全球范围(-90到90,-180到180),其厘米级的编码长度也不长。以下是此时的长度精确表:
2.4.2缺点
- 高精度编码没法使用:虽然精度到厘米编码长度也不长,但是当查询范围是1Km例如,此时编码长度只需要到2位,而查询却必须使用like去匹配,此时查询效率反而太低。
- 不同编码长度间跨越的精度太大:比如,查询1000M和查询2000M范围所对应的编码长度可能都是2,这样导致查询的结果的个数(格网切分)可能特别多。那么此时即使对编码字段做了索引,也不一定会产生实际效果(如果使用In则索引无效,而使用OR,查询条件又过多影响sql解析等)。
- 编码为字符串影响查询效率:geohash编码的结果是基于Base32规范进行结果编码,为字符串,影响数据库查询效率。
2.5 换一种思路
geohash编码由于随着地图范围不同各编码长度精度无法确定、编码只能以字符串存储等问题,在我们的业务场景上无法使用。那么,如果我们让编码精度确定、编码可以用数字替代,是否就可以达到业务场景的需要呢?
3.基于格网编码的范围查询
3.1算法介绍
格网划分算是GIS算法中的万金油。以前博客中写过的空间索引、地理插值、影像金字塔、矢量切片等等均可以基于格网的思路去探索。这里,同样可以利用格网算法来进行编码。
3.1.1基本算法
- 将地图的左上角坐标当做原点,设定好格网的长度(X方向和Y方向)
- 传入坐标,计算坐标分别在X方向和Y方向离坐标原点的格网个数,分别为xNum、yNum
/***
* 通过传入地图起始点,待编码坐标,编码的X和Y方向精确度,获取网格编码字符串
* @param minX 地图起始点X坐标
* @param minY 地图起始点Y坐标
* @param X
* @param Y
* @param gridXSize X方向精确度。平面坐标为M,经纬度坐标为度
* @param gridYSize Y方向精确度。平面坐标为M,经纬度坐标为度
* @return
*/
public static long GetGridCode(double minX, double minY, double X, double Y, double gridXSize,double gridYSize){
if (X < minX || Y < minY){
return -1;
}
int xNum = (int)Math.ceil(Math.abs(X - minX) / gridXSize);
int yNum = (int)Math.ceil(Math.abs(Y - minY) / gridYSize);
return CreateLongCode(xNum,yNum);
}
3.1.2编码优化
如果我们需要将编码转换成数字编码,那么我们同样需要设定一种规则。这里,我规定xNum和yNum都必须是八个字符串长度,不足的在前缀以0补充,最后再合并转换成整数。(注意,这里我设计以0作为前缀而不是后缀补充,是为了及时转换成数字后,以后可以通过数字将编码反转换为空间范围)
/***
* 以8位数和8位数分别将col和row填充组合成一个整数
*/
private static long CreateLongCode(int x,int y){
String sx=String.valueOf(y);
String sy=String.valueOf(y);
for(int i=sx.length();i<XLen;i++){
sx="0"+sx;
}
for(int j=sy.length();j<YLen;j++){
sy="0"+sy;
}
String scode=sx+sy;
long code=Long.parseLong(scode);
return code;
}
/***
* 获取网格编码所对应的真实地理范围
* @param minX
* @param minY
* @param value 编码值
* @param gridXSize X方向精确度。平面坐标为M,经纬度坐标为度
* @param gridYSize Y方向精确度。平面坐标为M,经纬度坐标为度
* @return
*/
public static List<Double> Decode(double minX, double minY, long value, double gridXSize,double gridYSize){
String svalue=String.valueOf(value);
String sx=svalue.substring(0,svalue.length()-YLen-1);
String sy=svalue.substring(svalue.length()-YLen);
int xnum=Integer.parseInt(sx);
int ynum=Integer.parseInt(sy);
double boundMinX = minX + (xnum - 1) * gridXSize;
double boundMaxX = boundMinX + gridXSize;
double boundMinY = minY + (ynum - 1) * gridYSize;
double boundMaxY = boundMinY + gridYSize;
List<Double> bound = new ArrayList<Double>();
bound.add(boundMinX);
bound.add(boundMinY);
bound.add(boundMaxX);
bound.add(boundMaxY);
return bound;
}
3.2范围查询
同样,这里也需要考虑与geohash查询时一样的情况:
- 查询XY落在网格的边角上
- 查询范围阈值大于网格大小 解决思路与之前相同:
/***
* 通过传入地图起始点、网格X和Y方向精确度、查询范围和查询点,返回对应查询范围内所有网格编码
* @param minX
* @param minY
* @param X
* @param Y
* @param gridXSize X方向精确度。平面坐标为M,经纬度坐标为度
* @param gridYSize Y方向精确度。平面坐标为M,经纬度坐标为度
* @param range 查询范围,平面坐标为M,经纬度坐标为度
* @return
*/
public static List<Long> GridCodeSearch(double minX, double minY, double X, double Y, double gridXSize, double gridYSize,double range){
if (X < minX || Y < minY){
return null;
}
double boundMinX = X - range;
double boundMinY = Y - range;
double boundMaxX = X + range;
double boundMaxY = Y + range;
double value=0.5;
List<Long> searchResult = new ArrayList<Long>();
for (int i = 0; boundMinX + (i - value) * gridXSize <= boundMaxX; i++){
for (int j = 0; boundMinY + (j - value) * gridYSize <= boundMaxY; j++){
long gridCode = GetGridCode(minX, minY, boundMinX + i * gridXSize, boundMinY + j * gridYSize, gridXSize, gridYSize);
if (!searchResult.contains(gridCode)){
searchResult.add(gridCode);
}
}
}
return searchResult;
}
3.3格网划分的一点建议
- 格网不宜划分太小,建议划分的比查询范围大,这样保证范围过滤查询时返回的匹配格网编码少。比如,格网大小500M,查询范围100M,查询时,在多数情况下将只返回一个编码。当然,此时基于该编码去数据库中查询,将得到更多的数据点,于是需要我们做精确的范围计算量变大。但是:将数据库压力适当转移到服务器计算是一种更划算的策略。当然,格网划的太大,也会适得其反,建议通用查询范围一两倍即可。
4.后续方案描述
- 坐标存入时,将坐标基于格网编码并同步存入到指定字段,对该字段建立索引(此时字段为长度大于16的长整型)。
- 查询时,调用编码查询接口,获取到该XY以及查询范围下,对应的网格编码。在数据库中利用这些编码做匹配查询(粗过滤)。对返回的结果进一步做精确范围匹配(精过滤可做可不做,视需求规格而定)。
-----欢迎转载,但保留版权,请于明显处标明出处:http://www.cnblogs.com/naaoveGIS/
如果您觉得本文确实帮助了您,可以微信扫一扫,进行小额的打赏和鼓励,谢谢 ^_^
空间搜索(圆范围)中Geohash编码方案和网格编码方案对比探讨的更多相关文章
- WebGIS中GeoHash编码的研究和扩展
文章版权由作者李晓晖和博客园共有,若转载请于明显处标明出处:http://www.cnblogs.com/naaoveGIS/. 1.背景 1.1普通地理编码流程 将采集的POI入库后,数据库里保存有 ...
- 基于Solr的空间搜索
如果需要对带经纬度的数据进行检索,比如查找当前所在位置附近1000米的酒店,一种简单的方法就是:获取数据库中的所有酒店数据,按经纬度计算距离,返回距离小于1000米的数据. 这种方式在数据量小的时候比 ...
- solr特点八:Spatial(空间搜索)
前言 在美团CRM系统中,搜索商家的效率与公司的销售额息息相关,为了让BD们更便捷又直观地去搜索商家,美团CRM技术团队基于Solr提供了空间搜索功能,其中移动端周边商家搜索和PC端的地图模式搜索功能 ...
- [ElasticSearch] 空间搜索 (一)
依据索引文档的地理坐标来进行搜索.Elasticsearch 也可以处理这种搜索.--空间搜索 一.为空间搜索准备映射 PUT my_space_test { "mappings" ...
- python语言中的编码问题
在编程的过程当中,常常会遇到莫名其妙的乱码问题.很多人选择出了问题直接在网上找答案,把别人的例子照搬过来,这是快速解决问题的一个好办法.然而,作为一个严谨求实的开发者,如果不从源头上彻底理解乱码产生的 ...
- SQL Server获取下一个编码字符串的实现方案分割和进位
我在前一种解决方案SQL Server获取下一个编码字符实现和后一种解决方案SQL Server获取下一个编码字符实现继续重构与增强两篇博文中均提供了一种解决编码的方案,考虑良久对比以上两种方 ...
- 中文在unicode中的编码范围
以前写过一篇贴子是写中文在unicode中的编码范围 unicode中文范围,但写的不是很详细,今天再次研究了下unicode,并给出详细的unicode取值范围. 本次研究的unicode对象是un ...
- (转载)MySQL LIKE 用法:搜索匹配字段中的指定内容
(转载)http://www.5idev.com/p-php_mysql_like.shtml MySQL LIKE 语法 LIKE 运算符用于 WHERE 表达式中,以搜索匹配字段中的指定内容,语法 ...
- Eclipse中设置编码的方式
如果要使插件开发应用能有更好的国际化支持,能够最大程度的支持中文输出,则最好使 Java文件使用UTF-8编码.然而,Eclipse工 作空间(workspace)的缺省字符编码是操作系统缺省的编码, ...
随机推荐
- http的CA证书安装(也就是https)
近几年随着安全意识的提高,https流行起来,很多小伙伴不太了解https是什么,其实http和https并没有区别,简单的来说,https就是将http通信进行了加密和解密的一个过程.加上谷歌浏览器 ...
- Vue基础
1.可以使用 methods 来替代 computed,效果上两个都是一样的. 但是 computed 是基于它的依赖缓存,只有相关依赖发生改变时才会重新取值. {{ reversedMessage ...
- LeetCode-391. 完美矩形(使用C语言编译,详解)
链接:https://leetcode-cn.com/problems/perfect-rectangle/description/ 题目 我们有 N 个与坐标轴对齐的矩形, 其中 N > 0, ...
- Alpha阶段报告-hywteam
一.Alpha版本测试报告 1. 在测试过程中总共发现了多少Bug?每个类别的Bug分别为多少个? BUG名 修复的BUG 不能重现的BUG 非BUG 没能力修复的BUG 下个版本修复 文件路径的表示 ...
- C语言作业(三)
一.完成PTA上四题作业 二.具体解题 (一).A乘以B 1.实验代码 #include <stdio.h> int main() { int A,B,C; scanf("%d ...
- ArcGIS地图打印那些事
记录了通过ArcGIS的PringTask进行地图打印,以及借助html2canvas实现屏幕截图的方法.两个方法各有适用的情景.过程中涉及的相关资源和问题给出链接,自行查看. 1.需求背景 地图打印 ...
- C#系统服务安装
转载 http://blog.csdn.net/vvhesj/article/details/8349615 1.1创建WindowsService项目 导入需要的引用比如System.configu ...
- zookeeper入门系列:paxos协议
上一章讨论了一种强一致性的情况,即需要分布式事务来解决,本章我们来讨论一种最终一致的算法,paxos算法. paxos算法是由大牛lamport发明的,关于paxos算法有很多趣事.比如lamport ...
- C#中的函数式编程:递归与纯函数(二)
在序言中,我们提到函数式编程的两大特征:无副作用.函数是第一公民.现在,我们先来深入第一个特征:无副作用. 无副作用是通过引用透明(Referential transparency)来定义的.如果一个 ...
- python之路--day15--常用模块之logging模块
常用模块 1 logging模块 日志级别:Noset (不设置) Debug---(调试信息)----也可用10表示 Info--(消息信息)----也可用20表示 Warning---(警告信息) ...