- Solr權威指南(下卷)
- 蘭小偉
- 8756字
- 2019-01-03 15:33:40
11.2 Solr地理空間查詢
Geospatial search是Solr中比較流行的一個功能,它支持基于地理位置進行搜索。實現在每個索引文檔中添加一個域,該域一個地理位置坐標點即經度和緯度,然后在查詢時請求Solr過濾出落在指定坐標點的半徑范圍內的索引文檔。Solr支持兩種主要的地理空間搜索實現方式,舊的實現方式是簡單的支持基于單個(緯度,經度)的半徑范圍內搜索,并且按照距離進行排序。新的地理空間搜索實現方式更復雜一些,它不僅能針對單個坐標點進行過濾,還支持對形狀(支持任意復雜的多邊形)進行過濾。簡單實現主要是在查詢時計算兩個坐標點的距離,過濾掉相距較遠的值。而目前的高級實現是以一系列網格坐標盒子模型來索引形狀,搜索轉變成跨多個被索引網格的查詢,而不需要在查詢時計算兩點之間的距離。簡單實現可以在數據量較小時運行,并且不需要索引額外的數據,然而高級實現方式通常適用于大數據量的情況下,而且它非常靈活,支持多種形狀的搜索。
11.2.1 Solr地理空間簡單查詢
Solr地理空間查詢的簡單實現支持基于單個坐標點(通常是提供經緯度)進行查詢,它能夠通過簡單的語法來實現圓或正方形范圍過濾。
要實現半徑范圍內搜索,首先你需要在schema.xml中定義一個域,如下所示:
<fieldType name="location" class="solr.LatLonType" subFieldSuffix="_coordinate" />
LatLonType類用于地理位置域定義,它包含一對(緯度,經度)坐標值,最后分割兩個坐標值映射到單獨的域。為了能夠成功映射到單獨的域,這些域也需要在schema.xml中存在。通過添加一個以指定后綴結尾的動態域來實現這些單獨域的定義,且動態域的域名稱后綴需要與域類型的subFieldSuffix屬性值保持一致,以下是配置示例:
<dynamicField name="*_coordinate" type="tdouble" indexed="true" stored="false" />
當測試數據導入完成之后,你可以采用多種方式來進行位置查詢,可以這樣查詢city:"San Francisco, CA"。即按照城市來查詢,但是這種方式并不能查詢離你附近位置的索引文檔,甚至潛在的還可能會返回不在你查詢的城市范圍內的文檔。
Solr提供了一種特定的Query Parser(即geofilt),它能解析(緯度,經度)并生成兩個單獨域同時計算與給定坐標點之間的距離(單位:千米),最后匹配距離指定坐標點指定地理區域內的所有索引文檔。比如我們想要查詢離"San Francisco, CA"半徑20千米范圍內的索引文檔,那么查詢語法如下所示:
http://localhost:8080/solr/geospatial/select? q=*:*& fq={! geofilt sfield=location pt=37.775, -122.419 d=20}
sfield參數用于指定表示地理位置的域,即在schema.xml中定義的LatLonType域,p(t即point的縮寫)參數表示緯度、經度的坐標點,d(即distance的縮寫)參數用于多少半徑范圍內(單位:千米),指定為20即表示以20千米為半徑畫圓,落在圓內的索引文檔將會被返回,如圖11-1所示。

圖11-1 指定半徑的圓內查詢
除了可以實現在指定半徑范圍內的位置進行查詢,還可以通過bbox filter來實現正方形范圍內的位置過濾,bbox filter和geofilt有點類似,而bbox filter是根據給定的半徑畫圓,然后再確定該圓的外切正方形,如圖11-2所示,bbox filter需要傳遞的參數與geofilt一樣,但是bbox filter是以正方形為邊界進行過濾的,而確定正方形邊界比確定圓計算速度要快,因此通常當要求范圍不需要太精確時可以采用正方形邊界來過濾,同時性能上稍微比geofilt好一些。如果你期望是在圓內過濾,但是又希望計算速度快,此時可以使用RPT域并且嘗試調大distErrPct值比,如0.1。示例如下:

圖11-2 指定半徑的2倍作為邊長的正方形邊界內查詢
http://localhost:8080/solr/geospatial/select? q=*:*& fq={! bbox sfield=location pt=37.775, -122.419 d=20}
目前為止,你已經知道如何執行一個快速的bbox filter過濾器去查找地理位置上附近的位置,以及如何使用更精確的geofilt filter過濾器來過濾指定半徑范圍內的文檔。對于geofilt而言,Solr計算了每個索引文檔離查詢時指定的坐標點之間的距離,因此Solr可以在其他方面使用這些距離值。比如Solr Cloud允許返回的索引文檔能夠按照距離值進行排序,或者將距離值隨著索引文檔一起返回。然而Solr已經支持這兩個功能。
除了可以根據指定的坐標點對距離值進行過濾之外,Solr還支持將通過geodist函數計算得到的距離值作為一個偽域隨著查詢結果集中的每個索引文檔一并返回。之前你已經知道了如何將一個函數的計算值作為一個偽域進行返回,返回geodist函數的計算值也與之如出一轍。geodist函數的使用語法為:geodist(sfield, latitude, longitude)。通過使用geodist函數,你可以返回每個文檔距離指定坐標點的距離值。下面的這個查詢示例演示了計算索引文檔距離(37.774 93, -122.419 42)這個坐標點的距離值并將其作為偽域返回:
http://localhost:8080/solr/geospatial/select? q=*:*& fl=id, city, distance:geodist(location,37.77493, -122.41942)
你會發現返回的結果集中每個索引文檔都包含了一個"distance"的偽域,同其他Function Query類似,可以隨意返回距離計算函數的返回值作為索引文檔的一個偽域一并返回。注意:因為地理位置相關的函數計算都需要執行一些重要的數學計算,而這些數學計算的執行開銷往往都很大,因此當你的索引文檔達到上百萬以上,對每個索引文檔執行距離計算函數就會顯得很慢,此時應使用geofilt filter來過濾掉一些索引文檔,減少計算量。
你不僅可以將距離計算值作為偽域進行返回,還可以根據距離值對索引文檔進行排序。在這點上,geodist函數的使用方式與其他函數并沒有什么不同。按照距離從近至遠對索引文檔進行排序,可以為sort參數添加geodist函數,使用示例如下所示:
http://localhost:8080/solr/geospatial/select? q=*:*& fl=id, city, distance:geodist(location,37.77493, -122.41942)& sort=geodist(location,37.77493, -122.41942) asc, score desc
返回的查詢結果集部分如下所示:
"response":{"numFound":4, "start":0, "docs":[ { "id":"3", "city":"San Francisco, CA", "distance":0.03772596784117343}, { "id":"4", "city":"Palo Alto, CA", "distance":43.17493506307893}, { "id":"1", "city":"Atlanta, GA", "distance":3436.669993915123}, { "id":"2", "city":"New York, NY", "distance":4128.9603389283575}]
以上示例中返回的結果集首先按照距離值從小到大進行排序,距離值相同的再按照索引文檔評分從高到低進行排序。當然也可以先按照評分排序再按照距離值排序。不僅僅可以結合多個Function Query,還可以結合相關性評分和地理距離作為復合性相關性評分的一個獨立因子。
以上的每個示例都是獨立演示的,如果想要在單個查詢請求中綜合使用距離值過濾、返回距離值為偽域、根據距離值進行排序,那么請看下面這個查詢示例:
http://localhost:8080/solr/geospatial/select? q=*:*& fq={! geofilt sfield=location pt=37.775, -122.419 d=20}& fl=*, distance:geodist(location, 37.775, -122.419)& sort=geodist(location, 37.775, -122.419) asc, score desc
上面的查詢示例看起來有點啰嗦,我們的坐標點參數指定了3次,geodist函數也使用了2次。其實你可以將這些輸入參數定義在請求的查詢文本中,然后geofilt、bbox以及geodist可以間接地引用這些參數,因此,可以將前面的查詢示例進行簡化,如下所示:
http://localhost:8080/solr/geospatial/select? q=*:*& fq={! geofilt}& fl=*, distance:geodist()& sort=geodist() asc, score desc& sfield=location&pt=37.775, -122.419&d=20
使用這種簡化語法,你還可以覆蓋任意地理空間查詢組件中顯式定義的本地參數,比如sfield、pt、d等參數。但是對于一般使用場景來說,這沒有必要,因為對于根據距離值過濾、根據距離值排序、返回距離值作為偽域這些操作都是基于相同的坐標點。
11.2.2 Solr地理空間高級查詢
上一節我們學習了Solr地理空間查詢中簡單的基于單個坐標點的文檔過濾。Solr支持更高級的實現,為每個索引文檔索引多個坐標點。這種高級地理空間查詢支持對任意多邊形進行索引而不僅僅是索引單個坐標點。這種功能有什么用途呢?當你想要為每個索引文檔索引多個坐標點時會很有用。想象下,假如你的索引數據表示的是一些飯店,其中有些飯店可能在全國有多個連鎖分店,因此可能有某些飯店對應了多個坐標點,此時你希望為每個飯店索引多個(緯度,經度)這樣的坐標點,在查詢時,與索引文檔的多個坐標點進行交互。當想要支持任意形狀(比如圓、正方形、或者任意的多邊形)的區域范圍內過濾時,會使用多邊形來描述多個坐標點,并為每個索引文檔索引一個形狀。當然也可以采用窮舉的方式將任意形狀內的多個坐標點索引到索引文檔中,但是當坐標點太多的時候就不太合適了,此時使用形狀去描述更為合理。采用這種方式的話,當用戶執行一個查詢,比如查詢100千米內的飯店,此時以10千米為半徑畫圓,然后每個索引文檔對應的多邊形與該圓的重疊部分的任意坐標點就是符合要求的。
為了高效的索引任意多邊形以及基于任意多邊形進行查詢,Solr提供了一個特殊的域類型:SpatialRecursivePrefixTreeFieldType。此域類型能夠將整個地球劃分為一個網格,在大多數的縮放級別上,地球都可以劃分為帶有一定數量區域的網格。這里我們將使用四個象限為例進行類比說明,每個象限又會被劃分為更小的4個象限,你可以遞歸任意深度繼續劃分下去,圖11-3形象展示了如何將象限劃分為3層。

圖11-3 將整個地球劃分為3個層級的象限,每一層可以繼續劃分4個象限
從圖11-3中可以看出,網格中的每個盒子都包含了一個唯一標識符來表示它所在層級和精度的等級。通過這種方式對整個地球進行建模,這樣就能高效地基于位置進行查詢而不是計算距離值。比如圖11-3中,假如你要定位某個城市,首先會在第1層中定位到box 0,然后會定位到box 03,最后會定位到box 032(當然可以劃分更多層以提高查詢精度),然后我們可以在Solr索引中對3個層進行索引,這樣使得地理空間查詢變得非常強大。假如想要查詢西半球的索引文檔,此時你只需要根據Term:"0" OR "2"在索引中的位置域上進行查詢即可,因為西半球在圖11-3的0和2區域。對網格進行索引然后基于網格進行查詢,查詢速度通常會比計算每個索引文檔離坐標點的距離值要快得多。
盡管從技術層面上來講,你沒有必要去理解:Solr地理空間查詢底層的基于網格的分層思想將索引位置轉變成索引網格的gridID(網格的唯一標識符),根據gridID能知道當前位置處在網格中哪一層哪個格子中。但是有個基本的了解有助于你更好地使用Solr的地理空間查詢,比如如何在索引創建或查詢時根據你的地理位置要求索引的精度來選擇一些配置項。
在Solr中,針對上述原理提供了不同的索引方式去實現,分別是GeohashPrefixTree類和QuadPrefixTree類,其中GeohashPrefixTree對應GeoHash算法(也采用了笛卡爾層的分層思想), QuadPrefixTree對應笛卡爾層的實現,兩者都是基于PrefixTree算法實現。
基于網格的位置系統利用前綴樹的概念將位置表示為一系列的前綴字符(比如31102)。SpatialRecursivePrefixTreeFieldType提供了一個前綴樹的抽象表示,它允許使用多網格分層的方式來索引和查詢地理空間數據,上面的示例描述了一個簡單的4網格實現。另外一個前綴樹實現是基于著名的Geohash算法,它幾乎是行業內的標準,因此它也是Solr中默認的實現。Geohash是專門設計用于對地球的地理位置進行建模的。當你在schema.xml中將Sp atialRecursivePrefixTreeFieldType的geo屬性設置為true,那么Geohash實現就會被啟用,其實默認geo屬性就是true。Geohash標準文檔很齊全,理解Geohash算法也不是本章的重點,因為它在概念上與4網格的前綴樹實現基本相似,想要更詳細地學習了解Geohash,請搜索"Geohash算法原理",會找到很多文章講解Geohash算法。
GeohashPrefixTree與QuadPrefixTree都繼承自SpatialPrefixTree這個前綴樹基類,都使用了網格分層思想,主要區別就是索引和查詢邏輯不一樣,比如,默認網格分層的層級不同,獲取子網格的方式不一樣。GeohashPrefixTree有32個子網格(編碼為0-z), QuadPrefixTree只有有4個子網格,編碼為ABCD,其中A為左上,B為右上,C為左下,D為右下,即相當于4個象限。
基本理解基于網格的搜索系統能夠讓你充分理解如何高效地使用Solr中的地理空間高級查詢,這一切關于將復雜的地理坐標和區域形狀映射成網格坐標系全部都封裝在Spatial-RecursivePrefixTreeFieldType類內部,你需要做的是在schema.xml中定義這個域類型,示例如下所示:
<fieldType name="location_rpt"class="solr.SpatialRecursivePrefixTreeFieldType" spatialContextFactory= "com.spatial4j.core.context.jts.JtsSpatialContextFactory" distErrPct="0.025" maxDistErr="0.000009" autoIndex="true" distanceUnits="degrees" /> <field name="location_rpt" type="location_rpt" indexed="true" stored="true" multiValued="true" />
其中:
?JtsSpatialContextFactory:當有Polygon多邊形時會使用jts(需要把jts.jar放到Solr war包的lib目錄下)。基本形狀使用SpatialContext(spatial4j的類)。
?distErrPct:定義非Point(坐標點)形狀的精度,范圍在0~0.5之間,默認0.025。該值決定了非Point的形狀在索引或查詢時的level(層級)(如geohash模式時就是geohash的長度)。distErrPct設置為0時取maxLevels(最大層級),即精度最大。
?units:表示計算單位,如數學中的角度degrees。此參數已經過時了,不推薦使用,建議使用distanceUnits參數代替units,參數可選值有degrees(角度), kilometers(千米), miles(英里)。
?format:表示形狀定義所使用的語法,可選值有WTK(默認值)、GeoJSON、LEGACY、POLY,關于其他語法請訪問如下鏈接詳細了解:
https://locationtech.github.io/spatial4j/apidocs/org/locationtech/spatial4j/io/PolyshapeReader.html。
?maxDistErr:定義非Point(坐標點)形狀的最大精度,如果此參數未指定,默認值是1米即稍微比0.000009度小一點點,此參數設置主要用于內部決定合適的最大層級。
?worldBounds:定義在ENVELOPE(minX, maxX, maxY, minY)語法中,x和y的合法數字范圍,如果域類型的geo="true",就使用標準的lat-lon(緯度-經度)來定義整個地球邊界,如果域類型的geo="false",你需要定義你自己的邊界。
?distCalculator:定義距離計算使用的算法,如果域類型的geo="true",那么默認使用"haversine"算法;如果域類型的geo="false",則默認會使用"cartesian"算法,其他可選值有"lawOfCosines"、"vincentySphere"、"cartesian^2"。
?prefixTree:定義網格的具體實現,因為PrefixTree(前綴樹,比如RecursivePrefixTree)將整個地球映射成一個網格,網格中的每個格子又會繼續劃分成下一個層級的網格。如果域類型的geo="true",那么默認的前綴樹實現就是geohash,否則就是quad(四網格形式), Geohash的每個層級采用32個格子,而quad每個層級只有4個格子。第3種選擇就是packedQuad,通常它比純quad的四網格形式性能更高效,packedQuad提供了大約20個層級,你可以設置更多。
?maxLevels:設置索引數據時網格層級的最大深度,一般通過指定maxDistErr參數來控制最終計算得到一個合適的最大層級深度數值會更直觀一些。
?geo:如果將geo參數設置為true,那么會使用緯度經度坐標,此時的數學模型通常會是球體;如果將geo參數設置為false,那么坐標點會是二維空間上的歐幾里得幾何或者笛卡爾積幾何中常見的(x, y)形式。
坐標點可以采用傳統的逗號分割方式來定義,比如緯度、經度,或者忽略逗號直接使用一個空格字符分隔,比如緯度經度,也可以采用POINT(x, y)語法來定義,示例如下所示:
<field name="location_rpt">43.17614, -80.57341</field> <field name="location_rpt">-80.57341 43.17614</field> <field name="location_rpt">POINT(-80.57341 43.17614)</field>
你可以使用LINESTRING語法來表示線段,示例如下所示:
<field name="location_rpt">LINESTRING(0 0,1 0,0 2)</field>
一個長方形有4個頂點,因此需要用4個點用一個空格字符分別分割來表示,4個點依次表示順序為MinX、MinY、MaxX、MaxY。示例如下所示:
<field name="location_rpt">-74.093 41.042-69.347 44.558</field>
上述代碼表示一個圓需要圓心坐標和一個半徑,為了表示更復雜的形狀,Solr提供了特定的語法來表示這些形狀,你需要將輸入參數包裹在特定語法內,示例如下所示:
<field name="location_rpt">Circle(37.775, -122.419 d=20)</field>
圓心坐標你依然可以采用前面的坐標點表示語法來進行定義,圓的半徑通過d參數來指定(單位:千米)。
為了能夠支持對任意復雜的形狀進行表示,Solr提供了使用WKT (well-known text)標準來定義任意的多邊形。盡管在Solr中常用的形狀一般是坐標點、圓、長方形,Solr通過使用Apache的Spatial4J類庫來對這些形狀進行支持,通過使用WKT來支持對任意多邊形的表示,使用WKT需要額外添加JTS(Java Topology Suite的縮寫,即Java拓撲套件)依賴。
注意
Apache Solr是基于Apache 2.0 License開源的,它允許你在自己的系統中使用Solr的任意代碼,你沒有義務必須分享你的代碼或者支付任何許可費用。JTS使用的是LGPL協議,JTS jar包并沒有包含在Solr源碼中。如果你想要在Solr中使用JTS,那么你需要額外添加JTS jar包依賴,只要你不修改JTS的源碼,那么你引用JTS通常認為是安全的,但是如果你修改了JTS源碼,那么你就必須要開源你的項目。因此當你選擇考慮使用JTS時,你需要考慮這個問題。
想在Solr中啟用WKT支持,需要添加jts jar包依賴到solr.war包的WEB-INF/lib目錄下,即apache-tomcat-7.0.55\webapps\solr\WEB-INF\lib下。jts jar包隨書源碼中有提供,也可以通過網絡搜索下載獲取到。請從隨書源碼中獲取"geospatial_jts" Core的配置文件創建Core,并通過執行IndexGeospatialJTS類導入測試數據,然后重啟Solr Server,這樣你的JTS就正式啟用了,我們可以演示一些強大的地理空間形狀索引和查詢功能。WKT允許你使用如下的語法來定義任意的多邊形:
<field name="location_rpt"> POLYGON((-10 30, -40 40, -10-20, 40 20, 0 0, -10 30)) </field>
使用JTS的語法來定義任意多邊形時你需要記住以下幾點:
??每個坐標點之間使用逗號分隔;
??每個坐標點定義格式為“經度緯度”;
??如果你想要定義閉合的形狀,那么第一個坐標點和最后一個坐標點定義需要相同;
?WKT坐標點會映射到有效的經度(+/-90°)和緯度(+/-180°)范圍內;
??只有圓支持南北極。
通過啟用對任意多邊形的索引,你就可以在Solr中基于這些形狀進行搜索,Solr的強大地理空間查詢功能也隨之解鎖。請執行下面這2個查詢示例,如下所示:
http://localhost:8080/solr/geospatial_jts/select? q=*:*& fq={! geofilt pt=37.775, -122.419 sfield=location_rpt d=5} http://localhost:8080/solr/geospatial_jts/select? q=*:*& fq={! bbox pt=37.775, -122.419 sfield=location_rpt d=5}
我們除了能夠使用類似上面示例那樣基于圓和正方形來查詢,還可以使用相同的形狀,使用SpatialRecursivePrefixTreeFieldType提供的更高級的基于網格的前綴樹系統來執行查詢,這類查詢的語法如下所示:
http://localhost:8080/solr/geospatial_jts/select? q=*:*& fq=location_rpt:"Intersects(POLYGON((-10 30, -40 40, -10-20, 40 20, 0 0, -10 30)))"
上面的查詢示例中我們定義了一個五邊形,然后Intersects表示相交,即索引文檔中我們在location_rpt域上索引的多邊形或者圓等形狀與查詢時指定的形狀時相交重疊,如果兩者有相交,那么就返回索引文檔。
除了可以使用Intersects(相交)操作,針對SpatialRecursivePrefixTreeFieldType域進行查詢,還支持IsWithin、Contains、IsDisjointTo操作,具體如表11-6所示。
表11-6 四類匹配操作

使用上面表格中的操作,你可以查詢被包含在指定的多邊形內部的所有索引文檔,查詢示例如下所示:
http://localhost:8080/solr/geospatial_jts/select? q=*:*& fl=id, location_rpt, city& fq=location_rpt:"IsWithin( POLYGON(( -85.4997 34.7442, -84.9723 30.6134, -81.2809 30.5255, -80.9294 32.0196, -83.3024 34.8321, -85.4997 34.7442)) ) distErrPct=0"
使用Boolean操作符(AND和OR)來連接多個形狀查詢,從而構建一個復雜的查詢。你不僅可以根據指定的形狀進行查詢,還可以使用兩個形狀之間的距離來影響你的查詢相關性,后續章節會詳細講解如何使用地理空間距離值來干預你的q參數構造的主查詢的相關性評分。
除了SpatialRecursivePrefixTreeFieldType,還可以使用RptWithGeometrySpatialField,它是SpatialRecursivePrefixTreeFieldType的派生子類,會額外在DocValues中存儲原始幾何結構,使用它能夠進行精確查詢。它同時還支持對坐標點進行索引。它的相交(默認操作)判定操作速度非常快,因為很多搜索結果可以作為一個精確命中而不需要返回進行幾何檢查。這個域類型與SpatialRecursivePrefixTreeFieldType域類型配置沒什么太大不同,它的distErrPct參數默認值是0.15,比SpatialRecursivePrefixTreeFieldType默認的0.025要大,因為使用網格純粹是出于性能考慮,而不是真正去表示形狀。
當索引數據中索引的形狀包含了很多頂點時,你可以配置使用如下這個緩存,比如有個域叫"geom",可以在solrconfig.xml中配置一個可選的緩存,配置如下所示:
<cache name="perSegSpatialFieldCache_geom" class="solr.LRUCache" size="256" initialSize="0" autowarmCount="100%" regenerator="solr.NoOpRegenerator"/>
當使用RptWithGeometrySpatialField這種域類型時,你可能不希望將這個域設置為stored="true",因為它已經存儲了冗余的DocValues值。但是你又想在查詢返回的結果集中返回這個域的域值,此時你可以使用geo formatter來獲取:
fl=geojson:[geo f=mySpatialField w=GeoJSON]
這里的f表示你的域名稱;w表示形狀定義語法格式,可選值為WTK和GeoJSON;前面的geojson是返回的偽域的別名。
此外還可以使用BBoxField域來對長方形進行索引,并且BBoxField域支持對邊界框進行查詢,它支持大部分的地理空間查詢判斷(比如相交、包含),而且還為重疊部分或者兩個長方形中間區域提供了增強型相關性模型。特別是它的相關性模型會非常有用,在schema.xml中配置BBoxField域的示例如下所示:
<field name="bbox" type="bbox" /> <fieldType name="bbox" class="solr.BBoxField" geo="true" units="kilometers" numberType="_bbox_coord" storeSubFields="false"/> <fieldType name="_bbox_coord" class="solr.TrieDoubleField" precisionStep="8" docValues="true" stored="false"/>
BBoxField實際上是基于4個其他數字域類型實例,它使用一個boolean變量標識國際日期變更線交叉。如果你想要使用它的相關性功能,那么docValues必須設置為true。對于geo、worldBounds、spatialContextFactory等參數域RPT(即SpatialRecursivePrefixTreeField Type域類型的簡稱)域類型,創建BBox Field會有所不同。比如你想要索引一個長方形,此時你需要添加一個bbox域,使用WKT/CQL的ENVELOPE語法來定義形狀的數據:
<field name="location_rpt">ENVELOPE(-10, 20, 15, 10)</field>
4個參數依次表示minX、maxX、maxY、minY。你可以使用使用WTK的POLYGON語法來定義一個矩形,或者使用GeoJSON語法(當設置format="GeoJSON", GeoJSON語法會被啟用)。
查詢的時候,可以使用{! bbox}查詢解析器,示例如下所示:
&q=*:*&fq={! bbox sfield=yoursfield}&pt=45.15, -93.85&d=5 或者 &q={! field f=bbox}Contains(ENVELOPE(-10, 20, 15, 10))
還可以通過score參數來指定相關性模型,示例如下所示:
&q={! field f=bbox score=overlapRatio}Intersects(ENVELOPE(-10, 20, 15, 10))
score參數可選值有overlapRatio、area、area2D等。
area通過球面數學(假定geo=true)進行打分,area2D簡單使用width * height, overlapRatio基于索引文檔的形狀重疊的區域面積大小來進行打分(分值范圍為[0-1])。關于overlapRatio的計算公式更詳細的內容請查閱BBoxOverlapRatioValueSource類的源碼。此外還有一個額外的queryTargetProportion參數,它允許劃分查詢端和索引段權重比例值,取值范圍為[0-1], 0.5表示兩邊權重相同。你可以在查詢時通過添加&debug=results參數來查詢更多有關評分計算的詳細信息。
geofilt和bbox支持一些共同參數,如表11-7所示。
表11-7 geofilt和bbox支持的參數

我們在函數查詢章節中介紹過geodist()這個地理空間計算函數,它同樣適用于RPT域(除了適用于LatLonType域之外)。然而遺憾的是,當使用前綴樹實現來索引形狀時會損失一些精度,因此此時通過geodist()函數計算的距離值可能會不太精確,但是這并不是什么大問題,只要你將distErrPct參數設置的足夠高。你可以就通過geofilt操作返回每個索引文檔的距離值來模擬geodist()函數。
回顧我們學習的geofilt query parser,它包含了一個性能優化點。首先geofilt會使用形狀的邊界過濾掉一些索引文檔,然后計算邊界內剩下的坐標點離中心坐標點的距離。因為geofilt會計算每個索引文檔和查詢時指定的坐標點之間的距離,因此你可以將這個距離值作為評分隨著索引文檔一并返回,具體請看下面的查詢示例:
http://localhost:8080/solr/geospatial_jts/select? sort=score asc& q={! geofilt pt=37.775, -122.419 sfield=location d=5 score=distance}
由于geofilt應用于q參數的主查詢,score=distance,那么意味著返回的每個索引文檔的評分計算方式就變成了geofilt計算的距離值了。除了可以指定score=distance,你還可以指定score=recipdistance, recipdistance是在geofilt計算的距離值基礎上再求倒數。如果你未指定score參數,那么score默認值是none,此時返回的每個索引文檔的評分都是固定的1.0。
如果想要為所有索引文檔進行評分,盡管那些索引文檔并不在geofilt過濾范圍內,此時可以設置filter=false來關閉geofilt query parser的文檔過濾功能,示例如下所示:
http://localhost:8080/solr/geospatial_jts/select? sort=score asc& q={! geofilt pt=37.775, -122.419 sfield=location_rpt d=5 score=distance filter=false}
通過將filter關閉,可以將geofilt轉變成跟geodist函數一樣用途,即都能返回距離值,同時不再使用filter來過濾索引文檔,最后將距離值作為每個索引文檔的評分。遺憾的是,因為geofilt并不是一個Function Query,所以如果想要根據geofilt返回的距離值進行排序同時又想要在函數中使用geofilt,那么此時可以如下執行查詢:
http://localhost:8080/solr/geospatial/select? sort=$distance asc& fl=id, distance:$distance&q=*:*& distance=query($distFilter)& distFilter={! geofilt pt=37.775, -122.419 sfield=location_rpt d=5 score=distance filter=true}
上面的查詢示例通過query函數將geofilt查詢包裝成一個Function Query,通過變量引用函數返回的距離值,然后可以在多個地方應用這個距離值,比如可以將這個變量作為偽域返回,可以根據這個變量表示的距離值對索引文檔進行排序。
第6章里我們學習了如何使用Facet對任意查詢進行統計。通過結合多個geofilt,可以很容易實現基于地理空間距離進行有趣的數據分析統計。為了使本章的學習更有趣,我們準備索引10萬條索引文檔。導入這些測試數據,請執行隨書源碼中的DistanceFacetDocGenerator測試類,導入之前請先創建好"distancefacet" Core。
導入成功后看到打印的提示信息:“totle documents: 103864”。每個索引文檔包含了一個location域(用經緯度表示),同時還有city域。我們可以創建一個Facet查詢,比如統計10千米以內、20千米以內、50千米以內、100千米以內等范圍內的索引文檔。為了展示一個更復雜的查詢示例,我們假設執行一個Facet查詢統計方圓50千米以內的前10位city,然后統計方圓20千米以內的前10位城市的統計數量。看起來蠻復雜的,下面的查詢示例演示了如何實現上面的需求:
http://localhost:8080/solr/distancefacet/select? q=*:*&rows=0& fq={! geofilt sfield=location pt=37.777, -122.420 d=80}& facet=true&facet.field=city& facet.limit=10
返回的查詢結果集如下所示:
"facet_fields":{ "city":[ "San Francisco, CA",11713, "San Jose, CA",3071, "Oakland, CA",1482, "Palo Alto, CA",1318, "Santa Clara, CA",1212, "Mountain View, CA",1045, "Sunnyvale, CA",1004, "Fremont, CA",726, "Redwood City, CA",633, "Berkeley, CA",599]},
先熱熱身,接下來我們要放大招了,請看下面的查詢示例:
http://localhost:8080/solr/distancefacet/select? q=*:*&rows=10& distanceFilter={! geofilt sfield=location pt=37.777, -122.420 score=distance filter=true d=20}& facet.limit=10&facet=true&facet.field=city&facet.sort=count& distance=query($distanceFilter)&sort=$distance asc&fl=id, city, distance:$distance& fq=_query_:{! geofilt sfield=location pt=37.777, -122.420 d=20}
上面的查詢示例中,我們首先q參數返回所有索引文檔,其次執行fq的geofilt過濾查詢,返回以(37.777, -122.420)為圓心,20千米為半徑的圓內的索引文檔。然后按照city域對剩余的索引文檔進行統計,對facet統計返回的結果集按照數量從高到低排序,同時限制Facet統計返回結果集數量為10,即實現對Top 10城市的統計。最后對返回的結果集中的索引文檔按照distanceFilter計算的距離值從小到大進行排序,即由近至遠進行排序,最終返回id、city、distance這3個域,其中distance表示距離值的偽域。fq查詢中我們通過_query語法將geofilt過濾轉換成了偽Term查詢,而_query_類似于偽域,看起來就好比在指定域上執行簡單的Term查詢,比如title:solr。你可以定義多個_query_來構造這種偽Term查詢,也可以使用AND或OR操作符來連接多個_query_查詢表達式,從而構造出一個超復雜的查詢。
此刻,你已經學會了Solr中的地理空間查詢,下一節我們將繼續學習Solr中的一些更有趣的數據統計分析功能:Pivot Facet。
- 精通JavaScript+jQuery:100%動態網頁設計密碼
- Android應用程序開發與典型案例
- Objective-C Memory Management Essentials
- Drupal 8 Blueprints
- C#程序設計實訓指導書
- 用Flutter極速構建原生應用
- ArcGIS By Example
- Clojure Reactive Programming
- Oracle 12c從入門到精通(視頻教學超值版)
- SEO教程:搜索引擎優化入門與進階(第3版)
- Raspberry Pi Blueprints
- Python無監督學習
- 基于MATLAB的控制系統仿真及應用
- Parallel Programming with Python
- Python全棧開發:數據分析