sinatra_wms 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -2,3 +2,6 @@ source "http://rubygems.org"
2
2
 
3
3
  # Specify your gem's dependencies in sinatra_wms.gemspec
4
4
  gemspec
5
+
6
+ gem 'rdoc'
7
+
data/README.rdoc ADDED
@@ -0,0 +1,81 @@
1
+ = SintraWMS
2
+
3
+ == What does SinatraWMS do?
4
+
5
+ Suppose you have a huge amount of data containing geolocations which you want to show graphically.
6
+
7
+ 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.
8
+ 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.
9
+
10
+ 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.
11
+ 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.
12
+
13
+ The solution for this problem: WMS (Web Map Service).
14
+ This gem helps you to provide a WMS to be used with OpenLayers. OpenLayers will display the OpenStreetMap 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.
15
+
16
+
17
+ == How do I use SinatraWMS?
18
+
19
+ Let's build a sample app.
20
+ In my example, I will use SinatraWMS to display the locations of my geolocated tweets.
21
+
22
+ +Gemfile+:
23
+ source :rubygems
24
+
25
+ gem "sinatra"
26
+ gem "rmagick"
27
+ gem "sinatra_wms"
28
+ gem "activerecord" # I'm using ActiveRecord to retrieve my data from the Database. You can do this your own way.
29
+
30
+ +sinatra_wms_test.rb+:
31
+ # This is just stuff for my ActiveRecord-addiction. ;-)
32
+ # You can use DataMapper, mysql, whatever.
33
+
34
+ ActiveRecord::Base.establish_connection(
35
+ :adapter => 'mysql',
36
+ :host => 'localhost',
37
+ :username => 'twirror',
38
+ :password => 'secret',
39
+ :database => 'twirror',
40
+ :encoding => 'UTF8')
41
+
42
+ class Tweet < ActiveRecord::Base
43
+ end
44
+
45
+ before do
46
+ ActiveRecord::Base.connection.verify!
47
+ end
48
+ # End of my ActiveRecord-stuff.
49
+
50
+ # This is the main entry point for this app.
51
+ # This code just outputs generic HTML code to load OpenLayers, show the OSM
52
+ # as transparent background and our WMS stuff on top of that.
53
+ get '/' do
54
+ SinatraWMS::get_html_for_map_at url("/wms"), :opacity=>0.3, :title=>"My Tweets"
55
+ end
56
+
57
+ # This code gets run for every image requested by OpenLayers. It is given a
58
+ # RMagick::Draw object on which you can immediately start painting as well as
59
+ # a hash containing some information about this request.
60
+ wms '/wms' do |canvas, options|
61
+ # Select matching Tweets from the DB.
62
+ # :bbox denotes the area visible in the current image. So we only select
63
+ # Tweets with coordinates within this box to draw.
64
+ # We also use a limit here, because Drawing lots and lots of points with
65
+ # RMagick is pretty slow. Since zooming in reduces the area each image has
66
+ # to span, more and more points are going to show up when we zoom in.
67
+ rel = Tweet.where(:sender_name=>'fabianonline').
68
+ where("geo_lat IS NOT NULL and geo_long IS NOT NULL").
69
+ where("geo_lat>=? and geo_lat<=?", options[:bbox][:wgs84][0][0], options[:bbox][:wgs84][1][0]).
70
+ where("geo_long>=? and geo_long<=?", options[:bbox][:wgs84][0][1], options[:bbox][:wgs84][1][1]).
71
+ limit(1000)
72
+
73
+ rel.each do |tweet|
74
+ # Draw each point. This gem extends RMagick::Draw to provide some
75
+ # methods which directly take coordinates in WGS84 (that's the most)
76
+ # commonly used format for coordinates. You know, longitude between
77
+ # -180 (West) and 180 (East) degrees and latitude between -90 (South)
78
+ # and 90 (North) degrees).
79
+ canvas.point_wgs84(tweet.geo_long, tweet.geo_lat)
80
+ end
81
+ end
data/Rakefile CHANGED
@@ -1 +1,7 @@
1
+ require 'rdoc/task'
1
2
  require 'bundler/gem_tasks'
