First and foremost, what is spatial search or geospatial search as some would say? It the functionality used in searching to search for locations based on a radius from a center point. For instance, let’s just say that I was on a Healthcare website and wanted to find all the healthcare locations within a 10 mile radius of my house. Spatial search is what you would use to achieve this search functionality. Previous to Sitecore 9 you had a some options based on which Index provider you were using i.e. Lucene or SOLR. These are the typical options:
- Roll your own custom code
- Complexity (high)
- Time Estimate to Implement/Integrate (high)
- Testing/Debugging time for exact matching (high)
- Maintainability (low)
- Lucene Spatial Search Support Module from the Sitecore Marketplace
- Complexity (low)
- No code to write (for the most part), just integrate into your Sitecore environments
- Time Estimate to Implement/Integrate (medium)
- Testing/Debugging time for exact matching (low)
- Already done for you by the Sitecore module creators
- Maintainability (low with risk)
- Sitecore upgrades may affect this module, and render it useless forcing you to reflect on the module code and your team must refactor to work again OR your team will be forced to roll your own custom code
- Sitecore upgrades may affect this module, and render it useless forcing you to reflect on the module code and your team must refactor to work again OR your team will be forced to roll your own custom code
- Complexity (low)
- Solr Spatial Search Support Module from the Sitecore Marketplace
- Complexity (low)
- No code to write (for the most part), just integrate into your Sitecore environments
- Time Estimate to Implement/Integrate (medium)
- Testing/Debugging time for exact matching (low)
- Already done for you by the Sitecore module creators
- Maintainability (low with risk)
- Sitecore upgrades may affect this module, and render it useless forcing you to reflect on the module code and your team must refactor to work again OR your team will be forced to roll your own custom code
- Complexity (low)
Well, Lucene has taken a back seat to SOLR, and SOLR is now the recommended index provider so forget about Lucene. The great news is now Sitecore 9 has SOLR Spatial Search built-in to Sitecore out of the box!
Below is my view on SOLR Spatial Search with Sitecore 9:
- SOLR Spatial Search with Sitecore 9+
- Complexity (low)
- You now have an example to follow with this blog post
- Time Estimate to Implement/Integrate (low)
- You now have an example to follow with this blog post
- Testing/Debugging time for exact matching (low)
- Already done for you by SOLR
- Maintainability (low)
- As long as you are using SOLR your team will just need to maintain code to make modifications based on business requirements every so often
- Complexity (low)
So here is what you will generally need for SOLR Spatial Search to work:
- Center Point for the Search
- Center Latitude
- Center Longitude
- Locations being searched
- Latitude
- Longitude
In Sitecore terms, you are generally going to have a static or dynamic center latitude & longitude. Basically, a center latitude or longitude that is either hard-coded, or set in fields in a Sitecore template. Or these can be provided by the user during an interactive session where the user will put in their zip code or address, and it gets converted to a center latitude and longitude dynamically on-the-fly. For our example, we are going to be using Sitecore fields that the Content Author will set up for center latitude and center longitude. Then we will search for locations within a 10 mile radius based on the center latitude and center longitude the Content Author provided. So, let’s get started building an Map Component!
Templates
Map Component
Fields:
- Center Latitude (Number field)
- Center Longitude (Number field)
Facility
Fields:
- Latitude (Number field)
- Longitude (Number field)
At a bare minimum, you must have these fields in place for SOLR Spatial Search to work. Now, you must setup an index with Sitecore to index the facilities. Here is a basic example on how to do that using HELIX patterns, principles, and conventions:
ExampleTenant.Foundation.Facilities.DefaultSolrFacilities.IndexConfiguration (Project)
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:role="http://www.sitecore.net/xmlconfig/role/" xmlns:search="http://www.sitecore.net/xmlconfig/search/"> <sitecore role:require="Standalone or ContentManagement" search:require="solr"> <contentSearch> <configuration type="Sitecore.ContentSearch.ContentSearchConfiguration, Sitecore.ContentSearch"> <indexes hint="list:AddIndex"> <index id="facilities_web_index" type="Sitecore.ContentSearch.SolrProvider.SolrSearchIndex, Sitecore.ContentSearch.SolrProvider"> <param desc="name">$(id)</param> <param desc="core">$(id)</param> <param desc="propertyStore" ref="contentSearch/indexConfigurations/databasePropertyStore" param1="$(id)" /> <configuration ref="contentSearch/indexConfigurations/defaultSolrFacilitiesIndexConfiguration" /> <strategies hint="list:AddStrategy"> <strategy ref="contentSearch/indexConfigurations/indexUpdateStrategies/onPublishEndAsync" /> </strategies> <locations hint="list:AddCrawler"> <crawler type="Sitecore.ContentSearch.SitecoreItemCrawler, Sitecore.ContentSearch"> <Database>web</Database> <Root>/sitecore/content/ExampleTenant/Global/Facilities/</Root> </crawler> </locations> <enableItemLanguageFallback>false</enableItemLanguageFallback> <enableFieldLanguageFallback>false</enableFieldLanguageFallback> </index> </indexes> </configuration> </contentSearch> </sitecore> </configuration>
ExampleTenant.Foundation.Facilities.DefaultSolrFacilities.IndexConfiguration (Foundation)
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:search="http://www.sitecore.net/xmlconfig/search/"> <sitecore search:require="solr"> <contentSearch> <indexConfigurations> <defaultSolrFacilitiesIndexConfiguration ref="contentSearch/indexConfigurations/defaultSolrIndexConfiguration"> <documentOptions ref="contentSearch/indexConfigurations/defaultSolrIndexConfiguration/documentOptions"> <fields hint="raw:AddComputedIndexField"> <field fieldName="coordinate" returnType="string">ExampleTenant.Foundation.Facilities.Search.ComputedFields.CoordinateComputedIndexField, ExampleTenant.Foundation.Facilities</field> </fields> <include hint="list:AddIncludedTemplate"> <Facility>{086b3834-be01-4d3e-b1e2-3827a2010ddd}</Facility> </include> </documentOptions> </defaultSolrFacilitiesIndexConfiguration> </indexConfigurations> </contentSearch> </sitecore> </configuration>
You will notice that we included the Facility template to our index the facilities, and that we included a computed index field called CoordinateComputedIndexField. The “Coordinate” term is very important because Sitecore 9+ now has an item called Coordinate located here in the Content tree: /sitecore/templates/System/Geospatial/Coordinate.
There are a couple of ways to use the Coordinate item.
- Create “Location” items based on the Coordinate template
- Any items that are created using this template get indexed automatically in the sitecore_master_index and sitecore_web_index. However, I went down this path of creating Location items based on these templates, as mentioned in the documentation, and I could not get the spatial search to work as intended. My hunch is that you must index your Facilities in the sitecore_master_index and sitecore_web_index as well, and I am more for more lightweight custom indexes versus bloating those anymore than needed.
- Add the Latitude and Longitude fields to your template, in our case the Facility fields, and then pass them into the Coordinate object in Sitecore.ContentSearch.Data library.
- This is the best method to use because you can retain your latitude and longitude in your original template, which is easier to maintain for Content Authors, and you can use a custom index that in my opinion is more lightweight and specialized.
CoordinateComputedIndexField.cs (Foundation)
using Sitecore.ContentSearch;
using Sitecore.ContentSearch.ComputedFields;
using Sitecore.ContentSearch.Data;
using Sitecore.Data;
namespace ExampleTenant.Foundation.Facilities.Search.ComputedFields
{
public class CoordinateComputedIndexField : AbstractComputedIndexField
{
public override object ComputeFieldValue(IIndexable indexable)
{
var indexableItem = indexable as SitecoreIndexableItem;
var item = indexableItem?.Item;
if (item?.Database == null) return null;
double latitude = 0;
double longitude = 0;
var parsed = double.TryParse(item[ID.Parse(Constants.Templates.Facility.Fields.Latitude)], out latitude);
parsed = parsed && double.TryParse(item[ID.Parse(Constants.Templates.Facility.Fields.Longitude)], out longitude);
return parsed ? new Coordinate(latitude, longitude) : null;
}
}
}
Now, you must set up your search result class as such for using the Coordinate object with Sitecore:
FacilitySearchResult.cs (Foundation)
using Sitecore.ContentSearch;
using Sitecore.ContentSearch.Data;
namespace ExampleTenant.Foundation.Facilities.Models
{
public class FacilitySearchResult
{
[IndexField(“coordinate”)]
public Coordinate Coordinate { get; set; }
}
}
Now, you are all set with your indexing and code configurations to start using SOLR Spatial Search with Sitecore 9!
Lastly, all you need to do is make a call into your code that will search through the index to get you your results.
FacilityRepository.cs (Foundation)
public List<FacilitySearchResult> GetFacilities(double latitude, double longitude)
{
var index = ContentSearchManager.GetIndex(“facilities_web_index”);
using (var context = index.CreateSearchContext())
{
var queryable = context.GetQueryable<FacilitySearchResult>()
.WithinRadius(f => f.Coordinate,
new Coordinate(latitude, longitude),
6.21371);
var list = queryable.ToList();
return list;
}
}
You will want to map these fields to your Facility object as well, which you should do during your call to get the results. I left this out just to simplify things, but I hope you get the point on how this is all done using SOLR Spatial Search for Sitecore 9+. Also, in case you are wondering what the 6.21371 is that is 10 miles in kilometers. Go forth and radius search with SOLR Sitecore community!