-
Notifications
You must be signed in to change notification settings - Fork 24
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
273 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
Tile Up | ||
======= | ||
|
||
*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/). | ||
|
||
Installation | ||
------------ | ||
|
||
`gem install tileup` | ||
|
||
`tileup` requires `rmagick` for image manipulation, which depends on `imagemagick`. `imagemagick` is avaliable through `homebrew`. | ||
|
||
Usage | ||
----- | ||
|
||
### Basics | ||
|
||
To generate some tiles from a large image, you'll probably use something like: | ||
|
||
``` | ||
tileup --in huge_image.png --output-dir image_tiles \ | ||
--prefix my_tiles --verbose | ||
``` | ||
|
||
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` | ||
|
||
``` | ||
image_tiles/my_tiles_0_0.png | ||
image_tiles/my_tiles_0_1.png | ||
image_tiles/my_tiles_0_2.png | ||
... | ||
``` | ||
|
||
### Auto zooming | ||
|
||
`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. | ||
|
||
*(Auto zooming is an experimental hack, it should work fine for smaller increments, but may be unreliable at higher levels. E.g. `--auto-zoom 4` should work fine, `--auto-zoom 20` might not work so well.)* | ||
|
||
``` | ||
tileup --in really_huge_image.png --auto-zoom 4 \ | ||
--output-dir map_tiles | ||
``` | ||
|
||
`--auto-zoom 4` means, make 4 levesl of zoom, starting from `really_huge_image.png` at zoom level 20, then scale that down for 19, etc. | ||
|
||
You should see something like: | ||
|
||
``` | ||
map_tiles/20/map_tile_0_0.png | ||
map_tiles/20/map_tile_0_1.png | ||
map_tiles/20/map_tile_0_2.png | ||
... | ||
map_tiles/19/map_tile_0_0.png | ||
map_tiles/19/map_tile_0_1.png | ||
map_tiles/19/map_tile_0_2.png | ||
... | ||
``` | ||
*(where `20` is zoom level 20, the largest zoom, `19` is half the size of `20`, `18` is half the size of `19`, …)* | ||
|
||
|
||
### Getting help | ||
|
||
You can get help by running `tileup -h`. | ||
|
||
Contributing | ||
------------ | ||
|
||
Fixes and patches welcome, to contribute: | ||
|
||
1. Fork this project | ||
1. Create a feature or fix branch | ||
1. Submit a pull request on that branch |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
#!/usr/bin/env ruby | ||
|
||
require 'tileup' | ||
require 'optparse' | ||
|
||
# auto zoom bigmap.png down 4 times, output to map-tiles dir with map-tile-X-Y.png filename | ||
# maw --in "bigmap.png" --auto-zoom --auto-zoom-levels 4 --output-dir "map-tiles" --filename-prefx="map-tile" | ||
|
||
# maw --in "bigmap.png" --output-dir "map-tiles/20/" --filename-prefix="map-tile" --tile-width 256 --tile-height 256 | ||
|
||
|
||
options = { | ||
tile_width: 256, | ||
tile_height: 256, | ||
filename_prefix: "map_tile", | ||
auto_zoom_levels: nil, | ||
input_filename: nil, | ||
output_dir: '.' | ||
|
||
} | ||
|
||
OptionParser.new do |o| | ||
o.on('--in=input_file', " Required input file, your large image to tile up") { |input_filename| options[:input_filename] = input_filename } | ||
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 } | ||
o.on('--tile-width=256', "Tile width, should normally equal tile height") { |width| options[:tile_width] = width } | ||
o.on('--tile-height=256', "Tile height, should normally equal tile width") { |height| options[:tile_height] = height } | ||
o.on('--auto-zoom=', Integer, "Automatically scale input image N times, must be a number"){ |zooms| options[:auto_zoom_levels] = zooms } | ||
o.on('--output-dir=', "Output directory (will be created if it doesn't exist)") { |output_dir| options[:output_dir] = output_dir } | ||
o.on('-v', '--verbose', "Enable verbose logging") { |verbose| options[:verbose] = true } | ||
o.on('-h', '--help', "You're looking at it.") { puts o; exit } | ||
begin | ||
o.parse! | ||
rescue Exception => e | ||
puts "Argument error, #{e.message}. Try running '#{File.basename($PROGRAM_NAME)} -h'" | ||
exit | ||
end | ||
end | ||
|
||
if options[:input_filename].nil? | ||
puts "No input file specified, Try running '#{File.basename($PROGRAM_NAME)} -h'" | ||
exit 1 | ||
end | ||
|
||
TileUp::Tiler.new(options[:input_filename], options) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
require 'ostruct' | ||
require 'rmagick' | ||
|
||
module TileUp | ||
|
||
class Tiler | ||
|
||
def initialize(image_filename, options) | ||
default_options = { | ||
auto_zoom_levels: nil, | ||
tile_width: 256, | ||
tile_height: 256, | ||
filename_prefix: "map_tile", | ||
output_dir: ".", | ||
verbose: false | ||
} | ||
@options = OpenStruct.new(default_options.merge(options)) | ||
@extension = image_filename.split(".").last | ||
@filename_prefix = @options.filename_prefix | ||
|
||
begin | ||
@image = Magick::Image::read(image_filename).first | ||
rescue Magick::ImageMagickError => e | ||
puts "Could not open image #{image_filename}." | ||
exit | ||
end | ||
|
||
if @options.auto_zoom_levels && @options.auto_zoom_levels > 20 | ||
puts "Warning: auto zoom levels hard limited to 20." | ||
@options.auto_zoom_levels = 20 | ||
end | ||
if @options.auto_zoom_levels && @options.auto_zoom_levels <= 0 | ||
@options.auto_zoom_levels = nil | ||
end | ||
|
||
puts "Opened #{image_filename}, #{@image.columns} x #{@image.rows}" | ||
|
||
# pre-process our inputs to work out what we're supposed to do | ||
tasks = [] | ||
|
||
if @options.auto_zoom_levels.nil? | ||
# if we have no auto zoom request, then | ||
# we dont shrink or scale, and save directly to the output | ||
# dir. | ||
tasks << { | ||
output_dir: @options.output_dir, # normal output dir | ||
scale: 1.0 # dont scale | ||
} | ||
else | ||
# do have zoom levels, so construct those tasks | ||
zoom_name = 20 | ||
scale = 1.0 | ||
tasks << { | ||
output_dir: File.join(@options.output_dir, zoom_name.to_s), | ||
scale: scale | ||
} | ||
(@options.auto_zoom_levels-1).times do |level| | ||
scale = scale / 2.0 | ||
zoom_name = zoom_name - 1 | ||
tasks << { | ||
output_dir: File.join(@options.output_dir, zoom_name.to_s), | ||
scale: scale | ||
} | ||
end | ||
end | ||
|
||
# run through tasks list | ||
tasks.each do |task| | ||
image = @image | ||
image_path = File.join(task[:output_dir], @filename_prefix) | ||
if task[:scale] != 1.0 | ||
# if scale required, scale image | ||
begin | ||
image = @image.scale(task[:scale]) | ||
rescue RuntimeError => e | ||
message = "Failed to scale image, are you sure the original image "\ | ||
"is large enough to scale down this far (#{scale}) with this "\ | ||
"tilesize (#{@options.tile_width}x#{@options.tile_height})?" | ||
puts message | ||
exit | ||
end | ||
end | ||
# make output dir | ||
make_path(task[:output_dir]) | ||
self.make_tiles(image, image_path, @options.tile_width, @options.tile_height) | ||
image = nil | ||
end | ||
|
||
puts "Finished." | ||
|
||
end | ||
|
||
def make_path(directory_path) | ||
parts = directory_path.split(File::SEPARATOR); | ||
parts.each_index do |i| | ||
upto = parts[0..i].join(File::SEPARATOR) | ||
Dir::mkdir(upto) unless Dir::exists?(upto) | ||
end | ||
end | ||
|
||
def make_tiles(image, filename_prefix, tile_width, tile_height) | ||
# find image width and height | ||
# then find out how many tiles we'll get out of | ||
# the image, then use that for the xy offset in crop. | ||
num_columns = image.columns/tile_width | ||
num_rows = image.rows/tile_height | ||
x,y,column,row = 0,0,0,0 | ||
crops = [] | ||
|
||
puts "Tiling image into columns: #{num_columns}, rows: #{num_rows}" | ||
|
||
while true | ||
x = column * tile_width | ||
y = row * tile_height | ||
crops << { | ||
x: x, | ||
y: y, | ||
row: row, | ||
column: column | ||
} | ||
column = column + 1 | ||
if column >= num_columns | ||
column = 0 | ||
row = row + 1 | ||
end | ||
if row >= num_rows | ||
break | ||
end | ||
end | ||
|
||
crops.each do |c| | ||
ci = image.crop(c[:x], c[:y], tile_width, tile_height, true); | ||
print "Saving tile: #{c[:row]}, #{c[:column]}..." if @options.verbose | ||
ci.write("#{filename_prefix}_#{c[:column]}_#{c[:row]}.#{@extension}") | ||
print "\rSaving tile: #{c[:row]}, #{c[:column]}... saved\n" if @options.verbose | ||
ci = nil | ||
end | ||
end | ||
|
||
end | ||
|
||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
Gem::Specification.new do |s| | ||
s.name = 'tileup' | ||
s.version = '0.1.0' | ||
s.date = '2013-02-23' | ||
s.summary = "Turn an image into an X,Y tile set for use with JS mapping libraries" | ||
s.description = s.summary | ||
s.authors = ["Oliver Marriott"] | ||
s.email = 'hello@omarriott.com' | ||
s.files = ["lib/tileup.rb"] | ||
s.homepage = | ||
'http://rubygems.org/gems/tileup' | ||
s.executables << 'tileup' | ||
s.add_runtime_dependency "rmagick", ["~> 2.13.2"] | ||
end |