3
+
4
+ RDoc::Task.new do |r|
5
+ r.main = "README.rdoc"
6
+ r.rdoc_files.include("README.rdoc", "lib/**/*.rb")
7
+ end
data/lib/sinatra_wms.rb CHANGED
@@ -3,16 +3,24 @@ require "sinatra_wms/sinatra_extension"
3
3
  require "sinatra_wms/rmagick_extension"
4
4
 
5
5
  module SinatraWMS
6
+
7
+ ##
8
+ # Convert a set of coordinates from sperical mercator to WGS84.
6
9
  def self.merc_to_latlon(x, y)
7
10
  lon = (x / 6378137.0) / Math::PI * 180
8
11
  lat = Math::atan(Math::sinh(y / 6378137.0)) / Math::PI * 180
9
12
  return [lat, lon]
10
13
  end
11
-
12
- def self.deg_sin(x)
13
- Math.sin(x * Math::PI / 180)
14
- end
15
14
 
15
+ ##
16
+ # Returns generic HTML code to display a transparent OSM map and images from the WMS.
17
+ #
18
+ # * +url+ has to be the full URL to the WMS. Since this module can't use Sinatra's helper,
19
+ # please call it using this helper, e.g. +url("/wms")+.
20
+ # * Other options used are:
21
+ # * +:title+ - Sets the HTML title attribute.
22
+ # * +:opacity:+ - Sets the opacity of the OSM map in the background.
23
+ # * +:datasource_name+ - Sets the name of the WMS source in the layer selector menu.
16
24
  def self.get_html_for_map_at(url, options={})
17
25
  options[:title] ||= "Sinatra-WMS"
18
26
  options[:opacity] ||= 1
@@ -1,10 +1,18 @@
1
1
  require 'RMagick'
2
2
 
3
+ ##
4
+ # Monkeypatching RMagick to add some methods to +Magick::Draw+.
3
5
  module Magick
4
6
  class Draw
7
+ ##
8
+ # Which methods with one coordinates as parameter to add +_wgs84+ method to
5
9
  WMS_FUNCTIONS_SINGLE = %w(color matte point text bigpoint)
10
+ ##
11
+ # Which methods with two coordinates as parameter to add +_wgs84+ method to
6
12
  WMS_FUNCTIONS_DOUBLE = %w(circle ellipse line rectangle roundrectangle)
7
13
 
14
+ ##
15
+ # We use method_missing to catch calls to the +_wgs84+ methods.
8
16
  def method_missing(sym, *args, &block)
9
17
  result = sym.to_s.match /^([a-z0-9_]+)_(?:wgs84)$/
10
18
  if result && respond_to?(result[1]) && @wms_settings
@@ -20,6 +28,9 @@ module Magick
20
28
  end
21
29
  end
22
30
 
31
+ ##
32
+ # This method gets the data from +SinatraExtension+ and does some calculations
33
+ # to have values prepared when they're needed.
23
34
  def wms_settings=(hash)
24
35
  hash[:min_sin_y] = Math::asinh(Math::tan(hash[:bbox][0][0] / 180.0 * Math::PI)) * 6378137.0
25
36
  hash[:max_sin_y] = Math::asinh(Math::tan(hash[:bbox][1][0] / 180.0 * Math::PI)) * 6378137.0
@@ -27,17 +38,24 @@ module Magick
27
38
  hash[:factor_x] = hash[:width] / (hash[:bbox][1][1] - hash[:bbox][0][1])
28
39
  @wms_settings = hash
29
40
  end
30
-
41
+
42
+ ##
43
+ # The same as +point+, but draws a "big point" with 3 pixels width and height.
31
44
  def bigpoint(x, y)
32
45
  rectangle(x-1, y-1, x+1, y+1)
33
46
  end
34
47
 
48
+ ##
49
+ # See +method_missing+
35
50
  def respond_to?(sym)
36
51
  result = sym.to_s.match /^([a-z0-9_]+)_(?:wgs84)$/
37
52
  (result && super(result[1]) && @wms_settings) || super(sym)
38
53
  end
39
54
 
40
55
  private
