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 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,11 @@
1
+ # encoding: utf-8
2
+
3
+ module MapnikLegendary
4
+ class Tags
5
+ def self.merge_nulls(tags, extras)
6
+ tags = {} if tags.nil?
7
+ extras = [] if extras.nil?
8
+ Hash[extras.map { |t| [t, 'null'] }].merge(tags)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,5 @@
1
+ # encoding: utf-8
2
+
3
+ module MapnikLegendary
4
+ VERSION = '0.3.0'
5
+ 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: []