使用Lucene-Spatial實現集成地理位置的全文檢索

Lucene經過Spatial包提供了對基於地理位置的全文檢索的支持,最典型的應用場景就是:「搜索中關村附近1千米內的火鍋店,並按遠近排序」。使用Lucene-Spatial添加對地理位置的支持,和以前普通文本搜索主要有兩點區別:html

        1. 將座標信息轉化爲笛卡爾層,創建索引java

 

  1.      private void indexLocation(Document document, JSONObject jo)  
  2.         throws Exception {  
  3.   
  4.     double longitude = jo.getDouble("longitude");  
  5.     double latitude = jo.getDouble("latitude");  
  6.   
  7.     document.add(new Field("lat", NumericUtils  
  8.             .doubleToPrefixCoded(latitude), Field.Store.YES,  
  9.             Field.Index.NOT_ANALYZED));  
  10.     document.add(new Field("lng", NumericUtils  
  11.             .doubleToPrefixCoded(longitude), Field.Store.YES,  
  12.             Field.Index.NOT_ANALYZED));  
  13.   
  14.     for (int tier = startTier; tier <= endTier; tier++) {  
  15.         ctp = new CartesianTierPlotter(tier, projector,  
  16.                 CartesianTierPlotter.DEFALT_FIELD_PREFIX);  
  17.         final double boxId = ctp.getTierBoxId(latitude, longitude);  
  18.         document.add(new Field(ctp.getTierFieldName(), NumericUtils  
  19.                 .doubleToPrefixCoded(boxId), Field.Store.YES,  
  20.                 Field.Index.NOT_ANALYZED_NO_NORMS));  
  21.     }  
  22. }  
      private void indexLocation(Document document, JSONObject jo)
			throws Exception {

		double longitude = jo.getDouble("longitude");
		double latitude = jo.getDouble("latitude");

		document.add(new Field("lat", NumericUtils
				.doubleToPrefixCoded(latitude), Field.Store.YES,
				Field.Index.NOT_ANALYZED));
		document.add(new Field("lng", NumericUtils
				.doubleToPrefixCoded(longitude), Field.Store.YES,
				Field.Index.NOT_ANALYZED));

		for (int tier = startTier; tier <= endTier; tier++) {
			ctp = new CartesianTierPlotter(tier, projector,
					CartesianTierPlotter.DEFALT_FIELD_PREFIX);
			final double boxId = ctp.getTierBoxId(latitude, longitude);
			document.add(new Field(ctp.getTierFieldName(), NumericUtils
					.doubleToPrefixCoded(boxId), Field.Store.YES,
					Field.Index.NOT_ANALYZED_NO_NORMS));
		}
	}


        2. 搜索時,指定使用DistanceQueryFiltergit

 

 

  1. DistanceQueryBuilder dq = new DistanceQueryBuilder(latitude,  
  2.                 longitude, miles, "lat""lng",  
  3.                 CartesianTierPlotter.DEFALT_FIELD_PREFIX, true, startTier,  
  4.                 endTier);  
  5. DistanceFieldComparatorSource dsort = new DistanceFieldComparatorSource(  
  6.                 dq.getDistanceFilter());  
  7. Sort sort = new Sort(new SortField("geo_distance", dsort));  
DistanceQueryBuilder dq = new DistanceQueryBuilder(latitude,
				longitude, miles, "lat", "lng",
				CartesianTierPlotter.DEFALT_FIELD_PREFIX, true, startTier,
				endTier);
DistanceFieldComparatorSource dsort = new DistanceFieldComparatorSource(
				dq.getDistanceFilter());
