Class: Gpx2png::OsmBase

Inherits:
Base
  • Object
show all
Defined in:
lib/gpx2png/osm_base.rb

Direct Known Subclasses

Osm

Constant Summary collapse

TILE_WIDTH =
256
TILE_HEIGHT =
256

Instance Attribute Summary collapse

Attributes inherited from Base

#color, #coords, #zoom

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Base

#add, deg2rad, #initialize, rad2deg

Constructor Details

This class inherits a constructor from Gpx2png::Base

Instance Attribute Details

#bitmap_point_x_maxObject (readonly)

points for cropping



198
199
200
# File 'lib/gpx2png/osm_base.rb', line 198

def bitmap_point_x_max
  @bitmap_point_x_max
end

#bitmap_point_x_minObject (readonly)

points for cropping



198
199
200
# File 'lib/gpx2png/osm_base.rb', line 198

def bitmap_point_x_min
  @bitmap_point_x_min
end

#bitmap_point_y_maxObject (readonly)

points for cropping



198
199
200
# File 'lib/gpx2png/osm_base.rb', line 198

def bitmap_point_y_max
  @bitmap_point_y_max
end

#bitmap_point_y_minObject (readonly)

points for cropping



198
199
200
# File 'lib/gpx2png/osm_base.rb', line 198

def bitmap_point_y_min
  @bitmap_point_y_min
end

#lat_maxObject (readonly)

Returns the value of attribute lat_max.



195
196
197
# File 'lib/gpx2png/osm_base.rb', line 195

def lat_max
  @lat_max
end

#lat_minObject (readonly)

Returns the value of attribute lat_min.



195
196
197
# File 'lib/gpx2png/osm_base.rb', line 195

def lat_min
  @lat_min
end

#lon_maxObject (readonly)

Returns the value of attribute lon_max.



195
196
197
# File 'lib/gpx2png/osm_base.rb', line 195

def lon_max
  @lon_max
end

#lon_minObject (readonly)

Returns the value of attribute lon_min.



195
196
197
# File 'lib/gpx2png/osm_base.rb', line 195

def lon_min
  @lon_min
end

#simulate_downloadObject

if true it will not download tiles



14
15
16
# File 'lib/gpx2png/osm_base.rb', line 14

def simulate_download
  @simulate_download
end

#tile_x_distanceObject (readonly)

Returns the value of attribute tile_x_distance.



196
197
198
# File 'lib/gpx2png/osm_base.rb', line 196

def tile_x_distance
  @tile_x_distance
end

#tile_y_distanceObject (readonly)

Returns the value of attribute tile_y_distance.



196
197
198
# File 'lib/gpx2png/osm_base.rb', line 196

def tile_y_distance
  @tile_y_distance
end

Class Method Details

.calc_zoom(lat_min, lat_max, lon_min, lon_max, width, height) ⇒ Object

Lazy calc proper zoom for drawing



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/gpx2png/osm_base.rb', line 51

def self.calc_zoom(lat_min, lat_max, lon_min, lon_max, width, height)
  # because I'm lazy! :] and math is not my best side

  last_zoom = 2
  (5..18).each do |zoom|
    # calculate drawing tile size and pixel size
    tile_min = point_on_absolute_image(zoom, [lat_min, lon_min])
    tile_max = point_on_absolute_image(zoom, [lat_max, lon_max])
    current_pixel_x_distance = tile_max[0] - tile_min[0]
    current_pixel_y_distance = tile_min[1] - tile_max[1]
    if current_pixel_x_distance > width or current_pixel_y_distance > height
      return last_zoom
    end
    last_zoom = zoom
  end
  return 18
end

.convert(zoom, coord) ⇒ Object

wiki.openstreetmap.org/wiki/Slippy_map_tilenames#X_and_Y Convert latlon deg to OSM tile coords



18
19
20
21
22
23
24
25
# File 'lib/gpx2png/osm_base.rb', line 18

def self.convert(zoom, coord)
  lat_deg, lon_deg = coord
  lat_rad = deg2rad(lat_deg)
  x = (((lon_deg + 180) / 360) * (2 ** zoom)).floor
  y = ((1 - Math.log(Math.tan(lat_rad) + 1 / Math.cos(lat_rad)) / Math::PI) /2 * (2 ** zoom)).floor

  return [x, y]
end

.licence_stringObject



317
318
319
# File 'lib/gpx2png/osm_base.rb', line 317

def self.licence_string
  "Map data OpenStreetMap (CC-by-SA 2.0)"
end

.point_on_absolute_image(zoom, geo_coord) ⇒ Object

