solr的空间查询(查询地图周围坐标)
原文網(wǎng)址:http://www.cnblogs.com/hanhuibing/articles/5680616.html
基于Solr的空間搜索
如果需要對帶經(jīng)緯度的數(shù)據(jù)進(jìn)行檢索,比如查找當(dāng)前所在位置附近1000米的酒店,一種簡單的方法就是:獲取數(shù)據(jù)庫中的所有酒店數(shù)據(jù),按經(jīng)緯度計(jì)算距離,返回距離小于1000米的數(shù)據(jù)。
這種方式在數(shù)據(jù)量小的時(shí)候比較有效,但是當(dāng)數(shù)據(jù)量大的時(shí)候,檢索的效率是很低的,本文介紹使用Solr的Spatial Query進(jìn)行空間搜索。
空間搜索原理
空間搜索,又名Spatial Search(Spatial Query),基于空間搜索技術(shù),可以做到:
1)對Point(經(jīng)緯度)和其他的幾何圖形建索引
2)根據(jù)距離排序
3)根據(jù)矩形,圓形或者其他的幾何形狀過濾搜索結(jié)果
在Solr中,空間搜索主要基于GeoHash和Cartesian Tiers 2個(gè)概念來實(shí)現(xiàn):
GeoHash算法
通過GeoHash算法,可以將經(jīng)緯度的二維坐標(biāo)變成一個(gè)可排序、可比較的的字符串編碼。
在編碼中的每個(gè)字符代表一個(gè)區(qū)域,并且前面的字符是后面字符的父區(qū)域。其算法的過程如下:
根據(jù)經(jīng)緯度計(jì)算GeoHash二進(jìn)制編碼
地球緯度區(qū)間是[-90,90], 如某緯度是39.92324,可以通過下面算法對39.92324進(jìn)行逼近編碼:
1)區(qū)間[-90,90]進(jìn)行二分為[-90,0),[0,90],稱為左右區(qū)間,可以確定39.92324屬于右區(qū)間[0,90],給標(biāo)記為1;
2)接著將區(qū)間[0,90]進(jìn)行二分為 [0,45),[45,90],可以確定39.92324屬于左區(qū)間 [0,45),給標(biāo)記為0;
3)遞歸上述過程39.92324總是屬于某個(gè)區(qū)間[a,b]。隨著每次迭代區(qū)間[a,b]總在縮小,并越來越逼近39.928167;
4)如果給定的緯度(39.92324)屬于左區(qū)間,則記錄0,如果屬于右區(qū)間則記錄1,這樣隨著算法的進(jìn)行會(huì)產(chǎn)生一個(gè)序列1011 1000 1100 0111 1001,序列的長度跟給定的區(qū)間劃分次數(shù)有關(guān)。
同理,地球經(jīng)度區(qū)間是[-180,180],對經(jīng)度116.3906進(jìn)行編碼的過程也類似:
組碼
通過上述計(jì)算,緯度產(chǎn)生的編碼為1011 1000 1100 0111 1001,經(jīng)度產(chǎn)生的編碼為1101 0010 1100 0100 0100。偶數(shù)位放經(jīng)度,奇數(shù)位放緯度,把2串編碼組合生成新串:11100 11101 00100 01111 00000 01101 01011 00001。
最后使用用0-9、b-z(去掉a, i, l, o)這32個(gè)字母進(jìn)行base32編碼,首先將11100 11101 00100 01111 00000 01101 01011 00001轉(zhuǎn)成十進(jìn)制 28,29,4,15,0,13,11,1,十進(jìn)制對應(yīng)的編碼就是wx4g0ec1。同理,將編碼轉(zhuǎn)換成經(jīng)緯度的解碼算法與之相反,具體不再贅述。
由上可知,字符串越長,表示的范圍越精確。當(dāng)GeoHash base32編碼長度為8時(shí),精度在19米左右,而當(dāng)編碼長度為9時(shí),精度在2米左右,編碼長度需要根據(jù)數(shù)據(jù)情況進(jìn)行選擇。不過從GeoHash的編碼算 法中可以看出它的一個(gè)缺點(diǎn),位于邊界兩側(cè)的兩點(diǎn),雖然十分接近,但編碼會(huì)完全不同。實(shí)際應(yīng)用中,可以同時(shí)搜索該點(diǎn)所在區(qū)域的其他八個(gè)區(qū)域的點(diǎn),即可解決這 個(gè)問題。
Cartesian Tiers 笛卡爾層
笛卡爾分層模型的思想是將經(jīng)緯度轉(zhuǎn)換成更大粒度的分層網(wǎng)格,該模型創(chuàng)建了很多的地理層,每一層在前一層的基礎(chǔ)上細(xì)化切分粒度,每一個(gè)網(wǎng)格被分配一個(gè)ID,代表一個(gè)地理位置。
每層以2的平方遞增,所以第一層為4個(gè)網(wǎng)格,第二層為16 個(gè),所以整個(gè)地圖的經(jīng)緯度將在每層的網(wǎng)格中體現(xiàn):
那么如何構(gòu)建這樣的索引結(jié)構(gòu)呢,其實(shí)很簡單,只需要對應(yīng)笛卡爾層的層數(shù)來構(gòu)建域即可,一個(gè)域或坐標(biāo)對應(yīng)多個(gè)tiers層次。也即是 tiers0->field_0,tiers1->field_1,tiers2->field_2,……,tiers19->field_19。 (一般20層即可)。每個(gè)對應(yīng)笛卡爾層次的域?qū)⒏鶕?jù)當(dāng)前這條記錄的經(jīng)緯度通過笛卡爾算法計(jì)算出歸屬于當(dāng)前層的網(wǎng)格,然后將gridId(網(wǎng)格唯一標(biāo)示)以 term的方式存入索引。這樣每條記錄關(guān)于笛卡爾0-19的域?qū)⒍紩?huì)有一個(gè)gridId對應(yīng)起來。但是查詢的時(shí)候一般是需要查周邊的地址,那么可能周邊的 范圍超過一個(gè)網(wǎng)格的范圍,那么實(shí)際操作過程是根據(jù)經(jīng)緯度和一個(gè)距離確定出需要涉及查詢的從19-0(從高往低查)若干層對應(yīng)的若干網(wǎng)格的數(shù)據(jù)。那么一個(gè)經(jīng) 緯度周邊地址的查詢只需要如下圖圓圈內(nèi)的數(shù)據(jù):
由上可知,基于Cartesian Tier的搜索步驟為:
1、根據(jù)Cartesian Tier層獲得坐標(biāo)點(diǎn)的地理位置gridId
2、與系統(tǒng)索引gridId匹配計(jì)算
3、計(jì)算結(jié)果集與目標(biāo)坐標(biāo)點(diǎn)的距離返回特定范圍內(nèi)的結(jié)果集合
使用笛卡爾層,能有效縮減少過濾范圍,快速定位坐標(biāo)點(diǎn)。
基于Solr的空間搜索實(shí)戰(zhàn)
Solr已經(jīng)提供了3種filedType來進(jìn)行空間搜索:
1)? LatLonType(用于平面坐標(biāo),而不是大地坐標(biāo))
2)? SpatialRecursivePrefixTreeFieldType(縮寫為RPT)
3)? BBoxField(用于邊界索引查詢)
本文重點(diǎn)介紹使用SpatialRecursivePrefixTreeFieldType,不僅可以用點(diǎn),也可以用于多邊形的查詢。
1、配置Solr
首先看下數(shù)據(jù):
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>這里重點(diǎn)是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
默認(rèn)為true,值為true的情況下坐標(biāo)基于球面坐標(biāo)系,采用Geohash的方式;值為false的情況下坐標(biāo)基于2D平面的坐標(biāo)系,采用Euclidean/Cartesian的方式。
distErrPct
定義非Point圖形的精度,范圍在0-0.5之間。該值決定了非Point的圖形索引或查詢時(shí)的level(如geohash模式時(shí)就是geohash編碼的長度)。當(dāng)為0時(shí)取maxLevels,即精度最大,精度越大將花費(fèi)更多的空間和時(shí)間去建索引。
maxDistErr/maxLevels:maxDistErr
定義了索引數(shù)據(jù)的最高層maxLevels,上述定義為0.000009,根據(jù) GeohashUtils.lookupHashLenForWidthHeight(0.000009, 0.000009)算出編碼長度為11位,精度在1米左右,直接決定了Point索引的term數(shù)。maxLevels優(yōu)先級高于maxDistErr, 即有maxLevels的話maxDistErr失效。詳見SpatialPrefixTreeFactory.init()方法。不過一般使用 maxDistErr。
units
單位是degrees。
worldBounds
世界坐標(biāo)值:”minX minY maxX maxY”。 geo=true即geohash模式時(shí),該值默認(rèn)為”-180 -90 180 90”。geo=false即quad時(shí),該值為Java double類型的正負(fù)邊界,此時(shí)需要指定該值,設(shè)置成”-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) { //添加基站數(shù)據(jù)到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!"); }這里使用“經(jīng)度 緯度”這樣的字符串格式將經(jīng)緯度索引到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:農(nóng)業(yè) |
| fl | 返回字段,如fl=poi_id,store_name |
| pt | 坐標(biāo)點(diǎn),如pt=54.729696,-98.525391 |
| d | 搜索半徑,如 d=10表示10km范圍內(nèi) |
| sfield | 指定坐標(biāo)索引字段,如sfield=geo |
| defType | 指定查詢類型可以取 dismax和edismax,edismax支持boost函數(shù)相乘作用,dismax是通過累加方式計(jì)算最后的score. |
| qf | 指定權(quán)重字段:qf=store_name^10+poi_location_p^5 |
| score | 排序字段根據(jù)qf定義的字段defType定義的方式計(jì)算得到score排序輸出 |
其中有幾種常見的Solr支持的幾何操作:
WITHIN:在內(nèi)部
CONTAINS:包含關(guān)系
DISJOINT:不相交
Intersects:相交(存在交集)
1)點(diǎn)查詢
測試代碼:查詢距離某個(gè)點(diǎn)pt距離為d的集合
SolrQuery params = new SolrQuery(); params.set("q", "*:*"); params.set("fq", "{!geofilt}"); //距離過濾函數(shù) params.set("pt", "118.227985 39.410722"); //當(dāng)前經(jīng)緯度 params.set("sfield", "station_position"); //經(jīng)緯度的字段 params.set("d", "50"); //就近 d km的所有數(shù)據(jù) //params.set("score", "kilometers"); params.set("sort", "geodist() asc"); //根據(jù)距離排序:由近到遠(yuǎn) params.set("start", "0"); //記錄開始位置 params.set("rows", "100"); //查詢的行數(shù) params.set("fl", "*,_dist_:geodist(),score");//查詢的結(jié)果中添加距離和score返回結(jié)果集:
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}
從這部分結(jié)果集中可以看出,前3條數(shù)據(jù)是離目標(biāo)點(diǎn)"118.227985 39.410722"最近的(這3條數(shù)據(jù)是我偽造的,僅僅用于測試)。
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
當(dāng)有Polygon多邊形時(shí)會(huì)使用jts(需要把jts.jar放到solr webapp服務(wù)的lib下)?;拘螤钍褂肧patialContext (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"); //查詢的行數(shù)params.set("fl", "*");返回在這個(gè)POLYGON內(nèi)的所有結(jié)果集。
3)? 地址分詞搜索
在“點(diǎn)查詢”的基礎(chǔ)上加上一些地址信息,就可以做一些地理位置+地址信息的LBS應(yīng)用。
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”這個(gè)字段進(jìn)行中文分詞。
下載mmseg4j-core-1.10.0.jar和mmseg4j-solr-2.2.0.jar放到solr webapp服務(wù)的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}"); //距離過濾函數(shù)//params.set("fq","{!bbox}"); //距離過濾函數(shù):圓的外接矩形params.set("pt", "118.227985 39.410722"); //當(dāng)前經(jīng)緯度params.set("sfield", "station_position"); //經(jīng)緯度的字段params.set("d", "50"); //就近 d km的所有數(shù)據(jù)//params.set("score", "distance"); params.set("sort", "geodist() asc"); //根據(jù)距離排序:由近到遠(yuǎn)params.set("start", "0"); //記錄開始位置params.set("rows", "100"); //查詢的行數(shù)params.set("fl", "*,_dist_:geodist(),score"); return params; } public static void main(String[] args) { BaseStationSearch baseStationSearch = new BaseStationSearch(); baseStationSearch.IndexBaseStation(); //執(zhí)行一次索引 //SolrQuery params = getPointQuery(); //SolrQuery params = getPolygonQuery(); SolrQuery params = getPointAddressQuery("鼓樓"); baseStationSearch.getAndPrintResult(params); }Search Results Count: 2
SolrDocument{station_id=12003, station_address=[江蘇南京鼓樓東南大學(xué)], station_position=[118.227996 39.410733], _version_=1500226229258682377, _dist_=0.001559071, score=4.0452886}
SolrDocument{station_id=12004, station_address=[江蘇南京鼓樓南京大學(xué)], station_position=[118.228996 39.411733], _version_=1500226229258682378, _dist_=0.14214091, score=4.0452886}
上面是測試的結(jié)果。
?
參考:
http://wiki.apache.org/solr/SpatialSearch
https://cwiki.apache.org/confluence/display/solr/Spatial+Search
http://tech.meituan.com/solr-spatial-search.html
人在山中,才知道,白云也可以抓上一把,蒼翠竟有清甜的味道。 人在山中,才知道,高度永遠(yuǎn)是一個(gè)變量,而快樂則是附于中跋涉過程的函數(shù)。 人在山中,才知道,莊嚴(yán)是望遠(yuǎn)時(shí)的一種心境,高處才能指點(diǎn)江山。?總結(jié)
以上是生活随笔為你收集整理的solr的空间查询(查询地图周围坐标)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 物联网智能硬件设备常见攻击方法
- 下一篇: 基于Azure Blob冷存储的数据压缩