blacklight_allmaps 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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