spacetimeid 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []