geo_combine 0.11.0 → 0.12.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 (62) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +35 -12
  3. data/.rubocop.yml +4 -0
  4. data/.rubocop_todo.yml +2 -10
  5. data/Gemfile +3 -0
  6. data/README.md +106 -63
  7. data/geo_combine.gemspec +1 -1
  8. data/lib/geo_combine/exceptions.rb +3 -0
  9. data/lib/geo_combine/fgdc.rb +7 -0
  10. data/lib/geo_combine/geoblacklight_aardvark.rb +70 -0
  11. data/lib/geo_combine/harvester.rb +104 -38
  12. data/lib/geo_combine/indexer.rb +39 -2
  13. data/lib/geo_combine/iso19139.rb +7 -0
  14. data/lib/geo_combine/migrators/v1_aardvark_migrator.rb +1 -2
  15. data/lib/geo_combine/schema.rb +32 -0
  16. data/lib/geo_combine/version.rb +1 -1
  17. data/lib/geo_combine/withdrawals.rb +125 -0
  18. data/lib/geo_combine.rb +25 -1
  19. data/lib/tasks/geo_combine.rake +11 -12
  20. data/lib/xslt/fgdc2Aardvark.xsl +1512 -0
  21. data/lib/xslt/iso2Aardvark.xsl +1634 -0
  22. data/spec/fixtures/docs/arizona_reservations_iso.xml +1183 -0
  23. data/spec/fixtures/docs/arizona_topo_iso.xml +560 -0
  24. data/spec/fixtures/docs/cornell_agdistricts_fgdc.xml +572 -0
  25. data/spec/fixtures/docs/duplicate_values_fgdc.xml +116 -0
  26. data/spec/fixtures/docs/gbl_keywords_fgdc.xml +105 -0
  27. data/spec/fixtures/docs/harvard_euratlas_fgdc.xml +449 -0
  28. data/spec/fixtures/docs/harvard_glb_fgdc.xml +455 -0
  29. data/spec/fixtures/docs/harvard_kng_fgdc.xml +403 -0
  30. data/spec/fixtures/docs/harvard_mapa_fgdc.xml +356 -0
  31. data/spec/fixtures/docs/harvard_nhgis_fgdc.xml +1706 -0
  32. data/spec/fixtures/docs/harvard_usgs_fgdc.xml +393 -0
  33. data/spec/fixtures/docs/nyu_colleges_iso.xml +616 -0
  34. data/spec/fixtures/docs/services_fgdc.xml +227 -0
  35. data/spec/fixtures/docs/services_iso.xml +433 -0
  36. data/spec/fixtures/indexing/no_schema.json +5 -0
  37. data/spec/fixtures/withdrawal/edu.example-a/metadata-aardvark/a-contradicted.json +8 -0
  38. data/spec/fixtures/withdrawal/edu.example-a/metadata-aardvark/a-live.json +8 -0
  39. data/spec/fixtures/withdrawal/edu.example-a/withdrawn.json +13 -0
  40. data/spec/fixtures/withdrawal/edu.example-b/metadata-aardvark/b-live.json +8 -0
  41. data/spec/fixtures/withdrawal/edu.example-b/withdrawn.json +7 -0
  42. data/spec/fixtures/withdrawal/edu.example-broken/withdrawn.json +4 -0
  43. data/spec/fixtures/withdrawal/edu.example-skipped/withdrawn.json +7 -0
  44. data/spec/fixtures/xml_docs.rb +110 -0
  45. data/spec/integration/withdrawals_spec.rb +47 -0
  46. data/spec/lib/geo_combine/ckan_metadata_spec.rb +44 -0
  47. data/spec/lib/geo_combine/fgdc_spec.rb +520 -0
  48. data/spec/lib/geo_combine/geoblacklight_aardvark_spec.rb +85 -0
  49. data/spec/lib/geo_combine/harvester_spec.rb +221 -11
  50. data/spec/lib/geo_combine/indexer_spec.rb +140 -2
  51. data/spec/lib/geo_combine/iso19139_spec.rb +540 -1
  52. data/spec/lib/geo_combine/logger_spec.rb +38 -0
  53. data/spec/lib/geo_combine/ogp_spec.rb +37 -0
  54. data/spec/lib/geo_combine/schema_spec.rb +74 -0
  55. data/spec/lib/geo_combine/withdrawals_spec.rb +294 -0
  56. data/spec/lib/tasks/geo_combine_spec.rb +51 -0
  57. data/spec/spec_helper.rb +4 -1
  58. metadata +68 -12
  59. data/lib/.DS_Store +0 -0
  60. data/lib/geo_combine/geo_blacklight_harvester.rb +0 -216
  61. data/lib/xslt/isoAardvark.xsl +0 -738
  62. data/spec/lib/geo_combine/geo_blacklight_harvester_spec.rb +0 -195
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 21614ec82bcbe5c5a7bb1291e398795f1baff048552746335f18f315da677c9e
4
- data.tar.gz: 6c94a18a0f43fd141f5967930a909efac60ab560ac91a0759e48cc45fdab39b5
3
+ metadata.gz: d06fb38092cd8d8db20f1c9dfa14c82c19162d23415d1c34d92a2edf3826ff56
4
+ data.tar.gz: c2f2e35183978ec29e04d976438c9c5c567dc05341b128074366359d6164b027
5
5
  SHA512:
6
- metadata.gz: 75ce7b76dfa6ad5905410e6b38a18ee98185c50d5d116c8fc5794eba3376e85a05d2e2ad7d70d6bde8de7b0073df22f15cb28759633e0d3c6e546e24a13dee82
7
- data.tar.gz: cbd5ccc9eac444e599e0dce59fda1f23ef752cec71508a83fbe32ea1b2785b5a8c52d66c4ebcebc81a913aaccf6f677f5555622a43a8e16e902ab5f3379cbfa0
6
+ metadata.gz: 13720c277a1d399d83a882df36eaad9f6c5cbef537fd2753f8391cc8ec73ab9f524308b2adc9c2b1b3bcefff44b928bd4d6b3fa5d05973b492e90bf79c0d524e
7
+ data.tar.gz: b9a42c999193d944a4c0b9fb309bcbb27a0f66bd8a022ea894cf7594acaf4370d66d1ed2a6d8b7601a1f8b6af1965dc343b823ebcf920790e5fe5e2a14de748d
@@ -6,11 +6,11 @@ jobs:
6
6
  rubocop:
7
7
  runs-on: ubuntu-latest
8
8
  steps:
9
- - uses: actions/checkout@v6
9
+ - uses: actions/checkout@v7
10
10
  - name: Set up Ruby and install dependencies
11
11
  uses: ruby/setup-ruby@v1
12
12
  with:
13
- ruby-version: 3.2
13
+ ruby-version: 3.3
14
14
  bundler-cache: true
15
15
  - name: Run linter
16
16
  run: bundle exec rubocop
@@ -18,27 +18,50 @@ jobs:
18
18
  test:
19
19
  runs-on: ubuntu-latest
20
20
  strategy:
21
+ fail-fast: false
21
22
  matrix:
22
- ruby: [3.2, 3.3, 3.4]
23
- faraday_version: ['', '~> 1.0'] # Defaults to whatever's the most recent version.
23
+ ruby: [3.3, 3.4, 4.0]
24
24
  steps:
25
- - uses: actions/checkout@v6
25
+ - uses: actions/checkout@v7
26
26
 
27
27
  - name: Set up Ruby and install dependencies
28
28
  uses: ruby/setup-ruby@v1
29
29
  with:
30
30
  ruby-version: ${{ matrix.ruby }}
31
31
  bundler-cache: true
32
- env:
33
- FARADAY_VERSION: ${{ matrix.faraday_version }}
34
32
 
35
- - name: "Run tests for Ruby ${{ matrix.ruby }} and Faraday ${{ matrix.faraday_version == '~> 1.0' && '1.x' || 'latest' }}"
33
+ - name: "Run tests for Ruby ${{ matrix.ruby }}"
36
34
  run: bundle exec rake spec
37
- env:
38
- FARADAY_VERSION: ${{ matrix.faraday_version }}
39
35
 
40
36
  - name: Upload coverage artifacts
41
- uses: actions/upload-artifact@v6
37
+ uses: actions/upload-artifact@v7
42
38
  with:
43
- name: "coverage-${{ matrix.ruby }}-${{ matrix.faraday_version == '~> 1.0' && 'faraday-1.x' || 'faraday-latest' }}"
39
+ name: "coverage-${{ matrix.ruby }}"
44
40
  path: coverage/
