Skip to main content

Implementing GeoSpatial Search Using Lucene 4.X


In this blog post, i am going to explain how to implement GeoSpatial Search using Lucene 4.x.

Problem I am trying to Implement :

Finding Nearest Places Within Certain Radius Given the Location of User.

Sample Input Places Data :

id  name             latitude   longitude

1   Bangalore        12.9558    77.620979
        2   Cubbon Park      12.974045  77.591995      
        3   Tipu Palace      12.959365  77.573792
        4   Bangalore Palace 12.998095  77.592041
        5   Monkey Bar       12.97018   77.61219
        6   Cafe Cofee Day   12.992189  80.2348618

Code :

import java.io.File;
import java.io.IOException;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.Field.Store;
import org.apache.lucene.document.FieldType;
import org.apache.lucene.document.IntField;
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.index.IndexableField;
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.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.SimpleFSDirectory;
import org.apache.lucene.util.Version;

import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.distance.DistanceUtils;
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Shape;


public class SpatialSearch {
private IndexWriter indexWriter;
private IndexReader indexReader;
private IndexSearcher searcher;
private SpatialContext ctx;
private SpatialStrategy strategy;
public SpatialSearch(String indexPath) {
StandardAnalyzer a = new StandardAnalyzer(Version.LUCENE_43);
IndexWriterConfig iwc = new IndexWriterConfig(Version.LUCENE_43, a);
Directory directory;
try {
directory = new SimpleFSDirectory(new File(indexPath));
indexWriter = new IndexWriter(directory, iwc);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
this.ctx = SpatialContext.GEO;
SpatialPrefixTree grid = new GeohashPrefixTree(ctx, 11);
this.strategy = new RecursivePrefixTreeStrategy(grid, "location");
}
public void indexDocuments() throws IOException {
indexWriter.addDocument(newGeoDocument(1, "Bangalore", ctx.makePoint(12.9558, 77.620979)));
indexWriter.addDocument(newGeoDocument(2, "Cubbon Park", ctx.makePoint(12.974045, 77.591995)));
indexWriter.addDocument(newGeoDocument(3, "Tipu palace", ctx.makePoint(12.959365, 77.573792)));
indexWriter.addDocument(newGeoDocument(4, "Bangalore palace", ctx.makePoint(12.998095, 77.592041)));
indexWriter.addDocument(newGeoDocument(5, "Monkey Bar", ctx.makePoint(12.97018, 77.61219)));
indexWriter.addDocument(newGeoDocument(6, "Cafe Cofee day", ctx.makePoint(12.992189, 80.2348618)));
indexWriter.commit();
indexWriter.close();
}

private Document newGeoDocument(int id, String name, Shape shape) {

FieldType ft = new FieldType();
ft.setIndexed(true);
ft.setStored(true);

Document doc = new Document();
doc.add(new IntField("id", id, Store.YES));
doc.add(new Field("name", name, ft));
for(IndexableField f:strategy.createIndexableFields(shape)) {
doc.add(f);
}
doc.add(new StoredField(strategy.getFieldName(), ctx.toString(shape)));
return doc;
}
public void setSearchIndexPath(String indexPath) throws IOException{
this.indexReader = DirectoryReader.open(new SimpleFSDirectory(new File(indexPath)));
this.searcher = new IndexSearcher(indexReader);
}
public void search(Double lat, Double lng, int distance) throws IOException{
Point p = ctx.makePoint(lat, lng);
SpatialArgs args = new SpatialArgs(SpatialOperation.Intersects,
ctx.makeCircle(lat, lng, DistanceUtils.dist2Degrees(distance, DistanceUtils.EARTH_MEAN_RADIUS_KM)));
Filter filter = strategy.makeFilter(args);
ValueSource valueSource = strategy.makeDistanceValueSource(p);
Sort distSort = new Sort(valueSource.getSortField(false)).rewrite(searcher);
int limit = 10;
TopDocs topDocs = searcher.search(new MatchAllDocsQuery(), filter, limit, distSort);
ScoreDoc[] scoreDocs = topDocs.scoreDocs;
for(ScoreDoc s: scoreDocs) {
Document doc = searcher.doc(s.doc);
Point docPoint = (Point) ctx.readShape(doc.get(strategy.getFieldName()));
double docDistDEG = ctx.getDistCalc().distance(args.getShape().getCenter(), docPoint);
double docDistInKM = DistanceUtils.degrees2Dist(docDistDEG, DistanceUtils.EARTH_EQUATORIAL_RADIUS_KM);
System.out.println(doc.get("id") + "\t" + doc.get("name") + "\t" + docDistInKM + " km ");
}
}

/**
* @param args
* @throws IOException 
*/
public static void main(String[] args) throws IOException {
// TODO Auto-generated method stub
String indexPath = "/home/vishnu/lucene_practices/geo_spatial_index";
SpatialSearch s = new SpatialSearch(indexPath);
//Indexes sample documents
s.indexDocuments();
s.setSearchIndexPath(indexPath);
//Get Places Within 4 kilometers from cubbon park.
s.search(12.974045,77.591995, 4);

}

}



Output :

2 Cubbon Park 0.0 km
4 Bangalore palace 0.5752837176108795 km
3 Tipu palace 2.056590450260357 km
5 Monkey Bar 2.2499941525889806 km
1 Bangalore 3.255797190098085 km


In this blog post we learned how to implement

Basic GeoSpatial Queries like whether a point contains within a circle or not.

My Next Blog Post will talk about how to implement advanced spatial queries like 

geoInterseting - where one polygon intersects with another polygon/line.

geoWithIn - where one polygon lies completely within another polygon.



Comments

