solr的空间查询(查询地图周围坐标)

原文网址:http://www.cnblogs.com/hanhuibing/articles/5680616.html

基于Solr的空间搜索

如果需要对带经纬度的数据进行检索,比如查找当前所在位置附近1000米的酒店,一种简单的方法就是:获取数据库中的所有酒店数据,按经纬度计算距离,返回距离小于1000米的数据。

这种方式在数据量小的时候比较有效,但是当数据量大的时候,检索的效率是很低的,本文介绍使用Solr的Spatial Query进行空间搜索。

空间搜索原理

空间搜索,又名Spatial Search(Spatial Query),基于空间搜索技术,可以做到:

1)对Point(经纬度)和其他的几何图形建索引

2)根据距离排序

3)根据矩形,圆形或者其他的几何形状过滤搜索结果

在Solr中,空间搜索主要基于GeoHash和Cartesian Tiers 2个概念来实现:

GeoHash算法

通过GeoHash算法,可以将经纬度的二维坐标变成一个可排序、可比较的的字符串编码。
在编码中的每个字符代表一个区域,并且前面的字符是后面字符的父区域。其算法的过程如下:

根据经纬度计算GeoHash二进制编码

地球纬度区间是[-90,90], 如某纬度是39.92324,可以通过下面算法对39.92324进行逼近编码:

1)区间[-90,90]进行二分为[-90,0),[0,90],称为左右区间,可以确定39.92324属于右区间[0,90],给标记为1;

2)接着将区间[0,90]进行二分为 [0,45),[45,90],可以确定39.92324属于左区间 [0,45),给标记为0;

3)递归上述过程39.92324总是属于某个区间[a,b]。随着每次迭代区间[a,b]总在缩小,并越来越逼近39.928167;

4)如果给定的纬度(39.92324)属于左区间,则记录0,如果属于右区间则记录1,这样随着算法的进行会产生一个序列1011 1000 1100 0111 1001,序列的长度跟给定的区间划分次数有关。

同理,地球经度区间是[-180,180],对经度116.3906进行编码的过程也类似:

组码

通过上述计算,纬度产生的编码为1011 1000 1100 0111 1001,经度产生的编码为1101 0010 1100 0100 0100。偶数位放经度,奇数位放纬度,把2串编码组合生成新串:11100 11101 00100 01111 00000 01101 01011 00001。

最后使用用0-9、b-z(去掉a, i, l, o)这32个字母进行base32编码,首先将11100 11101 00100 01111 00000 01101 01011 00001转成十进制 28,29,4,15,0,13,11,1,十进制对应的编码就是wx4g0ec1。同理,将编码转换成经纬度的解码算法与之相反,具体不再赘述。

由上可知,字符串越长,表示的范围越精确。当GeoHash base32编码长度为8时,精度在19米左右,而当编码长度为9时,精度在2米左右,编码长度需要根据数据情况进行选择。不过从GeoHash的编码算 法中可以看出它的一个缺点,位于边界两侧的两点,虽然十分接近,但编码会完全不同。实际应用中,可以同时搜索该点所在区域的其他八个区域的点,即可解决这 个问题。

Cartesian Tiers 笛卡尔层

笛卡尔分层模型的思想是将经纬度转换成更大粒度的分层网格,该模型创建了很多的地理层,每一层在前一层的基础上细化切分粒度,每一个网格被分配一个ID,代表一个地理位置。

每层以2的平方递增,所以第一层为4个网格,第二层为16 个,所以整个地图的经纬度将在每层的网格中体现:

那么如何构建这样的索引结构呢,其实很简单,只需要对应笛卡尔层的层数来构建域即可,一个域或坐标对应多个tiers层次。也即是 tiers0->field_0,tiers1->field_1,tiers2->field_2,……,tiers19->field_19。 (一般20层即可)。每个对应笛卡尔层次的域将根据当前这条记录的经纬度通过笛卡尔算法计算出归属于当前层的网格,然后将gridId(网格唯一标示)以 term的方式存入索引。这样每条记录关于笛卡尔0-19的域将都会有一个gridId对应起来。但是查询的时候一般是需要查周边的地址,那么可能周边的 范围超过一个网格的范围,那么实际操作过程是根据经纬度和一个距离确定出需要涉及查询的从19-0(从高往低查)若干层对应的若干网格的数据。那么一个经 纬度周边地址的查询只需要如下图圆圈内的数据:

