geospatial 1.0.0 → 1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2f5c7adeba4a6c4c4773d3f05ce924da0287e1b4
4
- data.tar.gz: f927b7952f48e67dd30df8c6076aa4d590d8f5cc
3
+ metadata.gz: 1958264822cca7e6426c5aada4f60e5b810f91a1
4
+ data.tar.gz: 4a76a3c348d3c75d9f557de8bc84473711d42ad5
5
5
  SHA512:
6
- metadata.gz: bb6ad414765df8a2c66706c1b157b51355b0e717fd1a9e779455bffc10c8d8a338c0f9c27171ca5023d78d5824414fe2c919be315c277380f4bebb73e6bda4c2
7
- data.tar.gz: 4f1f8d46e132ec47cbb07ee5c361b5215088e816514e254d85f40405e8fb5fa0f77fd32cc1d696f73ac0c0a424a2dd7ebc1db6f5fb4c4136a3b10dd20289990a
6
+ metadata.gz: c7961e865029758de632520c6b821bc52040b36ed443773081787fe9e6dad6afd4861a7dbd1c80db321218c03697a8994dffe03bf29da91433125d1724a741a7
7
+ data.tar.gz: 6eb65f74cad756520215e97394bfc76d8e081535a68642945062aba035963b64d2b4873600f89cdcec32de9f525f6773e5c2108273b79618299caf29503020f7
@@ -0,0 +1,80 @@
1
+ # Copyright, 2016, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ module Geospatial
22
+ # This location is specifically relating to a WGS84 coordinate on Earth.
23
+ class Distance
24
+ # Distance in meters:
25
+ def initialize(value)
26
+ @value = value
27
+ @formatted_value = nil
28
+ end
29
+
30
+ def freeze
31
+ formatted_value
32
+
33
+ super
34
+ end
35
+
36
+ UNITS = ['m', 'km'].freeze
37
+
38
+ def formatted_value
39
+ unless @formatted_value
40
+ scale = 0
41
+ value = @value
42
+
43
+ while value > 1000 and scale < UNITS.size
44
+ value /= 1000.0
45
+ scale += 1
46
+ end
47
+
48
+ @formatted_value = sprintf("%0.#{scale}f%s", value, UNITS.fetch(scale))
49
+ end
50
+
51
+ return @formatted_value
52
+ end
53
+
54
+ alias to_s formatted_value
55
+
56
+ def to_f
57
+ @value
58
+ end
59
+
60
+ def + other
61
+ Distance.new(@value + other.to_f)
62
+ end
63
+
64
+ def - other
65
+ Distance.new(@value - other.to_f)
66
+ end
67
+
68
+ def * other
69
+ Distance.new(@value * other.to_f)
70
+ end
71
+
72
+ def / other
73
+ Distance.new(@value / other.to_f)
74
+ end
75
+
76
+ def == other
77
+ @value == other.to_f
78
+ end
79
+ end
80
+ end
@@ -42,10 +42,34 @@ module Geospatial
42
42
  MIN_LATITUDE = -90.0 * D2R
43
43
  MAX_LATITUDE = 90 * D2R
44
44
  VALID_LATITUDE = MIN_LATITUDE...MAX_LATITUDE
45
-
45
+
46
+ class << self
47
+ def from_ecef(x, y, z)
48
+ # Constants (WGS ellipsoid)
49
+ a = WGS84_A
50
+ e = WGS84_E
51
+
52
+ b = Math::sqrt((a*a) * (1.0-(e*e)))
53
+ ep = Math::sqrt(((a*a)-(b*b))/(b*b))
54
+
55
+ p = Math::sqrt((x*x)+(y*y))
56
+ th = Math::atan2(a*z, b*p)
57
+
58
+ lon = Math::atan2(y, x)
59
+ lat = Math::atan2((z+ep*ep*b*(Math::sin(th) ** 3)), (p-e*e*a*(Math::cos(th)**3)))
60
+
61
+ n = a / Math::sqrt(1.0-e*e*(Math::sin(lat) ** 2))
62
+ # alt = p / Math::cos(lat)-n
63
+
64
+ return self.new(lat*R2D, lon*R2D)
65
+ end
66
+
67
+ alias [] new
68
+ end
69
+
46
70
  def initialize(longitude, latitude)
47
- @latitude = latitude
48
71
  @longitude = longitude
