mapsnatcher 0.1

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.
Files changed (7) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +19 -0
  3. data/README.md +26 -0
  4. data/VERSION +1 -0
  5. data/bin/mapsnatcher +192 -0
  6. data/mapsnatcher.gemspec +25 -0
  7. metadata +62 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 81899844de71e7d8e994867d291b9bf3effee5eac3af6a07686c9b9ee201660d
4
+ data.tar.gz: 98a6fc9902a46b0a8afc38df9471c805a0a0555066cb516d357a1ad68da76db2
5
+ SHA512:
6
+ metadata.gz: de0a27be646d2d197f4342c8525a5e5625074eb2d6e47c412e7d9e46dbfc50e795d877b2b621148658784caa60a47c76929cbcc9f4d4442029376ba85818f143
7
+ data.tar.gz: b72ac47c23bd90ffeb8744af6ba569142e85a967dd55822bc07cc1130815be07894fce94b94f24aa65fbf0d8d32a3e554adbf3975c20946154c139528a033b9e
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (C) 2012 Vinny Diehl
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
4
+ this software and associated documentation files (the "Software"), to deal in
5
+ the Software without restriction, including without limitation the rights to
6
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
7
+ of the Software, and to permit persons to whom the Software is furnished to do
8
+ so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ 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 THE
19
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,26 @@
1
+ # `mapsnatcher`
2
+
3
+ `mapsnatcher` is a bot to automatically download and splice together tiles from
4
+ QGIS maps. This simple system uses a URL such as
5
+ `http://mapwebsite.com/z/x/y.png`, where `z` is the zoom level, and `x` and `y`
6
+ are the coordinates of the tile.
7
+
8
+ You can use a tool such as
9
+ [ChromeCacheView](https://www.nirsoft.net/utils/chrome_cache_view.html) to find
10
+ these URLs easily.
11
+
12
+ ## Usage
13
+
14
+ Usage is as follows:
15
+
16
+ ```mapsnatcher "http://mapwebsite.com/0/XXX/YYY.png"```
17
+
18
+ Place `XXX` and `YYY` (case-insensitive) where the respective coordinate
19
+ numbers would be in the URL. You will then be prompted to enter the X and Y
20
+ boundaries that you would like to capture.
21
+
22
+ ```mapsnatcher -b "http://mapwebsite.com/0/XXX/YYY.png"```
23
+
24
+ If you are unsure of the boundaries and would like to capture the entire map,
25
+ add the `-b` argument and you will only need to enter one valid coordinate;
26
+ `mapsnatcher` will do the rest.
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1
data/bin/mapsnatcher ADDED
@@ -0,0 +1,192 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "fileutils"
4
+ require "yaml"
5
+ require "open-uri"
6
+ require "net/http"
7
+ require "rmagick"
8
+ include Magick
9
+
10
+ TEMP_DIR = "./.ms.tmp"
11
+
12
+ # False if URL returns a 200
13
+ #
14
+ # @param url [String] the url to be checked
15
+ # @return [Boolean] whether or not the URL is invalid
16
+ def invalid?(url)
17
+ uri = URI(url)
18
+ !Net::HTTP.start(uri.host, 443, use_ssl: true){ |http| break http.head uri.path }.instance_of? Net::HTTPOK
19
+ end
20
+
21
+ # Parses ARGV input into usable URL with given X and Y values
22
+ #
23
+ # @param x [Integer] x-coordinate
24
+ # @param y [Integer] y-coordinate
25
+ # @return usable URL for map tile
26
+ def build_url(url, x, y)
27
+ url.gsub(/xxx/i, x.to_s).gsub(/yyy/i, y.to_s)
28
+ end
29
+
30
+ if ARGV.empty? or %w[-h --help].include? ARGV.first
31
+ puts 'Usage: mapsnatcher "http://mapwebsite.com/0/XXX/YYY.png"'
32
+ puts
33
+ puts " Place XXX and YYY in place of where the respective coordinate numbers would be"
34
+ puts " in the URL. The first number is typically the zoom level, which is constant."
35
+ puts
36
+ puts " [-b] find outer bounds of map automatically"
37
+ exit 0
38
+ end
39
+
40
+ if %w[-v --version].include? ARGV.first
41
+ puts "mapsnatcher #{File.read(File.expand_path("../../VERSION", __FILE__)).strip}"
42
+ exit 0
43
+ end
44
+
45
+ # Load/create temp directory
46
+ if !File.exists? TEMP_DIR
47
+ Dir.mkdir TEMP_DIR
48
+ elsif !Dir.empty? TEMP_DIR
49
+ print "It looks like mapsnatcher didn't close properly. Use backed up files? [Y/n] "
50
+ FileUtils.rm_f(Dir.glob("#{TEMP_DIR}/*")) if STDIN.gets.chomp =~ /n.*/i
51
+ puts
52
+ end
53
+
54
+ if ARGV.include? "-b"
55
+ # Automatic boundary mode
56
+
57
+ url = (ARGV - ["-b"]).first
58
+
59
+ # CLI input function
60
+ #
61
+ # @return [Array] user input coordinates
62
+ def get_coords
63
+ print "Enter valid X coordinate [0]: "
64
+ x_in = STDIN.gets.chomp.to_i
65
+ print "Enter valid Y coordinate [0]: "
66
+ y_in = STDIN.gets.chomp.to_i
67
+
68
+ print "\nLoading...\r"
69
+
70
+ [x_in, y_in]
71
+ end
72
+
73
+ valid_x, valid_y = get_coords
74
+
75
+ while invalid? build_url(url, valid_x, valid_y)
76
+ puts "Invalid coordinate! Try again."
77
+ puts
78
+ valid_x, valid_y = get_coords
79
+ end
80
+
81
+ x_low = x_high = valid_x
82
+ until invalid? build_url(url, x_low - 1, valid_y)
83
+ print "Checking X value: #{x_low} \r"
84
+ x_low -= 1
85
+ end
86
+ until invalid? build_url(url, x_high + 1, valid_y)
87
+ print "Checking X value: #{x_high} \r"
88
+ x_high += 1
89
+ end
90
+
91
+ y_low = y_high = valid_y
92
+ until invalid? build_url(url, valid_x, y_low - 1)
93
+ print "Checking Y value: #{y_low} \r"
94
+ y_low -= 1
95
+ end
96
+ until invalid? build_url(url, valid_x, y_high + 1)
97
+ print "Checking Y value: #{y_high} \r"
98
+ y_high += 1
99
+ end
100
+
101
+ puts "Found Coordinate Range: [#{x_low}-#{x_high}], [#{y_low}-#{y_high}]"
102
+
103
+ x_range, y_range = (x_low..x_high), (y_low..y_high)
104
+ else
105
+ # User input coordinate mode
106
+
107
+ url = ARGV.first
108
+
109
+ # CLI input function
110
+ #
111
+ # @return [Array] user input coordinate ranges
112
+ def get_coords
113
+ print "Start of X range [0]: "
114
+ x_start = STDIN.gets.chomp.to_i
115
+ print "End of X range: "
116
+ x_range_in = (x_start..STDIN.gets.chomp.to_i)
117
+
118
+ print "Start of Y range [0]: "
119
+ y_start = STDIN.gets.chomp.to_i
120
+ print "End of Y range: "
121
+ y_range_in = (y_start..STDIN.gets.chomp.to_i)
122
+
123
+ print "\nLoading...\r"
124
+
125
+ [x_range_in, y_range_in]
126
+ end
127
+
128
+ x_range, y_range = get_coords
129
+
130
+ while invalid?(build_url url, x_range.first, y_range.first) or
131
+ invalid?(build_url url, x_range.first, y_range.last) or
132
+ invalid?(build_url url, x_range.last, y_range.first) or
133
+ invalid?(build_url url, x_range.last, y_range.last)
134
+ puts "\nError: Boundaries incorrect."
135
+ puts "Use `mapsnatcher -b` for help finding the boundaries of the image."
136
+ puts
137
+ x_range, y_range = get_coords
138
+ end
139
+ end
140
+
141
+ # Use the tile at 0, 0 to calculate final image size
142
+ sample = Image.from_blob(URI.open(build_url url, x_range.first, y_range.first).read).first
143
+ final_size = {x: sample.columns * x_range.size, y: sample.rows * y_range.size}
144
+ puts "Image found, #{sample.format} size #{final_size[:x]}x#{final_size[:y]}"
145
+ puts
146
+
147
+ dl_counter = 0
148
+ dl_total = x_range.size * y_range.size
149
+
150
+ stitch = ImageList.new
151
+ y_range.each do |y|
152
+ row = ImageList.new
153
+ x_range.each do |x|
154
+ print "Downloading... [#{dl_counter += 1}/#{dl_total}]\r"
155
+
156
+ temp = "#{TEMP_DIR}/#{x}_#{y}.tmp"
157
+ if File.exists? temp
158
+ # Load the backup file
159
+ blob = YAML.load(File.read temp)
160
+ else
161
+ blob = URI.open(build_url url, x, y).read
162
+ File.open(temp, "w") { |f| f.write(YAML.dump blob) }
163
+ end
164
+
165
+ row.push Image.from_blob(blob).first
166
+ end
167
+
168
+ stitch.push row.append(false)
169
+ end
170
+
171
+ puts "\nDone!"
172
+
173
+ # Warnings for incompatible formats for large image sizes
174
+ JPEG_MAX = 65535
175
+ WEBP_MAX = 16383
176
+
177
+ if final_size.values.max > JPEG_MAX
178
+ puts "\nWarning! Image dimensions greater than #{JPEG_MAX}px, JPG and WEBP"
179
+ puts "formats are unavailable."
180
+ puts
181
+ elsif final_size.values.max > WEBP_MAX
182
+ puts "\nWarning! Image dimensions greater than #{WEBP_MAX}px, WEBP"
183
+ puts "format is unavailable."
184
+ puts
185
+ end
186
+
187
+ print "Enter file name for map: "
188
+ filename = STDIN.gets.chomp
189
+ print "Saving, this may take a minute..."
190
+ stitch.append(true).write filename
191
+ FileUtils.rm_rf TEMP_DIR
192
+ puts "\nImage written to: #{filename}"
@@ -0,0 +1,25 @@
1
+ Gem::Specification.new do |gem|
2
+ gem.name = "mapsnatcher"
3
+ gem.version = File.read(File.expand_path("../VERSION", __FILE__)).strip
4
+
5
+ gem.author = "Vinny Diehl"
6
+ gem.email = "vinny.diehl@gmail.com"
7
+ gem.homepage = "https://github.com/vinnydiehl/mapsnatcher"
8
+
9
+ gem.license = "MIT"
10
+
11
+ gem.summary = "Downloads and combines tiles from QGIS maps into an image."
12
+ gem.description = "Downloads and combines tiles from QGIS maps into an image."
13
+
14
+ gem.bindir = "bin"
15
+ gem.executables = %w[mapsnatcher]
16
+
17
+ gem.test_files = Dir["spec/**/*"]
18
+ gem.files = Dir["bin/**/*"] + gem.test_files + %w[
19
+ LICENSE README.md VERSION mapsnatcher.gemspec
20
+ ]
21
+
22
+ gem.required_ruby_version = "~> 3.0"
23
+
24
+ gem.add_dependency "rmagick", "~> 5.1"
25
+ end
metadata ADDED
@@ -0,0 +1,62 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mapsnatcher
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.1'
5
+ platform: ruby
6
+ authors:
7
+ - Vinny Diehl
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-11-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rmagick
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '5.1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '5.1'
27
+ description: Downloads and combines tiles from QGIS maps into an image.
28
+ email: vinny.diehl@gmail.com
29
+ executables:
30
+ - mapsnatcher
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - LICENSE
35
+ - README.md
36
+ - VERSION
37
+ - bin/mapsnatcher
38
+ - mapsnatcher.gemspec
39
+ homepage: https://github.com/vinnydiehl/mapsnatcher
40
+ licenses:
41
+ - MIT
42
+ metadata: {}
43
+ post_install_message:
44
+ rdoc_options: []
45
+ require_paths:
46
+ - lib
47
+ required_ruby_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - "~>"
50
+ - !ruby/object:Gem::Version
51
+ version: '3.0'
52
+ required_rubygems_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: '0'
57
+ requirements: []
58
+ rubygems_version: 3.3.5
59
+ signing_key:
60
+ specification_version: 4
61
+ summary: Downloads and combines tiles from QGIS maps into an image.
62
+ test_files: []