geo_combine 0.1.0 → 0.5.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 (42) hide show
  1. checksums.yaml +5 -5
  2. data/.travis.yml +6 -1
  3. data/Gemfile +2 -1
  4. data/README.md +108 -23
  5. data/geo_combine.gemspec +4 -2
  6. data/lib/geo_combine.rb +11 -1
  7. data/lib/geo_combine/bounding_box.rb +71 -0
  8. data/lib/geo_combine/ckan_metadata.rb +112 -0
  9. data/lib/geo_combine/esri_open_data.rb +0 -9
  10. data/lib/geo_combine/exceptions.rb +8 -0
  11. data/lib/geo_combine/formatting.rb +6 -1
  12. data/lib/geo_combine/geo_blacklight_harvester.rb +203 -0
  13. data/lib/geo_combine/geoblacklight.rb +80 -12
  14. data/lib/geo_combine/ogp.rb +229 -0
  15. data/lib/geo_combine/railtie.rb +7 -0
  16. data/lib/geo_combine/version.rb +1 -1
  17. data/lib/tasks/geo_combine.rake +54 -20
  18. data/lib/xslt/fgdc2geoBL.xsl +95 -154
  19. data/lib/xslt/fgdc2html.xsl +105 -157
  20. data/lib/xslt/iso2geoBL.xsl +62 -84
  21. data/lib/xslt/iso2html.xsl +1107 -1070
  22. data/spec/features/iso2html_spec.rb +7 -1
  23. data/spec/fixtures/docs/basic_geoblacklight.json +5 -7
  24. data/spec/fixtures/docs/ckan.json +456 -0
  25. data/spec/fixtures/docs/full_geoblacklight.json +2 -8
  26. data/spec/fixtures/docs/geoblacklight_pre_v1.json +37 -0
  27. data/spec/fixtures/docs/ogp_harvard_line.json +28 -0
  28. data/spec/fixtures/docs/ogp_harvard_raster.json +28 -0
  29. data/spec/fixtures/docs/ogp_tufts_vector.json +31 -0
  30. data/spec/fixtures/json_docs.rb +20 -0
  31. data/spec/lib/geo_combine/bounding_box_spec.rb +59 -0
  32. data/spec/lib/geo_combine/ckan_metadata_spec.rb +114 -0
  33. data/spec/lib/geo_combine/esri_open_data_spec.rb +1 -14
  34. data/spec/lib/geo_combine/fgdc_spec.rb +11 -14
  35. data/spec/lib/geo_combine/formatting_spec.rb +6 -0
  36. data/spec/lib/geo_combine/geo_blacklight_harvester_spec.rb +190 -0
  37. data/spec/lib/geo_combine/geoblacklight_spec.rb +137 -11
  38. data/spec/lib/geo_combine/iso19139_spec.rb +5 -2
  39. data/spec/lib/geo_combine/ogp_spec.rb +163 -0
  40. data/spec/spec_helper.rb +1 -0
  41. metadata +63 -14
  42. data/lib/schema/geoblacklight-schema.json +0 -169
@@ -26,14 +26,12 @@ module GeoCombine
26
26
  # @return [Hash]
27
27
  def geoblacklight_terms
28
28
  {
29
- uuid: @metadata['id'],
30
29
  dc_identifier_s: @metadata['id'],
31
30
  dc_title_s: @metadata['name'],
32
31
  dc_description_s: sanitize_and_remove_lines(@metadata['description']),
33
32
  dc_rights_s: 'Public',
34
33
  dct_provenance_s: @metadata['owner'],
35
34
  dct_references_s: references,
36
- georss_box_s: georss_box,
37
35
  # layer_id_s is used for describing a layer id for a web serivce (WMS, WFS) but is still a required field
38
36
  layer_id_s: '',
39
37
  layer_geom_type_s: @metadata['geometry_type'],
@@ -62,13 +60,6 @@ module GeoCombine
62
60
  }
63
61
  end
64
62
 
65
- ##
66
- # Builds a GeoRSS box
67
- # @return [String]
68
- def georss_box
69
- "#{south} #{west} #{north} #{east}"
70
- end
71
-
72
63
  ##