Useful for calculating distance on output image It is not position on output image because we don’t know tile coords For upper-left tile



100
101
102
103
104
105
# File 'lib/gpx2png/osm_base.rb', line 100

def self.point_on_absolute_image(zoom, geo_coord)
  _p = point_on_image(zoom, geo_coord)
  _x = _p[:osm_title_coord][0] * TILE_WIDTH + _p[:pixel_offset][0]
  _y = _p[:osm_title_coord][1] * TILE_WIDTH + _p[:pixel_offset][1]
  return [_x, _y]
end

.point_on_image(zoom, geo_coord) ⇒ Object

Convert latlon deg coords to image point (x,y) and OSM tile coord return where you should put point on tile



71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/gpx2png/osm_base.rb', line 71

def self.point_on_image(zoom, geo_coord)
  osm_tile_coord = convert(zoom, geo_coord)
  top_left_corner = reverse_convert(zoom, osm_tile_coord)
  bottom_right_corner = reverse_convert(zoom, [
    osm_tile_coord[0] + 1, osm_tile_coord[1] + 1
  ])

  # some line math: y = ax + b

  x_geo = geo_coord[1]
  # offset
  x_offset = x_geo - top_left_corner[1]
  # scale
  x_distance = (bottom_right_corner[1] - top_left_corner[1])
  x = (TILE_WIDTH.to_f * (x_offset / x_distance)).round

  y_geo = geo_coord[0]
  # offset
  y_offset = y_geo - top_left_corner[0]
  # scale
  y_distance = (bottom_right_corner[0] - top_left_corner[0])
  y = (TILE_HEIGHT.to_f * (y_offset / y_distance)).round

  return { osm_title_coord: osm_tile_coord, pixel_offset: [x, y] }
end

.reverse_convert(zoom, coord) ⇒ Object

Convert OSM tile coords to latlon deg in top-left corner



42
43
44
45
46
47
48
# File 'lib/gpx2png/osm_base.rb', line 42

def self.reverse_convert(zoom, coord)
  x, y = coord
  n = 2 ** zoom
  lon_deg = x.to_f / n.to_f * 360.0 - 180.0
  lat_deg = rad2deg(Math.atan(Math.sinh(Math::PI * (1.to_f - 2.to_f * y.to_f / n.to_f))))
  return [lat_deg, lon_deg]
end

.url(zoom, coord, server = 'b.') ⇒ Object

Convert OSM tile coords to url



35
36
37
38
39
# File 'lib/gpx2png/osm_base.rb', line 35

def self.url(zoom, coord, server = 'b.')
  x, y = coord
  url = "http://#{server}tile.openstreetmap.org\/#{zoom}\/#{x}\/#{y}.png"
  return url
end

.url_convert(zoom, coord, server = 'b.') ⇒ Object

Convert latlon deg to OSM tile url TODO add algorithm to choose from diff. servers



29
30
31
32
# File 'lib/gpx2png/osm_base.rb', line 29

def self.url_convert(zoom, coord, server = 'b.')
  x, y = convert(zoom, coord)
  url(zoom, [x, y], server)
end

Instance Method Details

#auto_zoom_for(x = 0, y = 0) ⇒ Object

Calculate zoom level



191
192
193
# File 'lib/gpx2png/osm_base.rb', line 191

def auto_zoom_for(x = 0, y = 0)
  # TODO
end

#calculate_for_cropObject

Calculate some numbers for cropping operation



287
288
289
290
291
292
293
294
295
296
# File 'lib/gpx2png/osm_base.rb', line 287

def calculate_for_crop
  point_min = self.class.point_on_image(@zoom, [@lat_min, @lon_min])
  point_max = self.class.point_on_image(@zoom, [@lat_max, @lon_max])
  @bitmap_point_x_min = (point_min[:osm_title_coord][0] - @tile_x_range.min) * TILE_WIDTH + point_min[:pixel_offset][0]
  @bitmap_point_x_max = (point_max[:osm_title_coord][0] - @tile_x_range.min) * TILE_WIDTH + point_max[:pixel_offset][0]
  @bitmap_point_y_max = (point_min[:osm_title_coord][1] - @tile_y_range.min) * TILE_HEIGHT + point_min[:pixel_offset][1]
  @bitmap_point_y_min = (point_max[:osm_title_coord][1] - @tile_y_range.min) * TILE_HEIGHT + point_max[:pixel_offset][1]

  @r.set_crop(@bitmap_point_x_min, @bitmap_point_x_max, @bitmap_point_y_min, @bitmap_point_y_max)
end

#calculate_for_crop_with_auto_zoomObject

