natour 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 6a465921bd4e696f168be735851b576e1ec95eca0396eb52581801a16d9c75e1
4
+ data.tar.gz: 5c0fd93836e21d57752a185842c17787894b8c56d1850564240a6f88b514dcb5
5
+ SHA512:
6
+ metadata.gz: d39f3f80ab7bc2ee632d3458d623e8f7bb517c19b2f8c3d3fcc110c15ff8c09c646e52fb59623ca3be7c7f08877e527991c5f2774c41f1f17f4752d8b7a636ff
7
+ data.tar.gz: d20556324500e26c3d428c6925f0e5ceefad9dfd790f78083e25d38fe28e27756653f5eecb5f9545b978a36a28a761dd9d257fafa8ca1ff0c75b62d9d0670308
@@ -0,0 +1,12 @@
1
+ = natour Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on https://keepachangelog.com/en/1.0.0/[Keep a Changelog^], and this project adheres to https://semver.org/spec/v2.0.0.html[Semantic Versioning^].
6
+
7
+ == Unreleased
8
+
9
+ == 0.1.0 - 2020-12-04
10
+
11
+ === Added
12
+ - First release
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2020 Simon Gysi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,122 @@
1
+ = natour
2
+
3
+ This https://rubygems.org/[RubyGem^] provides an application and a library for reports on nature activities.
4
+
5
+ == Installation
6
+
7
+ . Install the <<Prerequisites>>
8
+ . Install the gem
9
+ +
10
+ NOTE: Use `sudo` on Ubuntu
11
+ +
12
+ [source,shell]
13
+ ----
14
+ gem install natour
15
+ ----
16
+
17
+ === Prerequisites
18
+
19
+ Install the required components by following the instructions below. Please read the installation guide of the components if you encounter any problems or if your OS is not listed below (e.{nbsp}g. macOS).
20
+
21
+ ==== Windows
22
+
23
+ . Download and install the latest version from https://rubyinstaller.org/[RubyInstaller for Windows^]
24
+ . Download and install the latest version from https://www.google.com/chrome/[Google Chrome-Webbroser^]
25
+ . Add `BROWSER_PATH=path\to\chrome.exe` to the environment variables
26
+ . Log off Windows and back on again to ensure the environment variable takes effect
27
+
28
+ ==== Ubuntu
29
+
30
+ . Install https://www.ruby-lang.org/[Ruby^]
31
+ +
32
+ [source,shell]
33
+ ----
34
+ sudo apt install ruby
35
+ ----
36
+
37
+ . Install https://nokogiri.org/[Nokogiri^] dependencies
38
+ +
39
+ [source,shell]
40
+ ----
41
+ sudo apt install build-essential patch ruby-dev zlib1g-dev liblzma-dev
42
+ ----
43
+
44
+ . Install https://libvips.github.io/libvips/[libvips^]
45
+ +
46
+ [source,shell]
47
+ ----
48
+ sudo apt install libvips42
49
+ ----
50
+
51
+ . Install https://www.google.com/chrome/[Google Chrome-Webbroser^]
52
+ +
53
+ [source,shell]
54
+ ----
55
+ wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
56
+ sudo apt install ./google-chrome-stable_current_amd64.deb
57
+ ----
58
+
59
+ == Application
60
+
61
+ [cols="1,4",options=header]
62
+ |===
63
+ |Command
64
+ |Description
65
+
66
+ |`natour`
67
+ |Create reports in https://asciidoc.org/[AsciiDoc^] format from a directory with GPS tracks, images and species lists. After editing the AsciiDoc files, they can be converted to various formats, including PDF and HTML. Use `natour --help` to get more information.
68
+ |===
69
+
70
+ === Configuration
71
+
72
+ The configuration is built by loading https://yaml.org/[YAML^] files in hierarchical order. The files must be named `natour.yml` and are loaded first from the home directory of the current user and then from the current working directory. The values of the previously loaded files are overwritten by the values of the files loaded later. The key-value pairs are stored internally in a Ruby Hash, where the keys correspond to the command line arguments without leading dashes and without `no-` prefix for boolean switches.
73
+
74
+ How to restrict GPS tracks to the FIT format (see https://developer.garmin.com/fit/[FIT SDK^]) and add additional map layers (see https://api3.geo.admin.ch/api/faq/index.html#which-layers-are-available[API FAQ - GeoAdmin API 3.0 documentation^]) is shown in the example below.
75
+
76
+ [source,yml]
77
+ ----
78
+ ---
79
+ track-formats:
80
+ - !ruby/sym fit
81
+ map-layers:
82
+ - ch.swisstopo.swisstlm3d-wanderwege
83
+ - ch.bav.haltestellen-oev
84
+ ----
85
+
86
+ == Library
87
+
88
+ === Basic Use
89
+
90
+ The basic use is shown in the following example. For details, see the source code and the unit tests.
91
+
92
+ [source,ruby]
93
+ ----
94
+ require 'natour'
95
+
96
+ Natour::create('path/to/dir')
97
+ ----
98
+
99
+ [source,ruby]
100
+ ----
101
+ require 'natour'
102
+
103
+ Natour::convert('path/to/dir/report.adoc')
104
+ ----
105
+
106
+ === Testing
107
+
108
+ Execute the following command to run the tests. Add `-Ilib` to the command to run the tests against the library sources instead of the installed gem.
109
+
110
+ NOTE: Some tests require an Internet connection.
111
+
112
+ [source,shell]
113
+ ----
114
+ ruby ./test/test.rb
115
+ ----
116
+
117
+ Run the following command to check the style and formatting by https://rubocop.org/[RuboCop^].
118
+
119
+ [source,shell]
120
+ ----
121
+ rubocop
122
+ ----
@@ -0,0 +1,111 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'word_wrap'
4
+ require 'pathname'
5
+ require 'optparse'
6
+ require 'natour'
7
+
8
+ config = Natour::Config.load_file(
9
+ Pathname($PROGRAM_NAME).basename.sub_ext('.yml'),
10
+ default: {
11
+ 'out-dir' => nil,
12
+ 'out-file' => nil,
13
+ 'overwrite' => false,
14
+ 'track-formats' => %i[gpx fit],
15
+ 'map' => true,
16
+ 'map-layers' => [],
17
+ 'adoc-author' => nil,
18
+ 'backend' => 'pdf',
19
+ 'draft' => false,
20
+ 'image-maxdim' => 1800
21
+ }
22
+ )
23
+
24
+ option_parser = OptionParser.new do |opts|
25
+ opts.banner = "Usage: #{Pathname($PROGRAM_NAME).basename('.*')} [OPTIONS]... PATH"
26
+ opts.separator('')
27
+ opts.separator(WordWrap.ww(
28
+ 'If PATH refers to a directory, one or more reports are created in ' \
29
+ 'AsciiDoc format. GPS tracks, images and species lists (Kosmos ' \
30
+ 'Vogelführer, Flora Helvetica, Info Flora Online-Feldbuch) are ' \
31
+ 'included. Images of the map section covered by the GPS tracks are also ' \
32
+ 'created and included. If PATH refers to an AsciiDoc file, it is ' \
33
+ 'converted to PDF (or any other supported format).'
34
+ ))
35
+ opts.separator('')
36
+ opts.separator('General:')
37
+ opts.on('-h', '--help', 'Display this help screen') do
38
+ $stdout.puts(opts)
39
+ exit
40
+ end
41
+ opts.on('-d', '--out-dir DIR', 'Output directory') do |value|
42
+ config['out-dir'] = value
43
+ end
44
+ opts.on('-f', '--out-file FILE', 'Output filename') do |value|
45
+ config['out-file'] = value
46
+ end
47
+ opts.on('--[no-]overwrite', 'Overwrite existing files') do |value|
48
+ config['overwrite'] = value
49
+ end
50
+ opts.separator('')
51
+ opts.separator('Creation:')
52
+ opts.on('--track-formats FORMATS', Array, 'GPS track formats (gpx, fit') do |value|
53
+ config['track-formats'] = value.map(&:to_sym)
54
+ end
55
+ opts.on('--[no-]map', 'Create map images') do |value|
56
+ config['map'] = value
57
+ end
58
+ opts.on('--map-layers LAYERS', Array, 'Add additional layers to map images') do |value|
59
+ config['map-layers'] = value
60
+ end
61
+ opts.on('--adoc-author AUTHOR', 'AsciiDoc author (name <email>[; name2 <email2>...])') do |value|
62
+ config['adoc-author'] = value
63
+ end
64
+ opts.separator('')
65
+ opts.separator('Conversion:')
66
+ opts.on('-b', '--backend BACKEND', 'Conversion backend (pdf, html5 ...)') do |value|
67
+ config['backend'] = value
68
+ end
69
+ opts.on('--[no-]draft', 'Show additional information (e.g. image paths)') do |value|
70
+ config['draft'] = value
71
+ end
72
+ opts.on('--image-maxdim DIM', 'Shrink oversized images (PDF only)') do |value|
73
+ config['image-maxdim'] = value.to_i
74
+ end
75
+ end
76
+
77
+ begin
78
+ option_parser.parse!
79
+ rescue OptionParser::InvalidArgument,
80
+ OptionParser::InvalidOption => e
81
+ abort("Error: #{e}")
82
+ end
83
+
84
+ abort('Error: wrong number of arguments') if ARGV.length != 1
85
+
86
+ path = Pathname(ARGV[0].encode('utf-8')
87
+ .delete_suffix('"')
88
+ .gsub('\\', '/'))
89
+
90
+ if path.directory?
91
+ Natour.create(
92
+ path,
93
+ out_dir: config['out-dir'],
94
+ out_file: config['out-file'],
95
+ overwrite: config['overwrite'],
96
+ track_formats: config['track-formats'],
97
+ create_map: config['map'],
98
+ map_layers: config['map-layers'],
99
+ adoc_author: config['adoc-author']
100
+ )
101
+ else
102
+ Natour.convert(
103
+ path,
104
+ out_dir: config['out-dir'],
105
+ out_file: config['out-file'],
106
+ overwrite: config['overwrite'],
107
+ backend: config['backend'],
108
+ draft: config['draft'],
109
+ image_maxdim: config['image-maxdim']
110
+ )
111
+ end
@@ -0,0 +1,17 @@
1
+ require 'natour/cli/convert'
2
+ require 'natour/cli/create'
3
+ require 'natour/helpers/date_parser'
4
+ require 'natour/helpers/suppress_output'
5
+ require 'natour/asciinurse'
6
+ require 'natour/config'
7
+ require 'natour/gps_track'
8
+ require 'natour/gps_track_point'
9
+ require 'natour/gpx_file'
10
+ require 'natour/fit_file'
11
+ require 'natour/image'
12
+ require 'natour/map_geo_admin'
13
+ require 'natour/public_transport'
14
+ require 'natour/report'
15
+ require 'natour/species'
16
+ require 'natour/species_list'
17
+ require 'natour/station'
@@ -0,0 +1,126 @@
1
+ require 'fileutils'
2
+ require 'pathname'
3
+ require 'ostruct'
4
+
5
+ module Natour
6
+ module Asciinurse
7
+ def save_adoc(filename, overwrite: false, author: nil)
8
+ dir = Pathname(filename).dirname
9
+ FileUtils.mkdir_p(dir)
10
+ mode = File::WRONLY | File::CREAT | File::TRUNC
11
+ mode |= File::EXCL unless overwrite
12
+ File.open(filename, mode) do |file|
13
+ file.write(to_adoc(
14
+ doc_root: Pathname(path).realpath
15
+ .relative_path_from(dir.realpath)
16
+ .to_s.force_encoding('utf-8'),
17
+ author: author
18
+ ))
19
+ end
20
+ end
21
+
22
+ def to_adoc(doc_root: '.', author: nil)
23
+ distance = ->(gps_track) { "#{gps_track.distance / 1000} km" if gps_track&.distance }
24
+ ascent = ->(gps_track) { "#{gps_track.ascent} m" if gps_track&.ascent }
25
+ descent = ->(gps_track) { "#{gps_track.descent} m" if gps_track&.descent }
26
+
27
+ title_image = images.find(&:landscape?)
28
+
29
+ doc = []
30
+ doc << "= #{title}"
31
+ if author
32
+ doc << author
33
+ doc << Date.today.strftime('%d.%m.%Y')
34
+ end
35
+ doc << ':figure-caption!:'
36
+ doc << ':pdf-page-mode: none'
37
+ doc << ':title-page:'
38
+ if title_image
39
+ doc << ":title-image: #{Pathname(doc_root).join(title_image.path)}"
40
+ doc << ':title-logo-image: image::{title-image}[top=0%]'
41
+ doc << ''
42
+ doc << 'ifndef::backend-pdf[]'
43
+ doc << 'image::{title-image}[]'
44
+ doc << 'endif::[]'
45
+ end
46
+ doc << ''
47
+ doc << '<<<'
48
+ doc << ''
49
+ doc << '== Allgemein'
50
+ doc << ''
51
+ doc << '[cols="h,3"]'
52
+ doc << '|==='
53
+ doc << "|Datum |#{gps_track&.date&.strftime('%d.%m.%Y')}"
54
+ doc << "|Startzeit |#{gps_track&.start_point&.time&.strftime('%H:%M')}"
55
+ doc << "|Dauer |#{gps_track&.duration&.strftime('%thh%M')}"
56
+ doc << "|Strecke |#{distance.call(gps_track)}"
57
+ doc << "|Aufstieg |#{ascent.call(gps_track)}"
58
+ doc << "|Abstieg |#{descent.call(gps_track)}"
59
+ doc << "|Ausgangsort|#{starting_point}"
60
+ doc << "|Ankunftsort|#{arrival_point}"
61
+ doc << '|Teilnehmer |'
62
+ doc << '|==='
63
+ doc << ''
64
+ if map_image
65
+ doc << "image::#{Pathname(doc_root).join(map_image.path)}[]"
66
+ doc << ''
67
+ end
68
+ doc << '<<<'
69
+ doc << ''
70
+ doc << '== Beschreibung'
71
+ doc << ''
72
+ doc << ''
73
+ doc << ''
74
+ unless images.empty?
75
+ doc << '<<<'
76
+ doc << ''
77
+ doc << '== Bilder'
78
+ doc << ''
79
+ images.each do |image|
80
+ width = if image.portrait?
81
+ '40%'
82
+ else
83
+ '80%'
84
+ end
85
+ doc << '.Abbildung {counter:image}'
86
+ doc << "image::#{Pathname(doc_root).join(image.path)}[width=#{width}]"
87
+ doc << ''
88
+ end
89
+ end
90
+ unless species_lists.empty?
91
+ birds_info = OpenStruct.new(
92
+ title: 'Vogelarten',
93
+ headers: %w[Deutscher\ Name Wissenschaftlicher\ Name],
94
+ columns: %i[name_de name]
95
+ )
96
+ plants_info = OpenStruct.new(
97
+ title: 'Pflanzenarten',
98
+ headers: %w[Wissenschaftlicher\ Name Deutscher\ Name],
99
+ columns: %i[name name_de]
100
+ )
101
+ doc << '<<<'
102
+ doc << ''
103
+ doc << '== Artenlisten'
104
+ doc << ''
105
+ species_lists.each.with_index(1) do |species_list, index|
106
+ info = {
107
+ kosmos_vogelfuehrer: birds_info,
108
+ flora_helvetica: plants_info,
109
+ info_flora: plants_info
110
+ }[species_list.type]
111
+ doc << "=== #{info.title}"
112
+ doc << ''
113
+ doc << '[cols="1,5,5",options=header]'
114
+ doc << '|==='
115
+ doc << "|Nr.|#{info.headers.join('|')}"
116
+ species_list.each do |species|
117
+ doc << "|{counter:species_list#{index}}|#{info.columns.map { |method| species.send(method) }.join('|')}"
118
+ end
119
+ doc << '|==='
120
+ doc << ''
121
+ end
122
+ end
123
+ doc.join("\n")
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,76 @@
1
+ require 'fileutils'
2
+ require 'pathname'
3
+ require 'asciidoctor'
4
+ require 'asciidoctor-pdf'
5
+ require 'vips'
6
+
7
+ module Natour
8
+ module_function
9
+
10
+ def convert(filename, out_dir: nil, out_file: nil, overwrite: false,
11
+ backend: 'pdf', draft: false, image_maxdim: 16000)
12
+ doc = Asciidoctor.load_file(
13
+ filename,
14
+ backend: backend,
15
+ safe: :unsafe,
16
+ standalone: true
17
+ )
18
+
19
+ dir = Pathname(filename).dirname
20
+ out_dir = Pathname(out_dir || dir)
21
+ out_file = Pathname(
22
+ out_file || "#{doc.attr('docname')}#{doc.attr('outfilesuffix')}"
23
+ )
24
+
25
+ if draft
26
+ doc.find_by(context: :image).each do |node|
27
+ node.title = "#{node.title} [#{node.attr('target')}]"
28
+ end
29
+ end
30
+
31
+ if backend == 'pdf'
32
+ Dir.mktmpdir do |tmp_dir|
33
+ tmp_dir = Pathname(tmp_dir)
34
+
35
+ title_logo_image = doc.attr('title-logo-image')
36
+ if title_logo_image
37
+ target = title_logo_image[/^image:{1,2}(.*?)\[(.*?)\]$/, 1]
38
+ options = {}
39
+ options[:autorotate] = true if target =~ /\.jpe?g$/i
40
+ image = Vips::Image.new_from_file(dir.join(target).to_s, options)
41
+ scale = image_maxdim / image.size.max.to_f
42
+ image = image.resize(scale) if scale < 1.0
43
+ new_target = tmp_dir.join("title_logo_image_#{Pathname(target).basename}").to_s
44
+ suppress_output { image.write_to_file(new_target) }
45
+ doc.set_attr('title-logo-image', title_logo_image.gsub(target, new_target))
46
+ end
47
+
48
+ doc.find_by(context: :image).each.with_index do |node, index|
49
+ target = node.attr('target')
50
+ options = {}
51
+ options[:autorotate] = true if target =~ /\.jpe?g$/i
52
+ image = Vips::Image.new_from_file(dir.join(target).to_s, options)
53
+ scale = image_maxdim / image.size.max.to_f
54
+ image = image.resize(scale) if scale < 1.0
55
+ new_target = tmp_dir.join("image#{index}_#{Pathname(target).basename}").to_s
56
+ suppress_output { image.write_to_file(new_target) }
57
+ node.set_attr('target', new_target)
58
+ end
59
+
60
+ FileUtils.mkdir_p(out_dir)
61
+ mode = File::WRONLY | File::CREAT | File::TRUNC | File::BINARY
62
+ mode |= File::EXCL unless overwrite
63
+ File.open(out_dir.join(out_file), mode) do |file|
64
+ doc.write(doc.convert, file)
65
+ end
66
+ end
67
+ else
68
+ FileUtils.mkdir_p(out_dir)
69
+ mode = File::WRONLY | File::CREAT | File::TRUNC
70
+ mode |= File::EXCL unless overwrite
71
+ File.open(out_dir.join(out_file), mode) do |file|
72
+ doc.write(doc.convert, file)
73
+ end
74
+ end
75
+ end
76
+ end