map_print 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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