/sinatra_wms

Rubygem to make developing a Web Map Service in Sinatra really easy.

Primary LanguageRuby

SintraWMS

What does SinatraWMS do?

Suppose you have a huge amount of data containing geolocations which you want to show graphically.

One way to achieve this would be to create a more or less huge image file with RMagick or something and just draw the data into this file.

This approach has two drawbacks: Either the image file you create will get really huge - or it won’t get that large but doesn’t provide the needed or wanted resolution. Also, you will just get a black image with white dots on it or something - not very nice. A real map as background would be nice.

Another way would be to use a Google Map or something and use a JavaScript to add lots and lots of markers to the map.

You could zoom in and out an such - but you’d have to care about getting your data to JavaScript to add the markers. And it won’t be that fast once you reach some kind of limit.

The solution for this problem: WMS (Web Map Service).

This gem helps you to provide a WMS to be used with OpenLayers. OpenLayers will display a baselayer (OpenStreetMap or Google Maps) at a configurable Opacity and add your data on top of it. Whenever you scroll or zoom around the map, OpenLayers will request images of a part of the visible screen. Your code has to generate them on-the-fly and deliver them back.

Current test status: <img src=“https://secure.travis-ci.org/fabianonline/sinatra_wms.png?branch=master” alt=“Build Status” />

How do I use SinatraWMS?

Let’s build a sample app.

In my example, I will use SinatraWMS to display the locations of my geolocated tweets.

Gemfile:

source :rubygems

gem "sinatra"
gem "rmagick"
gem "sinatra_wms"
gem "activerecord" # I'm using ActiveRecord to retrieve my data from the Database. You can do this your own way.

sinatra_wms_test.rb:

# This is just stuff for my ActiveRecord-addiction. ;-)
# You can use DataMapper, mysql, whatever.

ActiveRecord::Base.establish_connection(
    :adapter => 'mysql',
    :host =>     'localhost',
    :username => 'twirror',
    :password => 'secret',
    :database => 'twirror',
    :encoding => 'UTF8')

class Tweet < ActiveRecord::Base
end

before do
    ActiveRecord::Base.connection.verify!
end
# End of my ActiveRecord-stuff.

# This is the main entry point for this app.
# This code just outputs generic HTML code to load OpenLayers, show the OSM
# as transparent background and our WMS stuff on top of that.
# +:baselayer+ defined which source is used as a map underneath your data.
# You could replace +:osm+ by e.g. +:google_streets+ to show Google's street
# maps.
get '/' do
    SinatraWMS::get_html_for_map_at url("/wms"), :opacity=>0.3, :title=>"My Tweets", :baselayer=>:osm
end

# This code gets run for every image requested by OpenLayers. It is given a
# RMagick::Draw object on which you can immediately start painting as well as
# a hash containing some information about this request.
wms '/wms' do |canvas, options|
    # Select matching Tweets from the DB.
    # :bbox denotes the area visible in the current image. So we only select
    # Tweets with coordinates within this box to draw.
    # We also use a limit here, because Drawing lots and lots of points with
    # RMagick is pretty slow. Since zooming in reduces the area each image has
    # to span, more and more points are going to show up when we zoom in.
    rel = Tweet.where(:sender_name=>'fabianonline').
        where("geo_lat IS NOT NULL and geo_long IS NOT NULL").
        where("geo_lat>=? and geo_lat<=?", options[:bbox][:wgs84][0][0], options[:bbox][:wgs84][1][0]).
        where("geo_long>=? and geo_long<=?", options[:bbox][:wgs84][0][1], options[:bbox][:wgs84][1][1]).
        limit(1000)

    rel.each do |tweet|
        # Draw each point. This gem extends RMagick::Draw to provide some
        # methods which directly take coordinates in WGS84 (that's the most)
        # commonly used format for coordinates. You know, longitude between
        # -180 (West) and 180 (East) degrees and latitude between -90 (South)
        # and 90 (North) degrees).
        canvas.point_wgs84(tweet.geo_long, tweet.geo_lat)
    end
end