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 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: []