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 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
+