Skip to content

Commit

Permalink
Merge branch 'release/v0.1.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
omarriott committed Feb 24, 2013
2 parents 2143adb + af4c2b2 commit 0e67e1c
Show file tree
Hide file tree
Showing 4 changed files with 273 additions and 0 deletions.
73 changes: 73 additions & 0 deletions README.md
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
44 changes: 44 additions & 0 deletions bin/tileup
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)
142 changes: 142 additions & 0 deletions lib/tileup.rb
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
14 changes: 14 additions & 0 deletions tileup.gemspec
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

0 comments on commit 0e67e1c

Please sign in to comment.