spacetimeid 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +3 -0
- data/README.md +35 -0
- data/lib/extract_options.rb +38 -0
- data/lib/space_time_id.rb +275 -0
- data/lib/spacetimeid.rb +2 -0
- metadata +62 -0
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
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
|
data/lib/spacetimeid.rb
ADDED
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: []
|