Calculate some numbers for cropping operation with autozoom



299
300
301
302
303
304
305
306
307
308
309
310
311
# File 'lib/gpx2png/osm_base.rb', line 299

def calculate_for_crop_with_auto_zoom
  point_min = self.class.point_on_image(@zoom, [@lat_min, @lon_min])
  point_max = self.class.point_on_image(@zoom, [@lat_max, @lon_max])
  @bitmap_point_x_min = (point_min[:osm_title_coord][0] - @tile_x_range.min) * TILE_WIDTH + point_min[:pixel_offset][0]
  @bitmap_point_x_max = (point_max[:osm_title_coord][0] - @tile_x_range.min) * TILE_WIDTH + point_max[:pixel_offset][0]
  @bitmap_point_y_max = (point_min[:osm_title_coord][1] - @tile_y_range.min) * TILE_HEIGHT + point_min[:pixel_offset][1]
  @bitmap_point_y_min = (point_max[:osm_title_coord][1] - @tile_y_range.min) * TILE_HEIGHT + point_max[:pixel_offset][1]

  bitmap_x_center = (@bitmap_point_x_min + @bitmap_point_x_max) / 2
  bitmap_y_center = (@bitmap_point_y_min + @bitmap_point_y_max) / 2

  @r.set_crop_fixed(bitmap_x_center, bitmap_y_center, @fixed_width, @fixed_height)
end

#download_and_join_tilesObject

Do everything



201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
# File 'lib/gpx2png/osm_base.rb', line 201

def download_and_join_tiles
  puts "Output image dimension #{@full_image_x}x#{@full_image_y}" if @verbose
  @r.new_image

  # {:x, :y, :blob}
  @images = Array.new


  @tile_x_range.each do |x|
    @tile_y_range.each do |y|
      url = self.class.url(@zoom, [x, y])

      # blob time
      unless @simulate_download
        uri = URI.parse(url)
        response = Net::HTTP.get_response(uri)
        blob = response.body
      else
        blob = @r.blank_tile(TILE_WIDTH, TILE_HEIGHT, x+y)
      end

      @r.add_tile(
        blob,
        (x - @tile_x_range.min) * TILE_WIDTH,
        (y - @tile_y_range.min) * TILE_HEIGHT
      )

      @images << {
        url: url,
        x: x,
        y: y
      }

      puts "processed #{x - @tile_x_range.min}x#{y - @tile_y_range.min} (max #{@tile_x_range.max - @tile_x_range.min}x#{@tile_y_range.max - @tile_y_range.min})" if @verbose
    end
  end

  # sweet, image is joined

  # min/max points used for cropping
  @bitmap_point_x_max = (@full_image_x / 2).round
  @bitmap_point_x_min = (@full_image_x / 2).round
  @bitmap_point_y_max = (@full_image_y / 2).round
  @bitmap_point_y_min = (@full_image_y / 2).round

  # add some coords to the map
  (1...@coords.size).each do |i|
    lat_from = @coords[i-1][:lat]
    lon_from = @coords[i-1][:lon]

    lat_to = @coords[i][:lat]
    lon_to = @coords[i][:lon]

    point_from = self.class.point_on_image(@zoom, [lat_from, lon_from])
    point_to = self.class.point_on_image(@zoom, [lat_to, lon_to])
    # { osm_title_coord: osm_tile_coord, pixel_offset: [x, y] }

    # first point
    bitmap_xa = (point_from[:osm_title_coord][0] - @tile_x_range.min) * TILE_WIDTH + point_from[:pixel_offset][0]
    bitmap_ya = (point_from[:osm_title_coord][1] - @tile_y_range.min) * TILE_HEIGHT + point_from[:pixel_offset][1]
    bitmap_xb = (point_to[:osm_title_coord][0] - @tile_x_range.min) * TILE_WIDTH + point_to[:pixel_offset][0]
    bitmap_yb = (point_to[:osm_title_coord][1] - @tile_y_range.min) * TILE_HEIGHT + point_to[:pixel_offset][1]

    @r.line(
      bitmap_xa, bitmap_ya,
      bitmap_xb, bitmap_yb
    )
  end

  # add points
  @markers.each do |point|
    lat = point[:lat]
    lon = point[:lon]

    p = self.class.point_on_image(@zoom, [lat, lon])
    bitmap_x = (p[:osm_title_coord][0] - @tile_x_range.min) * TILE_WIDTH + p[:pixel_offset][0]
    bitmap_y = (p[:osm_title_coord][1] - @tile_y_range.min) * TILE_HEIGHT + p[:pixel_offset][1]

    point[:x] = bitmap_x
    point[:y] = bitmap_y

    @r.markers << point
  end
