class Gpx2png::OsmBase

Constants

TILE_HEIGHT
TILE_WIDTH

Attributes

bitmap_point_x_max[R]

points for cropping

bitmap_point_x_min[R]

points for cropping

bitmap_point_y_max[R]

points for cropping

bitmap_point_y_min[R]

points for cropping

lat_max[R]
lat_min[R]
lon_max[R]
lon_min[R]
simulate_download[RW]

if true it will not download tiles

tile_x_distance[R]
tile_y_distance[R]

Public Class Methods

calc_zoom(lat_min, lat_max, lon_min, lon_max, width, height) click to toggle source

Lazy calc proper zoom for drawing

# 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) click to toggle source

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

# 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_string() click to toggle source
# 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) click to toggle source

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

# 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) click to toggle source

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

# 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) click to toggle source

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

# 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.') click to toggle source

Convert OSM tile coords to url

# 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.') click to toggle source

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

# 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

Public Instance Methods

auto_zoom_for(x = 0, y = 0) click to toggle source

Calculate zoom level

# File lib/gpx2png/osm_base.rb, line 191
def auto_zoom_for(x = 0, y = 0)
  # TODO
end
calculate_for_crop() click to toggle source

Calculate some numbers for cropping operation

# 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_zoom() click to toggle source

Calculate some numbers for cropping operation with autozoom

# 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_tiles() click to toggle source

Do everything

# 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_map() click to toggle source
# File lib/gpx2png/osm_base.rb, line 313
def expand_map
  # TODO expand min and max ranges
end
fixed_size(_width, _height) click to toggle source

Create image with fixed size

# File lib/gpx2png/osm_base.rb, line 108
def fixed_size(_width, _height)
  @fixed_width = _width
  @fixed_height = _height
end
initial_calculations(scale={}) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# File lib/gpx2png/osm_base.rb, line 113
def min_coords_or(key,val)
  val || @coords.collect { |c| c[key] }.min
end