class Onebox::Engine::GoogleMapsOnebox

Public Class Methods

===(other) click to toggle source
Calls superclass method
# File lib/onebox/engine/google_maps_onebox.rb, line 9
def ===(other)
  if other.kind_of? URI
    @@matchers && @@matchers.any? { |m| other.to_s =~ m[:regexp] }
  else
    super
  end
end
new(url, timeout = nil) click to toggle source
Calls superclass method Onebox::Engine::new
# File lib/onebox/engine/google_maps_onebox.rb, line 42
def initialize(url, timeout = nil)
  super
  resolve_url!
rescue Net::HTTPServerException, Timeout::Error, Net::HTTPError, Errno::ECONNREFUSED, RuntimeError => err
  raise ArgumentError, "malformed url or unresolveable: #{err.message}"
end

Private Class Methods

matches_regexp(key, regexp) click to toggle source
# File lib/onebox/engine/google_maps_onebox.rb, line 19
def matches_regexp(key, regexp)
  (@@matchers ||= []) << { key: key, regexp: regexp }
end

Public Instance Methods

placeholder_html() click to toggle source
# File lib/onebox/engine/google_maps_onebox.rb, line 57
def placeholder_html
  ::Onebox::Helpers.map_placeholder_html
end
streetview?() click to toggle source
# File lib/onebox/engine/google_maps_onebox.rb, line 49
def streetview?
  !!@streetview
end
to_html() click to toggle source
# File lib/onebox/engine/google_maps_onebox.rb, line 53
def to_html
  "<div class='maps-onebox'><iframe src=\"#{link}\" width=\"690\" height=\"400\" frameborder=\"0\" style=\"border:0\">#{placeholder_html}</iframe></div>"
end

Private Instance Methods

data() click to toggle source
# File lib/onebox/engine/google_maps_onebox.rb, line 63
def data
  { link: url, title: url }
end
follow_redirect!() click to toggle source
# File lib/onebox/engine/google_maps_onebox.rb, line 163
def follow_redirect!
  begin
    http = Net::HTTP.start(
      uri.host,
      uri.port,
      use_ssl: uri.scheme == 'https',
      open_timeout: timeout,
      read_timeout: timeout
    )

    response = http.head(uri.path)
    raise "unexpected response code #{response.code}" unless %w(200 301 302).include?(response.code)

    @url = response.code == "200" ? uri.to_s : response["Location"]
    @uri = URI(@url)
  ensure
    http.finish rescue nil
  end
end
match_url() click to toggle source
# File lib/onebox/engine/google_maps_onebox.rb, line 148
def match_url
  @@matchers.each do |matcher|
    if m = matcher[:regexp].match(@url)
      return matcher[:key], m
    end
  end
  raise ArgumentError, "\"#{@url}\" does not match any known pattern"
end
resolve_url!() click to toggle source
# File lib/onebox/engine/google_maps_onebox.rb, line 67
def resolve_url!
  @streetview = false
  type, match = match_url

  # Resolve shortened URL, if necessary
  if type == :short
    follow_redirect!
    type, match = match_url
  end

  # Try to get the old-maps URI, it is far easier to embed.
  if type == :standard
    retry_count = 10
    while (retry_count -= 1) > 0
      follow_redirect!
      type, match = match_url
      break if type != :standard
      sleep 0.1
    end
  end

  case type
  when :standard
    # Fallback for map URLs that don't resolve into an easily embeddable old-style URI
    # Roadmaps use a "z" zoomlevel, satellite maps use "m" the horizontal width in meters
    # TODO: tilted satellite maps using "a,y,t"
    match = @url.match(/@(?<lon>[\d.-]+),(?<lat>[\d.-]+),(?<zoom>\d+)(?<mz>[mz])/)
    raise "unexpected standard url #{@url}" unless match
    zoom = match[:mz] == "z" ? match[:zoom] : Math.log2(57280048.0 / match[:zoom].to_f).round
    location = "#{match[:lon]},#{match[:lat]}"
    url = "https://maps.google.com/maps?ll=#{location}&z=#{zoom}&output=embed&dg=ntvb"
    url += "&q=#{$1}" if match = @url.match(/\/place\/([^\/\?]+)/)
    url += "&cid=#{($1 + $2).to_i(16)}" if @url.match(/!3m1!1s0x(\h{16}):0x(\h{16})/)
    @url = url
    @placeholder = "https://maps.googleapis.com/maps/api/staticmap?maptype=roadmap&center=#{location}&zoom=#{zoom}&size=690x400&sensor=false"

  when :custom
    url = @url.dup
    @url = rewrite_custom_url(url, "embed")
    @placeholder = rewrite_custom_url(url, "thumbnail")
    @placeholder_height = @placeholder_width = 120

  when :streetview
    @streetview = true
    panoid = match[:pano]
    lon = match[:lon].to_f.to_s
    lat = match[:lat].to_f.to_s
    heading = match[:heading].to_f.round(4).to_s
    pitch = (match[:pitch].to_f / 10.0).round(4).to_s
    fov = (match[:zoom].to_f / 100.0).round(4).to_s
    zoom = match[:zoom].to_f.round
    @url = "https://www.google.com/maps/embed?pb=!3m2!2sen!4v0!6m8!1m7!1s#{panoid}!2m2!1d#{lon}!2d#{lat}!3f#{heading}!4f#{pitch}!5f#{fov}"
    @placeholder = "https://maps.googleapis.com/maps/api/streetview?size=690x400&location=#{lon},#{lat}&pano=#{panoid}&fov=#{zoom}&heading=#{heading}&pitch=#{pitch}&sensor=false"

  when :canonical
    query = URI::decode_www_form(uri.query).to_h
    if !query.has_key?("ll")
      raise ArgumentError, "canonical url lacks location argument" unless query.has_key?("sll")
      query["ll"] = query["sll"]
      @url += "&ll=#{query["sll"]}"
    end
    location = query["ll"]
    if !query.has_key?("z")
      raise ArgumentError, "canonical url has incomplete query arguments" unless query.has_key?("spn") || query.has_key?("sspn")
      if !query.has_key?("spn")
        query["spn"] = query["sspn"]
        @url += "&spn=#{query["sspn"]}"
      end
      angle = query["spn"].split(",").first.to_f
      zoom = (Math.log(690.0 * 360.0 / angle / 256.0) / Math.log(2)).round
    else
      zoom = query["z"]
    end
    @url = @url.sub('output=classic', 'output=embed')
    @placeholder = "https://maps.googleapis.com/maps/api/staticmap?maptype=roadmap&size=690x400&sensor=false&center=#{location}&zoom=#{zoom}"

  else
    raise "unexpected url type #{type.inspect}"
  end
end
rewrite_custom_url(url, target) click to toggle source
# File lib/onebox/engine/google_maps_onebox.rb, line 157
def rewrite_custom_url(url, target)
  uri = URI(url)
  uri.path = uri.path.sub(/(?<=^\/maps\/d\/)\w+$/, target)
  uri.to_s
end