Class: Dor::GeoMetadataDS

Inherits:
ActiveFedora::NokogiriDatastream
  • Object
show all
Includes:
SolrDocHelper
Defined in:
lib/dor/datastreams/geo_metadata_ds.rb

Overview

GeoMetadataDS is a Fedora datastream for geographic metadata. It uses the ISO 19139 metadata standard schema - a metadata standard for Geographic Information The datastream is packaged using RDF to identify the optional ISO 19139 feature catalog

See Also:

Author:

  • Darren Hardy

Constant Summary collapse

NS =

namespaces

{
  :rdf => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
  :gco => 'http://www.isotc211.org/2005/gco',
  :gmd => 'http://www.isotc211.org/2005/gmd',
  :gfc => 'http://www.isotc211.org/2005/gfc'
}
XMLNS =

hash with all namespaces

Hash[NS.map {|k,v| ["xmlns:#{k}", v]
NS_XSD =

schema locations

NS.keys.collect {|k| "#{NS[k]} #{NS[k]}/#{k}.xsd"}
XSLT_GEOMODS =
Nokogiri::XSLT::Stylesheet

for ISO 19139 to MODS

Nokogiri::XSLT(File.read(
File.join(
File.dirname(__FILE__), 'geo2mods.xsl')))
XSLT_DC =
Nokogiri::XSLT(File.new(
File.expand_path(
File.dirname(__FILE__) + '/../models/mods2dc.xslt')))
E =

Convert DD.DD to DD MM SS.SS e.g.,

  • -109.758319 => 109°45ʹ29.9484ʺ

  • 48.999336 => 48°59ʹ57.609ʺ

1
QSEC =
'ʺ'
QMIN =
'ʹ'
QDEG =
"\u00B0"

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from SolrDocHelper

#add_solr_value

Instance Attribute Details

#geometryTypeObject

Returns the value of attribute geometryType.



15
16
17
# File 'lib/dor/datastreams/geo_metadata_ds.rb', line 15

def geometryType
  @geometryType
end

#purlObject

Returns the value of attribute purl.



15
16
17
# File 'lib/dor/datastreams/geo_metadata_ds.rb', line 15

def purl
  @purl
end

#zipNameObject

Returns the value of attribute zipName.



15
16
17
# File 'lib/dor/datastreams/geo_metadata_ds.rb', line 15

def zipName
  @zipName
end

Class Method Details

.dd2ddmmss_abs(f) ⇒ Object



313
314
315
316
317
318
319
320
321
322
# File 'lib/dor/datastreams/geo_metadata_ds.rb', line 313

def self.dd2ddmmss_abs f
  dd = f.to_f.abs
  d  = dd.floor
  mm = (dd - d) * 60
  m  = mm.floor
  s  = ((mm - mm.floor) * 60).round
  m, s = m + 1, 0 if s >= 60
  d, m = d + 1, 0 if m >= 60
  "#{d}#{QDEG}" + (m > 0 ? "#{m}#{QMIN}" : '') + (s > 0 ? "#{s}#{QSEC}" : '')
end

.to_coordinates_ddmmss(s) ⇒ Object

Convert to MARC 255 DD into DDMMSS westernmost longitude, easternmost longitude, northernmost latitude, and southernmost latitude e.g., -109.758319 – -88.990844/48.999336 – 29.423028

Raises:

  • (ArgumentError)


294
295
296
297
298
299
300
301
302
303
# File 'lib/dor/datastreams/geo_metadata_ds.rb', line 294

def self.to_coordinates_ddmmss s
  w, e, n, s = s.scanf('%f -- %f/%f -- %f')
  raise ArgumentError, "Out of bounds latitude: #{n} #{s}"  unless n >= -90  && n <= 90  && s >= -90  && s <= 90
  raise ArgumentError, "Out of bounds longitude: #{w} #{e}" unless w >= -180 && w <= 180 && e >= -180 && e <= 180
  w = "#{w < 0 ? 'W' : 'E'} #{Dor::GeoMetadataDS::dd2ddmmss_abs w}"
  e = "#{e < 0 ? 'W' : 'E'} #{Dor::GeoMetadataDS::dd2ddmmss_abs e}"
  n = "#{n < 0 ? 'S' : 'N'} #{Dor::GeoMetadataDS::dd2ddmmss_abs n}"
  s = "#{s < 0 ? 'S' : 'N'} #{Dor::GeoMetadataDS::dd2ddmmss_abs s}"
  "#{w}--#{e}/#{n}--#{s}"
end

.to_wkt(xy, xy2 = nil) ⇒ String

Returns WKT for point or rectangle.

Parameters:

  • (x,y) (Array<Numeric>)

    coordinates for point or bounding box

Returns:

  • (String)

    WKT for point or rectangle



279
280
281
282
283
284
285
286
287
288
289
# File 'lib/dor/datastreams/geo_metadata_ds.rb', line 279

def self.to_wkt xy, xy2 = nil
  if xy2
    w = [xy[0], xy2[0]].min
    e = [xy[0], xy2[0]].max
    s = [xy[1], xy2[1]].min
    n = [xy[1], xy2[1]].max
    "POLYGON((#{w} #{s}, #{w} #{n}, #{e} #{n}, #{e} #{s}, #{w} #{s}))"
  else
    "POINT(#{xy[0]} #{xy[1]})"
  end
end

.xml_templateNokogiri::XML::Document

Returns Contains skeleton geoMetadata XML Add your druid as the suffix to rdf:about attributes. Includes all possible xmlns for gmd and gfc.

Returns:

  • (Nokogiri::XML::Document)

    Contains skeleton geoMetadata XML Add your druid as the suffix to rdf:about attributes. Includes all possible xmlns for gmd and gfc



101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/dor/datastreams/geo_metadata_ds.rb', line 101

def self.xml_template
  Nokogiri::XML::Builder.new do |xml|
    xml['rdf'].RDF XMLNS,
      'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
      "xsi:schemaLocation" => NS_XSD.join(' ') do
      xml['rdf'].Description 'rdf:about' => nil do
        xml['gmd'].
      end
      xml['rdf'].Description 'rdf:about' => nil do
        xml['gfc'].FC_FeatureCatalogue
      end
    end
  end.doc
end

Instance Method Details

#feature_catalogueNokogiri::XML::Document

Returns with gfc:FC_FeatureCatalogue as root node or nil if not provided.

Returns:



89
90
91
92
93
94
95
96
# File 'lib/dor/datastreams/geo_metadata_ds.rb', line 89

def feature_catalogue
  root = ng_xml.xpath('/rdf:RDF/rdf:Description/gfc:FC_FeatureCatalogue', XMLNS)
  if root.nil? || root.empty?
    nil # Feature catalog is optional
  else
    Nokogiri::XML(root.first.to_xml)
  end
end

#metadataNokogiri::XML::Document

Returns with gmd:MD_Metadata as root node.

Returns:

Raises:



79
80
81
82
83
84
85
86
# File 'lib/dor/datastreams/geo_metadata_ds.rb', line 79

def 
  root = ng_xml.xpath('/rdf:RDF/rdf:Description/gmd:MD_Metadata', XMLNS)
  if root.nil? || root.empty?
    raise Dor::ParameterError, "Invalid geoMetadata -- missing MD_Metadata: #{root}"
  else
    Nokogiri::XML(root.first.to_xml)
  end
end

#to_bboxStruct

Returns in minX minY maxX maxY order with .w, .e, .n., .s for west, east, north, south as floats.

Returns:

  • (Struct)

    in minX minY maxX maxY order with .w, .e, .n., .s for west, east, north, south as floats



204
205
206
207
208
209
210
211
212
213
214
215
# File 'lib/dor/datastreams/geo_metadata_ds.rb', line 204

def to_bbox
  params = { 'xmlns:gmd' => NS[:gmd], 'xmlns:gco' => NS[:gco] }
  bb = .xpath(
    '//gmd:EX_Extent/gmd:geographicElement' +
    '/gmd:EX_GeographicBoundingBox', params).first
  Struct.new(:w, :e, :n, :s).new(
    bb.xpath('gmd:westBoundLongitude/gco:Decimal', params).text.to_f,
    bb.xpath('gmd:eastBoundLongitude/gco:Decimal', params).text.to_f,
    bb.xpath('gmd:northBoundLatitude/gco:Decimal', params).text.to_f,
    bb.xpath('gmd:southBoundLatitude/gco:Decimal', params).text.to_f
  )
end

#to_centroidArray<Numeric>

Returns (x y) coordinates of center point - assumes #to_bbox.

Returns:

  • (Array<Numeric>)

    (x y) coordinates of center point - assumes #to_bbox

See Also:



219
220
221
222
# File 'lib/dor/datastreams/geo_metadata_ds.rb', line 219

def to_centroid
  bb = to_bbox
  [ (bb.w + bb.e)/2, (bb.n + bb.s)/2 ]
end

#to_dc_coverageString

Returns in Dublin Core Coverage format.

Returns:

  • (String)

    in Dublin Core Coverage format



246
247
248
249
# File 'lib/dor/datastreams/geo_metadata_ds.rb', line 246

def to_dc_coverage
  bb = to_bbox
  "x.min=#{bb.w} x.max=#{bb.e} y.min=#{bb.s} y.max=#{bb.n}"
end

#to_dublin_coreObject



158
159
160
# File 'lib/dor/datastreams/geo_metadata_ds.rb', line 158

def to_dublin_core
  XSLT_DC.transform(to_mods)
end

#to_mods(params = {}) ⇒ Nokogiri::XML::Document

Generates MODS from ISO 19139

Uses GML SimpleFeatures for the geometry type (e.g., Polygon, LineString, etc.)

Returns:

Raises:

  • (CrosswalkError)

    Raises if the generated MODS is empty or has no children

See Also:



124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/dor/datastreams/geo_metadata_ds.rb', line 124

def to_mods(params = {})
  params = params.merge({
    'geometryType' => "'#{@geometryType.nil?? 'Polygon' : @geometryType}'",
    'zipName' => "'#{@zipName.nil?? 'data.zip' : @zipName}'",
    'purl' => "'#{@purl}'"
  })
  doc = XSLT_GEOMODS.transform(.document, params.to_a.flatten)
  unless doc.root && doc.root.children.size > 0
    raise CrosswalkError, 'to_mods produced incorrect xml'
  end
  # ap doc
  doc.xpath('/mods:mods' +
    '/mods:subject' +
    '/mods:cartographics' +
    '/mods:projection',
    'xmlns:mods' => Dor::DescMetadataDS::MODS_NS).each do |e|
    # Retrieve this mapping from config file
    case e.content.downcase
    when 'epsg:4326', 'epsg::4326', 'urn:ogc:def:crs:epsg::4326'
      e.content = 'World Geodetic System (WGS84)'
    when 'epsg:4269', 'epsg::4269', 'urn:ogc:def:crs:epsg::4269'
      e.content = 'North American Datum (NAD83)'
    end
  end
  doc.xpath('/mods:mods' +
    '/mods:subject' +
    '/mods:cartographics' +
    '/mods:coordinates',
    'xmlns:mods' => Dor::DescMetadataDS::MODS_NS).each do |e|
    e.content = '(' + self.class.to_coordinates_ddmmss(e.content.to_s) + ')'
  end
  doc
end

#to_solr_bbox(format = :solr4) ⇒ String

A lat-lon rectangle can be indexed with 4 numbers in minX minY maxX maxY order:

<field name="geo">-74.093 41.042 -69.347 44.558</field>
<field name="geo">POLYGON((...))</field>

Parameters:

  • either (Symbol)

    :solr3 or :solr4

Returns:

  • (String)

    minX minY maxX maxY for :solr3 or POLYGON((…)) for :solr4

See Also:



232
233
234
235
236
237
238
239
240
241
242
243
# File 'lib/dor/datastreams/geo_metadata_ds.rb', line 232

def to_solr_bbox format = :solr4
  bb = to_bbox

  case format
  when :solr3
    [bb.w, bb.s, bb.e, bb.n].join(' ')
  when :solr4
    Dor::GeoMetadataDS.to_wkt [bb.w, bb.s], [bb.e, bb.n]
  else
    raise ArgumentError, "Unsupported format #{format}"
  end
end

#to_solr_centroid(format = :solr4) ⇒ String

Returns (y,x) coordinates of center point matching the LatLonType Solr type.

Returns:

  • (String)

    (y,x) coordinates of center point matching the LatLonType Solr type

See Also:



262
263
264
265
266
267
268
269
270
271
272
273
# File 'lib/dor/datastreams/geo_metadata_ds.rb', line 262

def to_solr_centroid format = :solr4
  x, y = to_centroid

  case format
  when :solr3
    [y, x].join(',') # for solr.LatLonType
  when :solr4
    Dor::GeoMetadataDS.to_wkt [x, y]
  else
    raise ArgumentError, "Unsupported format #{format}"
  end
end

#to_solr_spatial(solr_doc = {}, *args) ⇒ Object

Deprecated.

stub for GeoBlacklight (not Argo – use to_solr as usual)



163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
# File 'lib/dor/datastreams/geo_metadata_ds.rb', line 163

def to_solr_spatial(solr_doc = {}, *args)
  # There are a whole bunch of namespace-related things that can go
  # wrong with this terminology. Until it's fixed in OM, ignore them all.
  begin
    doc = solr_doc # super solr_doc, *args
    bb = to_bbox
    ap({:doc => doc, :bb => bb, :self => self}) if $DEBUG
    # self is required once below, because format is a keyword
    {
      :id => id.first,
      :druid => URI(id.first).path.gsub(%r{^/}, ''),
      :file_id_s => file_id.first,
      :geo_bbox => to_solr_bbox,
      :geo_data_type_s => 'vector',
      :geo_format_s => self.format.first, # rubocop:disable Style/RedundantSelf
      :geo_geometry_type_s => 'Polygon',
      :geo_layername_s => File.basename(layername.first, '.shp'),
      :geo_ne_pt => Dor::GeoMetadataDS.to_wkt([bb.e, bb.n]),
      :geo_pt => to_solr_centroid,
      :geo_sw_pt => Dor::GeoMetadataDS.to_wkt([bb.w, bb.s]),
      :geo_proj => projection.first,
      :dc_coverage_t => to_dc_coverage,
      :dc_creator_t => originator.first,
      :dc_date_i => publish_dt.map {|i| i.to_s[0..3]},
      :dc_description_t => [abstract.first, purpose.first].join(";\n"),
      :dc_format_s => 'application/x-esri-shapefile',
      :dc_language_s => .first,
      :dc_title_t => title.first,
      :text => [title.first, abstract.first, purpose.first].join(";\n")
    }.each do |id, v|
      ::Solrizer::Extractor.insert_solr_field_value(doc, id.to_s, v)
    end

    return doc
  rescue
    solr_doc
  end
end