class Annotations2triannon::Revs

Attributes

db[RW]
open_annotations[RW]

Public Class Methods

new() click to toggle source
# File lib/annotations2triannon/revs.rb, line 11
def initialize
  @db = Annotations2triannon::RevsDb.new
end

Public Instance Methods

annotation2oa(annotation, user) click to toggle source

Mapping a REVS annotation into an Open Annotation

# File lib/annotations2triannon/revs.rb, line 105
def annotation2oa(annotation, user)

  begin
    # id --> part of URI for the annotation but, triannon POST will not accept an ID
    annotation_id = sprintf 'revs_annotation_%04d', annotation[:id]

    # convert the 'druid' into a PURL URI
    purl = 'http://purl.stanford.edu/' + annotation[:druid]
    purl_uri = RDF::URI.parse(purl)

    # Commentary on the annotation json field
    #
    # > for each row of the annotation table (in mysql), can the 'shapes' array
    # contain more than one entry?
    #
    # Shapes can currently only contain one entry, and are currently always
    # rectangular. This data structure and shape implementation is a result of our
    # use of the annotorious plugin, which is not guaranteed across projects or even
    # in Revs in the long term.
    #
    # > if so, this suggests that a 'text' annotation might refer to more than
    # one segment or region of a REVS image?
    #
    # Not at the moment. Not sure why it is an array. Perhaps so you can store
    # multiple annotations about the same image in one row instead of many, but we
    # do not do this for various reasons.
    #
    # > What is the 'context' field?  Would you rather use the 'context' field than
    # the 'src' field as the target of an open annotation (OA)?
    #
    # Context is just what annotorious uses for the src target.  Again, we just used
    # their vocabulary for ease of implementation.  If we were to use a different
    # back-end data store at some point, we could always transform into and out-of
    # their specific json structure as needed.
    #

    # convert the 'json' field
    annotation_json = JSON.parse(annotation[:json])
    revs_uri = RDF::URI.parse(annotation_json['context'])
    revs_img_src = annotation_json['src']
    revs_img_uri = RDF::URI.parse(revs_img_src)
    revs_fragments = []
    annotation_json['shapes'].each do |shape|
      # shapes are likely type 'rect'
      if shape['type'] == 'rect'
        # image annotation geometry
        # x is % across from top left
        # y is % down from top left
        # width is % across from x
        # height is % down from y
        x = shape['geometry']['x'] * 100
        y = shape['geometry']['y'] * 100
        w = shape['geometry']['width'] * 100
        h = shape['geometry']['height'] * 100
        # media fragment:  #xywh=percent:30.1,16.8,35.1,52.2
        fragment = sprintf '#xywh=percent:%04.1f,%04.1f,%04.1f,%04.1f', x, y, w, h
        revs_fragments << fragment
      end
    end
    revs_img_graph = RDF::Graph.new
    # revs_img_node = RDF::Node.new(revs_img_uri)
    # revs_img_graph.insert([revs_img_node, RDF.type, RDF::Vocab::OA.SpecificResource])
    # revs_img_graph.insert([revs_img_node, RDF::Vocab::OA.hasSource, revs_img_uri])
    revs_img_graph.insert([revs_uri, RDF.type, RDF::Vocab::OA.SpecificResource])
    revs_img_graph.insert([revs_uri, RDF::Vocab::OA.hasSource, revs_img_uri])
    revs_img_graph.insert([revs_img_uri, RDF.type, RDF::DCMIType.Image])
    # Note: it's most likely there is only one fragment in a REVS annotation.
    revs_fragment_graphs = []
    revs_fragments.each_with_index do |f, i|
      # img_uri = RDF::URI.parse(revs_img_src + fragment)
      # revs_img_uris << img_uri
      f_id = sprintf '%s_fragment_%02d', annotation_id, i
      f_uri = RDF::URI.parse(f_id)
      f_graph = RDF::Graph.new
      # avoid creation of blank nodes?
      # f_node = RDF::Node.new(f_uri)
      # f_graph.insert([f_node, RDF.type, RDF::Vocab::OA.FragmentSelector])
      # f_graph.insert([f_node, RDF::DC.conformsTo, RDF::MA.MediaFragment])
      # f_graph.insert([f_node, RDF.value, RDF::Literal.new(f)])
      # revs_img_graph.insert([revs_img_node, RDF::Vocab::OA.hasSelector, f_node])
      f_graph.insert([f_uri, RDF.type, RDF::Vocab::OA.FragmentSelector])
      f_graph.insert([f_uri, RDF::DC.conformsTo, RDF::MA.MediaFragment])
      f_graph.insert([f_uri, RDF.value, RDF::Literal.new(f)])
      revs_img_graph.insert([revs_uri, RDF::Vocab::OA.hasSelector, f_uri])
      revs_fragment_graphs << f_graph
    end

    # oa#hasBody
    # text --> value of cnt:chars property of a ContentAsText body of the annotation
    body_id = sprintf '%s_comment', annotation_id
    body_uri = RDF::URI.parse(body_id)
    # TODO: add a language tag?
    #body_text = RDF::Literal.new(annotation[:text], :language => :en)
    body_text = RDF::Literal.new(annotation[:text])
    body_graph = RDF::Graph.new
    # avoid creation of blank nodes?
    # body_node = RDF::Node.uuid
    # body_node = RDF::Node.new(annotation[:id])
    body_graph.insert([body_uri, RDF.type, RDF::Content.ContentAsText])
    body_graph.insert([body_uri, RDF.type, RDF::DCMIType.Text])
    body_graph.insert([body_uri, RDF::Content.chars, body_text])
    body_graph.insert([body_uri, RDF::Content.characterEncoding, 'UTF-8'])

    # oa#annotatedAt
    # created_at --> discard if updated_at is always present
    # updated_at --> oa:annotatedAt
    #
    # > annotation[:created_at].class
    # => Time
    # > annotation[:created_at].utc
    # => 2014-03-25 01:56:01 UTC
    # > annotation[:created_at].to_i  # unix time since epoch
    # => 1395712561
    # > [annotation[:created_at].utc, annotation[:updated_at].utc]
    # => [2014-03-25 01:56:01 UTC, 2014-03-25 01:56:14 UTC]
    #
    # create an RDF literal with datatype, see
    # http://rdf.greggkellogg.net/yard/RDF/Literal.html
    # > RDF::Literal.new(annotation[:created_at]).datatype
    # => #<RDF::Vocabulary::Term:0x3f86333d6ca8 URI:http://www.w3.org/2001/XMLSchema#time>
    # However, this automatic conversion discards the date!
    # > RDF::Literal.new(annotation[:created_at]).to_s
    # => "01:56:01Z"
    # So, an explicit datatype is required, i.e.:
    # > RDF::Literal.new(annotation[:created_at], :datatype => RDF::XSD.dateTime).to_s
    # => "2014-03-25T01:56:01Z"
    created_datetime = RDF::Literal.new(annotation[:created_at].utc, :datatype => RDF::XSD.dateTime)
    updated_datetime = RDF::Literal.new(annotation[:updated_at].utc, :datatype => RDF::XSD.dateTime)
    annotation_datetime = updated_datetime #if annotation[:created_at].utc < annotation[:updated_at].utc

    # Create and populate an Open Annotation instance.
    oa = Annotations2triannon::OpenAnnotation.new
    oa.insert_hasTarget(revs_uri)
    oa.insert_hasTarget(purl_uri)
    oa.insert_hasTarget(revs_img_uri)
    # oa.insert_hasTarget(revs_img_node)
    oa.graph.insert(revs_img_graph)
    revs_fragment_graphs.each {|g| oa.graph.insert(g) }
    # to enable the blank node, change body_graph to use body_node instead of body_uri
    # oa.insert_hasBody(body_node)
    oa.insert_hasBody(body_uri)
    oa.graph.insert(body_graph)
    oa.insert_annotatedAt(annotation_datetime)
    # to enable the blank node, change user_graph to use user_node instead of user_uri
    # oa.insert_annotatedBy(user[:node])
    oa.insert_annotatedBy(user[:uri])
    oa.graph.insert(user[:graph])
    oa
  rescue => e
    puts e.message
    # binding.pry
    raise e
  end
