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 +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: []
|