Skip to content

Commit

Permalink
adds geojson serialization for placename|coordinates field
Browse files Browse the repository at this point in the history
  • Loading branch information
mejackreed committed Mar 11, 2014
1 parent bd8d73b commit 589acb3
Show file tree
Hide file tree
Showing 12 changed files with 184 additions and 245 deletions.
37 changes: 17 additions & 20 deletions README.md
Expand Up @@ -25,34 +25,30 @@ Or install it yourself as:

Blacklight-Maps adds a map view capability for a results set that contains geospatial coordinates (latitude/longitude).

Blacklight-Maps requires that your SOLR index includes lat/lon coordinates located in a field that relates to a placename. For example:
For now, Blacklight-Maps requires that your SOLR index includes a field containing placenames with latitude and longitude coordinates delimited by `|`. This field can be multivalued.

A document could have the following placenames:
A document requires the following field:
```
subject_geo_facet:
- China
- Tibet
- India
```
These placenames are already geocoded and given as an array in the same order:
```
geoloc:
- "[35.86166, 104.195397]"
- "[29.646923, 91.117212]"
- "[20.593684, 78.96288]"
placename_coords:
- China|35.86166|104.195397
- Tibet|29.646923|91.117212
- India|20.593684|78.96288
```

Note: We are looking at implementing support for additional fields.

### Configuration

#### Required
Blacklight-Maps expects you to provide several things:
Blacklight-Maps expects you to provide:

- a field to map the placename array (`subject_geo_facet` in the example above)
- a field to map to the latitude/longitude array (`geoloc` in the example above)
- a field to map the placename coordinates (`placename_coords` in the example above)

#### Optional

