map_print 0.1.0 → 0.2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6dab073cc345466b64e2a4c61f06bc08edc4604b
4
- data.tar.gz: b4c5678c67565e3372759c90b10becb8ab25f961
3
+ metadata.gz: 139b682f39bdac95f76c9c486a146dc16376e0f6
4
+ data.tar.gz: 6df45284cb99894a860474e60b8443cbe4adf458
5
5
  SHA512:
6
- metadata.gz: bc8bfa8a173e93068ece013326b663c7f12e828bb6a9f43afb35ca4f4ae7fadb240b364a503a2ad10da372ed2069485005e15b860a6c3fd30725749e30a74d0f
7
- data.tar.gz: 9c250065b686fb6c121ad5aba9a2fe3655f7577ed53baad3cdd3bd58d7ed9ae61753a5ef864c790fe9dbfbe6f9ffa8ade5172b56e07fc1100c5ef24b307c84e6
6
+ metadata.gz: 5ecda32b632e257d4e79bd22900480796e2bb0f3167de4c5b53661902a923b8508314be59e69b6dd1b23e0a24638cd22f8eca0ae31954c59ada553d7b190ad57
7
+ data.tar.gz: 298ed01e24f773854d869243140641f33a5982fc640a504a3c3aa958cb9f79bb08d289a3ac77af272bf8ab57f130c45b6e6c10756c893f4903ea025b83965640
data/Gemfile.lock CHANGED
@@ -4,20 +4,55 @@ PATH
4
4
  map_print (0.1.0)
5
5
  geo-distance (~> 0.1)
6
6
  mini_magick (~> 4.3)
7
+ parallel (~> 1.6.1)
7
8
  prawn (~> 2.0)
9
+ prawn-fast-png (~> 0.2.3)
10
+ thor
8
11
 
9
12
  GEM
10
13
  remote: https://rubygems.org/
11
14
  specs:
12
- geo-distance (0.1.2)
15
+ activesupport (4.2.5)
16
+ i18n (~> 0.7)
17
+ json (~> 1.7, >= 1.7.7)
18
+ minitest (~> 5.1)
19
+ thread_safe (~> 0.3, >= 0.3.4)
20
+ tzinfo (~> 1.1)
21
+ geo-distance (0.2.0)
22
+ geo_point (~> 0.2.5)
23
+ geo_units (~> 0.2.4.1)
24
+ geo_calc (0.7.6)
25
+ activesupport (>= 3.0.1)
26
+ geo_units (~> 0.2.1)
27
+ i18n
28
+ require_all (~> 1.2.0)
29
+ sugar-high (~> 0.4.6.3)
30
+ geo_point (0.2.5)
31
+ geo_calc (~> 0.7.5)
32
+ geo_units (~> 0.2.1)
33
+ geo_units (0.2.4.1)
34
+ sugar-high (~> 0.4.6.2)
35
+ i18n (0.7.0)
36
+ json (1.8.3)
13
37
  mini_magick (4.3.6)
14
38
  minitest (5.8.2)
39
+ parallel (1.6.1)
15
40
  pdf-core (0.6.0)
16
41
  prawn (2.0.2)
17
42
  pdf-core (~> 0.6.0)
18
43
  ttfunk (~> 1.4.0)
44
+ prawn-fast-png (0.2.3)
45
+ prawn
46
+ rmagick
19
47
  rake (10.4.2)
48
+ require_all (1.2.1)
49
+ rmagick (2.15.4)
50
+ sugar-high (0.4.6.4)
51
+ thor (0.19.1)
52
+ thread_safe (0.3.5)
20
53
  ttfunk (1.4.0)
54
+ tzinfo (1.2.2)
55
+ thread_safe (~> 0.1)
21
56
 
22
57
  PLATFORMS
23
58
  ruby
@@ -25,7 +60,7 @@ PLATFORMS
25
60
  DEPENDENCIES
26
61
  bundler (~> 1.10)
27
62
  map_print!
28
- minitest
63
+ minitest (~> 5.8)
29
64
  rake (~> 10.0)
30
65
 
31
66
  BUNDLED WITH
data/README.md CHANGED
@@ -20,7 +20,118 @@ Or install it yourself as:
20
20
 
21
21
  ## Usage
22
22
 
23
- map_print file.pdf
23
+ ### Executable
24
+
25
+ `map_print print --south-west="-35.026862,-58.425003" --north-east="-29.980172,-52.959305" --zoom="10" --output="output.png"`
26
+
27
+ Indicating southwest and northeast to determine the bounding box. Set the zoom at which the tiles should be requested.
28
+
29
+ In addition set the `output` path to which the resulting image will be written.
30
+
31
+ This is intended to be more of a testing method as it doesn't support all the options available and always returns a png. In the future we might provide more functionality depending on feature request.
32
+
33
+ ### In ruby code
34
+
35
+ The mos common usage you'll be expecting is to create a new instance with the map configuration and then call `print` with the output path. MapPrint will take the map configuration, generate the map and write the output to the output path. In the example below `./map.png`.
36
+
37
+ ```ruby
38
+ MapPrint::Core.new(map_configuration).print('./map.png')```
39
+
40
+ `map_configuration` is a hash with the following options:
41
+
42
+ ```ruby
43
+ BASIC_MAP = {
44
+ format: 'pdf', # pdf or png
45
+ pdf_options: {
46
+ page_size: 'A4', # A0-10, B0-10, C0-10
47
+ page_layout: :portrait # :portrait, :landscape
48
+ },
49
+ map: {
50
+ sw: { # required
51
+ lat: -35.026862,
52
+ lng: -58.425003
53
+ },
54
+ ne: { # required
55
+ lat: -29.980172,
56
+ lng: -52.959305
57
+ },
58
+ zoom: 9, # whatever is supported by the source, typically between 1-15
59
+ position: { # x,y position on the PDF (as prawn determines it)
60
+ x: 50,
61
+ y: 50
62
+ },
63
+ size: { # Resize the map image, typically used to print the map on pdf
64
+ width: 500,
65
+ height: 800
66
+ },
67
+ layers: [{ # Whatever layers you want to include. Currently only OSM and Bing are supported
68
+ type: 'osm', # (osm, bing) to understand variable substitution and stitching toghether the final image
69
+ urls: ['http://a.tile.thunderforest.com/transport/${z}/${x}/${y}.png'], # currently only one is being used, in the future it will load balance
70
+ level: 1, # used to order the layer appearance
71
+ opacity: 1.0 # in case you want the layer to have some transparency
72
+ }], # see geojson specification for feature/object support. See http://leafletjs.com/reference.html#path-options for formatting options, the following attributes under properties are supported: `stroke, color, weight, opacity, fill, fillColor, fillOpacity, fillRule, dashArray, lineCap, lineJoin`
73
+ geojson: '{
74
+ "type": "FeatureCollection",
75
+ "features": [{
76
+ "type":"Feature",
77
+ "geometry":{"type":"Point", "coordinates":[-32.026862,-55.425003]},
78
+ "properties":{"image": "./marker.png"}
79
+ }, {
80
+ "type": "Feature",
81
+ "geometry": {"type": "LineString", "coordinates": [ [-32.026862,-55.425003], [-31.026862,-55.425003], [-31.026862,-54.425003], [-32.026862,-54.425003] ] },
82
+ "properties": {"color": "#000000"}
83
+ }, {
84
+ "type": "Feature",
85
+ "geometry": {"type": "Polygon", "coordinates": [ [-32.126862,-55.825003], [-31.426862,-55.225003], [-31.326862,-54.825003], [-32.146862,-54.835003] ] },
86
+ "properties": {
87
+ "stroke": true,
88
+ "color": "#000000",
89
+ "weight": 2,
90
+ "opacity": 1,
91
+ "fill": true,
92
+ "fillColor": "#ffffff",
93
+ "fillOpacity": 1,
94
+ "fillRule": "evenodd",
95
+ "dashArray": "5,2,3",
96
+ "lineCap": "round",
97
+ "lineJoin": "round"
98
+ }
99
+ }]
100
+ }'
101
+ },
102
+ images: [ # not supported yet
103
+ {
104
+ path: './file.png',
105
+ position: {x: 50, y: 50 },
106
+ size: {width: 50, height: 50},
107
+ options: {}
108
+ }
109
+ ],
110
+ texts: [ # not supported yet
111
+ {
112
+ text: "some text",
113
+ position: {x: 50, y: 50 },
114
+ size: {width: 50, height: 50},
115
+ options: {}
116
+ }
117
+ ],
118
+ legend: { # not supported yet
119
+ position: {x: 50, y: 50},
120
+ size: {width: 50, height: 50},
121
+ columns: 5,
122
+ rows: 5,
123
+ elements: [{
124
+ image: './file.png',
125
+ text: 'text'
126
+ }]
127
+ },
128
+ scalebar: { # not supported yet
129
+ unit: 'meters', # meters, km, miles, feet
130
+ position: {x: 50, y: 50},
131
+ size: {width: 50, height: 50}
132
+ }
133
+ }
134
+ ```
24
135
 
25
136
  ## Development
26
137
 
data/bin/map_print CHANGED
@@ -1,6 +1,54 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require "bundler/setup"
4
- require "map_print"
3
+ require 'thor'
4
+ require 'bundler/setup'
5
+ require 'map_print'
5
6
 
6
- MapPrint.print ARGV[0]
7
+ class MapPrintCommand < Thor
8
+
9
+ desc "print", "Print a map using the given parameters"
10
+
11
+ method_option :south_west, type: :string, required: true
12
+ method_option :north_east, type: :string, required: true
13
+ method_option :output, type: :string, required: true
14
+ method_option :zoom, type: :numeric, default: 10
15
+
16
+ method_option :provider, type: :string, default: 'osm',
17
+ enum: ['osm', 'bing']
18
+
19
+ def print
20
+ south_west_coordinate = options[:south_west].split(',')
21
+ .map {|coordinate| Float(coordinate)}
22
+ north_east_coordinate = options[:north_east].split(',')
23
+ .map {|coordinate| Float(coordinate)}
24
+
25
+ south_west = MapPrint::LatLng.new(*south_west_coordinate)
26
+ north_east = MapPrint::LatLng.new(*north_east_coordinate)
27
+
28
+ map_options = {
29
+ format: 'png',
30
+ map: {
31
+ sw: {
32
+ lat: south_west.lat,
33
+ lng: south_west.lng
34
+ },
35
+ ne: {
36
+ lat: north_east.lat,
37
+ lng: north_east.lng
38
+ },
39
+ zoom: options[:zoom],
40
+ layers: [{
41
+ type: 'osm', # to understand variable substitution and stitching toghether the final image
42
+ urls: ['http://a.tile.thunderforest.com/transport/${z}/${x}/${y}.png'],
43
+ level: 1,
44
+ opacity: 1.0 # in case you want the layer to have some transparency
45
+ }]
46
+ }
47
+ }
48
+
49
+ MapPrint::Core.new(map_options).print(options[:output])
50
+ end
51
+
52
+ end
53
+
54
+ MapPrintCommand.start
data/lib/base.rb ADDED
@@ -0,0 +1,3 @@
1
+ module MapPrint
2
+
3
+ end
@@ -1,41 +1,132 @@
1
- require 'map_print/lat_lng'
2
- require 'map_print/osm/tile'
3
- require 'map_print/osm/tile_factory'
1
+ require_relative 'lat_lng'
2
+ require_relative 'tiles/tile'
3
+ require_relative 'tiles/tile_factory'
4
+ require_relative 'providers/base'
5
+ require_relative 'providers/bing'
6
+ require_relative 'providers/open_street_map'
7
+ require_relative 'layer_handler'
8
+ require_relative 'scalebar_handler'
9
+ require_relative 'image_handler'
10
+ require_relative 'text_handler'
11
+ require_relative 'legend_handler'
12
+ require_relative 'geo_json_handler'
4
13
 
