Apache SOLR jest bardzo dobrym narzędziem do spatial searchingu. W ogóle jest bardzo dobrym narzędziem (żeby nie było) :) A zatem idealnie sprawdzi się gdy zechcemy znaleźć wszystkie punkty (tudzień knajpy, puby, ogródki piwne, winiarnie, sklepy monopolowe nocne, cokolwiek) w zasięgu np. 500m od wybranej (obecnej) lokalizacji o 1am gdy nam jeszcze mało. Jak ?


Krok pierwszy to zaopatrzenie się w bazę danych geo. Odradzam darmową bazę z serwisu http://www.geonames.org/ . Ktoś celowo udostępnia ją za free. Dokładność dla Polski jest rzędu 15 kilometrów :) Czyli np. kody pocztowe z Ursynowa są gdzieś kawałek za Muranowem. Więc... jeśli chcemy robić coś komercyjnie to wykładamy te kilkadziesiąt dolców...


Krok drugi to załadowanie danych do dowolnej bazy danych. Dane są w formacie więc najprościej będzie użyć SSIS, ewentualnie BULK INSERT w MySQLu...

LOAD DATA LOCAL INFILE '/path/allbooks.csv' INTO TABLE geopostcodes FIELDS TERMINATED BY ';' LINES TERMINATED BY '\n' (country_code,postal_code,place_name,admin_name1,admin_code1,admin_name2,admin_code2,admin_name3,admin_code3,latitude,longitude,accuracy);

W powyższy sposób ładujemy jeden z plików danych do tabeli, o której wcześniejszym utworzeniu należy pamięŧać :)


Teraz trochę od tyłu... ładujemy dane. Po uruchomieniu instancji SOLR-a w przeglądarce wchodzimy na url http://localhost:8983/solr/geodataimport?command=full-import. Strona zwróci wynik natychmiast, a w konsoli SOLRa będzie można obserwować faktyczny progress. Proces usuwa przed importem bieżącą zawartość.


Konfigurujemy zawczasu handler, który mapuje proces importujący dane do odpowiedniego URLa.

<requestHandler name="/geodataimport" class="org.apache.solr.handler.dataimport.DataImportHandler">
  <lst name="defaults">
    <str name="config">db-data-config.xml</str>
  </lst>
</requestHandler>

Jak widać konfiguracja data-source powinna znaleźć się w pliku db-data-config.xml . Gdy już jar ze sterownikiem JDBC trafi do katalogu 'lib' SOLR-a, możemy przystąpić do zdefiniowania źródła danych.

<dataConfig>
  <dataSource driver="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost/geosolr" user="root" password="secretpass" />
  <document>
    <entity name="item" query="SELECT id,country_code,postal_code,place_name,admin_name1,admin_code1,admin_name2,admin_code2,admin_name3,admin_code3,CONCAT(CAST(latitude AS CHAR),',',CAST(longitude AS CHAR)) AS location,accuracy FROM geopostcodes">
      <field column="id" name="id" />
      <field column="country_code" name="country_code" />
      <field column="postal_code" name="postal_code" />
      <field column="place_name" name="place_name" />
      <field column="admin_name1" name="admin_name1" />
      <field column="admin_code1" name="admin_code1" />
      <field column="admin_name2" name="admin_name2" />
      <field column="admin_code2" name="admin_code1" />
      <field column="admin_name3" name="admin_name3" />
      <field column="admin_code3" name="admin_code3" />
      <field column="location" name="location" />
      <field column="accuracy" name="accuracy" />
    </entity>
  </document>
</dataConfig>

Bardzo ważny jest CONCAT który skleja koordynaty w jeden string rozdzielony znakiem spacji. Tylko taki format akceptuje SOLR. Do tego jeszcze prosta konfiguracja schema...

<fields>
  <field name="id" type="string" indexed="true" stored="true" required="true"/>
  <field name="country_code" type="string" indexed="true" stored="true"/>
  <field name="postal_code" type="string" indexed="true" stored="true"/>
  <field name="place_name" type="string" indexed="true" stored="true"/>
  <field name="location" type="location" indexed="true" stored="true"/>
  <field name="admin_name1" type="string" indexed="false" stored="true"/>
  <field name="admin_code1" type="string" indexed="false" stored="true"/>
  <field name="admin_name2" type="string" indexed="false" stored="true"/>
  <field name="admin_code2" type="string" indexed="false" stored="true"/>
  <field name="admin_name3" type="string" indexed="false" stored="true"/>
  <field name="admin_code3" type="string" indexed="false" stored="true"/>
  <field name="accuracy" type="plong" indexed="false" stored="true"/>
  <dynamicField name="*_coordinate" type="tdouble" indexed="true" stored="false"/>
</fields>
<uniqueKey>id</uniqueKey>
<defaultSearchField>id</defaultSearchField>

Nadszedł czas na odpytanie bazy. Prosta klasa parametryzuje query by wyszukać wszystkie obiekty w określonej odległości od wskazanego punktu...

public class SolrGeoQuery extends SolrQuery {

public SolrGeoQuery(String latitude, String longitude, int distance, int limit) { super(); this.setRows(limit); this.add("sfield","location");
this.add("sort","geodist() asc"); this.add("indent","true"); this.add("fq","{!geofilt}"); this.add("d", Integer.toString(distance)); this.add("pt", latitude + "," + longitude); this.setQuery("*:*"); } }