geo_combine 0.3.1 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f7557bdb34860f4f6a38081e658c51ebec60d804
4
- data.tar.gz: 8d1fddf86f46fa134c5070a4c813cfdf362d1ea7
3
+ metadata.gz: 0370f82160756d74fe36f4c4a5e9639b8e06d461
4
+ data.tar.gz: e06d158e0770862c7033935c91c82bfe6b9d9f21
5
5
  SHA512:
6
- metadata.gz: eaa908a3ff9e0d181236bab6a1cb3af8464e6d8912e10dc1fe6fcbb56d787ac59356777965469a8d773714566180e1657354199050b033902f2524bfb310890f
7
- data.tar.gz: 5898290a91a655751dbd0bf23f5230075eeadb87546338f84c49a16c8a2fb2dd1f40bfa505a992fbb208870839ba34213e41c18da20daca98e78d8b22e5be881
6
+ metadata.gz: f5072b677855334cbe0716a3639394ead6c10bce24cdec3485daf5764d36df1466938247075cbf5737070a7b08fb5f1926aa618ac0c9679783c9edf8e5ea7b86
7
+ data.tar.gz: 26098a941f59b4c36fa0c4f8331d2c31d8575c2978e4bcba1804a9711b98a22e34d78e16dfa1a5d04da4a130f887bb91ff5bc4f0de794bb2dc2e07ffa72b93d2
data/.travis.yml CHANGED
@@ -2,6 +2,6 @@ sudo: false
2
2
  language: ruby
3
3
  cache: bundler
4
4
  rvm:
5
- - 2.1.10
6
- - 2.2.5
7
- - 2.3.1
5
+ - 2.2.7
6
+ - 2.3.4
7
+ - 2.4.1
data/Gemfile CHANGED
@@ -3,4 +3,5 @@ source 'https://rubygems.org'
3
3
  # Specify your gem's dependencies in geo_combine.gemspec
4
4
  gemspec
5
5
 