56
+ ##
57
+ # Converts WGS84 coordinates to pixel values.
58
+ # Gets called via +method_missing+, whenever one of the +*_wgs84* methods is used.
41
59
  def latlon_to_pixels(x, y)
42
60
  raise "wms_settings is missing values" unless [:bbox, :factor_x, :min_sin_y, :max_sin_y, :height].all?{|v| @wms_settings.has_key?(v)}
43
61
  x = ((x - @wms_settings[:bbox][0][1]) * @wms_settings[:factor_x]).round
@@ -1,6 +1,19 @@
1
1
  require 'sinatra/base'
2
+ ##
3
+ # +Sinatra+ is extended to contain the method +wms+, which controls all the
4
+ # WMS-relevant stuff.
2
5
  module Sinatra
3
6
  module WMS
7
+ ##
8
+ # Reacts on WMS calls
9
+ #
10
+ # Defines an +url+ to react on WMS calls.
11
+ # If we get a matching request, we will do some fancy calculations to the
12
+ # parameters given (as in "convert the bounding box from sperical mercator
13
+ # to the well-known WGS84 projection" and so on.)
14
+ # Also, we will prepare an image and a matching canvas which we give to the
15
+ # given block.
16
+ # After the block has run, we return the image to the requestee.
4
17
  def wms(url, &block)
5
18
  get url do
6
19
  headers "Content-Type" => "image/png"
@@ -10,7 +23,7 @@ module Sinatra
10
23
  # interpret :width and :height as integer
11
24
  [:width, :height].each {|what| options[what] = options[what].to_i}
12
25
 
13
- # convert bounding box coordinates
26
+ # convert bounding box coordinates to WGS84
14
27
  bbox = options[:bbox].split(',').collect{|v| v.to_f}
15
28
  options[:bbox] = {:original => [[bbox[0], bbox[1]], [bbox[2], bbox[3]]]}
16
29
  if options[:srs]=="EPSG:900913"
@@ -19,19 +32,26 @@ module Sinatra
19
32
  else
20
33
  raise "Unexpected Projection (srs): #{options[:srs]}"
21
34
  end
35
+
36
+ # calculate the current zoom level (between 0 and 17, with 0 being the while world)
22
37
  options[:zoom] = (17-((Math.log((options[:bbox][:google][1][0]-options[:bbox][:google][0][0]).abs*3.281/500) / Math.log(2)).round))
23
-
38
+
39
+ # generate a transparent image, an empty canvas and set the default
40
+ # drawing color to black
24
41
  image = Magick::Image.new(options[:width], options[:height]) { self.background_color = 'transparent' }
25
42
  gc = Magick::Draw.new
26
43
  gc.stroke("black")
27
44
 
45
+ # The canvas needs some values for the calculations for the +*_wgs84+ methods.
28
46
  gc.wms_settings = {
29
47
  :bbox => options[:bbox][:wgs84],
30
48
  :width => options[:width],
31
49
  :height => options[:height]}
32
50
 
51
+ # Call the block
33
52
  block.call(gc, options)
34
53
 
54
+ # Add the canvas to the image and return the resulting image as PNG.
35
55
  gc.draw(image) rescue nil
36
56
  image.to_blob {self.format="png"}
37
57
  end
@@ -1,3 +1,3 @@
1
1
  module SinatraWMS
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
data/sinatra_wms.gemspec CHANGED
@@ -20,5 +20,6 @@ This gem allows you to very easily represent your data via a WMS. On one hand it
20
20
  s.files = `git ls-files`.split("\n")
21
21
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
22
22
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
23
+ s.extra_rdoc_files = ["README.rdoc"]
23
24
  s.require_paths = ["lib"]
24
25
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sinatra_wms
3
3
  version: !ruby/object:Gem::Version
4
- hash: 29
4
+ hash: 27
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 0
9
- - 1
10
- version: 0.0.1
9
+ - 2
10
+ version: 0.0.2
11
11
  platform: ruby
12
12
  authors:
13
13
  - Fabian Schlenz (@fabianonline)
@@ -57,12 +57,12 @@ executables: []
57
57
 
