sinatra_wms 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
- ```