ruby-avm-library 0.0.1
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.
- data/.autotest +5 -0
- data/.gitignore +5 -0
- data/.rspec +5 -0
- data/Gemfile +14 -0
- data/README.md +56 -0
- data/Rakefile +2 -0
- data/autotest/discover.rb +2 -0
- data/bin/avm2avm +7 -0
- data/lib/avm/cli.rb +18 -0
- data/lib/avm/contact.rb +40 -0
- data/lib/avm/controlled_vocabulary.rb +23 -0
- data/lib/avm/coordinate_frame.rb +10 -0
- data/lib/avm/coordinate_system_projection.rb +10 -0
- data/lib/avm/creator.rb +123 -0
- data/lib/avm/image.rb +355 -0
- data/lib/avm/image_quality.rb +10 -0
- data/lib/avm/image_type.rb +10 -0
- data/lib/avm/node.rb +28 -0
- data/lib/avm/observation.rb +76 -0
- data/lib/avm/spatial_quality.rb +10 -0
- data/lib/avm/xmp.rb +157 -0
- data/lib/ruby-avm-library.rb +7 -0
- data/lib/ruby-avm-library/version.rb +7 -0
- data/reek.watchr +12 -0
- data/ruby-avm-library.gemspec +27 -0
- data/spec/avm/cli_spec.rb +0 -0
- data/spec/avm/contact_spec.rb +93 -0
- data/spec/avm/creator_spec.rb +268 -0
- data/spec/avm/image_spec.rb +350 -0
- data/spec/avm/observation_spec.rb +191 -0
- data/spec/avm/xmp_spec.rb +154 -0
- data/spec/quick_fix_formatter.rb +26 -0
- data/spec/sample_files/creator/no_creator.xmp +14 -0
- data/spec/sample_files/creator/one_creator.xmp +28 -0
- data/spec/sample_files/creator/two_creators.xmp +26 -0
- data/spec/sample_files/image/both.xmp +101 -0
- data/spec/sample_files/image/light_years.xmp +96 -0
- data/spec/sample_files/image/nothing.xmp +18 -0
- data/spec/sample_files/image/redshift.xmp +101 -0
- data/spec/sample_files/image/single_value_light_years.xmp +96 -0
- data/spec/sample_files/observation/none.xmp +5 -0
- data/spec/sample_files/observation/one.xmp +17 -0
- data/spec/sample_files/observation/two.xmp +17 -0
- data/spec/spec_helper.rb +3 -0
- metadata +184 -0
data/.autotest
ADDED
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
## The Ruby AVM Library
|
2
|
+
|
3
|
+
The Astronomy Visualization Metadata (AVM) standard is an extension of the Adobe XMP format. This
|
4
|
+
extension adds information to an astronomical image that describes the scientific data and methods
|
5
|
+
of collection that went in to producing the image. This Ruby library assists in reading the metadata from
|
6
|
+
XMP documents and writing out AVM data as a new XMP file.
|
7
|
+
|
8
|
+
## Installing the library
|
9
|
+
|
10
|
+
### From Bundler
|
11
|
+
|
12
|
+
In your Gemfile:
|
13
|
+
|
14
|
+
gem 'ruby-avm-library'
|
15
|
+
|
16
|
+
To use the current development version:
|
17
|
+
|
18
|
+
gem 'ruby-avm-library', :git => 'git://github.com/johnbintz/ruby-avm-library.git'
|
19
|
+
|
20
|
+
### From RubyGems
|
21
|
+
|
22
|
+
gem install ruby-avm-library
|
23
|
+
|
24
|
+
## Basic usage
|
25
|
+
|
26
|
+
### Reading an XMP file
|
27
|
+
|
28
|
+
require 'avm/image'
|
29
|
+
|
30
|
+
image = AVM::Image.from_xml(File.read('my-file.xmp'))
|
31
|
+
|
32
|
+
puts image.title #=> "The title of the image"
|
33
|
+
|
34
|
+
### Writing XML data
|
35
|
+
|
36
|
+
image.to_xml #=> <xmp data in xml format />
|
37
|
+
|
38
|
+
### Creating an Image from scratch
|
39
|
+
|
40
|
+
image = AVM::Image.new
|
41
|
+
image.title = "The title of the image"
|
42
|
+
|
43
|
+
observation = image.create_observation(:instrument => 'HST', :color_assignment => 'Green')
|
44
|
+
contact = image.creator.create_contact(:name => 'John Bintz')
|
45
|
+
|
46
|
+
## Command line tool
|
47
|
+
|
48
|
+
`avm2avm` currently performs one function: take an XMP file from stdin and pretty print the image as a Hash:
|
49
|
+
|
50
|
+
avm2avm < my-file.xmp
|
51
|
+
|
52
|
+
## More resources
|
53
|
+
|
54
|
+
* RDoc: [http://rdoc.info/github/johnbintz/ruby-avm-library/frames](http://rdoc.info/github/johnbintz/ruby-avm-library/frames)
|
55
|
+
* AVM Standard: [http://www.virtualastronomy.org/avm_metadata.php](http://www.virtualastronomy.org/avm_metadata.php)
|
56
|
+
|
data/Rakefile
ADDED
data/bin/avm2avm
ADDED
data/lib/avm/cli.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'avm/image'
|
3
|
+
require 'pp'
|
4
|
+
|
5
|
+
module AVM
|
6
|
+
# The CLI interface
|
7
|
+
class CLI < ::Thor
|
8
|
+
default_task :convert
|
9
|
+
|
10
|
+
desc 'convert', "Convert a file from one format to another"
|
11
|
+
def convert
|
12
|
+
data = $stdin.read
|
13
|
+
|
14
|
+
pp AVM::Image.from_xml(data).to_h
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
data/lib/avm/contact.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
module AVM
|
2
|
+
# A contributor to an image
|
3
|
+
class Contact
|
4
|
+
FIELD_MAP = {
|
5
|
+
:zip => :postal_code,
|
6
|
+
:state => :state_province,
|
7
|
+
:province => :state_province
|
8
|
+
}
|
9
|
+
|
10
|
+
HASH_FIELDS = [ :name, :email, :telephone, :address, :city, :state, :postal_code, :country ]
|
11
|
+
|
12
|
+
attr_accessor :primary
|
13
|
+
|
14
|
+
def initialize(info)
|
15
|
+
@info = Hash[info.collect { |key, value| [ FIELD_MAP[key] || key, value ] }]
|
16
|
+
@primary = false
|
17
|
+
end
|
18
|
+
|
19
|
+
def method_missing(key)
|
20
|
+
@info[FIELD_MAP[key] || key]
|
21
|
+
end
|
22
|
+
|
23
|
+
def <=>(other)
|
24
|
+
return -1 if primary?
|
25
|
+
self.name <=> other.name
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_creator_list_element
|
29
|
+
%{<rdf:li>#{self.name}</rdf:li>}
|
30
|
+
end
|
31
|
+
|
32
|
+
def primary?
|
33
|
+
@primary
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_h
|
37
|
+
Hash[HASH_FIELDS.collect { |key| [ key, send(key) ] } ]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module AVM
|
2
|
+
# Build a ControlledVocabulary set of classes for use with CV fields
|
3
|
+
module ControlledVocabulary
|
4
|
+
class << self
|
5
|
+
def included(klass)
|
6
|
+
klass::TERMS.each do |type|
|
7
|
+
new_klass = Class.new do
|
8
|
+
def to_s
|
9
|
+
self.class.to_s.split('::').last
|
10
|
+
end
|
11
|
+
|
12
|
+
def ==(other)
|
13
|
+
self.to_s == other.to_s
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
klass.const_set(type.to_sym, new_klass)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
data/lib/avm/creator.rb
ADDED
@@ -0,0 +1,123 @@
|
|
1
|
+
require 'avm/contact'
|
2
|
+
require 'nokogiri'
|
3
|
+
|
4
|
+
module AVM
|
5
|
+
# A container for Contacts (contributors to an image)
|
6
|
+
class Creator
|
7
|
+
attr_reader :contacts, :image
|
8
|
+
|
9
|
+
IPTC_CORE_FIELDS = [ :address, :city, :state, :zip, :country ]
|
10
|
+
PRIMARY_CONTACT_FIELDS = IPTC_CORE_FIELDS + [ :province, :postal_code ]
|
11
|
+
IPTC_MULTI_FIELD_MAP = [ [ :telephone, 'CiTelWork' ], [ :email, 'CiEmailWork' ] ]
|
12
|
+
IPTC_CORE_FIELD_ELEMENT_NAMES = %w{CiAdrExtadr CiAdrCity CiAdrRegion CiAdrPcode CiAdrCtry}
|
13
|
+
IPTC_CORE_FIELDS_AND_NAMES = IPTC_CORE_FIELDS.zip(IPTC_CORE_FIELD_ELEMENT_NAMES)
|
14
|
+
|
15
|
+
def initialize(image, given_contacts = [])
|
16
|
+
@options = {}
|
17
|
+
@contacts = given_contacts
|
18
|
+
@image = image
|
19
|
+
end
|
20
|
+
|
21
|
+
def merge!(hash)
|
22
|
+
@options.merge!(hash)
|
23
|
+
end
|
24
|
+
|
25
|
+
def length
|
26
|
+
contacts.length
|
27
|
+
end
|
28
|
+
|
29
|
+
def [](which)
|
30
|
+
contacts[which]
|
31
|
+
end
|
32
|
+
|
33
|
+
def to_a
|
34
|
+
contacts.sort.collect(&:to_h)
|
35
|
+
end
|
36
|
+
|
37
|
+
def method_missing(key, *opts)
|
38
|
+
if (key_to_s = key.to_s)[-1..-1] == '='
|
39
|
+
@options[key_to_s[0..-2].to_sym] = opts.first
|
40
|
+
else
|
41
|
+
if PRIMARY_CONTACT_FIELDS.include?(key)
|
42
|
+
primary_contact_field key
|
43
|
+
else
|
44
|
+
@options[key]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def add_to_document(document)
|
50
|
+
document.get_refs do |refs|
|
51
|
+
creator = refs[:dublin_core].add_child('<dc:creator><rdf:Seq></rdf:Seq></dc:creator>')
|
52
|
+
|
53
|
+
list = creator.at_xpath('.//rdf:Seq')
|
54
|
+
contact_info = refs[:iptc].add_child('<Iptc4xmpCore:CreatorContactInfo rdf:parseType="Resource" />').first
|
55
|
+
|
56
|
+
contacts.sort.each do |contact|
|
57
|
+
list.add_child(contact.to_creator_list_element)
|
58
|
+
end
|
59
|
+
|
60
|
+
if primary_contact
|
61
|
+
IPTC_MULTI_FIELD_MAP.each do |key, element_name|
|
62
|
+
contact_info.add_child "<Iptc4xmpCore:#{element_name}>#{contacts.sort.collect(&key).join(',')}</Iptc4xmpCore:#{element_name}>"
|
63
|
+
end
|
64
|
+
|
65
|
+
iptc_namespace = document.doc.root.namespace_scopes.find { |ns| ns.prefix == 'Iptc4xmpCore' }
|
66
|
+
|
67
|
+
IPTC_CORE_FIELDS_AND_NAMES.each do |key, element_name|
|
68
|
+
node = contact_info.document.create_element(element_name, primary_contact.send(key))
|
69
|
+
node.namespace = iptc_namespace
|
70
|
+
contact_info.add_child node
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def from_xml(image, document)
|
77
|
+
contacts = []
|
78
|
+
document.get_refs do |refs|
|
79
|
+
refs[:dublin_core].search('.//dc:creator//rdf:li').each do |name|
|
80
|
+
contacts << { :name => name.text.strip }
|
81
|
+
end
|
82
|
+
|
83
|
+
IPTC_MULTI_FIELD_MAP.each do |key, element_name|
|
84
|
+
if node = refs[:iptc].at_xpath(".//Iptc4xmpCore:#{element_name}")
|
85
|
+
node.text.split(',').collect(&:strip).each_with_index do |value, index|
|
86
|
+
contacts[index][key] = value
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
IPTC_CORE_FIELDS_AND_NAMES.each do |key, element_name|
|
92
|
+
if node = refs[:iptc].at_xpath("//Iptc4xmpCore:#{element_name}")
|
93
|
+
contacts.each { |contact| contact[key] = node.text.strip }
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
if !(@contacts = contacts.collect { |contact| Contact.new(contact) }).empty?
|
99
|
+
@contacts.first.primary = true
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def primary_contact
|
104
|
+
@contacts.find(&:primary) || @contacts.sort.first
|
105
|
+
end
|
106
|
+
|
107
|
+
def create_contact(info)
|
108
|
+
contact = Contact.new(info)
|
109
|
+
contacts << contact
|
110
|
+
contact
|
111
|
+
end
|
112
|
+
|
113
|
+
private
|
114
|
+
def primary_contact_field(field)
|
115
|
+
if contact = primary_contact
|
116
|
+
contact.send(field)
|
117
|
+
else
|
118
|
+
nil
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
data/lib/avm/image.rb
ADDED
@@ -0,0 +1,355 @@
|
|
1
|
+
require 'avm/creator'
|
2
|
+
require 'avm/xmp'
|
3
|
+
require 'avm/image_type'
|
4
|
+
require 'avm/image_quality'
|
5
|
+
require 'avm/spatial_quality'
|
6
|
+
require 'avm/coordinate_system_projection'
|
7
|
+
require 'avm/coordinate_frame'
|
8
|
+
require 'avm/observation'
|
9
|
+
|
10
|
+
module AVM
|
11
|
+
# A single image, which has Observations, Contacts, and other metadata
|
12
|
+
class Image
|
13
|
+
DUBLIN_CORE_FIELDS = [ :title, :description ]
|
14
|
+
|
15
|
+
PHOTOSHOP_SINGLE_FIELDS = [
|
16
|
+
'Headline',
|
17
|
+
'DateCreated',
|
18
|
+
'Credit'
|
19
|
+
]
|
20
|
+
|
21
|
+
PHOTOSHOP_SINGLE_METHODS = [
|
22
|
+
:headline,
|
23
|
+
:date,
|
24
|
+
:credit
|
25
|
+
]
|
26
|
+
|
27
|
+
PHOTOSHOP_SINGLES_MESSAGES = [
|
28
|
+
:headline,
|
29
|
+
:string_date,
|
30
|
+
:credit
|
31
|
+
]
|
32
|
+
|
33
|
+
PHOTOSHOP_SINGLES_FOR_METHODS = PHOTOSHOP_SINGLE_FIELDS.zip(PHOTOSHOP_SINGLE_METHODS)
|
34
|
+
PHOTOSHOP_SINGLES_FOR_MESSAGES = PHOTOSHOP_SINGLE_FIELDS.zip(PHOTOSHOP_SINGLES_MESSAGES)
|
35
|
+
|
36
|
+
AVM_SINGLE_FIELDS = [
|
37
|
+
'Distance.Notes',
|
38
|
+
'Spectral.Notes',
|
39
|
+
'ReferenceURL',
|
40
|
+
'ID',
|
41
|
+
'Type',
|
42
|
+
'Image.ProductQuality',
|
43
|
+
'Spatial.Equinox',
|
44
|
+
'Spatial.Rotation',
|
45
|
+
'Spatial.Notes',
|
46
|
+
'Spatial.FITSheader',
|
47
|
+
'Spatial.Quality',
|
48
|
+
'Spatial.CoordsystemProjection',
|
49
|
+
'Spatial.CDMatrix',
|
50
|
+
'Spatial.Scale',
|
51
|
+
'Spatial.ReferencePixel',
|
52
|
+
'Spatial.ReferenceDimension',
|
53
|
+
'Spatial.ReferenceValue',
|
54
|
+
'Spatial.Equinox',
|
55
|
+
'Spatial.CoordinateFrame',
|
56
|
+
'Publisher',
|
57
|
+
'PublisherID',
|
58
|
+
'ResourceID',
|
59
|
+
'ResourceURL',
|
60
|
+
'RelatedResources',
|
61
|
+
'MetadataDate',
|
62
|
+
'MetadataVersion',
|
63
|
+
'Subject.Category',
|
64
|
+
]
|
65
|
+
|
66
|
+
AVM_SINGLE_METHODS = [
|
67
|
+
:distance_notes,
|
68
|
+
:spectral_notes,
|
69
|
+
:reference_url,
|
70
|
+
:id,
|
71
|
+
:type,
|
72
|
+
:quality,
|
73
|
+
:spatial_equinox,
|
74
|
+
:spatial_rotation,
|
75
|
+
:spatial_notes,
|
76
|
+
:fits_header,
|
77
|
+
:spatial_quality,
|
78
|
+
:coordinate_system_projection,
|
79
|
+
:spatial_cd_matrix,
|
80
|
+
:spatial_scale,
|
81
|
+
:reference_pixel,
|
82
|
+
:reference_dimension,
|
83
|
+
:reference_value,
|
84
|
+
:equinox,
|
85
|
+
:coordinate_frame,
|
86
|
+
:publisher,
|
87
|
+
:publisher_id,
|
88
|
+
:resource_id,
|
89
|
+
:resource_url,
|
90
|
+
:related_resources,
|
91
|
+
:metadata_date,
|
92
|
+
:metadata_version,
|
93
|
+
:categories
|
94
|
+
]
|
95
|
+
|
96
|
+
AVM_SINGLE_MESSAGES = [
|
97
|
+
:distance_notes,
|
98
|
+
:spectral_notes,
|
99
|
+
:reference_url,
|
100
|
+
:id,
|
101
|
+
:image_type,
|
102
|
+
:image_quality,
|
103
|
+
:spatial_equinox,
|
104
|
+
:spatial_rotation,
|
105
|
+
:spatial_notes,
|
106
|
+
:fits_header,
|
107
|
+
:spatial_quality,
|
108
|
+
:coordinate_system_projection,
|
109
|
+
:spatial_cd_matrix,
|
110
|
+
:spatial_scale,
|
111
|
+
:reference_pixel,
|
112
|
+
:reference_dimension,
|
113
|
+
:reference_value,
|
114
|
+
:equinox,
|
115
|
+
:coordinate_frame,
|
116
|
+
:publisher,
|
117
|
+
:publisher_id,
|
118
|
+
:resource_id,
|
119
|
+
:resource_url,
|
120
|
+
:related_resources,
|
121
|
+
:string_metadata_date,
|
122
|
+
:metadata_version,
|
123
|
+
:categories
|
124
|
+
]
|
125
|
+
|
126
|
+
AVM_SINGLES = AVM_SINGLE_FIELDS.zip(AVM_SINGLE_METHODS)
|
127
|
+
AVM_SINGLES_FOR_MESSAGES = AVM_SINGLE_FIELDS.zip(AVM_SINGLE_MESSAGES)
|
128
|
+
|
129
|
+
AVM_TO_FLOAT = [
|
130
|
+
:spatial_rotation,
|
131
|
+
:spatial_cd_matrix,
|
132
|
+
:spatial_scale,
|
133
|
+
:reference_pixel,
|
134
|
+
:reference_dimension,
|
135
|
+
:reference_value
|
136
|
+
]
|
137
|
+
|
138
|
+
HASH_FIELDS = [ :title, :headline, :description, :distance_notes,
|
139
|
+
:spectral_notes, :reference_url, :credit, :date,
|
140
|
+
:id, :image_type, :image_quality, :coordinate_frame,
|
141
|
+
:equinox, :reference_value, :reference_dimension, :reference_pixel,
|
142
|
+
:spatial_scale, :spatial_rotation, :coordinate_system_projection, :spatial_quality,
|
143
|
+
:spatial_notes, :fits_header, :spatial_cd_matrix, :distance,
|
144
|
+
:publisher, :publisher_id, :resource_id, :resource_url,
|
145
|
+
:related_resources, :metadata_date, :metadata_version, :subject_names, :categories
|
146
|
+
]
|
147
|
+
|
148
|
+
attr_reader :creator, :observations
|
149
|
+
|
150
|
+
def initialize(options = {})
|
151
|
+
@creator = AVM::Creator.new(self)
|
152
|
+
@options = options
|
153
|
+
|
154
|
+
AVM_TO_FLOAT.each do |field|
|
155
|
+
@options[field] = case (value = @options[field])
|
156
|
+
when Array
|
157
|
+
value.collect(&:to_f)
|
158
|
+
else
|
159
|
+
value ? value.to_f : nil
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
|
164
|
+
@observations = []
|
165
|
+
end
|
166
|
+
|
167
|
+
def valid?
|
168
|
+
self.title && self.credit
|
169
|
+
end
|
170
|
+
|
171
|
+
def create_observation(options)
|
172
|
+
observation = Observation.new(self, options)
|
173
|
+
@observations << observation
|
174
|
+
observation
|
175
|
+
end
|
176
|
+
|
177
|
+
def to_xml
|
178
|
+
document = AVM::XMP.new
|
179
|
+
|
180
|
+
creator.add_to_document(document)
|
181
|
+
Observation.add_to_document(document, observations)
|
182
|
+
|
183
|
+
document.get_refs do |refs|
|
184
|
+
DUBLIN_CORE_FIELDS.each do |field|
|
185
|
+
refs[:dublin_core].add_child(%{<dc:#{field}>#{alt_li_tag(send(field))}</dc:#{field}>})
|
186
|
+
end
|
187
|
+
|
188
|
+
PHOTOSHOP_SINGLES_FOR_MESSAGES.each do |tag, message|
|
189
|
+
refs[:photoshop].add_child(%{<photoshop:#{tag}>#{send(message)}</photoshop:#{tag}>})
|
190
|
+
end
|
191
|
+
|
192
|
+
AVM_SINGLES_FOR_MESSAGES.each do |tag, message|
|
193
|
+
if value = send(message)
|
194
|
+
case value
|
195
|
+
when Array
|
196
|
+
container_tag = (message == :related_resources) ? 'Bag' : 'Seq'
|
197
|
+
value = "<rdf:#{container_tag}>" + value.collect { |v| "<rdf:li>#{v.to_s}</rdf:li>" }.join + "</rdf:#{container_tag}>"
|
198
|
+
else
|
199
|
+
value = value.to_s
|
200
|
+
end
|
201
|
+
|
202
|
+
refs[:avm].add_child(%{<avm:#{tag}>#{value}</avm:#{tag}>})
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
distance_nodes = []
|
207
|
+
distance_nodes << rdf_li(light_years) if light_years
|
208
|
+
if redshift
|
209
|
+
distance_nodes << rdf_li('-') if distance_nodes.empty?
|
210
|
+
distance_nodes << rdf_li(redshift)
|
211
|
+
end
|
212
|
+
|
213
|
+
if !distance_nodes.empty?
|
214
|
+
refs[:avm].add_child(%{<avm:Distance><rdf:Seq>#{distance_nodes.join}</rdf:Seq></avm:Distance>})
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
document.doc
|
219
|
+
end
|
220
|
+
|
221
|
+
def id
|
222
|
+
@options[:id]
|
223
|
+
end
|
224
|
+
|
225
|
+
def image_type
|
226
|
+
cv_class_instance_for(AVM::ImageType, :type)
|
227
|
+
end
|
228
|
+
|
229
|
+
def image_quality
|
230
|
+
cv_class_instance_for(AVM::ImageQuality, :quality)
|
231
|
+
end
|
232
|
+
|
233
|
+
def spatial_quality
|
234
|
+
cv_class_instance_for(AVM::SpatialQuality, :spatial_quality)
|
235
|
+
end
|
236
|
+
|
237
|
+
def coordinate_frame
|
238
|
+
cv_class_instance_for(AVM::CoordinateFrame, :coordinate_frame)
|
239
|
+
end
|
240
|
+
|
241
|
+
def coordinate_system_projection
|
242
|
+
cv_class_instance_for(AVM::CoordinateSystemProjection, :coordinate_system_projection)
|
243
|
+
end
|
244
|
+
|
245
|
+
def date
|
246
|
+
date_or_nil(:date)
|
247
|
+
end
|
248
|
+
|
249
|
+
def metadata_date
|
250
|
+
date_or_nil(:metadata_date)
|
251
|
+
end
|
252
|
+
|
253
|
+
def string_date
|
254
|
+
string_date_or_nil(:date)
|
255
|
+
end
|
256
|
+
|
257
|
+
def string_metadata_date
|
258
|
+
string_date_or_nil(:metadata_date)
|
259
|
+
end
|
260
|
+
|
261
|
+
def distance
|
262
|
+
[ light_years, redshift ]
|
263
|
+
end
|
264
|
+
|
265
|
+
def self.from_xml(string)
|
266
|
+
document = AVM::XMP.from_string(string)
|
267
|
+
|
268
|
+
options = {}
|
269
|
+
|
270
|
+
document.get_refs do |refs|
|
271
|
+
DUBLIN_CORE_FIELDS.each do |field|
|
272
|
+
if node = refs[:dublin_core].at_xpath(".//dc:#{field}//rdf:li[1]")
|
273
|
+
options[field] = node.text
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
if node = refs[:dublin_core].at_xpath(".//dc:subject/rdf:Bag")
|
278
|
+
options[:subject_names] = node.search('./rdf:li').collect(&:text)
|
279
|
+
end
|
280
|
+
|
281
|
+
AVM_SINGLES.each do |tag, field|
|
282
|
+
if node = refs[:avm].at_xpath("./avm:#{tag}")
|
283
|
+
if field == :categories
|
284
|
+
options[field] = node.text.split(";").collect(&:strip)
|
285
|
+
else
|
286
|
+
if !(list_items = node.search('.//rdf:li')).empty?
|
287
|
+
options[field] = list_items.collect(&:text)
|
288
|
+
else
|
289
|
+
options[field] = node.text
|
290
|
+
end
|
291
|
+
end
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
PHOTOSHOP_SINGLES_FOR_METHODS.each do |tag, field|
|
296
|
+
if node = refs[:photoshop].at_xpath("./photoshop:#{tag}")
|
297
|
+
options[field] = node.text
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
if node = refs[:avm].at_xpath('./avm:Distance')
|
302
|
+
list_values = node.search('.//rdf:li').collect { |li| li.text }
|
303
|
+
|
304
|
+
case list_values.length
|
305
|
+
when 0
|
306
|
+
options[:light_years] = node.text
|
307
|
+
when 1
|
308
|
+
options[:light_years] = list_values.first
|
309
|
+
when 2
|
310
|
+
options[:light_years] = (list_values.first == '-') ? nil : list_values.first
|
311
|
+
options[:redshift] = list_values.last
|
312
|
+
end
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
image = new(options)
|
317
|
+
image.creator.from_xml(self, document)
|
318
|
+
Observation.from_xml(image, document)
|
319
|
+
image
|
320
|
+
end
|
321
|
+
|
322
|
+
def to_h
|
323
|
+
hash = Hash[HASH_FIELDS.collect { |key| [ key, send(key) ] }]
|
324
|
+
hash[:creator] = creator.to_a
|
325
|
+
hash[:observations] = observations.collect(&:to_h)
|
326
|
+
hash
|
327
|
+
end
|
328
|
+
|
329
|
+
def method_missing(method)
|
330
|
+
@options[method]
|
331
|
+
end
|
332
|
+
|
333
|
+
private
|
334
|
+
def date_or_nil(field)
|
335
|
+
(Time.parse(@options[field]) rescue nil)
|
336
|
+
end
|
337
|
+
|
338
|
+
def string_date_or_nil(field)
|
339
|
+
(value = send(field)) ? value.strftime('%Y-%m-%d') : nil
|
340
|
+
end
|
341
|
+
|
342
|
+
def alt_li_tag(text)
|
343
|
+
%{<rdf:Alt><rdf:li xml:lang="x-default">#{text}</rdf:li></rdf:Alt>}
|
344
|
+
end
|
345
|
+
|
346
|
+
def rdf_li(text)
|
347
|
+
%{<rdf:li>#{text}</rdf:li>}
|
348
|
+
end
|
349
|
+
|
350
|
+
def cv_class_instance_for(mod, field)
|
351
|
+
(mod.const_get(@options[field].to_sym).new rescue nil)
|
352
|
+
end
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|