sinatra_wms 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in sinatra_wms.gemspec
4
+ gemspec
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
@@ -0,0 +1,3 @@
1
+ module SinatraWMS
2
+ VERSION = "0.0.1"
3
+ end
@@ -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
@@ -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
+