Geo search with eZ Find - search for nearest locations

By Ivo Lukač -

In this blog post we present a simple example on how to build a spatial search with eZFind. The most common usage of the spatial search would be finding  the nearest locations available and this is what we are describing here.

Prerequisites

1. eZFind 2.2 extension with Solr configured and working. We need 2.2 version because it introduces new Solr geopoint fieldtype. It is basically a two-dimensional double for storing longitude and latitude:

<fieldType name="geopoint" class="solr.PointType" dimension="2" subFieldType="double"/>

eZPublish attributes for location should have a suffix „_gpt“ when indexed by eZFind:

<dynamicField name="*_gpt" type="geopoint" indexed="true" stored="true"/>

Searching for the nearest objects can also be achieved with older versions of eZFind by defining 2 fields based on double type. There is a great thread about this here

2. Preparing location data in eZPublish. We took the ezgmaplocation extension (which comes included in the latest eZPublish versions) as datatype for storing location. But there are also other extensions that could be used for this purpose and it can be developed from scratch. Important thing is to have latitude and longitude prepared for indexation.

3. Indexing. For indexing gmaplocation datatype we used unofficial eZFind code from this link provided by Paul Borgermans. Hopefully it will be implemented in the next version of eZFind.
IMPORTANT NOTE: currently there is a bug within solr when using field names with dash „-„: https://issues.apache.org/jira/browse/SOLR-1172 and the indexing code creates such field names (e.g. subattr_gmaps_location-coordinates_gpt). To solve this issue the easiest way is to create additional solr field under the <fields> node in the extension/ezfind/java/solr/conf/schema.xml:

<field name="gmaps_coordinates" type="geopoint" indexed="true" stored="true" />

and define rule for automatical copying of location data to that field (under the <schema> node in schema.xml):

<copyField source="subattr_gmaps_location-coordinates_gpt" dest="gmaps_coordinates"/>

FYI if some other geo location datatype is used geopoint solr field (*_gpt) should be updated in following format: LATITUDE,LANGITUDE (use name without dashes to skip the copying part).

Reindex & check if data is in the Solr index over /solr/admin url.

search

Search on mobile phone

Query time boosting with distance

There are 2 main methods on how to sort the search results in Solr by distance.

  • by using distance function in sort parameter: e.g.  sort=dist(2, geopoint1, geopoint2) desc
  • by using relevance sorting and query time boosting:  _val_:recip(dist(2, geopoint1, geopoint2),1,1,0)

Direct sorting is not as tunable as sorting based on score (relevance) so we decided to use the second method.

The base for this method is the dist() Solr function. There are more ways how distance can be calculated with this function: taxicab method, euclidean method, etc. (more about it here). For small distances euclidean method is good enough. Formula is simple:

dist() <=> sqrt( (lat1-lat2)2+(lng1-lng2)2&nbsp;)

For bigger distances (thousands of km) Haversine function should be used (more about it here). 

Because we are using distance for boosting only (search result will not return the distance), the square root is not needed and we can use faster function sqedist().  Used formula is then even more simpler:

sqedist() <=> (lat1-lat2)2+(lng1-lng2)2&nbsp;

More info here.

Before boosting we need to kind of normalize the output of the sqedist() function and recip() functions is used for this task. More info here.

Finally, our solr function for boosting looks like this:

recip(sqedist(2, geopoint1, geopoint2),1,1,0)

To boost with this function when querying, following string should be placed before the searching text:

_val_:"recip(sqedist(gmaps_coordinates,vector(48.166085,-104.326172)),1,1,0)"

More about the whole topic: http://wiki.apache.org/solr/SpatialSearch

result

Search results based on distance

Testing

To test if the method works fine use /solr/admin url again. You will need a geopoint value (latitude and longitude) as a reference point. Easiest way to get it is to use maps.google.com. Pick a spot with a right mouse click and then click on „Center map here“ . Geo point value of the selected spot should be filled in the field under the Link button (upper right corner) as 'll' http get parameter.

Once you get the desired point, enter following into /solr/admin search field:

_val_:"recip( sqedist( gmaps_coordinates, vector(YOUR_LAT,YOUR_LONG) ), 1, 1, 0 )"YOUR SEARCH TERM

Search results should be sorted with nearest first.

Implementation

First thing is to prepare the reference location – the point from where is distance calculated. This is highly dependent on what you are trying to accomplish. In our case the reference point was the mobile phone location. We used  W3C  geolocation API (http://www.w3.org/TR/geolocation-API) to get the location data from the client. The location itself is extracted from GPS or calculated by BTS triangulation.

Location is then put in get parameters together with search text. In content/search.tpl templateboosting string is prepared like this:

{if ezhttp( 'spot', 'get', 'hasVariable' )} 
    {def $dboost=concat('_val_:\"recip(sqedist(attr_location_gpt,vector(',ezhttp( 'spot', 'get' ),')),1,1,0)\"')}
{/if}

And finally included in search function:

{set search=fetch( ezfind, search, hash(query,concat($dboost,$search_text)))}

And that's it.

Comments

This site uses cookies. Some of these cookies are essential, while others help us improve your experience by providing insights into how the site is being used. For more detailed information on the cookies we use, please check our Privacy Policy.