- a field that the document thumbnail url resides
- the maxZoom [property of the map](http://leafletjs.com/reference.html#map-maxzoom)
- a [tileLayer url](http://leafletjs.com/reference.html#tilelayer-l.tilelayer) to change the basemap
- an [attribution string](http://leafletjs.com/reference.html#tilelayer-attribution) to describe the basemap layer


All of these options can easily be configured in `CatalogController.rb` in the `config` block.
Expand All @@ -67,9 +63,10 @@ All of these options can easily be configured in `CatalogController.rb` in the `
:fl => '*'
}
config.view.maps.placename_field = "subject_geographic_ssim"
config.view.maps.thumbnail_field = "thumbnail_url_ssm"
config.view.maps.lat_lng_field = "subject_geographic_coords"
## Default values
config.view.maps.placename_coords_field = "placename_coords"
config.view.maps.tileurl = "http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
config.view.maps.attribution = 'Map data &copy; <a href="http://openstreetmap.org">OpenStreetMap</a> contributors, <a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>'
...
```
Expand Down
130 changes: 58 additions & 72 deletions app/assets/javascripts/blacklight-maps/blacklight-maps-browse.js
@@ -1,62 +1,61 @@

var map, sidebar;

Blacklight.onLoad(function() {

// Stop doing stuff if the map div isn't there
if ($("#map").length === 0){
if ($("#blacklight-map").length === 0){
return;
}

// Get the configuration options from the data-attributes
$.extend(Blacklight.mapOptions, $("#map").data());
$.extend(Blacklight.mapOptions, $("#blacklight-map").data());

map = L.map('map').setView([0,0], 2);
map = L.map('blacklight-map').setView([0,0], 2);
L.tileLayer(Blacklight.mapOptions.tileurl, {
attribution: Blacklight.mapOptions.attribution,
attribution: Blacklight.mapOptions.mapattribution,
maxZoom: Blacklight.mapOptions.maxzoom
}).addTo(map);

//Sets up leaflet-sidebar
sidebar = L.control.sidebar('leaflet-sidebar', {
// Sets up leaflet-sidebar
sidebar = L.control.sidebar('blacklight-map-sidebar', {
position: 'right',
autoPan: false
});

//Adds leaflet-sidebar control to map (object)
// Adds leaflet-sidebar control to map (object)
map.addControl(sidebar);

//Create a marker cluster object and set options
// Create a marker cluster object and set options
markers = new L.MarkerClusterGroup({
showCoverageOnHover: false,
spiderfyOnMaxZoom: false,
singleMarkerMode: true,
animateAddingMarkers: true
});

//Iterate through document looking for location values for the map
if (docs.length > 0){
$.each(docs, function(i,val){
if (val[Blacklight.mapOptions.latlngfield]){

//Look through the location field 'geoloc' to add multiple values for each document
$.each(val[Blacklight.mapOptions.latlngfield], function(j,loc){

//Parse the string to JSON lat/lng array
latlng = JSON.parse(loc);

title = val[Blacklight.mapOptions.placefield][j];
geoJsonLayer = L.geoJson(geojson_docs, {
onEachFeature: function(feature, layer){
layer.defaultOptions.title = feature.properties.placename;
layer.on('click', function(e){
if (sidebar.isVisible()){
sidebar.hide();
}
var placenames = {};
placenames[feature.properties.placename] = [feature.properties.html];
offsetMap(e);
$('#blacklight-map-sidebar').html(buildList(placenames));
sidebar.show();
});
}
});

//Add marker to marker cluster object
markers.addLayer(createMarker(latlng, title));
});
}
});
}
// Add GeoJSON layer to marker cluster object
markers.addLayer(geoJsonLayer);

//Add marker cluster object to map
// Add marker cluster object to map
map.addLayer(markers);

// Listeners for marker cluster clicks
markers.on('clusterclick', function(e){

//hide sidebar if it is visible
Expand All @@ -67,28 +66,13 @@ Blacklight.onLoad(function() {
//if map is at the lowest zoom level
if (map.getZoom() === Blacklight.mapOptions.maxzoom){

//get the title from the markers inside of the markercluster object
var titles = [];// = e.layer._markers[0].options.title;
$.each(e.layer._markers, function(i,val){
// console.log(val.options.title)
if ($.inArray(val.options.title, titles) === -1 ){
titles.push(val.options.title);
}
});
//build the results list sidebar
html = "";
$.each(titles, function(i,val){
html += "<h2>" + val + "</h2>";
html += buildList(val);
});

//Move the map so that it centers the clicked cluster TODO account for various size screens
mapWidth = $('#map').width();
mapHeight = $('#map').height();
map.panBy([(e.originalEvent.layerX - (mapWidth/4)), (e.originalEvent.layerY - (mapHeight/2))]);
var placenames = generatePlacenamesObject(e.layer._markers);


offsetMap(e);

//Update sidebar div with new html
$('#leaflet-sidebar').html(html);
$('#blacklight-map-sidebar').html(buildList(placenames));

//Show the sidebar!
sidebar.show();
Expand Down Expand Up @@ -117,35 +101,37 @@ Blacklight.onLoad(function() {

Blacklight.mapOptions = {
tileurl : 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
attribution : 'Map data &copy; <a href="http://openstreetmap.org">OpenStreetMap</a> contributors, <a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>'
mapattribution : 'Map data &copy; <a href="http://openstreetmap.org">OpenStreetMap</a> contributors, <a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>'
};

function createMarker(latlng, title){
return new L.Marker(latlng, {title: title}).on('click', function(e){
//hide the sidebar if it is visible
if (sidebar.isVisible()){
sidebar.hide();
}
var list = buildList(title);
mapWidth = $('#map').width();
mapHeight = $('#map').height();
map.panBy([(e.originalEvent.layerX - (mapWidth/4)), (e.originalEvent.layerY - (mapHeight/2))]);
$('#leaflet-sidebar').html("<h2>" + title + "</h2>" + list);
sidebar.show();
function buildList(placenames){
var html = "";
$.each(placenames, function(i,val){
html += "<h2>" + i + "</h2>";
html += "<ul class='sidebar-list'>";
$.each(val, function(j, val2){
html += val2;
});
html += "</ul>";
});
return html;
}

function buildList(title){
var list = "<div><ul class='media-list'>";
$.each(docs, function(i,val){
if ($.inArray(title, val[Blacklight.mapOptions.placefield]) !== -1){
if (Blacklight.mapOptions.thumbfield){
list += "<li class='media'><a class='pull-left' href='#'><img class='media-object sidebar-thumb' src='" + val[Blacklight.mapOptions.thumbfield] + "'></a><div class='media-body'><h4 class='media-heading'><a href='" + Blacklight.mapOptions.docurl + val[Blacklight.mapOptions.docid] + "'>" + val[Blacklight.mapOptions.titlefield] + "</a></h4></div></li>";
}else{
list += "<li class='media'><div class='media-body'><h4 class='media-heading'><a href='" + Blacklight.mapOptions.docurl + val[Blacklight.mapOptions.docid] + "'>" + val[Blacklight.mapOptions.titlefield] + "</a></h4></div></li>";
}
// Generates placenames object
function generatePlacenamesObject(markers){
var placenames = {};
$.each(markers, function(i,val){
if (!(val.feature.properties.placename in placenames)){
placenames[val.feature.properties.placename] = [];
}
placenames[val.feature.properties.placename].push(val.feature.properties.html);
});
list += "</ul></div>";
return list;
return placenames;
}

// Move the map so that it centers the clicked cluster TODO account for various size screens
function offsetMap(e){
mapWidth = $('#blacklight-map').width();
mapHeight = $('#blacklight-map').height();
map.panBy([(e.originalEvent.layerX - (mapWidth/4)), (e.originalEvent.layerY - (mapHeight/2))]);
}
7 changes: 6 additions & 1 deletion app/assets/stylesheets/blacklight_maps/default.css.scss
Expand Up @@ -9,11 +9,16 @@
&:before { content: "\e135"; }
}

#map{
#blacklight-map{
height: 550px;
}

.sidebar-thumb{
height: 64px;
width: 64px;
}

.sidebar-list{
padding-left: 0;
list-style: none;
}
54 changes: 23 additions & 31 deletions app/helpers/blacklight_maps_helper.rb
@@ -1,44 +1,36 @@
module BlacklightMapsHelper

def show_map_div
data_attributes = {:latlngfield => blacklight_config.view.maps.lat_lng_field.to_s,
:maxzoom => 8, :titlefield => blacklight_config.index.title_field,
:placefield => blacklight_config.view.maps.placename_field,
:docurl => doc_url_path,
:docid => SolrDocument.unique_key.to_s
data_attributes = {
:maxzoom => blacklight_config.view.maps.maxzoom,
:tileurl => blacklight_config.view.maps.tileurl

}

if has_thumbnail_field_defined?
data_attributes[:thumbfield] = blacklight_config.view.maps.thumbnail_field
end

content_tag(:div, "", :id => "map",
:data => data_attributes
content_tag(:div, "", id: "blacklight-map",
data: data_attributes
)
end

def has_thumbnail_field_defined?
blacklight_config.view.maps.thumbnail_field.present?
def serialize_geojson
geojson_docs = {type: "FeatureCollection", features: []}
@response.docs.each_with_index do |doc, counter|
if doc[blacklight_config.view.maps.placename_coord_field]
doc[blacklight_config.view.maps.placename_coord_field].each do |loc|
values = loc.split('|')
feature = {type: "Feature", geometry: {type: "Point",
coordinates: [values[2].to_f, values[1].to_f]},
properties: {placename: values[0],
html: render_leaflet_sidebar_partial(doc)}}
geojson_docs[:features].push feature
end
end
end
return geojson_docs.to_json
end

def doc_url_path
path = url_for_document(SolrDocument).to_s.gsub("SolrDocument", "")
if path.length < 1
path = '/catalog/'
end
return path
def render_leaflet_sidebar_partial(doc)
render partial: 'catalog/index_maps', locals: {document: SolrDocument.new(doc)}
end

def send_needed_map_fields
returned_fields = [SolrDocument.unique_key, blacklight_config.view.maps.lat_lng_field,
blacklight_config.index.title_field, blacklight_config.view.maps.placename_field]
if has_thumbnail_field_defined?
returned_fields.push blacklight_config.view.maps.thumbnail_field
end
returned_docs = []
@response.docs.each do |doc|
returned_docs.push doc.select {|k,v| returned_fields.include?(k)}
end
return returned_docs.to_json
end
end
4 changes: 2 additions & 2 deletions app/views/catalog/_document_maps.html.erb
@@ -1,6 +1,6 @@
<% # container for all documents in map view -%>
<div id="documents" class="map">
<%= show_map_div() %>
<div id="leaflet-sidebar"></div>
<%= javascript_tag "var docs = #{send_needed_map_fields};" %>
<div id="blacklight-map-sidebar"></div>
<%= javascript_tag "var geojson_docs = #{serialize_geojson}" %>
</div>
9 changes: 9 additions & 0 deletions app/views/catalog/_index_maps.html.erb
@@ -0,0 +1,9 @@
<% # the way each document will be viewed in the sidebar list -%>
<li class='media'>
<%= render_thumbnail_tag document, {class: 'sidebar-thumb media-object'}, {class: 'pull-left'} %>
<div class='media-body'>
<h4 class='media-heading'>
<%= link_to_document document, :label=>document_show_link_field(document) %>
</h4>
</div>
</li>
8 changes: 5 additions & 3 deletions lib/blacklight/maps/engine.rb
Expand Up @@ -9,9 +9,11 @@ module Maps
class Engine < Rails::Engine

# Set some default configurations
Blacklight::Configuration.default_values[:view].maps.lat_lng_field = "geoloc"
Blacklight::Configuration.default_values[:view].maps.placename_field = "subject_geo_facet"

Blacklight::Configuration.default_values[:view].maps.placename_coord_field = "placename_coords"
Blacklight::Configuration.default_values[:view].maps.tileurl = "http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
Blacklight::Configuration.default_values[:view].maps.mapattribution = 'Map data &copy; <a href="http://openstreetmap.org">OpenStreetMap</a> contributors, <a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>'
Blacklight::Configuration.default_values[:view].maps.maxzoom = 8

# Add our helpers
initializer 'blacklight-maps.helpers' do |app|
ActionView::Base.send :include, BlacklightMapsHelper
Expand Down
2 changes: 1 addition & 1 deletion solr_conf/conf/schema.xml
Expand Up @@ -528,7 +528,7 @@
<field name="format" type="string" indexed="true" stored="true"/>

<!-- addition -->
<field name="geoloc" type="string" indexed="true" stored="true" multiValued="true"/>
<field name="placename_coords" type="string" indexed="true" stored="true" multiValued="true"/>

<!-- Dynamic field definitions. If a field name is not found, dynamicFields
will be used if the name matches any of the patterns.
Expand Down
2 changes: 1 addition & 1 deletion solr_conf/conf/solrconfig.xml
Expand Up @@ -212,7 +212,7 @@
subtitle_vern_display,
url_fulltext_display,
url_suppl_display,
geoloc
placename_coords
</str>

<str name="facet">true</str>
Expand Down

0 comments on commit 589acb3

Please sign in to comment.