  1. Hi,
    I'm trying to use this script for my project, but haven't a good result(no result...). But i not use in my code your method indexDocuments because i have another process for saved. the question is, i need necessary use a method like indexDocuments? beacuase i save an id:String, lat:Double and lng:Double, and more other info, but i don't save other type. can u help me please?

    ReplyDelete
  2. Hi Francesco,
    Sorry for the late reply.
    You should use _id:String and Shape object instead of lat:Double and lng:Double.

    I mean you should create Shape Object using lat,lng "ctx.makePoint(12.9558, 77.620979)" as in my program.
    Otherwise just explore what 'ctx.toString(ctx.makePoint(12.9558, 77.620979))' returns and store in the new field.

    Using direct lat:double and lng:double wont work in lucene 4.x i guess.

    ReplyDelete
  3. Hi all,

    I think that in your code you may have mixed up latitude and longitude in the calls to "makePoint". The code for this methods is

    public Point makePoint(double x, double y) {
    verifyX(x);
    verifyY(y);
    return new PointImpl(x, y, this);
    }

    and the verification (when you use the world bounds for GEO) are -180<= x <= 180, and -90 <= y <= 90, which suggests to me that one should make calls such as

    ctx.makePoint(longitude, latitude)

    such as ctx.makePoint(77.620979, 12.9558) and *not* ctx.makePoint(12.9558, 77.620979). This came up as I was working with longitudes > 90.0.

    ReplyDelete

Post a Comment

Popular posts from this blog

Implementing Geospatial Bounding Box Search in Lucene 4.x

In my last blog post i explained about  Geospatial Radial Search  using Lucene 4.x. In continuation with this i am going to explain how to implement GeoSpatial Bounding Box Search using Lucene 4.x. Problem I am trying to Implement : Finding All Places Within Given Bounding Box. Sample Input Places Data : id  name             latitude   longitude                 //Bangalore places 1   Bangalore        12.9558    77.620979         2   Cubbon Park      12.974045  77.591995               3   Tipu Palace      12.959365  77.573792         4   Bangalore Palace 12.998095  77.592041         5   Monkey Bar       12.97018   77.61219         //Chennai places         6   Chennai    13.060422    80.249583         7   Elliot's Beach    12.998976    80.271286         8   Kapaleeshwar Temple    13.033889    80.269722 Code : import java.io.File; import java.io.IOException; import org.apache.lucene.analysis.standard.StandardAnalyzer; import org.apache.lucene.document.