sinatra_wms 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/README.md +89 -0
- data/Rakefile +1 -0
- data/lib/sinatra_wms/rmagick_extension.rb +48 -0
- data/lib/sinatra_wms/sinatra_extension.rb +42 -0
- data/lib/sinatra_wms/version.rb +3 -0
- data/lib/sinatra_wms.rb +48 -0
- data/sinatra_wms.gemspec +24 -0
- metadata +107 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,89 @@
|
|
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
|
+
```
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'RMagick'
|
2
|
+
|
3
|
+
module Magick
|
4
|
+
class Draw
|
5
|
+
WMS_FUNCTIONS_SINGLE = %w(color matte point text bigpoint)
|
6
|
+
WMS_FUNCTIONS_DOUBLE = %w(circle ellipse line rectangle roundrectangle)
|
7
|
+
|
8
|
+
def method_missing(sym, *args, &block)
|
9
|
+
result = sym.to_s.match /^([a-z0-9_]+)_(?:wgs84)$/
|
10
|
+
if result && respond_to?(result[1]) && @wms_settings
|
11
|
+
if WMS_FUNCTIONS_SINGLE.include?(result[1])
|
12
|
+
args[0], args[1] = latlon_to_pixels(args[0], args[1])
|
13
|
+
elsif WMS_FUNCTIONS_DOUBLE.include?(result[1])
|
14
|
+
args[0], args[1] = latlon_to_pixels(args[0], args[1])
|
15
|
+
args[2], args[3] = latlon_to_pixels(args[2], args[3])
|
16
|
+
end
|
17
|
+
send(result[1], *args, &block)
|
18
|
+
else
|
19
|
+
super(sym, *args, &block)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def wms_settings=(hash)
|
24
|
+
hash[:min_sin_y] = Math::asinh(Math::tan(hash[:bbox][0][0] / 180.0 * Math::PI)) * 6378137.0
|
25
|
+
hash[:max_sin_y] = Math::asinh(Math::tan(hash[:bbox][1][0] / 180.0 * Math::PI)) * 6378137.0
|
26
|
+
hash[:diff_y] = hash[:max_sin_y] - hash[:min_sin_y]
|
27
|
+
hash[:factor_x] = hash[:width] / (hash[:bbox][1][1] - hash[:bbox][0][1])
|
28
|
+
@wms_settings = hash
|
29
|
+
end
|
30
|
+
|
31
|
+
def bigpoint(x, y)
|
32
|
+
rectangle(x-1, y-1, x+1, y+1)
|
33
|
+
end
|
34
|
+
|
35
|
+
def respond_to?(sym)
|
36
|
+
result = sym.to_s.match /^([a-z0-9_]+)_(?:wgs84)$/
|
37
|
+
(result && super(result[1]) && @wms_settings) || super(sym)
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
def latlon_to_pixels(x, y)
|
42
|
+
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
|
+
x = ((x - @wms_settings[:bbox][0][1]) * @wms_settings[:factor_x]).round
|
44
|
+
y = (1 - (((Math::asinh(Math::tan(y / 180.0 * Math::PI)) * 6378137.0) - @wms_settings[:min_sin_y]) / @wms_settings[:diff_y])) * @wms_settings[:height]
|
45
|
+
return [x, y]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'sinatra/base'
|
2
|
+
module Sinatra
|
3
|
+
module WMS
|
4
|
+
def wms(url, &block)
|
5
|
+
get url do
|
6
|
+
headers "Content-Type" => "image/png"
|
7
|
+
# convert the given parameters to lowercase symbols
|
8
|
+
options = params.inject({}) {|hash, (key, value)| hash[key.to_s.downcase.to_sym] = value; hash }
|
9
|
+
|
10
|
+
# interpret :width and :height as integer
|
11
|
+
[:width, :height].each {|what| options[what] = options[what].to_i}
|
12
|
+
|
13
|
+
# convert bounding box coordinates
|
14
|
+
bbox = options[:bbox].split(',').collect{|v| v.to_f}
|
15
|
+
options[:bbox] = {:original => [[bbox[0], bbox[1]], [bbox[2], bbox[3]]]}
|
16
|
+
if options[:srs]=="EPSG:900913"
|
17
|
+
options[:bbox][:google] = options[:bbox][:original]
|
18
|
+
options[:bbox][:wgs84] = [*SinatraWMS::merc_to_latlon(bbox[0], bbox[1])], [*SinatraWMS::merc_to_latlon(bbox[2], bbox[3])]
|
19
|
+
else
|
20
|
+
raise "Unexpected Projection (srs): #{options[:srs]}"
|
21
|
+
end
|
22
|
+
options[:zoom] = (17-((Math.log((options[:bbox][:google][1][0]-options[:bbox][:google][0][0]).abs*3.281/500) / Math.log(2)).round))
|
23
|
+
|
24
|
+
image = Magick::Image.new(options[:width], options[:height]) { self.background_color = 'transparent' }
|
25
|
+
gc = Magick::Draw.new
|
26
|
+
gc.stroke("black")
|
27
|
+
|
28
|
+
gc.wms_settings = {
|
29
|
+
:bbox => options[:bbox][:wgs84],
|
30
|
+
:width => options[:width],
|
31
|
+
:height => options[:height]}
|
32
|
+
|
33
|
+
block.call(gc, options)
|
34
|
+
|
35
|
+
gc.draw(image) rescue nil
|
36
|
+
image.to_blob {self.format="png"}
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
register WMS
|
42
|
+
end
|
data/lib/sinatra_wms.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
require "sinatra_wms/version"
|
2
|
+
require "sinatra_wms/sinatra_extension"
|
3
|
+
require "sinatra_wms/rmagick_extension"
|
4
|
+
|
5
|
+
module SinatraWMS
|
6
|
+
def self.merc_to_latlon(x, y)
|
7
|
+
lon = (x / 6378137.0) / Math::PI * 180
|
8
|
+
lat = Math::atan(Math::sinh(y / 6378137.0)) / Math::PI * 180
|
9
|
+
return [lat, lon]
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.deg_sin(x)
|
13
|
+
Math.sin(x * Math::PI / 180)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.get_html_for_map_at(url, options={})
|
17
|
+
options[:title] ||= "Sinatra-WMS"
|
18
|
+
options[:opacity] ||= 1
|
19
|
+
options[:datasource_name] ||= "WMS-Data"
|
20
|
+
|
21
|
+
%Q{
|
22
|
+
<html>
|
23
|
+
<head>
|
24
|
+
<title>#{options[:title]}</title>
|
25
|
+
<script src="http://openlayers.org/api/OpenLayers.js"></script>
|
26
|
+
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
|
27
|
+
</head>
|
28
|
+
<body>
|
29
|
+
<div style="width:100%; height:100%" id="map"></div>
|
30
|
+
<script type="text/javascript" defer="defer">
|
31
|
+
var imagebounds = new OpenLayers.Bounds(-20037508.34, -20037508.34, 20037508.34, 20037508.34);
|
32
|
+
|
33
|
+
var map = new OpenLayers.Map('map');
|
34
|
+
map.addControl(new OpenLayers.Control.LayerSwitcher());
|
35
|
+
|
36
|
+
var osm_layer = new OpenLayers.Layer.OSM();
|
37
|
+
osm_layer.setOpacity(#{options[:opacity]});
|
38
|
+
map.addLayer(osm_layer);
|
39
|
+
|
40
|
+
var layer = new OpenLayers.Layer.WMS("#{options[:datasource_name]}", "#{url}", {transparent: true}, {maxExtent: imagebounds});
|
41
|
+
|
42
|
+
map.addLayer(layer);
|
43
|
+
map.zoomToExtent(imagebounds);
|
44
|
+
</script>
|
45
|
+
</body>
|
46
|
+
</html>}
|
47
|
+
end
|
48
|
+
end
|
data/sinatra_wms.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "sinatra_wms/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "sinatra_wms"
|
7
|
+
s.version = SinatraWMS::VERSION
|
8
|
+
s.authors = ["Fabian Schlenz (@fabianonline)"]
|
9
|
+
s.email = ["mail@fabianonline.de"]
|
10
|
+
s.homepage = "http://github.com/fabianonline/sinatra_wms"
|
11
|
+
s.summary = "Extends Sinatra to allow the developer to easily build a WMS server"
|
12
|
+
s.description = %q(A WMS (Web Map Service) is a great way to show lots of geolocated data on a map. Instead of generating static images (which will either be huge or don't have enough resolution), a WMS allows you to dynamically zoom in and out of your dataset.
|
13
|
+
This gem allows you to very easily represent your data via a WMS. On one hand it extends Sinatra to give it a method called "wms" to process WMS-requests; on the other hand it extends RMagick to allow the developer to use coordinates in the methods used for drawing.)
|
14
|
+
|
15
|
+
s.rubyforge_project = "sinatra_wms"
|
16
|
+
|
17
|
+
s.add_dependency('sinatra', '>=1.0.0')
|
18
|
+
s.add_dependency('rmagick')
|
19
|
+
|
20
|
+
s.files = `git ls-files`.split("\n")
|
21
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
22
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
23
|
+
s.require_paths = ["lib"]
|
24
|
+
end
|
metadata
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sinatra_wms
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Fabian Schlenz (@fabianonline)
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2012-04-23 00:00:00 +02:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: sinatra
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 23
|
30
|
+
segments:
|
31
|
+
- 1
|
32
|
+
- 0
|
33
|
+
- 0
|
34
|
+
version: 1.0.0
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: rmagick
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
hash: 3
|
46
|
+
segments:
|
47
|
+
- 0
|
48
|
+
version: "0"
|
49
|
+
type: :runtime
|
50
|
+
version_requirements: *id002
|
51
|
+
description: |-
|
52
|
+
A WMS (Web Map Service) is a great way to show lots of geolocated data on a map. Instead of generating static images (which will either be huge or don't have enough resolution), a WMS allows you to dynamically zoom in and out of your dataset.
|
53
|
+
This gem allows you to very easily represent your data via a WMS. On one hand it extends Sinatra to give it a method called "wms" to process WMS-requests; on the other hand it extends RMagick to allow the developer to use coordinates in the methods used for drawing.
|
54
|
+
email:
|
55
|
+
- mail@fabianonline.de
|
56
|
+
executables: []
|
57
|
+
|
58
|
+
extensions: []
|
59
|
+
|
60
|
+
extra_rdoc_files: []
|
61
|
+
|
62
|
+
files:
|
63
|
+
- .gitignore
|
64
|
+
- Gemfile
|
65
|
+
- README.md
|
66
|
+
- Rakefile
|
67
|
+
- lib/sinatra_wms.rb
|
68
|
+
- lib/sinatra_wms/rmagick_extension.rb
|
69
|
+
- lib/sinatra_wms/sinatra_extension.rb
|
70
|
+
- lib/sinatra_wms/version.rb
|
71
|
+
- sinatra_wms.gemspec
|
72
|
+
has_rdoc: true
|
73
|
+
homepage: http://github.com/fabianonline/sinatra_wms
|
74
|
+
licenses: []
|
75
|
+
|
76
|
+
post_install_message:
|
77
|
+
rdoc_options: []
|
78
|
+
|
79
|
+
require_paths:
|
80
|
+
- lib
|
81
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
82
|
+
none: false
|
83
|
+
requirements:
|
84
|
+
- - ">="
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
hash: 3
|
87
|
+
segments:
|
88
|
+
- 0
|
89
|
+
version: "0"
|
90
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
91
|
+
none: false
|
92
|
+
requirements:
|
93
|
+
- - ">="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
hash: 3
|
96
|
+
segments:
|
97
|
+
- 0
|
98
|
+
version: "0"
|
99
|
+
requirements: []
|
100
|
+
|
101
|
+
rubyforge_project: sinatra_wms
|
102
|
+
rubygems_version: 1.4.2
|
103
|
+
signing_key:
|
104
|
+
specification_version: 3
|
105
|
+
summary: Extends Sinatra to allow the developer to easily build a WMS server
|
106
|
+
test_files: []
|
107
|
+
|