5
14
  module MapPrint
6
15
  class Core
7
- def self.print(path)
8
- puts path
9
- sw = LatLng.new(-35.026862, -58.425003)
10
- ne = LatLng.new(-29.980172, -52.959305)
11
- osm = OSM::TileFactory.new('http://a.tile.openstreetmap.org/${z}/${x}/${y}.png', sw, ne, 7)
12
-
13
- osm.tiles.collect &:download
14
-
15
- MiniMagick::Tool::Montage.new do |montage|
16
- montage.mode('concatenate')
17
- montage.tile("#{osm.x_size}x#{osm.y_size}")
18
- montage.merge! osm.tiles.collect(&:path)
19
- montage << 'output.jpg'
16
+ attr_accessor :map, :images, :texts, :legend, :scalebar
17
+
18
+ PROVIDERS = {
19
+ 'bing' => MapPrint::Providers::Bing,
20
+ 'osm' => MapPrint::Providers::OpenStreetMap
21
+ }
22
+
23
+ def initialize(args)
24
+ @format = args[:format]
25
+ @pdf_options = args[:pdf_options]
26
+ @map = args[:map]
27
+ @images = args[:images]
28
+ @texts = args[:texts]
29
+ @legend = args[:legend]
30
+ @scalebar = args[:scalebar]
31
+ end
32
+
33
+ def print(output_path)
34
+ @output_path = output_path
35
+
36
+ if @format == 'pdf'
37
+ print_pdf
38
+ elsif @format == 'png'
39
+ print_png
40
+ else
41
+ raise "Unsupported format: #{@format}"
20
42
  end
43
+ end
44
+
45
+ private
46
+ def print_pdf
47
+ pdf = init_pdf
48
+ map_image = print_layers
49
+ map_image = print_geojson(MiniMagick::Image.new(map_image.path))
50
+
51
+ FileUtils.cp map_image.path, './map.png' if defined?(DEBUG)
52
+
53
+ pdf.image map_image.path, at: [@map[:position][:x], pdf.bounds.top - @map[:position][:y]]
54
+
55
+ print_images_on_pdf(pdf)
56
+ print_texts_on_pdf(pdf)
57
+ print_legend_on_pdf(pdf)
58
+
59
+ pdf.render_file(@output_path)
60
+ @output_path
61
+ end
62
+
63
+ def print_png
64
+ map_image = print_layers
65
+ map_image = print_geojson(MiniMagick::Image.new(map_image.path))
66
+
67
+ print_images_on_png(map_image)
68
+ print_texts_on_png(map_image)
69
+ print_legend_on_png(map_image)
70
+
71
+ FileUtils.cp map_image.path, @output_path
72
+ end
73
+
74
+ def init_file
75
+ @file = File.open @output_path, 'wb'
76
+ end
21
77
 
22
- puts osm.px_offset
78
+ def init_pdf
79
+ Prawn::Document.new @pdf_options || {}
80
+ end
23
81
 
24
- osm.tiles.each &:delete
82
+ def print_layers
83
+ file = LayerHandler.new(@map[:layers], @map[:sw], @map[:ne], @map[:zoom]).process
84
+ size = @map[:size]
25
85
 
26
- image = MiniMagick::Image.new('output.jpg')
27
- width = image.width - osm.px_offset[:left] - osm.px_offset[:right]
28
- height = image.height - osm.px_offset[:top] - osm.px_offset[:bottom]
29
- puts "width – old: #{image.width} - new #{width}"
30
- puts "height – old: #{image.height} - new #{height}"
86
+ FileUtils.cp file.path, 'layers.png' if defined?DEBUG
31
87
 
32
- image.crop("#{width}x#{height}+#{osm.px_offset[:left]}+#{osm.px_offset[:top]}").repage("#{width}x#{height}")
88
+ if size
89
+ image = MiniMagick::Image.new(file.path)
90
+ size[:width] ||= image.width
91
+ size[:height] ||= image.height
92
+ puts "Fitting map image (#{image.width}x#{image.height}) in #{size[:width]}x#{size[:height]}"
93
+ image.colorspace("RGB").resize("#{size[:width]}x#{size[:height]}\>").colorspace("sRGB").unsharp "0x0.75+0.75+0.008"
94
+ end
33
95
 
34
- Prawn::Document.generate(path) do
35
- image 'output.jpg', width: 500, at: [0, 700]
96
+ file
97
+ end
98
+
99
+ def print_geojson(map_image)
100
+ if @map[:geojson]
101
+ geojson_image = GeoJSONHandler.new(@map[:geojson], @map[:sw], @map[:ne], map_image.width, map_image.height).process
102
+ result = MiniMagick::Image.open(map_image.path).composite(geojson_image) do |c|
103
+ c.compose "atop"
104
+ end
105
+ result.write map_image.path
36
106
  end
37
107
 
