reality 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -71,6 +71,26 @@ module Reality
71
71
  end
72
72
  end
73
73
 
74
+ def to_simple_type(val)
75
+ case val
76
+ when Rational
77
+ val.to_f
78
+ when nil, Numeric, String, Symbol
79
+ val
80
+ when Array
81
+ val.map{|v| to_simple_type(v)}
82
+ #when Hash
83
+ #val.map{|k, v| [to_simple_type(k), to_simple_type(v)]}.to_h
84
+ when Entity
85
+ val.loaded? ? val.to_h : val.to_s
86
+
87
+ when ->(v){v.respond_to?(:to_h)}
88
+ val.to_h
89
+ else
90
+ val.to_s
91
+ end
92
+ end
93
+
74
94
  end
75
95
  end
76
96
  end
@@ -1,5 +1,6 @@
1
1
  module Reality
2
2
  class Entity
3
+ # @private
3
4
  module WikidataPredicates
4
5
  module_function
5
6
 
@@ -1,5 +1,6 @@
1
1
  module Reality
2
2
  class Entity
3
+ # @private
3
4
  module WikipediaType
4
5
  def infobox_name(*infobox_names)
5
6
  infobox_names.each do |n|
@@ -12,10 +12,12 @@ module Reality
12
12
  private
13
13
 
14
14
  def guess_timezone
15
- Timezone::Configure.username = Reality.config.fetch('keys', 'geonames')
15
+ Timezone::Lookup.config(:geonames) do |c|
16
+ c.username = Reality.config.fetch('keys', 'geonames')
17
+ end
16
18
 
17
- gnzone = Timezone::Zone.new(latlon: [lat.to_f, lng.to_f])
18
- gnzone && TZInfo::Timezone.new(gnzone.zone)
19
+ gnzone = Timezone.lookup(lat.to_f, lng.to_f)
20
+ gnzone && TZInfo::Timezone.new(gnzone.name)
19
21
  end
20
22
  end
21
23
 
@@ -0,0 +1,57 @@
1
+ require 'quandl'
2
+
3
+ module Reality
4
+ module Extras
5
+ module Quandl
6
+ class Economy
7
+ def initialize(country)
8
+ @country = country
9
+ end
10
+
11
+ def gdp
12
+ gdp = fetch("ODA/#{@country.iso3_code}_NGDPD")
13
+ gdp = gdp.to_i * 1_000_000_000
14
+ Reality::Measure(gdp, '$')
15
+ end
16
+
17
+ def inflation
18
+ inflation = fetch("ODA/#{@country.iso3_code}_PCPIPCH")
19
+ Reality::Measure(inflation, '%')
20
+ end
21
+
22
+ def unemployment
23
+ unemployment = fetch("ODA/#{@country.iso3_code}_LUR")
24
+ Reality::Measure(unemployment, '%')
25
+ end
26
+
27
+ def inspect
28
+ "#<Reality::Quandl::Economy (%s)>" % [@country.name]
29
+ end
30
+
31
+ private
32
+
33
+ def fetch(code)
34
+ ::Quandl::ApiConfig.api_key ||= Reality.config.fetch('keys', 'quandl')
35
+ database, dataset = code.split('/')
36
+ data = ::Quandl::Data.all({ params: { database_code: database, dataset_code: dataset }})
37
+ data.values.select { |v| v['date'] <= Date.today }.sort_by { |d| d['date'] }.last.value
38
+ rescue ::Quandl::NotFoundError
39
+ nil
40
+ end
41
+ end
42
+
43
+ module CountryEconomics
44
+ def economy
45
+ @economy ||= Extras::Quandl::Economy.new(self)
46
+ end
47
+ end
48
+
49
+ def self.included(reality)
50
+ reality.config.register('keys', 'quandl',
51
+ desc: 'Quandl API key. Can be obtained here: http://quandl.com')
52
+
53
+ reality::Country.include CountryEconomics
54
+ end
55
+ end
56
+ end
57
+ end
@@ -4,11 +4,30 @@ Geokit::default_units = :kms # TODO: use global settings
4
4
 
5
5
  module Reality
6
6
  module Geo