73
64
  # Builds a Solr Envelope using CQL syntax
74
65
  # @return [String]
@@ -0,0 +1,8 @@
1
+ module GeoCombine
2
+ module Exceptions
3
+ class InvalidDCTReferences < StandardError
4
+ end
5
+ class InvalidGeometry < StandardError
6
+ end
7
+ end
8
+ end
@@ -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
@@ -0,0 +1,203 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GeoCombine
4
+ ##
5
+ # A class to harvest and index results from GeoBlacklight sites
6
+ # You can configure the sites to be harvested via a configure command.
7
+ # GeoCombine::GeoBlacklightHarvester.configure do
8
+ # {
9
+ # SITE: { host: 'https://example.com', params: { f: { dct_provenance_s: ['SITE'] } } }
10
+ # }
11
+ # end
12
+ # The class configuration also allows for various other things to be configured:
13
+ # - A debug parameter to print out details of what is being harvested and indexed
14
+ # - crawl delays for each page of results (globally or on a per site basis)
15
+ # - Solr's commitWithin parameter (defaults to 5000)
16
+ # - A document transformer proc to modify a document before indexing (defaults to removing _version_, score, and timestamp)
17
+ # Example: GeoCombine::GeoBlacklightHarvester.new('SITE').index
18
+ class GeoBlacklightHarvester
19
+ require 'active_support/core_ext/object/to_query'
20
+
21
+ class << self
22
+ attr_writer :document_transformer
23
+
24
+ def configure(&block)
25
+ @config = yield block
26
+ end
27
+
28
+ def config
29
+ @config || {}
30
+ end
31
+
32
+ def document_transformer
33
+ @document_transformer || ->(document) do
34
+ document.delete('_version_')
35
+ document.delete('score')
36
+ document.delete('timestamp')
37
+ document
38
+ end
39
+ end
40
+ end
41
+
42
+
43
+ attr_reader :site, :site_key
44
+ def initialize(site_key)
45
+ @site_key = site_key
46
+ @site = self.class.config[site_key]
47
+
48
+ raise ArgumentError, "Site key #{@site_key.inspect} is not configured for #{self.class.name}" unless @site
49
+ end
50
+
51
+ def index
52
+ puts "Fetching page 1 @ #{base_url}&page=1" if self.class.config[:debug]
53
+ response = JSON.parse(Net::HTTP.get(URI("#{base_url}&page=1")))
54
+ response_class = BlacklightResponseVersionFactory.call(response)
55
+
56
+ response_class.new(response: response, base_url: base_url).documents.each do |docs|
57
+ docs.map! do |document|
58
+ self.class.document_transformer.call(document) if self.class.document_transformer
59
+ end.compact
60
+
61
+ puts "Adding #{docs.count} documents to solr" if self.class.config[:debug]
62
+ solr_connection.update params: { commitWithin: commit_within, overwrite: true },
63
+ data: docs.to_json,
64
+ headers: { 'Content-Type' => 'application/json' }
65
+
66
+ sleep(crawl_delay.to_i) if crawl_delay
67
+ end
68
+ end
69
+
70
+ ##
71
+ # A "factory" class to determine the blacklight response version to use
72
+ class BlacklightResponseVersionFactory
73
+ def self.call(json)
74
+ keys = json.keys
75
+ if keys.include?('response')
76
+ LegacyBlacklightResponse
77
+ elsif keys.any? && %w[links data].all? { |param| keys.include?(param) }
78
+ ModernBlacklightResponse
79
+ else
80
+ raise NotImplementedError, "The following json response was not able to be parsed by the GeoBlacklightHarvester\n#{json}"
81
+ end
82
+ end
83
+ end
84
+
85
+ class LegacyBlacklightResponse
86
+ attr_reader :base_url
87
+ attr_accessor :response, :page
88
+ def initialize(response:, base_url:)
89
+ @base_url = base_url
90
+ @response = response
91
+ @page = 1
92
+ end
93
+
94
+ def documents
95
+ return enum_for(:documents) unless block_given?
96
+
97
+ while current_page && total_pages && (current_page <= total_pages) do
98
+ yield response.dig('response', 'docs')
99
+
100
+ break if current_page == total_pages
101
+ self.page += 1
102
+ puts "Fetching page #{page} @ #{url}" if GeoCombine::GeoBlacklightHarvester.config[:debug]
103
+
104
+ begin
105
+ self.response = JSON.parse(Net::HTTP.get(URI(url)))
106
+ rescue => e
107
+ puts "Request for #{url} failed with #{e}"
108
+ self.response = nil
109
+ end
110
+ end
111
+ end
112
+
113
+ private
114
+
115
+ def url
116
+ "#{base_url}&page=#{page}"
117
+ end
118
+
119
+ def current_page
120
+ response.dig('response', 'pages', 'current_page')
121
+ end
122
+
123
+ def total_pages
124
+ response.dig('response', 'pages', 'total_pages')
125
+ end
126
+ end
127
+
128
+ ##
129
+ # Class to return documents from the Blacklight API (v7 and above)
130
+ class ModernBlacklightResponse
131
+ attr_reader :base_url
132
+ attr_accessor :response, :page
133
+ def initialize(response:, base_url:)
134
+ @base_url = base_url
135
+ @response = response
136
+ @page = 1
137
+ end
138
+
139
+ def documents
140
+ return enum_for(:documents) unless block_given?
141
+
142
+ while response && response['data'].any?
143
+ document_urls = response['data'].collect { |data| data.dig('links', 'self') }.compact
144
+
145
+ yield documents_from_urls(document_urls)
146
+
147
+ url = response.dig('links', 'next')
148
+ break unless url
149
+ self.page += 1
150
+ puts "Fetching page #{page} @ #{url}" if GeoCombine::GeoBlacklightHarvester.config[:debug]
151
+ begin
152
+ self.response = JSON.parse(Net::HTTP.get(URI(url)))
153
+ rescue => e
154
+ puts "Request for #{url} failed with #{e}"
155
+ self.response = nil
156
+ end
157
+ end
158
+ end
159
+
160
+ private
161
+
162
+ def documents_from_urls(urls)
163
+ puts "Fetching #{urls.count} documents for page #{page}" if GeoCombine::GeoBlacklightHarvester.config[:debug]
164
+ urls.map do |url|
165
+ begin
166
+ JSON.parse(Net::HTTP.get(URI("#{url}/raw")))
167
+ rescue => e
168
+ puts "Fetching \"#{url}/raw\" failed with #{e}"
169
+
170
+ nil
171
+ end
172
+ end.compact
173
+ end
174
+ end
175
+
176
+ private
177
+
178
+ def base_url
179
+ "#{site[:host]}?#{default_params.to_query}"
180
+ end
181
+
182
+ def solr_connection
183
+ solr_url = ENV['SOLR_URL'] || 'http://127.0.0.1:8983/solr/blacklight-core'
184
+
185
+ RSolr.connect url: solr_url, adapter: :net_http_persistent
186
+ end
187
+
188
+ def commit_within
189
+ self.class.config[:commit_within] || '5000'
190
+ end
191
+
192
+ def crawl_delay
193
+ site[:crawl_delay] || self.class.config[:crawl_delay]
194
+ end
195
+
196
+ def default_params
197
+ {
198
+ per_page: 100,
199
+ format: :json
200
+ }.merge(site[:params])
201
+ end
202
+ end
203
+ end
@@ -1,3 +1,7 @@
1
+ require 'active_support/core_ext/object/blank'
2
+ require 'active_support/core_ext/hash/except'
3
+ require 'open-uri'
4
+
1
5
  module GeoCombine
