natour 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,46 @@
1
+ require 'uri'
2
+ require 'net/http'
3
+ require 'openssl'
4
+ require 'json'
5
+
6
+ module Natour
7
+ class PublicTransport
8
+ def self.search_station(position, radius: 200)
9
+ position = position.join(',') if position.is_a?(Array)
10
+ uri = URI('https://timetable.search.ch/api/completion.json')
11
+ uri.query = URI.encode_www_form(latlon: position.gsub(' ', ''))
12
+ http = Net::HTTP.new(uri.host, uri.port)
13
+ http.use_ssl = true
14
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
15
+ response = http.request(Net::HTTP::Get.new(uri))
16
+ return unless response.is_a?(Net::HTTPSuccess)
17
+
18
+ stations = JSON.parse(response.body, symbolize_names: true)
19
+ stations.reject! { |station| station[:dist] > radius }
20
+ station_types = %w[
21
+ sl-icon-type-train
22
+ sl-icon-type-strain
23
+ sl-icon-type-tram
24
+ sl-icon-type-bus
25
+ sl-icon-type-ship
26
+ sl-icon-type-funicular
27
+ sl-icon-type-cablecar
28
+ sl-icon-type-gondola
29
+ sl-icon-type-chairlift
30
+ ]
31
+
32
+ stations = station_types.map do |station_type|
33
+ stations.find { |station| station[:iconclass] == station_type }
34
+ end
35
+
36
+ station = stations.compact.first
37
+ return unless station
38
+
39
+ Station.new(
40
+ station[:label],
41
+ station[:iconclass].delete_prefix('sl-icon-type-').to_sym,
42
+ station[:dist]
43
+ )
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,93 @@
1
+ require 'pathname'
2
+
3
+ module Natour
4
+ class Report
5
+ attr_reader :path
6
+ attr_reader :title
7
+ attr_reader :images
8
+ attr_reader :species_lists
9
+ attr_reader :gps_track
10
+ attr_reader :map_image
11
+ attr_reader :starting_point
12
+ attr_reader :arrival_point
13
+
14
+ def initialize(path,
15
+ title,
16
+ images,
17
+ species_lists,
18
+ gps_track: nil,
19
+ map_image: nil,
20
+ starting_point: nil,
21
+ arrival_point: nil)
22
+ @path = path
23
+ @title = title
24
+ @images = images
25
+ @species_lists = species_lists
26
+ @gps_track = gps_track
27
+ @map_image = map_image
28
+ @starting_point = starting_point
29
+ @arrival_point = arrival_point
30
+ end
31
+
32
+ def self.load_directory(dir, track_formats: %i[gpx fit], create_map: true, map_layers: [])
33
+ Dir.chdir(dir) do
34
+ path = Pathname(dir)
35
+ title = Pathname.pwd.basename.to_s.encode('utf-8')
36
+ .gsub(/^\d{4}-\d{2}-\d{2}( |_|-)?/, '')
37
+ images = Pathname.glob('**/*.{jpg,jpeg}', File::FNM_CASEFOLD)
38
+ .map { |filename| Image.new(filename.to_s) }
39
+ .sort_by { |image| [image.date_time ? 0 : 1, image.date_time, image.path] }
40
+ species_lists =
41
+ Pathname.glob('**/*.csv', File::FNM_CASEFOLD)
42
+ .map { |filename| SpeciesList.load_file(filename.to_s) }
43
+ .flatten
44
+ .sort_by { |species_list| [species_list.type, species_list.date ? 0 : 1, species_list.date] }
45
+ gps_tracks = if track_formats.empty?
46
+ []
47
+ else
48
+ Pathname.glob("**/*.{#{track_formats.join(',')}}", File::FNM_CASEFOLD)
49
+ .map { |filename| GPSTrack.load_file(filename.to_s) }
50
+ end
51
+
52
+ if create_map
53
+ map_images = MapGeoAdmin.open do |map|
54
+ Dir.mktmpdir do |tmp_dir|
55
+ gps_tracks.map do |gps_track|
56
+ track = Pathname(tmp_dir).join(gps_track.path).sub_ext('.gpx')
57
+ gps_track.save_gpx(track, overwrite: true)
58
+ filename = Pathname(gps_track.path).sub_ext('.jpg')
59
+ map.save_image(filename, tracks: [track], layers: map_layers)
60
+ Image.new(filename.to_s)
61
+ end
62
+ end
63
+ end
64
+ end
65
+
66
+ if gps_tracks.empty?
67
+ [Report.new(path.to_s, title, images, species_lists)]
68
+ else
69
+ gps_tracks.zip(map_images.to_a).map do |gps_track, map_image|
70
+ starting_station = PublicTransport.search_station(
71
+ [gps_track.start_point.latitude, gps_track.start_point.longitude]
72
+ )
73
+ arrival_station = PublicTransport.search_station(
74
+ [gps_track.end_point.latitude, gps_track.end_point.longitude]
75
+ )
76
+ Report.new(
77
+ path.to_s,
78
+ title,
79
+ images.select { |image| !image.date_time || image.date_time.to_date == gps_track.date },
80
+ species_lists.select { |species_list| !species_list.date || species_list.date == gps_track.date },
81
+ gps_track: gps_track.round_effective_km!,
82
+ map_image: map_image,
83
+ starting_point: starting_station&.label,
84
+ arrival_point: arrival_station&.label
85
+ )
86
+ end
87
+ end
88
+ end
89
+ end
90
+
91
+ include Asciinurse
92
+ end
93
+ end
@@ -0,0 +1,24 @@
1
+ module Natour
2
+ class Species
3
+ attr_reader :name
4
+ attr_reader :name_de
5
+
6
+ def initialize(name, name_de)
7
+ @name = name
8
+ @name_de = name_de
9
+ end
10
+
11
+ include Comparable
12
+
13
+ def <=>(other)
14
+ [@name, @name_de] <=>
15
+ [other.name, other.name_de]
16
+ end
17
+
18
+ def hash
19
+ [@name, @name_de].hash
20
+ end
21
+
22
+ alias eql? ==
23
+ end
24
+ end
@@ -0,0 +1,77 @@
1
+ require 'csv'
2
+ require 'pathname'
3
+
4
+ module Natour
5
+ class SpeciesList
6
+ attr_reader :path
7
+ attr_reader :date
8
+ attr_reader :type
9
+ attr_reader :name
10
+ attr_reader :description
11
+
12
+ def initialize(path, date, type, name, description, items)
13
+ @path = path
14
+ @date = date
15
+ @type = type
16
+ @name = name
17
+ @description = description
18
+ @items = items
19
+ end
20
+
21
+ def self.load_file(filename)
22
+ block = IO.binread(filename, 32)
23
+ header = if block.unpack('CC') == [0xff, 0xfe]
24
+ block[2..-1].force_encoding('utf-16le').encode('utf-8')
25
+ elsif block.unpack('CCC') == [0xef, 0xbb, 0xbf]
26
+ block[3..-1].force_encoding('utf-8')
27
+ else
28
+ block.force_encoding('utf-8')
29
+ end
30
+
31
+ case header
32
+ when /^Primary/
33
+ CSV.open(filename, 'r:windows-1252:utf-8', headers: true, liberal_parsing: true) do |csv|
34
+ date = DateParser.parse(Pathname(filename).basename).compact.first
35
+ items = csv.map { |row| Species.new(row[1], row[0]) }
36
+ .sort_by(&:name_de).uniq
37
+ [SpeciesList.new(filename, date, :kosmos_vogelfuehrer, nil, nil, items)]
38
+ end
39
+ when /^Favoriten/
40
+ CSV.open(filename, 'r:bom|utf-8', col_sep: ';', skip_blanks: true) do |csv|
41
+ chunks = csv.reject { |row| row.count == 1 && row[0] != 'Favoriten' }
42
+ .reject { |row| row.count == 4 && row[0] == 'NUMMER_FLORA' }
43
+ .slice_before { |row| row.count == 1 || row.count == 3 }
44
+ .reject { |rows| rows.count == 1 }
45
+ chunks.map do |rows|
46
+ name, description = rows.shift
47
+ date = DateParser.parse(name, Pathname(filename).basename).compact.first
48
+ items = rows.map { |row| Species.new(row[1][/^(([^ ]+ [^ ]+)(( aggr\.)|( subsp\. [^ ]+))?)/, 1], row[2]) }
49
+ .sort_by(&:name).uniq
50
+ SpeciesList.new(
51
+ filename,
52
+ date,
53
+ :flora_helvetica,
54
+ name&.gsub(/^(\d{4}-)?\d{2}-\d{2}( |_|-)?/, ''),
55
+ description,
56
+ items
57
+ )
58
+ end
59
+ end
60
+ when /^obs_id/
61
+ CSV.open(filename, 'r:bom|utf-16le:utf-8', col_sep: "\t", headers: true) do |csv|
62
+ date = DateParser.parse(Pathname(filename).basename).compact.first
63
+ items = csv.select { |row| row[0] }
64
+ .map { |row| Species.new(row[11][/^(([^ ]+ [^ ]+)(( aggr\.)|( subsp\. [^ ]+))?)/, 1], nil) }
65
+ .sort_by(&:name).uniq
66
+ [SpeciesList.new(filename, date, :info_flora, nil, nil, items)]
67
+ end
68
+ end
69
+ end
70
+
71
+ include Enumerable
72
+
73
+ def each(&block)
74
+ @items.each(&block)
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,26 @@
1
+ module Natour
2
+ class Station
3
+ attr_reader :label
4
+ attr_reader :type
5
+ attr_reader :distance
6
+
7
+ def initialize(label, type, distance)
8
+ @label = label
9
+ @type = type
10
+ @distance = distance
11
+ end
12
+
13
+ include Comparable
14
+
15
+ def <=>(other)
16
+ [@label, @type, @distance] <=>
17
+ [other.label, other.type, other.distance]
18
+ end
19
+
20
+ def hash
21
+ [@label, @type, @distance].hash
22
+ end
23
+
24
+ alias eql? ==
25
+ end
26
+ end
metadata ADDED
@@ -0,0 +1,223 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: natour
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Simon Gysi
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-12-04 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: asciidoctor
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: asciidoctor-pdf
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.5'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.5'
41
+ - !ruby/object:Gem::Dependency
42
+ name: concurrent-ruby
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.1'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.1'
55
+ - !ruby/object:Gem::Dependency
56
+ name: ferrum
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.9'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.9'
69
+ - !ruby/object:Gem::Dependency
70
+ name: fit4ruby
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.7'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.7'
83
+ - !ruby/object:Gem::Dependency
84
+ name: nokogiri
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '1.10'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '1.10'
97
+ - !ruby/object:Gem::Dependency
98
+ name: ruby-duration
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '3.2'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '3.2'
111
+ - !ruby/object:Gem::Dependency
112
+ name: ruby-vips
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '2.0'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '2.0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: timeliness
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '0.4'
132
+ type: :runtime
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '0.4'
139
+ - !ruby/object:Gem::Dependency
140
+ name: word_wrap
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '1.0'
146
+ type: :runtime
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: '1.0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: rubocop
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: '1.2'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: '1.2'
167
+ description:
168
+ email: simon.gysi@gmail.com
169
+ executables:
170
+ - natour
171
+ extensions: []
172
+ extra_rdoc_files: []
173
+ files:
174
+ - CHANGELOG.adoc
175
+ - LICENSE
176
+ - README.adoc
177
+ - bin/natour
178
+ - lib/natour.rb
179
+ - lib/natour/asciinurse.rb
180
+ - lib/natour/cli/convert.rb
181
+ - lib/natour/cli/create.rb
182
+ - lib/natour/config.rb
183
+ - lib/natour/fit_file.rb
184
+ - lib/natour/gps_track.rb
185
+ - lib/natour/gps_track_point.rb
186
+ - lib/natour/gpx_file.rb
187
+ - lib/natour/helpers/date_parser.rb
188
+ - lib/natour/helpers/suppress_output.rb
189
+ - lib/natour/image.rb
190
+ - lib/natour/js/bootstrap.min.js
191
+ - lib/natour/js/jquery-3.5.1.slim.min.js
192
+ - lib/natour/js/loader.js
193
+ - lib/natour/map_geo_admin.rb
194
+ - lib/natour/public_transport.rb
195
+ - lib/natour/report.rb
196
+ - lib/natour/species.rb
197
+ - lib/natour/species_list.rb
198
+ - lib/natour/station.rb
199
+ homepage: https://rubygems.org/gems/natour
200
+ licenses:
201
+ - MIT
202
+ metadata:
203
+ source_code_uri: https://github.com/simongysi/natour
204
+ post_install_message:
205
+ rdoc_options: []
206
+ require_paths:
207
+ - lib
208
+ required_ruby_version: !ruby/object:Gem::Requirement
209
+ requirements:
210
+ - - ">="
211
+ - !ruby/object:Gem::Version
212
+ version: '2.4'
213
+ required_rubygems_version: !ruby/object:Gem::Requirement
214
+ requirements:
215
+ - - ">="
216
+ - !ruby/object:Gem::Version
217
+ version: '0'
218
+ requirements: []
219
+ rubygems_version: 3.0.3
220
+ signing_key:
221
+ specification_version: 4
222
+ summary: natour provides an application and a library for reports on nature activities
223
+ test_files: []