geo_combine 0.9.2 → 0.11.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.
- checksums.yaml +4 -4
- data/.github/dependabot.yml +11 -0
- data/.github/workflows/ruby.yml +7 -7
- data/.gitignore +1 -0
- data/.rubocop.yml +2 -0
- data/.rubocop_todo.yml +1 -1
- data/Gemfile +2 -1
- data/README.md +127 -77
- data/geo_combine.gemspec +2 -2
- data/lib/.DS_Store +0 -0
- data/lib/geo_combine/bounding_box.rb +0 -4
- data/lib/geo_combine/ckan_metadata.rb +1 -1
- data/lib/geo_combine/esri_open_data.rb +1 -0
- data/lib/geo_combine/geoblacklight.rb +30 -9
- data/lib/geo_combine/harvester.rb +40 -24
- data/lib/geo_combine/indexer.rb +10 -2
- data/lib/geo_combine/ogp.rb +11 -10
- data/lib/geo_combine/version.rb +1 -1
- data/lib/tasks/geo_combine.rake +10 -4
- data/lib/xslt/isoAardvark.xsl +738 -0
- data/spec/features/fgdc2html_spec.rb +1 -0
- data/spec/features/iso2html_spec.rb +1 -0
- data/spec/fixtures/indexing/restricted_aardvark.json +34 -0
- data/spec/fixtures/indexing/restricted_geoblacklight.json +42 -0
- data/spec/lib/geo_combine/ckan_metadata_spec.rb +1 -0
- data/spec/lib/geo_combine/esri_open_data_spec.rb +1 -0
- data/spec/lib/geo_combine/fgdc_spec.rb +3 -2
- data/spec/lib/geo_combine/geoblacklight_spec.rb +21 -20
- data/spec/lib/geo_combine/harvester_spec.rb +79 -15
- data/spec/lib/geo_combine/iso19139_spec.rb +1 -0
- metadata +18 -14
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 21614ec82bcbe5c5a7bb1291e398795f1baff048552746335f18f315da677c9e
|
|
4
|
+
data.tar.gz: 6c94a18a0f43fd141f5967930a909efac60ab560ac91a0759e48cc45fdab39b5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 75ce7b76dfa6ad5905410e6b38a18ee98185c50d5d116c8fc5794eba3376e85a05d2e2ad7d70d6bde8de7b0073df22f15cb28759633e0d3c6e546e24a13dee82
|
|
7
|
+
data.tar.gz: cbd5ccc9eac444e599e0dce59fda1f23ef752cec71508a83fbe32ea1b2785b5a8c52d66c4ebcebc81a913aaccf6f677f5555622a43a8e16e902ab5f3379cbfa0
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# To get started with Dependabot version updates, you'll need to specify which
|
|
2
|
+
# package ecosystems to update and where the package manifests are located.
|
|
3
|
+
# Please see the documentation for all configuration options:
|
|
4
|
+
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
|
|
5
|
+
|
|
6
|
+
version: 2
|
|
7
|
+
updates:
|
|
8
|
+
- package-ecosystem: "github-actions"
|
|
9
|
+
directory: "/"
|
|
10
|
+
schedule:
|
|
11
|
+
interval: "weekly"
|
data/.github/workflows/ruby.yml
CHANGED
|
@@ -6,11 +6,11 @@ jobs:
|
|
|
6
6
|
rubocop:
|
|
7
7
|
runs-on: ubuntu-latest
|
|
8
8
|
steps:
|
|
9
|
-
- uses: actions/checkout@
|
|
9
|
+
- uses: actions/checkout@v6
|
|
10
10
|
- name: Set up Ruby and install dependencies
|
|
11
11
|
uses: ruby/setup-ruby@v1
|
|
12
12
|
with:
|
|
13
|
-
ruby-version: 3.
|
|
13
|
+
ruby-version: 3.2
|
|
14
14
|
bundler-cache: true
|
|
15
15
|
- name: Run linter
|
|
16
16
|
run: bundle exec rubocop
|
|
@@ -19,10 +19,10 @@ jobs:
|
|
|
19
19
|
runs-on: ubuntu-latest
|
|
20
20
|
strategy:
|
|
21
21
|
matrix:
|
|
22
|
-
ruby: [3.
|
|
22
|
+
ruby: [3.2, 3.3, 3.4]
|
|
23
23
|
faraday_version: ['', '~> 1.0'] # Defaults to whatever's the most recent version.
|
|
24
24
|
steps:
|
|
25
|
-
- uses: actions/checkout@
|
|
25
|
+
- uses: actions/checkout@v6
|
|
26
26
|
|
|
27
27
|
- name: Set up Ruby and install dependencies
|
|
28
28
|
uses: ruby/setup-ruby@v1
|
|
@@ -32,13 +32,13 @@ jobs:
|
|
|
32
32
|
env:
|
|
33
33
|
FARADAY_VERSION: ${{ matrix.faraday_version }}
|
|
34
34
|
|
|
35
|
-
- name: Run tests
|
|
35
|
+
- name: "Run tests for Ruby ${{ matrix.ruby }} and Faraday ${{ matrix.faraday_version == '~> 1.0' && '1.x' || 'latest' }}"
|
|
36
36
|
run: bundle exec rake spec
|
|
37
37
|
env:
|
|
38
38
|
FARADAY_VERSION: ${{ matrix.faraday_version }}
|
|
39
39
|
|
|
40
40
|
- name: Upload coverage artifacts
|
|
41
|
-
uses: actions/upload-artifact@
|
|
41
|
+
uses: actions/upload-artifact@v6
|
|
42
42
|
with:
|
|
43
|
-
name: coverage
|
|
43
|
+
name: "coverage-${{ matrix.ruby }}-${{ matrix.faraday_version == '~> 1.0' && 'faraday-1.x' || 'faraday-latest' }}"
|
|
44
44
|
path: coverage/
|
data/.gitignore
CHANGED
data/.rubocop.yml
CHANGED
data/.rubocop_todo.yml
CHANGED
|
@@ -41,7 +41,7 @@ Metrics/CyclomaticComplexity:
|
|
|
41
41
|
# Offense count: 13
|
|
42
42
|
# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
|
|
43
43
|
Metrics/MethodLength:
|
|
44
|
-
Max:
|
|
44
|
+
Max: 25
|
|
45
45
|
|
|
46
46
|
# Offense count: 2
|
|
47
47
|
# Configuration parameters: AllowedMethods, AllowedPatterns.
|
data/Gemfile
CHANGED
data/README.md
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
# GeoCombine
|
|
2
2
|
|
|
3
3
|

|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
[]()
|
|
5
|
+
[](https://github.com/OpenGeoMetadata/GeoCombine/releases)
|
|
6
6
|
|
|
7
7
|
A Ruby toolkit for managing geospatial metadata, including:
|
|
8
8
|
|
|
9
|
-
-
|
|
10
|
-
-
|
|
9
|
+
- harvesting and indexing records from OpenGeoMetadata
|
|
10
|
+
- harvesting records directly from a GeoBlacklight instance
|
|
11
|
+
- converting geospatial metadata records between standards
|
|
11
12
|
|
|
12
13
|
## Installation
|
|
13
14
|
|
|
15
|
+
> [!TIP]
|
|
16
|
+
> If you're using GeoBlacklight, GeoCombine is already bundled with your installation – you can skip this section.
|
|
17
|
+
|
|
14
18
|
Add this line to your application's `Gemfile`:
|
|
15
19
|
|
|
16
20
|
```ruby
|
|
@@ -20,111 +24,64 @@ gem 'geo_combine'
|
|
|
20
24
|
And then execute:
|
|
21
25
|
|
|
22
26
|
```sh
|
|
23
|
-
|
|
27
|
+
bundle install
|
|
24
28
|
```
|
|
25
29
|
|
|
26
30
|
Or install it yourself as:
|
|
27
31
|
|
|
28
32
|
```sh
|
|
29
|
-
|
|
33
|
+
gem install geo_combine
|
|
30
34
|
```
|
|
31
35
|
|
|
32
36
|
## Usage
|
|
33
37
|
|
|
34
|
-
###
|
|
35
|
-
#### Converting metadata into GeoBlacklight JSON
|
|
36
|
-
GeoCombine provides several classes representing different metadata standards that implement the `#to_geoblacklight` method for generating records in the [GeoBlacklight JSON format](https://opengeometadata.org/reference/):
|
|
37
|
-
```ruby
|
|
38
|
-
GeoCombine::Iso19139 # ISO 19139 XML
|
|
39
|
-
GeoCombine::OGP # OpenGeoPortal JSON
|
|
40
|
-
GeoCombine::Fgdc # FGDC XML
|
|
41
|
-
GeoCombine::EsriOpenData # Esri Open Data Portal JSON
|
|
42
|
-
GeoCombine::CkanMetadata # CKAN JSON
|
|
43
|
-
```
|
|
44
|
-
An example for converting an ISO 19139 XML record:
|
|
45
|
-
```ruby
|
|
46
|
-
# Create a new ISO19139 object
|
|
47
|
-
> iso_metadata = GeoCombine::Iso19139.new('./tmp/opengeometadata/edu.stanford.purl/bb/338/jh/0716/iso19139.xml')
|
|
48
|
-
|
|
49
|
-
# Convert to GeoBlacklight's metadata format
|
|
50
|
-
> iso_metadata.to_geoblacklight
|
|
51
|
-
|
|
52
|
-
# Output it as JSON instead of a Ruby hash
|
|
53
|
-
> iso_metadata.to_geoblacklight.to_json
|
|
54
|
-
```
|
|
55
|
-
Some formats also support conversion into HTML for display in a web browser:
|
|
56
|
-
```ruby
|
|
57
|
-
# Create a new ISO19139 object
|
|
58
|
-
> iso_metadata = GeoCombine::Iso19139.new('./tmp/opengeometadata/edu.stanford.purl/bb/338/jh/0716/iso19139.xml')
|
|
59
|
-
|
|
60
|
-
# Convert ISO to HTML
|
|
61
|
-
> iso_metadata.to_html
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
### Migrating metadata
|
|
65
|
-
|
|
66
|
-
You can use the `GeoCombine::Migrators` to migrate metadata from one schema to another.
|
|
67
|
-
|
|
68
|
-
Currently, the only migrator is `GeoCombine::Migrators::V1AardvarkMigrator` which migrates from the [GeoBlacklight v1 schema](https://github.com/OpenGeoMetadata/opengeometadata.github.io/blob/main/docs/gbl-1.0.md) to the [Aardvark schema](https://github.com/OpenGeoMetadata/opengeometadata.github.io/blob/main/docs/ogm-aardvark.md)
|
|
69
|
-
|
|
70
|
-
```ruby
|
|
71
|
-
# Load a record in geoblacklight v1 schema
|
|
72
|
-
record = JSON.parse(File.read('.spec/fixtures/docs/full_geoblacklight.json'))
|
|
73
|
-
|
|
74
|
-
# Migrate it to Aardvark schema
|
|
75
|
-
GeoCombine::Migrators::V1AardvarkMigrator.new(v1_hash: record).run
|
|
76
|
-
```
|
|
38
|
+
### Downloading metadata from OpenGeoMetadata
|
|
77
39
|
|
|
78
|
-
|
|
40
|
+
#### Schema version
|
|
79
41
|
|
|
80
|
-
|
|
81
|
-
# You can store this mapping as a JSON or CSV file and load it into a hash
|
|
82
|
-
id_map = {
|
|
83
|
-
'My Collection 1' => 'institution:my-collection-1',
|
|
84
|
-
'My Collection 2' => 'institution:my-collection-2'
|
|
85
|
-
}
|
|
42
|
+
By default, GeoCombine will only fetch and index records using the current schema version. If you instead want to index records using an older schema (e.g. because your GeoBlacklight instance is an older version), you can set the `SCHEMA_VERSION` environment variable:
|
|
86
43
|
|
|
87
|
-
|
|
44
|
+
```sh
|
|
45
|
+
# Only fetch and index schema version 1.0 records
|
|
46
|
+
export SCHEMA_VERSION=1.0
|
|
88
47
|
```
|
|
89
48
|
|
|
90
|
-
|
|
49
|
+
#### Restricted records
|
|
91
50
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
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`:
|
|
51
|
+
By default, GeoCombine will only fetch and index records that are not marked as restricted. If you want to include restricted records, you can set the `OGM_SKIP_RESTRICTED` environment variable to `false`:
|
|
95
52
|
|
|
96
53
|
```sh
|
|
97
|
-
|
|
54
|
+
export OGM_SKIP_RESTRICTED=false
|
|
98
55
|
```
|
|
99
56
|
|
|
100
57
|
#### Clone OpenGeoMetadata repositories locally
|
|
101
58
|
|
|
102
59
|
```sh
|
|
103
|
-
|
|
60
|
+
bundle exec rake geocombine:clone
|
|
104
61
|
```
|
|
105
62
|
|
|
106
|
-
Will clone all
|
|
63
|
+
Will clone all OpenGeoMetadata repositories containing metadata matching the `SCHEMA_VERSION` into `./tmp/opengeometadata`. Location of the OpenGeoMetadata repositories can be configured using the `OGM_PATH` environment variable.
|
|
107
64
|
|
|
108
65
|
```sh
|
|
109
|
-
|
|
66
|
+
OGM_PATH='my/custom/location' bundle exec rake geocombine:clone
|
|
110
67
|
```
|
|
111
68
|
|
|
112
69
|
You can also specify a single repository:
|
|
113
70
|
|
|
114
71
|
```sh
|
|
115
|
-
|
|
72
|
+
bundle exec rake geocombine:clone[edu.stanford.purl]
|
|
116
73
|
```
|
|
117
74
|
|
|
118
75
|
_Note: If you are using zsh, you will need to use escape characters in front of the brackets:_
|
|
119
76
|
|
|
120
77
|
```sh
|
|
121
|
-
|
|
78
|
+
bundle exec rake geocombine:clone\[edu.stanford.purl\]
|
|
122
79
|
```
|
|
123
80
|
|
|
124
81
|
#### Update local OpenGeoMetadata repositories
|
|
125
82
|
|
|
126
83
|
```sh
|
|
127
|
-
|
|
84
|
+
bundle exec rake geocombine:pull
|
|
128
85
|
```
|
|
129
86
|
|
|
130
87
|
Runs `git pull origin master` on all cloned repositories in `./tmp/opengeometadata` (or custom path with configured environment variable `OGM_PATH`).
|
|
@@ -132,37 +89,67 @@ Runs `git pull origin master` on all cloned repositories in `./tmp/opengeometada
|
|
|
132
89
|
You can also specify a single repository:
|
|
133
90
|
|
|
134
91
|
```sh
|
|
135
|
-
|
|
92
|
+
bundle exec rake geocombine:pull[edu.stanford.purl]
|
|
136
93
|
```
|
|
137
94
|
|
|
138
95
|
_Note: If you are using zsh, you will need to use escape characters in front of the brackets:_
|
|
139
96
|
|
|
140
97
|
```sh
|
|
141
|
-
|
|
98
|
+
bundle exec rake geocombine:pull\[edu.stanford.purl\]
|
|
142
99
|
```
|
|
143
100
|
|
|
144
101
|
#### Index GeoBlacklight documents
|
|
145
102
|
|
|
103
|
+
> [!TIP]
|
|
104
|
+
> If you're using GeoBlacklight, GeoCombine will automatically detect the path to your Solr index if you run it in the directory where GeoBlacklight is installed.
|
|
105
|
+
|
|
146
106
|
To index into Solr, GeoCombine requires a Solr instance that is running the
|
|
147
107
|
[GeoBlacklight schema](https://github.com/geoblacklight/geoblacklight):
|
|
148
108
|
|
|
149
109
|
```sh
|
|
150
|
-
|
|
110
|
+
bundle exec rake geocombine:index
|
|
151
111
|
```
|
|
152
112
|
|
|
153
113
|
If Blacklight is installed in the ruby environment and a solr index is configured, the rake task will use the solr index configured in the Blacklight application (this is the case when invoking GeoCombine from your GeoBlacklight installation). If Blacklight is unavailable, the rake task will try to find a Solr instance running at `http://localhost:8983/solr/blacklight-core`.
|
|
154
114
|
|
|
155
|
-
You can also set
|
|
115
|
+
You can also set the Solr instance URL using `SOLR_URL`:
|
|
116
|
+
|
|
117
|
+
```sh
|
|
118
|
+
SOLR_URL=http://www.example.com:1234/solr/collection bundle exec rake geocombine:index
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
#### Logging
|
|
122
|
+
|
|
123
|
+
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`:
|
|
124
|
+
|
|
125
|
+
```sh
|
|
126
|
+
export LOG_LEVEL=DEBUG
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Indexing local documents
|
|
130
|
+
|
|
131
|
+
To index a collection of records in a local directory, you can pass the directory as a `rake` argument:
|
|
156
132
|
|
|
157
133
|
```sh
|
|
158
|
-
|
|
134
|
+
bundle exec rake geocombine:index\[/path/to/your/files\]
|
|
159
135
|
```
|
|
160
136
|
|
|
161
|
-
|
|
137
|
+
Or you can provide it as `OGM_PATH`:
|
|
162
138
|
|
|
163
139
|
```sh
|
|
164
|
-
|
|
165
|
-
|
|
140
|
+
OGM_PATH=/path/to/your/files rake geocombine:index
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
For a single file, it works the same way:
|
|
144
|
+
|
|
145
|
+
```sh
|
|
146
|
+
bundle exec rake geocombine:index\[/path/to/your/file.json\]
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Or:
|
|
150
|
+
|
|
151
|
+
```sh
|
|
152
|
+
OGM_PATH=/path/to/your/file.json rake geocombine:index
|
|
166
153
|
```
|
|
167
154
|
|
|
168
155
|
### Harvesting and indexing documents from GeoBlacklight sites
|
|
@@ -226,12 +213,75 @@ GeoCombine::GeoBlacklightIndexer.document_transformer = -> (document) do
|
|
|
226
213
|
end
|
|
227
214
|
```
|
|
228
215
|
|
|
216
|
+
### Converting metadata
|
|
217
|
+
|
|
218
|
+
#### Converting metadata into GeoBlacklight JSON
|
|
219
|
+
|
|
220
|
+
GeoCombine provides several classes representing different metadata standards that implement the `#to_geoblacklight` method for generating records in the [GeoBlacklight JSON format](https://opengeometadata.org/reference/):
|
|
221
|
+
|
|
222
|
+
```ruby
|
|
223
|
+
GeoCombine::Iso19139 # ISO 19139 XML
|
|
224
|
+
GeoCombine::OGP # OpenGeoPortal JSON
|
|
225
|
+
GeoCombine::Fgdc # FGDC XML
|
|
226
|
+
GeoCombine::EsriOpenData # Esri Open Data Portal JSON
|
|
227
|
+
GeoCombine::CkanMetadata # CKAN JSON
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
An example for converting an ISO 19139 XML record:
|
|
231
|
+
|
|
232
|
+
```ruby
|
|
233
|
+
# Create a new ISO19139 object
|
|
234
|
+
> iso_metadata = GeoCombine::Iso19139.new('./tmp/opengeometadata/edu.stanford.purl/bb/338/jh/0716/iso19139.xml')
|
|
235
|
+
|
|
236
|
+
# Convert to GeoBlacklight's metadata format
|
|
237
|
+
> iso_metadata.to_geoblacklight
|
|
238
|
+
|
|
239
|
+
# Output it as JSON instead of a Ruby hash
|
|
240
|
+
> iso_metadata.to_geoblacklight.to_json
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
Some formats also support conversion into HTML for display in a web browser:
|
|
244
|
+
|
|
245
|
+
```ruby
|
|
246
|
+
# Create a new ISO19139 object
|
|
247
|
+
> iso_metadata = GeoCombine::Iso19139.new('./tmp/opengeometadata/edu.stanford.purl/bb/338/jh/0716/iso19139.xml')
|
|
248
|
+
|
|
249
|
+
# Convert ISO to HTML
|
|
250
|
+
> iso_metadata.to_html
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### Migrating metadata
|
|
254
|
+
|
|
255
|
+
You can use the `GeoCombine::Migrators` to migrate metadata from one schema to another.
|
|
256
|
+
|
|
257
|
+
Currently, the only migrator is `GeoCombine::Migrators::V1AardvarkMigrator` which migrates from the [GeoBlacklight v1 schema](https://github.com/OpenGeoMetadata/opengeometadata.github.io/blob/main/docs/gbl-1.0.md) to the [Aardvark schema](https://github.com/OpenGeoMetadata/opengeometadata.github.io/blob/main/docs/ogm-aardvark.md)
|
|
258
|
+
|
|
259
|
+
```ruby
|
|
260
|
+
# Load a record in geoblacklight v1 schema
|
|
261
|
+
record = JSON.parse(File.read('.spec/fixtures/docs/full_geoblacklight.json'))
|
|
262
|
+
|
|
263
|
+
# Migrate it to Aardvark schema
|
|
264
|
+
GeoCombine::Migrators::V1AardvarkMigrator.new(v1_hash: record).run
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
Some fields cannot be migrated automatically. To handle the migration of collection names to IDs when migrating from v1 to Aardvark, you can provide a mapping of collection names to IDs to the migrator:
|
|
268
|
+
|
|
269
|
+
```ruby
|
|
270
|
+
# You can store this mapping as a JSON or CSV file and load it into a hash
|
|
271
|
+
id_map = {
|
|
272
|
+
'My Collection 1' => 'institution:my-collection-1',
|
|
273
|
+
'My Collection 2' => 'institution:my-collection-2'
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
GeoCombine::Migrators::V1AardvarkMigrator.new(v1_hash: record, collection_id_map: id_map).run
|
|
277
|
+
```
|
|
278
|
+
|
|
229
279
|
## Tests
|
|
230
280
|
|
|
231
281
|
To run the tests, use:
|
|
232
282
|
|
|
233
283
|
```sh
|
|
234
|
-
|
|
284
|
+
bundle exec rake spec
|
|
235
285
|
```
|
|
236
286
|
|
|
237
287
|
## Contributing
|
data/geo_combine.gemspec
CHANGED
|
@@ -32,8 +32,8 @@ Gem::Specification.new do |spec|
|
|
|
32
32
|
spec.add_development_dependency 'rake'
|
|
33
33
|
spec.add_development_dependency 'rspec'
|
|
34
34
|
spec.add_development_dependency 'rspec-html-matchers'
|
|
35
|
-
spec.add_development_dependency 'rubocop'
|
|
36
|
-
spec.add_development_dependency 'rubocop-rspec'
|
|
35
|
+
spec.add_development_dependency 'rubocop'
|
|
36
|
+
spec.add_development_dependency 'rubocop-rspec'
|
|
37
37
|
spec.add_development_dependency 'rubocop-rake'
|
|
38
38
|
spec.add_development_dependency 'simplecov'
|
|
39
39
|
spec.add_development_dependency 'webmock', '~> 3.14'
|
data/lib/.DS_Store
ADDED
|
Binary file
|
|
@@ -48,8 +48,6 @@ module GeoCombine
|
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
def self.from_envelope(envelope)
|
|
51
|
-
return if envelope.nil?
|
|
52
|
-
|
|
53
51
|
envelope = envelope[/.*ENVELOPE\(([^)]*)/, 1].split(',')
|
|
54
52
|
new(
|
|
55
53
|
west: envelope[0],
|
|
@@ -63,8 +61,6 @@ module GeoCombine
|
|
|
63
61
|
# @param [String] spatial w,s,e,n or w s e n
|
|
64
62
|
# @param [String] delimiter "," or " "
|
|
65
63
|
def self.from_string_delimiter(spatial, delimiter: ',')
|
|
66
|
-
return if spatial.nil?
|
|
67
|
-
|
|
68
64
|
spatial = spatial.split(delimiter)
|
|
69
65
|
new(
|
|
70
66
|
west: spatial[0],
|
|
@@ -32,7 +32,7 @@ module GeoCombine
|
|
|
32
32
|
layer_slug_s: @metadata['name'],
|
|
33
33
|
solr_geom: envelope,
|
|
34
34
|
dc_subject_sm: subjects,
|
|
35
|
-
dct_references_s: external_references.to_json
|
|
35
|
+
dct_references_s: external_references.to_json,
|
|
36
36
|
dc_format_s: downloadable? ? 'ZIP' : nil # TODO: we only allow direct ZIP file downloads
|
|
37
37
|
}.compact
|
|
38
38
|
end
|
|
@@ -42,6 +42,7 @@ module GeoCombine
|
|
|
42
42
|
metadata.each do |key, value|
|
|
43
43
|
translate_formats(key, value)
|
|
44
44
|
enhance_subjects(key, value)
|
|
45
|
+
infer_geometry_type_from_subject(key, value)
|
|
45
46
|
format_proper_date(key, value)
|
|
46
47
|
fields_should_be_array(key, value)
|
|
47
48
|
translate_geometry_type(key, value)
|
|
@@ -55,20 +56,29 @@ module GeoCombine
|
|
|
55
56
|
metadata.to_json(options)
|
|
56
57
|
end
|
|
57
58
|
|
|
59
|
+
# True if the GeoBlacklight-Schema document is valid
|
|
60
|
+
# @return [Boolean]
|
|
61
|
+
def valid?
|
|
62
|
+
validate!
|
|
63
|
+
true
|
|
64
|
+
rescue StandardError
|
|
65
|
+
false
|
|
66
|
+
end
|
|
67
|
+
|
|
58
68
|
##
|
|
59
69
|
# Validates a GeoBlacklight-Schema json document
|
|
60
70
|
# @return [Boolean]
|
|
61
|
-
def
|
|
62
|
-
JSON::Validator.validate!(schema, to_json, fragment: '#/definitions/layer')
|
|
63
|
-
|
|
64
|
-
|
|
71
|
+
def validate!
|
|
72
|
+
JSON::Validator.validate!(schema, to_json, fragment: '#/definitions/layer')
|
|
73
|
+
validate_references!
|
|
74
|
+
validate_spatial!
|
|
65
75
|
end
|
|
66
76
|
|
|
67
77
|
##
|
|
68
78
|
# Validate dct_references_s
|
|
69
79
|
# @return [Boolean]
|
|
70
|
-
def
|
|
71
|
-
return
|
|
80
|
+
def validate_references!
|
|
81
|
+
return unless metadata.key?('dct_references_s') # Not required
|
|
72
82
|
|
|
73
83
|
begin
|
|
74
84
|
ref = JSON.parse(metadata['dct_references_s'])
|
|
@@ -83,8 +93,8 @@ module GeoCombine
|
|
|
83
93
|
end
|
|
84
94
|
end
|
|
85
95
|
|
|
86
|
-
def
|
|
87
|
-
GeoCombine::BoundingBox.from_envelope(metadata['solr_geom']).valid?
|
|
96
|
+
def validate_spatial!
|
|
97
|
+
raise GeoCombine::Exceptions::InvalidGeometry unless GeoCombine::BoundingBox.from_envelope(metadata['solr_geom']).valid?
|
|
88
98
|
end
|
|
89
99
|
|
|
90
100
|
private
|
|
@@ -106,6 +116,17 @@ module GeoCombine
|
|
|
106
116
|
metadata[key] = geometry_types[value]
|
|
107
117
|
end
|
|
108
118
|
|
|
119
|
+
##
|
|
120
|
+
# Enhances empty 'layer_geom_type_s' field by populating from related subject
|
|
121
|
+
def infer_geometry_type_from_subject(key, value)
|
|
122
|
+
return unless key == 'layer_geom_type_s' && value.blank?
|
|
123
|
+
|
|
124
|
+
# If any subjects match a known geometry type, use the first that does as the geometry type
|
|
125
|
+
metadata['dc_subject_sm'].find { |subject| geometry_types.value?(subject.capitalize) }&.tap do |subject|
|
|
126
|
+
metadata[key] = subject.capitalize
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
109
130
|
##
|
|
110
131
|
# Enhances the 'dc_subject_sm' field by translating subjects to ISO topic
|
|
111
132
|
# categories
|
|
@@ -163,7 +184,7 @@ module GeoCombine
|
|
|
163
184
|
metadata.except!(*DEPRECATED_KEYS_V1)
|
|
164
185
|
|
|
165
186
|
# ensure we have a proper v1 record
|
|
166
|
-
|
|
187
|
+
validate!
|
|
167
188
|
end
|
|
168
189
|
|
|
169
190
|
def schema
|
|
@@ -11,35 +11,33 @@ module GeoCombine
|
|
|
11
11
|
class Harvester
|
|
12
12
|
attr_reader :ogm_path, :schema_version
|
|
13
13
|
|
|
14
|
-
# Non-metadata repositories that shouldn't be harvested
|
|
15
|
-
def self.denylist
|
|
16
|
-
[
|
|
17
|
-
'GeoCombine',
|
|
18
|
-
'aardvark',
|
|
19
|
-
'metadata-issues',
|
|
20
|
-
'ogm_utils-python',
|
|
21
|
-
'opengeometadata.github.io',
|
|
22
|
-
'opengeometadata-rails',
|
|
23
|
-
'gbl-1_to_aardvark'
|
|
24
|
-
]
|
|
25
|
-
end
|
|
26
|
-
|
|
27
14
|
# GitHub API endpoint for OpenGeoMetadata repositories
|
|
28
15
|
def self.ogm_api_uri
|
|
29
16
|
URI('https://api.github.com/orgs/opengeometadata/repos?per_page=1000')
|
|
30
17
|
end
|
|
31
18
|
|
|
19
|
+
# Initialize a new harvester
|
|
20
|
+
# @param ogm_path [String] path to the directory where repositories will be cloned
|
|
21
|
+
# @param skip_repos [Array<String>] list of repository names to skip
|
|
22
|
+
# @param skip_restricted [Boolean] whether to skip indexing restricted documents
|
|
23
|
+
# @param schema_version [String] schema version to filter repositories by
|
|
24
|
+
# @param logger [Logger] logger to use for logging messages
|
|
32
25
|
def initialize(
|
|
33
|
-
ogm_path
|
|
26
|
+
ogm_path:,
|
|
27
|
+
skip_repos: ENV.fetch('OGM_SKIP_REPOS', '').split(',').map(&:strip),
|
|
28
|
+
skip_restricted: ENV.fetch('OGM_SKIP_RESTRICTED', 'true') == 'true',
|
|
34
29
|
schema_version: ENV.fetch('SCHEMA_VERSION', 'Aardvark'),
|
|
35
30
|
logger: GeoCombine::Logger.logger
|
|
36
31
|
)
|
|
37
32
|
@ogm_path = ogm_path
|
|
38
33
|
@schema_version = schema_version
|
|
34
|
+
@skip_repos = skip_repos
|
|
35
|
+
@skip_restricted = skip_restricted
|
|
39
36
|
@logger = logger
|
|
40
37
|
end
|
|
41
38
|
|
|
42
39
|
# Enumerable of docs to index, for passing to an indexer
|
|
40
|
+
# rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
43
41
|
def docs_to_index
|
|
44
42
|
return to_enum(:docs_to_index) unless block_given?
|
|
45
43
|
|
|
@@ -53,19 +51,34 @@ module GeoCombine
|
|
|
53
51
|
|
|
54
52
|
doc = JSON.parse(File.read(path))
|
|
55
53
|
[doc].flatten.each do |record|
|
|
56
|
-
# skip indexing if this record has a different schema version than what we want
|
|
57
54
|
record_schema = record['gbl_mdVersion_s'] || record['geoblacklight_version']
|
|
58
55
|
record_id = record['layer_slug_s'] || record['dc_identifier_s']
|
|
56
|
+
record_rights = record['dct_accessRights_s'] || record['dc_rights_s']
|
|
57
|
+
|
|
58
|
+
# skip indexing if no identifiable schema version
|
|
59
|
+
unless record_schema
|
|
60
|
+
@logger.debug "skipping #{record_id || path}; no schema version declared in record"
|
|
61
|
+
next
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# skip indexing if this record has a different schema version than what we want
|
|
59
65
|
if record_schema != @schema_version
|
|
60
66
|
@logger.debug "skipping #{record_id}; schema version #{record_schema} doesn't match #{@schema_version}"
|
|
61
67
|
next
|
|
62
68
|
end
|
|
63
69
|
|
|
70
|
+
# skip indexing if this record is restricted and we want to skip restricted records
|
|
71
|
+
if @skip_restricted && record_rights == 'Restricted'
|
|
72
|
+
@logger.debug "skipping #{record_id}; access rights are restricted"
|
|
73
|
+
next
|
|
74
|
+
end
|
|
75
|
+
|
|
64
76
|
@logger.debug "found record #{record_id} at #{path}"
|
|
65
77
|
yield record, path
|
|
66
78
|
end
|
|
67
79
|
end
|
|
68
80
|
end
|
|
81
|
+
# rubocop:enable Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
69
82
|
|
|
70
83
|
# Update a repository via git
|
|
71
84
|
# If the repository doesn't exist, clone it.
|
|
@@ -87,19 +100,20 @@ module GeoCombine
|
|
|
87
100
|
end
|
|
88
101
|
|
|
89
102
|
# Clone a repository via git
|
|
90
|
-
#
|
|
103
|
+
# Return the name of the repository cloned, or nil if skipped
|
|
91
104
|
def clone(repo)
|
|
92
105
|
repo_path = File.join(@ogm_path, repo)
|
|
93
106
|
repo_info = repository_info(repo)
|
|
94
107
|
repo_url = "https://github.com/OpenGeoMetadata/#{repo}.git"
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
if
|
|
98
|
-
|
|
99
|
-
|
|
108
|
+
repo_schemas = Array(repo_info.dig('custom_properties', 'supported_schemas'))
|
|
109
|
+
|
|
110
|
+
# Skip if exists, archived, empty, or different schema
|
|
111
|
+
return @logger.warn "skipping clone to #{repo_path}; directory exists" if File.directory? repo_path
|
|
112
|
+
return @logger.warn "repository is archived: #{repo_url}" if repo_info['archived']
|
|
113
|
+
return @logger.warn "repository is empty: #{repo_url}" if repo_info['size'].zero?
|
|
114
|
+
unless repo_schemas.include? @schema_version
|
|
115
|
+
return @logger.warn "repository #{repo_url} clone to #{repo_path}; repository properties don't include schema version #{@schema_version} (found #{repo_schemas.join(', ')})"
|
|
100
116
|
end
|
|
101
|
-
@logger.warn "repository is archived: #{repo_url}" if repo_info['archived']
|
|
102
|
-
@logger.warn "repository is empty: #{repo_url}" if repo_info['size'].zero?
|
|
103
117
|
|
|
104
118
|
Git.clone(repo_url, nil, path: ogm_path, depth: 1)
|
|
105
119
|
@logger.info "cloned #{repo_url} to #{repo_path}"
|
|
@@ -119,12 +133,14 @@ module GeoCombine
|
|
|
119
133
|
# List of repository names to harvest
|
|
120
134
|
def repositories
|
|
121
135
|
@repositories ||= JSON.parse(Net::HTTP.get(self.class.ogm_api_uri))
|
|
136
|
+
.filter { |repo| Array(repo.dig('custom_properties', 'supported_schemas')).include? @schema_version }
|
|
122
137
|
.filter { |repo| repo['size'].positive? }
|
|
123
138
|
.reject { |repo| repo['archived'] }
|
|
139
|
+
.reject { |repo| @skip_repos.include?(repo['name']) }
|
|
124
140
|
.map { |repo| repo['name'] }
|
|
125
|
-
.reject { |name| self.class.denylist.include? name }
|
|
126
141
|
end
|
|
127
142
|
|
|
143
|
+
# Fetch repository metadata from GitHub API
|
|
128
144
|
def repository_info(repo_name)
|
|
129
145
|
JSON.parse(Net::HTTP.get(URI("https://api.github.com/repos/opengeometadata/#{repo_name}")))
|
|
130
146
|
end
|