npolar/api.npolar.no

View on GitHub
lib/metadata/dif_hashifier.rb

Summary

Maintainability
F
4 days
Test Coverage
# encoding  : utf-8
require "uri"

module Metadata

  # GCMD [DIF Hash](https://github.com/npolar/gcmd/blob/master/lib/gcmd/dif.rb) from [Npolar API dataset](http://api.npolar.no/schema/dataset)
  #
  # The mapping is defined in #to_hash. Each DIF element is typically
  # defined in a method named after the element (lowercased), like #dif_revision_history
  #
  # Usage
  #   dif_hash = Metadata::DifHashifier.new(npolar_dataset_hash).to_hash
  #   dif_xml = Gcmd::Dif.new(dif_hash).to_xml
  #
  # GCMD DIF elements
  # A dataset may hold a special "gcmd" block containing raw DIF elements in
  # the following keys => DIF element mapping:
  # { "sciencekeywords" => dif.Parameters||[],
  #   "instruments" => dif.Sensor_Name||[],
  #   "platforms" => dif.Source_Name||[],
  #   "locations" => dif.Location||[],
  #   "projects" => dif.Project||[],
  #   "resolutions" => dif.Data_Resolution||[],
  #   "disciplines" => dif.Discipline||[],
  #   "idn_nodes" => dif.IDN_Node||[],
  #   "paleo_temporal_coverage" => dif.Paleo_Temporal_Coverage||[],
  #   "instruments" => dif.Sensor_Name||[],
  #   "references" => dif.Reference||[],
  #   "extended" => dif.Additional_Metadata||[],
  #   "citation" => dif.Data_Set_Citation||[]
  #   "entry_id" => dif.Entry_ID
  # }
  # Notice: GCMD-specific metadata (like IDN_Node, Parameters, and Location) is
  # also created automagically when there is an appropriate mapping.
  #
  # Links
  # * Metadata::Dataset#to_dif_hash
  # * https://github.com/npolar/gcmd/blob/master/lib/gcmd/dif.rb
  # * http://gcmd.nasa.gov/add/difguide/
  #
  # Open issues
  # * "author" and set pI to author by default? (allowing for other authors?) organisations as authors
  # * Validating gcmd block (need concept version - where to store)
  # * GCMD concepts uuid
  #   # @todo map isotopics to topics
  # FIXME Dif author missing
  # FIXME OOps#<Role>author</Role>

  class DifHashifier < Hashie::Mash

    # Map [topics](http://api.npolar.no/schema/npolar_topic)to DIF Parameters (Science Keywords)
    # DIF Parameters: http://api.npolar.no/gcmd/concept/?q=&filter-concept=sciencekeywords
    # The main trouble is that 3 levels (down to Topic) are required by DIF - impossible for just "biology"/"geology"/"atmosphere"
    # @param [String] topic
    # @return [Hashie::Mash]
    def self.dif_Parameter(topic)

      dif_Topic, dif_Term, dif_Variable_Level_1 = case topic

        # Terms for BIOSPHERE from: http://api.npolar.no/gcmd/concept/?q=&start=0&limit=10&sort=&fq=concept:sciencekeywords&fq=ancestors:EARTH+SCIENCE&fq=ancestors:Science+Keywords&fq=ancestors:BIOSPHERE&fq=cardinality:3
        # * TERRESTRIAL ECOSYSTEMS (Science Keywords > EARTH SCIENCE > BIOSPHERE) | concept
        # * VEGETATION (Science Keywords > EARTH SCIENCE > BIOSPHERE) | concept
        # * ECOLOGICAL DYNAMICS (Science Keywords > EARTH SCIENCE > BIOSPHERE) | concept
        # * AQUATIC ECOSYSTEMS (Science Keywords > EARTH SCIENCE > BIOSPHERE) | concept
        when "biology"
          ["BIOSPHERE", "ECOLOGICAL DYNAMICS"]

        # ECOTOXICOLOGY (Science Keywords > EARTH SCIENCE > BIOSPHERE > ECOLOGICAL DYNAMICS)
        # http://api.npolar.no/gcmd/concept/?q=impacts&start=0&limit=10&sort=&fq=ancestors:HUMAN+DIMENSIONS&fq=ancestors:ENVIRONMENTAL+IMPACTS
        when "ecotoxicology"
          ["BIOSPHERE", "ECOLOGICAL DYNAMICS", "ECOTOXICOLOGY"]

        # http://www.npolar.no/en/about-us/organization/research/geology-and-geophysics.html
        when "geology"
          ["SOLID EARTH"]

        # GLACIERS (Science Keywords > EARTH SCIENCE > CRYOSPHERE > GLACIERS/ICE SHEETS) | concept
        # GLACIERS (Science Keywords > EARTH SCIENCE > TERRESTRIAL HYDROSPHERE > GLACIERS/ICE SHEETS) | concept
        when "glaciology"
          ["CRYOSPHERE", "GLACIERS/ICE SHEETS", "GLACIERS"]

        # Science Keywords > EARTH SCIENCE > ATMOSPHERE
        # Topics: http://api.npolar.no/gcmd/concept/?q=atmosphere&start=0&limit=10&sort=&fq=concept:sciencekeywords&fq=ancestors:Science+Keywords&fq=ancestors:EARTH+SCIENCE&fq=ancestors:ATMOSPHERE&fq=cardinality:3
        when "atmosphere"
          ["ATMOSPHERE"]

        # Science Keywords > EARTH SCIENCE > LAND SURFACE > TOPOGRAPHY
        when "maps", "topography"
          ["LAND SURFACE", "TOPOGRAPHY"]

        # Science Keywords > EARTH SCIENCE > OCEANS > SEA ICE
        when "seaice"
          ["OCEANS", "SEA ICE"]

        when "oceanography"
          ["OCEANS", "SALINITY/DENSITY"]
          # Others/more:
          # WATER TEMPERATURE (Science Keywords > EARTH SCIENCE > OCEANS > OCEAN TEMPERATURE)
          # WATER PRESSURE (Science Keywords > EARTH SCIENCE > OCEANS > OCEAN PRESSURE)
          # DENSITY (Science Keywords > EARTH SCIENCE > OCEANS > SALINITY/DENSITY) | concept
          # SALINITY (Science Keywords > EARTH SCIENCE > OCEANS > SALINITY/DENSITY) | concept

        else
          []
      end

      Hashie::Mash.new({ "Category" => "EARTH SCIENCE",
        "Topic" => dif_Topic,
        "Term" => dif_Term,
        "Variable_Level_1" => dif_Variable_Level_1})
    end

    # @return [String]
    def access_constraints
      restrictions
    end

    # @return [Hashie::Mash] with GCMD DIF element names as keys
    def to_hash

        unless gcmd?
          self[:gcmd]=Hashie::Mash.new
        end
        unless coverage?
          self[:coverage]=[]
        end
        unless activity?
          self[:activity]=[]
        end
        unless changes?
          self[:changes]=[]
        end
        unless topics?
          self[:topics]=[]
        end
        unless licences?
          self[:licences]=[]
        end
        unless rights?
          self[:rights]=nil
        end
        # Code below is for a future Gcmd::DifSchema class
        # hash = {}
        # Gcmd::DifSchema.new.info.keys.each do |element|
        #   hash[element.to_sym]=self.send(element.downcase.to_sym)
        # end

        hash = Hashie::Mash.new({ "Entry_ID" => id||_id||"MISSING",
          "Entry_Title" => title,
          "Data_Set_Citation" => data_set_citation,
          "Personnel" => personnel,
          "Discipline" => discipline,
          "Parameters" => parameters,
          "ISO_Topic_Category" => iso_topic_category,
          "Keyword" => keyword,
          "Sensor_Name" => sensor_name,
          "Source_Name" => source_name,
          "Temporal_Coverage" => temporal_coverage,
          "Paleo_Temporal_Coverage" => paleo_temporal_coverage,
          "Data_Set_Progress" => data_set_progress,
          "Spatial_Coverage" => spatial_coverage,
          "Location" => location,
          "Data_Resolution" => data_resolution,
          "Project" => project,
          "Quality" => quality,
          "Access_Constraints" => access_constraints,
          "Use_Constraints" => use_constraints,
          "Data_Set_Language" => data_set_language,
          "Originating_Center" => originating_center,
          "Data_Center" => data_center,
          "Distribution" => distribution,
          "Multimedia_Sample" => multimedia_sample,
          "Reference" => reference,
          "Summary" => {"Abstract" => summary},
          "Related_URL" => related_url,
          "Parent_DIF" => parent_dif,
          "IDN_Node" => idn_node,


          # Watch out, all of these have multiplicity 1
          #<xs:element ref="Originating_Metadata_Node" minOccurs="0" maxOccurs="1"/>
          #<xs:element ref="Metadata_Name" minOccurs="1" maxOccurs="1"/>
          #<xs:element ref="Metadata_Version" minOccurs="1" maxOccurs="1"/>
          #<xs:element ref="DIF_Creation_Date" minOccurs="0" maxOccurs="1"/>
          #<xs:element ref="Last_DIF_Revision_Date" minOccurs="0" maxOccurs="1"/>
          #<xs:element ref="DIF_Revision_History" minOccurs="0" maxOccurs="1"/>
          #<xs:element ref="Future_DIF_Review_Date" minOccurs="0" maxOccurs="1"/>
          #<xs:element ref="Private" minOccurs="0" maxOccurs="1"/>

          "Originating_Metadata_Node" => originating_metadata_node,
          "Metadata_Name" => "CEOS IDN DIF",
          "Metadata_Version" => ::Gcmd::Schema::VERSION,
          "DIF_Creation_Date" => (created||"T").split("T")[0],
          "Last_DIF_Revision_Date" => (updated||"T").split("T")[0],
          "DIF_Revision_History" => dif_revision_history,
          "Future_DIF_Review_Date" => future_dif_review_date,
          "Private" => draft == "yes" ? "True" : "False",
          "Extended_Metadata" => extended_metadata
      })
      # Gcmd::Dif currently needs a ordered Hash to write valid DIF XML.
      # Therefore we insert all DIF elements above, and then remove empty ones
      # here to keep the ordering
      hash.each do |k,v|
        if v.respond_to?(:none?) and ( v.none? or v.nil? )
          hash.delete k
        end
      end
      hash

    end

    # In DIF, a data center is the organization or institution responsible for distributing the data
    # Organisation with roles "owner","resourceProvider", "publisher" are mapped to data centres,
    # but only if they have a GCMD provider code ("short name").
    def data_center
      data_center = (organisations||[]).reject {|o| not o.gcmd_short_name or o.gcmd_short_name.to_s == "" }.select {|o|
        o.roles.include?("owner") or o.roles.include?("resourceProvider") or o.roles.include?("publisher")
      }.map {|o|

        data_center_url = o.homepage
        data_center_contacts = personnel(/pointOfContact/, o.id)

        # If no point of contact is listed that can be used as data center contact
        # Set Npolar if we are the data center else generate a blank entry.
        if data_center_contacts.none?

          if data_center_url =~ /npolar.no/ or o.id == "npolar.no"
            data_set_id = id
            data_center_contacts << {
              Role: "Data Center Contact",
              Last_Name: "Norwegian Polar Data",
              Email: "data[*]npolar.no"
            }
          else
            # Data Center Contact is required. When no point of contact is listed use the
            # GCMD name of the organisation as last name and list it as data center contact.
            data_center_contacts = [Hashie::Mash.new({
              Role: "Data Center Contact",
              Last_Name:  o.name
            })]
          end
        end

        {
          Data_Center_Name: {
            Short_Name: o.gcmd_short_name,
            Long_Name: o.name
          },
          Data_Center_URL: data_center_url,
          Data_Set_ID: data_set_id,
          Personnel: data_center_contacts
        }
      }

      # If no data center was found set NP as the data center
      if data_center.none?
        npolar = Metadata::Dataset.npolar(["pointOfContact"])
        data_center << {
          Data_Center_Name: {
          Short_Name: npolar.gcmd_short_name,
          Long_Name: npolar.name
          },
          Data_Center_URL: npolar.homepage,
          Personnel: {
              Role: "Data Center Contact",
              Last_Name: "Norwegian Polar Data",
              Email: "data[*]npolar.no"
          }
        }
      end

      data_center
    end

    # Data_Set_Language = inferred from link[rel=data].hreflang
    # http://gcmd.gsfc.nasa.gov/add/difguide/data_set_language.html
    def data_set_language
      links.select {|link| link.rel == "data"}.map {|link| link.hreflang }
    end

    # Data_Set_Progress
    def data_set_progress
      case progress
        when "complete", "", nil
          "Complete"
        when "ongoing"
          "In Work"
        when "planned"
          "Planned"
      end
    end

    # Note: Data_Resolution stems from "gcmd.resolution" array only
    def data_resolution
      gcmd.resolutions? ? gcmd.resolutions : []
    end

    def dif_revision_history
      history = []
     (changes||[]).sort_by {|edit| edit.datetime }.each {|edit|
      history << "#{(edit.datetime||"T").split("T")[0]}, #{edit.comment||"edited by"} #{edit.name} (#{edit.email})"
      }
      history.join("\n")
    end

    # Discipline (experimental)
    def discipline
      #gcmd.discipline? ? gcmd.discipline : []
      discipline_name = lambda {|topic|
        case topic
          when "biology", "geology", "oceanography", "geophysics"
            topic.upcase
          else ""
        end
      }

      # @mightdo Subdiscipline
      # @mightdo Detailed_Subdiscipline
      names = (topics||[]).map {|topic| discipline_name.call(topic) }.uniq
      if names.any?
        { "Discipline_Name" => names[0] }
      else
        []
      end
    end

    # Distribution
    def distribution
      #gcmd.distribution? ? gcmd.distribution : []
      links.select {|link| link.rel =~ /data/ and link.href =~ /^http(s)?[:]/ }.map {|link|
        Hashie::Mash.new({
          Distribution_Media: "Online Internet (HTTP)", Distribution_Size: link[:length], Distribution_Format: link.type
        })
      }.uniq
    end

    # Data_Set_Citation = "gcmd.citation" (if present) OR generated
    # using other metadata.
    #
    # http://gcmd.nasa.gov/add/difguide/data_set_citation.html
    def data_set_citation
      if gcmd.citation? and gcmd.citation.any?
        return gcmd.citation
      end

      publishers = (organisations||[]).select {|o| o.roles.include? "publisher" }
      html_links = links.select {|link| link.rel == "alternate" and link.type == "text/html"}

      publisher = publishers.any? ? publishers[0].name : nil
      online_resource = html_links.any? ? html_links[0].href : nil

      # Release date if set, otherwise use published date if at least one data link
      released = released.nil? ? published : released
      release_date = (0 < links.select {|link| link.rel == "data"}.size) ? (released||"T").split("T")[0] : nil
      release_place = publisher =~ /^Norwegian Polar/ ? "Tromsø, Norway" : nil

      Hashie::Mash.new({ "Dataset_Creator" => authors.join(", "),
        "Dataset_Title" => title,
        "Dataset_Release_Date" => release_date,
        "Dataset_Release_Place" => release_place,
        "Dataset_Publisher" => publisher,
        "Dataset_DOI" => (not doi.nil? ? "https://doi.org/#{doi}" : ""),
        "Online_Resource" => online_resource
      })
    end

    # Entry_ID - not NOT from gcmd.entry_id
    def entry_id
      id||_id
    end

    def future_dif_review_date
      if released?
        now = DateTime.now
        rleased = DateTime.parse(released)
        if rleased > now
          rleased.to_date.to_s
        else
          nil
        end
      else
        nil
      end
    end
    # IDN_Node <= sets and owner
    def idn_node
      nodes = []

      norwegian = (owners||[]).select {|o| o.id =~ /\.no$/i }.size > 0
      norway = (placenames||[]).select{|c|c.country =~ /NO/i }.size > 0

      # AMD/NO
      if ((sets||[]).include?("antarctic") and norwegian)
        nodes << "AMD/NO"
      end

      #ARCTIC/NO
      if ((sets||[]).include?("arctic") and norway)
        nodes << "ARCTIC/NO"
      end

      nodes += (sets||[]).map {|set|
        case set
          when /IPY/ui
            "IPY"
          when "arctic"
            "ARCTIC"
          when "antarctic"
            "AMD"
        end
      }.uniq
      nodes.select {|n| not n.nil? }.map { |n|
        {"Short_Name" => n }
      }
    end

    # Keyword
    def keyword
      ((tags||[])+(topics||[])).uniq
    end

    # Sensor_Name <= gcmd.instruments
    def instruments
      gcmd.instruments? ? gcmd.instruments : []
    end
    alias :sensor_name :instruments

    # Paleo_Temporal_Coverage
    def paleo_temporal_coverage
       gcmd.paleo_temporal_coverage? ? gcmd.paleo_temporal_coverage : []
    end

    # Parameters = gcmd.sciencekeywords + dif_Parameter(topic)
    def parameters
      parameters = gcmd.sciencekeywords? ? gcmd.sciencekeywords : []
      parameters += topics.map {|topic| self.class.dif_Parameter(topic) }
      parameters = parameters.uniq

      parameters.reject! {|p| p.Topic.nil? or p.Term.nil? }

      parameters.each_with_index do |p,i|
        if p.Topic.nil?
          # One of: http://api.npolar.no/gcmd/concept/?limit=250&q=&sort=&filter-concept=sciencekeywords&filter-cardinality=2&fields=title&format=csv
          p.Topic = "MISSING-TOPIC"
        end
        if p.Term.nil?
          # One of: http://api.npolar.no/gcmd/concept/?limit=250&q=&sort=&filter-concept=sciencekeywords&filter-cardinality=3&fields=title&format=csv
          p.Term = "MISSING-TERM"
        end
      end
      parameters

    end
    alias :sciencekeywords :parameters

    #http://gcmd.nasa.gov/add/difguide/extended_metadata.html
    def extended_metadata
      { "Metadata" => [
        {"Group" => "no.npolar.api", "Name" => "dataset.rev", "Value" => rev||_rev },
        { "Group" => "gov.nasa.gsfc.gcmd", "Name" => "metadata.keyword_version", "Value" => ::Gcmd::Concepts::VERSION }
      ]}
    end

    # ISO_Topic_Category = iso_topics
    def iso_topic_category
      (iso_topics||[]).map {|i|
        case i
          when "climatologyMeteorologyAtmosphere"
            "CLIMATOLOGY/METEOROLOGY/ATMOSPHERE"
          when "geoscientificInformation"
            "GEOSCIENTIFIC INFORMATION"
          else
            i.upcase
        end
      }
    end

    def originating_center
      # or links[rel=datacenter]?
      originators = (organisations||[]).select {|o| o.roles.include? "originator"}

      # If there is an organisation with the originator role return that else
      # respond with Norwegian Polar Institute. This is required by the NMDC
      # metadata harvester. They made originating_center a required field in
      # contrast to the default DIF implementation where it is just recommended.
      if originators.length > 0
        return originators.first.name
      else
        return "Norwegian Polar Institute"
      end
    end

    def originating_metadata_node
      (organisations||[]).select {|o| o.roles.include? "publisher"}.map {|o| "#{o.name} (#{o.gcmd_short_name})" }.first
    end

    # Location = placenames + gcmd.locations
    # The Norwegian polar areas are autmagically mapped to proper DIF Locations
    # Locations may also be stored in "gcmd.locations"
    def location
      location =  gcmd.locations? ? gcmd.locations  : []

      polar = {"Location_Category" => "GEOGRAPHIC REGION", "Location_Type" => "POLAR"}
      arctic = {"Location_Category" => "GEOGRAPHIC REGION", "Location_Type" => "ARCTIC"}

      (placenames||[]).each do | p |

        area = p.area
        if p.area? and p.area == ""
          area = p.placename
        end

        case area

          when /^(Svalbard|Jan Mayen)$/ then
            location << {
              "Location_Category" => "OCEAN",
              "Location_Type" => "ATLANTIC OCEAN",
              "Location_Subregion1" => "NORTH ATLANTIC OCEAN",
              "Location_Subregion2" => "SVALBARD AND JAN MAYEN",
              "Detailed_Location" => p.placename
            }
            location << polar
            location << arctic

          when /^(Dronning Maud Land|Antarctica|Antarktis)$/ then
            location << {
              "Location_Category" => "CONTINENT",
              "Location_Type" => "ANTARCTICA",
              "Detailed_Location" =>  p.placename
            }
            location << polar

          when /^(Bouvetøya|Bouvet Island)$/
            location << {
              "Location_Category" => "OCEAN",
              "Location_Type" => "ATLANTIC OCEAN",
              "Location_Subregion1" => "SOUTH ATLANTIC OCEAN",
              "Location_Subregion2" => "BOUVET ISLAND",
              "Detailed_Location" =>  p.placename
            }
            location << polar

          when "Peter I Øy" then
            locations << {
              "Location_Category" => "OCEAN",
              "Location_Type" => "PACIFIC OCEAN",
              "Location_Subregion1" => "SOUTH PACIFIC OCEAN",
              "Detailed_Location" => p.placename
            }
            locations << polar
            locations << {
              "Location_Category" => "CONTINENT",
              "Location_Type" => "ANTARCTICA",
              "Detailed_Location" => p.placename
            }
        end

      end
      location.uniq
    end

    # Multimedia_Sample
    def multimedia_sample
      #  multimedia_samples = gcmd.multimedia_samples? ? gcmd.multimedia_samples : [] @todo
      links.select {|link| multimedia?(link) }.map {|link|
        Hashie::Mash.new({
          URL: link.href, Format: link.type, Caption: link.title
        })
      }
    end

    def multimedia?(link)
      if link.type =~ /^(image|video|audio)\// or link.href =~ /[.](jp(e)?g|png|svg|tif(f)?)$/
        true
      else
        false
      end
    end


    def platforms
      gcmd.platforms? ? gcmd.platforms : []
    end
    alias :source_name :platforms

    # References = links[rel="reference"] + gcmd.references
    def references
      references = gcmd.references? ? gcmd.references : []
      references += links.select {|link| link.rel == "publication" }.map {|link|
        Hashie::Mash.new({
          Publication_Date: "Unknown", Title: link.title, Online_Resource: link.href,
        })
      }

    end
    alias :reference :references

    # Related_URL = links (except internal, parent, and datacentre)
    def related_url

      typer = lambda {|type| case type
        when "dataset", "data"
          "GET DATA"
        when "metadata"
          "VIEW EXTENDED METADATA"
        when "project"
          "VIEW PROJECT HOME PAGE"
        when "edit", "service", "parent", "via"
          "GET SERVICE"
        # related DIF is hard/impossible to identify without a media type for DIF
        #  "GET RELATED METADATA RECORD (DIF)"
        when "internal", "parent", "datacentre" # parent => Parent_DIF, datacentre => Data_Center
          nil
        else # e.g. "related", "alternate", "", nil
          "VIEW RELATED INFORMATION"
        end

      }

      # GET SERVICE subtypes
      # http://api.npolar.no/gcmd/concept/?q=&limit=100&filter-concept=rucontenttype&filter-ancestors=GET+SERVICE&fields=title&format=csv

      # GET DATA subtypes
      # http://api.npolar.no/gcmd/concept/?q=&limit=100&filter-concept=rucontenttype&filter-ancestors=GET+DATA&fields=title&format=csv
      # Subtype for THREDDS
      #http://www.unidata.ucar.edu/software/thredds/current/tds/interfaceSpec/NetcdfSubsetService.html#REST
      # wms!
      subtyper = lambda {|link, dif_type="GET DATA"|
        if dif_type =~ /GET (DATA|SERVICE)/
          if link.type =~ /application\/(x-)?netcdf/
            "THREDDS DATA"
          elsif link.href =~ /\.(cdl|nc)$/i
            "THREDDS DATA"
          elsif link.href =~ /request=GetCapabilities&service=WMS/
            "GET WEB MAP SERVICE (WMS)"
          elsif link.type == "application/atom+xml"
            nil
          elsif link.type == "application/json"
            nil
          end
        end
      }

      related_url = []
      # All links except internal (hidden), datacentre (in Online_Resource) and
      # publication (in References)
      links.reject {|link| link.rel =~ /internal|datacentre|publication|alternate/ }.each {|link|

        dif_type = typer.call(link.rel)
        dif_subtype = subtyper.call(link)

        r = Hashie::Mash.new({ "URL_Content_Type" => {"Type" => dif_type},
          "URL" => link.href, "Description" => link.title||link.rel.capitalize })

        if (link.type? and not link.type.nil?)
          r.Description += " (#{link.type})"
        end
        if not dif_subtype.nil?
          r.URL_Content_Type.Subtype = dif_subtype
        end

        related_url << r

      }
      related_url
    end

    # Parent_DIF = links[rel=parent] (yes unbounded)
    def parent_dif
      links.select {|link| link.rel == "parent" }.map {|link| link.href.gsub(/json$/, "xml") }
    end

    # Personnel <= people
    def personnel(role_filter=/principalInvestigator|processor|author|editor/, organisation=nil)

      personnel = []
      (people||[]).select {|p| organisation.nil? ? true : organisation == p.organisation
      }.each do |p|

        dif_Role = p.roles.reject {|r| r !~ role_filter}.map {|role|
          case role
            when "principalInvestigator"
              "Investigator"
            when "processor"
              "Technical Contact"
            when "pointOfContact"
              "Data Center Contact"
            when "editor"
              "DIF Author"
            else nil
          end
        }

        if dif_Role.any?

          personnel << Hashie::Mash.new({
            "Role" => dif_Role,
            "First_Name" => p.first_name,
            "Last_Name" => p.last_name,
            "Email" => [p.email]
          })
        end
      end

      #if edits.any?
      #  # FIXME select max date
      #  dif_author = edits.last
      #else
      #  dif_author = Hashie::Mash.new({ first_name: updated_by, last_name: "", email: "" })
      #end

      #personnel << Hashie::Mash.new({
      #  "Role" => "DIF Author",
      #  "First_Name" => dif_author.first_name,
      #  "Last_Name" =>dif_author.last_name,
      #  "Email" => dif_author.email
      #})

      personnel
    end

    # Spatial_Coverage <= coverage
    def spatial_coverage
      coverage.map {|c|
        {
          "Southernmost_Latitude" => c.south,
          "Northernmost_Latitude" => c.north,
          "Westernmost_Longitude" => c.west,
          "Easternmost_Longitude" => c.east
        }
      }
    end

    # Temporal_Coverage <= activity
    def temporal_coverage
      activity.map {|a|
        {
          "Start_Date" => (a.start||"T").split("T")[0],
          "Stop_Date" => (a.stop||"T").split("T")[0]
        }
      }
    end

    # Use_Constraints <= rights (but only if npolar.no is publisher)
    def use_constraints
      if organisations? and organisations.any? {|o| o.id =~ /npolar\.no$/ and o.roles.include? "publisher" }
        rights
      else
        nil
      end
    end

    protected

    def authors
      if people.nil?
        return []
      end
      people.select {|p|
        p.roles.include? "principalInvestigator" or p.roles.include? "author"
      }.map {|a| a.first_name+" "+a.last_name }.uniq
    end

    def owners
      if organisations.nil?
        return []
      end
      organisations.select {|o|
        o.roles.include? "owner"
      }
    end

    def links
      if self[:links].nil?
        return []
      end
      self[:links]
    end


  end
end