72
+ @latitude = latitude
49
73
  end
50
74
 
51
75
  def valid?
@@ -57,7 +81,7 @@ module Geospatial
57
81
  end
58
82
 
59
83
  def to_s
60
- "#<Location longitude=#{@longitude.to_f} latitude=#{@latitude}>"
84
+ "#{self.class}#{self.to_a}"
61
85
  end
62
86
 
63
87
  alias inspect to_s
@@ -113,34 +137,14 @@ module Geospatial
113
137
 
114
138
  return x, y, z
115
139
  end
116
-
117
- def self.from_ecef(x, y, z)
118
- # Constants (WGS ellipsoid)
119
- a = WGS84_A
120
- e = WGS84_E
121
-
122
- b = Math::sqrt((a*a) * (1.0-(e*e)))
123
- ep = Math::sqrt(((a*a)-(b*b))/(b*b))
124
-
125
- p = Math::sqrt((x*x)+(y*y))
126
- th = Math::atan2(a*z, b*p)
127
-
128
- lon = Math::atan2(y, x)
129
- lat = Math::atan2((z+ep*ep*b*(Math::sin(th) ** 3)), (p-e*e*a*(Math::cos(th)**3)))
130
-
131
- n = a / Math::sqrt(1.0-e*e*(Math::sin(lat) ** 2))
132
- # alt = p / Math::cos(lat)-n
133
-
134
- return self.new(lat*R2D, lon*R2D)
135
- end
136
140
 
137
141
  # calculate distance in metres between us and something else
138
142
  # ref: http://codingandweb.blogspot.co.nz/2012/04/calculating-distance-between-two-points.html
139
- def distance_from(other_position)
143
+ def distance_from(other)
140
144
  rlong1 = self.longitude * D2R
141
145
  rlat1 = self.latitude * D2R
142
- rlong2 = other_position.longitude * D2R
143
- rlat2 = other_position.latitude * D2R
146
+ rlong2 = other.longitude * D2R
147
+ rlat2 = other.latitude * D2R
144
148
 
145
149
  dlon = rlong1 - rlong2
146
150
  dlat = rlat1 - rlat2
@@ -151,5 +155,9 @@ module Geospatial
151
155
 
152
156
  return d
153
157
  end
158
+
159
+ def - other
160
+ Distance.new(self.distance_from(other))
161
+ end
154
162
  end
155
163
  end
@@ -44,6 +44,8 @@ module Geospatial
44
44
 
45
45
  attr :coordinates
46
46
 
47
+ alias to_a coordinates
48
+
47
49
  def eql?(other)
48
50
  self.class.eql?(other.class) and @coordinates.eql?(other.coordinates)
49
51
  end
@@ -143,6 +145,7 @@ module Geospatial
143
145
  def filter_for(region, **options)
144
146
  filter = Filter.new
145
147
 
148
+ # The filter will coalesce sequential segments of the curve into a single range.
146
149
  traverse(region, **options) do |child, prefix, order|
147
150
  filter.add(prefix, order)
148
151
  end
@@ -19,5 +19,5 @@
19
19
  # THE SOFTWARE.
20
20
 
21
21
  module Geospatial
22
- VERSION = "1.0.0"
22
+ VERSION = "1.1.0"
23
23
  end