Sort sort = new Sort(new SortField("geo_distance", dsort));


      下面是基於Lucene3.2.0和JUnit4.8.2的完整代碼。apache

 

 

  1. <dependencies>  
  2.     <dependency>  
  3.         <groupId>junit</groupId>  
  4.         <artifactId>junit</artifactId>  
  5.         <version>4.8.2</version>  
  6.         <type>jar</type>  
  7.         <scope>test</scope>  
  8.     </dependency>  
  9.     <dependency>  
  10.         <groupId>org.apache.lucene</groupId>  
  11.         <artifactId>lucene-core</artifactId>  
  12.         <version>3.2.0</version>  
  13.         <type>jar</type>  
  14.         <scope>compile</scope>  
  15.     </dependency>  
  16.     <dependency>  
  17.         <groupId>org.apache.lucene</groupId>  
  18.         <artifactId>lucene-spatial</artifactId>  
  19.         <version>3.2.0</version>  
  20.         <type>jar</type>  
  21.         <scope>compile</scope>  
  22.     </dependency>  
  23.     <dependency>  
  24.         <groupId>org.json</groupId>  
  25.         <artifactId>json</artifactId>  
  26.         <version>20100903</version>  
  27.         <type>jar</type>  
  28.         <scope>compile</scope>  
  29.     </dependency>  
  30. </dependencies>  
  <dependencies>
  	<dependency>
  		<groupId>junit</groupId>
  		<artifactId>junit</artifactId>
  		<version>4.8.2</version>
  		<type>jar</type>
  		<scope>test</scope>
  	</dependency>
  	<dependency>
  		<groupId>org.apache.lucene</groupId>
  		<artifactId>lucene-core</artifactId>
  		<version>3.2.0</version>
  		<type>jar</type>
  		<scope>compile</scope>
  	</dependency>
  	<dependency>
  		<groupId>org.apache.lucene</groupId>
  		<artifactId>lucene-spatial</artifactId>
  		<version>3.2.0</version>
  		<type>jar</type>
  		<scope>compile</scope>
  	</dependency>
  	<dependency>
  		<groupId>org.json</groupId>
  		<artifactId>json</artifactId>
  		<version>20100903</version>
  		<type>jar</type>
  		<scope>compile</scope>
  	</dependency>
  </dependencies>

 

 

        首先準備測試用的數據:json

 

  1. {"id":12,"title":"時尚碼頭美容美髮熱燙特價","longitude":116.3838183,"latitude":39.9629015}  
  2. {"id":17,"title":"審美我的美容美髮套餐","longitude":116.386564,"latitude":39.966102}  
  3. {"id":23,"title":"海底撈吃300送300","longitude":116.38629,"latitude":39.9629573}  
  4. {"id":26,"title":"僅98元!享原價335元李老爹","longitude":116.3846175,"latitude":39.9629125}  
  5. {"id":29,"title":"都美造型燙染美髮護理套餐","longitude":116.38629,"latitude":39.9629573}  
  6. {"id":30,"title":"僅售55元!原價80元的老舍茶館相聲下午場","longitude":116.0799914,"latitude":39.9655391}  
  7. {"id":33,"title":"僅售55元!原價80元的新笑聲客棧早場","longitude":116.0799914,"latitude":39.9655391}  
  8. {"id":34,"title":"僅售39元(紅色禮盒)!原價80元的平谷桃","longitude":116.0799914,"latitude":39.9655391}  
  9. {"id":46,"title":"僅售38元!原價180元地質禮堂白雪公主","longitude":116.0799914,"latitude":39.9655391}  
  10. {"id":49,"title":"僅99元!享原價342.7元自助餐","longitude":116.0799914,"latitude":39.9655391}  
  11. {"id":58,"title":"桑海教育暑期學生報名培訓九折優惠券","longitude":116.0799914,"latitude":39.9655391}  
  12. {"id":59,"title":"全國發貨:僅29元!貝玲妃超模粉紅高光光","longitude":116.0799914,"latitude":39.9655391}  
  13. {"id":65,"title":"海之嶼生態水族用品店抵用券","longitude":116.0799914,"latitude":39.9655391}  
  14. {"id":67,"title":"小區東門時尚燙染我的護理美髮套餐","longitude":116.3799914,"latitude":39.9655391}  
  15. {"id":74,"title":"《郭德綱相聲專輯》CD套裝","longitude":116.0799914,"latitude":39.9655391}  
{"id":12,"title":"時尚碼頭美容美髮熱燙特價","longitude":116.3838183,"latitude":39.9629015}
{"id":17,"title":"審美我的美容美髮套餐","longitude":116.386564,"latitude":39.966102}
{"id":23,"title":"海底撈吃300送300","longitude":116.38629,"latitude":39.9629573}
{"id":26,"title":"僅98元!享原價335元李老爹","longitude":116.3846175,"latitude":39.9629125}
{"id":29,"title":"都美造型燙染美髮護理套餐","longitude":116.38629,"latitude":39.9629573}
{"id":30,"title":"僅售55元!原價80元的老舍茶館相聲下午場","longitude":116.0799914,"latitude":39.9655391}
{"id":33,"title":"僅售55元!原價80元的新笑聲客棧早場","longitude":116.0799914,"latitude":39.9655391}
{"id":34,"title":"僅售39元(紅色禮盒)!原價80元的平谷桃","longitude":116.0799914,"latitude":39.9655391}
{"id":46,"title":"僅售38元!原價180元地質禮堂白雪公主","longitude":116.0799914,"latitude":39.9655391}
{"id":49,"title":"僅99元!享原價342.7元自助餐","longitude":116.0799914,"latitude":39.9655391}
{"id":58,"title":"桑海教育暑期學生報名培訓九折優惠券","longitude":116.0799914,"latitude":39.9655391}
{"id":59,"title":"全國發貨:僅29元!貝玲妃超模粉紅高光光","longitude":116.0799914,"latitude":39.9655391}
{"id":65,"title":"海之嶼生態水族用品店抵用券","longitude":116.0799914,"latitude":39.9655391}
{"id":67,"title":"小區東門時尚燙染我的護理美髮套餐","longitude":116.3799914,"latitude":39.9655391}
{"id":74,"title":"《郭德綱相聲專輯》CD套裝","longitude":116.0799914,"latitude":39.9655391}


     根據上面的測試數據,編寫測試用例,分別搜索座標(116.3838183, 39.96290153公里之內的「美髮」和所有內容,分別獲得的結果應該是4條和6條。app

 

 

  1. import static org.junit.Assert.assertEquals;  
  2. import static org.junit.Assert.fail;  
  3.   
  4. import java.util.List;  
  5.   
  6. import org.junit.Test;  
  7.   
  8. public class LuceneSpatialTest {  
  9.       
  10.     private static LuceneSpatial spatialSearcher = new LuceneSpatial();  
  11.   
  12.     @Test  
  13.     public void testSearch() {  
  14.         try {  
  15.             long start = System.currentTimeMillis();  
  16.             List<String> results = spatialSearcher.search("美髮"116.383818339.96290153.0);  
  17.             System.out.println(results.size()  
  18.                     + "個匹配結果,共耗時 "  
  19.                     + (System.currentTimeMillis() - start) + "毫秒。\n");  
  20.             assertEquals(4, results.size());  
  21.         } catch (Exception e) {  
  22.             fail("Exception occurs...");  
  23.             e.printStackTrace();  
  24.         }  
  25.     }  
  26.   
  27.     @Test  
  28.     public void testSearchWithoutKeyword() {  
  29.         try {  
  30.             long start = System.currentTimeMillis();  
  31.             List<String> results = spatialSearcher.search(null116.383818339.96290153.0);  
  32.             System.out.println( results.size()  
  33.                     + "個匹配結果,共耗時 "  
  34.                     + (System.currentTimeMillis() - start) + "毫秒.\n");  
  35.             assertEquals(6, results.size());  
  36.         } catch (Exception e) {  
  37.             fail("Exception occurs...");  
  38.             e.printStackTrace();  
  39.         }  
  40.     }  
  41. }  
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;

import java.util.List;

import org.junit.Test;

public class LuceneSpatialTest {
	
	private static LuceneSpatial spatialSearcher = new LuceneSpatial();

	@Test
	public void testSearch() {
		try {
			long start = System.currentTimeMillis();
			List<String> results = spatialSearcher.search("美髮", 116.3838183, 39.9629015, 3.0);
			System.out.println(results.size()
					+ "個匹配結果,共耗時 "
					+ (System.currentTimeMillis() - start) + "毫秒。\n");
			assertEquals(4, results.size());
		} catch (Exception e) {
			fail("Exception occurs...");
			e.printStackTrace();
		}
	}

	@Test
	public void testSearchWithoutKeyword() {
		try {
			long start = System.currentTimeMillis();
			List<String> results = spatialSearcher.search(null, 116.3838183, 39.9629015, 3.0);
			System.out.println( results.size()
					+ "個匹配結果,共耗時 "
					+ (System.currentTimeMillis() - start) + "毫秒.\n");
			assertEquals(6, results.size());
		} catch (Exception e) {
			fail("Exception occurs...");
			e.printStackTrace();
		}
	}
}


         下面是LuceneSpatial類,在構造函數中初始化變量和建立索引:ssh

 

  1. public class LuceneSpatial {  
  2.   
  3.     private Analyzer analyzer;  
  4.     private IndexWriter writer;  
  5.     private FSDirectory indexDirectory;  
  6.     private IndexSearcher indexSearcher;  
  7.     private IndexReader indexReader;  
  8.     private String indexPath = "c:/lucene-spatial";  
  9.   
  10.     // Spatial  
  11.     private IProjector projector;  
  12.     private CartesianTierPlotter ctp;  
  13.     public static final double RATE_MILE_TO_KM = 1.609344//英里和千米的比率  
  14.     public static final String LAT_FIELD = "lat";  
  15.     public static final String LON_FIELD = "lng";  
  16.     private static final double MAX_RANGE = 15.0// 索引支持的最大範圍,單位是公里  
  17.     private static final double MIN_RANGE = 3.0;  // 索引支持的最小範圍,單位是公里  
  18.     private int startTier;  
  19.     private int endTier;  
  20.   
  21.     public LuceneSpatial() {  
  22.         try {  
  23.             init();  
  24.         } catch (Exception e) {  
  25.             e.printStackTrace();  
  26.         }  
  27.     }  
  28.   
  29.     private void init() throws Exception {  
  30.         initializeSpatialOptions();  
  31.   
  32.         analyzer = new StandardAnalyzer(Version.LUCENE_32);  
  33.   
  34.         File path = new File(indexPath);  
  35.   
  36.         boolean isNeedCreateIndex = false;  
  37.   
  38.         if (path.exists() && !path.isDirectory())  
  39.             throw new Exception("Specified path is not a directory");  
  40.   
  41.         if (!path.exists()) {  
  42.             path.mkdirs();  
  43.             isNeedCreateIndex = true;  
  44.         }  
  45.   
  46.         indexDirectory = FSDirectory.open(new File(indexPath));  
  47.   
  48.         //創建索引  
  49.         if (isNeedCreateIndex) {  
  50.             IndexWriterConfig indexWriterConfig = new IndexWriterConfig(  
  51.                     Version.LUCENE_32, analyzer);  
  52.             indexWriterConfig.setOpenMode(OpenMode.CREATE_OR_APPEND);  
  53.             writer = new IndexWriter(indexDirectory, indexWriterConfig);  
  54.             buildIndex();  
  55.         }  
  56.   
  57.         indexReader = IndexReader.open(indexDirectory, true);  
  58.         indexSearcher = new IndexSearcher(indexReader);  
  59.   
  60.     }  
  61.   
  62.     @SuppressWarnings("deprecation")  
  63.     private void initializeSpatialOptions() {  
  64.         projector = new SinusoidalProjector();  
  65.         ctp = new CartesianTierPlotter(0, projector,  
  66.                 CartesianTierPlotter.DEFALT_FIELD_PREFIX);  
  67.         startTier = ctp.bestFit(MAX_RANGE / RATE_MILE_TO_KM);  
  68.         endTier = ctp.bestFit(MIN_RANGE / RATE_MILE_TO_KM);  
  69.     }  
  70.   
  71.   
  72.   
  73.     private int mile2Meter(double miles) {  
  74.         double dMeter = miles * RATE_MILE_TO_KM * 1000;  
  75.   
  76.         return (int) dMeter;  
  77.     }  
  78.   
  79.     private double km2Mile(double km) {  
  80.         return km / RATE_MILE_TO_KM;  
  81.     }  
public class LuceneSpatial {

	private Analyzer analyzer;
	private IndexWriter writer;
	private FSDirectory indexDirectory;
	private IndexSearcher indexSearcher;
	private IndexReader indexReader;
	private String indexPath = "c:/lucene-spatial";

	// Spatial
	private IProjector projector;
	private CartesianTierPlotter ctp;
	public static final double RATE_MILE_TO_KM = 1.609344; //英里和千米的比率
	public static final String LAT_FIELD = "lat";
	public static final String LON_FIELD = "lng";
	private static final double MAX_RANGE = 15.0; // 索引支持的最大範圍,單位是公里
	private static final double MIN_RANGE = 3.0;  // 索引支持的最小範圍,單位是公里
	private int startTier;
	private int endTier;

	public LuceneSpatial() {
		try {
			init();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private void init() throws Exception {
		initializeSpatialOptions();

		analyzer = new StandardAnalyzer(Version.LUCENE_32);

		File path = new File(indexPath);

		boolean isNeedCreateIndex = false;

		if (path.exists() && !path.isDirectory())
			throw new Exception("Specified path is not a directory");

		if (!path.exists()) {
			path.mkdirs();
			isNeedCreateIndex = true;
		}

		indexDirectory = FSDirectory.open(new File(indexPath));

		//創建索引
		if (isNeedCreateIndex) {
			IndexWriterConfig indexWriterConfig = new IndexWriterConfig(
					Version.LUCENE_32, analyzer);
			indexWriterConfig.setOpenMode(OpenMode.CREATE_OR_APPEND);
			writer = new IndexWriter(indexDirectory, indexWriterConfig);
			buildIndex();
		}

		indexReader = IndexReader.open(indexDirectory, true);
		indexSearcher = new IndexSearcher(indexReader);

	}

	@SuppressWarnings("deprecation")
	private void initializeSpatialOptions() {
		projector = new SinusoidalProjector();
		ctp = new CartesianTierPlotter(0, projector,
				CartesianTierPlotter.DEFALT_FIELD_PREFIX);
		startTier = ctp.bestFit(MAX_RANGE / RATE_MILE_TO_KM);
		endTier = ctp.bestFit(MIN_RANGE / RATE_MILE_TO_KM);
	}



	private int mile2Meter(double miles) {
		double dMeter = miles * RATE_MILE_TO_KM * 1000;

		return (int) dMeter;
	}

	private double km2Mile(double km) {
		return km / RATE_MILE_TO_KM;
	}

 

 

              建立索引的具體實現:函數

 

  1. private void buildIndex() {  
  2.     BufferedReader br = null;  
  3.     try {  
  4.         //逐行添加測試數據到索引中,測試數據文件和源文件在同一個目錄下  
  5.         br = new BufferedReader(new InputStreamReader(  
  6.                 LuceneSpatial.class.getResourceAsStream("data")));  
  7.         String line = null;  
  8.         while ((line = br.readLine()) != null) {  
  9.             index(new JSONObject(line));  
  10.         }  
  11.   
  12.         writer.commit();  
  13.     } catch (Exception e) {  
  14.         e.printStackTrace();  
  15.     } finally {  
  16.         if (br != null) {  
  17.             try {  
  18.                 br.close();  
  19.             } catch (IOException e) {  
  20.                 e.printStackTrace();  
  21.             }  
  22.         }  
  23.     }  
  24. }  
  25.   
  26. private void index(JSONObject jo) throws Exception {  
  27.     Document doc = new Document();  
  28.   
  29.     doc.add(new Field("id", jo.getString("id"), Field.Store.YES,  
  30.             Field.Index.ANALYZED));  
  31.   
  32.     doc.add(new Field("title", jo.getString("title"), Field.Store.YES,  
  33.             Field.Index.ANALYZED));  
  34.   
  35.     //將位置信息添加到索引中  
  36.     indexLocation(doc, jo);  
  37.   
  38.     writer.addDocument(doc);  
  39. }  
  40.   
  41. private void indexLocation(Document document, JSONObject jo)  
  42.         throws Exception {  
  43.   
  44.     double longitude = jo.getDouble("longitude");  
  45.     double latitude = jo.getDouble("latitude");  
  46.   
  47.     document.add(new Field("lat", NumericUtils  
  48.             .doubleToPrefixCoded(latitude), Field.Store.YES,  
  49.             Field.Index.NOT_ANALYZED));  
  50.     document.add(new Field("lng", NumericUtils  
  51.             .doubleToPrefixCoded(longitude), Field.Store.YES,  
  52.             Field.Index.NOT_ANALYZED));  
  53.   
  54.     for (int tier = startTier; tier <= endTier; tier++) {  
  55.         ctp = new CartesianTierPlotter(tier, projector,  
  56.                 CartesianTierPlotter.DEFALT_FIELD_PREFIX);  
  57.         final double boxId = ctp.getTierBoxId(latitude, longitude);  
  58.         document.add(new Field(ctp.getTierFieldName(), NumericUtils  
  59.                 .doubleToPrefixCoded(boxId), Field.Store.YES,  
  60.                 Field.Index.NOT_ANALYZED_NO_NORMS));  
  61.     }  
  62. }  
	private void buildIndex() {
		BufferedReader br = null;
		try {
			//逐行添加測試數據到索引中,測試數據文件和源文件在同一個目錄下
			br = new BufferedReader(new InputStreamReader(
					LuceneSpatial.class.getResourceAsStream("data")));
			String line = null;
			while ((line = br.readLine()) != null) {
				index(new JSONObject(line));
			}

			writer.commit();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (br != null) {
				try {
					br.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

	private void index(JSONObject jo) throws Exception {
		Document doc = new Document();

		doc.add(new Field("id", jo.getString("id"), Field.Store.YES,
				Field.Index.ANALYZED));

		doc.add(new Field("title", jo.getString("title"), Field.Store.YES,
				Field.Index.ANALYZED));

		//將位置信息添加到索引中
		indexLocation(doc, jo);

		writer.addDocument(doc);
	}

	private void indexLocation(Document document, JSONObject jo)
			throws Exception {

		double longitude = jo.getDouble("longitude");
		double latitude = jo.getDouble("latitude");

		document.add(new Field("lat", NumericUtils
				.doubleToPrefixCoded(latitude), Field.Store.YES,
				Field.Index.NOT_ANALYZED));
		document.add(new Field("lng", NumericUtils
				.doubleToPrefixCoded(longitude), Field.Store.YES,
				Field.Index.NOT_ANALYZED));

		for (int tier = startTier; tier <= endTier; tier++) {
			ctp = new CartesianTierPlotter(tier, projector,
					CartesianTierPlotter.DEFALT_FIELD_PREFIX);
			final double boxId = ctp.getTierBoxId(latitude, longitude);
			document.add(new Field(ctp.getTierFieldName(), NumericUtils
					.doubleToPrefixCoded(boxId), Field.Store.YES,
					Field.Index.NOT_ANALYZED_NO_NORMS));
		}
	}


          搜索的具體實現:測試

 

 

  1. public List<String> search(String keyword, double longitude,  
  2.         double latitude, double range) throws Exception {  
  3.     List<String> result = new ArrayList<String>();  
  4.   
  5.     double miles = km2Mile(range);  
  6.       
  7.     DistanceQueryBuilder dq = new DistanceQueryBuilder(latitude,  
  8.             longitude, miles, "lat""lng",  
  9.             CartesianTierPlotter.DEFALT_FIELD_PREFIX, true, startTier,  
  10.             endTier);  
  11.   
  12.     //按照距離排序  
  13.     DistanceFieldComparatorSource dsort = new DistanceFieldComparatorSource(  
  14.             dq.getDistanceFilter());  
  15.     Sort sort = new Sort(new SortField("geo_distance", dsort));  
  16.   
  17.     Query query = buildQuery(keyword);  
  18.   
  19.     //搜索結果  
  20.     TopDocs hits = indexSearcher.search(query, dq.getFilter(),  
  21.             Integer.MAX_VALUE, sort);  
  22.     //得到各條結果相對應的距離  
  23.     Map<Integer, Double> distances = dq.getDistanceFilter()  
  24.             .getDistances();  
  25.   
  26.     for (int i = 0; i < hits.totalHits; i++) {  
  27.         final int docID = hits.scoreDocs[i].doc;  
  28.   
  29.         final Document doc = indexSearcher.doc(docID);  
  30.   
  31.         final StringBuilder builder = new StringBuilder();  
  32.         builder.append("找到了: ")  
  33.                 .append(doc.get("title"))  
  34.                 .append(", 距離: ")  
  35.                 .append(mile2Meter(distances.get(docID)))  
  36.                 .append("米。");  
  37.         System.out.println(builder.toString());  
  38.   
  39.         result.add(builder.toString());  
  40.     }  
  41.   
  42.     return result;  
  43. }  
  44.   
  45. private Query buildQuery(String keyword) throws Exception {  
  46.     //若是沒有指定關鍵字,則返回範圍內的全部結果  
  47.     if (keyword == null || keyword.isEmpty()) {  
  48.         return new MatchAllDocsQuery();  
  49.     }  
  50.     QueryParser parser = new QueryParser(Version.LUCENE_32, "title",  
  51.             analyzer);  
  52.   
  53.     parser.setDefaultOperator(Operator.AND);  
  54.   
  55.     return parser.parse(keyword.toString());  
  56. }  
	public List<String> search(String keyword, double longitude,
			double latitude, double range) throws Exception {
		List<String> result = new ArrayList<String>();

		double miles = km2Mile(range);
		
		DistanceQueryBuilder dq = new DistanceQueryBuilder(latitude,
				longitude, miles, "lat", "lng",
				CartesianTierPlotter.DEFALT_FIELD_PREFIX, true, startTier,
				endTier);

		//按照距離排序
		DistanceFieldComparatorSource dsort = new DistanceFieldComparatorSource(
				dq.getDistanceFilter());
		Sort sort = new Sort(new SortField("geo_distance", dsort));

		Query query = buildQuery(keyword);

		//搜索結果
		TopDocs hits = indexSearcher.search(query, dq.getFilter(),
				Integer.MAX_VALUE, sort);
		//得到各條結果相對應的距離
		Map<Integer, Double> distances = dq.getDistanceFilter()
				.getDistances();

		for (int i = 0; i < hits.totalHits; i++) {
			final int docID = hits.scoreDocs[i].doc;

			final Document doc = indexSearcher.doc(docID);

			final StringBuilder builder = new StringBuilder();
			builder.append("找到了: ")
					.append(doc.get("title"))
					.append(", 距離: ")
					.append(mile2Meter(distances.get(docID)))
					.append("米。");
			System.out.println(builder.toString());

			result.add(builder.toString());
		}

		return result;
	}

	private Query buildQuery(String keyword) throws Exception {
		//若是沒有指定關鍵字,則返回範圍內的全部結果
		if (keyword == null || keyword.isEmpty()) {
			return new MatchAllDocsQuery();
		}
		QueryParser parser = new QueryParser(Version.LUCENE_32, "title",
				analyzer);

		parser.setDefaultOperator(Operator.AND);

		return parser.parse(keyword.toString());
	}

       

 

             

執行測試用例,能夠獲得下面的結果:ui

 

  1. 找到了: 時尚碼頭美容美髮熱燙特價, 距離: 0米。  
  2. 找到了: 都美造型燙染美髮護理套餐, 距離: 210米。  
  3. 找到了: 審美我的美容美髮套餐, 距離: 426米。  
  4. 找到了: 小區東門時尚燙染我的護理美髮套餐, 距離: 439米。  
  5. 4個匹配結果,共耗時 119毫秒。  
  6.   
  7. 找到了: 時尚碼頭美容美髮熱燙特價, 距離: 0米。  
  8. 找到了: 僅98元!享原價335元李老爹, 距離: 68米。  
  9. 找到了: 海底撈吃300送300, 距離: 210米。  
  10. 找到了: 都美造型燙染美髮護理套餐, 距離: 210米。  
  11. 找到了: 審美我的美容美髮套餐, 距離: 426米。  
  12. 找到了: 小區東門時尚燙染我的護理美髮套餐, 距離: 439米。  
  13. 6個匹配結果,共耗時 3毫秒.  
找到了: 時尚碼頭美容美髮熱燙特價, 距離: 0米。
找到了: 都美造型燙染美髮護理套餐, 距離: 210米。
找到了: 審美我的美容美髮套餐, 距離: 426米。
找到了: 小區東門時尚燙染我的護理美髮套餐, 距離: 439米。
4個匹配結果,共耗時 119毫秒。

找到了: 時尚碼頭美容美髮熱燙特價, 距離: 0米。
找到了: 僅98元!享原價335元李老爹, 距離: 68米。
找到了: 海底撈吃300送300, 距離: 210米。
找到了: 都美造型燙染美髮護理套餐, 距離: 210米。
找到了: 審美我的美容美髮套餐, 距離: 426米。
找到了: 小區東門時尚燙染我的護理美髮套餐, 距離: 439米。
6個匹配結果,共耗時 3毫秒.


            參考文獻:

 

            Lucene-Spatial的原理介紹:http://www.nsshutdown.com/projects/lucene/whitepaper/locallucene.htm

            GeoHash:http://en.wikipedia.org/wiki/Geohash

            兩篇示例(其中大部分代碼就來自於這裏):

            Spatial search with Lucene

相關文章
相關標籤/搜索