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 +3 -0
- data/README.rdoc +81 -0
- data/Rakefile +6 -0
- data/lib/sinatra_wms.rb +12 -4
- data/lib/sinatra_wms/rmagick_extension.rb +19 -1
- data/lib/sinatra_wms/sinatra_extension.rb +22 -2
- data/lib/sinatra_wms/version.rb +1 -1
- data/sinatra_wms.gemspec +1 -0
- metadata +6 -6
- data/README.md +0 -89
data/Gemfile
CHANGED
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
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
|
data/lib/sinatra_wms/version.rb
CHANGED
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:
|
4
|
+
hash: 27
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version: 0.0.
|
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.
|
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
|
-
```
|