@@ -0,0 +1,47 @@
1
+ # Copyright, 2015, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require 'geospatial/distance'
22
+
23
+ RSpec.describe Geospatial::Distance.new(10000) do
24
+ it "should have correct value" do
25
+ expect(subject.to_f).to be == 10000.0
26
+ end
27
+
28
+ it "should format string" do
29
+ expect(subject.to_s).to be == "10.0km"
30
+ end
31
+
32
+ it "can divide distances" do
33
+ expect(subject / 2).to be == 5000.0
34
+ end
35
+
36
+ it "can multiply distances" do
37
+ expect(subject * 2).to be == 20000.0
38
+ end
39
+
40
+ it "can add distances" do
41
+ expect(subject + 100).to be == 10100.0
42
+ end
43
+
44
+ it "can subtract distances" do
45
+ expect(subject - 100).to be == 9900.0
46
+ end
47
+ end
@@ -0,0 +1,55 @@
1
+
2
+ require 'geospatial/map'
3
+ require 'geospatial/polygon'
4
+
5
+ require_relative 'visualization'
6
+
7
+ RSpec.describe Geospatial::Map.for_earth(30) do
8
+ let(:ranges) {[[888899773526966272, 888899773560520703], [888899773912842240, 888899775322128383], [888899775355682816, 888899775389237247], [888899775422791680, 888899775573786623], [888899775708004352, 888899776311984127], [888899776932741120, 888899776966295551], [888899776999849984, 888899777050181631], [888899781680693248, 888899782217564159], [888899782754435072, 888899783291305983], [888899787317837824, 888899788123144191], [888899788660015104, 888899816141094911], [888899816157872128, 888899816191426559], [888899816224980992, 888899816392753151], [888899816426307584, 888899816443084799], [888899816510193664, 888899817181282303], [888899817349054464, 888899817416163327], [888899817617489920, 888899817634267135], [888899817651044352, 888899821224591359], [888899821342031872, 888899821509804031], [888899822516436992, 888899822717763583], [888899822751318016, 888899823019753471], [888900520767389696, 888900520800944127], [888900520834498560, 888900520868052991], [888900521572696064, 888900521690136575], [888900521824354304, 888900521992126463], [888900525599227904, 888900525632782335], [888900525666336768, 888900525699891199], [888900525917995008, 888900526874296319], [888900527411167232, 888900527444721663], [888900527461498880, 888900580964040703], [888900581031149568, 888900581064703999], [888900581098258432, 888900581349916671], [888900582843088896, 888900582876643327], [888900583044415488, 888900583077969919], [888900583111524352, 888900583161855999], [888900584051048448, 888900584084602879], [888900584134934528, 888900584369815551], [888900584504033280, 888900587154833407], [888900587171610624, 888900587255496703], [888900587624595456, 888900587641372671], [888900587674927104, 888900587708481535], [888900587876253696, 888900587909808127], [888900587926585344, 888900587993694207], [888900588161466368, 888900588178243583], [888900588195020800, 888900591718236159], [888900591768567808, 888900591802122239], [888900591818899456, 888900593093967871], [888900593110745088, 888900593127522303], [888900593244962816, 888900593261740031], [888900593983160320, 888900594016714751], [888900594100600832, 888900594117378047], [888900594134155264, 888900594503254015], [888900594520031232, 888900594536808447], [888900594704580608, 888900594721357823], [888900594738135040, 888900594855575551], [888900594872352768, 888900623544614911], [888900623678832640, 888900633426395135], [888900633795493888, 888900633812271103], [888900633845825536, 888900633879379967], [888900634214924288, 888900634483359743], [888900634567245824, 888900634634354687], [888900634651131904, 888900634667909119], [888900636110749696, 888900636161081343], [888900636244967424, 888900636261744639], [888900636278521856, 888900636882501631], [888900636999942144, 888900637016719359], [888900637520035840, 888900637570367487], [888900644365139968, 888900644381917183], [888900644415471616, 888900644499357695], [888900644549689344, 888900645757648895], [888900645791203328, 888900645807980543], [888900645942198272, 888900646328074239], [888900646344851456, 888900646512623615], [888900649347973120, 888900649364750335], [888900677651136512, 888900677667913727], [888900677701468160, 888900677751799807], [888900677953126400, 888900677969903615], [888912854286073856, 888912854420291583], [888912854621618176, 888912858111279103], [888912858195165184, 888912858228719615], [888912858279051264, 888912858480377855], [888912858530709504, 888912858564263935], [888912858614595584, 888912859487010815], [888912859537342464, 888912859570896895], [888912859621228544, 888912859822555135], [888912859856109568, 888912877103087615], [888912877237305344, 888912877371523071], [888912877505740800, 888912878143275007], [888912878344601600, 888912878411710463], [888912878713700352, 888912878881472511], [888912878931804160, 888912878965358591], [888912879015690240, 888912879888105471], [888912879904882688, 888912879988768767], [888912885978234880, 888912886011789311], [888912886045343744, 888912886078898175], [888912886783541248, 888912886817095679], [888912886850650112, 888912886900981759], [888912887035199488, 888912887169417215], [888912891162394624, 888912891179171839], [888912891263057920, 888912891967700991], [888912892018032640, 888912892051587071], [888912892638789632, 888912892655566847], [888912892705898496, 888912901815926783], [888912901849481216, 888912901883035647], [888912901916590080, 888912902067585023], [888912902201802752, 888912903090995199], [888912903124549632, 888912903174881279], [888912903426539520, 888912903460093951], [888912903476871168, 888912903543980031], [888912906849091584, 888912906882646015], [888912906916200448, 888912909013352447], [888912909214679040, 888912909265010687], [888912909298565120, 888912909315342335], [888912910405861376, 888912910523301887], [888912910657519616, 888912910791737343], [888912911680929792, 888912911714484223], [888912911731261440, 888912912016474111], [888912912050028544, 888912912083582975], [888912912117137408, 888913026034434047], [888913026051211264, 888913026151874559], [888913026168651776, 888913026185428991], [888913026789408768, 888913026906849279], [888913026923626496, 888913027913482239], [888913027930259456, 888913028098031615], [888913028114808832, 888913028131586047], [888913028433575936, 888913028450353151], [888913028483907584, 888913028567793663], [888913028584570880, 888913028601348095], [888913037912702976, 888913037929480191], [888913038550237184, 888913038718009343], [888913038734786560, 888913039993077759], [888913040009854976, 888913040177627135], [888913040446062592, 888913040462839807], [888913040496394240, 888913040597057535], [888913040613834752, 888913056082427903], [888913056115982336, 888913056266977279], [888913056602521600, 888913056686407679], [888913056719962112, 888913059202990079], [888913059622420480, 888913059689529343], [888913713212424192, 888913713413750783], [888913713430528000, 888913713447305215], [888913713682186240, 888913713698963455], [888913713715740672, 888913716366540799], [888913716383318016, 888913716416872447], [888913716685307904, 888913716718862335], [888913716735639552, 888913716836302847], [888913716853080064, 888913716869857279], [888913720711839744, 888913720745394175], [888913720762171392, 888913720896389119], [888913721282265088, 888913721299042303], [888913721466814464, 888913721483591679], [888913721500368896, 888913723110981631], [888913723329085440, 888913756464087039], [888913756665413632, 888913756732522495], [888913756933849088, 888913757772709887], [888913761698578432, 888913761748910079], [888913761782464512, 888913762352889855], [888913762386444288, 888913762436775935], [888913762772320256, 888913762822651903], [888913762856206336, 888913763393077247], [888913763460186112, 888913763510517759], [888913769013444608, 888913769231548415], [888913769265102848, 888913769281880063], [888913769583869952, 888913769617424383], [888913769785196544, 888913771177705471], [888913771446140928, 888913771647467519]]}
9
+
10
+ it "can generate visualisation" do
11
+ Geospatial::Visualization.for_map(subject) do |pdf, origin|
12
+ ranges.each do |(min, max)|
13
+ a = Vector.elements(subject.point_for_hash(min).to_a)
14
+ b = Vector.elements(subject.point_for_hash(max).to_a)
15
+ size = (b - a).collect(&:abs)
16
+
17
+ top_left = origin + a + Vector[0, size[1]]
18
+ pdf.rectangle(top_left.to_a, *size.to_a)
19
+ end
20
+
21
+ pdf.fill_and_stroke
22
+ end.render_file "kaikoura.pdf"
23
+ end
24
+
25
+ let(:kaikoura_polygon) {Geospatial::Polygon[
26
+ Vector[173.7218528654108, -42.32817252073923],
27
+ Vector[173.6307775307161, -42.32729039137249],
28
+ Vector[173.5400659958715, -42.39758413896335],
29
+ Vector[173.5446498680837, -42.43847509799515],
30
+ Vector[173.6833471779081, -42.44870319335309],
31
+ Vector[173.7608096128163, -42.42144813099029],
32
+ Vector[173.7218528654108, -42.32817252073923],
33
+ ]}
34
+
35
+ it "should not contain point" do
36
+ map = Geospatial::Map.for_earth(30)
37
+ filter = map.filter_for(kaikoura_polygon, depth: 12)
38
+
39
+ puts "filter.ranges.count: #{filter.ranges.count}"
40
+
41
+ #ranges.each_with_index do |(min, max), index|
42
+ # puts "#{min} -> #{max} :: #{filter.ranges[index].min} -> #{filter.ranges[index].max}"
43
+ #end
44
+
45
+ location = Geospatial::Location[176.204319, -37.660294]
46
+ point = map.point_for_object(location)
47
+
48
+ puts map.hash_for_coordinates(location.to_a)
49
+ # 888186547785722805
50
+ # 888900599658148001
51
+
52
+ expect(filter).to_not include location
53
+ end
54
+ end
55
+
@@ -25,13 +25,13 @@ require_relative 'visualization'
25
25
  RSpec.shared_context "kaikoura region" do