38
- #File.delete 'output.jpg' if File.exist?('output.jpg')
108
+ map_image
109
+ end
110
+
111
+ def print_images_on_pdf(pdf)
112
+ end
113
+
114
+ def print_texts_on_pdf(pdf)
115
+ end
116
+
117
+ def print_scalebar
118
+ end
119
+
120
+ def print_legend_on_pdf(pdf)
121
+ end
122
+
123
+ def print_images_on_png(png)
124
+ end
125
+
126
+ def print_texts_on_png(png)
127
+ end
128
+
129
+ def print_legend_on_png(png)
39
130
  end
40
131
  end
41
132
  end
@@ -0,0 +1,135 @@
1
+ require 'json'
2
+
3
+ module MapPrint
4
+ class GeoJSONHandler
5
+ def initialize(geojson, sw, ne, width, height)
6
+ @top_lat = ne[:lat]
7
+ @total_lat = ne[:lat] - sw[:lat]
8
+ @left_lng = sw[:lng]
9
+ @total_lng = ne[:lng] - sw[:lng]
10
+ @height = height
11
+ @width = width
12
+ @geojson = JSON[geojson]
13
+ end
14
+
15
+ def process
16
+ tempfile = Tempfile.new ['geojson', '.png']
17
+ `convert -size #{@width}x#{@height} xc:transparent #{tempfile.path}`
18
+ @image = MiniMagick::Image.new tempfile.path
19
+
20
+ draw_geojson
21
+
22
+ @image.write(tempfile.path)
23
+ MiniMagick::Image.open(tempfile.path)
24
+ ensure
25
+ tempfile.close
26
+ end
27
+
28
+ def draw_geojson
29
+ if @geojson['type'] == 'Feature'
30
+ feature(@geojson['geometry'], @geojson['properties'])
31
+ elsif @geojson['type'] == 'FeatureCollection'
32
+ feature_collection(@geojson['features'])
33
+ else
34
+ puts "Warning, expected type Feature with #{@geojson['type']} inside geometry and drawing properties, like: {'type': 'Feature', 'geometry':#{@geojson.to_json}, 'properties':{'image': 'path/or/url/to/image'}}"
35
+ end
36
+ end
37
+
38
+ def feature(geometry, properties={})
39
+ case geometry['type']
40
+ when 'Feature'
41
+ feature(geometry['geometry'], geometry['properties'])
42
+ when 'FeatureCollection'
43
+ feature_collection(geometry['features'])
44
+ when 'Point'
45
+ point(geometry, properties['image'])
46
+ when 'LineString'
47
+ line_string(geometry, properties)
48
+ when 'Polygon'
49
+ polygon(geometry, properties)
50
+ when 'MultiPoint'
51
+ multi_point(geometry, properties)
52
+ when 'MultiLineString'
53
+ multi_line_string(geometry, properties)
54
+ when 'MultiPolygon'
55
+ multi_polygon(geometry, properties)
56
+ when 'GeometryCollection'
57
+ geometry_collection(geometry, properties)
58
+ end
59
+ end
60
+
61
+ def feature_collection(features)
62
+ features.each do |object|
63
+ feature(object)
64
+ end
65
+ end
66
+
67
+ def point(point, image_path)
68
+ x = get_x(point['coordinates'][1])
69
+ y = get_y(point['coordinates'][0])
70
+
71
+ point_image = MiniMagick::Image.open(image_path)
72
+ x -= point_image.width / 2
73
+ y -= point_image.height / 2
74
+
75
+ @image.composite(point_image) do |c|
76
+ c.geometry("+#{x}+#{y}")
77
+ end.write @image.path
78
+ end
79
+
80
+ def line_string(geometry, properties)
81
+ properties ||= {}
82
+ points = geometry['coordinates'].map do |coord|
83
+ "#{get_x(coord[1])},#{get_y(coord[0])}"
84
+ end
85
+
86
+ draw_command = (0..(points.length - 2)).map do |i|
87
+ "line #{points[i]} #{points[i+1]}"
88
+ end.join(' ')
89
+
90
+ @image.combine_options do |c|
91
+ c.draw "#{draw_options(properties)} #{draw_command}"
92
+ end
93
+ end
94
+
95
+ def polygon(geometry, properties)
96
+ properties ||= {}
97
+ points = geometry['coordinates'].map do |coord|
98
+ "#{get_x(coord[1])},#{get_y(coord[0])}"
99
+ end
100
+
101
+ @image.combine_options do |c|
102
+ c.draw "#{draw_options(properties, false)} polygon #{points.join(' ')}"
103
+ end
104
+ end
105
+
106
+ private
107
+ def draw_options(properties, line=true)
108
+ options = ''
109
+ if properties['stroke'] || properties['stroke'].nil?
110
+ options += "stroke #{properties['color'] || '#0033ff'} "
111
+ options += "stroke-width #{properties['weight'] || 5} "
112
+ options += "stroke-opacity #{properties['opacity'] || 0.5} "
113
+ end
114
+
115
+ if properties['fill'] || (!line && properties['fill'].nil?)
116
+ options += "fill #{properties['fillColor'] || '#0033ff'} "
117
+ options += "fill-opacity #{properties['fillOpacity'] || 0.2} "
118
+ options += "fill-rule #{properties['fillRule'] || 'evenodd'} "
119
+ end
120
+
121
+ options += "stroke-dasharray #{properties['dashArray']} " if properties['dashArray']
122
+ options += "stroke-linecap #{properties['lineCap']} " if properties['lineCap']
123
+ options += "stroke-linejoin #{properties['lineJoin']} " if properties['lineJoin']
124
+ options
125
+ end
126
+
127
+ def get_x(lng)
128
+ @width * (lng - @left_lng) / @total_lng;
129
+ end
130
+
131
+ def get_y(lat)
132
+ @height * (1 - (@top_lat - lat) / @total_lat);
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,9 @@
1
+ module MapPrint
2
+ class ImageHandler
3
+ def initialize(images, file)
4
+ end
5
+
6
+ def process
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,44 @@
1
+ module MapPrint
2
+ class LayerHandler
3
+ PROVIDERS = {
4
+ 'bing' => MapPrint::Providers::Bing,
5
+ 'osm' => MapPrint::Providers::OpenStreetMap
6
+ }
7
+
8
+ def initialize(layers, south_west, north_east, zoom)
9
+ @layers = layers.sort_by { |layer| layer[:level] }
10
+ @south_west = MapPrint::LatLng.new(south_west[:lat], south_west[:lng])
11
+ @north_east = MapPrint::LatLng.new(north_east[:lat], north_east[:lng])
12
+ @zoom = zoom
13
+ end
14
+
15
+ def process
16
+ @layers.each do |layer|
17
+ provider_class = PROVIDERS[layer[:type]]
18
+ provider = provider_class.new(@south_west, @north_east, @zoom, layer[:urls].first)
19
+ layer[:image] = provider.download
20
+ end
21
+
22
+ @layers.first[:image].close
23
+ image = MiniMagick::Image.open(@layers.first[:image].path)
24
+ tmp_file = Tempfile.new(['layers', '.png'])
25
+
26
+ @layers[1..-1].each do |layer|
27
+ next_image = layer[:image]
28
+ next_image.close
29
+ tmp_image = MiniMagick::Image.open(next_image.path)
30
+ result = image.composite(tmp_image) do |c|
31
+ c.compose "atop"
32
+ if layer[:opacity] && layer[:opacity] < 1
33
+ c.blend layer[:opacity] * 100
34
+ end
35
+ end
36
+ result.write tmp_file.path
37
+ image = MiniMagick::Image.open(tmp_file.path)
38
+ end
39
+ image.write tmp_file.path
40
+ tmp_file.close
41
+ tmp_file
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,9 @@
1
+ module MapPrint
2
+ class LegendHandler
3
+ def initialize(legend, file)
4
+ end
5
+
6
+ def process
7
+ end
8
+ end
9
+ end
@@ -1,9 +1,11 @@
1
1
  require 'open-uri'
