Toggle menu

Location Based Searches

The SOLR engine contains a range of features you can use to carry out location based searches. This article looks at the different ways you can pass location related functions and queries to SOLR using the iCMAPI worker and CSSearchMultiple. The full SOLR documentation can be found at SOLR Spatial Search (opens new window).

Storing Location Based Data

Before you can start searching for locations, you'll need to index some location data in your SOLR collection.

The examples in this article all use a set of iCM objects, of a type called FORM_OBJECTSWITHLOCATIONS. These objects were created by submitting a form with a database save action several times. The form has two fields, a text field called LOCATIONNAME and a map field called LOCATION that stores lat/long values. Both fields are set as searchable.

In reality you're more likely to be searching for iCM articles that have location data saved in their article extras (the GOSS event location template does this) or you may have indexed a custom collection, brining non-iCM content and location data into your SOLR instance.

Searching and Sorting by Distance from a Point

When content is returned from SOLR one of the standard properties is a "score". This is used to indicate the relevance of an item to the query.

When you use the geodist() function the score property changes to return a distance rather than relevance. This function is passed three parameters. sfield is the name of the spatial index field in the items you are searching (the property that holds the lat/long information). The latitude and longitude parameters are the point you would like to measure from, passed as the single pt property.

To pass a SOLR function as a query, you need to set your function as the SearchCommand. A filter query can be used for the actual search term.

Example

This request is made in the "Search for Objects with Location Data" example form related to this article.

client.invoke("icmapi", "CSSearchMultiple_get", {
    "set": {
        "CollectionNameList": "Object",
        "LuceneRepositoryPath": "http://localhost:5506/solrsite",
        "SearchCommand": "{!func}geodist()",
        "FilterQueries": ["custom1:FORM_OBJECTSWITHLOCATIONS"],
        "SolrParameters": [{
            Name: "pt",
            Value: ptValue
        }, {
            Name: "sfield",
            Value: "OBJECT_LL_LOCATION"
        }, {
            Name: "sort",
            Value: "score asc"
        }]
    },
    "get": "*"
});

The SearchCommand is our geodist() function query. The filter query searches all objects called FORM_OBJECTSWITHLOCATIONS. In a real world example you would more likely pass a search term as a filter query, perhaps searching the titles of articles, or metadata related to them. Note how the additional parameters are included in the SolrParameters array (ptValue is the value of the location picker field on the form).

The final SOLR parameter is sort, and as the score is now the distance, we can sort by that. Use asc to return the closest item first, desc the most distant.

And our results:

"result": {
    "data": {
        "multipleItemData": [{
            "ParentData": "",
            "CreationDate": "2018-02-26T13:45:55Z",
            "_ItemClass": "CSSearchItem",
            "GroupKey": "Object18948",
            "Title": "FORM_OBJECTSWITHLOCATIONS,{ts '2018-02-26 13:45:55'},TIMG",
            "Url": "E4112954-048A-4D70-93FD-941A8C565910",
            "Custom3": "",
            "Score": 1295.2273,
            "Custom2": "1",
            "Type": "Object",
            "Custom1": "FORM_OBJECTSWITHLOCATIONS",
            "ModificationDate": "2018-02-26T13:45:55Z",
            "MetaData": "",
            "DynamicFields": {
                "OBJECT_C__icmTypeName": "FORM_OBJECTSWITHLOCATIONS",
                "OBJECT_C__icmCreatedBy": "TIMG",
                "OBJECT_C_LOCATIONNAME": "Plymouth",
                "OBJECT_LL_LOCATION": "50.3754565,-4.1426565",
                "OBJECT_C__title": "FORM_OBJECTSWITHLOCATIONS,{ts '2018-02-26 13:45:55'},TIMG",
                "OBJECT_C__icmPublicType": "1",
                "OBJECT_C__icmLastUpdatedBy": "TIMG"
            },
            "Summary": "FORM_OBJECTSWITHLOCATIONS E4112954-048A-4D70-93FD-941A8C565910",
            "Key": "18948"
        }, {
            "ParentData": "",
            "CreationDate": "2018-02-26T13:49:17Z",
            "_ItemClass": "CSSearchItem",
            "GroupKey": "Object18951",
            "Title": "FORM_OBJECTSWITHLOCATIONS,{ts '2018-02-26 13:49:17'},TIMG",
            "Url": "93E51183-BD02-40F7-BC14-EF801ECC20EF",
            "Custom3": "",
            "Score": 4478.3013,
            "Custom2": "1",
            "Type": "Object",
            "Custom1": "FORM_OBJECTSWITHLOCATIONS",
            "ModificationDate": "2018-02-26T13:49:17Z",
            "MetaData": "",
            "DynamicFields": {
                "OBJECT_C__icmTypeName": "FORM_OBJECTSWITHLOCATIONS",
                "OBJECT_C__icmCreatedBy": "TIMG",
                "OBJECT_C_LOCATIONNAME": "Torquay",
                "OBJECT_LL_LOCATION": "50.4619209,-3.525315",
                "OBJECT_C__title": "FORM_OBJECTSWITHLOCATIONS,{ts '2018-02-26 13:49:17'},TIMG",
                "OBJECT_C__icmPublicType": "1",
                "OBJECT_C__icmLastUpdatedBy": "TIMG"
            },
            "Summary": "FORM_OBJECTSWITHLOCATIONS 93E51183-BD02-40F7-BC14-EF801ECC20EF",
            "Key": "18951"
        }]
    },
    "success": true
}