26
26
  let(:region) do
27
27
  Geospatial::Polygon[
28
- Vector[173.7218528654108, -42.32817252073923],
29
- Vector[173.6307775307161, -42.32729039137249],
30
- Vector[173.5400659958715, -42.39758413896335],
31
- Vector[173.5446498680837, -42.43847509799515],
32
- Vector[173.6833471779081, -42.44870319335309],
33
- Vector[173.7608096128163, -42.42144813099029],
34
- Vector[173.7218528654108, -42.32817252073923],
28
+ Vector[173.7218528654108, -42.32817252073923],
29
+ Vector[173.6307775307161, -42.32729039137249],
30
+ Vector[173.5400659958715, -42.39758413896335],
31
+ Vector[173.5446498680837, -42.43847509799515],
32
+ Vector[173.6833471779081, -42.44870319335309],
33
+ Vector[173.7608096128163, -42.42144813099029],
34
+ Vector[173.7218528654108, -42.32817252073923],
35
35
  ]
36
36
  end
37
37
 
@@ -69,8 +69,14 @@ end
69
69
  RSpec.describe Geospatial::Polygon do
70
70
  include_context "kaikoura region"
71
71
 
72
+ it "generates correct number of ranges" do
73
+ map = Geospatial::Map.for_earth(30)
74
+ filter = map.filter_for(region, depth: 12)
75
+ expect(filter.ranges.count).to be 166
76
+ end
77
+
72
78
  it "can generate visualisation" do
