mapnik_legendary 0.3.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.
- 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: []
|