blacklight_allmaps 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +201 -0
- data/README.md +109 -0
- data/Rakefile +98 -0
- data/app/assets/config/blacklight_allmaps_manifest.js +2 -0
- data/app/assets/images/blacklight/allmaps/allmaps-logo.svg +39 -0
- data/app/assets/images/blacklight/allmaps/blacklight-logo.png +0 -0
- data/app/assets/images/blacklight/allmaps/geoblacklight-logo.png +0 -0
- data/app/assets/images/blacklight/allmaps/logo.svg +39 -0
- data/app/assets/stylesheets/blacklight_allmaps/application.css +15 -0
- data/app/controllers/blacklight/allmaps/application_controller.rb +6 -0
- data/app/helpers/blacklight/allmaps/application_helper.rb +17 -0
- data/app/javascripts/map_controller.js +39 -0
- data/app/jobs/blacklight/allmaps/application_job.rb +6 -0
- data/app/jobs/blacklight/allmaps/store_sidecar_annotation.rb +39 -0
- data/app/mailers/blacklight/allmaps/application_mailer.rb +8 -0
- data/app/models/blacklight/allmaps/application_record.rb +7 -0
- data/app/models/blacklight/allmaps/sidecar.rb +23 -0
- data/app/models/concerns/blacklight/allmaps/solr_document.rb +14 -0
- data/app/views/allmaps/show/_blacklight.html.erb +213 -0
- data/app/views/allmaps/show/_geoblacklight.html.erb +47 -0
- data/app/views/allmaps/sidebar/_allmaps.html.erb +55 -0
- data/app/views/catalog/_show_default_viewer_container.html.erb +52 -0
- data/app/views/catalog/_show_main_content.html.erb +58 -0
- data/app/views/catalog/_show_sidebar.html.erb +5 -0
- data/app/views/catalog/_show_sidebar_blacklight.html.erb +10 -0
- data/app/views/catalog/_show_sidebar_geoblacklight.html.erb +20 -0
- data/config/routes.rb +2 -0
- data/db/migrate/20240307155110_create_solr_document_sidecars.rb +15 -0
- data/lib/blacklight/allmaps/engine.rb +6 -0
- data/lib/blacklight/allmaps/rake_task.rb +7 -0
- data/lib/blacklight/allmaps/tasks/index.rake +77 -0
- data/lib/blacklight/allmaps/tasks/sidecars.rake +50 -0
- data/lib/blacklight/allmaps/version.rb +5 -0
- data/lib/blacklight/allmaps.rb +7 -0
- data/lib/generators/blacklight/allmaps/config_generator.rb +54 -0
- data/lib/generators/blacklight/allmaps/install_generator.rb +27 -0
- data/lib/generators/blacklight/allmaps/models_generator.rb +46 -0
- data/lib/generators/blacklight/allmaps/templates/manifest.js +5 -0
- metadata +229 -0
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
##
|
4
|
+
# ActiveRecord appendage for SolrDocuments
|
5
|
+
module Blacklight
|
6
|
+
module Allmaps
|
7
|
+
class Sidecar < ApplicationRecord
|
8
|
+
self.table_name = "blacklight_allmaps_sidecars"
|
9
|
+
|
10
|
+
alias_attribute :georeferenced, :annotated
|
11
|
+
|
12
|
+
before_save :set_allmaps_id
|
13
|
+
|
14
|
+
def solr_document
|
15
|
+
document_type.constantize.find(solr_document_id)
|
16
|
+
end
|
17
|
+
|
18
|
+
def set_allmaps_id
|
19
|
+
self.allmaps_id = Digest::SHA1.hexdigest(manifest_id)[0..15] if manifest_id.present?
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Extends Blacklight::Solr::Document for Blacklight::Allmaps specific functionality
|
4
|
+
module Blacklight
|
5
|
+
module Allmaps
|
6
|
+
module SolrDocument
|
7
|
+
# Blacklight
|
8
|
+
# @TODO: Make this configurable
|
9
|
+
def iiif_manifest_url
|
10
|
+
self["iiif_manifest_url_ssi"]
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,213 @@
|
|
1
|
+
<!-- Georeferenced Map -->
|
2
|
+
<h3 class="h6">Georeferenced Map</h3>
|
3
|
+
<div id="allmaps-map" class="mt-3 mb-3" style="height: 400px;"></div>
|
4
|
+
|
5
|
+
<!-- Leaflet -->
|
6
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/leaflet@1.9.4/dist/leaflet.min.css">
|
7
|
+
<script src="https://cdn.jsdelivr.net/npm/leaflet@1.9.4/dist/leaflet.min.js"></script>
|
8
|
+
|
9
|
+
<!-- Leaflet Fullscreen -->
|
10
|
+
<script src="https://cdn.jsdelivr.net/npm/leaflet-fullscreen@1.0.2/dist/Leaflet.fullscreen.min.js"></script>
|
11
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/leaflet-fullscreen@1.0.2/dist/leaflet.fullscreen.min.css">
|
12
|
+
|
13
|
+
<!-- Leaflet Layer Opacity -->
|
14
|
+
<script>
|
15
|
+
// Adopts the Mapbox opacity control into a Leaflet plugin
|
16
|
+
|
17
|
+
!function(global) {
|
18
|
+
'use strict';
|
19
|
+
|
20
|
+
L.Control.LayerOpacity = L.Control.extend({
|
21
|
+
initialize: function(layer) {
|
22
|
+
var options = { position: 'topleft' };
|
23
|
+
|
24
|
+
// check if layer is actually a layer group
|
25
|
+
if (typeof layer.getLayers !== 'undefined') {
|
26
|
+
|
27
|
+
// add first layer from layer group to options
|
28
|
+
options.layer = layer.getLayers()[0];
|
29
|
+
} else {
|
30
|
+
|
31
|
+
// add layer to options
|
32
|
+
options.layer = layer;
|
33
|
+
}
|
34
|
+
|
35
|
+
L.Util.setOptions(this, options);
|
36
|
+
},
|
37
|
+
|
38
|
+
onAdd: function(map) {
|
39
|
+
var container = L.DomUtil.create('div', 'opacity-control unselectable'),
|
40
|
+
controlArea = L.DomUtil.create('div', 'opacity-area', container),
|
41
|
+
handle = L.DomUtil.create('div', 'opacity-handle', container),
|
42
|
+
handleArrowUp = L.DomUtil.create('div', 'opacity-arrow-up', handle),
|
43
|
+
handleText = L.DomUtil.create('div', 'opacity-text', handle),
|
44
|
+
handleArrowDown = L.DomUtil.create('div', 'opacity-arrow-down', handle),
|
45
|
+
bottom = L.DomUtil.create('div', 'opacity-bottom', container);
|
46
|
+
|
47
|
+
L.DomEvent.stopPropagation(container);
|
48
|
+
L.DomEvent.disableClickPropagation(container);
|
49
|
+
|
50
|
+
this.setListeners(handle, bottom, handleText);
|
51
|
+
handle.style.top = handle.offsetTop - 13 + 50 + 'px';
|
52
|
+
handleText.innerHTML = parseInt(this.options.layer.options.opacity * 100) + '%';
|
53
|
+
return container;
|
54
|
+
},
|
55
|
+
|
56
|
+
setListeners: function(handle, bottom, handleText) {
|
57
|
+
var _this = this,
|
58
|
+
start = false,
|
59
|
+
startTop;
|
60
|
+
|
61
|
+
L.DomEvent.on(document, 'mousemove', function(e) {
|
62
|
+
if (!start) return;
|
63
|
+
var percentInverse = Math.max(0, Math.min(200, startTop + parseInt(e.clientY, 10) - start)) / 2;
|
64
|
+
handle.style.top = ((percentInverse * 2) - 13) + 'px';
|
65
|
+
handleText.innerHTML = Math.round((1 - (percentInverse / 100)) * 100) + '%';
|
66
|
+
bottom.style.height = Math.max(0, (((100 - percentInverse) * 2) - 13)) + 'px';
|
67
|
+
bottom.style.top = Math.min(200, (percentInverse * 2) + 13) + 'px';
|
68
|
+
_this.options.layer.setOpacity(1 - (percentInverse / 100));
|
69
|
+
});
|
70
|
+
|
71
|
+
L.DomEvent.on(handle, 'mousedown', function(e) {
|
72
|
+
start = parseInt(e.clientY, 10);
|
73
|
+
startTop = handle.offsetTop - 12;
|
74
|
+
return false;
|
75
|
+
});
|
76
|
+
|
77
|
+
L.DomEvent.on(document, 'mouseup', function(e) {
|
78
|
+
start = null;
|
79
|
+
});
|
80
|
+
}
|
81
|
+
});
|
82
|
+
}(this);
|
83
|
+
</script>
|
84
|
+
<style>
|
85
|
+
.leaflet-control.opacity-control {
|
86
|
+
background-color: #a9acb1;
|
87
|
+
border-radius: 15px;
|
88
|
+
color: black;
|
89
|
+
font: bold 18px 'Lucida Console', Monaco, monospace;
|
90
|
+
display: block;
|
91
|
+
height: 200px;
|
92
|
+
left: 11px;
|
93
|
+
position: relative;
|
94
|
+
top: 15px;
|
95
|
+
width: 5px;
|
96
|
+
|
97
|
+
.opacity-handle {
|
98
|
+
background-color: #fff;
|
99
|
+
border-radius: 4px;
|
100
|
+
border: 1px solid #eee;
|
101
|
+
cursor: ns-resize;
|
102
|
+
font-size: 10px;
|
103
|
+
height: 26px;
|
104
|
+
left: -11px;
|
105
|
+
line-height: 26px;
|
106
|
+
position: absolute;
|
107
|
+
text-align: center;
|
108
|
+
top: 0;
|
109
|
+
width: 26px;
|
110
|
+
@include map-control-shadow;
|
111
|
+
|
112
|
+
&:hover {
|
113
|
+
background-color: #f4f4f4;
|
114
|
+
}
|
115
|
+
}
|
116
|
+
|
117
|
+
.opacity-arrow-up {
|
118
|
+
color: #aaa;
|
119
|
+
position: absolute;
|
120
|
+
top: -11px;
|
121
|
+
text-align: center;
|
122
|
+
width: 100%;
|
123
|
+
|
124
|
+
&:before {
|
125
|
+
content: '=';
|
126
|
+
}
|
127
|
+
}
|
128
|
+
|
129
|
+
.opacity-arrow-down {
|
130
|
+
bottom: -10px;
|
131
|
+
color: #aaa;
|
132
|
+
position: absolute;
|
133
|
+
text-align: center;
|
134
|
+
width: 100%;
|
135
|
+
|
136
|
+
&:before {
|
137
|
+
content: '=';
|
138
|
+
}
|
139
|
+
}
|
140
|
+
|
141
|
+
.opacity-bottom {
|
142
|
+
background-color: #017afd;
|
143
|
+
border-radius: 15px;
|
144
|
+
display: block;
|
145
|
+
height: 137px;
|
146
|
+
left: 0px;
|
147
|
+
position: relative;
|
148
|
+
top: 63px;
|
149
|
+
width: 5px;
|
150
|
+
}
|
151
|
+
|
152
|
+
// Area underneath slider to prevent unintentioned map clicks
|
153
|
+
.opacity-area {
|
154
|
+
padding: 14px;
|
155
|
+
cursor: default;
|
156
|
+
height: 200px;
|
157
|
+
left: -11px;
|
158
|
+
position: absolute;
|
159
|
+
top: 0px;
|
160
|
+
width: 20px;
|
161
|
+
}
|
162
|
+
}
|
163
|
+
|
164
|
+
.opacity-control.unselectable {
|
165
|
+
-webkit-touch-callout: none;
|
166
|
+
-webkit-user-select: none;
|
167
|
+
-khtml-user-select: none;
|
168
|
+
-moz-user-select: none;
|
169
|
+
-ms-user-select: none;
|
170
|
+
user-select: none;
|
171
|
+
}
|
172
|
+
</style>
|
173
|
+
|
174
|
+
<!-- Allmaps -->
|
175
|
+
<script src="https://cdn.jsdelivr.net/npm/@allmaps/leaflet/dist/bundled/allmaps-leaflet-1.9.umd.js"></script>
|
176
|
+
|
177
|
+
<script>
|
178
|
+
document.addEventListener("DOMContentLoaded", () => {
|
179
|
+
const element = document.getElementById("allmaps-map");
|
180
|
+
|
181
|
+
const map = L.map("allmaps-map", {
|
182
|
+
center: [0, 0],
|
183
|
+
zoom: 15,
|
184
|
+
zoomAnimationThreshold: 1
|
185
|
+
});
|
186
|
+
|
187
|
+
// Basemap and Attribution
|
188
|
+
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
|
189
|
+
attribution: "© <a href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap</a> contributors",
|
190
|
+
maxZoom: 18
|
191
|
+
}).addTo(map);
|
192
|
+
|
193
|
+
// Fullscreen control
|
194
|
+
map.addControl(new L.Control.Fullscreen({
|
195
|
+
position: "topright"
|
196
|
+
}));
|
197
|
+
|
198
|
+
const annotationUrl = "https://annotations.allmaps.org/manifests/<%= document.sidecar_allmaps.allmaps_id %>";
|
199
|
+
const warpedMapLayer = new Allmaps.WarpedMapLayer(annotationUrl)
|
200
|
+
.addTo(map);
|
201
|
+
|
202
|
+
// Layer opacity control
|
203
|
+
map.addControl(new L.Control.LayerOpacity(warpedMapLayer));
|
204
|
+
|
205
|
+
map.on(
|
206
|
+
"warpedmapadded",
|
207
|
+
(event) => {
|
208
|
+
map.fitBounds(warpedMapLayer.getBounds());
|
209
|
+
},
|
210
|
+
map
|
211
|
+
);
|
212
|
+
});
|
213
|
+
</script>
|
@@ -0,0 +1,47 @@
|
|
1
|
+
<!-- Georeferenced Map -->
|
2
|
+
<h3 class="h6">Georeferenced Map</h3>
|
3
|
+
<div id="allmaps-map" data-map-geom="<%= document.geometry.geojson %>" style="height: 400px;"></div>
|
4
|
+
|
5
|
+
<script src="https://cdn.jsdelivr.net/npm/@allmaps/leaflet/dist/bundled/allmaps-leaflet-1.9.umd.js"></script>
|
6
|
+
<script>
|
7
|
+
document.addEventListener("DOMContentLoaded", () => {
|
8
|
+
const element = document.getElementById("allmaps-map");
|
9
|
+
const value = element.getAttribute("data-map-geom");
|
10
|
+
const layer = L.geoJSON();
|
11
|
+
layer.addData(JSON.parse(value));
|
12
|
+
const bounds = layer.getBounds();
|
13
|
+
const annotationUrl = "https://annotations.allmaps.org/manifests/<%= document.sidecar_allmaps.allmaps_id %>";
|
14
|
+
const warpedMapLayer = new Allmaps.WarpedMapLayer(annotationUrl);
|
15
|
+
const geoTab = document.getElementById("georeferenced-tab");
|
16
|
+
const map = L.map("allmaps-map");
|
17
|
+
|
18
|
+
// Basemap and Attribution
|
19
|
+
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
|
20
|
+
attribution: "© <a href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap</a> contributors",
|
21
|
+
maxZoom: 18
|
22
|
+
}).addTo(map);
|
23
|
+
|
24
|
+
// Fullscreen control
|
25
|
+
map.addControl(new L.Control.Fullscreen({
|
26
|
+
position: "topright"
|
27
|
+
}));
|
28
|
+
|
29
|
+
// Layer opacity control
|
30
|
+
map.addControl(new L.Control.LayerOpacity(warpedMapLayer));
|
31
|
+
|
32
|
+
// Leaflet: Multiple maps on the same page is a challenge...
|
33
|
+
// 1. Need to watch the page DOM mutate
|
34
|
+
// 2. When the georeferenced tab is visible, invalidate the map size
|
35
|
+
// 3. Add the WarpedMapLayer to the map
|
36
|
+
// 4. Fit the map to the GeoJSON bounds
|
37
|
+
const observer = new MutationObserver(function(){
|
38
|
+
if(geoTab.style.display !== "none"){
|
39
|
+
map.invalidateSize();
|
40
|
+
warpedMapLayer.addTo(map);
|
41
|
+
map.fitBounds(bounds);
|
42
|
+
}
|
43
|
+
});
|
44
|
+
|
45
|
+
observer.observe(geoTab, { attributes: true });
|
46
|
+
});
|
47
|
+
</script>
|
@@ -0,0 +1,55 @@
|
|
1
|
+
<div class="card mb-3 mt-3" id="allmaps-sidebar" data-iiif-manifest="<%=@document.iiif_manifest_url %>">
|
2
|
+
<div class="card-header">
|
3
|
+
<h2 class="mb-0 h6"><%= t('home.georeferencing') %></h2>
|
4
|
+
</div>
|
5
|
+
|
6
|
+
<div class="card-body" id="georeferencing">
|
7
|
+
<% if @document.sidecar_allmaps.georeferenced? %>
|
8
|
+
<div id="georef_present">
|
9
|
+
<%= link_to 'View this georeferenced item', 'https://viewer.allmaps.org/?url=https://annotations.allmaps.org/?url=' + @document.iiif_manifest_url, target: '_blank' %>
|
10
|
+
</div>
|
11
|
+
<% else %>
|
12
|
+
<%# content will be updated via updateGeorefLinks(); %>
|
13
|
+
<div id="georef_loading">
|
14
|
+
Loading...
|
15
|
+
</div>
|
16
|
+
<% end %>
|
17
|
+
</div>
|
18
|
+
|
19
|
+
<div class="card-footer text-muted">
|
20
|
+
<small>
|
21
|
+
<a href="https://allmaps.org" class="text-decoration-none text-dark">
|
22
|
+
<%= image_tag 'blacklight/allmaps/allmaps-logo.svg', alt: 'Allmaps', height:"20" %>
|
23
|
+
Allmaps
|
24
|
+
</a>
|
25
|
+
</small>
|
26
|
+
</div>
|
27
|
+
</div>
|
28
|
+
|
29
|
+
<script type="text/javascript">
|
30
|
+
document.addEventListener('DOMContentLoaded', () => {
|
31
|
+
const updateGeorefLinks = async () => {
|
32
|
+
const manifestUrl = document.getElementById('allmaps-sidebar').getAttribute('data-iiif-manifest');
|
33
|
+
const georefDiv = document.getElementById('georeferencing');
|
34
|
+
const annotationUrlByIIIFUri = `https://annotations.allmaps.org/?url=${manifestUrl}`;
|
35
|
+
|
36
|
+
try {
|
37
|
+
const response = await fetch(annotationUrlByIIIFUri);
|
38
|
+
if (!response.ok) {
|
39
|
+
georefDiv.innerHTML = `<a href="https://editor.allmaps.org/#/collection?url=${manifestUrl}">Georeference this item</a>`;
|
40
|
+
} else {
|
41
|
+
const annotationUrl = response.url;
|
42
|
+
georefDiv.innerHTML = `<a href="https://viewer.allmaps.org/?url=${annotationUrl}">View this georeferenced item</a>`;
|
43
|
+
|
44
|
+
// TODO: Ping Blacklight::Allmaps::AnnotationsController to save the annotation data
|
45
|
+
|
46
|
+
|
47
|
+
}
|
48
|
+
} catch (error) {
|
49
|
+
console.error("Fetch error:", error);
|
50
|
+
}
|
51
|
+
};
|
52
|
+
|
53
|
+
updateGeorefLinks();
|
54
|
+
});
|
55
|
+
</script>
|
@@ -0,0 +1,52 @@
|
|
1
|
+
<% document ||= @document %>
|
2
|
+
<div class='row'>
|
3
|
+
<div id='viewer-container' class="col-md-12">
|
4
|
+
|
5
|
+
<span class="sr-only">Georeferenced: <%= document.sidecar_allmaps.georeferenced? %></span>
|
6
|
+
|
7
|
+
<ul class="nav nav-tabs mt-3" id="myTab" role="tablist">
|
8
|
+
<li class="nav-item" role="presentation">
|
9
|
+
<button class="nav-link active" id="item-viewer" data-toggle="tab" data-target="#item-viewer-tab" type="button" role="tab" aria-controls="home" aria-selected="true">Item Viewer</button>
|
10
|
+
</li>
|
11
|
+
<% if document.sidecar_allmaps.georeferenced? %>
|
12
|
+
<li class="nav-item" role="presentation">
|
13
|
+
<button class="nav-link" id="georeferenced-viewer" data-toggle="tab" data-target="#georeferenced-tab" type="button" role="tab" aria-controls="profile" aria-selected="false">Georeferenced Map</button>
|
14
|
+
</li>
|
15
|
+
<% end %>
|
16
|
+
</ul>
|
17
|
+
|
18
|
+
<div class="tab-content mt-3">
|
19
|
+
<div class="tab-pane fade show active" id="item-viewer-tab" role="tabpanel" aria-labelledby="item-viewer-tab">
|
20
|
+
<% if show_help_text?('viewer_protocol', document.viewer_protocol) %>
|
21
|
+
<%= render_help_text_entry('viewer_protocol', document.viewer_protocol) %>
|
22
|
+
<% end %>
|
23
|
+
|
24
|
+
<% if document.item_viewer.index_map %>
|
25
|
+
<div class="index-map-legend">
|
26
|
+
<div class="index-map-legend-info">
|
27
|
+
<span class="index-map-legend-default"></span>
|
28
|
+
<p><span class="sr-only">Green tile indicates </span>Map held by collection</p>
|
29
|
+
</div>
|
30
|
+
<div class="index-map-legend-info">
|
31
|
+
<span class="index-map-legend-unavailable"></span>
|
32
|
+
<p><span class="sr-only">Yellow tile indicates </span>Map not held by collection</p>
|
33
|
+
</div>
|
34
|
+
<div class="index-map-legend-info">
|
35
|
+
<span class="index-map-legend-selected"></span>
|
36
|
+
<p><span class="sr-only">Blue tile indicates </span>Selected map tile</p>
|
37
|
+
</div>
|
38
|
+
</div>
|
39
|
+
<% end %>
|
40
|
+
|
41
|
+
<%= content_tag :div, id: 'map', aria: { label: t('geoblacklight.map.label') }, data: { map: 'item', protocol: document.viewer_protocol.camelize, url: document.viewer_endpoint, 'layer-id' => document.wxs_identifier, 'map-geom' => document.geometry.geojson, 'catalog-path'=> search_catalog_path, available: document_available?, inspect: show_attribute_table?, basemap: geoblacklight_basemap, leaflet_options: leaflet_options } do %>
|
42
|
+
<% end %>
|
43
|
+
</div>
|
44
|
+
|
45
|
+
<% if document.sidecar_allmaps.georeferenced? %>
|
46
|
+
<div class="tab-pane fade" id="georeferenced-tab" role="tabpanel" aria-labelledby="georeferenced-tab">
|
47
|
+
<%= render partial: 'allmaps/show/geoblacklight', locals: { document: document } %>
|
48
|
+
</div>
|
49
|
+
<% end %>
|
50
|
+
</div>
|
51
|
+
</div>
|
52
|
+
</div>
|
@@ -0,0 +1,58 @@
|
|
1
|
+
<% if Blacklight::VERSION.to_i > 8 %>
|
2
|
+
<%= render blacklight_config.track_search_session.item_pagination_component.new(search_context: @search_context, search_session: search_session, current_document: @document) if blacklight_config.track_search_session.item_pagination_component %>
|
3
|
+
<% @page_title = t('blacklight.search.show.title', document_title: document_presenter(@document).html_title, application_name: application_name).html_safe %>
|
4
|
+
<% content_for(:head) { render_link_rel_alternates } %>
|
5
|
+
|
6
|
+
<% document_component = blacklight_config.view_config(:show).document_component -%>
|
7
|
+
<%= render (document_component).new(document_component.collection_parameter => document_presenter(@document), component: :div, show: true, partials: blacklight_config.view_config(:show).partials) do |component| %>
|
8
|
+
<% component.with_title(as: 'h1', classes: '', link_to_document: false, actions: false) %>
|
9
|
+
<% component.with_footer do %>
|
10
|
+
|
11
|
+
<% unless defined?(Geoblacklight) %>
|
12
|
+
<% if @document.sidecar_allmaps.georeferenced? %>
|
13
|
+
<%= render partial: 'allmaps/show/blacklight', locals: { document: @document } %>
|
14
|
+
<% end %>
|
15
|
+
<% end %>
|
16
|
+
|
17
|
+
<% if @document.respond_to?(:export_as_openurl_ctx_kev) %>
|
18
|
+
<!-- COinS, for Zotero among others. -->
|
19
|
+
<span class="Z3988" title="<%= @document.export_as_openurl_ctx_kev(document_presenter(@document).display_type) %>"></span>
|
20
|
+
<% end %>
|
21
|
+
<% end %>
|
22
|
+
<% end %>
|
23
|
+
|
24
|
+
<% else %>
|
25
|
+
<%= render(Blacklight::SearchContextComponent.new(search_context: @search_context, search_session: search_session)) if search_session['document_id'] == @document.id %>
|
26
|
+
|
27
|
+
<% @page_title = t('blacklight.search.show.title', document_title: Deprecation.silence(Blacklight::BlacklightHelperBehavior) { document_show_html_title }, application_name: application_name).html_safe %>
|
28
|
+
<% content_for(:head) { render_link_rel_alternates } %>
|
29
|
+
|
30
|
+
<%= render (blacklight_config.view_config(:show).document_component || Blacklight::DocumentComponent).new(presenter: document_presenter(@document), component: :div, title_component: :h1, show: true) do |component| %>
|
31
|
+
<% component.with_footer do %>
|
32
|
+
|
33
|
+
<% unless defined?(Geoblacklight) %>
|
34
|
+
<% if @document.sidecar_allmaps.georeferenced? %>
|
35
|
+
<%= render partial: 'allmaps/show/blacklight', locals: { document: @document } %>
|
36
|
+
<% end %>
|
37
|
+
<% end %>
|
38
|
+
|
39
|
+
<% if @document.respond_to?(:export_as_openurl_ctx_kev) %>
|
40
|
+
<!--
|
41
|
+
// COinS, for Zotero among others.
|
42
|
+
// This document_partial_name(@document) business is not quite right,
|
43
|
+
// but has been there for a while.
|
44
|
+
-->
|
45
|
+
<span class="Z3988" title="<%= @document.export_as_openurl_ctx_kev(Deprecation.silence(Blacklight::RenderPartialsHelperBehavior) { document_partial_name(@document) }) %>"></span>
|
46
|
+
<% end %>
|
47
|
+
<% end %>
|
48
|
+
|
49
|
+
<%# Use :body for complete backwards compatibility (overriding the component body markup),
|
50
|
+
but if the app explicitly opted-in to components, make the partials data available as :partials to ease migrations pain %>
|
51
|
+
<% component.public_send(blacklight_config.view_config(:show).document_component.blank? && blacklight_config.view_config(:show).partials.any? ? :with_body : :with_partial) do %>
|
52
|
+
<div id="doc_<%= @document.id.to_s.parameterize %>">
|
53
|
+
<%= render_document_partials @document, blacklight_config.view_config(:show).partials, component: component %>
|
54
|
+
</div>
|
55
|
+
<% end %>
|
56
|
+
<% end %>
|
57
|
+
<% end %>
|
58
|
+
|
@@ -0,0 +1,10 @@
|
|
1
|
+
<% if Blacklight::VERSION.to_i > 8 %>
|
2
|
+
<% presenter = document_presenter(document) %>
|
3
|
+
<%= render presenter.view_config.sidebar_component.new(presenter: presenter) %>
|
4
|
+
<% else %>
|
5
|
+
<%= render 'show_tools', document: @document %>
|
6
|
+
<%= render(Blacklight::Document::MoreLikeThisComponent.new(document: @document)) %>
|
7
|
+
<% if georeferenceable? %>
|
8
|
+
<%= render :partial => "allmaps/sidebar/allmaps" %>
|
9
|
+
<% end %>
|
10
|
+
<% end %>
|
@@ -0,0 +1,20 @@
|
|
1
|
+
<%= render :partial => 'show_sidebar_static_map' if render_sidebar_map?(@document) %>
|
2
|
+
<%= render :partial => 'show_tools' %>
|
3
|
+
|
4
|
+
<% if georeferenceable? %>
|
5
|
+
<%= render :partial => "allmaps/sidebar/allmaps" %>
|
6
|
+
<% end %>
|
7
|
+
|
8
|
+
<div class="sidebar-buttons">
|
9
|
+
<%= render :partial => "show_web_services" %>
|
10
|
+
<%= render :partial => "show_downloads" %>
|
11
|
+
</div>
|
12
|
+
|
13
|
+
<% unless @document.more_like_this.empty? %>
|
14
|
+
<div class="card">
|
15
|
+
<div class="card-header">More Like This</div>
|
16
|
+
<div class="card-body">
|
17
|
+
<%= render :collection => @document.more_like_this, :partial => 'show_more_like_this', :as => :document %>
|
18
|
+
</div>
|
19
|
+
</div>
|
20
|
+
<% end %>
|
data/config/routes.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
class CreateSolrDocumentSidecars < ActiveRecord::Migration[7.0]
|
2
|
+
def change
|
3
|
+
create_table :blacklight_allmaps_sidecars do |t|
|
4
|
+
t.string :solr_document_id, index: true
|
5
|
+
t.string :document_type, default: "SolrDocument"
|
6
|
+
t.string :manifest_id, index: true
|
7
|
+
t.boolean :annotated, default: false
|
8
|
+
t.string :allmaps_id, index: true
|
9
|
+
t.text :iiif_manifest
|
10
|
+
t.text :allmaps_annotation
|
11
|
+
t.bigint :solr_version
|
12
|
+
t.timestamps
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "blacklight"
|
4
|
+
|
5
|
+
namespace :blacklight_allmaps do
|
6
|
+
namespace :index do
|
7
|
+
desc "Index - add Allmaps fixture data to Blacklight solr"
|
8
|
+
task :bl_fixtures do
|
9
|
+
# @TODO: JSON works when pasted into Solr, but fails here?
|
10
|
+
docs = Dir["spec/fixtures/solr_documents/bl_*.json"].map { |f| JSON.parse File.read(f) }.flatten
|
11
|
+
Blacklight.default_index.connection.add docs
|
12
|
+
Blacklight.default_index.connection.commit
|
13
|
+
end
|
14
|
+
|
15
|
+
desc "Index - add Allmaps fixture data to GeoBlacklight solr"
|
16
|
+
task :gbl_fixtures do
|
17
|
+
docs = Dir["spec/fixtures/solr_documents/gbl_*.json"].map { |f| JSON.parse File.read(f) }.flatten
|
18
|
+
Blacklight.default_index.connection.add docs
|
19
|
+
Blacklight.default_index.connection.commit
|
20
|
+
end
|
21
|
+
|
22
|
+
desc "Index - add Allmaps facet data to GeoBlacklight solr"
|
23
|
+
task gbl_georeferenced_facet: [:environment] do
|
24
|
+
# Steps
|
25
|
+
# 1. Use cursor to paginate all documents in Solr
|
26
|
+
# 2. Determine which documents have georeferenced data
|
27
|
+
# 3. Clean JSON for re-indexing
|
28
|
+
# 4. Add gbl_georeferenced_b values
|
29
|
+
# 5. Re-index the georeferenced documents
|
30
|
+
|
31
|
+
# 1. Get all the documents from Solr
|
32
|
+
cursor_mark = "*"
|
33
|
+
loop do
|
34
|
+
response = Blacklight.default_index.connection.get(
|
35
|
+
"select", params: {
|
36
|
+
q: "*:*", # all docs
|
37
|
+
fl: "*", # all fields
|
38
|
+
cursorMark: cursor_mark, # use the cursor mark to handle paging
|
39
|
+
rows: 1000,
|
40
|
+
sort: "id asc" # must sort by id to use the cursor mark
|
41
|
+
}
|
42
|
+
)
|
43
|
+
|
44
|
+
response["response"]["docs"].each do |doc|
|
45
|
+
# 2. Determine which documents have georeferenced data
|
46
|
+
solr_document = SolrDocument.find(doc["id"])
|
47
|
+
if solr_document.sidecar.present? && solr_document.sidecar.annotated?
|
48
|
+
|
49
|
+
# 3. Clean JSON for re-indexing
|
50
|
+
keys_for_deletion = %w[
|
51
|
+
_version_
|
52
|
+
timestamp
|
53
|
+
solr_bboxtype
|
54
|
+
solr_bboxtype__minX
|
55
|
+
solr_bboxtype__minY
|
56
|
+
solr_bboxtype__maxX
|
57
|
+
solr_bboxtype__maxY
|
58
|
+
]
|
59
|
+
|
60
|
+
cleaned_doc = doc.except!(*keys_for_deletion)
|
61
|
+
|
62
|
+
# 4. Add gbl_georeferenced_b value
|
63
|
+
# @TODO: add allmaps_id?
|
64
|
+
cleaned_doc["gbl_georeferenced_b"] = true
|
65
|
+
|
66
|
+
# 5. Re-index the georeferenced documents
|
67
|
+
Blacklight.default_index.connection.add cleaned_doc
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
break if response["nextCursorMark"] == cursor_mark # this means the result set is finished
|
72
|
+
cursor_mark = response["nextCursorMark"]
|
73
|
+
end
|
74
|
+
Blacklight.default_index.connection.commit
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
namespace :blacklight_allmaps do
|
4
|
+
namespace :sidecars do
|
5
|
+
namespace :harvest do
|
6
|
+
desc "Sidecars - Harvest: Crawl SolrDocuments to store Allmaps data locally"
|
7
|
+
task allmaps: [:environment] do
|
8
|
+
cursor_mark = "*"
|
9
|
+
loop do
|
10
|
+
response = Blacklight.default_index.connection.get(
|
11
|
+
"select", params: {
|
12
|
+
q: "*:*", # all docs
|
13
|
+
fl: "id", # just id field
|
14
|
+
cursorMark: cursor_mark, # use the cursor mark to handle paging
|
15
|
+
rows: 1000,
|
16
|
+
sort: "id asc" # must sort by id to use the cursor mark
|
17
|
+
}
|
18
|
+
)
|
19
|
+
|
20
|
+
response["response"]["docs"].each do |doc|
|
21
|
+
puts "Harvesting Allmaps data for #{doc["id"]}"
|
22
|
+
Blacklight::Allmaps::StoreSidecarAnnotation.perform_later(doc["id"])
|
23
|
+
end
|
24
|
+
|
25
|
+
break if response["nextCursorMark"] == cursor_mark # this means the result set is finished
|
26
|
+
cursor_mark = response["nextCursorMark"]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
desc "Sidecars - Purage all: Destroy all harvested images and sidecar AR objects"
|
32
|
+
task purge_all: [:environment] do
|
33
|
+
Blacklight::Allmaps::Sidecar.destroy_all
|
34
|
+
end
|
35
|
+
|
36
|
+
desc "Sidecars - Purge orphans: Destroy orphaned sidecar Active Record objects"
|
37
|
+
# When a Sidecar object exists,
|
38
|
+
# but it's corresponding SolrDocument is no longer in the Solr index.
|
39
|
+
task purge_orphans: [:environment] do
|
40
|
+
# Remove all sidecars that have no corresponding SolrDocument
|
41
|
+
sidecars = Blacklight::Allmaps::Sidecar.all
|
42
|
+
sidecars.each do |sc|
|
43
|
+
::SolrDocument.find(sc.document_id)
|
44
|
+
rescue
|
45
|
+
sc.destroy
|
46
|
+
puts "orphaned / #{sc.document_id} / destroyed"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|