mapnik_legendary 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE.md +19 -0
- data/README.md +39 -0
- data/bin/mapnik_legendary +44 -0
- data/lib/mapnik_legendary/docwriter.rb +60 -0
- data/lib/mapnik_legendary/feature.rb +27 -0
- data/lib/mapnik_legendary/geometry.rb +39 -0
- data/lib/mapnik_legendary/part.rb +27 -0
- data/lib/mapnik_legendary/tags.rb +11 -0
- data/lib/mapnik_legendary/version.rb +5 -0
- data/lib/mapnik_legendary.rb +93 -0
- metadata +120 -0
data/LICENSE.md
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2013 Andy Allan
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all 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
|
19
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# Mapnik Legendary
|
2
|
+
|
3
|
+
[Mapnik Legendary](https://github.com/gravitystorm/mapnik-legendary) is a small utility to help with generating legends (aka map keys) from Mapnik stylesheets. You describe in a config file which attributes, and which zoom level(s) you want an image for, and it reads the stylesheet and spits out .png files. It uses the ruby-mapnik bindings to load the stylesheets and mess around with the datasources, so you don't actually need any of the shapefiles or database connections to make this work.
|
4
|
+
|
5
|
+
## Requirements
|
6
|
+
|
7
|
+
* [Ruby-Mapnik bindings](https://github.com/mapnik/Ruby-Mapnik) >= 0.2.0
|
8
|
+
* mapnik 2.x and ruby (both required for Ruby-Mapnik)
|
9
|
+
|
10
|
+
## Installation
|
11
|
+
|
12
|
+
In the future (i.e. when I make a packaged release) you'll be able to use rubygems. Until then:
|
13
|
+
|
14
|
+
`git clone https://github.com/gravitystorm/mapnik-legendary`
|
15
|
+
|
16
|
+
If you want to install the gem locally, run
|
17
|
+
|
18
|
+
```
|
19
|
+
gem build mapnik_legendary.gemspec
|
20
|
+
gem install mapnik_legendary-0.x.x.gem
|
21
|
+
```
|
22
|
+
|
23
|
+
## Running
|
24
|
+
|
25
|
+
To run locally, without installing a gem, run:
|
26
|
+
|
27
|
+
`ruby -Ilib bin/mapnik_legendary`
|
28
|
+
|
29
|
+
If you've installed the gem, `mapnik_legendary` will be in your path.
|
30
|
+
|
31
|
+
For full options, run
|
32
|
+
|
33
|
+
`mapnik_legendary -h`
|
34
|
+
|
35
|
+
## Examples
|
36
|
+
|
37
|
+
`mapnik_legendary examples/openstreetmap-carto-legend.yml osm-carto.xml`
|
38
|
+
|
39
|
+
See [examples/openstreetmap-carto-legend.yml](examples/openstreetmap-carto-legend.yml)
|
@@ -0,0 +1,44 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
|
4
|
+
require 'mapnik_legendary'
|
5
|
+
require 'mapnik_legendary/version'
|
6
|
+
require 'optparse'
|
7
|
+
require 'ostruct'
|
8
|
+
|
9
|
+
options = OpenStruct.new
|
10
|
+
options.overwrite = false
|
11
|
+
|
12
|
+
optparse = OptionParser.new do |opts|
|
13
|
+
opts.banner = 'Usage: mapnik_legendary [options] <legend_filename> <stylesheet_filename>'
|
14
|
+
|
15
|
+
opts.on('-z', '--zoom Z', Float, 'Override the zoom level stated in the legend file') do |z|
|
16
|
+
options.zoom = z
|
17
|
+
end
|
18
|
+
|
19
|
+
opts.on('--overwrite', 'Overwrite existing output files') do |o|
|
20
|
+
options.overwrite = o
|
21
|
+
end
|
22
|
+
|
23
|
+
opts.on_tail('-h', '--help', 'Show this message') do
|
24
|
+
puts opts
|
25
|
+
exit
|
26
|
+
end
|
27
|
+
|
28
|
+
opts.on_tail('--version', 'Show version') do
|
29
|
+
puts MapnikLegendary::VERSION
|
30
|
+
exit
|
31
|
+
end
|
32
|
+
end
|
33
|
+
optparse.parse!
|
34
|
+
|
35
|
+
# Check required conditions
|
36
|
+
if ARGV.length != 2
|
37
|
+
optparse.abort('Error: Two input filenames required')
|
38
|
+
exit(-1)
|
39
|
+
end
|
40
|
+
|
41
|
+
legend_file = ARGV[0]
|
42
|
+
map_file = ARGV[1]
|
43
|
+
|
44
|
+
MapnikLegendary.generate_legend(legend_file, map_file, options)
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'prawn/table'
|
3
|
+
require 'prawn'
|
4
|
+
|
5
|
+
module MapnikLegendary
|
6
|
+
class Docwriter
|
7
|
+
attr_accessor :image_width
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@entries = []
|
11
|
+
@image_width = 100
|
12
|
+
end
|
13
|
+
|
14
|
+
def add(image, description)
|
15
|
+
@entries << [image, description]
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_html
|
19
|
+
doc = '<html><head></head><body><table>' + "\n"
|
20
|
+
@entries.each do |entry|
|
21
|
+
doc += "<tr><td><img src=\"#{entry[0]}\"></td><td>#{entry[1]}</td></tr>\n"
|
22
|
+
end
|
23
|
+
doc += '</table></body></html>' + "\n"
|
24
|
+
doc
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_pdf(filename, title)
|
28
|
+
entries = @entries
|
29
|
+
image_width = @image_width
|
30
|
+
Prawn::Document.generate(filename, page_size: 'A4') do
|
31
|
+
font_families.update(
|
32
|
+
'Ubuntu' => { bold: '/usr/share/fonts/truetype/ubuntu-font-family/Ubuntu-B.ttf',
|
33
|
+
italic: '/usr/share/fonts/truetype/ubuntu-font-family/Ubuntu-RI.ttf',
|
34
|
+
normal: '/usr/share/fonts/truetype/ubuntu-font-family/Ubuntu-R.ttf' }
|
35
|
+
)
|
36
|
+
font 'Ubuntu'
|
37
|
+
font_size 12
|
38
|
+
text title, style: :bold, size: 24, align: :center
|
39
|
+
move_down(40)
|
40
|
+
data = Array.new
|
41
|
+
image_scale = 0.5
|
42
|
+
entries.each do |entry|
|
43
|
+
data << { image: File.join(File.dirname(filename), entry[0]),
|
44
|
+
scale: image_scale,
|
45
|
+
position: :center,
|
46
|
+
vposition: :center }
|
47
|
+
data << entry[1]
|
48
|
+
end
|
49
|
+
|
50
|
+
columns = image_width >= 200 ? 2 : 3
|
51
|
+
|
52
|
+
table(data.each_slice(columns * 2).to_a, cell_style: { border_width: 0.1 }) do
|
53
|
+
(0..columns - 1).each do |n|
|
54
|
+
column(n * 2).width = image_width * image_scale
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'mapnik_legendary/part'
|
4
|
+
|
5
|
+
module MapnikLegendary
|
6
|
+
# A feature has a name, description, and one or more parts holding geometries, tags and layers
|
7
|
+
class Feature
|
8
|
+
attr_reader :name, :parts, :description
|
9
|
+
|
10
|
+
def initialize(feature, zoom, map, extra_tags)
|
11
|
+
@name = feature['name']
|
12
|
+
@description = feature.key?('description') ? feature['description'] : @name.capitalize
|
13
|
+
@parts = []
|
14
|
+
if feature.key? 'parts'
|
15
|
+
feature['parts'].each do |part|
|
16
|
+
@parts << Part.new(part, zoom, map, extra_tags)
|
17
|
+
end
|
18
|
+
else
|
19
|
+
@parts << Part.new(feature, zoom, map, extra_tags)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def envelope
|
24
|
+
@parts.first.geom.envelope
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module MapnikLegendary
|
4
|
+
class Geometry
|
5
|
+
def initialize(type, zoom, map)
|
6
|
+
proj = Mapnik::Projection.new(map.srs)
|
7
|
+
width_of_world_in_pixels = 2**zoom * 256
|
8
|
+
width_of_world_in_metres = proj.forward(Mapnik::Coord2d.new(180, 0)).x - proj.forward(Mapnik::Coord2d.new(-180, 0)).x
|
9
|
+
width_of_image_in_metres = map.width.to_f / width_of_world_in_pixels * width_of_world_in_metres
|
10
|
+
height_of_image_in_metres = map.height.to_f / width_of_world_in_pixels * width_of_world_in_metres
|
11
|
+
|
12
|
+
@max_x = width_of_image_in_metres
|
13
|
+
@max_y = height_of_image_in_metres
|
14
|
+
@min_x = 0
|
15
|
+
@min_y = 0
|
16
|
+
|
17
|
+
@geom = case type
|
18
|
+
when 'point' then "POINT(#{@max_x / 2} #{@max_y / 2})"
|
19
|
+
when 'point75' then "POINT(#{@max_x * 0.5} #{@max_y * 0.75})"
|
20
|
+
when 'polygon' then "POLYGON((0 0, #{@max_x} 0, #{@max_x} #{@max_y}, 0 #{@max_y}, 0 0))"
|
21
|
+
when 'linestring-with-gap' then "MULTILINESTRING((0 0, #{@max_x * 0.45} #{@max_y * 0.45}),(#{@max_x * 0.55} #{@max_y * 0.55},#{@max_x} #{@max_y}))"
|
22
|
+
when 'polygon-with-hole' then "POLYGON((#{0.7 * @max_x} #{0.2 * @max_y}, #{0.9 * @max_x} #{0.9 * @max_y}" +
|
23
|
+
", #{0.3 * @max_x} #{0.8 * @max_y}, #{0.2 * @max_x} #{0.4 * @max_y}" +
|
24
|
+
", #{0.7 * @max_y} #{0.2 * @max_y}),( #{0.4 * @max_x} #{0.6 * @max_y}" +
|
25
|
+
", #{0.7 * @max_x} #{0.7 * @max_y}, #{0.6 * @max_x} #{0.4 * @max_y}" +
|
26
|
+
", #{0.4 * @max_x} #{0.6 * @max_y}))"
|
27
|
+
else "LINESTRING(0 0, #{@max_x} #{@max_y})"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_csv
|
32
|
+
%("#{@geom}")
|
33
|
+
end
|
34
|
+
|
35
|
+
def envelope
|
36
|
+
Mapnik::Envelope.new(@min_x, @min_y, @max_x, @max_y)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'mapnik_legendary/tags'
|
4
|
+
require 'mapnik_legendary/geometry'
|
5
|
+
|
6
|
+
module MapnikLegendary
|
7
|
+
# A part is a combination of tags, geometry and layers.
|
8
|
+
class Part
|
9
|
+
attr_reader :tags, :geom, :layers
|
10
|
+
|
11
|
+
def initialize(h, zoom, map, extra_tags)
|
12
|
+
@tags = Tags.merge_nulls(h['tags'], extra_tags)
|
13
|
+
@geom = Geometry.new(h['type'], zoom, map)
|
14
|
+
if h['layer']
|
15
|
+
@layers = [h['layer']]
|
16
|
+
else
|
17
|
+
@layers = h['layers']
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_csv
|
22
|
+
csv = ''
|
23
|
+
csv << @tags.keys.push('wkt').join(',') + "\n"
|
24
|
+
csv << @tags.values.push(@geom.to_csv).join(',') + "\n"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'mapnik'
|
4
|
+
require 'yaml'
|
5
|
+
require 'fileutils'
|
6
|
+
require 'logger'
|
7
|
+
|
8
|
+
require 'mapnik_legendary/feature'
|
9
|
+
require 'mapnik_legendary/docwriter'
|
10
|
+
|
11
|
+
module MapnikLegendary
|
12
|
+
DEFAULT_ZOOM = 17
|
13
|
+
|
14
|
+
def self.generate_legend(legend_file, map_file, options)
|
15
|
+
log = Logger.new(STDERR)
|
16
|
+
|
17
|
+
legend = YAML.load(File.read(legend_file))
|
18
|
+
|
19
|
+
if legend.key?('fonts_dir')
|
20
|
+
Mapnik::FontEngine.register_fonts(legend['fonts_dir'])
|
21
|
+
end
|
22
|
+
|
23
|
+
map = Mapnik::Map.from_xml(File.read(map_file), false, File.dirname(map_file))
|
24
|
+
map.width = legend['width']
|
25
|
+
map.height = legend['height']
|
26
|
+
map.srs = '+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over'
|
27
|
+
|
28
|
+
if legend.key?('background')
|
29
|
+
map.background = Mapnik::Color.new(legend['background'])
|
30
|
+
end
|
31
|
+
|
32
|
+
layer_styles = []
|
33
|
+
map.layers.each do |l|
|
34
|
+
layer_styles.push(name: l.name,
|
35
|
+
style: l.styles.map { |s| s } # get them out of the collection
|
36
|
+
)
|
37
|
+
end
|
38
|
+
|
39
|
+
docs = Docwriter.new
|
40
|
+
docs.image_width = legend['width']
|
41
|
+
|
42
|
+
legend['features'].each_with_index do |feature, idx|
|
43
|
+
# TODO: use a proper csv library rather than .join(",") !
|
44
|
+
zoom = options.zoom || feature['zoom'] || DEFAULT_ZOOM
|
45
|
+
feature = Feature.new(feature, zoom, map, legend['extra_tags'])
|
46
|
+
map.zoom_to_box(feature.envelope)
|
47
|
+
map.layers.clear
|
48
|
+
|
49
|
+
feature.parts.each do |part|
|
50
|
+
if part.layers.nil?
|
51
|
+
log.warn "Can't find any layers defined for a part of #{feature.name}"
|
52
|
+
next
|
53
|
+
end
|
54
|
+
part.layers.each do |layer_name|
|
55
|
+
ls = layer_styles.select { |l| l[:name] == layer_name }
|
56
|
+
if ls.empty?
|
57
|
+
log.warn "Can't find #{layer_name} in the xml file"
|
58
|
+
next
|
59
|
+
else
|
60
|
+
ls.each do |layer_style|
|
61
|
+
l = Mapnik::Layer.new(layer_name, map.srs)
|
62
|
+
datasource = Mapnik::Datasource.create(type: 'csv', inline: part.to_csv)
|
63
|
+
l.datasource = datasource
|
64
|
+
layer_style[:style].each do |style_name|
|
65
|
+
l.styles << style_name
|
66
|
+
end
|
67
|
+
map.layers << l
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
FileUtils.mkdir_p('output')
|
74
|
+
# map.zoom_to_box(Mapnik::Envelope.new(0,0,1,1))
|
75
|
+
id = feature.name || "legend-#{idx}"
|
76
|
+
filename = File.join(Dir.pwd, 'output', "#{id}-#{zoom}.png")
|
77
|
+
i = 0
|
78
|
+
while File.exist?(filename) && !options.overwrite
|
79
|
+
i += 1
|
80
|
+
filename = File.join(Dir.pwd, 'output', "#{id}-#{zoom}-#{i}.png")
|
81
|
+
end
|
82
|
+
map.render_to_file(filename, 'png256:t=2')
|
83
|
+
docs.add File.basename(filename), feature.description
|
84
|
+
end
|
85
|
+
|
86
|
+
f = File.open(File.join(Dir.pwd, 'output', 'docs.html'), 'w')
|
87
|
+
f.write(docs.to_html)
|
88
|
+
f.close
|
89
|
+
|
90
|
+
title = legend.key?('title') ? legend['title'] : 'Legend'
|
91
|
+
docs.to_pdf(File.join(Dir.pwd, 'output', 'legend.pdf'), title)
|
92
|
+
end
|
93
|
+
end
|
metadata
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mapnik_legendary
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.3.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Andy Allan
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2015-08-13 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: mapnik
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 0.2.0
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 0.2.0
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: prawn
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: prawn-table
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: rspec
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
description: Creating legends by hand is tedious. This software allows you to generate
|
79
|
+
them automatically.
|
80
|
+
email: andy@gravitystorm.co.uk
|
81
|
+
executables: []
|
82
|
+
extensions: []
|
83
|
+
extra_rdoc_files: []
|
84
|
+
files:
|
85
|
+
- lib/mapnik_legendary/tags.rb
|
86
|
+
- lib/mapnik_legendary/geometry.rb
|
87
|
+
- lib/mapnik_legendary/feature.rb
|
88
|
+
- lib/mapnik_legendary/part.rb
|
89
|
+
- lib/mapnik_legendary/version.rb
|
90
|
+
- lib/mapnik_legendary/docwriter.rb
|
91
|
+
- lib/mapnik_legendary.rb
|
92
|
+
- bin/mapnik_legendary
|
93
|
+
- LICENSE.md
|
94
|
+
- README.md
|
95
|
+
homepage: https://github.com/gravitystorm/mapnik-legendary
|
96
|
+
licenses:
|
97
|
+
- MIT
|
98
|
+
post_install_message:
|
99
|
+
rdoc_options: []
|
100
|
+
require_paths:
|
101
|
+
- lib
|
102
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
103
|
+
none: false
|
104
|
+
requirements:
|
105
|
+
- - ! '>='
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
version: '0'
|
108
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
109
|
+
none: false
|
110
|
+
requirements:
|
111
|
+
- - ! '>='
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: '0'
|
114
|
+
requirements: []
|
115
|
+
rubyforge_project:
|
116
|
+
rubygems_version: 1.8.23
|
117
|
+
signing_key:
|
118
|
+
specification_version: 3
|
119
|
+
summary: Create legends (map keys) for mapnik stylesheets
|
120
|
+
test_files: []
|