blacklight_allmaps 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +201 -0
  3. data/README.md +109 -0
  4. data/Rakefile +98 -0
  5. data/app/assets/config/blacklight_allmaps_manifest.js +2 -0
  6. data/app/assets/images/blacklight/allmaps/allmaps-logo.svg +39 -0
  7. data/app/assets/images/blacklight/allmaps/blacklight-logo.png +0 -0
  8. data/app/assets/images/blacklight/allmaps/geoblacklight-logo.png +0 -0
  9. data/app/assets/images/blacklight/allmaps/logo.svg +39 -0
  10. data/app/assets/stylesheets/blacklight_allmaps/application.css +15 -0
  11. data/app/controllers/blacklight/allmaps/application_controller.rb +6 -0
  12. data/app/helpers/blacklight/allmaps/application_helper.rb +17 -0
  13. data/app/javascripts/map_controller.js +39 -0
  14. data/app/jobs/blacklight/allmaps/application_job.rb +6 -0
  15. data/app/jobs/blacklight/allmaps/store_sidecar_annotation.rb +39 -0
  16. data/app/mailers/blacklight/allmaps/application_mailer.rb +8 -0
  17. data/app/models/blacklight/allmaps/application_record.rb +7 -0
  18. data/app/models/blacklight/allmaps/sidecar.rb +23 -0
  19. data/app/models/concerns/blacklight/allmaps/solr_document.rb +14 -0
  20. data/app/views/allmaps/show/_blacklight.html.erb +213 -0
  21. data/app/views/allmaps/show/_geoblacklight.html.erb +47 -0
  22. data/app/views/allmaps/sidebar/_allmaps.html.erb +55 -0
  23. data/app/views/catalog/_show_default_viewer_container.html.erb +52 -0
  24. data/app/views/catalog/_show_main_content.html.erb +58 -0
  25. data/app/views/catalog/_show_sidebar.html.erb +5 -0
  26. data/app/views/catalog/_show_sidebar_blacklight.html.erb +10 -0
  27. data/app/views/catalog/_show_sidebar_geoblacklight.html.erb +20 -0
  28. data/config/routes.rb +2 -0
  29. data/db/migrate/20240307155110_create_solr_document_sidecars.rb +15 -0
  30. data/lib/blacklight/allmaps/engine.rb +6 -0
  31. data/lib/blacklight/allmaps/rake_task.rb +7 -0
  32. data/lib/blacklight/allmaps/tasks/index.rake +77 -0
  33. data/lib/blacklight/allmaps/tasks/sidecars.rake +50 -0
  34. data/lib/blacklight/allmaps/version.rb +5 -0
  35. data/lib/blacklight/allmaps.rb +7 -0
  36. data/lib/generators/blacklight/allmaps/config_generator.rb +54 -0
  37. data/lib/generators/blacklight/allmaps/install_generator.rb +27 -0
  38. data/lib/generators/blacklight/allmaps/models_generator.rb +46 -0
  39. data/lib/generators/blacklight/allmaps/templates/manifest.js +5 -0
  40. 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: "&copy; <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: "&copy; <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,5 @@
1
+ <% if defined?(Geoblacklight) && Geoblacklight::VERSION %>
2
+ <%= render :partial => 'show_sidebar_geoblacklight' %>
3
+ <% elsif Blacklight::VERSION %>
4
+ <%= render :partial => 'show_sidebar_blacklight' %>
5
+ <% end %>
@@ -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,2 @@
1
+ Blacklight::Allmaps::Engine.routes.draw do
2
+ end
@@ -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,6 @@
1
+ module Blacklight
2
+ module Allmaps
3
+ class Engine < ::Rails::Engine
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,7 @@
1
+ module Blacklight
2
+ module Allmaps
3
+ module RakeTask
4
+ Dir[File.expand_path(File.join(File.dirname(__FILE__), "tasks/*.rake"))].each { |ext| load ext } if defined?(Rake)
5
+ end
6
+ end
7
+ 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