2
6
  class Geoblacklight
3
7
  include GeoCombine::Formats
@@ -6,6 +10,18 @@ module GeoCombine
6
10
 
7
11
  attr_reader :metadata
8
12
 
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
24
+
9
25
  ##
10
26
  # Initializes a GeoBlacklight object
11
27
  # @param [String] metadata be a valid JSON string document in
@@ -13,13 +29,16 @@ module GeoCombine
13
29
  # @param [Hash] fields enhancements to metadata that are merged with @metadata
14
30
  def initialize(metadata, fields = {})
15
31
  @metadata = JSON.parse(metadata).merge(fields)
32
+ @schema = nil
16
33
  end
17
34
 
18
35
  ##
19
36
  # Calls metadata enhancement methods for each key, value pair in the
20
37
  # metadata hash
21
38
  def enhance_metadata
22
- @metadata.each do |key, value|
39
+ upgrade_to_v1 if metadata['geoblacklight_version'].blank?
40
+
41
+ metadata.each do |key, value|
23
42
  translate_formats(key, value)
24
43
  enhance_subjects(key, value)
25
44
  format_proper_date(key, value)
@@ -31,16 +50,36 @@ module GeoCombine
31
50
  ##
32
51
  # Returns a string of JSON from a GeoBlacklight hash