end

#expand_mapObject



313
314
315
# File 'lib/gpx2png/osm_base.rb', line 313

def expand_map
  # TODO expand min and max ranges
end

#fixed_size(_width, _height) ⇒ Object

Create image with fixed size



108
109
110
111
# File 'lib/gpx2png/osm_base.rb', line 108

def fixed_size(_width, _height)
  @fixed_width = _width
  @fixed_height = _height
end

#initial_calculations(scale = {}) ⇒ Object



121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/gpx2png/osm_base.rb', line 121

def initial_calculations(scale={})
  scale ||= {}
  puts "Initializing with scale: #{scale.inspect}"
  @lat_min = min_coords_or(:lat,scale[:lat_min])
  @lat_max = max_coords_or(:lat,scale[:lat_max])
  @lon_min = min_coords_or(:lon,scale[:lon_min])
  @lon_max = max_coords_or(:lon,scale[:lon_max])

  if scale[:scale].to_f > 0.00001
    puts "Scaling from: (#{lat_min},#{lon_min})-(#{lat_max},#{lon_max})"
    dlat = @lat_max - @lat_min
    dlon = @lon_max - @lon_min
    dlat2 = dlat * scale[:scale].to_f
    dlon2 = dlon * scale[:scale].to_f
    shift_x = dlon2 * scale[:shift_x].to_f
    shift_y = dlat2 * scale[:shift_y].to_f
    @lat_max += shift_y - (dlat - dlat2)/2.0
    @lat_min += shift_y + (dlat - dlat2)/2.0
    @lon_max += shift_x - (dlon - dlon2)/2.0
    @lon_max += shift_x + (dlon - dlon2)/2.0
    puts "Scaled to: (#{lat_min},#{lon_min})-(#{lat_max},#{lon_max})"
  end

  # auto zoom must be here
  # drawing must fit into fixed resolution
  # map must be bigger than fixed resolution
  if @fixed_width and @fixed_height
    @new_zoom = self.class.calc_zoom(
      @lat_min, @lat_max,
      @lon_min, @lon_max,
      @fixed_width, @fixed_height
    )
    puts "Calculated new zoom #{@new_zoom} (was #{@zoom})" if @verbose
    @zoom = @new_zoom
  end

  @border_tiles = [
    self.class.convert(@zoom, [@lat_min, @lon_min]),
    self.class.convert(@zoom, [@lat_max, @lon_max])
  ]

  @tile_x_range = (@border_tiles[0][0])..(@border_tiles[1][0])
  @tile_y_range = (@border_tiles[1][1])..(@border_tiles[0][1])

  # enlarging ranges to fill up map area
  # both sizes are enlarged
  # = ( ( (preferred size - real size) / tile width ) / 2 ).ceil
  if @fixed_width and @fixed_height
    x_axis_expand_count = ((@fixed_width - (1 + @tile_x_range.max - @tile_x_range.min) * TILE_WIDTH).to_f / (TILE_WIDTH.to_f * 2.0)).ceil
    y_axis_expand_count = ((@fixed_height - (1 + @tile_y_range.max - @tile_y_range.min) * TILE_HEIGHT).to_f / (TILE_HEIGHT.to_f * 2.0)).ceil
    puts "Expanding X tiles from both sides #{x_axis_expand_count}" if @verbose
    puts "Expanding Y tiles from both sides #{y_axis_expand_count}" if @verbose
    @tile_x_range = ((@tile_x_range.min - x_axis_expand_count)..(@tile_x_range.max + x_axis_expand_count))
    @tile_y_range = ((@tile_y_range.min - y_axis_expand_count)..(@tile_y_range.max + y_axis_expand_count))
  end

  # new/full image size
  @full_image_x = (1 + @tile_x_range.max - @tile_x_range.min) * TILE_WIDTH
  @full_image_y = (1 + @tile_y_range.max - @tile_y_range.min) * TILE_HEIGHT
  @r.x = @full_image_x
  @r.y = @full_image_y

  if @fixed_width and @fixed_height
    calculate_for_crop_with_auto_zoom
  else
    calculate_for_crop
  end
end

#max_coords_or(key, val) ⇒ Object



117
118
119
# File 'lib/gpx2png/osm_base.rb', line 117

def max_coords_or(key,val)
  val || @coords.collect { |c| c[key] }.max
end

#min_coords_or(key, val) ⇒ Object



113
114
115
# File 'lib/gpx2png/osm_base.rb', line 113

def min_coords_or(key,val)
  val || @coords.collect { |c| c[key] }.min
end