spacetimeid 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7675891f5066fffedef7a012ff3818fa359a1d6f
4
+ data.tar.gz: 512877f20f6b9e5fb3059bed607364955eda7178
5
+ SHA512:
6
+ metadata.gz: dca84379331ab91abaff2ed959f7c4331871d5be58a1f6076566cf8b7a892df16ed3ac6b888167f8de2f4737f47f29e04d948a50efb8c55cc7366eaf34d72321
7
+ data.tar.gz: 7985334f122f72f4b349dace6fe774ecd329dd3332e92d6caa3be7f58dfd9caec796901dca855224b7a08773a1d02e5ca6fab775d9542c0d3e555b9933e83e14
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
data/README.md ADDED
@@ -0,0 +1,35 @@
1
+ # SpaceTimeId
2
+ A unique spationtemporal id implementation for use in nanocubes
3
+
4
+ ## General Requirements
5
+
6
+ Install via rubygems
7
+
8
+ ```gem install spacetimeid```
9
+
10
+ or add it to your Gemfile
11
+
12
+ ```gem 'spacetimeid'```
13
+
14
+ ## Example
15
+
16
+ ```ruby
17
+ require 'time'
18
+ time = Time.parse("2015-06-01T00:05:32Z")
19
+ id = SpaceTimeId.new(time.to_i, [1.3, -3.048])
20
+
21
+ id.id
22
+ # => "1433116800_1.30_-3.05"
23
+
24
+ # DEFAULTS = {
25
+ # xy_base_step: 0.01, # 0.01 degrees
26
+ # xy_expansion: 5, # expands into a 5x5 grid recursively
27
+ # ts_base_step: 600, # 10 minutes
28
+ # ts_expansion: [1, 3, 2, 3, 2], # each time interval expands this many times the previous one
29
+ # decimals: 2
30
+ # }
31
+
32
+ ```
33
+
34
+ ### Disclaimer
35
+ This project is written on a need to use basis for inclusion to other projects I'm working on for now, so completion is not an immediate goal.
@@ -0,0 +1,38 @@
1
+ if !Hash.method_defined?(:extractable_options?)
2
+
3
+ class Hash
4
+ # By default, only instances of Hash itself are extractable.
5
+ # Subclasses of Hash may implement this method and return
6
+ # true to declare themselves as extractable. If a Hash
7
+ # is extractable, Array#extract_options! pops it from
8
+ # the Array when it is the last element of the Array.
9
+ def extractable_options?
10
+ instance_of?(Hash)
11
+ end
12
+ end
13
+
14
+ end
15
+
16
+
17
+ if !Array.method_defined?(:extract_options!)
18
+
19
+ class Array
20
+ # Extracts options from a set of arguments. Removes and returns the last
21
+ # element in the array if it's a hash, otherwise returns a blank hash.
22
+ #
23
+ # def options(*args)
24
+ # args.extract_options!
25
+ # end
26
+ #
27
+ # options(1, 2) # => {}
28
+ # options(1, 2, a: :b) # => {:a=>:b}
29
+ def extract_options!
30
+ if last.is_a?(Hash) && last.extractable_options?
31
+ pop
32
+ else
33
+ {}
34
+ end
35
+ end
36
+ end
37
+
38
+ end
@@ -0,0 +1,275 @@
1
+ class SpaceTimeId
2
+
3
+ DEFAULTS = {
4
+ xy_base_step: 0.01, # 0.01 degrees
5
+ xy_expansion: 5, # expands into a 5x5 grid recursively
6
+ ts_base_step: 600, # 10 minutes
7
+ ts_expansion: [1, 3, 2, 3, 2], # expands 3 times each interval, then 2, then 3....
8
+ decimals: 2
9
+ }
10
+
11
+ attr_accessor :ts_sec
12
+ attr_accessor :x
13
+ attr_accessor :y
14
+ attr_accessor :options
15
+ attr_accessor :interval
16
+ attr_accessor :level
17
+
18
+ def initialize(*args)
19
+ self.options = DEFAULTS.merge(args.extract_options!)
20
+ self.interval = options.delete(:interval) || 0
21
+ self.level = options.delete(:level) || 0
22
+ options[:ts_expansion] = Array(options[:ts_expansion])
23
+ if args.length == 1 && args.first.is_a?(String)
24
+ initialize(*args.first.split("_").map(&:to_f), original_options)
25
+ elsif args.length == 1 && args.first.is_a?(Array) && args.first.length == 2
26
+ initialize(*(args.first), original_options)
27
+ elsif args.all? { |arg| arg.is_a?(Fixnum) || arg.is_a?(Float) }
28
+ case args.length
29
+ when 1
30
+ self.ts_sec = args.first
31
+ self.ts_sec = ts_id.to_i
32
+ when 2
33
+ self.x, self.y = args
34
+ self.x, self.y = xy_id_2d
35
+ when 3
36
+ self.ts_sec, self.x, self.y = args
37
+ self.x, self.y = xy_id_2d
38
+ self.ts_sec = ts_id.to_i
39
+ else
40
+ raise "Invalid initialize arguments! (#{args.inspect})"
41
+ end
42
+ elsif args.length == 2 &&
43
+ (args.first.is_a?(Fixnum) || args.first.is_a?(Float)) && args.last.is_a?(Array)
44
+ ts = args.first
45
+ x, y = args.last
46
+ initialize(ts, x, y, original_options)
47
+ else
48
+ raise "Invalid initialize arguments! (#{args.inspect})"
49
+ end
50
+ end
51
+
52
+ def id
53
+ [ts_id, xy_id_str].compact.join("_")
54
+ end
55
+
56
+ def to_s
57
+ id
58
+ end
59
+
60
+ def inspect
61
+ "<SpaceTimeId:#{id} #{original_options.inspect}>"
62
+ end
63
+
64
+ def to_html
65
+ html = "id: #{id}<br/>"
66
+ if ts?
67
+ html << "interval: #{inteval}<br/>"
68
+ html << "time_base_step: #{time_base_step}<br/>"
69
+ html << "time_expansion: #{time_expansion}<br/>"
70
+ end
71
+ if xy?
72
+ html << "level: #{level}<br/>"
73
+ html << "xy_base_step: #{xy_base_step}<br/>"
74
+ html << "xy_expansion: #{xy_expansion}<br/>"
75
+ end
76
+ html
77
+ end
78
+
79
+ def ==(other)
80
+ other.nil? || !other.respond_to?(:id) ? false : id == other.id
81
+ end
82
+
83
+ def eql?(other)
84
+ other.is_a?(SpaceTimeId) && self == other
85
+ end
86
+
87
+ def hash
88
+ [id, interval, level].hash
89
+ end
90
+
91
+ def dup(new_id = id, options = self.options)
92
+ SpaceTimeId.new(new_id, original_options.merge(options))
93
+ end
94
+
95
+ def dec
96
+ options[:decimals]
97
+ end
98
+
99
+ def original_options
100
+ {level: level, interval: interval}.merge(options)
101
+ end
102
+
103
+ # # # # # # # # # # # # T I M E # # # # # # # # # # # # # # # # # # # # # # #
104
+
105
+ def ts?
106
+ !ts_sec.nil?
107
+ end
108
+
109
+ def ts
110
+ Time.at(ts_sec) if ts_sec
111
+ end
112
+
113
+ def ts_ms
114
+ ts_sec * 1000 if ts_sec
115
+ end
116
+
117
+ def ts_id
118
+ digitize(ts_sec, ts_step).to_i if ts_sec
119
+ end
120
+
121
+ def ts_step
122
+ @ts_step ||= ts_step_aux(interval)
123
+ end
124
+
125
+ def ts_step_aux(interval)
126
+ ts_base_step * ts_expansion
127
+ end
128
+
129
+ def ts_base_step
130
+ options[:ts_base_step]
131
+ end
132
+
133
+ def ts_expansion
134
+ ts_expansion_aux
135
+ options[:ts_expansion][0..interval].reduce(:*)
136
+ end
137
+
138
+ def ts_expansion_aux
139
+ if interval >= options[:ts_expansion].length
140
+ padd = [options[:ts_expansion].last] * (options[:ts_expansion].length - interval + 1)
141
+ options[:ts_expansion] += padd
142
+ end
143
+ end
144
+
145
+ # # # # # # # # # # # # # T I M E R E L A T I O N S # # # # # # # # # # # #
146
+
147
+ def next_ts(i = 1)
148
+ @_next_ts ||= {}
149
+ @_next_ts[i] ||= digitize(ts_id + ts_step * i + ts_step / 2.0, ts_step) if ts?
150
+ end
151
+
152
+ def ts_neighbors
153
+ @ts_neighbors ||= begin
154
+ prevt = dup(next_ts(-1))
155
+ nextt = dup(next_ts)
156
+ @ts_neighbors = [prevt, nextt]
157
+ end if ts?
158
+ end
159
+
160
+ def ts_parent
161
+ dup(id, interval: interval + 1) if ts?
162
+ end
163
+
164
+ # # # # # # # # # # # # S P A C E # # # # # # # # # # # # # # # # # # # # # #
165
+
166
+ def xy?
167
+ !xy.empty?
168
+ end
169
+
170
+ def xy
171
+ [x, y].compact
172
+ end
173
+
174
+ def xy_id_2d
175
+ xy.map { |c| digitize(c, xy_step).round(dec) } if xy?
176
+ end
177
+
178
+ def xy_id_str
179
+ xy.map { |c| digitize_str(c, xy_step, dec) } .join("_") if xy?
180
+ end
181
+
182
+ def xy_id_2d_center
183
+ xy_id_2d.map { |c| (c + (xy_step / 2.0)).round(dec * 2) } if xy?
184
+ end
185
+
186
+ def xy_id_str_center
187
+ xy_id_2d_center.map { |c| digitize_str(c, (xy_step / 2.0), dec * 2) } .join("_") if xy?
188
+ end
189
+
190
+ def xy_step
191
+ (xy_expansion ** level) * xy_base_step
192
+ end
193
+
194
+ def xy_base_step
195
+ options[:xy_base_step]
196
+ end
197
+
198
+ def xy_expansion
199
+ options[:xy_expansion]
200
+ end
201
+
202
+ # # # # # # # # # # # # S P A C E R E L A T I O N S # # # # # # # # # # # #
203
+
204
+ def next_x(i = 1)
205
+ @_next_x ||= {}
206
+ @_next_x[i] ||= digitize(x + xy_step * i + xy_step / 2.0, xy_step) if xy?
207
+ end
208
+
209
+ def next_y(i = 1)
210
+ @_next_y ||= {}
211
+ @_next_y[i] ||= digitize(y + xy_step * i + xy_step / 2.0, xy_step) if xy?
212
+ end
213
+
214
+ def xy_neighbors
215
+ @xy_neighbors ||= begin
216
+ topLeft = dup([next_x(-1), next_y(1) ])
217
+ top = dup([x , next_y(1) ])
218
+ topRight = dup([next_x(1) , next_y(1) ])
219
+ right = dup([next_x(1) , y ])
220
+ bottomRight = dup([next_x(1) , next_y(-1)])
221
+ bottom = dup([x , next_y(-1)])
222
+ bottomLeft = dup([next_x(-1), next_y(-1)])
223
+ left = dup([next_x(-1), y ])
224
+ @xy_neighbors = [topLeft, top, topRight, right, bottomRight, bottom, bottomLeft, left]
225
+ end if xy?
226
+ end
227
+
228
+ def xy_parent
229
+ dup(id, level: level + 1) if xy?
230
+ end
231
+
232
+ def xy_children
233
+ raise "No children for level zero!" if level == 0
234
+ @xy_children ||= begin
235
+ bottom_left = dup(id, level: level - 1)
236
+ @xy_children = []
237
+ (0...xy_expansion).each do |h|
238
+ (0...xy_expansion).each do |v|
239
+ @xy_children << bottom_left.dup([bottom_left.next_x(h), bottom_left.next_y(v)])
240
+ end
241
+ end
242
+ @xy_children
243
+ end if xy?
244
+ end
245
+
246
+ def xy_descendants
247
+ xy_children + (level == 1 ? [] : xy_children.map(&:xy_descendants).flatten) if xy?
248
+ end
249
+
250
+ def xy_siblings
251
+ xy_parent.xy_children if xy?
252
+ end
253
+
254
+ def xy_four_corners
255
+ [xy, [next_x, y], [next_x, next_y], [x, next_y], xy] if xy?
256
+ end
257
+
258
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
259
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
260
+
261
+ private
262
+
263
+ def digitize n, step
264
+ # JS method to avoid floating point rounding errors: 85.000000000000001
265
+ # Math.floor((c + @baseStep / 1000) / @baseStep) / (1 / @baseStep)
266
+ step = step.to_f
267
+ ((n + step / 1000) / step).floor / (1 / step)
268
+ end
269
+
270
+ def digitize_str n, step, dec = 2
271
+ n = digitize n, step
272
+ "%.#{dec}f" % n
273
+ end
274
+
275
+ end
@@ -0,0 +1,2 @@
1
+ require 'extract_options'
2
+ autoload :SpaceTimeId, 'space_time_id'
metadata ADDED
@@ -0,0 +1,62 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: spacetimeid
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - George Lamprianidis
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-06-01 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rspec
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description: Unified space and time id with hierarchy in both dimensions!
28
+ email: giorgos.lamprianidis@gmail.com
29
+ executables: []
30
+ extensions: []
31
+ extra_rdoc_files: []
32
+ files:
33
+ - Gemfile
34
+ - README.md
35
+ - lib/extract_options.rb
36
+ - lib/space_time_id.rb
37
+ - lib/spacetimeid.rb
38
+ homepage: https://github.com/glampr/spacetimeid
39
+ licenses:
40
+ - MIT
41
+ metadata: {}
42
+ post_install_message:
43
+ rdoc_options: []
44
+ require_paths:
45
+ - lib
46
+ required_ruby_version: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: '0'
51
+ required_rubygems_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ requirements: []
57
+ rubyforge_project:
58
+ rubygems_version: 2.4.6
59
+ signing_key:
60
+ specification_version: 4
61
+ summary: Unified space and time id with hierarchy in both dimensions!
62
+ test_files: []