You can see the two properties of the object, OBJECT_C_LOCATIONNAME and OBJECT_LL_LOCATION in the DynamicFields. The Score can be converted to kilometres by dividing by 100.

Filtering

The second example form builds on the location search above. It uses the same SearchCommand and now includes a geo filter as one of the filter queries:

"FilterQueries": ["custom1:FORM_OBJECTSWITHLOCATIONS","{!geofilt sfield=OBJECT_LL_LOCATION pt=50.2781708303,-4.3707226562 d=40}"]

The first query restricts the search to objects called FORM_OBJECTSWITHLOCATIONS. The geofilt is built up in similar way to the normal search parameters, we have to name the spatial index field and the point we want to filter from, and then supply the range (the radius of a circle from the point) as "d".

SOLR's documentation for geofilt can be found here SOLR Geo Filters (opens new window).

Grouping

The CSSearchMultiple GroupQueries property can be used to group search results by distance. You could set a series of geo filters as groups, which would create a cumulative group structure (grouping all results within a series of maximum distances), or use the SOLR frange (opens new window) query parser to set date ranges between which results should be grouped.

Geo Filters as Groups

Using the same structure as the filter query above, an array of geo filters could be added as group queries. In this example the field that holds location data hasn't been renamed, it's just FIELD2.

"GroupQueries": ["{!geofilt sfield=OBJECT_LL_FIELD2 pt=50.4233524,-4.0965925 d=0.1}", "{!geofilt sfield=OBJECT_LL_FIELD2 pt=50.4233524,-4.0965925 d=10}", "{!geofilt sfield=OBJECT_LL_FIELD2 pt=50.4233524,-4.0965925 d=20}", "{!geofilt sfield=OBJECT_LL_FIELD2 pt=50.4233524,-4.0965925 d=30}", "{!geofilt sfield=OBJECT_LL_FIELD2 pt=50.4233524,-4.0965925 d=40}", "{!geofilt sfield=OBJECT_LL_FIELD2 pt=50.4233524,-4.0965925 d=50}"]

This will create groups of results at 10, 20, 30, 40 and 50 kilometres.

The third example form related to this article uses geofilt group queries and lays the results out in various tables.

Ranges as Groups

The frange parser restricts results to a range of values found in a declared field or function query. Ranges are set in kilometres between a lower (l) and upper (u) bound. The example below is used in the fourth example for related to this article.

"GroupQueries": ["{!frange l=0 u=50} geodist(OBJECT_LL_LOCATION, 50.4233524,-4.0965925)","{!frange l=50 u=100} geodist(OBJECT_LL_LOCATION, 50.4233524,-4.0965925)","{!frange l=100 u=150} geodist(OBJECT_LL_LOCATION, 50.4233524,-4.0965925)","{!frange l=150 u=200} geodist(OBJECT_LL_LOCATION, 50.4233524,-4.0965925)","{!frange l=200 u=250} geodist(OBJECT_LL_LOCATION, 50.4233524,-4.0965925)"];

Each group query defines a range and a distance function that includes the field in our search items that holds lat/long values and the point we want to measure from.

Last modified on February 28, 2018

Share this page

Facebook icon Twitter icon email icon

Print

print icon