moiristo-tileup 1.0.0
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.
- checksums.yaml +7 -0
- data/.gitignore +22 -0
- data/.travis.yml +12 -0
- data/CHANGES.md +43 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +34 -0
- data/LICENCE.txt +22 -0
- data/README.md +94 -0
- data/Rakefile +10 -0
- data/bin/tileup +45 -0
- data/lib/tileup.rb +10 -0
- data/lib/tileup/image_processor.rb +66 -0
- data/lib/tileup/image_processors/mini_magick.rb +62 -0
- data/lib/tileup/image_processors/rmagick.rb +57 -0
- data/lib/tileup/logger.rb +94 -0
- data/lib/tileup/loggers/console.rb +18 -0
- data/lib/tileup/loggers/none.rb +18 -0
- data/lib/tileup/tiler.rb +155 -0
- data/lib/tileup/version.rb +3 -0
- data/tileup.gemspec +32 -0
- metadata +147 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: c2f73ac375c4eacafa750e4e03ef7343fa988544
|
4
|
+
data.tar.gz: c6ec4fe16e14a3c943fe5f486359df568873fbf0
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5f037259541831f2af80bf5cfc74ea9a66fc6e51c0b346aa2d9032d7b2dfc1c8c244b65c4d1d139c759e695e6cfa4c0a3bdf154c56a5afcdb52b760cfae833a6
|
7
|
+
data.tar.gz: 31751b7bc4d79339912bed4fc4e057005332b60f27b051009b27eda19ce5cefc7c3238e676359775813f544e859f2e867f003aca3fa5b489feb1b202c8adb1e5
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/CHANGES.md
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
Tileup Change Log
|
2
|
+
=================
|
3
|
+
|
4
|
+
v1.0.0
|
5
|
+
------
|
6
|
+
|
7
|
+
- Added: MiniMagick support
|
8
|
+
- Added: Null logger
|
9
|
+
- Added: Test suite
|
10
|
+
- Added: Travis-ci integration
|
11
|
+
- Changed: Refactored implementation, split up functionality
|
12
|
+
|
13
|
+
v0.1.4
|
14
|
+
------
|
15
|
+
|
16
|
+
- Fixed: Background color for extended tiles (now transparent instead of white,
|
17
|
+
which would clobber transparent backgrounds)
|
18
|
+
|
19
|
+
v0.1.3
|
20
|
+
------
|
21
|
+
|
22
|
+
- Added: Functionality to automatically pad tiles that are not tile_width x tile_height
|
23
|
+
- Added: CLI switch to disable functionality (`dont-extend-incomplete-tiles`)
|
24
|
+
|
25
|
+
v0.1.2
|
26
|
+
------
|
27
|
+
|
28
|
+
- Lost to history
|
29
|
+
|
30
|
+
v0.1.1
|
31
|
+
------
|
32
|
+
|
33
|
+
- Lost to history
|
34
|
+
|
35
|
+
v0.1.0
|
36
|
+
------
|
37
|
+
|
38
|
+
- Lost to history
|
39
|
+
|
40
|
+
v0.0.1
|
41
|
+
------
|
42
|
+
|
43
|
+
- Lost to history
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
moiristo-tileup (1.0.0)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
coderay (1.1.0)
|
10
|
+
method_source (0.8.2)
|
11
|
+
mini_magick (4.7.0)
|
12
|
+
minitest (5.10.2)
|
13
|
+
pry (0.10.3)
|
14
|
+
coderay (~> 1.1.0)
|
15
|
+
method_source (~> 0.8.1)
|
16
|
+
slop (~> 3.4)
|
17
|
+
rake (10.5.0)
|
18
|
+
rmagick (2.16.0)
|
19
|
+
slop (3.6.0)
|
20
|
+
|
21
|
+
PLATFORMS
|
22
|
+
ruby
|
23
|
+
|
24
|
+
DEPENDENCIES
|
25
|
+
bundler (~> 1.13)
|
26
|
+
mini_magick (~> 4.7)
|
27
|
+
minitest (~> 5.10)
|
28
|
+
moiristo-tileup!
|
29
|
+
pry
|
30
|
+
rake (~> 10.0)
|
31
|
+
rmagick (~> 2.16)
|
32
|
+
|
33
|
+
BUNDLED WITH
|
34
|
+
1.15.3
|
data/LICENCE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2017 Reinier de Lange
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
# Tile Up
|
2
|
+
|
3
|
+
*Tile Up* is a ruby Ruby gem that splits a large image into a set of tiles to use with javascript mapping libraries such as [Leaflet.js](http://leafletjs.com) or [Modest Maps](http://modestmaps.com/). You can also use *Tile Up* to make tiles for `CATiledLayer` (or anything else really...).
|
4
|
+
|
5
|
+
[](https://travis-ci.org/moiristo/tileup)
|
6
|
+
|
7
|
+
*Note* This is not the official version of tileup. Since the offical version was poorly maintained, I decided to create my own version instead.
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
`gem install moiristo-tileup`
|
12
|
+
|
13
|
+
`tileup` requires `rmagick` or `mini_magick` for image manipulation, which depends on `imagemagick`. `imagemagick` is avaliable through `homebrew`.
|
14
|
+
|
15
|
+
## Usage
|
16
|
+
|
17
|
+
### Basics
|
18
|
+
|
19
|
+
To generate some tiles from a large image, you'll probably use something like:
|
20
|
+
|
21
|
+
```
|
22
|
+
tileup --in huge_image.png --output-dir image_tiles \
|
23
|
+
--prefix my_tiles --verbose
|
24
|
+
```
|
25
|
+
|
26
|
+
Which will split `huge_image.png` up into `256x256` (default) sized tiles, and save them into the directory `image_tiles`. The images will be saved as `my_tiles_[COLUMN]_[ROW].png`
|
27
|
+
|
28
|
+
```
|
29
|
+
image_tiles/my_tiles_0_0.png
|
30
|
+
image_tiles/my_tiles_0_1.png
|
31
|
+
image_tiles/my_tiles_0_2.png
|
32
|
+
...
|
33
|
+
```
|
34
|
+
|
35
|
+
### Using `Tiler` directly
|
36
|
+
|
37
|
+
You can also call `Tiler` directly from your application code:
|
38
|
+
|
39
|
+
```
|
40
|
+
tiler = TileUp::Tiler.new('huge_image.png', output_dir: 'image_tiles', processor: 'mini_magick', auto_zoom_level: 'auto', logger: 'none')
|
41
|
+
|
42
|
+
# Metadata
|
43
|
+
tiler.recommended_auto_zoom_level # Yields the recommended zoom level
|
44
|
+
tiler.image_processor.width(tiler.image) # Get the image width of 'huge_image.png' using the specified image processor
|
45
|
+
tiler.image_processor.height(tiler.image) # Get the image height of 'huge_image.png' using the specified image processor
|
46
|
+
|
47
|
+
# Tile generation
|
48
|
+
tiler.make_tiles!
|
49
|
+
```
|
50
|
+
|
51
|
+
### Auto zooming
|
52
|
+
|
53
|
+
`tileup` can also scale your image for a number of zoom levels (max 20 levels). This is done by *scaling down* the original image, so make sure its pretty big.
|
54
|
+
|
55
|
+
```
|
56
|
+
tileup --in really_huge_image.png --auto-zoom auto \
|
57
|
+
--output-dir map_tiles
|
58
|
+
```
|
59
|
+
|
60
|
+
`--auto-zoom auto` means that `tileup` will autoamtically try to determine the best auto zoom level. The level will be based on the image size and the tile dimensions specified.
|
61
|
+
|
62
|
+
```
|
63
|
+
tileup --in really_huge_image.png --auto-zoom 4 \
|
64
|
+
--output-dir map_tiles
|
65
|
+
```
|
66
|
+
|
67
|
+
`--auto-zoom 4` means, make 4 levels of zoom, starting from `really_huge_image.png` at zoom level 20, then scale that down for 19, etc.
|
68
|
+
|
69
|
+
You should see something like:
|
70
|
+
|
71
|
+
```
|
72
|
+
map_tiles/20/map_tile_0_0.png
|
73
|
+
map_tiles/20/map_tile_0_1.png
|
74
|
+
map_tiles/20/map_tile_0_2.png
|
75
|
+
...
|
76
|
+
map_tiles/19/map_tile_0_0.png
|
77
|
+
map_tiles/19/map_tile_0_1.png
|
78
|
+
map_tiles/19/map_tile_0_2.png
|
79
|
+
...
|
80
|
+
```
|
81
|
+
*(where `20` is zoom level 20, the largest zoom, `19` is half the size of `20`, `18` is half the size of `19`, …)*
|
82
|
+
|
83
|
+
|
84
|
+
## Getting help
|
85
|
+
|
86
|
+
You can get help by running `tileup -h`.
|
87
|
+
|
88
|
+
### Contributing
|
89
|
+
|
90
|
+
Fixes and patches welcome, to contribute:
|
91
|
+
|
92
|
+
1. Fork this project
|
93
|
+
1. Create a feature or fix branch *off the develop branch*
|
94
|
+
1. Submit a pull request on that branch
|
data/Rakefile
ADDED
data/bin/tileup
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'tileup'
|
4
|
+
require 'optparse'
|
5
|
+
|
6
|
+
options = TileUp::Tiler::DEFAULT_OPTIONS.dup
|
7
|
+
|
8
|
+
OptionParser.new do |o|
|
9
|
+
o.on('--in=input_file', "Required input file, your large image to tile up.") { |input_filename| options[:input_filename] = input_filename }
|
10
|
+
o.on('--prefix=map_tile', "Prefix to append to tile files, e.g. --prefix=my_tile => my_tile_[XN]_[YN].png.") { |prefix| options[:filename_prefix] = prefix }
|
11
|
+
o.on('--tile-width=256', "Tile width, should normally equal tile height.") { |width| options[:tile_width] = width }
|
12
|
+
o.on('--tile-height=256', "Tile height, should normally equal tile width.") { |height| options[:tile_height] = height }
|
13
|
+
o.on('--auto-zoom=auto', "Automatically scale input image N times when given a number. When 'auto' is specified, the auto zoom level is automatically determined"){ |zoom_level| options[:auto_zoom_level] = zoom_level }
|
14
|
+
o.on('--output-dir=', "Output directory (will be created if it doesn't exist).") { |output_dir| options[:output_dir] = output_dir }
|
15
|
+
o.on('--processor=rmagick', "Image processor, should be 'rmagick' or 'mini_magick'") { |processor| options[:processor] = processor }
|
16
|
+
o.on('--logger=console', "Sets logger, should be 'console' or 'none'") { |logger| options[:logger] = logger }
|
17
|
+
o.on('--extend-color=none', "Extend color for edge tiles.") {|extend_color| options[:extend_color] = extend_color }
|
18
|
+
o.on('--dont-extend-incomplete-tiles',
|
19
|
+
"Do not extend edge tiles if they do not fill an entire tile_width x tile_height. " +
|
20
|
+
"By default tileup will extend tiles to tile_width x tile_height if required.") {|e| options[:extend_incomplete_tiles] = false}
|
21
|
+
o.on('-v', '--verbose', "Enable verbose logging.") { |verbose| options[:verbose] = true }
|
22
|
+
o.on('-h', '--help', "You're looking at it.") { puts o; exit }
|
23
|
+
o.on('--version', "Version information (v#{TileUp::VERSION})") { puts TileUp::VERSION; exit }
|
24
|
+
begin
|
25
|
+
o.parse!
|
26
|
+
rescue SystemExit => e
|
27
|
+
exit
|
28
|
+
rescue Exception => e
|
29
|
+
puts e.class
|
30
|
+
puts "Argument error, #{e.message}. Try running '#{File.basename($PROGRAM_NAME)} -h'"
|
31
|
+
exit
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
if options[:input_filename].nil?
|
36
|
+
puts "No input file specified, Try running '#{File.basename($PROGRAM_NAME)} -h'"
|
37
|
+
exit 1
|
38
|
+
end
|
39
|
+
|
40
|
+
begin
|
41
|
+
TileUp::Tiler.new(options[:input_filename], options).make_tiles!
|
42
|
+
rescue Interrupt
|
43
|
+
puts "\n\nInterrupt received, exiting.\n"
|
44
|
+
exit
|
45
|
+
end
|
data/lib/tileup.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
require 'tileup/logger'
|
2
|
+
require 'tileup/loggers/console'
|
3
|
+
require 'tileup/loggers/none'
|
4
|
+
|
5
|
+
require 'tileup/image_processor'
|
6
|
+
require 'tileup/image_processors/mini_magick'
|
7
|
+
require 'tileup/image_processors/rmagick'
|
8
|
+
|
9
|
+
require 'tileup/tiler'
|
10
|
+
require 'tileup/version'
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module TileUp
|
2
|
+
class ImageProcessor
|
3
|
+
attr_accessor :logger
|
4
|
+
|
5
|
+
def self.build(processor, logger)
|
6
|
+
case processor
|
7
|
+
when 'mini_magick' then TileUp::ImageProcessors::MiniMagick.new(logger)
|
8
|
+
else TileUp::ImageProcessors::RMagick.new(logger)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize logger
|
13
|
+
self.logger = logger
|
14
|
+
end
|
15
|
+
|
16
|
+
def open image_filename
|
17
|
+
image = open_image(image_filename)
|
18
|
+
logger.info "Opened #{image_filename}, #{width(image)} x #{height(image)}"
|
19
|
+
image
|
20
|
+
rescue => e
|
21
|
+
logger.error "Could not open image #{image_filename}: #{e.message}"
|
22
|
+
raise e
|
23
|
+
end
|
24
|
+
|
25
|
+
def scale image, scale
|
26
|
+
logger.verbose "Scale: #{scale}"
|
27
|
+
|
28
|
+
if scale != 1.0
|
29
|
+
scale_image(image, scale)
|
30
|
+
else
|
31
|
+
image
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def crop_and_save image, crop, filename:, extend_crop: false, extend_color: 'none'
|
36
|
+
logger.verbose "Crop: x: #{crop[:x]}, y: #{crop[:y]}, w: #{crop[:width]}, h: #{crop[:height]}"
|
37
|
+
crop_and_save_image(image, crop, filename: filename, extend_crop: extend_crop, extend_color: extend_color)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Interface
|
41
|
+
|
42
|
+
def width image
|
43
|
+
raise NotImplementedError
|
44
|
+
end
|
45
|
+
|
46
|
+
def height image
|
47
|
+
raise NotImplementedError
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def open_image image
|
53
|
+
raise NotImplementedError
|
54
|
+
end
|
55
|
+
|
56
|
+
def scale_image image
|
57
|
+
raise NotImplementedError
|
58
|
+
end
|
59
|
+
|
60
|
+
def crop_image image, crop, extend_crop: false, extend_color: 'none'
|
61
|
+
raise NotImplementedError
|
62
|
+
end
|
63
|
+
|
64
|
+
# / Interface
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module TileUp
|
2
|
+
module ImageProcessors
|
3
|
+
class MiniMagick < TileUp::ImageProcessor
|
4
|
+
|
5
|
+
def initialize logger
|
6
|
+
require 'mini_magick'
|
7
|
+
super(logger)
|
8
|
+
end
|
9
|
+
|
10
|
+
def width image
|
11
|
+
image.width
|
12
|
+
end
|
13
|
+
|
14
|
+
def height image
|
15
|
+
image.height
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def open_image(image_filename)
|
21
|
+
::MiniMagick::Image.open(image_filename)
|
22
|
+
end
|
23
|
+
|
24
|
+
def scale_image image, scale
|
25
|
+
open_image(image.path).resize("#{100.0 * scale}%")
|
26
|
+
rescue RuntimeError => e
|
27
|
+
logger.error "Failed to scale image, are you sure the original image is large enough (#{image.width} x #{image.height})?"
|
28
|
+
return false
|
29
|
+
end
|
30
|
+
|
31
|
+
def crop_and_save_image image, crop, filename:, extend_crop: false, extend_color: 'none'
|
32
|
+
logger.verbose "Saving tile: #{crop[:column]}, #{crop[:row]}..."
|
33
|
+
|
34
|
+
::MiniMagick::Tool::Convert.new do |convert|
|
35
|
+
convert << mpc(image).path
|
36
|
+
convert.merge! ['-crop', "#{crop[:width]}x#{crop[:height]}+#{crop[:x]}+#{crop[:y]}"]
|
37
|
+
|
38
|
+
if extend_crop
|
39
|
+
convert.merge! ['-background', extend_color]
|
40
|
+
convert.merge! ['-extent', "#{crop[:width]}x#{crop[:height]}"]
|
41
|
+
end
|
42
|
+
|
43
|
+
convert << filename
|
44
|
+
end
|
45
|
+
|
46
|
+
logger.verbose "Saving tile: #{crop[:column]}, #{crop[:row]}... saved"
|
47
|
+
|
48
|
+
true
|
49
|
+
rescue RuntimeError => e
|
50
|
+
raise e
|
51
|
+
logger.error "Failed to crop image: #{e.message}"
|
52
|
+
return false
|
53
|
+
end
|
54
|
+
|
55
|
+
def mpc image
|
56
|
+
@mpcs ||= {}
|
57
|
+
@mpcs[image] ||= open_image(image.path).format 'mpc'
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module TileUp
|
2
|
+
module ImageProcessors
|
3
|
+
class RMagick < TileUp::ImageProcessor
|
4
|
+
|
5
|
+
def initialize logger
|
6
|
+
require 'rmagick'
|
7
|
+
super(logger)
|
8
|
+
end
|
9
|
+
|
10
|
+
def width image
|
11
|
+
image.columns
|
12
|
+
end
|
13
|
+
|
14
|
+
def height image
|
15
|
+
image.rows
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def open_image(image_filename)
|
21
|
+
::Magick::Image.read(image_filename).first
|
22
|
+
end
|
23
|
+
|
24
|
+
def scale_image image, scale
|
25
|
+
image.scale(scale)
|
26
|
+
rescue RuntimeError => e
|
27
|
+
logger.error "Failed to scale image, are you sure the original image is large enough (#{image.columns} x #{image.rows})?"
|
28
|
+
false
|
29
|
+
end
|
30
|
+
|
31
|
+
def crop_and_save_image image, crop, filename:, extend_crop: false, extend_color: 'none'
|
32
|
+
cropped_image = image.crop(crop[:x], crop[:y], crop[:width], crop[:height], true)
|
33
|
+
|
34
|
+
# unless told to do otherwise, extend tiles in the last row and column
|
35
|
+
# if they do not fill an entire tile width and height.
|
36
|
+
needs_extension = cropped_image.columns != crop[:width] || cropped_image.rows != crop[:height]
|
37
|
+
|
38
|
+
if extend_crop && needs_extension
|
39
|
+
# defaults to white background color, but transparent is probably
|
40
|
+
# a better default for our purposes.
|
41
|
+
cropped_image.background_color = extend_color
|
42
|
+
# fill to width height, start from top left corner.
|
43
|
+
cropped_image = cropped_image.extent(crop[:width], crop[:height], 0, 0)
|
44
|
+
end
|
45
|
+
|
46
|
+
logger.verbose "Saving tile: #{crop[:column]}, #{crop[:row]}..."
|
47
|
+
cropped_image.write(filename)
|
48
|
+
logger.verbose "Saving tile: #{crop[:column]}, #{crop[:row]}... saved"
|
49
|
+
|
50
|
+
true
|
51
|
+
rescue RuntimeError => e
|
52
|
+
logger.error "Failed to crop image: #{e.message}"
|
53
|
+
false
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
require 'logger'
|
3
|
+
|
4
|
+
module TileUp
|
5
|
+
|
6
|
+
# Base logger class, subclass this, do not use directly.
|
7
|
+
class Logger
|
8
|
+
|
9
|
+
def self.build type, level, options = {}
|
10
|
+
case type
|
11
|
+
when 'none' then TileUp::Loggers::None.new(level, options)
|
12
|
+
when ::Logger then TileUp::Logger.new(level, options.merge(logger: type))
|
13
|
+
else TileUp::Loggers::Console.new(level, options)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.sym_to_severity(sym)
|
18
|
+
severities = {
|
19
|
+
:debug => ::Logger::DEBUG,
|
20
|
+
:info => ::Logger::INFO,
|
21
|
+
:warn => ::Logger::WARN,
|
22
|
+
:error => ::Logger::ERROR,
|
23
|
+
:fatal => ::Logger::FATAL
|
24
|
+
}
|
25
|
+
severity = severities[sym] || ::Logger::UNKNOWN
|
26
|
+
end
|
27
|
+
|
28
|
+
# create logger set to given level
|
29
|
+
# where level is a symbol (:debug, :info, :warn, :error, :fatal)
|
30
|
+
# options may specifiy verbose, which will log more info messages
|
31
|
+
def initialize(level, options = {})
|
32
|
+
@severity = level
|
33
|
+
@logger = options[:logger] if options[:logger]
|
34
|
+
default_options = { verbose: false }
|
35
|
+
@options = OpenStruct.new(default_options.merge(options))
|
36
|
+
end
|
37
|
+
|
38
|
+
def level
|
39
|
+
@level
|
40
|
+
end
|
41
|
+
|
42
|
+
def level=(severity)
|
43
|
+
logger.level = Logger.sym_to_severity(severity)
|
44
|
+
end
|
45
|
+
|
46
|
+
# log an error message
|
47
|
+
def error(message)
|
48
|
+
# note, we always log error messages
|
49
|
+
add(:error, message)
|
50
|
+
end
|
51
|
+
|
52
|
+
# log a regular message
|
53
|
+
def info(message)
|
54
|
+
add(:info, message)
|
55
|
+
end
|
56
|
+
|
57
|
+
def warn(message)
|
58
|
+
add(:warn, message)
|
59
|
+
end
|
60
|
+
|
61
|
+
# log a verbose message
|
62
|
+
def verbose(message)
|
63
|
+
add(:info, message) if verbose?
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
# add message to log
|
69
|
+
def add(severity, message)
|
70
|
+
severity = Logger.sym_to_severity(severity)
|
71
|
+
logger.add(severity, message)
|
72
|
+
end
|
73
|
+
|
74
|
+
# is logger in verbose mode?
|
75
|
+
def verbose?
|
76
|
+
@options.verbose
|
77
|
+
end
|
78
|
+
|
79
|
+
# create or return a logger
|
80
|
+
def logger
|
81
|
+
@logger ||= create_logger
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
# subclasses should overwrite this method, creating what ever
|
87
|
+
# logger they want to
|
88
|
+
def create_logger
|
89
|
+
raise "You should create your own `create_logger` method"
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module TileUp
|
2
|
+
module Loggers
|
3
|
+
class Console < Logger
|
4
|
+
|
5
|
+
private
|
6
|
+
|
7
|
+
def create_logger
|
8
|
+
logger = ::Logger.new(STDOUT)
|
9
|
+
logger.formatter = Proc.new do |sev, time, prg, msg|
|
10
|
+
"#{time.strftime('%H:%M:%S').to_s} => #{msg}\n"
|
11
|
+
end
|
12
|
+
|
13
|
+
logger
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/tileup/tiler.rb
ADDED
@@ -0,0 +1,155 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'tileup/logger'
|
4
|
+
|
5
|
+
module TileUp
|
6
|
+
|
7
|
+
class Tiler
|
8
|
+
|
9
|
+
DEFAULT_OPTIONS = {
|
10
|
+
processor: 'rmagick', # or 'mini_magick'
|
11
|
+
auto_zoom_level: nil,
|
12
|
+
tile_width: 256,
|
13
|
+
tile_height: 256,
|
14
|
+
filename_prefix: 'map_tile',
|
15
|
+
output_dir: '.',
|
16
|
+
logger: 'console',
|
17
|
+
extend_incomplete_tiles: true,
|
18
|
+
extend_color: 'none',
|
19
|
+
verbose: false
|
20
|
+
}.freeze
|
21
|
+
|
22
|
+
attr_accessor :image_processor, :image, :options, :extension, :logger
|
23
|
+
|
24
|
+
def initialize image_filename, options = {}
|
25
|
+
self.options = options = OpenStruct.new(DEFAULT_OPTIONS.merge(options))
|
26
|
+
self.logger = TileUp::Logger.build(options.logger, :info, verbose: options.verbose)
|
27
|
+
self.image_processor = TileUp::ImageProcessor.build(options.processor, logger)
|
28
|
+
self.image = image_processor.open(image_filename)
|
29
|
+
self.extension = image_filename.split(".").last
|
30
|
+
|
31
|
+
%w(tile_width tile_height).each{|dimension| options[dimension] = options[dimension].to_i }
|
32
|
+
|
33
|
+
if options.auto_zoom_level
|
34
|
+
if options.auto_zoom_level == 'auto'
|
35
|
+
options.auto_zoom_level = recommended_auto_zoom_level
|
36
|
+
else
|
37
|
+
options.auto_zoom_level = options.auto_zoom_level.to_i
|
38
|
+
end
|
39
|
+
|
40
|
+
if options.auto_zoom_level > 20
|
41
|
+
logger.warn 'Warning: auto zoom levels hard limited to 20.'
|
42
|
+
options.auto_zoom_level = 20
|
43
|
+
elsif options.auto_zoom_level <= 0
|
44
|
+
options.auto_zoom_level = nil
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def make_tiles! base_images = build_base_images
|
50
|
+
if base_images
|
51
|
+
result = base_images.map do |base_image|
|
52
|
+
FileUtils.mkdir_p(base_image[:image_path])
|
53
|
+
{ base_image[:image_path] => make_tiles_for_base_image!(base_image[:image], File.join(base_image[:image_path], options.filename_prefix)) }
|
54
|
+
end
|
55
|
+
logger.verbose result
|
56
|
+
logger.info 'Done'
|
57
|
+
result
|
58
|
+
else
|
59
|
+
false
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def build_base_images
|
64
|
+
logger.info 'Building base images'
|
65
|
+
|
66
|
+
base_images = base_image_processing_tasks.map do |task|
|
67
|
+
{
|
68
|
+
image: image_processor.scale(image, task[:scale]),
|
69
|
+
image_path: task[:output_dir]
|
70
|
+
}
|
71
|
+
end
|
72
|
+
|
73
|
+
if base_images
|
74
|
+
logger.info "Building base images finished."
|
75
|
+
base_images
|
76
|
+
else
|
77
|
+
logger.info 'Building base images failed.'
|
78
|
+
false
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def recommended_auto_zoom_level
|
83
|
+
zoom_level = (1..20).detect do |zoom_level|
|
84
|
+
scale = (1.to_f / 2 ** (zoom_level - 1))
|
85
|
+
|
86
|
+
image_processor.width(image) * scale < options.tile_width ||
|
87
|
+
image_processor.height(image) * scale < options.tile_height
|
88
|
+
end
|
89
|
+
|
90
|
+
[1, zoom_level.to_i - 1].max
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
def make_tiles_for_base_image!(base_image, filename_prefix)
|
96
|
+
# find image width and height
|
97
|
+
# then find out how many tiles we'll get out of
|
98
|
+
# the image, then use that for the xy offset in crop.
|
99
|
+
num_columns = (image_processor.width(base_image) / options.tile_width.to_f).ceil
|
100
|
+
num_rows = (image_processor.height(base_image) / options.tile_height.to_f).ceil
|
101
|
+
|
102
|
+
logger.info "Tiling image into columns: #{num_columns}, rows: #{num_rows}"
|
103
|
+
|
104
|
+
crops_for(num_columns, num_rows).map do |crop|
|
105
|
+
filename = "#{filename_prefix}_#{crop[:column]}_#{crop[:row]}.#{extension}"
|
106
|
+
is_edge = (crop[:row] == num_rows - 1 || crop[:column] == num_columns - 1)
|
107
|
+
image_processor.crop_and_save(base_image, crop, filename: filename, extend_crop: options.extend_incomplete_tiles && is_edge, extend_color: options.extend_color)
|
108
|
+
filename
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def base_image_processing_tasks
|
113
|
+
if options.auto_zoom_level.nil?
|
114
|
+
[{ output_dir: options.output_dir, scale: 1.0 }]
|
115
|
+
else
|
116
|
+
current_scale = 1.0
|
117
|
+
(1..options.auto_zoom_level).to_a.reverse.map do |zoom_level|
|
118
|
+
task = {
|
119
|
+
output_dir: File.join(options.output_dir, zoom_level.to_s),
|
120
|
+
scale: current_scale
|
121
|
+
}
|
122
|
+
|
123
|
+
current_scale = current_scale / 2
|
124
|
+
task
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def crops_for num_columns, num_rows
|
130
|
+
crops = []
|
131
|
+
|
132
|
+
x = y = column = row = 0
|
133
|
+
while row < num_rows
|
134
|
+
crops << {
|
135
|
+
row: row,
|
136
|
+
column: column,
|
137
|
+
x: column * options.tile_width,
|
138
|
+
y: row * options.tile_height,
|
139
|
+
width: options.tile_width,
|
140
|
+
height: options.tile_height
|
141
|
+
}
|
142
|
+
|
143
|
+
column += 1
|
144
|
+
if column >= num_columns
|
145
|
+
column = 0
|
146
|
+
row += 1
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
crops
|
151
|
+
end
|
152
|
+
|
153
|
+
end
|
154
|
+
|
155
|
+
end
|
data/tileup.gemspec
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'tileup/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'moiristo-tileup'
|
8
|
+
spec.version = TileUp::VERSION
|
9
|
+
spec.authors = ['Reinier de Lange']
|
10
|
+
spec.email = 'rein@bookingexperts.nl'
|
11
|
+
|
12
|
+
spec.summary = 'Turn an image into an X,Y tile set for use with JS mapping libraries'
|
13
|
+
spec.description = spec.summary
|
14
|
+
spec.homepage = 'http://github.com/rktjmp/tileup'
|
15
|
+
spec.license = 'MIT'
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
18
|
+
f.match(%r{^test/})
|
19
|
+
end
|
20
|
+
|
21
|
+
spec.bindir = 'bin'
|
22
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
23
|
+
spec.require_paths = ['lib']
|
24
|
+
|
25
|
+
spec.add_development_dependency 'bundler', '~> 1.13'
|
26
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
27
|
+
spec.add_development_dependency 'minitest', '~> 5.10'
|
28
|
+
spec.add_development_dependency 'pry'
|
29
|
+
|
30
|
+
spec.add_development_dependency 'rmagick', '~> 2.16'
|
31
|
+
spec.add_development_dependency 'mini_magick', '~> 4.7'
|
32
|
+
end
|
metadata
ADDED
@@ -0,0 +1,147 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: moiristo-tileup
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Reinier de Lange
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-08-18 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.13'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.13'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '5.10'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '5.10'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: pry
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rmagick
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '2.16'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '2.16'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: mini_magick
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '4.7'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '4.7'
|
97
|
+
description: Turn an image into an X,Y tile set for use with JS mapping libraries
|
98
|
+
email: rein@bookingexperts.nl
|
99
|
+
executables:
|
100
|
+
- tileup
|
101
|
+
extensions: []
|
102
|
+
extra_rdoc_files: []
|
103
|
+
files:
|
104
|
+
- ".gitignore"
|
105
|
+
- ".travis.yml"
|
106
|
+
- CHANGES.md
|
107
|
+
- Gemfile
|
108
|
+
- Gemfile.lock
|
109
|
+
- LICENCE.txt
|
110
|
+
- README.md
|
111
|
+
- Rakefile
|
112
|
+
- bin/tileup
|
113
|
+
- lib/tileup.rb
|
114
|
+
- lib/tileup/image_processor.rb
|
115
|
+
- lib/tileup/image_processors/mini_magick.rb
|
116
|
+
- lib/tileup/image_processors/rmagick.rb
|
117
|
+
- lib/tileup/logger.rb
|
118
|
+
- lib/tileup/loggers/console.rb
|
119
|
+
- lib/tileup/loggers/none.rb
|
120
|
+
- lib/tileup/tiler.rb
|
121
|
+
- lib/tileup/version.rb
|
122
|
+
- tileup.gemspec
|
123
|
+
homepage: http://github.com/rktjmp/tileup
|
124
|
+
licenses:
|
125
|
+
- MIT
|
126
|
+
metadata: {}
|
127
|
+
post_install_message:
|
128
|
+
rdoc_options: []
|
129
|
+
require_paths:
|
130
|
+
- lib
|
131
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
132
|
+
requirements:
|
133
|
+
- - ">="
|
134
|
+
- !ruby/object:Gem::Version
|
135
|
+
version: '0'
|
136
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
137
|
+
requirements:
|
138
|
+
- - ">="
|
139
|
+
- !ruby/object:Gem::Version
|
140
|
+
version: '0'
|
141
|
+
requirements: []
|
142
|
+
rubyforge_project:
|
143
|
+
rubygems_version: 2.4.8
|
144
|
+
signing_key:
|
145
|
+
specification_version: 4
|
146
|
+
summary: Turn an image into an X,Y tile set for use with JS mapping libraries
|
147
|
+
test_files: []
|