raubarede/Ruiby

View on GitHub
samples/aerial_viewer.rb

Summary

Maintainability
F
3 days
Test Coverage
# Creative Commons BY-SA :  Regis d'Aubarede <regis.aubarede@gmail.com>
# LGPL
####################################################################################
#   aerial_viewer.rb :  Mapbox  photo viewer,caching tiles in temporary directory
#
#   WARNNG!! : I don't know if layer geoeye.map is public in Mapbox  ...
#
####################################################################################
# Usage : 
#    > ruby aerial_viewer.rb [zoomLevel [lon lat]]
####################################################################################
require_relative '../lib/Ruiby'
require 'open-uri'
#$URLTILES='http://otile4.mqcdn.com/tiles/1.0.0/osm/ZOOM/LON/LAT.jpg'
 $URLTILES='http://b.tiles.mapbox.com/v3/geoeye.map-cjxgqnhb/ZOOM/LON/LAT.png'
 
$SHOW_TILES_BORDER=false

######################################### Tiles cache ###############################

class CacheTiles
  DIR="#{Dir.tmpdir}/atiles"
  TMPZ="#{DIR}/ZOOM"
  TMPZL="#{TMPZ}/LON"
  TMP="#{TMPZL}/LAT.jpg"
  URL=$URLTILES
  
  def initialize(app)
     Dir.mkdir(DIR) unless Dir.exists?(DIR)
     p DIR
     @app=app
     @current={}
  end
  def get_tile(z,lon,lat)
     filename=TMP.gsub("LAT",lat.to_s).gsub("LON",lon.to_s).gsub("ZOOM",z.to_s)
     if File.exists?(filename)  
       return(filename)
     else
        if ! @current[filename]
          @current[filename]=true
          Thread.new { load_tile(z,lon,lat,filename) }
        end
       return(nil)
     end
  end
  def load_tile(z,lon,lat,filename)
    tmpz= TMPZ.gsub("LAT",lat.to_s).gsub("LON",lon.to_s).gsub("ZOOM",z.to_s)
    Dir.mkdir(tmpz) unless Dir.exists?(tmpz)
    tmpl= TMPZL.gsub("LAT",lat.to_s).gsub("LON",lon.to_s).gsub("ZOOM",z.to_s)
    Dir.mkdir(tmpl) unless Dir.exists?(tmpl)
    
    url= URL.gsub("LAT",lat.to_s).gsub("LON",lon.to_s).gsub("ZOOM",z.to_s)
    puts "#{filename} delayed to #{url}"
    open(url,"rb") do |resp|
      File.open(filename+".tmp","wb") { |f| f.write(resp.read) }
      puts "notif app for raster #{filename}"
      current=@current
      File.rename(filename+".tmp",filename)
      gui_invoke {  current.delete(filename) ; refresh }
    end rescue (puts "unknown #{filename}")
  end  
end
###################################### Tools carto ###########################################
module Tools  
  CROSSGAP=8
  def draw_tile(ctx,f,x,y,pdx,pdy)
    ctx.set_source_pixbuf(get_pixbuf(f),(x-pdx)*256,(y-pdy)*256)
    ctx.paint
    draw_border(ctx,x,y,pdx,pdy) if $SHOW_TILES_BORDER
  end
  def draw_border(ctx,x,y,pdx,pdy)
    ctx.set_line_width(1)
    ctx.set_source_rgba(0,0,0,0.4)    
    ctx.move_to((x-pdx+1)*256,(y-pdy)*256).line_to((x-pdx)*256,(y-pdy)*256).line_to((x-pdx)*256,(y-pdy+1)*256)
    ctx.stroke
  end
  def draw_cross(ctx,w,h)
    ctx.set_line_width(2)
    ctx.set_source_rgba(0,0,0,0.7)    
    x0,y0=w/2.0,h/2.0
    ctx.move_to(x0,y0)
    ctx.line_to(x0-CROSSGAP,y0);ctx.line_to(x0+CROSSGAP,y0)
    ctx.line_to(x0,y0);ctx.line_to(x0,y0-CROSSGAP);ctx.line_to(x0,y0+CROSSGAP)
    ctx.stroke
  end
end

