SOLR i spatial search
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("*:*");
}
}