41
+
42
+ # Runs the specs that need a real Solr, using the GeoBlacklight schema
43
+ integration:
44
+ runs-on: ubuntu-latest
45
+ env:
46
+ GEOBLACKLIGHT_VERSION: v5.4.0
47
+ SOLR_INTEGRATION_URL: http://localhost:8983/solr/blacklight-core
48
+ steps:
49
+ - uses: actions/checkout@v7
50
+
51
+ - name: Start Solr with the GeoBlacklight ${{ env.GEOBLACKLIGHT_VERSION }} configuration
52
+ run: |
53
+ mkdir -p tmp/solr-conf
54
+ curl -sfL "https://github.com/geoblacklight/geoblacklight/archive/refs/tags/${GEOBLACKLIGHT_VERSION}.tar.gz" \
55
+ | tar -xz -C tmp/solr-conf --strip-components=3 "geoblacklight-${GEOBLACKLIGHT_VERSION#v}/solr/conf"
56
+ rm tmp/solr-conf/core.properties
57
+ docker run -d -p 8983:8983 -v "$PWD/tmp/solr-conf:/gbl-conf:ro" solr:9 solr-precreate blacklight-core /gbl-conf
58
+ timeout 120 sh -c 'until curl -sf "$SOLR_INTEGRATION_URL/get?id=ready" > /dev/null; do sleep 2; done'
59
+
60
+ - name: Set up Ruby and install dependencies
61
+ uses: ruby/setup-ruby@v1
62
+ with:
63
+ ruby-version: 3.4
64
+ bundler-cache: true
65
+
66
+ - name: Run tests, including integration tests against Solr
67
+ run: bundle exec rake spec
data/.rubocop.yml CHANGED
@@ -24,3 +24,7 @@ RSpec/MultipleMemoizedHelpers:
24
24
  RSpec/BeforeAfterAll:
25
25
  Exclude:
26
26
  - 'spec/lib/tasks/geo_combine_spec.rb'
27
+
28
+ Metrics/BlockLength:
29
+ Exclude:
30
+ - 'lib/tasks/*.rake'
data/.rubocop_todo.yml CHANGED
@@ -64,7 +64,6 @@ RSpec/ExampleLength:
64
64
  # Offense count: 4
65
65
  RSpec/ExpectInHook:
66
66
  Exclude:
67
- - 'spec/lib/geo_combine/geo_blacklight_harvester_spec.rb'
68
67
  - 'spec/lib/geo_combine/geoblacklight_spec.rb'
69
68
 
70
69
  # Offense count: 23
@@ -73,7 +72,6 @@ RSpec/ExpectInHook:
73
72
  RSpec/MessageSpies:
74
73
  Exclude:
75
74
  - 'spec/lib/geo_combine/esri_open_data_spec.rb'
76
- - 'spec/lib/geo_combine/geo_blacklight_harvester_spec.rb'
77
75
  - 'spec/lib/geo_combine/geoblacklight_spec.rb'
78
76
  - 'spec/lib/geo_combine/ogp_spec.rb'
79
77
  - 'spec/lib/geo_combine_spec.rb'
@@ -121,7 +119,6 @@ RSpec/SpecFilePathFormat:
121
119
  RSpec/StubbedMock:
122
120
  Exclude:
123
121
  - 'spec/lib/geo_combine/esri_open_data_spec.rb'
124
- - 'spec/lib/geo_combine/geo_blacklight_harvester_spec.rb'
125
122
  - 'spec/lib/geo_combine/geoblacklight_spec.rb'
126
123
  - 'spec/lib/geo_combine/ogp_spec.rb'
127
124
  - 'spec/lib/geo_combine_spec.rb'
@@ -131,16 +128,11 @@ RSpec/SubjectStub:
131
128
  Exclude:
132
129
  - 'spec/lib/geo_combine/ogp_spec.rb'
133
130
 
134
- # Offense count: 1
135
- # Configuration parameters: IgnoreNameless, IgnoreSymbolicNames.
136
- RSpec/VerifiedDoubles:
137
- Exclude:
138
- - 'spec/lib/geo_combine/geo_blacklight_harvester_spec.rb'
139
-
140
131
  # Offense count: 1
141
132
  Security/Open:
142
133
  Exclude:
143
134
  - 'lib/geo_combine/geoblacklight.rb'
135
+ - 'lib/geo_combine/geoblacklight_aardvark.rb'
144
136
 
