sinatra_wms 0.0.1
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/.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
|
+
|