reality 0.0.3 → 0.0.4

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.
@@ -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