33
52
  # @return (String)
34
- def to_json
35
- @metadata.to_json
53
+ def to_json(options = {})
54
+ metadata.to_json(options)
36
55
  end
37
56
 
38
57
  ##
39
58
  # Validates a GeoBlacklight-Schema json document
40
59
  # @return [Boolean]
41
60
  def valid?
42
- schema = JSON.parse(File.read(File.join(File.dirname(__FILE__), '../schema/geoblacklight-schema.json')))
43
- JSON::Validator.validate!(schema, to_json, validate_schema: true)
61
+ @schema ||= JSON.parse(open(SCHEMA_JSON_URL).read)
62
+ JSON::Validator.validate!(@schema, to_json, fragment: '#/properties/layer') &&
63
+ dct_references_validate! &&
64
+ spatial_validate!
65
+ end
66
+
67
+ ##
68
+ # Validate dct_references_s
69
+ # @return [Boolean]
70
+ def dct_references_validate!
71
+ return true unless metadata.key?('dct_references_s') # TODO: shouldn't we require this field?
72
+ begin
73
+ ref = JSON.parse(metadata['dct_references_s'])
74
+ raise GeoCombine::Exceptions::InvalidDCTReferences, 'dct_references must be parsed to a Hash' unless ref.is_a?(Hash)
75
+ true
76
+ rescue JSON::ParserError => e
77
+ raise e, "Invalid JSON in dct_references_s: #{e.message}"
78
+ end
79
+ end
80
+
81
+ def spatial_validate!
82
+ GeoCombine::BoundingBox.from_envelope(metadata['solr_geom']).valid?
44
83
  end
45
84
 
46
85
  private
@@ -49,43 +88,72 @@ module GeoCombine
49
88
  # Enhances the 'dc_format_s' field by translating a format type to a valid
50
89
  # GeoBlacklight-Schema format
51
90
  def translate_formats(key, value)
52
- @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]
53
93
  end
54
94
 
55
95
  ##
56
96
  # Enhances the 'layer_geom_type_s' field by translating from known types
57
97
  def translate_geometry_type(key, value)
58
- @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]
59
100
  end
60
101
 
61
102
  ##
62
103
  # Enhances the 'dc_subject_sm' field by translating subjects to ISO topic
63
104
  # categories
64
105
  def enhance_subjects(key, value)
65
- @metadata[key] = value.map do |val|
106
+ return unless key == 'dc_subject_sm'
107
+ metadata[key] = value.map do |val|
66
108
  if subjects.include?(val)
67
109
  subjects[val]
68
110
  else
69
111
  val
70
112
  end
71
- end if key == 'dc_subject_sm'
113
+ end
72
114
  end
73
115
 
74
116
  ##
75
117
  # Formats the 'layer_modified_dt' to a valid valid RFC3339 date/time string
76
118
  # and ISO8601 (for indexing into Solr)
77
119
  def format_proper_date(key, value)
78
- @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
79
122
  end
80
123
 
81
124
  def fields_should_be_array(key, value)
82
- @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]
83
127
  end
84
128
 
85
129
  ##
86
130
  # GeoBlacklight-Schema fields that should be type Array
87
131
  def should_be_array
88
- ['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?
89
157
  end
90
158
  end
91
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