end
open_annotation(id=nil) click to toggle source
# File lib/annotations2triannon/revs.rb, line 36
def open_annotation(id=nil)
  # Not using a join, because some annotations may be anonymous.
  # join = r.db.annotations.join_table(:inner, r.db.users, :id=>:user_id)
  raise 'Invalid annotation ID' if id.nil?
  # find and convert an annotation by id
  annotation = @db.annotation(id)
  raise "No annotation with id => #{id}" if annotation.nil?
  user = user_rdf(annotation[:user_id])
  annotation2oa(annotation, user)
end
open_annotations_as_jsonld() click to toggle source
# File lib/annotations2triannon/revs.rb, line 31
def open_annotations_as_jsonld
  oa_list = open_annotations
  oa_list.collect {|a| a.as_jsonld }
end
user_rdf(user_id) click to toggle source
# File lib/annotations2triannon/revs.rb, line 47
def user_rdf(user_id)
  user = @db.user(user_id)
  user_id = sprintf 'revs_user_%04d', user[:id]
  user_uri = RDF::URI.parse(user_id)
  # avoid creation of blank nodes?
  # user_node = RDF::Node.new(user_uri)
  user_graph = RDF::Graph.new
  user_graph.insert([user_uri, RDF.type, RDF::SCHEMA.Person])
  # user_info = RDF::Literal.new("REVS user id: #{user[:id]}")
  # user_graph.insert([user_uri, RDF::SCHEMA.description, user_info])
  if user[:public]
    unless user[:first_name].nil? || user[:first_name].empty?
      # TODO: add language tags?
      #fn = RDF::Literal.new(user[:first_name], :language => :en)
      fn = RDF::Literal.new(user[:first_name])
      user_graph.insert([user_uri, RDF::SCHEMA.givenName, fn])
    end
    unless user[:last_name].nil? || user[:last_name].empty?
      #ln = RDF::Literal.new(user[:last_name], :language => :en)
      ln = RDF::Literal.new(user[:last_name])
      user_graph.insert([user_uri, RDF::SCHEMA.familyName, ln])
    end
    unless user[:bio].nil? || user[:bio].empty?
      #description = RDF::Literal.new(user[:bio], :language => :en)
      description = RDF::Literal.new(user[:bio])
      user_graph.insert([user_uri, RDF::SCHEMA.description, description])
    end
    unless user[:email].nil? || user[:email].empty?
      email = RDF::URI.parse('mailto:' + user[:email])
      user_graph.insert([user_uri, RDF::SCHEMA.email, email])
    end
    unless user[:url].nil? || user[:url].empty?
      url = user[:url]
      url = url.start_with?('http://') ? url : 'http://' + url
      url = RDF::URI.parse(url)
      user_graph.insert([user_uri, RDF::SCHEMA.url, url])
    end
    unless user[:twitter].nil? || user[:twitter].empty?
      url = user[:twitter]
      unless (url.start_with? 'https://twitter.com') || (url.start_with? 'http://twitter.com')
        url = 'https://twitter.com/' + url
      end
      url = RDF::URI.parse(url)
      user_graph.insert([user_uri, RDF::SCHEMA.url, url])
    end
  end
  {
      :uri => user_uri,
      # :node => user_node,
      :graph => user_graph
  }
end