7
- # GeoKit, RGeo, GeoRuby -- I know, ok?
8
- # It's just incredibly simple class to hold two values
9
- # and show them prettily.
7
+ # Keeps information about coordinates point.
8
+ # All services that are based on particular point in space (Weather, Time) depend on it.
9
+ # Many Entity types can have coordinates, they can be accessed as #coord method which returns this class.
10
+ #
11
+ # Example:
12
+ #
13
+ # ```ruby
14
+ # Reality::Entity('Mississippi').coord
15
+ # # => #<Reality::Geo::Coord(33°0′0″N,90°0′0″W)>
16
+ # ```
17
+ #
18
+ # Usage examples:
19
+ #
20
+ # ```ruby
21
+ # coord = Reality::Geo::Coord.new(50.45, 30.52)
22
+ # # => #<Reality::Geo::Coord(50°27′0″N,30°31′24″E)>
23
+ # coord.sunrise
24
+ # # => 2016-03-31 03:35:22 UTC
25
+ # coord.distance_to(Reality::Entity('London'))
26
+ # # => #<Reality::Measure(2,135 km)>
27
+ # ```
28
+ #
29
+ # Uses Geokit for some operations like #distance_to, #close_to? etc.
10
30
  #
11
- # GeoKit will be used at its time.
12
31
  class Coord
13
32
  attr_reader :lat, :lng
14
33
 
@@ -44,33 +63,52 @@ module Reality
44
63
  sign * (d.abs.to_i + Rational(m.to_i) / 60 + Rational(s.to_f) / 3600)
45
64
  end
46
65
  end
47
-
66
+
67
+ # @param latitude
68
+ # @param longitude
48
69
  def initialize(lat, lng)
49
70
  @lat, @lng = Rational(lat), Rational(lng)
50
71
  end
51
72
 
73
+ # @param point - Coordinates, e.g "50.45,30.52", [50.45, 30.52], Coord or Entity instance
74
+ #
75
+ # @return [Reality::Measure]
52
76
  def distance_to(point)
53
77
  destination_coords = normalize_point(point).to_s
54
78
  res = Geokit::LatLng.distance_between(to_s, destination_coords, formula: :sphere)
55
79
  Reality::Measure(res, 'km')
56
80
  end
57
81
 
82
+ # @param point - Coordinates, e.g "50.45,30.52", [50.45, 30.52], Coord or Entity instance
83
+ #
84
+ # @return [Reality::Measure]
58
85
  def direction_to(point)
59
86
  destination_coords = normalize_point(point).to_s
60
87
  res = Geokit::LatLng.heading_between(to_s, destination_coords)
61
88
  Reality::Measure(res, '°')
62
89
  end
63
90
 
91
+ # @param direction - angle from the North by clock, e.g. '90' means 'to the east'
92
+ # @param distance - km, e.g. 100
93
+ #
94
+ # @return [Reality::Geo::Coord]
64
95
  def endpoint(direction, distance)
65
96
  res = Geokit::LatLng.endpoint(to_s, direction.to_f, distance.to_f)
66
97
  Coord.new res.lat, res.lng
67
98
  end
68
99
 
100
+ # @param point - Coordinates, e.g "50.45,30.52", [50.45, 30.52], Coord or Entity instance
101
+ # @param radius - km, e.g. 10
102
+ #
103
+ # @return [true, false]
69
104
  def close_to?(point, radius)
70
105
  area = Geokit::Bounds.from_point_and_radius(to_s, radius.to_f)
71
106
  area.contains?(normalize_point(point).to_s)
72
107
  end
73
108
 
109
+ # Latitude in "degrees minutes seconds" format
110
+ #
111
+ # @return [Array]
74
112
  def lat_dms(direction = true)
75
113
  seconds = (lat.abs % 1.0) * 3600.0
76
114
  d, m, s = lat.to_i, (seconds / 60).to_i, (seconds % 60)
@@ -81,6 +119,9 @@ module Reality
81
119
  end
82
120
  end
83
121
 
122
+ # Longitude in "degrees minutes seconds" format
123
+ #
124
+ # @return [Array]
84
125
  def lng_dms(direction = true)
85
126
  seconds = (lng.abs % 1.0) * 3600.0
86
127
  d, m, s = lng.to_i, (seconds / 60).to_i, (seconds % 60)
@@ -94,13 +135,18 @@ module Reality
94
135
  def to_s
95
136
  "#{lat.to_f},#{lng.to_f}"
96
137
  end
138
+ alias_method :latlng, :to_s
139
+
140
+ def to_h
141
+ {lat: lat.to_f, lng: lng.to_f}
142
+ end
97
143
 
98
144
  def ==(other)
99
145
  other.is_a?(self.class) && lat == other.lat && lng == self.lng
100
146
  end
101
147
 
102
148
  def inspect
103
- "#<%s(%i°%i′%i″%s,%i°%i′%i″%s)>" % [self.class, *lat_dms, *lng_dms]
149
+ "#<%s(%i°%i′%.0f″%s,%i°%i′%.0f″%s)>" % [self.class, *lat_dms, *lng_dms]
104
150
  end