由上可知,基于Cartesian Tier的搜索步骤为:
1、根据Cartesian Tier层获得坐标点的地理位置gridId
2、与系统索引gridId匹配计算
3、计算结果集与目标坐标点的距离返回特定范围内的结果集合

使用笛卡尔层,能有效缩减少过滤范围,快速定位坐标点。

基于Solr的空间搜索实战

Solr已经提供了3种filedType来进行空间搜索:

1)  LatLonType(用于平面坐标,而不是大地坐标)

2)  SpatialRecursivePrefixTreeFieldType(缩写为RPT)

3)  BBoxField(用于边界索引查询)

本文重点介绍使用SpatialRecursivePrefixTreeFieldType,不仅可以用点,也可以用于多边形的查询。

1、配置Solr

首先看下数据:

Solr的schema.xml配置:

<field name="station_id" type="long" indexed="true" stored="true" required="true" multiValued="false" /> <field name="station_address" type="text_general" indexed="true" stored="true"/> <field name="station_position" type="location_rpt" indexed="true" stored="true"/> <uniqueKey>station_id</uniqueKey>

这里重点是station_position,它的type是location_rpt,它在Solr中的定义如下:

复制代码
复制代码
<!-- A specialized field for geospatial search. If indexed, this fieldType must not be multivalued. -->
<fieldType name="location" class="solr.LatLonType" subFieldSuffix="_coordinate"/> <!-- An alternative geospatial field type new to Solr 4. It supports multiValued and polygon shapes. For more information about this and other Spatial fields new to Solr 4, see: http://wiki.apache.org/solr/SolrAdaptersForLuceneSpatial4 --> <fieldType name="location_rpt" class="solr.SpatialRecursivePrefixTreeFieldType" geo="true" distErrPct="0.025" maxDistErr="0.000009" units="degrees" /> <!-- Spatial rectangle (bounding box) field. It supports most spatial predicates, and has special relevancy modes: score=overlapRatio|area|area2D (local-param to the query). DocValues is required for relevancy. --> <fieldType name="bbox" class="solr.BBoxField" geo="true" units="degrees" numberType="_bbox_coord" /> <fieldType name="_bbox_coord" class="solr.TrieDoubleField" precisionStep="8" docValues="true" stored="false"/>
复制代码
复制代码

对solr.SpatialRecursivePrefixTreeFieldType的配置说明:

SpatialRecursivePrefixTreeFieldType

用于深度遍历前缀树的FieldType,主要用于获得基于Lucene中的RecursivePrefixTreeStrategy。

geo

默认为true,值为true的情况下坐标基于球面坐标系,采用Geohash的方式;值为false的情况下坐标基于2D平面的坐标系,采用Euclidean/Cartesian的方式。

distErrPct

定义非Point图形的精度,范围在0-0.5之间。该值决定了非Point的图形索引或查询时的level(如geohash模式时就是geohash编码的长度)。当为0时取maxLevels,即精度最大,精度越大将花费更多的空间和时间去建索引。

maxDistErr/maxLevels:maxDistErr

定义了索引数据的最高层maxLevels,上述定义为0.000009,根据 GeohashUtils.lookupHashLenForWidthHeight(0.000009, 0.000009)算出编码长度为11位,精度在1米左右,直接决定了Point索引的term数。maxLevels优先级高于maxDistErr, 即有maxLevels的话maxDistErr失效。详见SpatialPrefixTreeFactory.init()方法。不过一般使用 maxDistErr。

units

单位是degrees。

worldBounds
世界坐标值:”minX minY maxX maxY”。 geo=true即geohash模式时,该值默认为”-180 -90 180 90”。geo=false即quad时,该值为Java double类型的正负边界,此时需要指定该值,设置成”-180 -90 180 90”。

2、建立索引

这里使用Solrj来建立索引:

复制代码
复制代码
    //Index some base station data for testpublic void IndexBaseStation(){BaseStationDb baseStationDb = new BaseStationDb();List<BaseStation> stations = baseStationDb.getAllBaseStations();Collection<SolrInputDocument> docList = new ArrayList<SolrInputDocument>(); for (BaseStation baseStation : stations) { //添加基站数据到Solr索引中 SolrInputDocument doc = new SolrInputDocument(); doc.addField("station_id", baseStation.getBaseStationId()); doc.addField("station_address", baseStation.getAddress()); String posString = baseStation.getLongitude()+" "+baseStation.getLatitude() ; doc.addField("station_position", posString); docList.add(doc); } try { server.add(docList); server.commit(); } catch (SolrServerException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } System.out.println("Index base station data done!"); }
复制代码
复制代码

这里使用“经度 纬度”这样的字符串格式将经纬度索引到station_position字段中。

3、查询

查询语法示例:

q={!geofilt pt=45.15,-93.85 sfield=poi_location_p d=5 score=distance}

q={!bbox pt=45.15,-93.85 sfield=poi_location_p d=5 score=distance}

q=poi_location_p:"Intersects(-74.093 41.042 -69.347 44.558)" //a bounding box (not in WKT)

q=poi_location_p:"Intersects(POLYGON((-10 30, -40 40, -10 -20, 40 20, 0 0, -10 30)))" //a WKT example

涉及到的字段说明:

字段

含义

q

查询条件,如 q=poi_id:134567

fq

过滤条件,如 fq=store_name:农业

fl

返回字段,如fl=poi_id,store_name

pt

坐标点,如pt=54.729696,-98.525391

d

搜索半径,如 d=10表示10km范围内

sfield

指定坐标索引字段,如sfield=geo

defType

指定查询类型可以取 dismax和edismax,edismax支持boost函数相乘作用,dismax是通过累加方式计算最后的score.

qf

指定权重字段:qf=store_name^10+poi_location_p^5

score

排序字段根据qf定义的字段defType定义的方式计算得到score排序输出

其中有几种常见的Solr支持的几何操作:
WITHIN:在内部
CONTAINS:包含关系
DISJOINT:不相交
Intersects:相交(存在交集)

1)点查询

测试代码:查询距离某个点pt距离为d的集合

复制代码
复制代码
SolrQuery params = new SolrQuery();   
params.set("q", "*:*");    
params.set("fq", "{!geofilt}");           //距离过滤函数
params.set("pt", "118.227985 39.410722"); //当前经纬度
params.set("sfield", "station_position"); //经纬度的字段
params.set("d", "50"); //就近 d km的所有数据
//params.set("score", "kilometers"); 
params.set("sort", "geodist() asc");  //根据距离排序:由近到远
params.set("start", "0");  //记录开始位置
params.set("rows", "100");  //查询的行数
params.set("fl", "*,_dist_:geodist(),score");//查询的结果中添加距离和score
复制代码
复制代码

返回结果集:

SolrDocument{station_id=12003, station_address=江苏南京1, station_position=118.227996 39.410733, _version_=1499776366043725838, _dist_=0.001559071, score=1.0}

SolrDocument{station_id=12004, station_address=江苏南京2, station_position=118.228996 39.411733, _version_=1499776366044774400, _dist_=0.14214091, score=1.0}

SolrDocument{station_id=12005, station_address=江苏南京3, station_position=118.238996 39.421733, _version_=1499776366044774401, _dist_=1.5471642, score=1.0}

SolrDocument{station_id=7583, station_address=河北省唐山市于唐线, station_position=118.399614 39.269098, _version_=1499776365690355717, _dist_=21.583544, score=1.0}

从这部分结果集中可以看出,前3条数据是离目标点"118.227985 39.410722"最近的(这3条数据是我伪造的,仅仅用于测试)。

2)多边形查询:

修改schema.xml配置文件:

<field name="station_position" type="location_jts" indexed="true" stored="true"/> <fieldType name="location_jts" class="solr.SpatialRecursivePrefixTreeFieldType" spatialContextFactory="com.spatial4j.core.context.jts.JtsSpatialContextFactory" distErrPct="0.025" maxDistErr="0.000009" units="degrees"/>