2
+ require 'fileutils'
2
3
 
3
4
  module MapPrint
4
5
  module OSM
5
6
  class Tile
6
- attr_reader :path
7
+
8
+ BIT_TO_QUADKEY = { [false, false] => "0", [false, true] => "1", [true, false] => "2", [true, true] => "3"}
7
9
 
8
10
  METERS_PER_PIXELS = {
9
11
  0 => 26862156543.031,
@@ -28,17 +30,18 @@ module MapPrint
28
30
  }
29
31
 
30
32
  class << self
33
+
31
34
  def meters_per_pixel(zoom)
32
35
  METERS_PER_PIXELS[zoom]
33
36
  end
37
+
34
38
  end
35
39
 
36
- def initialize(x, y, z, base_url=nil)
40
+ def initialize(x, y, z, base_url)
37
41
  @base_url = base_url
38
42
  @x = x
39
43
  @y = y
40
44
  @z = z
41
- @path = "#{@x}-#{@y}-#{@z}.png"
42
45
  end
43
46
 
44
47
  def coords
@@ -46,26 +49,14 @@ module MapPrint
46
49
  end
47
50
 
48
51
  def download
49
- puts "getting url #{get_url}"
50
-
51
- delete
52
- @image = open(get_url).read
53
- File.open @path, 'wb' do |f|
54
- f.write @image
52
+ unless File.exists?(file_path)
53
+ content = open(get_url).read
54
+ write_file(content)
55
55
  end
56
56
  end
57
57
 
58
- def delete
59
- File.delete @path if File.exist?(@path)
60
- end
61
-
62
58
  def get_pixel_difference(lat_lng)
63
59
  tile_lat_lng = tile_number_to_lat_lng
64
- puts '================'
65
- puts lat_lng.lat
66
- puts lat_lng.lng
67
- puts tile_lat_lng
68
- puts '================'
69
60
 
70
61
  x_pixels = GeoDistance.distance(lat_lng.lat, lat_lng.lng, lat_lng.lat, tile_lat_lng[:lng]).meters.number / METERS_PER_PIXELS[@z]
71
62
  y_pixels = GeoDistance.distance(lat_lng.lat, lat_lng.lng, tile_lat_lng[:lat], lat_lng.lng).meters.number / METERS_PER_PIXELS[@z]
@@ -82,9 +73,52 @@ module MapPrint
82
73
  { lat: lat_deg, lng: lon_deg }
83
74
  end
84
75
 
76
+ def file_path
77
+ File.join(folder_name, "#{@y}.png")
78
+ end
79
+
80
+ def tile2quad
81
+ quadkey_chars = []
82
+
83
+ tx = @x.to_i
84
+ ty = @y.to_i
85
+
86
+ @z.times do
87
+ quadkey_chars.push BIT_TO_QUADKEY[[ty.odd?, tx.odd?]] # bit order y,x
88
+ tx >>= 1 ; ty >>= 1
89
+ end
90
+
91
+ quadkey_chars.join.reverse
92
+ end
93
+
85
94
  private
95
+
96
+ def write_file(content)
97
+ FileUtils.mkdir_p(folder_name)
98
+
99
+ File.open file_path, 'wb' do |f|
100
+ f.write content
101
+ end
102
+ end
103
+
104
+ def provider_name
105
+ if @base_url =~ /openstreetmap/
106
+ 'osm'
107
+ elsif @base_url =~ /virtualearth/
108
+ 'bing'
109
+ end
110
+ end
111
+
112
+ def folder_name
113
+ "cache/#{provider_name}/#{@z}/#{@x}"
114
+ end
115
+
86
116
  def get_url
