天天看點

Lucene5學習之Spatial地理位置搜尋

 現在手機app滿天飛,我想大家都用過這個功能:【搜尋我附近的飯店或飯店】之類的功能,類似這樣的地理位置搜尋功能非常适用,因為它需要利用到使用者目前的地理位置資料,是以使用者角度出發,找到符合使用者自身需求的資訊,應用傳回的資訊對于使用者來說滿意度會比較高,可見,地理位置空間搜尋在提高使用者體驗方面有至關重要的作用。在lucene中,地理位置空間搜尋是借助spatial子產品來實作的。

         要實作地理位置空間搜尋,我們首先需要對地理位置資料建立索引,比較容易想到的就是把經度和緯度存入索引,可是這樣做,有個弊端,因為地理位置資料(經緯度)是非常精細的,一般兩個地點相差就0.0幾,這樣我們需要建構的索引體積會很大,這會顯著減慢你的搜尋速度。在精确度上采取折衷的方法通常是将緯度和經度封裝到層中。您可以将每個層看作是地圖的特定部分的縮放級别,比如位于美國中央上方的第 2 層幾乎包含了整個北美,而第 19 層可能隻是某戶人家的後院。尤其是,每個層都将地圖分成 2層 # 的箱子或網格。然後給每個箱子配置設定一個号碼并添加到文檔索引中。如果希望使用一個字段,那麼可以使用 geohash編碼方式将緯度/經度編碼到一個 string 中。geohash 的好處是能夠通過切去散列碼末尾的字元來實作任意的精度。在許多情況下,相鄰的位置通常有相同的字首。

      同樣比較重要的,就是距離計算,給定兩個坐标點需要你計算這兩個點之間的距離,至于怎麼計算,這取決于你對地球怎麼進行模組化,一般對于距離計算精度要求不是很精确的(誤差在10-20米範圍内能接受的話)

采用平面模型就夠了。當然你也可以計算球面模型,這樣計算精度更精确,但更耗cpu,意味着計算時間更長,需要自己去優化。

     下面給出一個spatial使用示例代碼:

Lucene5學習之Spatial地理位置搜尋

package com.yida.framework.lucene5.spatial;  

import org.apache.lucene.document.document;  

import org.apache.lucene.document.field;  

import org.apache.lucene.document.numericdocvaluesfield;  

import org.apache.lucene.document.storedfield;  

import org.apache.lucene.index.directoryreader;  

import org.apache.lucene.index.indexreader;  

import org.apache.lucene.index.indexwriter;  

import org.apache.lucene.index.indexwriterconfig;  

import org.apache.lucene.queries.function.valuesource;  

import org.apache.lucene.search.filter;  

import org.apache.lucene.search.indexsearcher;  

import org.apache.lucene.search.matchalldocsquery;  

import org.apache.lucene.search.scoredoc;  

import org.apache.lucene.search.sort;  

import org.apache.lucene.search.sortfield;  

import org.apache.lucene.search.topdocs;  

import org.apache.lucene.spatial.spatialstrategy;  

import org.apache.lucene.spatial.prefix.recursiveprefixtreestrategy;  

import org.apache.lucene.spatial.prefix.tree.geohashprefixtree;  

import org.apache.lucene.spatial.prefix.tree.spatialprefixtree;  

import org.apache.lucene.spatial.query.spatialargs;  

import org.apache.lucene.spatial.query.spatialoperation;  

import org.apache.lucene.store.directory;  

import org.apache.lucene.store.ramdirectory;  

import org.wltea.analyzer.lucene.ikanalyzer;  

import com.spatial4j.core.context.spatialcontext;  

import com.spatial4j.core.distance.distanceutils;  

import com.spatial4j.core.shape.point;  

import com.spatial4j.core.shape.shape;  

/** 

 * lucene地理位置查詢測試 

 *  

 * @author lanxiaowei 

 */  

public class lucenespatialtest {  

    /** spatial上下文 */  

    private spatialcontext ctx;  

    /** 提供索引和查詢模型的政策接口 */  

    private spatialstrategy strategy;  

    /** 索引目錄 */  

    private directory directory;  

    /** 

     * spatial初始化 

     */  

    protected void init() {  

        // spatialcontext也可以通過spatialcontextfactory工廠類來建構  

        this.ctx = spatialcontext.geo;  

        //網格最大11層  

        int maxlevels = 11;  

        // spatialprefixtree也可以通過spatialprefixtreefactory工廠類建構  

        spatialprefixtree grid = new geohashprefixtree(ctx, maxlevels);  

        this.strategy = new recursiveprefixtreestrategy(grid, "mygeofield");  

        // 初始化索引目錄  

        this.directory = new ramdirectory();  

    }  