JtsSpatialContextFactory
当有Polygon多边形时会使用jts(需要把jts.jar放到solr webapp服务的lib下)。基本形状使用SpatialContext (spatial4j的类)。

Jts下载:http://sourceforge.net/projects/jts-topo-suite/

测试代码:

复制代码
        SolrQuery params = new SolrQuery();   //q=geo:"Intersects(POLYGON((-10 30, -40 40, -10 -20, 40 20, 0 0, -10 30)))"params.set("q", "station_position:\"Intersects(POLYGON((118 40, 118.5 40, 118.5 38, 118.3 35, 118 38,118 40)))\"");    params.set("start", "0");  //记录开始位置params.set("rows", "100");  //查询的行数params.set("fl", "*");
复制代码

返回在这个POLYGON内的所有结果集。

3)  地址分词搜索

在“点查询”的基础上加上一些地址信息,就可以做一些地理位置+地址信息的LBS应用。

Solr分词配置

这里使用了mmseg4j分词器:https://github.com/chenlb/mmseg4j-solr

Schema.xml配置:

复制代码
复制代码
<field name="station_address" type="textComplex" indexed="true" stored="true" multiValued="true"/> <fieldtype name="textComplex" class="solr.TextField" positionIncrementGap="100"> <analyzer> <tokenizer class="com.chenlb.mmseg4j.solr.MMSegTokenizerFactory" mode="complex" dicPath="dic"/> </analyzer> </fieldtype> <fieldtype name="textMaxWord" class="solr.TextField" positionIncrementGap="100"> <analyzer> <tokenizer class="com.chenlb.mmseg4j.solr.MMSegTokenizerFactory" mode="max-word" /> </analyzer> </fieldtype> <fieldtype name="textSimple" class="solr.TextField" positionIncrementGap="100"> <analyzer> <tokenizer class="com.chenlb.mmseg4j.solr.MMSegTokenizerFactory" mode="simple" dicPath="D:/my_dic" /> </analyzer> </fieldtype>
复制代码
复制代码

这里对“station_address”这个字段进行中文分词。

下载mmseg4j-core-1.10.0.jar和mmseg4j-solr-2.2.0.jar放到solr webapp服务的lib下。

测试代码:

复制代码
复制代码
    public static SolrQuery getPointAddressQuery(String address){SolrQuery params = new SolrQuery();String q_params = "station_address:"+address;params.set("q", q_params);params.set("fq", "{!geofilt}");        //距离过滤函数//params.set("fq","{!bbox}");          //距离过滤函数:圆的外接矩形params.set("pt", "118.227985 39.410722"); //当前经纬度params.set("sfield", "station_position"); //经纬度的字段params.set("d", "50"); //就近 d km的所有数据//params.set("score", "distance"); params.set("sort", "geodist() asc");  //根据距离排序:由近到远params.set("start", "0");  //记录开始位置params.set("rows", "100");  //查询的行数params.set("fl", "*,_dist_:geodist(),score");  return params; } public static void main(String[] args) { BaseStationSearch baseStationSearch = new BaseStationSearch(); baseStationSearch.IndexBaseStation(); //执行一次索引 //SolrQuery params = getPointQuery(); //SolrQuery params = getPolygonQuery(); SolrQuery params = getPointAddressQuery("鼓楼"); baseStationSearch.getAndPrintResult(params); } 
复制代码
复制代码

Search Results Count: 2

SolrDocument{station_id=12003, station_address=[江苏南京鼓楼东南大学], station_position=[118.227996 39.410733], _version_=1500226229258682377, _dist_=0.001559071, score=4.0452886}

SolrDocument{station_id=12004, station_address=[江苏南京鼓楼南京大学], station_position=[118.228996 39.411733], _version_=1500226229258682378, _dist_=0.14214091, score=4.0452886}

上面是测试的结果。

 

参考:

http://wiki.apache.org/solr/SpatialSearch

https://cwiki.apache.org/confluence/display/solr/Spatial+Search

http://tech.meituan.com/solr-spatial-search.html

人在山中,才知道,白云也可以抓上一把,苍翠竟有清甜的味道。 人在山中,才知道,高度永远是一个变量,而快乐则是附于中跋涉过程的函数。 人在山中,才知道,庄严是望远时的一种心境,高处才能指点江山。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/288105.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

「零门槛多语言 Python/C/C# 通用思想学习系列」第一篇:经典HelloWorld

环境说明 操作系统&#xff1a;Windows7 SP1 编辑器说明&#xff1a; Python&#xff1a;notepadC&#xff1a;devcC sharp&#xff08;C#&#xff09;&#xff1a;Visual Studio2019 注意&#xff1a; 标点&#xff1a;在编程中要使用英文的标点符号&#xff0c;必须切记数…

服务器2008 系统日志 提示打印机,介绍服务器日志出现打印机错误的解决方法

今天来聊聊一篇关于服务器日志出现打印机错误的解决方法的文章,现在就为大家来简单介绍下服务器日志出现打印机错误的解决方法,希望对各位小伙伴们有所帮助。windows2003日志提示&#xff0c;打印机 ****未知。登录之前&#xff0c;请与管理员联系&#xff0c;安装驱动程序。事…

js 错误/异常处理

为什么80%的码农都做不了架构师&#xff1f;>>> /*** 自定义错误处理*/ onerror handleError; function handleError(desc,page,line){alert("desc:"desc"\n""page:"page"\n""line:"line); } var s null; s.t…

多面体 (Multipatch)

多面体要素是一种可存储面集合的 GIS 对象,能够在数据库中将 3D 对象的边界表示为单个行。面可存储表示要素组成部分的纹理、颜色、透明度和几何信息。面中存储的几何信息可以是三角形、三角扇、三角条带或环,如下所示。 所有多面体都将…

Python turtle库实现基本剖析

有关turtle的相关使用请参考《python图形绘制库turtle中文开发文档及示例大全》 本篇文为turtle库的实现剖析&#xff0c;但不涉及 python 的 TK库。 开始 入口探寻 在turtle中&#xff0c;直走是使用 forward 或者 fd 函数&#xff1b;在本机安装好了 turtle 库后&#xf…

分享我做Dotnet9博客网站时积累的一些资料

从2019年使用WordPress搭建Dotnet9网站&#xff0c;到现在手撸代码开发&#xff0c;介绍中间使用的一些资源&#xff0c;绝无保留&#xff0c;希望对大家有用。1. 申请域名、搭建WordPress网站时间点&#xff1a;2019年11月申请Dotnet9域名&#xff0c;讲个实话&#xff0c;站长…

基于Azure Blob冷存储的数据压缩备份总结

基于上一篇的压缩算法对比分析报告&#xff0c;选择了LZ4算法的普通模式&#xff0c;其测试压缩率为28%&#xff0c;20G压缩时间为256s&#xff0c;估计1T的冷备时间为3.5h。 接下来&#xff0c;将23T的HBase历史数据进行了压缩冷备&#xff0c;压缩后大小为3.5T&#xff0c;冷…

Ubuntu使用VNC运行基于Docker的桌面系统

2019独角兽企业重金招聘Python工程师标准>>> docker-ubuntu-vnc-desktop From Docker Index docker pull dorowu/ubuntu-desktop-lxde-vnc Build yourself git clone https://github.com/fcwu/docker-ubuntu-vnc-desktop.git docker build --rm -t dorowu/ubuntu-de…

【ArcGIS风暴】ArcGIS个人数据库(.mdb)中矢量字段(如Shape_Length、Shape_Area)无法删除的解决办法

ArcGIS中,某些操作,比如空间连接(Spatial Join)结果属性表中会自动产生Shape_Length和Shape_Area字段,这些字段无法删除,如下: 即使在工具箱中的【删除字段】工具,也找不到这些字段,如图所示,给我们的数据入库工作带来了一定的困扰。 解决思路: mdb是个人数据库,基…

python thinker canvas create_arc 使用详解

版权声明&#xff1a;本文为博主原创文章&#xff0c;遵循 CC 4.0 BY-SA 版权协议&#xff0c;转载请附上原文出处链接和本声明。 本文链接&#xff1a;https://blog.csdn.net/A757291228/article/details/106739556 ———————————————— 版权声明&#xff1a;本文…

剑指offer之二叉搜索树和双向链表

1 问题 比如我们搜索二叉树如下&#xff0c;我们需要变成双向链表 2 分析 我们知道这个变成双向链接的时候是按照树的中序遍历打印的&#xff0c;我们只需要在中序遍历打印的时候操作该节点&#xff0c;我们可以用临时变量保存这个节点&#xff0c;同时我们也需要单独增加一…

text 热敏打印机_只要有想象力,打印机都能做游戏

不知道为什么&#xff0c;文本冒险游戏最近频频被人提及。这不&#xff0c;现在又出来一个基于实时打印的文本冒险游戏 —— Quest Smith。这位脑洞较大的创客名叫 Bekir Dağ &#xff0c;他用微型打印机和树莓派做出了这个手持游戏设备。关于文本冒险游戏的背景知识&#xff…

C#内建接口:IConvertible

这节来讲一个比较有意思的接口&#xff1a;IConvertible。IConvertible接口处于System.Runtime命名空间下&#xff0c;这个接口规定了一批ToXxx()方法&#xff0c;凡是实现了这个接口的方法&#xff0c;我们都可以尝试将其转换为自己想要的类型。IConvertible中有什么&#xff…

ArcGIS升级地理数据库

本博文教大家如何快速地将低版本地理数据库升级为高版本地理数据库。 方法一&#xff1a;使用“升级地理数据库”工具箱二、使用ArcCatalog地理数据库→右键→属性。三、创建不同版本的地理数据库

汉高软件服务器安装系统,如何安装了如指掌眼镜管理系统的服务器和客户端,还需要安装什么软件的?...

满意答案ven85202014.05.09采纳率&#xff1a;53% 等级&#xff1a;7已帮助&#xff1a;356人易软眼镜店管理系统它整合了整个眼镜店的收费、客户登记、验光单管理、配镜管理、财务管理和查旬报表以及库存管理一体化的管理系统。随时可以查看客户的验光单、对比&#xff0c;…

Python3 实现单例设计模式

单例模式的一般实现 饿汉式 懒汉式就是通过一个方法才能实现单例&#xff0c;我不是很常用&#xff0c;所以在此就写个饿汉式。以后再补懒汉式。 单例模式的核心作用是保证一个类只有一个该类型的对象。在一个对象被过多调用时避免过多的消耗内存&#xff0c;即可使用单例模式…

基于casbin的ABAC/RBAC权限实践

五一假期疫情封在家也没事做&#xff0c;就想来优化一下一个前端容器小项目之前的TODOlist里面有一项是权限这块时隔2年了还一直没有动手迟迟没搞主要还是我太懒了&#xff0c;哈哈 其实我一直想要找一个轻量级的权限通用方案权限的数据源可以切换&#xff0c;但是逻辑基本不用…

Python3 实现建造者模式

建造者模式 建造者模式用于创建复杂的对象。使用建造者模式可以使复杂的过程层次明了、清晰&#xff0c;把对象的创建以及使用进行了解耦。实际上从代码的角度上看&#xff0c;是进行了多次封装&#xff0c;使代码结构更为规范合理&#xff0c;层次结构更加鲜明。 在一个复杂…

系列网络服务器机柜,什么是网络机柜 网络机柜和服务器机柜有哪些区别【详解】...

【网络机柜】什么是网络机柜 网络机柜和服务器机柜区别服务器机柜和网络机柜的区别服务器机柜 &#xff1a;用来组合安装面板、插件、插箱、电子元件、器件和机械零件与部件&#xff0c;使其构成一个整体的安装箱。可以配置&#xff1a;专用固定托盘、专用滑动托盘、电源插排、…

【3D Max】3D max如何删除环境贴图

问题描述&#xff1a;在用3dm max贴图的时候&#xff0c;如果不选中对象&#xff0c;很容易将图贴到背景环境中去&#xff0c;情况如下所示&#xff1a; 解决办法有二&#xff1a; 一、不参与渲染 快捷键8&#xff0c;在“环境和效果”窗口中去掉“使用贴图”前面的√。 二、…