87
- @base_url.gsub('${x}', @x.to_s).gsub('${y}', @y.to_s).gsub('${z}', @z.to_s)
117
+ if provider_name == 'osm'
118
+ @base_url.gsub('${x}', @x.to_s).gsub('${y}', @y.to_s).gsub('${z}', @z.to_s)
119
+ elsif provider_name == 'bing'
120
+ @base_url.gsub('${quadkey}', tile2quad)
121
+ end
88
122
  end
89
123
  end
90
124
  end
@@ -1,6 +1,7 @@
1
1
  module MapPrint
2
2
  module OSM
3
3
  class TileFactory
4
+
4
5
  def initialize(base_url, sw_lat_lng, ne_lat_lng, zoom)
5
6
  @base_url = base_url
6
7
  @sw_lat_lng = sw_lat_lng
@@ -19,6 +20,12 @@ module MapPrint
19
20
  @px_offset = offset
20
21
  end
21
22
 
23
+ def download
24
+ Parallel.each(tiles, in_processes: 20) do |tile|
25
+ tile.download
26
+ end
27
+ end
28
+
22
29
  def tiles
23
30
  return @tiles if @tiles
24
31
 
@@ -0,0 +1,55 @@
1
+ require 'tempfile'
2
+
3
+ module MapPrint
4
+ module Providers
5
+
6
+ class Base
7
+
8
+ attr_accessor :provider
9
+
10
+ attr_accessor :south_west, :north_east, :zoom
11
+
12
+ def initialize(south_west, north_east, zoom, base_url=nil)
13
+ @south_west, @north_east, @zoom = south_west, north_east, zoom
14
+ @provider = build_provider(base_url)
15
+ end
16
+
17
+ def build_provider(base_url = nil)
18
+ raise 'SubClasses must override this method'
19
+ end
20
+
21
+ def download
22
+ provider.download
23
+
24
+ to_image
25
+ end
26
+
27
+ protected
28
+
29
+ def to_image
30
+ file = Tempfile.new(['map', '.png'])
31
+
32
+ MiniMagick::Tool::Montage.new do |montage|
33
+ montage.mode('concatenate')
34
+ montage.tile("#{provider.x_size}x#{provider.y_size}")
35
+ montage.merge! provider.tiles.collect(&:file_path)
36
+ montage << file.path
37
+ end
38
+
39
+ result_file = Tempfile.new(['result', '.png'])
40
+
41
+ image = MiniMagick::Image.new(file.path)
42
+ width = image.width - provider.px_offset[:left] - provider.px_offset[:right]
43
+ height = image.height - provider.px_offset[:top] - provider.px_offset[:bottom]
44
+
45
+ image.crop("#{width}x#{height}+#{provider.px_offset[:left]}+#{provider.px_offset[:top]}").repage("#{width}x#{height}")
46
+ image.write result_file.path
47
+
48
+ file.close
49
+ result_file
50
+ end
51
+
52
+ end
53
+
54
+ end
55
+ end
@@ -0,0 +1,13 @@
1
+ module MapPrint
2
+ module Providers
3
+
4
+ class Bing < Base
5
+ BASE_URL = 'http://ecn.t0.tiles.virtualearth.net/tiles/r${quadkey}.jpeg?g=1515&shading=hill'
6
+
7
+ def build_provider(base_url=nil)
8
+ TileFactory.new(base_url || BASE_URL, 'bing', @south_west, @north_east, @zoom)
9
+ end
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,14 @@
1
+ module MapPrint
2
+ module Providers
3
+
4
+ class OpenStreetMap < Base
5
+ BASE_URL = 'http://a.tile.openstreetmap.org/${z}/${x}/${y}.png'
6
+
7
+ def build_provider(base_url=nil)
8
+ TileFactory.new(base_url || BASE_URL, 'osm', @south_west, @north_east, @zoom)
9
+ end
10
+
11
+ end
12
+
13
+ end
14
+ end
@@ -0,0 +1,9 @@
1
+ module MapPrint
2
+ class ScalebarHandler
3
+ def initialize(scalebar, file)
4
+ end
5
+
6
+ def process
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module MapPrint
2
+ class TextHandler
3
+ def initialize(texts, file)
4
+ end
5
+
6
+ def process
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,42 @@
1
+ module MapPrint
2
+
3
+ class BingTile < Tile
4
+
5
+ BIT_TO_QUADKEY = {
6
+ [false, false] => '0',
7
+ [false, true] => '1',
8
+ [true, false] => '2',
9
+ [true, true] => '3'
10
+ }
11
+
12
+ def provider_name
13
+ 'bing'
14
+ end
15
+
16
+ def cache_name
17
+ 'bing-' + @base_url.scan(/\/\/(.*?)\/\$/).first.first.gsub('/', '-')
18
+ rescue
19
+ 'bing'
20
+ end
21
+
22
+ def tile2quad
23
+ quadkey_chars = []
24
+
25
+ tx = @x.to_i
26
+ ty = @y.to_i
27
+
28
+ @z.times do
29
+ quadkey_chars.push BIT_TO_QUADKEY[[ty.odd?, tx.odd?]] # bit order y,x
30
+ tx >>= 1 ; ty >>= 1
31
+ end
32
+
33
+ quadkey_chars.join.reverse
34
+ end
35
+
36
+ def tile_url
37
+ @base_url.gsub('${quadkey}', tile2quad)
38
+ end
39
+
40
+ end
41
+
42
+ end
@@ -0,0 +1,21 @@
1
+ module MapPrint
2
+
3
+ class OSMTile < Tile
4
+
5
+ def provider_name
6
+ 'osm'
7
+ end
8
+
9
+ def cache_name
10
+ 'osm-' + @base_url.scan(/\/\/(.*?)\/\$/).first.first.gsub('/', '-')
11
+ rescue
12
+ 'osm'
13
+ end
14
+
15
+ def tile_url
16
+ @base_url.gsub('${x}', @x.to_s).gsub('${y}', @y.to_s).gsub('${z}', @z.to_s)
17
+ end
18
+
19
+ end
20
+
21
+ end
@@ -0,0 +1,110 @@
1
+ require 'open-uri'
2
+ require 'fileutils'
3
+
4
+ module MapPrint
5
+ class Tile
6
+
7
+ METERS_PER_PIXELS = {
8
+ 0 => 26862156543.031,
9
+ 1 => 8078271.521,
10
+ 2 => 7739135.761,
11
+ 3 => 6919567.881,
12
+ 4 => 889783.941,
13
+ 5 => 214891.9771,
14
+ 6 => 222445.9801721,
15
+ 7 => 161731222.991,
16
+ 8 => 11611.5001,
17
+ 9 => 52305.751,
18
+ 10 => 152.871,
19
+ 11 => 76.4371,
20
+ 12 => 38.2191,
21
+ 13 => 519.1091,
22
+ 14 => 9.55461,
23
+ 15 => 4.77731,
24
+ 16 => 2.38871,
25
+ 17 => 1.19431,
26
+ 18 => 0.5972
27
+ }
28
+
29
+ class << self
30
+
31
+ def meters_per_pixel(zoom)
32
+ METERS_PER_PIXELS[zoom]
33
+ end
34
+
35
+ end
36
+
37
+ def initialize(x, y, z, base_url)
38
+ @base_url = base_url
39
+ @x = x
40
+ @y = y
41
+ @z = z
42
+ end
43
+
44
+ def coords
45
+ {x: @x, y: @y, z: @z}
46
+ end
47
+
48
+ def download
49
+ unless File.exists?(file_path)
50
+ content = open(tile_url).read
51
+ write_file(content)
52
+ end
53
+ end
54
+
55
+ def get_pixel_difference(lat_lng)
56
+ tile_lat_lng = tile_number_to_lat_lng
57
+ puts '================'
58
+ puts lat_lng.lat
59
+ puts lat_lng.lng
60
+ puts tile_lat_lng
61
+ puts '================'
62
+
63
+ x_pixels = GeoDistance.distance(lat_lng.lat, lat_lng.lng, lat_lng.lat, tile_lat_lng[:lng]).meters.number / METERS_PER_PIXELS[@z]
64
+ y_pixels = GeoDistance.distance(lat_lng.lat, lat_lng.lng, tile_lat_lng[:lat], lat_lng.lng).meters.number / METERS_PER_PIXELS[@z]
65
+
66
+ { x: x_pixels, y: y_pixels }
67
+ end
68
+
69
+ def tile_number_to_lat_lng
70
+ n = 2.0 ** @z
71
+ lon_deg = @x / n * 360.0 - 180.0
72
+ lat_rad = Math::atan(Math::sinh(Math::PI * (1 - 2 * @y / n)))
73
+ lat_deg = 180.0 * (lat_rad / Math::PI)
74
+
75
+ { lat: lat_deg, lng: lon_deg }
76
+ end
77
+
78
+ def file_path
79
+ File.join(folder_name, "#{@y}.png")
80
+ end
81
+
82
+ def provider_name
83
+ raise 'SubClasses should overwrite this method'
84
+ end
85
+
86
+ def cache_name
87
+ raise 'SubClasses should overwrite this method'
88
+ end
89
+
90
+ def tile_url
91
+ raise 'SubClasses should overwrite this method'
92
+ end
93
+
94
+ private
95
+
96
+ def write_file(content)
97
+ FileUtils.mkdir_p(folder_name)
98
+
99
+ File.open file_path, 'wb' do |f|
100
+ f.write content
101
+ end
102
+ end
103
+
104
+ def folder_name
105
+ "cache/#{cache_name}/#{@z}/#{@x}"
106
+ end
107
+
108
+ end
109
+
110
+ end
@@ -0,0 +1,90 @@
1
+ require_relative 'osm_tile'
2
+ require_relative 'bing_tile'
3
+
4
+ module MapPrint
5
+
6
+ class TileFactory
7
+
8
+ def initialize(base_url, type, sw_lat_lng, ne_lat_lng, zoom)
9
+ @base_url = base_url
10
+ @type = type
11
+ @sw_lat_lng = sw_lat_lng
12
+ @ne_lat_lng = ne_lat_lng
13
+ @zoom = zoom
14
+ end
15
+
16
+ def px_offset
17
+ return @px_offset if @px_offset
18
+ offset = {}
19
+
20
+ offset[:top] = (ne_offset[:y] * 256).to_i
21
+ offset[:right] = 256 - (ne_offset[:x] * 256).to_i
22
+ offset[:bottom] = 256 - (sw_offset[:y] * 256).to_i
23
+ offset[:left] = (sw_offset[:x] * 256).to_i
24
+ @px_offset = offset
25
+ end
26
+
27
+ def download
28
+ Parallel.each(tiles, in_processes: 20) do |tile|
29
+ tile.download
30
+ end
31
+ end
32
+
33
+ def tiles
34
+ return @tiles if @tiles
35
+
36
+ @tiles = []
37
+ y_array.each do |y|
38
+ x_array.each do |x|
39
+ @tiles << tile_class.new(x, y, @zoom, @base_url)
40
+ end
41
+ end
42
+
43
+ @tiles
44
+ end
45
+
46
+ def x_size
47
+ x_array.size
48
+ end
49
+
50
+ def y_size
51
+ y_array.size
52
+ end
53
+
54
+ def ne_offset
55
+ @ne_lat_lng.get_slippy_map_tile_number(@zoom)[:offset]
56
+ end
57
+
58
+ def sw_offset
59
+ @sw_lat_lng.get_slippy_map_tile_number(@zoom)[:offset]
60
+ end
61
+
62
+ def x_array
63
+ return @x_array if @x_array
64
+
65
+ x1 = @sw_lat_lng.get_slippy_map_tile_number(@zoom)[:x]
66
+ x2 = @ne_lat_lng.get_slippy_map_tile_number(@zoom)[:x]
67
+ @x_array ||= x1 < x2 ? x1..x2 : (x2..x1).to_a
68
+ end
69
+
70
+ def y_array
71
+ return @y_array if @y_array
72
+
73
+ y1 = @sw_lat_lng.get_slippy_map_tile_number(@zoom)[:y]
74
+ y2 = @ne_lat_lng.get_slippy_map_tile_number(@zoom)[:y]
75
+ @y_array ||= y1 < y2 ? y1..y2 : (y2..y1).to_a
76
+ end
77
+
78
+ private
79
+
80
+ def tile_class
81
+ if @type == 'osm'
82
+ OSMTile
83
+ elsif @type =~ 'bing'
84
+ BingTile
85
+ end
86
+ end
87
+
88
+ end
89
+
90
+ end
@@ -1,3 +1,3 @@
1
1
  module MapPrint