6
- gem 'coveralls', require: false
6
+ gem 'coveralls', require: false
7
+ gem 'byebug'
data/README.md CHANGED
@@ -3,12 +3,13 @@
3
3
  [![Build Status](https://travis-ci.org/OpenGeoMetadata/GeoCombine.svg?branch=master)](https://travis-ci.org/OpenGeoMetadata/GeoCombine) | [![Coverage Status](https://coveralls.io/repos/OpenGeoMetadata/GeoCombine/badge.svg?branch=master)](https://coveralls.io/r/OpenGeoMetadata/GeoCombine?branch=master)
4
4
 
5
5
 
6
-
7
- A Ruby toolkit for managing geospatial metadata
6
+ A Ruby toolkit for managing geospatial metadata, including:
7
+ - tasks for cloning, updating, and indexing OpenGeoMetdata metadata
8
+ - library for converting metadata between standards
8
9
 
9
10
  ## Installation
10
11
 
11
- Add this line to your application's Gemfile:
12
+ Add this line to your application's `Gemfile`:
12
13
 
13
14
  ```ruby
14
15
  gem 'geo_combine'
@@ -16,81 +17,96 @@ gem 'geo_combine'
16
17
 
17
18
  And then execute:
18
19
 
19
- $ bundle
20
+ $ bundle install
20
21
 
21
22
  Or install it yourself as:
22
23
 
23
24
  $ gem install geo_combine
24
25
 
25
26
  ## Usage
26
- GeoCombine can be used as a set of rake tasks for cloning, updating, and indexing OpenGeoMetdata metdata. It can also be used as a Ruby library for converting metdata.
27
27
 
28
- ### Transforming metadata
28
+ ### Converting metadata
29
29
 
30
30
  ```ruby
31
31
  # Create a new ISO19139 object
32
32
  > iso_metadata = GeoCombine::Iso19139.new('./tmp/opengeometadata/edu.stanford.purl/bb/338/jh/0716/iso19139.xml')
33
33
 
34
- # Convert it to GeoBlacklight
34
+ # Convert ISO to GeoBlacklight
35
35
  > iso_metadata.to_geoblacklight
36
36
 
37
37
  # Convert that to JSON
38
38
  > iso_metadata.to_geoblacklight.to_json
39
39
 
40
- # Convert ISO or FGDC to HTML
40
+ # Convert ISO (or FGDC) to HTML
41
41
  > iso_metadata.to_html
42
42
  ```
43
43
 
44
- ## Command line ##
45
-
46
- GeoCombine's tasks can be run either as rake tasks or as standalone executables.
44
+ ### OpenGeoMetadata
47
45
 
48
- ### Clone all OpenGeoMetadata repositories
46
+ #### Clone OpenGeoMetadata repositories locally
49
47
 
50
48
  ```sh
51
- $ rake geocombine:clone
49
+ $ bundle exec rake geocombine:clone
52
50
  ```
53
51
 
52
+ Will clone all `edu.*`,` org.*`, and `uk.*` OpenGeoMetadata repositories into `./tmp/opengeometadata`. Location of the OpenGeoMetadata repositories can be configured using the `OGM_PATH` environment variable.
53
+
54
54
  ```sh
55
- $ bundle exec geocombine clone
55
+ $ OGM_PATH='my/custom/location' bundle exec rake geocombine:clone
56
56
  ```
57
57
 
58
- Will clone all edu.* OpenGeoMetadata repositories into `./tmp/opengeometadata`. Location of the OpenGeoMetadata repositories can be configured using the `OGM_PATH` environment variable.
58
+ You can also specify a single repository:
59
59
 
60
60
  ```sh
61
- $ OGM_PATH='my/custom/location' rake geocombine:clone
61
+ $ bundle exec rake geocombine:clone[edu.stanford.purl]
62
62
  ```
63
63
 
64
- ### Pull all OpenGeoMetadata repositories
64
+ #### Update local OpenGeoMetadata repositories
65
65
 
66
66
  ```sh
67
- $ rake geocombine:pull
67
+ $ bundle exec rake geocombine:pull
68
68
  ```
69
69
 
70
+ Runs `git pull origin master` on all cloned repositories in `./tmp/opengeometadata` (or custom path with configured environment variable `OGM_PATH`).
71
+
72
+ You can also specify a single repository:
73
+
70
74
  ```sh
71
- $ bundle exec geocombine pull
75
+ $ bundle exec rake geocombine:pull[edu.stanford.purl]
72
76
  ```
73
77
 
74
- Runs `git pull origin master` on all cloned repositories in `./tmp/opengeometadata` (or custom path with configured environment variable `OGM_PATH`)
78
+ #### Index GeoBlacklight documents
75
79
 
76
- ### Index all of the GeoBlacklight documents
80
+ To index into Solr, GeoCombine requires a Solr instance that is running the
81
+ [GeoBlacklight schema](https://github.com/geoblacklight/geoblacklight):
77
82
 
78
83
  ```sh
79
- $ rake geocombine:index
84
+ $ bundle exec rake geocombine:index
80
85
  ```
81
86
 
87
+ Indexes the `geoblacklight.json` files in cloned repositories to a Solr index running at http://127.0.0.1:8983/solr
88
+
89
+ ##### Custom Solr location
90
+
91
+ Solr location can also be specified by an environment variable `SOLR_URL`.
92
+
82
93
  ```sh
83
- $ bundle exec geocombine index
94
+ $ SOLR_URL=http://www.example.com:1234/solr/collection bundle exec rake geocombine:index
84
95
  ```
85
96
 
86
- Indexes all of the `geoblacklight.json` files in cloned repositories to a Solr index running at http://127.0.0.1:8983/solr
97
+ Depending on your Solr instance's performance characteristics, you may want to
98
+ change the [`commitWithin` parameter](https://lucene.apache.org/solr/guide/6_6/updatehandlers-in-solrconfig.html) (in milliseconds):
99
+
100
+ ```sh
101
+ $ SOLR_COMMIT_WITHIN=100 bundle exec rake geocombine:index
102
+ ```
87
103
 
88
- #### Custom Solr location
104
+ ## Tests
89
105
 
90
- Solr location can also be specified by an environment variable `SOLR_URL`.
106
+ To run the tests, use:
91
107
 
92
108
  ```sh
93
- $ SOLR_URL=http://www.example.com:1234/solr/collection rake geocombine:index
109
+ $ bundle exec rake spec
94
110
  ```
95
111
 
96
112
  ## Contributing
data/geo_combine.gemspec CHANGED
@@ -18,14 +18,16 @@ Gem::Specification.new do |spec|
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ["lib"]
20
20
 
21
+ spec.add_dependency 'activesupport'
21
22
  spec.add_dependency 'rsolr'
23
+ spec.add_dependency 'net-http-persistent', '~> 2.0' # pin since faraday (rsolr) doesn't work correctly with 3.x
22
24
  spec.add_dependency 'nokogiri'
23
25
  spec.add_dependency 'json-schema'
24
26
  spec.add_dependency 'sanitize'
25
27
  spec.add_dependency 'thor'
26
28
 
27
- spec.add_development_dependency "bundler", "~> 1.7"
28
- spec.add_development_dependency "rake", "~> 10.0"
29
+ spec.add_development_dependency 'bundler'
30
+ spec.add_development_dependency 'rake'
29
31
  spec.add_development_dependency 'rspec'
30
32
  spec.add_development_dependency 'rspec-html-matchers'
31
33
  end
data/lib/geo_combine.rb CHANGED
@@ -20,7 +20,7 @@ module GeoCombine
20
20
  ##
21
21
  # Creates a new GeoCombine::Metadata object, where metadata parameter is can
22
22
  # be a File path or String of XML
23
- # @param [String] metadata can be a File path
23
+ # @param [String] metadata can be a File path
24
24
  # "./tmp/edu.stanford.purl/bb/338/jh/0716/iso19139.xml" or a String of XML
25
25
  # metadata
26
26
  def initialize metadata
@@ -66,6 +66,8 @@ require 'geo_combine/geoblacklight'
66
66
  require 'geo_combine/iso19139'
67
67
  require 'geo_combine/esri_open_data'
68
68
  require 'geo_combine/ckan_metadata'
69
+ require 'geo_combine/ogp'
69
70
 
70
71
  # Require gem files
71
72
  require 'geo_combine/version'
73
+ require 'geo_combine/railtie' if defined?(Rails)
@@ -15,7 +15,7 @@ module GeoCombine
15
15
  # @param [String] text
16
16
  # @return [String]
17
17
  def remove_lines(text)
18
- text.gsub(/\n/, '')
18
+ text.delete("\n")
19
19
  end
20
20
 
21
21
  ##
@@ -25,5 +25,10 @@ module GeoCombine
25
25
  def sanitize_and_remove_lines(text)
26
26
  remove_lines(sanitize(text))
27
27
  end
28
+
29
+ # slugs should be lowercase and only have a-z, A-Z, 0-9, and -
30
+ def sluggify(slug)
31
+ slug.gsub(/[^a-zA-Z0-9\-]/, '-').gsub(/[\-]+/, '-').downcase
32
+ end
28
33
  end
29
34
  end
@@ -1,3 +1,5 @@
1
+ require 'active_support/core_ext/object/blank'
2
+ require 'active_support/core_ext/hash/except'
1
3
  require 'open-uri'
2
4
 
3
5
  module GeoCombine
@@ -9,6 +11,16 @@ module GeoCombine
9
11
  attr_reader :metadata
10
12
 
11
13
  GEOBLACKLIGHT_VERSION = 'v1.1.0'
14
+ SCHEMA_JSON_URL = "https://raw.githubusercontent.com/geoblacklight/geoblacklight/#{GEOBLACKLIGHT_VERSION}/schema/geoblacklight-schema.json".freeze
15
+ DEPRECATED_KEYS_V1 = %w[
16
+ uuid
17
+ georss_polygon_s
18
+ georss_point_s
19
+ georss_box_s
20
+ dc_relation_sm
21
+ solr_issued_i
22
+ solr_bbox
23
+ ].freeze
12
24
 
13
25
  ##
14
26
  # Initializes a GeoBlacklight object
@@ -24,7 +36,9 @@ module GeoCombine
24
36
  # Calls metadata enhancement methods for each key, value pair in the
25
37
  # metadata hash
26
38
  def enhance_metadata
27
- @metadata.each do |key, value|
39
+ upgrade_to_v1 if metadata['geoblacklight_version'].blank?
40
+
41
+ metadata.each do |key, value|
28
42
  translate_formats(key, value)
29
43
  enhance_subjects(key, value)
30
44
  format_proper_date(key, value)
@@ -36,15 +50,15 @@ module GeoCombine
36
50
  ##
37
51
  # Returns a string of JSON from a GeoBlacklight hash
38
52
  # @return (String)
39
- def to_json
40
- @metadata.to_json
53
+ def to_json(options = {})
54
+ metadata.to_json(options)
41
55
  end
42
56
 
43
57
  ##
44
58
  # Validates a GeoBlacklight-Schema json document
45
59
  # @return [Boolean]
46
60
  def valid?
47
- @schema ||= JSON.parse(open("https://raw.githubusercontent.com/geoblacklight/geoblacklight/#{GEOBLACKLIGHT_VERSION}/schema/geoblacklight-schema.json").read)
61
+ @schema ||= JSON.parse(open(SCHEMA_JSON_URL).read)
48
62
  JSON::Validator.validate!(@schema, to_json, fragment: '#/properties/layer') &&
49
63
  dct_references_validate! &&
50
64
  spatial_validate!
@@ -54,7 +68,7 @@ module GeoCombine
54
68
  # Validate dct_references_s
55
69
  # @return [Boolean]
56
70
  def dct_references_validate!
57
- return true unless metadata.key?('dct_references_s')
71
+ return true unless metadata.key?('dct_references_s') # TODO: shouldn't we require this field?
58
72
  begin
59
73
  ref = JSON.parse(metadata['dct_references_s'])
60
74
  raise GeoCombine::Exceptions::InvalidDCTReferences, 'dct_references must be parsed to a Hash' unless ref.is_a?(Hash)
@@ -74,43 +88,72 @@ module GeoCombine
74
88
  # Enhances the 'dc_format_s' field by translating a format type to a valid
75
89
  # GeoBlacklight-Schema format
76
90
  def translate_formats(key, value)
77
- @metadata[key] = formats[value] if key == 'dc_format_s' && formats.include?(value)
91
+ return unless key == 'dc_format_s' && formats.include?(value)
92
+ metadata[key] = formats[value]
78
93
  end
79
94
 
80
95
  ##
81
96
  # Enhances the 'layer_geom_type_s' field by translating from known types
82
97
  def translate_geometry_type(key, value)
83
- @metadata[key] = geometry_types[value] if key == 'layer_geom_type_s' && geometry_types.include?(value)
98
+ return unless key == 'layer_geom_type_s' && geometry_types.include?(value)
99
+ metadata[key] = geometry_types[value]
84
100
  end
85
101
 
86
102
  ##
87
103
  # Enhances the 'dc_subject_sm' field by translating subjects to ISO topic
88
104
  # categories
89
105
  def enhance_subjects(key, value)
90
- @metadata[key] = value.map do |val|
106
+ return unless key == 'dc_subject_sm'
107
+ metadata[key] = value.map do |val|
91
108
  if subjects.include?(val)
92
109
  subjects[val]
93
110
  else
94
111
  val
95
112
  end
96
- end if key == 'dc_subject_sm'
113
+ end
97
114
  end
98
115
 
99
116
  ##
100
117
  # Formats the 'layer_modified_dt' to a valid valid RFC3339 date/time string
101
118
  # and ISO8601 (for indexing into Solr)
102
119
  def format_proper_date(key, value)
103
- @metadata[key] = Time.parse(value).utc.iso8601 if key == 'layer_modified_dt'
120
+ return unless key == 'layer_modified_dt'
121
+ metadata[key] = Time.parse(value).utc.iso8601
104
122
  end
105
123
 
106
124
  def fields_should_be_array(key, value)
107
- @metadata[key] = [value] if should_be_array.include?(key) && !value.kind_of?(Array)
125
+ return unless should_be_array.include?(key) && !value.is_a?(Array)
126
+ metadata[key] = [value]
108
127
  end
109
128
 
110
129
  ##
111
130
  # GeoBlacklight-Schema fields that should be type Array
112
131
  def should_be_array
113
- ['dc_creator_sm', 'dc_subject_sm', 'dct_spatial_sm', 'dct_temporal_sm', 'dct_isPartOf_sm']
132
+ %w[
133
+ dc_creator_sm
134
+ dc_subject_sm
135
+ dct_spatial_sm
136
+ dct_temporal_sm
137
+ dct_isPartOf_sm
138
+ ].freeze
139
+ end
140
+
141
+ ##
142
+ # Converts a pre-v1.0 schema into a compliant v1.0 schema
143
+ def upgrade_to_v1
144
+ metadata['geoblacklight_version'] = '1.0'
145
+
146
+ # ensure required fields
147
+ metadata['dc_identifier_s'] = metadata['uuid'] if metadata['dc_identifier_s'].blank?
148
+
149
+ # normalize to alphanum and - only
150
+ metadata['layer_slug_s'].gsub!(/[^[[:alnum:]]]+/, '-') if metadata['layer_slug_s'].present?
151
+
152
+ # remove deprecated fields
153
+ metadata.except!(*DEPRECATED_KEYS_V1)
154
+
155
+ # ensure we have a proper v1 record
156
+ valid?
114
157
  end
115
158
  end
116
159
  end
@@ -0,0 +1,229 @@
1
+ require 'active_support/core_ext/object/blank'
2
+ require 'cgi'
3
+
4
+ module GeoCombine
5
+ # Data model for OpenGeoPortal metadata
6
+ class OGP
7
+ class InvalidMetadata < RuntimeError; end
8
+ include GeoCombine::Formatting
9
+ attr_reader :metadata
10
+
11
+ ##
12
+ # Initializes an OGP object for parsing
13
+ # @param [String] metadata a valid serialized JSON string from OGP instance
14
+ # @raise [InvalidMetadata]
15
+ def initialize(metadata)
16
+ @metadata = JSON.parse(metadata)
17
+ raise InvalidMetadata unless valid?
18
+ end
19
+
20
+ OGP_REQUIRED_FIELDS = %w[
21
+ Access
22
+ Institution
23
+ LayerDisplayName
24
+ LayerId
25
+ MaxX
26
+ MaxY
27
+ MinX
28
+ MinY
29
+ Name
30
+ ].freeze
31
+
32
+ ##
33
+ # Runs validity checks on OGP metadata to ensure fields are present
34
+ def valid?
35
+ OGP_REQUIRED_FIELDS.all? { |k| metadata[k].present? }
36
+ end
37
+
38
+ ##
39
+ # Creates and returns a Geoblacklight schema object from this metadata
40
+ # @return [GeoCombine::Geoblacklight]
41
+ def to_geoblacklight
42
+ GeoCombine::Geoblacklight.new(geoblacklight_terms.to_json)
43
+ end
44
+
45
+ ##
46
+ # Builds a Geoblacklight Schema type hash from Esri Open Data portal
47
+ # metadata
48
+ # @return [Hash]
49
+ def geoblacklight_terms
50
+ {
51
+ # Required fields
52
+ dc_identifier_s: identifier,
53
+ layer_slug_s: slug,
54
+ dc_title_s: metadata['LayerDisplayName'],
55
+ solr_geom: envelope,
56
+ dct_provenance_s: institution,
57
+ dc_rights_s: metadata['Access'],
58
+ geoblacklight_version: '1.0',
59
+
60
+ # Recommended fields
61
+ dc_description_s: metadata['Abstract'],
62
+ layer_geom_type_s: ogp_geom,
63
+ dct_references_s: references,
64
+ layer_id_s: "#{metadata['WorkspaceName']}:#{metadata['Name']}",
65
+
66
+ # Optional
67
+ dct_temporal_sm: [metadata['ContentDate']],
68
+ dc_format_s: ogp_formats,
69
+ # dct_issued_dt
70
+ # dc_language_s
71
+ dct_spatial_sm: placenames,
72
+ solr_year_i: year,
73
+ dc_publisher_s: metadata['Publisher'],
74
+ dc_subject_sm: subjects,
75
+ dc_type_s: 'Dataset'
76
+ }.delete_if { |_k, v| v.nil? }
77
+ end
78
+
79
+ def date
80
+ begin
81
+ DateTime.rfc3339(metadata['ContentDate'])
82
+ rescue
83
+ nil
84
+ end
85
+ end
86
+
87
+ def year
88
+ date.year unless date.nil?
89
+ end
90
+
91
+ ##
92
+ # Convert "Paper Map" to Raster, assumes all OGP "Paper Maps" have WMS
93
+ def ogp_geom
94
+ case metadata['DataType']
95
+ when 'Paper Map'
96
+ 'Raster'
97
+ else
98
+ metadata['DataType']
99
+ end
100
+ end
101
+
102
+ ##
103
+ # OGP doesn't ship format types, so we just try and be clever here.
104
+ def ogp_formats
105
+ case metadata['DataType']
106
+ when 'Paper Map', 'Raster'
107
+ return 'GeoTIFF'
108
+ when 'Polygon', 'Point', 'Line'
109
+ return 'Shapefile'
110
+ else
111
+ raise ArgumentError, metadata['DataType']
112
+ end
113
+ end
114
+
115
+ ##
116
+ # Converts references to json
117
+ # @return [String]
118
+ def references
119
+ references_hash.to_json
120
+ end
121
+
122
+ ##
123
+ # Builds a Solr Envelope using CQL syntax
124
+ # @return [String]
125
+ def envelope
126
+ raise ArgumentError unless west >= -180 && west <= 180 &&
127
+ east >= -180 && east <= 180 &&
128
+ north >= -90 && north <= 90 &&
129
+ south >= -90 && south <= 90 &&
130
+ west <= east && south <= north
131
+ "ENVELOPE(#{west}, #{east}, #{north}, #{south})"
132
+ end
133
+
134
+ def subjects
135
+ fgdc.metadata.xpath('//themekey').map(&:text) if fgdc
136
+ end
137
+
138
+ def placenames
139
+ fgdc.metadata.xpath('//placekey').map(&:text) if fgdc
140
+ end
141
+
142
+ def fgdc
143
+ GeoCombine::Fgdc.new(metadata['FgdcText']) if metadata['FgdcText']
144
+ end
145
+
146
+ private
147
+
148
+ ##
149
+ # Builds references used for dct_references
150
+ # @return [Hash]
151
+ def references_hash
152
+ results = {
153
+ 'http://www.opengis.net/def/serviceType/ogc/wfs' => location['wfs'],
154
+ 'http://www.opengis.net/def/serviceType/ogc/wms' => location['wms'],
155
+ 'http://schema.org/url' => location['url'],
156
+ download_uri => location['download']
157
+ }
158
+
159
+ # Handle null, "", and [""]
160
+ results.map { |k, v| { k => ([] << v).flatten.first } if v }
161
+ .flatten
162
+ .compact
163
+ .reduce({}, :merge)
164
+ end
165
+
166
+ def download_uri
167
+ return 'http://schema.org/DownloadAction' if institution == 'Harvard'
168
+ 'http://schema.org/downloadUrl'
169
+ end
170
+
171
+ ##
172
+ # OGP "Location" field parsed
173
+ def location
174
+ JSON.parse(metadata['Location'])
175
+ end
176
+
177
+ def north
178
+ metadata['MaxY'].to_f
179
+ end
180
+
181
+ def south
182
+ metadata['MinY'].to_f
183
+ end
184
+
185
+ def east
186
+ metadata['MaxX'].to_f
187
+ end
188
+
189
+ def west
190
+ metadata['MinX'].to_f
191
+ end
192
+
193
+ def institution
194
+ metadata['Institution']
195
+ end
196
+
197
+ def identifier
198
+ CGI.escape(metadata['LayerId']) # TODO: why are we using CGI.escape?
199
+ end
200
+
201
+ def slug
202
+ name = metadata['LayerId'] || metadata['Name'] || ''
203
+ name = [institution, name].join('-') if institution.present? &&
204
+ !name.downcase.start_with?(institution.downcase)
205
+ sluggify(filter_name(name))
206
+ end
207
+
208
+ SLUG_BLACKLIST = %w[
209
+ SDE_DATA.
210
+ SDE.
211
+ SDE2.
212
+ GISPORTAL.GISOWNER01.
213
+ GISDATA.
214
+ MORIS.
215
+ ].freeze
216
+
217
+ def filter_name(name)
218
+ # strip out schema and usernames
219
+ SLUG_BLACKLIST.each do |blacklisted|
220
+ name.sub!(blacklisted, '')
221
+ end
222
+ unless name.size > 1
223
+ # use first word of title is empty name
224
+ name = metadata['LayerDisplayName'].split.first
225
+ end
226
+ name
227
+ end
228
+ end
229
+ end