145
137
  # Offense count: 6
146
138
  # Configuration parameters: AllowedConstants.
@@ -150,8 +142,8 @@ Style/Documentation:
150
142
  - 'test/**/*'
151
143
  - 'lib/geo_combine/bounding_box.rb'
152
144
  - 'lib/geo_combine/ckan_metadata.rb'
153
- - 'lib/geo_combine/geo_blacklight_harvester.rb'
154
145
  - 'lib/geo_combine/geoblacklight.rb'
146
+ - 'lib/geo_combine/geoblacklight_aardvark.rb'
155
147
  - 'lib/geo_combine/geometry_types.rb'
156
148
  - 'lib/geo_combine/iso19139.rb'
157
149
 
data/Gemfile CHANGED
@@ -7,3 +7,6 @@ gemspec
7
7
 
8
8
  gem 'byebug', require: false
9
9
  gem 'rdbg', require: false
10
+
11
+ # Json 3 drops quicks_mode which we use in our json-schema
12
+ gem 'json', '< 3'
data/README.md CHANGED
@@ -1,14 +1,14 @@
1
1
  # GeoCombine
2
2
 
3
3
  ![CI](https://github.com/OpenGeoMetadata/GeoCombine/actions/workflows/ruby.yml/badge.svg)
4
- [![Coverage Status](https://img.shields.io/badge/coverage-95%25-brightgreen)]()
4
+ [![Coverage Status](https://img.shields.io/badge/coverage-100%25-brightgreen)]()
5
5
  [![Gem Version](https://img.shields.io/gem/v/geo_combine.svg)](https://github.com/OpenGeoMetadata/GeoCombine/releases)
6
6
 
7
7
  A Ruby toolkit for managing geospatial metadata, including:
8
8
 
9
9
  - harvesting and indexing records from OpenGeoMetadata
10
- - harvesting records directly from a GeoBlacklight instance
11
10
  - converting geospatial metadata records between standards
11
+ - migrating OpenGeoMetadata records from older to newer schema versions
12
12
 
13
13
  ## Installation
14
14
 
@@ -54,6 +54,14 @@ By default, GeoCombine will only fetch and index records that are not marked as
54
54
  export OGM_SKIP_RESTRICTED=false
55
55
  ```
56
56
 
57
+ #### GitHub API token
58
+
59
+ GeoCombine uses the GitHub API to find OpenGeoMetadata repositories. Unauthenticated requests are limited to 60 per hour, and `geocombine:clone` makes one for each repository. If you hit the limit, set the `GITHUB_TOKEN` environment variable to a GitHub personal access token to raise it; the token only needs read access to public repositories:
60
+
61
+ ```sh
62
+ export GITHUB_TOKEN=your-token
63
+ ```
64
+
57
65
  #### Clone OpenGeoMetadata repositories locally
58
66
 
59
67
  ```sh
@@ -118,6 +126,36 @@ You can also set the Solr instance URL using `SOLR_URL`:
118
126
  SOLR_URL=http://www.example.com:1234/solr/collection bundle exec rake geocombine:index
119
127
  ```
120
128
 
129
+ #### Withdrawn records
130
+
131
+ An OpenGeoMetadata repository can withdraw records by listing them in a `withdrawn.json` file at its root:
132
+
133
+ ```json
134
+ {
135
+ "$schema": "https://opengeometadata.org/schema/ogm-withdrawals-1.0.json",
136
+ "ogm_version": "1.0",
137
+ "withdrawn": [
138
+ { "id": "stanford-bb058zh0946", "date": "2026-03-14", "reason": "superseded" }
139
+ ]
140
+ }
141
+ ```
142
+
143
+ After indexing, `geocombine:index` deletes the records these files list from your Solr index. It is careful about what it deletes:
144
+
145
+ - A record is only deleted if a `withdrawn.json` lists its `id` (or `layer_slug_s`, for `SCHEMA_VERSION=1.0`). Removing a record's file from a repository does not delete it from your index.
146
+ - A record that is still present in the repository is never deleted, even if it is also listed as withdrawn. GeoCombine logs a warning instead.
147
+ - A `withdrawn.json` that can't be read is logged and skipped; it doesn't stop indexing.
148
+ - Repositories listed in `OGM_SKIP_REPOS` can't delete anything.
149
+ - If the harvest stops before it has walked every record, nothing is deleted.
150
+
151
+ GeoCombine keeps no record of what it has deleted. Every run deletes every withdrawn record again, which changes nothing for records that are already gone. Deletes match by `id` alone, so a withdrawn `id` that matches a record you indexed from somewhere other than OpenGeoMetadata will delete that record too.
152
+
153
+ To delete withdrawn records without reindexing everything, run:
154
+
155
+ ```sh
156
+ bundle exec rake geocombine:withdraw
157
+ ```
158
+
121
159
  #### Logging
122
160
 
123
161
  Some of the tools and scripts in this gem use Ruby's `Logger` class to print information to `$stderr`. By default, the log level is set to `Logger::INFO`. For more verbose information, you can set the `LOG_LEVEL` environment variable to `DEBUG`:
@@ -152,67 +190,6 @@ Or:
152
190
  OGM_PATH=/path/to/your/file.json rake geocombine:index
153
191
  ```
154
192
 
155
- ### Harvesting and indexing documents from GeoBlacklight sites
156
-
157
- GeoCombine provides a Harvester class and rake task to harvest and index content from GeoBlacklight sites (or any site that follows the Blacklight API format). Given that the configurations can change from consumer to consumer and site to site, the class provides a relatively simple configuration API. This can be configured in an initializer, a wrapping rake task, or any other ruby context where the rake task our class would be invoked.
158
-
159
- ```sh
160
- bundle exec rake geocombine:geoblacklight_harvester:index[YOUR_CONFIGURED_SITE_KEY]
161
- ```
162
-
163
- #### Harvester configuration
164
-
165
- Only the sites themselves are required to be configured but there are various configuration options that can (optionally) be supplied to modify the harvester's behavior.
166
-
167
- ```ruby
168
- GeoCombine::GeoBlacklightHarvester.configure do
169
- {
170
- commit_within: '10000',
171
- crawl_delay: 1, # All sites
172
- debug: true,
173
- SITE1: {
174
- crawl_delay: 2, # SITE1 only
175
- host: 'https://geoblacklight.example.edu',
176
- params: {
177
- f: {
178
- dct_provenance_s: ['Institution']
179
- }
180
- }
181
- },
182
- SITE2: {
183
- host: 'https://geoportal.example.edu',
184
- params: {
185
- q: '*'
186
- }
187
- }
188
- }
189
- end
190
- ```
191
-
192
- ##### Crawl Delays (default: none)
193
-
194
- Crawl delays can be configured (in seconds) either globally for all sites or on a per-site basis. This will cause a delay for that number of seconds between each search results page (note that Blacklight 7 necessitates a lot of requests per results page and this only causes the delay per page of results)
195
-
196
- ##### Solr's commitWithin (default: 5000 milliseconds)
197
-
198
- Solr's commitWithin option can be configured (in milliseconds) by passing a value under the commit_within key.
199
-
200
- #### Transforming Documents
201
-
202
- You may need to transform documents that are harvested for various purposes (removing fields, adding fields, omitting a document all together, etc). You can configure some ruby code (a proc) that will take the document in, transform it, and return the transformed document. By default the indexer will remove the `score`, `timestamp`, and `_version_` fields from the documents harvested. If you provide your own transformer, you'll likely want to remove these fields in addition to the other transformations you provide.
203
-
204
- ```ruby
205
- GeoCombine::GeoBlacklightIndexer.document_transformer = -> (document) do
206
- # Removes "bogus_field" from the content we're harvesting
207
- # in addition to some other solr fields we don't want
208
- %w[_version_ score timestamp bogus_field].each do |field|
209
- document.delete(field)
210
- end
211
-
212
- document
213
- end
214
- ```
215
-
216
193
  ### Converting metadata
217
194
 
218
195
  #### Converting metadata into GeoBlacklight JSON
@@ -250,6 +227,66 @@ Some formats also support conversion into HTML for display in a web browser:
250
227
  > iso_metadata.to_html
251
228
  ```
252
229
 
230
+ #### Converting metadata into Aardvark JSON
231
+
232
+ `GeoCombine::Fgdc` and `GeoCombine::Iso19139` also implement `#to_aardvark`, which converts FGDC or ISO 19139 XML into the [OGM Aardvark schema](https://opengeometadata.org/ogm-aardvark/) and returns a `GeoCombine::GeoblacklightAardvark`:
233
+
234
+ ```ruby
235
+ # Create a new Fgdc object
236
+ > fgdc_metadata = GeoCombine::Fgdc.new('./tmp/opengeometadata/edu.tufts/0/108/220/208/fgdc.xml')
237
+
238
+ # Convert to the Aardvark schema
239
+ > aardvark = fgdc_metadata.to_aardvark
240
+
241
+ # Validate it against the Aardvark JSON schema
242
+ > aardvark.valid?
243
+ => true
244
+
245
+ # Output it as JSON instead of a Ruby hash
246
+ > aardvark.to_json
247
+ ```
248
+
249
+ Aardvark defines some fields that FGDC and ISO have no source for, such as `gbl_suppressed_b`, and the links in a record may not be the ones you want to publish. Pass fields in and they are merged into the transformed record, replacing any derived values:
250
+
251
+ ```ruby
252
+ > fgdc_metadata.to_aardvark(
253
+ 'id' => 'tufts-ecuador-drilling-towers',
254
+ 'schema_provider_s' => 'Tufts',
255
+ 'dct_references_s' => { 'http://schema.org/url' => 'https://example.edu/catalog/tufts-1' }.to_json
256
+ )
257
+ ```
258
+
259
+ Note about `schema_provider_s` and `id`:
260
+
261
+ ```ruby
262
+ # Without a provider, id and provider are derived from the record
263
+ > fgdc_metadata.to_aardvark.metadata['id']
264
+ => "tufts-university-gis-center-ecuador50kdrillingtower11"
265
+
266
+ # With one, the provider is used as the id prefix
267
+ > fgdc_metadata.to_aardvark('schema_provider_s' => 'Tufts').metadata['id']
268
+ => "tufts-ecuador50kdrillingtower11"
269
+
270
+ # Or set the id yourself
271
+ > fgdc_metadata.to_aardvark('id' => 'tufts-ecuador-drilling-towers').metadata['id']
272
+ => "tufts-ecuador-drilling-towers"
273
+ ```
274
+
275
+ The FGDC transformation fills in `dct_references_s` from the citation's online linkage and the network resources of each digital form, classified by URL and by the format they're distributed in: a landing page, downloads (a labeled list when there's more than one), FGDC and HTML metadata, and OGC (WMS, WFS, WCS, WMTS) and Esri service endpoints. A browse graphic URL becomes the thumbnail, and `gbl_wxsIdentifier_s` is the layer an OGC request asks for. References you pass in replace the derived ones.
276
+
277
+ ISO 19139 records (including ISO 19115-2 `gmi:MI_Metadata`) work the same way. Their ids use the last segment of the `fileIdentifier`, falling back to the `dataSetURI`, the citation identifier and the title:
278
+
279
+ ```ruby
280
+ > iso_metadata = GeoCombine::Iso19139.new('./tmp/opengeometadata/edu.stanford.purl/bb/338/jh/0716/iso19139.xml')
281
+
282
+ > iso_metadata.to_aardvark('schema_provider_s' => 'Stanford').metadata['id']
283
+ => "stanford-bb338jh0716"
284
+ ```
285
+
286
+ The ISO transformation also fills in `dct_references_s`, from the dataset URI, downloads, and OGC (WMS, WFS, WCS, WMTS) and Esri service links, along with `gbl_wxsIdentifier_s` from the name of an OGC link. References you pass in replace the derived ones.
287
+
288
+ Some values can't be derived reliably and are left for you to pass in: `gbl_resourceType_sm` for vector data whose geometry is only described as `composite` (used for both lines and polygons), and `pcdm_memberOf_sm` and `dct_isPartOf_sm`, which take the ids of other Aardvark records.
289
+
253
290
  ### Migrating metadata
254
291
 
255
292
  You can use the `GeoCombine::Migrators` to migrate metadata from one schema to another.
@@ -284,6 +321,12 @@ To run the tests, use:
284
321
  bundle exec rake spec
285
322
  ```
286
323
 
324
+ Some tests need a real Solr core running the GeoBlacklight schema. They delete documents, so they only run when `SOLR_INTEGRATION_URL` points at a core you don't mind them changing:
325
+
326
+ ```sh
327
+ SOLR_INTEGRATION_URL=http://localhost:8983/solr/blacklight-core bundle exec rake spec
328
+ ```
329
+
287
330
  ## Contributing
288
331
 
289
332
  1. Fork it ( https://github.com/[my-github-username]/GeoCombine/fork )
data/geo_combine.gemspec CHANGED
@@ -25,7 +25,7 @@ Gem::Specification.new do |spec|
25
25
  spec.add_dependency 'sanitize'
26
26
  spec.add_dependency 'thor'
27
27
  spec.add_dependency 'faraday-net_http_persistent', '~> 2.0'
28
- spec.add_dependency 'git'
28
+ spec.add_dependency 'git', '~> 6.0'
29
29
  spec.add_dependency 'faraday-retry', '~> 2.2'
30
30
 
31
31
  spec.add_development_dependency 'bundler'
@@ -7,5 +7,8 @@ module GeoCombine
7
7
 
8
8
  class InvalidGeometry < StandardError
9
9
  end
10
+
11
+ class InvalidSchemaVersion < StandardError
12
+ end
10
13
  end
11
14
  end
@@ -11,6 +11,13 @@ module GeoCombine
11
11
  Nokogiri::XSLT(File.open(File.join(File.dirname(__FILE__), '../xslt/fgdc2geoBL.xsl')))
12
12
  end
13
13
 
14
+ ##
15
+ # Returns a Nokogiri::XSLT object containing the FGDC to Aardvark XSL
16
+ # @return [Nokogiri::XSLT]
17
+ def xsl_aardvark
18
+ Nokogiri::XSLT(File.open(File.join(File.dirname(__FILE__), '../xslt/fgdc2Aardvark.xsl')))
19
+ end
20
+
14
21
  ##
15
22
  # Returns a Nokogiri::XSLT object containing the ISO19139 to HTML XSL
16
23
  # @return [Nokogiri:XSLT]
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'open-uri'
4
+
5
+ module GeoCombine
6
+ class GeoblacklightAardvark
7
+ attr_reader :metadata
8
+
9
+ SCHEMA_VERSION = 'Aardvark'
10
+ SCHEMA_JSON_URL = 'https://raw.githubusercontent.com/OpenGeoMetadata/opengeometadata.github.io/main/docs/schema/geoblacklight-schema-aardvark.json'
11
+ SCHEMA_DRAFT_URL = 'http://json-schema.org/draft-04/schema#'
12
+
13
+ ##
14
+ # @param [String] metadata a JSON string document in the Aardvark schema
15
+ # @param [Hash] fields values merged with the parsed document, used for
16
+ # fields Aardvark defines but the source metadata cannot supply
17
+ def initialize(metadata, fields = {})
18
+ @metadata = JSON.parse(metadata).merge(fields)
19
+ end
20
+
21
+ ##
22
+ # Returns a string of JSON from an Aardvark hash
23
+ # @return [String]
24
+ def to_json(options = {})
25
+ metadata.to_json(options)
26
+ end
27
+
28
+ ##
29
+ # True if the document is a valid Aardvark record
30
+ # @return [Boolean]
31
+ def valid?
32
+ validate!
33
+ true
34
+ rescue StandardError
35
+ false
36
+ end
37
+
38
+ ##
39
+ # Validates an Aardvark json document
40
+ def validate!
41
+ JSON::Validator.validate!(schema, to_json)
42
+ validate_version!
43
+ validate_spatial!
44
+ end
45
+
46
+ ##
47
+ # Validate gbl_mdVersion_s
48
+ def validate_version!
49
+ return if metadata['gbl_mdVersion_s'] == SCHEMA_VERSION
50
+
51
+ raise GeoCombine::Exceptions::InvalidSchemaVersion,
52
+ "gbl_mdVersion_s must be #{SCHEMA_VERSION.inspect}, got #{metadata['gbl_mdVersion_s'].inspect}"
53
+ end
54
+
55
+ ##
56
+ # Validate locn_geometry
57
+ def validate_spatial!
58
+ geometry = metadata['locn_geometry']
59
+ return unless geometry =~ /ENVELOPE\(/
60
+
61
+ raise GeoCombine::Exceptions::InvalidGeometry unless GeoCombine::BoundingBox.from_envelope(geometry).valid?
62
+ end
63
+
64
+ private
65
+
66
+ def schema
67
+ @schema ||= JSON.parse(URI.open(SCHEMA_JSON_URL).read).merge('$schema' => SCHEMA_DRAFT_URL)
68
+ end
69
+ end
70
+ end