2
- VERSION = '0.1.0'
2
+ VERSION = '0.2.0'
3
3
  end
data/lib/map_print.rb CHANGED
@@ -1,17 +1,8 @@
1
- require 'map_print/version'
2
1
  require 'prawn'
3
2
  require 'mini_magick'
4
3
  require 'geo-distance'
4
+ require 'parallel'
5
5
 
6
- module MapPrint
7
- # Your code goes here...
8
- end
9
-
10
- require 'map_print/core'
11
-
12
-
13
- module MapPrint
14
- def self.print(path)
15
- MapPrint::Core.print(path)
16
- end
17
- end
6
+ require_relative 'base'
7
+ require_relative 'map_print/version'
8
+ require_relative 'map_print/core'
data/map_print.gemspec CHANGED
@@ -23,14 +23,17 @@ Gem::Specification.new do |spec|
23
23
  end
24
24
 
25
25
  spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
26
- spec.bindir = 'exe'
27
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
26
+ spec.bindir = 'bin'
27
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
28
28
  spec.require_paths = ['lib']
29
29
 
30
30
  spec.add_development_dependency 'bundler', '~> 1.10'
31
31
  spec.add_development_dependency 'rake', '~> 10.0'
32
32
  spec.add_development_dependency 'minitest', '~> 5.8'