58
58
  extensions: []
59
59
 
60
- extra_rdoc_files: []
61
-
60
+ extra_rdoc_files:
61
+ - README.rdoc
62
62
  files:
63
63
  - .gitignore
64
64
  - Gemfile
65
- - README.md
65
+ - README.rdoc
66
66
  - Rakefile
67
67
  - lib/sinatra_wms.rb
68
68
  - lib/sinatra_wms/rmagick_extension.rb
data/README.md DELETED
@@ -1,89 +0,0 @@
1
- SintraWMS
2
- =========
3
-
4
- What does SinatraWMS do?
5
- ------------------------
6
-
7
- Suppose you have a huge amount of data containing geolocations which you want to show graphically.
8
-
9
- 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.
10
- 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.
11
-
12
- 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.
13
- 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.
14
-
15
- The solution for this problem: WMS (Web Map Service).
16
- This gem helps you to provide a WMS to be used with OpenLayers. OpenLayers will display the OpenStreetMap 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.
17
-
18
-
19
- How do I use SinatraWMS?
20
- ------------------------
21
-
22
- Let's build a sample app.
23
- In my example, I will use SinatraWMS to display the locations of my geolocated tweets.
24
-
25
- **Gemfile:**
26
- ```ruby
27
- source :rubygems
28
-
29
- gem "sinatra"
30
- gem "rmagick"
31
- gem "sinatra_wms"
32
- gem "activerecord" # I'm using ActiveRecord to retrieve my data from the Database. You can do this your own way.
33
- ```
34
-
35
- **sinatra_wms_test:**
36
- ```ruby
37
- # This is just stuff for my ActiveRecord-addiction. ;-)
38
- # You can use DataMapper, mysql, whatever.
39
-
40
- ActiveRecord::Base.establish_connection(
41
- :adapter => 'mysql',
42
- :host => 'localhost',
43
- :username => 'twirror',
44
- :password => 'secret',
45
- :database => 'twirror',
46
- :encoding => 'UTF8')
47
-
48
- class Tweet < ActiveRecord::Base
49
- end
50
-
51
- before do
52
- ActiveRecord::Base.connection.verify!
53
- end
54
- # End of my ActiveRecord-stuff.
55
-
56
- # This is the main entry point for this app.
57
- # This code just outputs generic HTML code to load OpenLayers, show the OSM
58
- # as transparent background and our WMS stuff on top of that.
59
- get '/' do
60
- SinatraWMS::get_html_for_map_at url("/wms"), :opacity=>0.3, :title=>"My Tweets"
61
- end
62
-
63
- # This code gets run for every image requested by OpenLayers. It is given a
64
- # RMagick::Draw object on which you can immediately start painting as well as
65
- # a hash containing some information about this request.
66
- wms '/wms' do |canvas, options|
67
- # Select matching Tweets from the DB.
68
- # :bbox denotes the area visible in the current image. So we only select
69
- # Tweets with coordinates within this box to draw.
70
- # We also use a limit here, because Drawing lots and lots of points with
71
- # RMagick is pretty slow. Since zooming in reduces the area each image has
72
- # to span, more and more points are going to show up when we zoom in.
73
- rel = Tweet.where(:sender_name=>'fabianonline').
74
- where("geo_lat IS NOT NULL and geo_long IS NOT NULL").
75
- where("geo_lat>=? and geo_lat<=?", options[:bbox][:wgs84][0][0], options[:bbox][:wgs84][1][0]).
76
- where("geo_long>=? and geo_long<=?", options[:bbox][:wgs84][0][1], options[:bbox][:wgs84][1][1]).
77
- limit(1000)
78
-
79
- rel.each do |tweet|
80
- # Draw each point. This gem extends RMagick::Draw to provide some
81
- # methods which directly take coordinates in WGS84 (that's the most)
82
- # commonly used format for coordinates. You know, longitude between
83
- # -180 (West) and 180 (East) degrees and latitude between -90 (South)
84
- # and 90 (North) degrees).
85
- canvas.point_wgs84(tweet.geo_long, tweet.geo_lat)
86
- end
87
- end
88
-
89
- ```