stac 0.2.0 → 0.3.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.
- checksums.yaml +4 -4
- data/.rubocop.yml +3 -0
- data/CHANGELOG.md +10 -0
- data/GETTING_STARTED.md +384 -0
- data/Gemfile.lock +48 -37
- data/README.md +12 -59
- data/Rakefile +62 -4
- data/Steepfile +2 -0
- data/lib/stac/asset.rb +3 -1
- data/lib/stac/catalog.rb +78 -8
- data/lib/stac/collection.rb +43 -9
- data/lib/stac/common_metadata.rb +1 -1
- data/lib/stac/errors.rb +2 -5
- data/lib/stac/extension.rb +34 -0
- data/lib/stac/extensions/electro_optical.rb +67 -0
- data/lib/stac/extensions/projection.rb +42 -0
- data/lib/stac/extensions/scientific_citation.rb +84 -0
- data/lib/stac/extensions/view_geometry.rb +38 -0
- data/lib/stac/extent.rb +39 -31
- data/lib/stac/file_writer.rb +31 -0
- data/lib/stac/hash_like.rb +74 -0
- data/lib/stac/item.rb +56 -22
- data/lib/stac/link.rb +51 -9
- data/lib/stac/object_resolver.rb +4 -4
- data/lib/stac/properties.rb +3 -1
- data/lib/stac/provider.rb +5 -1
- data/lib/stac/simple_http_client.rb +3 -0
- data/lib/stac/stac_object.rb +138 -36
- data/lib/stac/version.rb +1 -1
- data/lib/stac.rb +12 -1
- data/sig/stac/asset.rbs +1 -3
- data/sig/stac/catalog.rbs +28 -5
- data/sig/stac/collection.rbs +13 -5
- data/sig/stac/common_metadata.rbs +2 -2
- data/sig/stac/errors.rbs +1 -4
- data/sig/stac/extension.rbs +12 -0
- data/sig/stac/extensions/electro_optical.rbs +40 -0
- data/sig/stac/extensions/projection.rbs +32 -0
- data/sig/stac/extensions/scientific_citation.rbs +38 -0
- data/sig/stac/extensions/view_geometry.rbs +22 -0
- data/sig/stac/extent.rbs +13 -16
- data/sig/stac/file_writer.rbs +13 -0
- data/sig/stac/hash_like.rbs +13 -0
- data/sig/stac/item.rbs +16 -7
- data/sig/stac/link.rbs +20 -12
- data/sig/stac/object_resolver.rbs +2 -2
- data/sig/stac/properties.rbs +1 -3
- data/sig/stac/provider.rbs +2 -3
- data/sig/stac/simple_http_client.rbs +3 -0
- data/sig/stac/stac_object.rbs +33 -10
- data/stac.gemspec +1 -1
- metadata +18 -3
data/lib/stac/extent.rb
CHANGED
@@ -1,18 +1,50 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'hash_like'
|
4
|
+
|
3
5
|
module STAC
|
4
|
-
# Represents \STAC extent object, which describes the spatio-temporal extents of a
|
6
|
+
# Represents \STAC extent object, which describes the spatio-temporal extents of a collection.
|
5
7
|
#
|
6
8
|
# Specification: https://github.com/radiantearth/stac-spec/blob/master/collection-spec/collection-spec.md#extent-object
|
7
9
|
class Extent
|
8
|
-
|
10
|
+
include HashLike
|
11
|
+
|
12
|
+
class << self
|
13
|
+
# Deserializes an Extent from a Hash.
|
14
|
+
def from_hash(hash)
|
15
|
+
transformed = hash.transform_keys(&:to_sym)
|
16
|
+
transformed[:spatial] = Spatial.from_hash(transformed.fetch(:spatial))
|
17
|
+
transformed[:temporal] = Temporal.from_hash(transformed.fetch(:temporal))
|
18
|
+
new(**transformed)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
attr_accessor :spatial, :temporal
|
23
|
+
|
24
|
+
def initialize(spatial:, temporal:, **extra)
|
25
|
+
@spatial = spatial
|
26
|
+
@temporal = temporal
|
27
|
+
@extra = extra.transform_keys(&:to_s)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Serializes self to a Hash.
|
31
|
+
def to_h
|
32
|
+
{
|
33
|
+
'spatial' => spatial.to_h,
|
34
|
+
'temporal' => temporal.to_h,
|
35
|
+
}.merge(extra)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Describes the spatial extents of a collection
|
9
39
|
class Spatial
|
40
|
+
include HashLike
|
41
|
+
|
10
42
|
# Deserializes a Spatial from a Hash.
|
11
43
|
def self.from_hash(hash)
|
12
44
|
new(**hash.transform_keys(&:to_sym))
|
13
45
|
end
|
14
46
|
|
15
|
-
attr_accessor :bbox
|
47
|
+
attr_accessor :bbox
|
16
48
|
|
17
49
|
def initialize(bbox:, **extra)
|
18
50
|
@bbox = bbox
|
@@ -27,14 +59,16 @@ module STAC
|
|
27
59
|
end
|
28
60
|
end
|
29
61
|
|
30
|
-
# Describes the temporal extents of a
|
62
|
+
# Describes the temporal extents of a collection.
|
31
63
|
class Temporal
|
64
|
+
include HashLike
|
65
|
+
|
32
66
|
# Deserializes a Temporal from a Hash.
|
33
67
|
def self.from_hash(hash)
|
34
68
|
new(**hash.transform_keys(&:to_sym))
|
35
69
|
end
|
36
70
|
|
37
|
-
attr_accessor :interval
|
71
|
+
attr_accessor :interval
|
38
72
|
|
39
73
|
def initialize(interval:, **extra)
|
40
74
|
@interval = interval
|
@@ -48,31 +82,5 @@ module STAC
|
|
48
82
|
}.merge(extra)
|
49
83
|
end
|
50
84
|
end
|
51
|
-
|
52
|
-
class << self
|
53
|
-
# Deserializes an Extent from a Hash.
|
54
|
-
def from_hash(hash)
|
55
|
-
transformed = hash.transform_keys(&:to_sym)
|
56
|
-
transformed[:spatial] = Spatial.from_hash(transformed.fetch(:spatial))
|
57
|
-
transformed[:temporal] = Temporal.from_hash(transformed.fetch(:temporal))
|
58
|
-
new(**transformed)
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
attr_accessor :spatial, :temporal, :extra
|
63
|
-
|
64
|
-
def initialize(spatial:, temporal:, **extra)
|
65
|
-
@spatial = spatial
|
66
|
-
@temporal = temporal
|
67
|
-
@extra = extra.transform_keys(&:to_s)
|
68
|
-
end
|
69
|
-
|
70
|
-
# Serializes self to a Hash.
|
71
|
-
def to_h
|
72
|
-
{
|
73
|
-
'spatial' => spatial.to_h,
|
74
|
-
'temporal' => temporal.to_h,
|
75
|
-
}.merge(extra)
|
76
|
-
end
|
77
85
|
end
|
78
86
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'pathname'
|
4
|
+
require 'json'
|
5
|
+
require 'uri'
|
6
|
+
require_relative 'errors'
|
7
|
+
|
8
|
+
module STAC
|
9
|
+
# Class to write a hash as JSON on a file.
|
10
|
+
class FileWriter
|
11
|
+
def initialize(hash_to_json: ->(hash) { JSON.generate(hash) })
|
12
|
+
@hash_to_json = hash_to_json
|
13
|
+
end
|
14
|
+
|
15
|
+
# Creates a file on `dest` with the given hash as JSON.
|
16
|
+
def write(hash, dest:)
|
17
|
+
dest_uri = URI.parse(dest)
|
18
|
+
path = if dest_uri.relative?
|
19
|
+
dest
|
20
|
+
elsif dest_uri.is_a?(URI::File)
|
21
|
+
dest_uri.path.to_s
|
22
|
+
else
|
23
|
+
raise NotSupportedURISchemeError, "not supported URI scheme: #{dest}"
|
24
|
+
end
|
25
|
+
|
26
|
+
pathname = Pathname(path)
|
27
|
+
pathname.dirname.mkpath
|
28
|
+
pathname.write(@hash_to_json.call(hash))
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module STAC
|
6
|
+
# Enables included class to behave like Hash.
|
7
|
+
module HashLike
|
8
|
+
# Extra fields that do not belong to the STAC core specification.
|
9
|
+
attr_reader :extra
|
10
|
+
|
11
|
+
# When there is an attribute with the given name, returns the attribute value.
|
12
|
+
# Otherwise, calls `extra [key]`.
|
13
|
+
def [](key)
|
14
|
+
if respond_to?(key) && method(key).arity.zero?
|
15
|
+
public_send(key)
|
16
|
+
else
|
17
|
+
extra[key.to_s]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# When there is an attribute writer with the given name, assigns the value to the attribute.
|
22
|
+
# Otherwise, adds the given key-value pair to `extra` hash.
|
23
|
+
def []=(key, value)
|
24
|
+
method = "#{key}="
|
25
|
+
if respond_to?(method)
|
26
|
+
public_send(method, value)
|
27
|
+
else
|
28
|
+
extra[key.to_s] = value
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Sets the attributes (like ActiveModel::AttributeAssignment#assign_attributes)
|
33
|
+
# or merges the args into `extra` hash (like Hash#update).
|
34
|
+
def update(**options)
|
35
|
+
options.each do |key, value|
|
36
|
+
self[key] = value
|
37
|
+
end
|
38
|
+
self
|
39
|
+
end
|
40
|
+
|
41
|
+
def to_hash # :nodoc:
|
42
|
+
to_h
|
43
|
+
end
|
44
|
+
|
45
|
+
# Serializes self to a Hash.
|
46
|
+
def to_h
|
47
|
+
extra
|
48
|
+
end
|
49
|
+
|
50
|
+
# Serializes self to a JSON string.
|
51
|
+
def to_json(...)
|
52
|
+
to_h.to_json(...)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Returns `true` if all of the followings are true:
|
56
|
+
# - the given object is an instance of tha same class
|
57
|
+
# - `self.to_hash == other.to_hash`
|
58
|
+
#
|
59
|
+
# Otherwise, returns `false`.
|
60
|
+
def ==(other)
|
61
|
+
other.instance_of?(self.class) && to_hash == other.to_hash
|
62
|
+
end
|
63
|
+
|
64
|
+
# Returns a copy of self by serializes self to a JSON and desirializes it by `.from_hash`.
|
65
|
+
def deep_dup
|
66
|
+
unless self.class.respond_to?(:from_hash)
|
67
|
+
raise NotImplementedError, "#{self.class} must implement `.from_hash(hash)` to use `HashLike#deep_dup`"
|
68
|
+
end
|
69
|
+
|
70
|
+
hash = JSON.parse(to_json)
|
71
|
+
self.class.from_hash(hash)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
data/lib/stac/item.rb
CHANGED
@@ -10,25 +10,26 @@ module STAC
|
|
10
10
|
#
|
11
11
|
# \STAC \Item Specification: https://github.com/radiantearth/stac-spec/tree/master/item-spec
|
12
12
|
class Item < STAC::STACObject
|
13
|
-
|
13
|
+
@type = 'Feature'
|
14
14
|
|
15
15
|
class << self
|
16
16
|
def from_hash(hash)
|
17
|
-
h = hash.
|
18
|
-
h[
|
19
|
-
h[
|
17
|
+
h = hash.transform_keys(&:to_sym)
|
18
|
+
h[:properties] = Properties.from_hash(h.fetch(:properties, {}))
|
19
|
+
h[:assets] = h.fetch(:assets, {}).transform_values { |v| Asset.from_hash(v) }
|
20
20
|
super(h)
|
21
21
|
rescue KeyError => e
|
22
22
|
raise ArgumentError, "required field not found: #{e.key}"
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
26
|
-
attr_accessor :id, :geometry, :bbox, :
|
26
|
+
attr_accessor :id, :geometry, :bbox, :collection_id
|
27
|
+
|
28
|
+
attr_reader :properties, :assets
|
27
29
|
|
28
30
|
def initialize(
|
29
|
-
id:, geometry:, properties:, links
|
31
|
+
id:, geometry:, properties:, links: [], assets: {}, bbox: nil, collection: nil, stac_extensions: [], **extra
|
30
32
|
)
|
31
|
-
super(links: links, stac_extensions: stac_extensions, **extra)
|
32
33
|
@id = id
|
33
34
|
@geometry = geometry
|
34
35
|
@properties = properties
|
@@ -40,6 +41,16 @@ module STAC
|
|
40
41
|
else
|
41
42
|
@collection_id = collection
|
42
43
|
end
|
44
|
+
super(links: links, stac_extensions: stac_extensions, **extra)
|
45
|
+
end
|
46
|
+
|
47
|
+
def [](key)
|
48
|
+
value = super
|
49
|
+
if value.nil?
|
50
|
+
properties.extra[key.to_s]
|
51
|
+
else
|
52
|
+
value
|
53
|
+
end
|
43
54
|
end
|
44
55
|
|
45
56
|
def to_h
|
@@ -58,11 +69,6 @@ module STAC
|
|
58
69
|
)
|
59
70
|
end
|
60
71
|
|
61
|
-
# Returns datetime from #properties.
|
62
|
-
def datetime
|
63
|
-
properties.datetime
|
64
|
-
end
|
65
|
-
|
66
72
|
# Returns a rel="collection" link as a collection object if it exists.
|
67
73
|
def collection
|
68
74
|
link = find_link(rel: 'collection')
|
@@ -71,17 +77,45 @@ module STAC
|
|
71
77
|
|
72
78
|
# Overwrites rel="collection" link and #collection_id attribute.
|
73
79
|
def collection=(collection)
|
74
|
-
raise ArgumentError, 'collection must have a rel="self" link' unless (collection_href = collection.self_href)
|
75
|
-
|
76
80
|
@collection_id = collection.id
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
81
|
+
remove_links(rel: 'collection')
|
82
|
+
add_link(collection, rel: 'collection', type: 'application/json', title: collection.title)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Adds an asset with the given key.
|
86
|
+
#
|
87
|
+
# When the item has extendable stac_extensions, make the asset extend the extension modules.
|
88
|
+
def add_asset(key:, href:, title: nil, description: nil, type: nil, roles: nil, **extra)
|
89
|
+
asset = Asset.new(href: href, title: title, description: description, type: type, roles: roles, **extra)
|
90
|
+
extensions.each do |extension|
|
91
|
+
asset.extend(extension::Asset) if extension.const_defined?(:Asset)
|
92
|
+
end
|
93
|
+
assets[key] = asset
|
94
|
+
self
|
95
|
+
end
|
96
|
+
|
97
|
+
private
|
98
|
+
|
99
|
+
def respond_to_missing?(symbol, include_all)
|
100
|
+
if properties.respond_to?(symbol)
|
101
|
+
true
|
102
|
+
else
|
103
|
+
super
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def method_missing(symbol, *args, **options, &block)
|
108
|
+
if properties.respond_to?(symbol)
|
109
|
+
properties.public_send(symbol, *args, **options, &block)
|
110
|
+
else
|
111
|
+
super
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def apply_extension!(extension)
|
116
|
+
super
|
117
|
+
properties.extend(extension::Properties) if extension.const_defined?(:Properties)
|
118
|
+
assets.each_value { |asset| asset.extend(extension::Asset) } if extension.const_defined?(:Asset)
|
85
119
|
end
|
86
120
|
end
|
87
121
|
end
|
data/lib/stac/link.rb
CHANGED
@@ -3,10 +3,23 @@
|
|
3
3
|
require 'pathname'
|
4
4
|
require 'uri'
|
5
5
|
require_relative 'errors'
|
6
|
+
require_relative 'hash_like'
|
6
7
|
|
7
8
|
module STAC
|
9
|
+
# Raised when a link does not have href or owner.
|
10
|
+
class LinkHrefError < Error
|
11
|
+
attr_reader :link
|
12
|
+
|
13
|
+
def initialize(msg = nil, link:)
|
14
|
+
super(msg)
|
15
|
+
@link = link
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
8
19
|
# Represents \STAC link object, which describes a relationship with another entity.
|
9
20
|
class Link
|
21
|
+
include HashLike
|
22
|
+
|
10
23
|
class << self
|
11
24
|
# Deserializes a Link from a Hash.
|
12
25
|
def from_hash(hash)
|
@@ -14,14 +27,15 @@ module STAC
|
|
14
27
|
end
|
15
28
|
end
|
16
29
|
|
17
|
-
attr_accessor :rel, :
|
30
|
+
attr_accessor :rel, :type, :title
|
31
|
+
|
32
|
+
attr_writer :href
|
18
33
|
|
19
34
|
# Owner object of this link.
|
20
35
|
attr_accessor :owner
|
21
36
|
|
22
|
-
|
23
|
-
|
24
|
-
def initialize(rel:, href:, type: nil, title: nil, **extra)
|
37
|
+
def initialize(target = nil, rel:, href:, type: nil, title: nil, **extra)
|
38
|
+
@target = target
|
25
39
|
@rel = rel
|
26
40
|
@href = href
|
27
41
|
@type = type
|
@@ -39,14 +53,30 @@ module STAC
|
|
39
53
|
}.merge(extra).compact
|
40
54
|
end
|
41
55
|
|
56
|
+
# Determines if the link's target is a resolved STACObject.
|
57
|
+
def resolved?
|
58
|
+
!@target.nil?
|
59
|
+
end
|
60
|
+
|
61
|
+
def href
|
62
|
+
@href || @target&.self_href
|
63
|
+
end
|
64
|
+
|
42
65
|
# Returns the absolute HREF for this link.
|
43
|
-
#
|
44
|
-
# When it could not assemble the absolute HREF, it returns nil.
|
45
66
|
def absolute_href
|
46
|
-
if URI(href).absolute?
|
47
|
-
href
|
67
|
+
if URI(href!).absolute?
|
68
|
+
href!
|
69
|
+
elsif (base_href = owner&.self_href)
|
70
|
+
Pathname(base_href).dirname.join(href!).to_s
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Returns the relative HREF for this link.
|
75
|
+
def relative_href
|
76
|
+
if URI(href!).relative?
|
77
|
+
href!
|
48
78
|
elsif (base_href = owner&.self_href)
|
49
|
-
Pathname(base_href).dirname
|
79
|
+
Pathname(href!).relative_path_from(Pathname(base_href).dirname).to_s
|
50
80
|
end
|
51
81
|
end
|
52
82
|
|
@@ -60,5 +90,17 @@ module STAC
|
|
60
90
|
object
|
61
91
|
end
|
62
92
|
end
|
93
|
+
|
94
|
+
def href! # :nodoc:
|
95
|
+
href or raise LinkHrefError.new('href is nil', link: self)
|
96
|
+
end
|
97
|
+
|
98
|
+
def absolute_href! # :nodoc:
|
99
|
+
absolute_href or raise LinkHrefError.new('could not assemble absolute href', link: self)
|
100
|
+
end
|
101
|
+
|
102
|
+
def relative_href! # :nodoc:
|
103
|
+
relative_href or raise LinkHrefError.new('could not assemble relative href', link: self)
|
104
|
+
end
|
63
105
|
end
|
64
106
|
end
|
data/lib/stac/object_resolver.rb
CHANGED
@@ -11,10 +11,10 @@ module STAC
|
|
11
11
|
class ObjectResolver
|
12
12
|
class << self
|
13
13
|
# Resolvable classes. Default is Catalog, Collection and Item.
|
14
|
-
|
14
|
+
attr_reader :resolvables
|
15
15
|
end
|
16
16
|
|
17
|
-
|
17
|
+
@resolvables = [Catalog, Collection, Item]
|
18
18
|
|
19
19
|
attr_reader :http_client
|
20
20
|
|
@@ -30,7 +30,7 @@ module STAC
|
|
30
30
|
# - file
|
31
31
|
#
|
32
32
|
# Raises:
|
33
|
-
# - STAC::
|
33
|
+
# - STAC::NotSupportedURISchemeError when a URL with not supported scheme was given
|
34
34
|
# - STAC::TypeError when it could not resolve any \STAC objects
|
35
35
|
def resolve(url)
|
36
36
|
hash = read(url)
|
@@ -53,7 +53,7 @@ module STAC
|
|
53
53
|
str = File.read(uri.path.to_s)
|
54
54
|
JSON.parse(str)
|
55
55
|
else
|
56
|
-
raise
|
56
|
+
raise NotSupportedURISchemeError, "not supported URI scheme: #{url}"
|
57
57
|
end
|
58
58
|
end
|
59
59
|
end
|
data/lib/stac/properties.rb
CHANGED
@@ -2,12 +2,14 @@
|
|
2
2
|
|
3
3
|
require 'time'
|
4
4
|
require_relative 'common_metadata'
|
5
|
+
require_relative 'hash_like'
|
5
6
|
|
6
7
|
module STAC
|
7
8
|
# Represents \STAC properties object, which is additional metadata for Item.
|
8
9
|
#
|
9
10
|
# Specification: https://github.com/radiantearth/stac-spec/blob/master/item-spec/item-spec.md#properties-object
|
10
11
|
class Properties
|
12
|
+
include HashLike
|
11
13
|
include CommonMetadata
|
12
14
|
|
13
15
|
class << self
|
@@ -23,7 +25,7 @@ module STAC
|
|
23
25
|
|
24
26
|
def initialize(datetime:, **extra)
|
25
27
|
@datetime = datetime
|
26
|
-
|
28
|
+
@extra = extra.transform_keys(&:to_s)
|
27
29
|
end
|
28
30
|
|
29
31
|
# Serializes self to a Hash.
|
data/lib/stac/provider.rb
CHANGED
@@ -1,10 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'hash_like'
|
4
|
+
|
3
5
|
module STAC
|
4
6
|
# Represents \STAC provider object, which provides information about a provider.
|
5
7
|
#
|
6
8
|
# Specicication: https://github.com/radiantearth/stac-spec/blob/master/collection-spec/collection-spec.md#provider-object
|
7
9
|
class Provider
|
10
|
+
include HashLike
|
11
|
+
|
8
12
|
class << self
|
9
13
|
# Deserializes a Provider from a Hash.
|
10
14
|
def from_hash(hash)
|
@@ -12,7 +16,7 @@ module STAC
|
|
12
16
|
end
|
13
17
|
end
|
14
18
|
|
15
|
-
attr_accessor :name, :description, :roles, :url
|
19
|
+
attr_accessor :name, :description, :roles, :url
|
16
20
|
|
17
21
|
def initialize(name:, description: nil, roles: nil, url: nil, **extra)
|
18
22
|
@name = name
|