Using Funnelback search results to populate a map
Managed by | Updated .
Background
Funnelback search results can be used to populate a map (eg. Google Maps or OpenStreetMaps).
At a high level the implementation works this way:
- Funnelback index is built containing geo-coded results
- A template is created within Funnelback that is configured to return results as GeoJSON
- LeafletJS is used to read in this GeoJSON data and populate the items onto a Map.
The advantage of using LeafletJS is that the Funnelback component is very compact (only the GeoJSON template is required), and the map layer is very flexible (you can choose to use Google Maps, or Bing Maps, or OpenStreetMap etc). LeafletJS also contains many plugins to easily extend the functionality.
The basic implementation includes built-in support for:
- Data feed from Funnelback search results via a GeoJSON template
- Clustering of result pins
- Customisation of pin icons, pin information popups
- Configuration of the mapping service to use (eg. Google/Bing/OSM)
- Full screen mode
- Easy extension of functionality
Code
The mapping code is available from: https://github.com/funnelback/funnelback-mapping
Working demonstration
A working demonstration of the mapping code is available at: http://showcase.funnelback.com/s/search.html?collection=showcase-mapping
This demo provides an interactive map based on a dataset of global airport locations.
8. Edit the mapservice padre_opts:
8.1 Ensure the SF value includes all the metadata classes requried to populate the popups. x must be included as a minimum.
8.2 Set the appropriate num_ranks value that should be applied to map queries.
8.3 Set an appropriate value for MBL
8.4 If map usage is not of interest for analytics disable logging (add -log=false)
Basic usage
Configuring the map tile layer to use
The implementation currently has support for Google and OpenStreetMap tile layers. The Leaflet libraries support a number of number of other services (eg. Bing, Yandex) - configuration of these services is possible but requires further customisation of the code. The tile layer is set by editing the tileLayer variable used in funnelback_mapping_config.js, and possibly adding some Leaflet plugins.
Note: For Google Maps and API key is required, and this must be configured in collection.cfg. Use of most other map services (Bing, Yandex etc) also require API keys.
// To use Google roadmaps:
var tileLayer = "google";
// To use Google terrain maps:
var tileLayer = "google-terrrain";
// To use Google satellite maps:
var tileLayer = "google-satellite";
// To use Google hybrid maps:
var tileLayer = "google-hybrid";
//To use OpenStreetMap maps:
var tileLayer = "osm";
A tile layer switcher can be configured to allow switching between the different map types (eg. roadmap/satellite/terrain). The customiseMap() function has some example code that sets this up.
Configuring pin clustering
To use pin clustering:
var useClusters = true
Configuring the map ID
The ID of the div within the search result template to which the map is bound can be configured by setting the mapdiv variable in funnelback_mapping_config.js
Configuring the pin popups
A pin popup can be used to display information about the pin. This is sourced from metadata associated with the result item, in a similar manner to how a search result item is customised in standard search results.
The format for the pin popup is defined in the createPopup(feature) function in the funnelback_mapping_config.js file. eg.
// Configure the code that should be returned for each popup.
function createPopup(feature) {
if (feature.properties.metaData.c) {var summary = feature.properties.metaData.c.trim()} else {var summary = ""}
var html = ""
html += "<div class=\"mapitem-popup\">"
html += "<h2><a href=\""+feature.properties.liveUrl+"\">"+feature.properties.title+"</a></h2>"
html += "<p>"+summary+"</p></div>"
return html;
}
Configuring custom no results text
The no results text can be configured by setting the noResultsText variable in funnelback_mapping_config.js
If you edit the message the styles for the div positioning may require adjustment. These styles are defined in funnelback_mapping.ftl
Don't forget to update the padre_opts.cfg for the mapservice profile to return any custom metadata fields that you require for the popup display (SF query processor option).
Advanced usage
Configuring custom marker pins
Customising the marker pins is very easy and requires a three steps:
1. Create and upload the custom pin images to the mapservice web resources folder. eg. upload a file myCustomPin.png (30x30 PNG image with transparent background) to ($COLLECTION/conf/mapservice/web/). An associated pin shadow can optionally be uploaded here too.
2. Configure the custom pins. This sets up the Javascript objects that correspond to each icon definition. Configuration is applied in the section 'DEFINE CUSTOM PINS' in funnelback_mapping_config.js eg.
//DEFINE CUSTOM PINS
//default custom pin definition
var defaultMarker = L.icon({
iconUrl: '/s/resources/'+COLLECTIONNAME+'/mapservice/markers/default.png',
shadowUrl: '/s/resources/'+COLLECTIONNAME+'/mapservice/markers/default-shadow.png',
iconSize: [12, 30], // size of the icon
shadowSize: [12, 30], // size of the shadow
iconAnchor: [6, 15], // point of the icon which will correspond to marker's location (rel to top left)
shadowAnchor: [2, 32], // the same for the shadow
popupAnchor: [6,-2] // point from which the popup should open relative to the iconAnchor
});
//add additional custom pin definitions here eg.
var customPin= L.icon({
iconUrl: '/s/resources/'+COLLECTIONNAME+'/mapservice/markers/myCustomPin.png',
shadowUrl: '/s/resources/'+COLLECTIONNAME+'/mapservice/markers/default-shadow.png',
iconSize: [30, 18], // size of the icon
shadowSize: [30, 18], // size of the shadow
iconAnchor: [15, 9], // point of the icon which will correspond to marker's location (rel to top left)
shadowAnchor: [2, 20], // the same for the shadow
popupAnchor: [15,-2] // point from which the popup should open relative to the iconAnchor
});
Further information regarding pin customisation can be found here: http://leafletjs.com/reference.html#icon
Configure the business rules to apply the custom pins to a map item by editing the markerOptions(feature) function in funnelback_mapping_config.js. These rules are matched against a result item and are used to determine which pin image to apply. eg:
function markerOptions(feature) {
// Set the default pin icon to use
var iconImg = defaultMarker
// Read relevant data for business rules from the result item.
var type = feature.properties.metaData.A.trim().toUpperCase()
var section = feature.properties.metaData.B.trim().toUpperCase()
// Define business rules for custom pins, based on attributes relating to an individual result. eg.
if ((type == 'CUSTOMTYPE') && (section == 'CUSTOMSECTION')) {
iconImg = customPin
}
else if ((roadUserType == 'CUSTOMTYPE2') && (gender == 'CUSTOMSECTION2')) {
iconImg = customPin2
}
//etc.
var mOpts = {
icon: iconImg
}
return mOpts;
}
Further information regarding pin customisation can be found here: http://leafletjs.com/reference.html#icon
3. Configure the business rules to apply the custom pins to a map item by editing the markerOptions(feature) function in funnelback_mapping_config.js. These rules are matched against a result item and are used to determine which pin image to apply. eg:
function markerOptions(feature) {
// Set the default pin icon to use
var iconImg = defaultMarker
// Read relevant data for business rules from the result item.
var type = feature.properties.metaData.A.trim().toUpperCase()
var section = feature.properties.metaData.B.trim().toUpperCase()
// Define business rules for custom pins, based on attributes relating to an individual result. eg.
if ((type == 'CUSTOMTYPE') && (section == 'CUSTOMSECTION')) {
iconImg = customPin
}
else if ((roadUserType == 'CUSTOMTYPE2') && (gender == 'CUSTOMSECTION2')) {
iconImg = customPin2
}
//etc.
var mOpts = {
icon: iconImg
}
return mOpts;
}
Don't forget to update the padre_opts.cfg for the mapservice profile to return any custom metadata fields that you require for the business rules (SF query processor option).
Loading additional Leaflet plugins to extend functionality
The loading of additional leaflet plugins requires editing of the funnelback_mapping.ftl file. There is a commented section on where to add the plugin load calls so that you can utilise them from your code.
The plugins need to be added after the leaflet Javascript libraries are loaded, but before the map is initialised. Once the plugins are loaded the customiseMap() function in funnelback_mapping_config.js can be used to apply additional customisation to the map.
e.g. Add the KML plugin so that custom layers can be added to the map.
1. Download the KML plugin and add this to the mapservice web resources folder. ($COLLECTION/conf/mapservice/web/)
2. Edit funnelback_mapping.ftl and add the script tag to load the library:
<#-- Load custom Leaflet plugins here -->
<!-- load the KML library -->
<script src="/s/resources/${question.inputParameterMap["collection"]}/mapservice/KML.js"></script>
<#-- END custom leaflet plugins-->
3. Edit the customiseMap() function in funnelback_mapping_config.js to load the KML layer and enable it via the layer switch control:
function customiseMap() {
// Add additional map customisation code here
// Add the KML layer containing the LGA boundaries
var lgaLayer = new L.KML('/s/resources/'+COLLECTIONNAME+'/mapservice/lga.kml', {async: true});
map.addControl(new L.Control.Layers( {}, {'LGA boundaries':lgaLayer}));
}
Notes
It's very important to optimise the query to avoid problems with memory usage in Jetty and response times. The following settings are applied by default (in the mapservice/padre_opts.cfg) but can be adjusted as required.
The idea here is to make the Padre XML and modern UI data model as small as possible.
Default padre opts are currently set to -SM=meta -SF=[t,x,A,B,C,D,T,latLong] -bb=false -MBL=255 -num_ranks=500
- Set -SM=meta
- Set -SF to include the metadata class containing the geospatial metadata and add only the fields that you will be using in your map popups
- Disable best bets (-bb=false)
- Set a short metadata buffer length (-MBL=255). This is likely to be determined by the max length of the metadata value you'll be using for your popup's description text.
- Set num_ranks to as small a value as possible, defaults to 500 (it's a good idea to negotiate a max number of results to display with the customer if possible).
- If collection-level faceted navigation is used for the collection then it is recommended that rmcf and gscope counts are disabled. This can be done using a pre_datafetch hook script.
See: Query processing optimisation for a fuller list of optimisations.
Dependencies
See the readme.txt located within the src folder of the funnelback_mapping.tar.gz bundle