73
- map = Geospatial::Map.for_earth
79
+ map = Geospatial::Map.for_earth(30)
74
80
 
75
81
  map << kaikoura
76
82
 
@@ -79,16 +85,16 @@ RSpec.describe Geospatial::Polygon do
79
85
  pdf.line (origin + pa).to_a, (origin + pb).to_a
80
86
  end
81
87
 
82
- #count = 0
83
- map.traverse(region, depth: map.order - 15) do |child, prefix, order|
84
- #count += 1
88
+ count = 0
89
+ map.traverse(region, depth: 12) do |child, prefix, order|
90
+ count += 1
85
91
  size = child.size
86
92
  top_left = (origin + child.min) + Vector[0, size[1]]
87
93
  pdf.rectangle(top_left.to_a, *size.to_a)
88
94
  # puts "#{top_left.to_a} #{size.to_a}"
89
95
  end
90
96
 
91
- #puts "count=#{count}"
97
+ # puts "count=#{count}"
92
98
 
93
99
  pdf.fill_and_stroke
94
100
  end.render_file "polygon.pdf"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: geospatial
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-12-27 00:00:00.000000000 Z
11
+ date: 2016-12-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -72,6 +72,7 @@ files:
72
72
  - lib/geospatial/box.rb
73
73
  - lib/geospatial/circle.rb
74
74
  - lib/geospatial/dimensions.rb
75
+ - lib/geospatial/distance.rb
75
76
  - lib/geospatial/filter.rb
76
77
  - lib/geospatial/hilbert.rb
77
78
  - lib/geospatial/hilbert/curve.rb
@@ -86,6 +87,8 @@ files:
86
87
  - spec/geospatial/box_spec.rb
87
88
  - spec/geospatial/circle_spec.rb
88
89
  - spec/geospatial/dimensions_spec.rb
90
+ - spec/geospatial/distance_spec.rb
91
+ - spec/geospatial/filter_spec.rb
89
92
  - spec/geospatial/hilbert_curve_spec.rb
90
93
  - spec/geospatial/hilbert_traverse_spec.rb
91
94
  - spec/geospatial/index_spec.rb
@@ -124,6 +127,8 @@ test_files:
124
127
  - spec/geospatial/box_spec.rb
125
128
  - spec/geospatial/circle_spec.rb
126
129
  - spec/geospatial/dimensions_spec.rb
130
+ - spec/geospatial/distance_spec.rb
131
+ - spec/geospatial/filter_spec.rb
127
132
  - spec/geospatial/hilbert_curve_spec.rb
128
133
  - spec/geospatial/hilbert_traverse_spec.rb
129
134
  - spec/geospatial/index_spec.rb