105
151
 
106
152
  def sunrise(date = Date.today)
@@ -111,8 +157,23 @@ module Reality
111
157
  SunTimes.new.set(date, lat.to_f, lng.to_f)
112
158
  end
113
159
 
160
+ # Links to popular maps sites with this point marked
161
+ # Maps: openstreetmap, google maps, wikimapia
162
+ #
163
+ # @return [Hash]
164
+ def links
165
+ param = {lat: lat.to_f, lng: lng.to_f, latlng: latlng}
166
+ Hashie::Mash.new(EXTERNAL_LINKS.map{|key, pattern| [key, pattern % param]}.to_h)
167
+ end
168
+
114
169
  private
115
170
 
171
+ EXTERNAL_LINKS = {
172
+ osm: "https://www.openstreetmap.org/?mlat=%{lat}&mlon=%{lng}&zoom=12&layers=M",
173
+ google: "https://maps.google.com/maps?ll=%{latlng}&q=%{latlng}&hl=en&t=m&z=12",
174
+ wikimapia: "http://wikimapia.org/#lang=en&lat=%{lat}&lon=%{lng}&z=12&m=w"
175
+ }
176
+
116
177
  def normalize_point(point)
117
178
  return point if point.is_a?(Coord)
118
179
  point.coord if point.respond_to?(:coord)
@@ -1,16 +1,59 @@
1
1
  module Reality
2
+ # List is subclass of Array, which allows effective entities storing
3
+ # and loading.
4
+ #
5
+ # You can create list from entities or just their names:
6
+ #
7
+ # ```ruby
8
+ # Reality::List.new('Argentina', 'Ukraine')
9
+ # # => #<Reality::List[Argentina?, Ukraine?]>
10
+ # ```
11
+ #
12
+ # ...or from existing entities:
13
+ #
14
+ # ```ruby
15
+ # Reality::List.new(Reality::Entity.new('Argentina'), Reality::Entity.new('Ukraine'))
16
+ # # => #<Reality::List[Argentina?, Ukraine?]>
17
+ # ```
18
+ #
19
+ # List is useful for compact inspect (see above), and for effective
20
+ # batch loading (see {#load!}).
21
+ #
22
+ # Also, List is smart enough to remain List on multiple enumerable
23
+ # methods like `#select`, `#reject`, `#map` and so on:
24
+ #
25
+ # ```ruby
26
+ # Reality::List.new('Argentina', 'Bolivia', 'Chile').sample(2)
27
+ # # => #<Reality::List[Chile?, Argentina?]>
28
+ #
29
+ # Reality::List.new('Argentina', 'Bolivia', 'Chile').load!.map(&:capital)
30
+ # # => #<Reality::List[Buenos Aires?, La Paz?, Santiago?]>
31
+ # ```
32
+ #
2
33
  class List < Array
3
34
  using Refinements
4
-
35
+
36
+ # Creates List from a set of entity names or entity objects.
37
+ # Also aliased as `Reality::List()` method.
38
+ #
5
39
  def initialize(*names)
6
40
  super names.map(&method(:coerce))
7
41
  end
8
42
 
43
+ # Loads all entities in batch. Optimized to make as few requests
44
+ # as possible. Typically, when you want to load several entities
45
+ #
46
+ # @return [self]
9
47
  def load!
10
- partition(&:wikidata_id).tap{|wd, wp|
48
+ compact.partition(&:wikidata_id).tap{|wd, wp|
11
49
  load_by_wikipedia(wp)
12
50
  load_by_wikidata(wd)
13
51
  }
52
+ # try to fallback to labels:
53
+ compact.reject(&:loaded?).tap{|entities|
54
+ load_by_wikidata_labels(entities)
55
+ }
56
+
14
57
  self
15
58
  end
16
59
 
@@ -21,10 +64,24 @@ module Reality
21
64
  }
22
65
  end
23
66
 
67
+ # @return [String]
24
68
  def inspect
25
- "#<#{self.class.name}[#{map(&:to_s?).join(', ')}]>"
69
+ "#<#{self.class.name}[#{map{|e| e ? e.to_s? : e.inspect}.join(', ')}]>"
26
70
  end
27
71
 
