simple_mercator_location 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,137 @@
1
+ class SimpleMercatorLocation
2
+
3
+ attr_accessor :lat_deg, :lon_deg, :zoom
4
+
5
+ def initialize(*args)
6
+ @zoom = 2
7
+ @lat_deg = 0
8
+ @lon_deg = 0
9
+
10
+ if args.first.is_a?(Hash)
11
+ @lat_deg = args.first[:lat] if args.first.has_key?(:lat)
12
+ @lon_deg = args.first[:lon] if args.first.has_key?(:lon)
13
+ @zoom = args.first[:zoom] if args.first.has_key?(:zoom)
14
+ end
15
+ end
16
+
17
+
18
+ # latitude in radiants
19
+ def lat_rad
20
+ shift_to_rad * lat_deg
21
+ end
22
+
23
+
24
+ # longitude in radiants
25
+ def lon_rad
26
+ shift_to_rad * lon_deg
27
+ end
28
+
29
+
30
+ # factor for scaling to radiants
31
+ def shift_to_rad
32
+ Rational(Math::PI, 180)
33
+ end
34
+
35
+
36
+ # factor for scaling to degrees
37
+ def shift_to_deg
38
+ Rational(180.0, Math::PI)
39
+ end
40
+
41
+
42
+ # size of our tiles
43
+ def tile_size
44
+ 256
45
+ end
46
+
47
+
48
+ # earth radius in meters
49
+ def earth_radius
50
+ 6378137
51
+ end
52
+
53
+
54
+ # set the zoom-level and returns self
55
+ def zoom_at(scale)
56
+ @zoom = scale
57
+ self
58
+ end
59
+
60
+
61
+ # origin of google map lieves on top-left. For getting pixels,
62
+ # we assume displaying the earh an zoomm-level 0 and a square tile
63
+ # of tile_size * tile_size pixels. The origin of our wsg84 system
64
+ # (lat, lon = (0,0) sits and (128,128)px.
65
+ def origin_px
66
+ [tile_size / 2, tile_size / 2]
67
+ end
68
+
69
+
70
+ # return the pixels per degree
71
+ def pixel_per_degree
72
+ Rational(tile_size, 360)
73
+ end
74
+
75
+
76
+ # return the pixels per radiant
77
+ def pixel_per_rad
78
+ tile_size / (2 * Math::PI) # tile_size / (360 * shift_to_rad)
79
+ end
80
+
81
+
82
+ # scale the latitude via mercator projections
83
+ # see http://en.wikipedia.org/wiki/Mercator_projection#Derivation_of_the_Mercator_projection
84
+ # return the scaled latitude as radiant
85
+ def lat_scaled_rad
86
+ Math.log( Math.tan( Rational(Math::PI,4) + Rational(lat_rad,2) ))
87
+ end
88
+
89
+
90
+ # returns the scaled latitude as degrees
91
+ def lat_scaled_deg
92
+ lat_scaled_rad * shift_to_deg
93
+ end
94
+
95
+
96
+ # returns the mercotors meters count for the location
97
+ def to_m
98
+ mx = earth_radius * lon_rad
99
+ my = earth_radius * lat_scaled_rad
100
+
101
+ my = (my.round(8) == 0)? 0 : my
102
+
103
+ [mx.to_f, my.to_f]
104
+ end
105
+
106
+
107
+ # calculates the google world-coordinates as described here:
108
+ # https://developers.google.com/maps/documentation/javascript/examples/map-coordinates
109
+ def to_w
110
+ px = origin_px.first + pixel_per_rad * lon_rad
111
+ py = origin_px.last - pixel_per_rad * lat_scaled_rad
112
+
113
+ return [px, py]
114
+ end
115
+
116
+
117
+ # returns the pixel coordinates at the given zoom level
118
+ def to_px
119
+ tiles_count = 2**zoom
120
+ wx,wy = self.to_w
121
+ px = wx * tiles_count
122
+ py = wy * tiles_count
123
+ return [px.to_i, py.to_i]
124
+ end
125
+
126
+
127
+ # calculates the tile numbers for google maps at the given zoom level
128
+ def to_tile
129
+ px,py = self.to_px
130
+
131
+ tx = Rational(px, tile_size).to_i
132
+ ty = Rational(py, tile_size).to_i
133
+
134
+ return [tx,ty]
135
+ end
136
+
137
+ end
@@ -0,0 +1,130 @@
1
+ require "spec_helper"
2
+
3
+ describe SimpleMercatorLocation do
4
+
5
+ describe "accessors" do
6
+
7
+ let(:loc) { SimpleMercatorLocation.new }
8
+
9
+ attrs = [:lat_deg, :lon_deg, :zoom]
10
+
11
+ attrs.each do |attr|
12
+ it "should have getter and setter for #{attr}" do
13
+ loc.should respond_to("#{attr}".to_sym)
14
+ loc.should respond_to("#{attr}=".to_sym)
15
+ loc.send("#{attr}=".to_sym, "asd")
16
+ loc.send("#{attr}".to_sym).should eql "asd"
17
+ end
18
+ end
19
+ end
20
+
21
+
22
+ describe "#initialize" do
23
+ context "without args" do
24
+
25
+ let(:loc) { SimpleMercatorLocation.new }
26
+
27
+ it "returns a location object" do
28
+ loc.should be_an_instance_of(SimpleMercatorLocation)
29
+ end
30
+ end
31
+
32
+
33
+ context "given a hash with lat, lon and zoom" do
34
+ let(:loc) { SimpleMercatorLocation.new(lat: 1, lon: 2, zoom: 12) }
35
+
36
+ it "returns a location object" do
37
+ loc.should be_an_instance_of(SimpleMercatorLocation)
38
+ end
39
+
40
+ it "return a location object with lat and lon set" do
41
+ loc.lat_deg.should eql 1
42
+ loc.lon_deg.should eql 2
43
+ loc.zoom.should eql 12
44
+ end
45
+ end
46
+ end
47
+
48
+
49
+ describe "#zoom_at" do
50
+ let(:loc) { SimpleMercatorLocation.new }
51
+ it "sets the zoom scale and returns self" do
52
+ loc.zoom_at(15).should eql loc
53
+ loc.zoom_at(15).zoom.should eql 15
54
+ end
55
+ end
56
+
57
+ describe "to_m" do
58
+ context "given no args (so using lat = 0 and lon = 0)" do
59
+ let(:loc) { SimpleMercatorLocation.new }
60
+ it "should return origin" do
61
+ loc.to_m.should eql [0.0,0.0]
62
+ end
63
+ end
64
+
65
+ context "given a location" do
66
+ places =
67
+ [
68
+ { lat: 48.16608541901253, lon: 11.6455078125, mx: 1296371.99971659, my: 6134530.14205511 },
69
+ { lat: 48.10743118848038, lon: 11.42578125, mx: 1271912.15066533, my: 6124746.20243460 },
70
+ { lat: 48.22467264956519, lon: 12.12890625, mx: 1350183.66762935, my: 6144314.08167561 },
71
+ { lat: 0, lon: 0, mx: 0.0, my: 0.0 },
72
+ ]
73
+ places.each do |place|
74
+ it "calculates the mercator projection of (lat: #{place[:lat]}, lon: #{place[:lon]}) to (meters x: #{place[:mx]}, meters y: #{place[:my]})" do
75
+ meters = SimpleMercatorLocation.new(lon: place[:lon], lat: place[:lat]).to_m
76
+ meters.map!{|m| m.round(8) }.should eql([place[:mx], place[:my]])
77
+ end
78
+ end
79
+ end
80
+ end
81
+
82
+ describe "to_w" do
83
+ places =
84
+ [
85
+ { lat: 41.850033, lon: -87.65005229999997, wx: 65.67107392000001, wy: 95.1748950436046 },
86
+ #{ lat: 48.10743118848038, lon: 11.42578125, wx: 1271912.15066533, wy: 6124746.20243460 },
87
+ #{ lat: 48.22467264956519, lon: 12.12890625, wx: 1350183.66762935, wy: 6144314.08167561 },
88
+ #{ lat: 0, lon: 0, wx: 0.0, wy: 0.0 },
89
+ ]
90
+ places.each do |place|
91
+ it "calculates the mercator projection of (lat: #{place[:lat]}, lon: #{place[:lon]}) to (world coordinate x: #{place[:wx]}, world coordinate y: #{place[:wy]})" do
92
+ SimpleMercatorLocation.new(lon: place[:lon], lat: place[:lat]).to_w.should eql([place[:wx], place[:wy]])
93
+ end
94
+ end
95
+
96
+
97
+ end
98
+
99
+
100
+ describe "to_px" do
101
+ places =
102
+ [
103
+ { zoom: 11, lat: 49.38237278700955, lon: 8.61328125, px: 274688, py: 179200 },
104
+ { zoom: 14, lat: 49.38237278700955, lon: 8.61328125, px: 2197504, py: 1433600 },
105
+ ]
106
+
107
+ places.each do |place|
108
+ it "calculates the pixels of (lat: #{place[:lat]}, lon: #{place[:lon]}) to (px: #{place[:px]}, py: #{place[:py]})" do
109
+ SimpleMercatorLocation.new(lon: place[:lon], lat: place[:lat], zoom: place[:zoom]).to_px.should eql([place[:px], place[:py]])
110
+ end
111
+ end
112
+ end
113
+
114
+
115
+ describe "to_tile" do
116
+ places =
117
+ [
118
+ { zoom: 11, lat: 49.412758, lon: 8.671938, tx: 1073, ty: 699 },
119
+ { zoom: 11, lat: 40.689359, lon: -74.045197, tx: 602, ty: 770 },
120
+ { zoom: 15, lat: 40.689359, lon: -74.045197, tx: 9644, ty: 12322 },
121
+ ]
122
+
123
+ places.each do |place|
124
+ it "calculates the tile of (lat: #{place[:lat]}, lon: #{place[:lon]}) to (tile x: #{place[:tx]}, tile y: #{place[:ty]})" do
125
+ SimpleMercatorLocation.new(lon: place[:lon], lat: place[:lat], zoom: place[:zoom]).to_tile.should eql([place[:tx], place[:ty]])
126
+ end
127
+ end
128
+ end
129
+
130
+ end
@@ -0,0 +1,19 @@
1
+ require "rspec"
2
+ require "awesome_print"
3
+
4
+ RSpec.configure do |config|
5
+ config.color_enabled = true
6
+ config.filter_run :focus => true
7
+ config.run_all_when_everything_filtered = true
8
+ config.treat_symbols_as_metadata_keys_with_true_values = true
9
+ end
10
+
11
+ def timed(name)
12
+ start = Time.now
13
+ puts "\n[STARTED: #{name}]"
14
+ yield if block_given?
15
+ finish = Time.now
16
+ puts "[FINISHED: #{name} in #{(finish - start) * 1000} milliseconds]"
17
+ end
18
+
19
+ require "simple_mercator_location"
metadata ADDED
@@ -0,0 +1,66 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: simple_mercator_location
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Roman Lehnert
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-06-29 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '2.5'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '2.5'
30
+ description: Converts WSG84 Coordinates via Mercator-projection to meters and tiles
31
+ email: roman.lehnert@googlemail.com
32
+ executables: []
33
+ extensions: []
34
+ extra_rdoc_files: []
35
+ files:
36
+ - lib/simple_mercator_location.rb
37
+ - spec/lib/simple_mercator_location_spec.rb
38
+ - spec/spec_helper.rb
39
+ homepage: https://github.com/romanlehnert/simple_mercator_location
40
+ licenses:
41
+ - MIT
42
+ post_install_message:
43
+ rdoc_options: []
44
+ require_paths:
45
+ - lib
46
+ required_ruby_version: !ruby/object:Gem::Requirement
47
+ none: false
48
+ requirements:
49
+ - - ! '>='
50
+ - !ruby/object:Gem::Version
51
+ version: '0'
52
+ required_rubygems_version: !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ! '>='
56
+ - !ruby/object:Gem::Version
57
+ version: '0'
58
+ requirements: []
59
+ rubyforge_project:
60
+ rubygems_version: 1.8.25
61
+ signing_key:
62
+ specification_version: 3
63
+ summary: A tiny lib for the mercator projecton
64
+ test_files:
65
+ - spec/lib/simple_mercator_location_spec.rb
66
+ - spec/spec_helper.rb