    private void indexpoints() throws exception {  

        indexwriterconfig iwconfig = new indexwriterconfig(new ikanalyzer());  

        indexwriter indexwriter = new indexwriter(directory, iwconfig);  

        //這裡的x,y即經緯度,x為longitude(經度),y為latitude(緯度)   

        indexwriter.adddocument(newsampledocument(2,  

                ctx.makepoint(-80.93, 33.77)));  

        /** wkt表示法:point(longitude,latitude)*/  

        indexwriter.adddocument(newsampledocument(4,  

                ctx.readshapefromwkt("point(60.9289094 -50.7693246)")));  

        indexwriter.adddocument(newsampledocument(20, ctx.makepoint(0.1, 0.1),  

                ctx.makepoint(0, 0)));  

        indexwriter.close();  

     * 建立document索引對象 

     *  

     * @param id 

     * @param shapes 

     * @return 

    private document newsampledocument(int id, shape... shapes) {  

        document doc = new document();  

        doc.add(new storedfield("id", id));  

        doc.add(new numericdocvaluesfield("id", id));  

        for (shape shape : shapes) {  

            for (field f : strategy.createindexablefields(shape)) {  

                doc.add(f);  

            }  

            point pt = (point) shape;  

            doc.add(new storedfield(strategy.getfieldname(), pt.getx() + " "  

                    + pt.gety()));  

        }  

        return doc;  

     * 地理位置搜尋 

     * @throws exception 

    private void search() throws exception {  

        indexreader indexreader = directoryreader.open(directory);  

        indexsearcher indexsearcher = new indexsearcher(indexreader);  

        // 按照id升序排序  

        sort idsort = new sort(new sortfield("id", sortfield.type.int));  

        //搜尋方圓200千米範圍以内,這裡-80.0, 33.0分别是目前位置的經緯度,以目前位置為圓心,200千米為半徑畫圓  

        //注意後面的earth_mean_radius_km表示200的機關是千米,看到km了麼。  

        spatialargs args = new spatialargs(spatialoperation.intersects,  

                ctx.makecircle(-80.0, 33.0, distanceutils.dist2degrees(200,  

                        distanceutils.earth_mean_radius_km)));  

        //根據spatialargs參數建立過濾器  

        filter filter = strategy.makefilter(args);  

        //開始搜尋  

        topdocs docs = indexsearcher.search(new matchalldocsquery(), filter,  

                10, idsort);  

        document doc1 = indexsearcher.doc(docs.scoredocs[0].doc);  

        string doc1str = doc1.getfield(strategy.getfieldname()).stringvalue();  

        int spaceidx = doc1str.indexof(' ');  

        double x = double.parsedouble(doc1str.substring(0, spaceidx));  

        double y = double.parsedouble(doc1str.substring(spaceidx + 1));  

        double doc1distdeg = ctx  

                .calcdistance(args.getshape().getcenter(), x, y);  

        system.out.println("(longitude,latitude):" + "(" + x + "," + y + ")");  

        system.out.println("doc1distdeg:" + doc1distdeg * distanceutils.deg_to_km);  

        system.out.println(distanceutils.degrees2dist(doc1distdeg,distanceutils.earth_mean_radius_km));  

        //定義一個坐标點(x,y)即(經度,緯度)即目前使用者所在地點  

        point pt = ctx.makepoint(60, -50);  

        //計算目前使用者所在坐标點與索引坐标點中心之間的距離即目前使用者地點與每個待比對地點之間的距離,deg_to_km表示以km為機關  

        valuesource valuesource = strategy.makedistancevaluesource(pt,  

                distanceutils.deg_to_km);  

        //根據命中點與目前位置坐标點的距離遠近降序排,距離數字大的排在前面,false表示降序,true表示升序  

        sort distsort = new sort(valuesource.getsortfield(false))  

                .rewrite(indexsearcher);  

        topdocs topdocs = indexsearcher.search(new matchalldocsquery(), 10,  

                distsort);  

        scoredoc[] scoredocs = topdocs.scoredocs;  

        for (scoredoc scoredoc : scoredocs) {  

            int docid = scoredoc.doc;  

            document document = indexsearcher.doc(docid);  

            int gotid = document.getfield("id").numericvalue().intvalue();  

            string geofield = document.getfield(strategy.getfieldname()).stringvalue();  

            int xy = geofield.indexof(' ');  

            double xpoint = double.parsedouble(geofield.substring(0, xy));  

            double ypoint = double.parsedouble(geofield.substring(xy + 1));  

            double distdeg = ctx  

                    .calcdistance(args.getshape().getcenter(), xpoint, ypoint);  

            double juli = distanceutils.degrees2dist(distdeg,distanceutils.earth_mean_radius_km);  

            system.out.println("docid:" + docid + ",id:" + gotid + ",distance:" + juli + "km");  

        /*args = new spatialargs(spatialoperation.intersects, ctx.makecircle( 

                -80.0, 33.0, 1)); 

        spatialargs args2 = new spatialargsparser().parse( 

                "intersects(buffer(point(-80 33),1))", ctx); 

        system.out.println("args2:" + args2.tostring());*/  

        indexreader.close();  

    public static void main(string[] args) throws exception {  

        lucenespatialtest lucenespatialtest = new lucenespatialtest();  

        lucenespatialtest.init();  

        lucenespatialtest.indexpoints();  

        lucenespatialtest.search();  

}  

        有關wtk 空間資料表示法參考資料:

        wkt - 概念

wkt - 幾何對象

    wkt可以表示的幾何對象包括:點,線,多邊形,tin(不規則三角網)及多面體。可以通過幾何集合的方式來表示不同次元的幾何對象。

    幾何物體的坐标可以是2d(x,y),3d(x,y,z),4d(x,y,z,m),加上一個屬于線性參照系統的m值。

    以下為幾何wkt字串樣例:

point(6 10)

linestring(3 4,10 50,20 25)

polygon((1 1,5 1,5 5,1 5,1 1),(2 2,2 3,3 3,3 2,2 2))

multipoint(3.5 5.6, 4.8 10.5)

multilinestring((3 4,10 50,20 25),(-5 -8,-10 -8,-15 -4))

multipolygon(((1 1,5 1,5 5,1 5,1 1),(2 2,2 3,3 3,3 2,2 2)),((6 3,9 2,9 4,6 3)))

geometrycollection(point(4 6),linestring(4 6,7 10))

point zm (1 1 5 60)

point m (1 1 80)

point empty

multipolygon empty

wkt - 空間參照系統

    一個表示空間參照系統的wkt字串描述了空間物體的測地基準、大地水準面、坐标系統及地圖投影。

    wkt在許多gis程式中被廣泛采用。esri亦在其shape檔案格式(*.prj)中使用wkt。

    以下是空間參照系統的wkt表示樣例:

compd_cs["osgb36 / british national grid + odn",

    projcs["osgb 1936 / british national grid",

        geogcs["osgb 1936",

            datum["osgb_1936",

                spheroid["airy 1830",6377563.396,299.3249646,authority["epsg","7001"]],

                towgs84[375,-111,431,0,0,0,0],

                authority["epsg","6277"]],

            primem["greenwich",0,authority["epsg","8901"]],

            unit["dmsh",0.0174532925199433,authority["epsg","9108"]],

            axis["lat",north],

            axis["long",east],

            authority["epsg","4277"]],

        projection["transverse_mercator"],

        parameter["latitude_of_origin",49],

        parameter["central_meridian",-2],

        parameter["scale_factor",0.999601272],

        parameter["false_easting",400000],

        parameter["false_northing",-100000],

        unit["metre",1,authority["epsg","9001"]],

        axis["e",east],

        axis["n",north],

        authority["epsg","27700"]],

    vert_cs["newlyn",

        vert_datum["ordnance datum newlyn",2005,authority["epsg","5101"]],

        axis["up",up],

        authority["epsg","5701"]],

    authority["epsg","7405"]]

        關于如何對地球進行模組化方面的知識,請自己google學習,比如平面模組化,球面模組化,曼哈頓距離等等,平面模組化一般采用勾股定理或其變體就能解決,球面模組化一般是采用求大圓弧長來解決,在lucene spatial中有haversine 和 geohash haversine兩個公式實作。至于haversine公式的算法以及geoahash編碼的算法啊自己google學習去吧。demo源碼請看底下的附件!!!

        如果你還有什麼問題請加我Q-q:7-3-6-0-3-1-3-0-5,

或者加裙

Lucene5學習之Spatial地理位置搜尋

一起交流學習!

轉載:http://iamyida.iteye.com/blog/2204455

繼續閱讀