33
33
  spec.add_dependency 'prawn', '~> 2.0'
34
+ spec.add_dependency 'prawn-fast-png', '~> 0.2.3'
34
35
  spec.add_dependency 'mini_magick', '~> 4.3'
35
36
  spec.add_dependency 'geo-distance', '~> 0.1'
37
+ spec.add_dependency 'parallel', '~> 1.6'
38
+ spec.add_dependency 'thor', '~> 0.19'
36
39
  end
data/marker.png ADDED
Binary file
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: map_print
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andreas Fast
8
8
  autorequire:
9
- bindir: exe
9
+ bindir: bin
10
10
  cert_chain: []
11
- date: 2015-11-16 00:00:00.000000000 Z
11
+ date: 2015-12-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -66,6 +66,20 @@ dependencies:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
68
  version: '2.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: prawn-fast-png
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 0.2.3
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 0.2.3
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: mini_magick
71
85
  requirement: !ruby/object:Gem::Requirement
@@ -94,11 +108,42 @@ dependencies:
94
108
  - - "~>"
95
109
  - !ruby/object:Gem::Version
96
110
  version: '0.1'
111
+ - !ruby/object:Gem::Dependency
112
+ name: parallel
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '1.6'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '1.6'
125
+ - !ruby/object:Gem::Dependency
126
+ name: thor
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '0.19'
132
+ type: :runtime
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '0.19'
97
139
  description: MapPrint allows to export many map sources and GeoJSON objects to a pdf
98
140
  file, along with legend and text elements to create rich maps.
99
141
  email:
100
142
  - andis.machine@gmail.com
101
- executables: []
143
+ executables:
144
+ - console
145
+ - map_print
146
+ - setup
102
147
  extensions: []
103
148
  extra_rdoc_files: []
104
149
  files:
@@ -113,13 +158,28 @@ files:
113
158
  - bin/console
114
159
  - bin/map_print
115
160
  - bin/setup
161
+ - lib/base.rb
116
162
  - lib/map_print.rb
117
163
  - lib/map_print/core.rb
164
+ - lib/map_print/geo_json_handler.rb
165
+ - lib/map_print/image_handler.rb
118
166
  - lib/map_print/lat_lng.rb
167
+ - lib/map_print/layer_handler.rb
168
+ - lib/map_print/legend_handler.rb
119
169
  - lib/map_print/osm/tile.rb
120
170
  - lib/map_print/osm/tile_factory.rb
171
+ - lib/map_print/providers/base.rb
172
+ - lib/map_print/providers/bing.rb
173
+ - lib/map_print/providers/open_street_map.rb
174
+ - lib/map_print/scalebar_handler.rb
175
+ - lib/map_print/text_handler.rb
176
+ - lib/map_print/tiles/bing_tile.rb
177
+ - lib/map_print/tiles/osm_tile.rb
178
+ - lib/map_print/tiles/tile.rb
179
+ - lib/map_print/tiles/tile_factory.rb
121
180
  - lib/map_print/version.rb
122
181
  - map_print.gemspec
182
+ - marker.png
123
183
  homepage: http://github.com/afast/map_print
124
184
  licenses:
125
185
  - MIT