72
+ # Prints compact description of the list. Implicitly loads all list
73
+ # if not loaded.
74
+ #
75
+ # ```ruby
76
+ # Reality::List.new('Argentina', 'Bolivia', 'Chile').describe
77
+ # # -------------------------
78
+ # # #<Reality::List(3 items)>
79
+ # # -------------------------
80
+ # # keys: adm_divisions (3), area (3), calling_code (3), capital (3), continent (3), coord (3), country (3), created_at (3), currency (3), gdp_nominal (3), gdp_ppp (3), head_of_government (2), head_of_state (3), highest_point (3), iso2_code (3), iso3_code (3), long_name (3), neighbours (3), official_website (1), organizations (3), part_of (3), population (3), tld (3), tz_offset (3)
81
+ # # types: country (3)
82
+ # ```
83
+ #
84
+ # @return [nil]
28
85
  def describe
29
86
  load! unless all?(&:loaded?)
30
87
 
@@ -50,7 +107,7 @@ module Reality
50
107
 
51
108
  pages = Infoboxer.wp.get_h(*entities.map(&:name))
52
109
  datum = Wikidata::Entity.
53
- fetch_list(*pages.values.compact.map(&:title))
110
+ by_wikititle(*pages.values.compact.map(&:title))
54
111
 
55
112
  entities.each do |entity|
56
113
  page = pages[entity.name]
@@ -63,7 +120,7 @@ module Reality
63
120
  return if entities.empty?
64
121
 
65
122
  datum = Wikidata::Entity.
66
- fetch_list_by_id(*entities.map(&:wikidata_id))
123
+ by_id(*entities.map(&:wikidata_id))
67
124
  pages = Infoboxer.wp.
68
125
  get_h(*datum.values.compact.map(&:en_wikipage).compact)
69
126
  entities.each do |entity|
@@ -73,8 +130,19 @@ module Reality
73
130
  end
74
131
  end
75
132
 
133
+ def load_by_wikidata_labels(entities)
134
+ return if entities.empty?
135
+
136
+ datum = Wikidata::Entity.
137
+ by_label(*entities.map(&:name))
138
+ entities.each do |entity|
139
+ data = datum[entity.name]
140
+ entity.setup!(wikidata: data)
141
+ end
142
+ end
143
+
76
144
  def ensure_type(arr)
77
- if arr.kind_of?(Array) && arr.all?{|e| e.is_a?(Entity)}
145
+ if arr.kind_of?(Array) && arr.all?{|e| e.nil? || e.is_a?(Entity)}
78
146
  List[*arr]
79
147
  else
80
148
  arr
@@ -83,6 +151,8 @@ module Reality
83
151
 
84
152
  def coerce(val)
85
153
  case val
154
+ when nil
155
+ val
86
156
  when String
87
157
  Entity.new(val)
88
158
  when Entity
@@ -1,4 +1,10 @@
1
1
  module Reality
2
+ # Wrapper for numeric values.
3
+ # Example:
4
+ # Reality::Entity.new('Ukraine', load: true).area
5
+ # => #<Reality::Measure(603,500 km²)>
6
+ # Keeps information about value unit
7
+ # Allows coercion and general Numeric operations
2
8
  class Measure
3
9
  %w[unit].each{|mod| require_relative "measure/#{mod}"}
4
10
 
@@ -7,7 +13,9 @@ module Reality
7
13
  def Measure.coerce(amount, unit)
8
14
  amount && unit && new(amount, unit)
9
15
  end
10
-
16
+
17
+ # @param amount - numeric value, e.g. 100.5
18
+ # @param unit - can be any string, e.g. 'km', '$'
11
19
  def initialize(amount, unit)
12
20
  @amount, @unit = Rational(amount), Unit.parse(unit)
13
21
  end
@@ -18,10 +26,6 @@ module Reality
18
26
  amount <=> other.amount
19
27
  end
20
28
 
21
- def ==(other)
22
- amount == other.amount && unit == other.unit
23
- end
24
-
25
29
  def -@
26
30
  self.class.new(-amount, unit)
27
31
  end
@@ -65,12 +69,20 @@ module Reality
65
69
  (num-1).times.inject(self){|res| res*self}
66
70
  end
67
71
 
72
+ def abs
73
+ self.class.new(amount.abs, unit)
74
+ end
75
+
68
76
  include Comparable
69
77
 
70
78
  def to_s
71
79
  '%s%s' % [Util::Format.number(amount), unit]
72
80
  end
73
81
 
82
+ def to_h
83
+ {amount: amount.to_f, unit: unit.to_s}
84
+ end
85
+
74
86
  def to_f
75
87
  amount.to_f
76
88
  end
@@ -91,8 +103,4 @@ module Reality
91
103
  end
92
104
  end
93
105
  end
94
-
95
- def Reality.Measure(*arg)
96
- Measure.new(*arg)
97
- end
98
106
  end