#################################### Carto wiewer #############################################
module Carto
  def radians(degrees) (Math::PI * degrees) / 180.0 end
  def degrees(radians) (radians * 180.0) / Math::PI end
  def tile_nums_2_lonlat(xtile, ytile, zoom)
    factor = 2.0 ** (zoom)
    lon = ((xtile * 360) / factor) - 180.0
    lat = Math.atan(Math.sinh(Math::PI * (1 - 2 * ytile / factor)))
    return  [lon,degrees(lat),zoom]
  end
  def lonlat_2_tilenums(lon,lat, zoom)
    factor1 = 2**(zoom)
    rlat = radians(lat)

    xtile = factor1 * (lon+180.0)/ 360.0

    sec= (1 / Math.cos(rlat))
    tan=Math.tan(rlat)
    ytile = factor1 * (1 - (Math.log(tan + sec) / Math::PI) )/2.0
    
    ([xtile.to_i, ytile.to_i,zoom,xtile-xtile.to_i, ytile-ytile.to_i] rescue [1,1,zoom,0,0])
  end

  def expose(w,ctx)
    @z=[1,@z,18].sort[1]
    #puts "lon=#{@lon0} lat=#{@lat0} z=#{@z}"
    xtile,ytile,bidon,pdx,pdy=lonlat_2_tilenums(@lon0,@lat0, @z)
    w,h=*$app.size
    nbx,nby=w/256,h/256
    x0,y0=w/2.0,h/2.0
    ((xtile-nbx/2)..(xtile+nbx/2+2)).each_with_index  do |xt,x| ((ytile-nby/2)..(ytile+nby/2+2)).each_with_index do |yt,y|
        f=@ct.get_tile(@z,xt,yt)   
        draw_tile(ctx,f,x,y,pdx,pdy)  if f
    end end
    draw_cross(ctx,w,h)
    @wlonlat.text= "%3.5f / %3.5f" % [@lon0,@lat0]
    @wzoom.text= @z.to_s
  end
  
  def move_carto(dx,dy)
    xtile,ytile,bidon,pdx,pdy=lonlat_2_tilenums(@lon0,@lat0, @z)
    lon0,lat0=tile_nums_2_lonlat(xtile,ytile,@z)
    lon1,lat1=tile_nums_2_lonlat(xtile-dx/256.0,ytile-dy/256.0,@z)
    @lonRef+=lon1-lon0
    @latRef+=lat1-lat0
    refresh
  end
end

######################################## Ruiby App ############################################
w= (ARGV.shift || "800").to_i
h= (ARGV.shift || "800").to_i
Ruiby.app(:width=> w, :height=>h, :title=> "Aerial Map from Mapbox") do
  extend Tools
  extend Carto
  $app=self
  @ct=CacheTiles.new(self)
  @z=(ARGV[0]||Ruiby.stock_get("Z","5")).to_i
  @lon0=(ARGV[1]||Ruiby.stock_get("LON","2.0")).to_f
  @lat0=(ARGV[2]||Ruiby.stock_get("LAT","48.0")).to_f
  @lonRef=@lon0
  @latRef=@lat0
  @zRef=@z
  stack {
    @cv=canvas(self.default_width,self.default_height) {
      on_canvas_draw { |w,ctx|  expose(w,ctx) }
      on_canvas_button_press {|w,e|  [e.x,e.y]  }
      on_canvas_button_motion {|w,e,o| n=[e.x,e.y] ;$app.move_carto(n[0]-o[0],n[1]-o[1]) if o ;n }
      on_canvas_button_release {|w,e,o| n=[e.x,e.y] ;$app.move_carto(n[0]-o[0],n[1]-o[1]) }
    }
    flowi { 
      regular
      table(0,0) { 
          row { cell( label "Lon/Lat : " ) ; cell( @wlonlat=entry("",6) ) }
         row  { cell( label "Zoom: "     ) ; cell( @wzoom=ientry(@z,min: 1,max: 18) { |v| @z=v.to_i })}
      }
      button("Goto...") { 
        prompt("Longitude ?",@lon0.to_s) { |lon|  
          prompt("Latitude  ?",@lat0.to_s) { |lat| 
            if ask("#{lon.to_f} ; #{lat.to_f}\n Validation ?")
              @lonRef,@latRef=lon.to_f,lat.to_f
              puts "========> #{@lonRef} #{@latRef}"
            end
            true
          } 
          true
        }
      }
      flowi {
        button("X") { begin load __FILE__ ; rescue Exception => e ; error(e) ; end} 
        button("Exit") { ruiby_exit } 
      }
    }
  }
  anim(20) {
    #@lon0+=0.001
    if @lon0!=@lonRef || @lat0!=@latRef || @zRef!=@z
      @lon0+= sqrs((@lonRef-@lon0))
      @lat0+= sqrs((@latRef-@lat0))
      if ((@lon0-@lonRef).abs+(@lat0-@latRef).abs) < 0.05/(2.0 ** @z)
        Ruiby.stock_put("Z",@z)
        Ruiby.stock_put("LON",@lon0)
        Ruiby.stock_put("LAT",@lat0)
        @lon0,@lat0=@lonRef,@latRef 
      end
      @zRef=@z
      refresh 
    end
  }
  def sqrs(b) 
    ret=b>0 ? b*b+b : -b*b+b 
    ret=[0,b/10,ret].sort[1]
  end
  def refresh() @cv.redraw end
end