quad_sphere 0.9.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,182 @@
1
+ # A shaded view of the CSC projection. You need OpenGL for this.
2
+
3
+ require 'quad_sphere/csc'
4
+ require 'opengl'
5
+
6
+ class CSCShadedApp
7
+
8
+ WINDOW_SIZE = 480
9
+
10
+ # How many subdivisions of each cube face. Can be raised for
11
+ # smoother shading, at the cost of model complexity.
12
+ FACE_SUBDIV = 10
13
+
14
+ def run
15
+ @ang1 = 0
16
+ @ang2 = 0
17
+ @ang3 = 0
18
+
19
+ glutInit
20
+ glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH)
21
+ glutInitWindowSize(WINDOW_SIZE, WINDOW_SIZE)
22
+ glutCreateWindow('CSC')
23
+
24
+ glEnable(GL_LIGHTING)
25
+ glEnable(GL_LIGHT0)
26
+ glEnable(GL_DEPTH_TEST)
27
+ glEnable(GL_CULL_FACE)
28
+ glFrontFace(GL_CW)
29
+ glClearColor(0.5, 0.5, 0.5, 0.0)
30
+ setup_model
31
+
32
+ glutDisplayFunc(method :display)
33
+ glutReshapeFunc(method :reshape)
34
+ glutKeyboardFunc(method :keyboard)
35
+
36
+ # And run.
37
+ glutMainLoop()
38
+ end
39
+
40
+ private
41
+
42
+ def setup_model
43
+ glGenLists(1)
44
+ glNewList(1, GL_COMPILE)
45
+
46
+ # The cube faces, and the colour we want each:
47
+ faces = [ [ QuadSphere::TOP_FACE, [ 0.9, 0.25, 0.25, 1.0 ] ], # red
48
+ [ QuadSphere::FRONT_FACE, [ 0.25, 0.25, 0.9, 1.0 ] ], # blue
49
+ [ QuadSphere::EAST_FACE, [ 0.25, 0.9, 0.25, 1.0 ] ], # green
50
+ [ QuadSphere::BACK_FACE, [ 0.9, 0.9, 0.25, 1.0 ] ], # yellow
51
+ [ QuadSphere::WEST_FACE, [ 0.25, 0.9, 0.9, 1.0 ] ], #cyan
52
+ [ QuadSphere::BOTTOM_FACE, [ 0.9, 0.25, 0.9, 1.0 ] ] ] #magenta
53
+
54
+ faces.each do |face, colour|
55
+ glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, colour);
56
+ # Calculate all the vertices we'll use for this face...
57
+ vertices = grid(face, FACE_SUBDIV)
58
+ # ... and arrange them in triangle strips:
59
+ mesh2strips(FACE_SUBDIV, vertices)
60
+ end
61
+
62
+ glEndList
63
+ end
64
+
65
+ def reshape(w, h)
66
+ glViewport(0, 0, w, h)
67
+ glMatrixMode(GL_PROJECTION)
68
+ glLoadIdentity()
69
+ gluPerspective(45.0, w.to_f / h.to_f, 1.0, 20.0)
70
+ glMatrixMode(GL_MODELVIEW)
71
+ glLoadIdentity()
72
+ gluLookAt(3.2,0,0, 0,0,0, 0,0,1)
73
+
74
+ # Add a light.
75
+ glPushMatrix()
76
+ glTranslate(2.5,-0.8,0.8);
77
+ glLightfv(GL_LIGHT0, GL_POSITION, [0, 0, 0, 1])
78
+ glPopMatrix()
79
+ end
80
+
81
+ def display
82
+ # XXX - maybe it'd be more efficient to only clear the depth
83
+ # buffer if we moved the model or camera?
84
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
85
+
86
+ # Rotate and draw the model.
87
+ glPushMatrix()
88
+ glRotate(@ang1, 0, 0, 1)
89
+ glRotate(@ang2, 1, 0, 0)
90
+ glRotate(@ang3, 0, 1, 0)
91
+ glCallList(1)
92
+ glPopMatrix()
93
+
94
+ # Done.
95
+ glutSwapBuffers()
96
+ end
97
+
98
+ def keyboard(key, x, y)
99
+ case (key)
100
+ when ?s
101
+ @ang1 = (@ang1 + 5) % 360
102
+ glutPostRedisplay()
103
+ when ?a
104
+ @ang1 = (@ang1 - 5) % 360
105
+ glutPostRedisplay()
106
+ when ?w
107
+ @ang2 = (@ang2 + 5) % 360
108
+ glutPostRedisplay()
109
+ when ?q
110
+ @ang2 = (@ang2 - 5) % 360
111
+ glutPostRedisplay()
112
+ when ?x
113
+ @ang3 = (@ang3 + 5) % 360
114
+ glutPostRedisplay()
115
+ when ?z
116
+ @ang3 = (@ang3 - 5) % 360
117
+ glutPostRedisplay()
118
+ when ?r
119
+ @ang1 = @ang2 = @ang3 = 0
120
+ glutPostRedisplay()
121
+ when ?\e, ?Q
122
+ exit(0)
123
+ end
124
+ end
125
+
126
+ # Create a NxN grid of points on a face of the cube. Note that this
127
+ # will generate (N+1)*(N+1) points.
128
+ #
129
+ # Each point is projected on the sphere and stored in an array. Note
130
+ # that all these are points on the unit sphere, and so their distance
131
+ # to the origin is 1, and so each point can be used as its own normal.
132
+ def grid(face, n)
133
+ dx = 2.0/n
134
+ dy = 2.0/n
135
+ a = Array.new
136
+ n += 1
137
+
138
+ n.times do |j|
139
+ y = -1.0 + j*dy
140
+ n.times do |i|
141
+ x = -1.0 + i*dx
142
+ lon, lat = QuadSphere::CSC.inverse(face, x, y)
143
+ sx = Math::cos(lat) * Math::cos(lon)
144
+ sy = Math::cos(lat) * Math::sin(lon)
145
+ sz = Math::sin(lat)
146
+ a << [sx,sy,sz]
147
+ end
148
+ end
149
+
150
+ a
151
+ end
152
+
153
+ # p grid(0, 3)
154
+ #
155
+ # Create triangle strips to represent a NxN mesh. The given array
156
+ # should then contain (N+1)**2 points, arranged as N+1 rows of N+1
157
+ # points.
158
+
159
+ def mesh2strips(n,a)
160
+ dx = 2.0/n
161
+ dy = 2.0/n
162
+ row = n+1
163
+
164
+ n.times do |j|
165
+ glBegin(GL_TRIANGLE_STRIP)
166
+ rowi = j*row
167
+ row.times do |x|
168
+ add_vertex(a[rowi+x])
169
+ add_vertex(a[rowi+row+x])
170
+ end
171
+ glEnd
172
+ end
173
+ end
174
+
175
+ def add_vertex(v)
176
+ glNormal3fv(v)
177
+ glVertex3fv(v)
178
+ end
179
+
180
+ end
181
+
182
+ CSCShadedApp.new.run
@@ -0,0 +1,69 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require 'quad_sphere/csc'
4
+ require 'chunky_png'
5
+
6
+ # Not the best way to draw a grid — we repaint a lot of pixels. But
7
+ # it's compact and hopefully clear.
8
+
9
+ # The size of each cube face, and some colours:
10
+ size = 99
11
+ colour = ChunkyPNG::Color.html_color(:grey)
12
+ background = ChunkyPNG::Color::WHITE
13
+ face_border = ChunkyPNG::Color::BLACK
14
+
15
+ # This is where we'll place each face, the usual cross.
16
+ faces = {
17
+ QuadSphere::TOP_FACE => [ 2*size, 0 ],
18
+ QuadSphere::BACK_FACE => [ 0, size ],
19
+ QuadSphere::WEST_FACE => [ size, size ],
20
+ QuadSphere::FRONT_FACE => [ 2*size, size ],
21
+ QuadSphere::EAST_FACE => [ 3*size, size ],
22
+ QuadSphere::BOTTOM_FACE => [ 2*size, 2*size ]
23
+ }
24
+
25
+ image = ChunkyPNG::Image.new(4*size+1, 3*size+1, ChunkyPNG::Color::TRANSPARENT)
26
+
27
+ # The longest distance on a cube face is the diagonal, which is of
28
+ # length d=size*√2. This is an angle of π/2 on the sphere. We split
29
+ # π/2 over d to get a small angle Δ that, along the diagonal, would
30
+ # map to pixels at unit distance from each other, and we use Δ as
31
+ # "angular resolution" everywhere, because this will keep our lines
32
+ # continuous. This is wasteful: Δ could be larger everywhere else; we
33
+ # repaint a lot of pixels. But hey, tis just an example.
34
+ delta = Math::PI/(2*size*Math::sqrt(2))
35
+
36
+ # And this is a lambda to paint a point by latitude and longitude.
37
+ # We'll do this a lot.
38
+ plot = lambda do |lon,lat|
39
+ face, x, y = QuadSphere::CSC.forward(lon, lat)
40
+ x = (size*(x+1)/2).floor
41
+ y = (size*(y+1)/2).floor
42
+ offx, offy = faces[face]
43
+ image[x+offx,y+offy] = colour
44
+ end
45
+
46
+ # To work. Draw a rectangle around each face.
47
+ faces.each_pair do |face, offsets|
48
+ off_x, off_y = offsets
49
+ image.rect(off_x, off_y, off_x+size, off_y+size, face_border, background)
50
+ end
51
+
52
+ # Draw the meridians.
53
+ 36.times do |meridian|
54
+ lon = meridian*Math::PI/18
55
+ (-Math::PI/2).step(Math::PI/2, delta) do |lat|
56
+ plot.call(lon,lat)
57
+ end
58
+ end
59
+
60
+ # Draw the parallels.
61
+ 18.times do |parallel|
62
+ lat = -Math::PI/2 + parallel*Math::PI/18
63
+ (-Math::PI).step(Math::PI, delta) do |lon|
64
+ plot.call(lon,lat)
65
+ end
66
+ end
67
+
68
+ # There done, save our masterpiece.
69
+ image.save('grid.png', :best_compression)
@@ -165,7 +165,7 @@ module QuadSphere
165
165
  # approximation there, don't quote me on those figures). So aye,
166
166
  # we could use more precision.
167
167
  #
168
- # There is code in the +extras+ directory to compute the error
168
+ # There is code in the +examples+ directory to compute the error
169
169
  # distribution, and even generate a picture showing the areas of
170
170
  # low and high error on the plane. Peek there if you're curious.
171
171
  #
@@ -0,0 +1,5 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ module QuadSphere
4
+ VERSION = '1.0.0'
5
+ end
@@ -0,0 +1,30 @@
1
+ # -*- coding: utf-8; mode: ruby -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = 'quad_sphere'
5
+ s.version = '1.0.0'
6
+ s.date = '2013-02-04'
7
+ s.summary = 'Quadrilateralised spherical cube projection'
8
+ s.description = <<END
9
+ An implementation of the quadrilateralised spherical cube, an
10
+ approximately equal-area projection of the sphere onto the faces of a
11
+ cube. It is useful for storing data collected on a spherical surface,
12
+ and for general mapmaking.
13
+ END
14
+ s.homepage = 'https://github.com/crinc/QuadSphere'
15
+ s.author = 'Cesar Rincon'
16
+ s.email = 'crincon@gmail.com'
17
+ s.files = [ 'README.md',
18
+ 'COPYING',
19
+ 'quad_sphere.gemspec',
20
+ 'examples/binning.rb',
21
+ 'examples/distort_closure.rb',
22
+ 'examples/gl1.rb',
23
+ 'examples/gl2.rb',
24
+ 'examples/grid.rb',
25
+ 'lib/quad_sphere.rb',
26
+ 'lib/quad_sphere/tangential.rb',
27
+ 'lib/quad_sphere/csc.rb',
28
+ 'lib/quad_sphere/version.rb' ]
29
+ s.test_files = [ 'test/test_quad_sphere.rb' ]
30
+ end
@@ -0,0 +1,860 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require 'test/unit'
4
+ require 'quad_sphere'
5
+ require 'quad_sphere/tangential'
6
+ require 'quad_sphere/csc'
7
+
8
+ module MappingTests
9
+ # Some constants to save typing (not that I typed all this heh)
10
+ PI = Math::PI
11
+
12
+ TOP_FACE = QuadSphere::TOP_FACE
13
+ FRONT_FACE = QuadSphere::FRONT_FACE
14
+ EAST_FACE = QuadSphere::EAST_FACE
15
+ BACK_FACE = QuadSphere::BACK_FACE
16
+ WEST_FACE = QuadSphere::WEST_FACE
17
+ BOTTOM_FACE = QuadSphere::BOTTOM_FACE
18
+
19
+ # This is the floating point round-off error expected to be
20
+ # introduced by our computations. Most of our tangential
21
+ # projections are exact to the unit roundoff given by Ruby,
22
+ # actually. It's only a couple that are above one epsilon. But in
23
+ # any case, just take from this the tangential projection is
24
+ # accurate to 15 digits...
25
+ #
26
+ # Float::EPSILON = 2.220446049250313e-16 on my MacBook, ruby
27
+ # 1.9.3p374 (2013-01-15 revision 38858) [x86_64-darwin11.4.2]
28
+ DELTA = 2*Float::EPSILON
29
+
30
+ # This is a small value we use to test rollover to the neighbouring
31
+ # face, in the forward projection.
32
+ NUDGE = Float::EPSILON
33
+
34
+ # Picture a sphere of unit radius inscribed in a cube, with the
35
+ # centre of the sphere at the origin of the cartesian space, and
36
+ # with the cube oriented so that each coordinate axis passes through
37
+ # the centres of two opposite cube faces.
38
+ #
39
+ # The sides of this cube are then of length 2 (twice the sphere's
40
+ # radius), thus the diagonal of the cube is of length 2*sqrt(3). It
41
+ # follows that, at each corner of the cube, the distance from the
42
+ # corner vertex to the horizontal plane is 1 (half the length of a
43
+ # cube side), and the distance from the vertex to the origin is
44
+ # sqrt(3) (half the length of the cube diagonal). It further
45
+ # follows that the sine of the elevation angle to the corners of the
46
+ # cube is 1/sqrt(3).
47
+ #
48
+ # So the four northern corners of the cube are at latitude
49
+ # θc = arcsin(1/sqrt(3)); and the southern corners at latitude -θc.
50
+ #
51
+ # I don't know is there is a way to write this angle in terms of π
52
+ # or roots or something. Would be cute.
53
+ THETA_C = Math::asin(1/Math::sqrt(3))
54
+
55
+ def test_forward_mapping
56
+ p = self.projection
57
+
58
+ # We'll test the mappings of 26 specific points around the sphere.
59
+
60
+ # First, the one north pole.
61
+
62
+ # 1. θ = π/2 (90°N) should map to the centre point of the top
63
+ # face. φ is irrelevant here.
64
+ face, x, y = p.forward(0.0, PI/2)
65
+ assert_equal(TOP_FACE, face)
66
+ assert_in_delta( 0.0, x, DELTA)
67
+ assert_in_delta( 0.0, y, DELTA)
68
+
69
+ # Then the four middle points of the edges of the top cube face.
70
+ # These are all at θ = π/4 (45°N).
71
+
72
+ # 2. φ = -π/2, θ = π/4 (45°N 90°W) should map to the left point of
73
+ # the top face, or the up point of the west face. When
74
+ # coordinates are given exactly like that, this implementation
75
+ # maps to the west face.
76
+ face, x, y = p.forward(-PI/2, PI/4)
77
+ assert_equal(WEST_FACE, face)
78
+ assert_in_delta( 0.0, x, DELTA)
79
+ assert_in_delta( 1.0, y, DELTA)
80
+ # A nudge northward, though, and we're on the top face.
81
+ face, x, y = p.forward(-PI/2, PI/4+NUDGE)
82
+ assert_equal(TOP_FACE, face)
83
+ assert_in_delta(-1.0+NUDGE, x, DELTA)
84
+ assert_in_delta( 0.0, y, DELTA)
85
+
86
+ # 3. φ = 0, θ = π/4 (45°N 0°) should map to the down point of the
87
+ # top face, or the up point of the front face. When coordinates
88
+ # are given exactly like that, this implementation maps to the
89
+ # front face.
90
+ face, x, y = p.forward(0.0, PI/4)
91
+ assert_equal(FRONT_FACE, face)
92
+ assert_in_delta( 0.0, x, DELTA)
93
+ assert_in_delta( 1.0, y, DELTA)
94
+ # A nudge northward, though, and we're on the top face.
95
+ face, x, y = p.forward(0.0, PI/4+NUDGE)
96
+ assert_equal(TOP_FACE, face)
97
+ assert_in_delta( 0.0, x, DELTA)
98
+ assert_in_delta(-1.0+NUDGE, y, DELTA)
99
+
100
+ # 4. φ = π/2, θ = π/4 (45°N 90°E) should map to the right point of
101
+ # the top face, or the up point of the east face. When
102
+ # coordinates are given exactly like that, this implementation
103
+ # maps to the east face.
104
+ face, x, y = p.forward(PI/2, PI/4)
105
+ assert_equal(EAST_FACE, face)
106
+ assert_in_delta( 0.0, x, DELTA)
107
+ assert_in_delta( 1.0, y, DELTA)
108
+ # A nudge northward, though, and we're on the top face.
109
+ face, x, y = p.forward(PI/2, PI/4+NUDGE)
110
+ assert_equal(TOP_FACE, face)
111
+ assert_in_delta( 1.0-NUDGE, x, DELTA)
112
+ assert_in_delta( 0.0, y, DELTA)
113
+
114
+ # 5. φ = π, θ = π/4 (45°N 180°) should map to the up point of the
115
+ # top face, or the up point of the back face. When coordinates
116
+ # are given exactly like that, this implementation maps to the
117
+ # back face.
118
+ face, x, y = p.forward(PI, PI/4)
119
+ assert_equal(BACK_FACE, face)
120
+ assert_in_delta( 0.0, x, DELTA)
121
+ assert_in_delta( 1.0, y, DELTA)
122
+ # A nudge northward, though, and we're on the top face.
123
+ face, x, y = p.forward(PI, PI/4+NUDGE)
124
+ assert_equal(TOP_FACE, face)
125
+ assert_in_delta( 0.0, x, DELTA)
126
+ assert_in_delta( 1.0-NUDGE, y, DELTA)
127
+
128
+ # Then the four corners of the top cube face. These are all at
129
+ # θ = θc = arcsin(1/√3) -- about 35.26°N.
130
+
131
+ # 6. φ = -3π/4, θ = θc (35.26°N 135°W) should map to the up-left
132
+ # point of the top face, or the up-right point of the back face,
133
+ # or the up-left point of the west face. When coordinates are
134
+ # given exactly like that, this implementation maps to the top
135
+ # face.
136
+ face, x, y = p.forward(-3*PI/4, THETA_C)
137
+ assert_equal(TOP_FACE, face)
138
+ assert_in_delta(-1.0, x, DELTA)
139
+ assert_in_delta( 1.0, y, DELTA)
140
+ # A nudge westward, though, and we're on the back face.
141
+ face, x, y = p.forward(-3*PI/4-2*NUDGE, THETA_C)
142
+ assert_equal(BACK_FACE, face)
143
+ assert_in_delta( 1.0-3*NUDGE, x, DELTA)
144
+ assert_in_delta( 1.0, y, DELTA)
145
+ # Or a nudge eastward, and we're on the west face.
146
+ face, x, y = p.forward(-3*PI/4+2*NUDGE, THETA_C)
147
+ assert_equal(WEST_FACE, face)
148
+ assert_in_delta(-1.0+3*NUDGE, x, DELTA)
149
+ assert_in_delta( 1.0, y, DELTA)
150
+
151
+ # 7. φ = -π/4, θ = θc (35.26°N 45°W) should map to the down-left
152
+ # point of the top face, or the up-left point of the front face,
153
+ # or the up-right point of the west face. When coordinates are
154
+ # given exactly like that, this implementation maps to the top
155
+ # face.
156
+ face, x, y = p.forward(-PI/4, THETA_C)
157
+ assert_equal(TOP_FACE, face)
158
+ assert_in_delta(-1.0, x, DELTA)
159
+ assert_in_delta(-1.0, y, DELTA)
160
+ # A nudge westward, though, and we're on the west face.
161
+ face, x, y = p.forward(-PI/4-2*NUDGE, THETA_C)
162
+ assert_equal(WEST_FACE, face)
163
+ assert_in_delta( 1.0-3*NUDGE, x, DELTA)
164
+ assert_in_delta( 1.0, y, DELTA)
165
+ # Or a nudge eastward, and we're on the front face.
166
+ face, x, y = p.forward(-PI/4+2*NUDGE, THETA_C)
167
+ assert_equal(FRONT_FACE, face)
168
+ assert_in_delta(-1.0+3*NUDGE, x, DELTA)
169
+ assert_in_delta( 1.0, y, DELTA)
170
+
171
+ # 8. φ = π/4, θ = θc (35.26°N 45°E) should map to the down-right
172
+ # point of the top face, or the up-right point of the front face,
173
+ # or the up-left point of the east face. When coordinates are
174
+ # given exactly like that, this implementation maps to the top
175
+ # face.
176
+ face, x, y = p.forward(PI/4, THETA_C)
177
+ assert_equal(TOP_FACE, face)
178
+ assert_in_delta( 1.0, x, DELTA)
179
+ assert_in_delta(-1.0, y, DELTA)
180
+ # A nudge westward, though, and we're on the front face.
181
+ face, x, y = p.forward(PI/4-2*NUDGE, THETA_C)
182
+ assert_equal(FRONT_FACE, face)
183
+ assert_in_delta( 1.0-3*NUDGE, x, DELTA)
184
+ assert_in_delta( 1.0, y, DELTA)
185
+ # Or a nudge eastward, and we're on the east face.
186
+ face, x, y = p.forward(PI/4+2*NUDGE, THETA_C)
187
+ assert_equal(EAST_FACE, face)
188
+ assert_in_delta(-1.0+3*NUDGE, x, DELTA)
189
+ assert_in_delta( 1.0, y, DELTA)
190
+
191
+ # 9. φ = 3π/4, θ = θc (35.26°N 135°E) should map to the up-right
192
+ # point of the top face, or the up-right point of the east face,
193
+ # or the up-left point of the back face. When coordinates are
194
+ # given exactly like that, this implementation maps to the top
195
+ # face.
196
+ face, x, y = p.forward(3*PI/4, THETA_C)
197
+ assert_equal(TOP_FACE, face)
198
+ assert_in_delta( 1.0, x, DELTA)
199
+ assert_in_delta( 1.0, y, DELTA)
200
+ # A nudge westward, though, and we're on the east face.
201
+ face, x, y = p.forward(3*PI/4-2*NUDGE, THETA_C)
202
+ assert_equal(EAST_FACE, face)
203
+ assert_in_delta( 1.0-3*NUDGE, x, DELTA)
204
+ assert_in_delta( 1.0, y, DELTA)
205
+ # Or a nudge eastward, and we're on the back face.
206
+ face, x, y = p.forward(3*PI/4+2*NUDGE, THETA_C)
207
+ assert_equal(BACK_FACE, face)
208
+ assert_in_delta(-1.0+3*NUDGE, x, DELTA)
209
+ assert_in_delta( 1.0, y, DELTA)
210
+
211
+ # Then the "equatorial belt", eight points, being the four centres
212
+ # of the front, east, back and west faces, and the the midpoints
213
+ # of the vertical edges of these faces. These eight points are at
214
+ # θ = zero, of course.
215
+
216
+ # 10. φ = -3π/4, θ = 0 (0° 135°W) should map to the right point of
217
+ # the back face, or the left point of the west face. When
218
+ # coordinates are given exactly like that, this implementation
219
+ # maps to the west face.
220
+ face, x, y = p.forward(-3*PI/4, 0.0)
221
+ assert_equal(WEST_FACE, face)
222
+ assert_in_delta(-1.0, x, DELTA)
223
+ assert_in_delta( 0.0, y, DELTA)
224
+ # A nudge westward, though, and we're on the back face.
225
+ face, x, y = p.forward(-3*PI/4-2*NUDGE, 0.0)
226
+ assert_equal(BACK_FACE, face)
227
+ assert_in_delta( 1.0-3*NUDGE, x, DELTA)
228
+ assert_in_delta( 0.0, y, DELTA)
229
+
230
+ # 11. φ = -π/2, θ = 0 (0° 90°W) should map to the centre point of
231
+ # the west face.
232
+ face, x, y = p.forward(-PI/2, 0.0)
233
+ assert_equal(WEST_FACE, face)
234
+ assert_in_delta( 0.0, x, DELTA)
235
+ assert_in_delta( 0.0, y, DELTA)
236
+
237
+ # 12. φ = -π/4, θ = 0 (0° 45°W) should map to the left point of
238
+ # the front face, or the right point of the west face. When
239
+ # coordinates are given exactly like that, this implementation
240
+ # maps to the front face.
241
+ face, x, y = p.forward(-PI/4, 0.0)
242
+ assert_equal(FRONT_FACE, face)
243
+ assert_in_delta(-1.0, x, DELTA)
244
+ assert_in_delta( 0.0, y, DELTA)
245
+ # A nudge westward, though, and we're on the XXX face.
246
+ face, x, y = p.forward(-PI/4-2*NUDGE, 0.0)
247
+ assert_equal(WEST_FACE, face)
248
+ assert_in_delta( 1.0-3*NUDGE, x, DELTA)
249
+ assert_in_delta( 0.0, y, DELTA)
250
+
251
+ # 13. φ = 0, θ = 0 (0° 0°) should map to the centre point of the
252
+ # front face.
253
+ face, x, y = p.forward(0.0, 0.0)
254
+ assert_equal(FRONT_FACE, face)
255
+ assert_in_delta( 0.0, x, DELTA)
256
+ assert_in_delta( 0.0, y, DELTA)
257
+
258
+ # 14. φ = π/4, θ = 0 (0° 45°E) should map to the right point of
259
+ # the front face, or the left point of the east face. When
260
+ # coordinates are given exactly like that, this implementation
261
+ # maps to the front face.
262
+ face, x, y = p.forward(PI/4, 0.0)
263
+ assert_equal(FRONT_FACE, face)
264
+ assert_in_delta( 1.0, x, DELTA)
265
+ assert_in_delta( 0.0, y, DELTA)
266
+ # A nudge eastward, though, and we're on the east face.
267
+ face, x, y = p.forward(PI/4+2*NUDGE, 0.0)
268
+ assert_equal(EAST_FACE, face)
269
+ assert_in_delta(-1.0+3*NUDGE, x, DELTA)
270
+ assert_in_delta( 0.0, y, DELTA)
271
+
272
+ # 15. φ = π/2, θ = 0 (0° 90°E) should map to the centre point of
273
+ # the east face.
274
+ face, x, y = p.forward(PI/2, 0.0)
275
+ assert_equal(EAST_FACE, face)
276
+ assert_in_delta( 0.0, x, DELTA)
277
+ assert_in_delta( 0.0, y, DELTA)
278
+
279
+ # 16. φ = 3π/4, θ = 0 (0° 135°E) should map to the right point of
280
+ # the east face, or the left point of the back face. When
281
+ # coordinates are given exactly like that, this implementation
282
+ # maps to the east face.
283
+ face, x, y = p.forward(3*PI/4, 0.0)
284
+ assert_equal(EAST_FACE, face)
285
+ assert_in_delta( 1.0, x, DELTA)
286
+ assert_in_delta( 0.0, y, DELTA)
287
+ # A nudge eastward, though, and we're on the back face.
288
+ face, x, y = p.forward(3*PI/4+2*NUDGE, 0.0)
289
+ assert_equal(BACK_FACE, face)
290
+ assert_in_delta(-1.0+3*NUDGE, x, DELTA)
291
+ assert_in_delta( 0.0, y, DELTA)
292
+
293
+ # 17. φ = π, θ = 0 (0° 180°) should map to the centre point of the
294
+ # back face.
295
+ face, x, y = p.forward(PI, 0.0)
296
+ assert_equal(BACK_FACE, face)
297
+ assert_in_delta( 0.0, x, DELTA)
298
+ assert_in_delta( 0.0, y, DELTA)
299
+
300
+ # Then the four corners of the bottom cube face. These are all at
301
+ # θ = -θc = -arcsin(1/√3) -- about 35.26°S.
302
+
303
+ # 18. φ = -3π/4, θ = -θc (35.26°S 135°W) should map to the
304
+ # down-right point of the back face, or the down-left point of the
305
+ # west face, or the down-left point of the bottom face. When
306
+ # coordinates are given exactly like that, this implementation
307
+ # maps to the bottom face.
308
+ face, x, y = p.forward(-3*PI/4, -THETA_C)
309
+ assert_equal(BOTTOM_FACE, face)
310
+ assert_in_delta(-1.0, x, DELTA)
311
+ assert_in_delta(-1.0, y, DELTA)
312
+ # A nudge westward, though, and we're on the back face.
313
+ face, x, y = p.forward(-3*PI/4-2*NUDGE, -THETA_C)
314
+ assert_equal(BACK_FACE, face)
315
+ assert_in_delta( 1.0-3*NUDGE, x, DELTA)
316
+ assert_in_delta(-1.0, y, DELTA)
317
+ # Or a nudge eastward, and we're on the west face.
318
+ face, x, y = p.forward(-3*PI/4+2*NUDGE, -THETA_C)
319
+ assert_equal(WEST_FACE, face)
320
+ assert_in_delta(-1.0+3*NUDGE, x, DELTA)
321
+ assert_in_delta(-1.0, y, DELTA)
322
+
323
+ # 19. φ = -π/4, θ = -θc (35.26°S 45°W) should map to the down-left
324
+ # point of the front face, or the down-right point of the west
325
+ # face, or the up-left point of the bottom face. When coordinates
326
+ # are given exactly like that, this implementation maps to the
327
+ # bottom face.
328
+ face, x, y = p.forward(-PI/4, -THETA_C)
329
+ assert_equal(BOTTOM_FACE, face)
330
+ assert_in_delta(-1.0, x, DELTA)
331
+ assert_in_delta( 1.0, y, DELTA)
332
+ # A nudge westward, though, and we're on the west face.
333
+ face, x, y = p.forward(-PI/4-2*NUDGE, -THETA_C)
334
+ assert_equal(WEST_FACE, face)
335
+ assert_in_delta( 1.0-3*NUDGE, x, DELTA)
336
+ assert_in_delta(-1.0, y, DELTA)
337
+ # Or a nudge eastward, and we're on the front face.
338
+ face, x, y = p.forward(-PI/4+2*NUDGE, -THETA_C)
339
+ assert_equal(FRONT_FACE, face)
340
+ assert_in_delta(-1.0+3*NUDGE, x, DELTA)
341
+ assert_in_delta(-1.0, y, DELTA)
342
+
343
+ # 20. φ = π/4, θ = -θc (35.26°S 45°E) should map to the down-right
344
+ # point of the front face, or the down-left point of the east
345
+ # face, or the up-right point of the bottom face. When
346
+ # coordinates are given exactly like that, this implementation
347
+ # maps to the bottom face.
348
+ face, x, y = p.forward(PI/4, -THETA_C)
349
+ assert_equal(BOTTOM_FACE, face)
350
+ assert_in_delta( 1.0, x, DELTA)
351
+ assert_in_delta( 1.0, y, DELTA)
352
+ # A nudge westward, though, and we're on the front face.
353
+ face, x, y = p.forward(PI/4-2*NUDGE, -THETA_C)
354
+ assert_equal(FRONT_FACE, face)
355
+ assert_in_delta( 1.0-3*NUDGE, x, DELTA)
356
+ assert_in_delta(-1.0, y, DELTA)
357
+ # Or a nudge eastward, and we're on the east face.
358
+ face, x, y = p.forward(PI/4+2*NUDGE, -THETA_C)
359
+ assert_equal(EAST_FACE, face)
360
+ assert_in_delta(-1.0+3*NUDGE, x, DELTA)
361
+ assert_in_delta(-1.0, y, DELTA)
362
+
363
+ # 21. φ = 3π/4, θ = -θc (35.26°S 135°E) should map to the
364
+ # down-right point of the east face, or the down-left point of the
365
+ # back face, or the down-right point of the bottom face. When
366
+ # coordinates are given exactly like that, this implementation
367
+ # maps to the bottom face.
368
+ face, x, y = p.forward(3*PI/4, -THETA_C)
369
+ assert_equal(BOTTOM_FACE, face)
370
+ assert_in_delta( 1.0, x, DELTA)
371
+ assert_in_delta(-1.0, y, DELTA)
372
+ # A nudge westward, though, and we're on the east face.
373
+ face, x, y = p.forward(3*PI/4-2*NUDGE, -THETA_C)
374
+ assert_equal(EAST_FACE, face)
375
+ assert_in_delta( 1.0-3*NUDGE, x, DELTA)
376
+ assert_in_delta(-1.0, y, DELTA)
377
+ # Or a nudge eastward, and we're on the back face.
378
+ face, x, y = p.forward(3*PI/4+2*NUDGE, -THETA_C)
379
+ assert_equal(BACK_FACE, face)
380
+ assert_in_delta(-1.0+3*NUDGE, x, DELTA)
381
+ assert_in_delta(-1.0, y, DELTA)
382
+
383
+ # Then the four middle points of the edges of the bottom cube
384
+ # face. These are all at θ = -π/4 (45°S).
385
+
386
+ # 22. φ = -π/2, θ = -π/4 (45°S 90°W) should map to the down point
387
+ # of the west face, or the left point of the bottom face. When
388
+ # coordinates are given exactly like that, this implementation
389
+ # maps to the west face.
390
+ face, x, y = p.forward(-PI/2, -PI/4)
391
+ assert_equal(WEST_FACE, face)
392
+ assert_in_delta( 0.0, x, DELTA)
393
+ assert_in_delta(-1.0, y, DELTA)
394
+ # A nudge southward, though, and we're on the bottom face.
395
+ face, x, y = p.forward(-PI/2, -PI/4-NUDGE)
396
+ assert_equal(BOTTOM_FACE, face)
397
+ assert_in_delta(-1.0+NUDGE, x, DELTA)
398
+ assert_in_delta( 0.0, y, DELTA)
399
+
400
+ # 23. φ = 0, θ = -π/4 (45°S 0°) should map to the down point of
401
+ # the front face, or the up point of the bottom face. When
402
+ # coordinates are given exactly like that, this implementation
403
+ # maps to the FRONT face.
404
+ face, x, y = p.forward(0.0, -PI/4)
405
+ assert_equal(FRONT_FACE, face)
406
+ assert_in_delta( 0.0, x, DELTA)
407
+ assert_in_delta(-1.0, y, DELTA)
408
+ # A nudge southward, though, and we're on the bottom face.
409
+ face, x, y = p.forward(0.0, -PI/4-NUDGE)
410
+ assert_equal(BOTTOM_FACE, face)
411
+ assert_in_delta( 0.0, x, DELTA)
412
+ assert_in_delta( 1.0-NUDGE, y, DELTA)
413
+
414
+ # 24. φ = π/2, θ = -π/4 (45°S 90°E) should map to the down point
415
+ # of the east face, or the right point of the bottom face. When
416
+ # coordinates are given exactly like that, this implementation
417
+ # maps to the east face.
418
+ face, x, y = p.forward(PI/2, -PI/4)
419
+ assert_equal(EAST_FACE, face)
420
+ assert_in_delta( 0.0, x, DELTA)
421
+ assert_in_delta(-1.0, y, DELTA)
422
+ # A nudge southward, though, and we're on the bottom face.
423
+ face, x, y = p.forward(PI/2, -PI/4-NUDGE)
424
+ assert_equal(BOTTOM_FACE, face)
425
+ assert_in_delta( 1.0-NUDGE, x, DELTA)
426
+ assert_in_delta( 0.0, y, DELTA)
427
+
428
+ # 25. φ = π, θ = -π/4 (45°S 180°) should map to the down point of
429
+ # the back face, or the down point of the bottom face. When
430
+ # coordinates are given exactly like that, this implementation
431
+ # maps to the back face.
432
+ face, x, y = p.forward(PI, -PI/4)
433
+ assert_equal(BACK_FACE, face)
434
+ assert_in_delta( 0.0, x, DELTA)
435
+ assert_in_delta(-1.0, y, DELTA)
436
+ # A nudge southward, though, and we're on the bottom face.
437
+ face, x, y = p.forward(PI, -PI/4-NUDGE)
438
+ assert_equal(BOTTOM_FACE, face)
439
+ assert_in_delta( 0.0, x, DELTA)
440
+ assert_in_delta(-1.0+NUDGE, y, DELTA)
441
+
442
+ # Finally, the one south pole.
443
+
444
+ # 26. θ = -π/2 (90°S ) should map to the centre point of the
445
+ # bottom face. φ is irrelevant here.
446
+
447
+ face, x, y = p.forward(0.0, -PI/2)
448
+ assert_equal(BOTTOM_FACE, face)
449
+ assert_in_delta( 0.0, x, DELTA)
450
+ assert_in_delta( 0.0, y, DELTA)
451
+
452
+ end # test_forward_mapping
453
+
454
+
455
+ def test_inverse_mapping
456
+ p = self.projection
457
+
458
+ # We'll test nine points on each cube face: the four corners, the
459
+ # four centres of each edge, and the centre of the face. Please
460
+ # note that, except for the face centres, the mappings are not
461
+ # necessarily reversible. This is because, for any point that is
462
+ # shared amongst two or three faces, the forward projection may
463
+ # choose to return a mapping to any of the shared faces. See all
464
+ # those nudges in the forward mapping tests above.
465
+
466
+ # 1. The top face.
467
+
468
+ # The up-left point of the top face should map to
469
+ # φ = -3π/4, θ = θc.
470
+ phi, theta = p.inverse(TOP_FACE, -1.0, 1.0)
471
+ assert_in_delta(-3*PI/4, phi, DELTA)
472
+ assert_in_delta(THETA_C, theta, DELTA)
473
+
474
+ # The up point of the top face should map to
475
+ # φ = π, θ = π/4.
476
+ phi, theta = p.inverse(TOP_FACE, 0.0, 1.0)
477
+ assert_in_delta(PI, phi, DELTA)
478
+ assert_in_delta(PI/4, theta, DELTA)
479
+
480
+ # The up-right point of the top face should map to
481
+ # φ = 3π/4, θ = θc.
482
+ phi, theta = p.inverse(TOP_FACE, 1.0, 1.0)
483
+ assert_in_delta(3*PI/4, phi, DELTA)
484
+ assert_in_delta(THETA_C, theta, DELTA)
485
+
486
+ # The left point of the top face should map to
487
+ # φ = -π/2, θ = π/4.
488
+ phi, theta = p.inverse(TOP_FACE, -1.0, 0.0)
489
+ assert_in_delta(-PI/2, phi, DELTA)
490
+ assert_in_delta(PI/4, theta, DELTA)
491
+
492
+ # The centre point of the top face should map to
493
+ # θ = π/2, with φ being irrelevant.
494
+ phi, theta = p.inverse(TOP_FACE, 0.0, 0.0)
495
+ assert_in_delta(PI/2, theta, DELTA)
496
+ # So here, any value is correct. This implementation returns
497
+ # PI, though, for some reason, and we'll watch for changes.
498
+ assert_in_delta(PI, phi, DELTA)
499
+
500
+ # The right point of the top face should map to
501
+ # φ = π/2, θ = π/4.
502
+ phi, theta = p.inverse(TOP_FACE, 1.0, 0.0)
503
+ assert_in_delta(PI/2, phi, DELTA)
504
+ assert_in_delta(PI/4, theta, DELTA)
505
+
506
+ # The down-left point of the top face should map to
507
+ # φ = -π/4, θ = θc.
508
+ phi, theta = p.inverse(TOP_FACE, -1.0, -1.0)
509
+ assert_in_delta(-PI/4, phi, DELTA)
510
+ assert_in_delta(THETA_C, theta, DELTA)
511
+
512
+ # The down point of the top face should map to
513
+ # φ = 0, θ = π/4.
514
+ phi, theta = p.inverse(TOP_FACE, 0.0, -1.0)
515
+ assert_in_delta(0.0, phi, DELTA)
516
+ assert_in_delta(PI/4, theta, DELTA)
517
+
518
+ # The down-right point of the top face should map to
519
+ # φ = π/4, θ = θc.
520
+ phi, theta = p.inverse(TOP_FACE, 1.0, -1.0)
521
+ assert_in_delta(PI/4, phi, DELTA)
522
+ assert_in_delta(THETA_C, theta, DELTA)
523
+
524
+ # 2. The front face.
525
+
526
+ # The up-left point of the front face should map to
527
+ # φ = -π/4, θ = θc.
528
+ phi, theta = p.inverse(FRONT_FACE, -1.0, 1.0)
529
+ assert_in_delta(-PI/4, phi, DELTA)
530
+ assert_in_delta(THETA_C, theta, DELTA)
531
+
532
+ # The up point of the front face should map to
533
+ # φ = 0, θ = π/4.
534
+ phi, theta = p.inverse(FRONT_FACE, 0.0, 1.0)
535
+ assert_in_delta(0.0, phi, DELTA)
536
+ assert_in_delta(PI/4, theta, DELTA)
537
+
538
+ # The up-right point of the front face should map to
539
+ # φ = π/4, θ = θc.
540
+ phi, theta = p.inverse(FRONT_FACE, 1.0, 1.0)
541
+ assert_in_delta(PI/4, phi, DELTA)
542
+ assert_in_delta(THETA_C, theta, DELTA)
543
+
544
+ # The left point of the front face should map to
545
+ # φ = -π/4, θ = 0.
546
+ phi, theta = p.inverse(FRONT_FACE, -1.0, 0.0)
547
+ assert_in_delta(-PI/4, phi, DELTA)
548
+ assert_in_delta(0.0, theta, DELTA)
549
+
550
+ # The centre point of the front face should map to
551
+ # φ = 0, θ = 0.
552
+ phi, theta = p.inverse(FRONT_FACE, 0.0, 0.0)
553
+ assert_in_delta(0.0, phi, DELTA)
554
+ assert_in_delta(0.0, theta, DELTA)
555
+
556
+ # The right point of the front face should map to
557
+ # φ = π/4, θ = 0.
558
+ phi, theta = p.inverse(FRONT_FACE, 1.0, 0.0)
559
+ assert_in_delta(PI/4, phi, DELTA)
560
+ assert_in_delta(0.0, theta, DELTA)
561
+
562
+ # The down-left point of the front face should map to
563
+ # φ = -π/4, θ = -θc.
564
+ phi, theta = p.inverse(FRONT_FACE, -1.0, -1.0)
565
+ assert_in_delta(-PI/4, phi, DELTA)
566
+ assert_in_delta(-THETA_C, theta, DELTA)
567
+
568
+ # The down point of the front face should map to
569
+ # φ = 0, θ = -π/4.
570
+ phi, theta = p.inverse(FRONT_FACE, 0.0, -1.0)
571
+ assert_in_delta(0.0, phi, DELTA)
572
+ assert_in_delta(-PI/4, theta, DELTA)
573
+
574
+ # The down-right point of the front face should map to
575
+ # φ = π/4, θ = -θc.
576
+ phi, theta = p.inverse(FRONT_FACE, 1.0, -1.0)
577
+ assert_in_delta(PI/4, phi, DELTA)
578
+ assert_in_delta(-THETA_C, theta, DELTA)
579
+
580
+ # 3. The east face.
581
+
582
+ # The up-left point of the east face should map to
583
+ # φ = π/4, θ = θc.
584
+ phi, theta = p.inverse(EAST_FACE, -1.0, 1.0)
585
+ assert_in_delta(PI/4, phi, DELTA)
586
+ assert_in_delta(THETA_C, theta, DELTA)
587
+
588
+ # The up point of the east face should map to
589
+ # φ = π/2, θ = π/4.
590
+ phi, theta = p.inverse(EAST_FACE, 0.0, 1.0)
591
+ assert_in_delta(PI/2, phi, DELTA)
592
+ assert_in_delta(PI/4, theta, DELTA)
593
+
594
+ # The up-right point of the east face should map to
595
+ # φ = 3π/4, θ = θc.
596
+ phi, theta = p.inverse(EAST_FACE, 1.0, 1.0)
597
+ assert_in_delta(3*PI/4, phi, DELTA)
598
+ assert_in_delta(THETA_C, theta, DELTA)
599
+
600
+ # The left point of the east face should map to
601
+ # φ = π/4, θ = 0.
602
+ phi, theta = p.inverse(EAST_FACE, -1.0, 0.0)
603
+ assert_in_delta(PI/4, phi, DELTA)
604
+ assert_in_delta(0.0, theta, DELTA)
605
+
606
+ # The centre point of the east face should map to
607
+ # φ = π/2, θ = 0.
608
+ phi, theta = p.inverse(EAST_FACE, 0.0, 0.0)
609
+ assert_in_delta(PI/2, phi, DELTA)
610
+ assert_in_delta(0.0, theta, DELTA)
611
+
612
+ # The right point of the east face should map to
613
+ # φ = 3π/4, θ = 0.
614
+ phi, theta = p.inverse(EAST_FACE, 1.0, 0.0)
615
+ assert_in_delta(3*PI/4, phi, DELTA)
616
+ assert_in_delta(0.0, theta, DELTA)
617
+
618
+ # The down-left point of the east face should map to
619
+ # φ = π/4, θ = -θc.
620
+ phi, theta = p.inverse(EAST_FACE, -1.0, -1.0)
621
+ assert_in_delta(PI/4, phi, DELTA)
622
+ assert_in_delta(-THETA_C, theta, DELTA)
623
+
624
+ # The down point of the east face should map to
625
+ # φ = π/2, θ = -π/4.
626
+ phi, theta = p.inverse(EAST_FACE, 0.0, -1.0)
627
+ assert_in_delta(PI/2, phi, DELTA)
628
+ assert_in_delta(-PI/4, theta, DELTA)
629
+
630
+ # The down-right point of the east face should map to
631
+ # φ = 3π/4, θ = -θc.
632
+ phi, theta = p.inverse(EAST_FACE, 1.0, -1.0)
633
+ assert_in_delta(3*PI/4, phi, DELTA)
634
+ assert_in_delta(-THETA_C, theta, DELTA)
635
+
636
+ # 4. The back face.
637
+
638
+ # The up-left point of the back face should map to
639
+ # φ = 3π/4, θ = θc.
640
+ phi, theta = p.inverse(BACK_FACE, -1.0, 1.0)
641
+ assert_in_delta(3*PI/4, phi, DELTA)
642
+ assert_in_delta(THETA_C, theta, DELTA)
643
+
644
+ # The up point of the back face should map to
645
+ # φ = π, θ = π/4.
646
+ # Note the implementation returns φ = -π, which is also correct.
647
+ phi, theta = p.inverse(BACK_FACE, 0.0, 1.0)
648
+ assert_in_delta(-PI, phi, DELTA)
649
+ assert_in_delta(PI/4, theta, DELTA)
650
+
651
+ # The up-right point of the back face should map to
652
+ # φ = -3π/4, θ = θc.
653
+ phi, theta = p.inverse(BACK_FACE, 1.0, 1.0)
654
+ assert_in_delta(-3*PI/4, phi, DELTA)
655
+ assert_in_delta(THETA_C, theta, DELTA)
656
+
657
+ # The left point of the back face should map to
658
+ # φ = 3π/4, θ = 0.
659
+ phi, theta = p.inverse(BACK_FACE, -1.0, 0.0)
660
+ assert_in_delta(3*PI/4, phi, DELTA)
661
+ assert_in_delta(0.0, theta, DELTA)
662
+
663
+ # The centre point of the back face should map to
664
+ # φ = π, θ = 0.
665
+ # Note the implementation returns φ = -π, which is also correct.
666
+ phi, theta = p.inverse(BACK_FACE, 0.0, 0.0)
667
+ assert_in_delta(-PI, phi, DELTA)
668
+ assert_in_delta(0.0, theta, DELTA)
669
+
670
+ # The right point of the back face should map to
671
+ # φ = -3π/4, θ = 0.
672
+ phi, theta = p.inverse(BACK_FACE, 1.0, 0.0)
673
+ assert_in_delta(-3*PI/4, phi, DELTA)
674
+ assert_in_delta(0.0, theta, DELTA)
675
+
676
+ # The down-left point of the back face should map to
677
+ # φ = 3π/4, θ = -θc.
678
+ phi, theta = p.inverse(BACK_FACE, -1.0, -1.0)
679
+ assert_in_delta(3*PI/4, phi, DELTA)
680
+ assert_in_delta(-THETA_C, theta, DELTA)
681
+
682
+ # The down point of the back face should map to
683
+ # φ = π, θ = -π/4.
684
+ # Note the implementation returns φ = -π, which is also correct.
685
+ phi, theta = p.inverse(BACK_FACE, 0.0, -1.0)
686
+ assert_in_delta(-PI, phi, DELTA)
687
+ assert_in_delta(-PI/4, theta, DELTA)
688
+
689
+ # The down-right point of the back face should map to
690
+ # φ = -3π/4, θ = -θc.
691
+ phi, theta = p.inverse(BACK_FACE, 1.0, -1.0)
692
+ assert_in_delta(-3*PI/4, phi, DELTA)
693
+ assert_in_delta(-THETA_C, theta, DELTA)
694
+
695
+ # 5. The west face.
696
+
697
+ # The up-left point of the west face should map to
698
+ # φ = -3π/4, θ = θc.
699
+ phi, theta = p.inverse(WEST_FACE, -1.0, 1.0)
700
+ assert_in_delta(-3*PI/4, phi, DELTA)
701
+ assert_in_delta(THETA_C, theta, DELTA)
702
+
703
+ # The up point of the west face should map to
704
+ # φ = -π/2, θ = π/4.
705
+ phi, theta = p.inverse(WEST_FACE, 0.0, 1.0)
706
+ assert_in_delta(-PI/2, phi, DELTA)
707
+ assert_in_delta(PI/4, theta, DELTA)
708
+
709
+ # The up-right point of the west face should map to
710
+ # φ = -π/4, θ = θc.
711
+ phi, theta = p.inverse(WEST_FACE, 1.0, 1.0)
712
+ assert_in_delta(-PI/4, phi, DELTA)
713
+ assert_in_delta(THETA_C, theta, DELTA)
714
+
715
+ # The left point of the west face should map to
716
+ # φ = -3π/4, θ = 0.
717
+ phi, theta = p.inverse(WEST_FACE, -1.0, 0.0)
718
+ assert_in_delta(-3*PI/4, phi, DELTA)
719
+ assert_in_delta(0.0, theta, DELTA)
720
+
721
+ # The centre point of the west face should map to
722
+ # φ = -π/2, θ = 0.
723
+ phi, theta = p.inverse(WEST_FACE, 0.0, 0.0)
724
+ assert_in_delta(-PI/2, phi, DELTA)
725
+ assert_in_delta(0.0, theta, DELTA)
726
+
727
+ # The right point of the west face should map to
728
+ # φ = -π/4, θ = 0.
729
+ phi, theta = p.inverse(WEST_FACE, 1.0, 0.0)
730
+ assert_in_delta(-PI/4, phi, DELTA)
731
+ assert_in_delta(0.0, theta, DELTA)
732
+
733
+ # The down-left point of the west face should map to
734
+ # φ = -3π/4, θ = -θc.
735
+ phi, theta = p.inverse(WEST_FACE, -1.0, -1.0)
736
+ assert_in_delta(-3*PI/4, phi, DELTA)
737
+ assert_in_delta(-THETA_C, theta, DELTA)
738
+
739
+ # The down point of the west face should map to
740
+ # φ = -π/2, θ = -π/4.
741
+ phi, theta = p.inverse(WEST_FACE, 0.0, -1.0)
742
+ assert_in_delta(-PI/2, phi, DELTA)
743
+ assert_in_delta(-PI/4, theta, DELTA)
744
+
745
+ # The down-right point of the west face should map to
746
+ # φ = -π/4, θ = -θc.
747
+ phi, theta = p.inverse(WEST_FACE, 1.0, -1.0)
748
+ assert_in_delta(-PI/4, phi, DELTA)
749
+ assert_in_delta(-THETA_C, theta, DELTA)
750
+
751
+ # 6. The bottom face.
752
+
753
+ # The up-left point of the bottom face should map to
754
+ # φ = -π/4, θ = -θc.
755
+ phi, theta = p.inverse(BOTTOM_FACE, -1.0, 1.0)
756
+ assert_in_delta(-PI/4, phi, DELTA)
757
+ assert_in_delta(-THETA_C, theta, DELTA)
758
+
759
+ # The up point of the bottom face should map to
760
+ # φ = 0, θ = -π/4.
761
+ phi, theta = p.inverse(BOTTOM_FACE, 0.0, 1.0)
762
+ assert_in_delta(0.0, phi, DELTA)
763
+ assert_in_delta(-PI/4, theta, DELTA)
764
+
765
+ # The up-right point of the bottom face should map to
766
+ # φ = π/4, θ = -θc.
767
+ phi, theta = p.inverse(BOTTOM_FACE, 1.0, 1.0)
768
+ assert_in_delta(PI/4, phi, DELTA)
769
+ assert_in_delta(-THETA_C, theta, DELTA)
770
+
771
+ # The left point of the bottom face should map to
772
+ # φ = -π/2, θ = -π/4.
773
+ phi, theta = p.inverse(BOTTOM_FACE, -1.0, 0.0)
774
+ assert_in_delta(-PI/2, phi, DELTA)
775
+ assert_in_delta(-PI/4, theta, DELTA)
776
+
777
+ # The centre point of the bottom face should map to
778
+ # θ = -π/2, with φ being irrelevant.
779
+ phi, theta = p.inverse(BOTTOM_FACE, 0.0, 0.0)
780
+ assert_in_delta(-PI/2, theta, DELTA)
781
+ # So here, any value is correct. This implementation returns
782
+ # zero, though, for some reason. And we'll watch for changes.
783
+ assert_in_delta(0.0, phi, DELTA)
784
+
785
+ # The right point of the bottom face should map to
786
+ # φ = π/2, θ = -π/4.
787
+ phi, theta = p.inverse(BOTTOM_FACE, 1.0, 0.0)
788
+ assert_in_delta(PI/2, phi, DELTA)
789
+ assert_in_delta(-PI/4, theta, DELTA)
790
+
791
+ # The down-left point of the bottom face should map to
792
+ # φ = -3π/4, θ = -θc.
793
+ phi, theta = p.inverse(BOTTOM_FACE, -1.0, -1.0)
794
+ assert_in_delta(-3*PI/4, phi, DELTA)
795
+ assert_in_delta(-THETA_C, theta, DELTA)
796
+
797
+ # The down point of the bottom face should map to
798
+ # φ = π, θ = -π/4.
799
+ phi, theta = p.inverse(BOTTOM_FACE, 0.0, -1.0)
800
+ assert_in_delta(PI, phi, DELTA)
801
+ assert_in_delta(-PI/4, theta, DELTA)
802
+
803
+ # The down-right point of the bottom face should map to
804
+ # φ = 3π/4, θ = -θc.
805
+ phi, theta = p.inverse(BOTTOM_FACE, 1.0, -1.0)
806
+ assert_in_delta(3*PI/4, phi, DELTA)
807
+ assert_in_delta(-THETA_C, theta, DELTA)
808
+
809
+ end # test_inverse_mapping
810
+
811
+ end # module MappingTests
812
+
813
+ class TangentialMappingTest < Test::Unit::TestCase
814
+ include MappingTests
815
+
816
+ def projection
817
+ QuadSphere::Tangential
818
+ end
819
+ end
820
+
821
+ class CSCMappingTest < Test::Unit::TestCase
822
+ include MappingTests
823
+
824
+ def projection
825
+ QuadSphere::CSC
826
+ end
827
+
828
+ def test_distortion_closure
829
+ expected_error = 2.4e-4
830
+
831
+ (-0.99).step(0.99, 0.01) do |psi|
832
+ (-0.99).step(0.99, 0.01) do |chi|
833
+ x = projection.forward_distort(chi, psi)
834
+ y = projection.forward_distort(psi, chi)
835
+ chi1 = projection.inverse_distort(x,y)
836
+ assert_in_delta(0.0, chi1-chi, expected_error)
837
+ psi1 = projection.inverse_distort(y,x)
838
+ error = Math::sqrt((chi1-chi)**2 + (psi1-psi)**2)
839
+ assert_in_delta(0.0, error, expected_error)
840
+ end
841
+ end
842
+ end
843
+
844
+ def test_closure
845
+ error = 3.0e-4
846
+
847
+ 100.times do |row|
848
+ 100.times do |col|
849
+ x = 0.005+col/100.0
850
+ y = 0.005+row/100.0
851
+ phi, theta = projection.inverse(WEST_FACE, x, y)
852
+ f1, x1, y1 = projection.forward(phi, theta)
853
+ assert_equal(WEST_FACE, f1)
854
+ assert_in_delta(x, x1, error)
855
+ assert_in_delta(y, y1, error)
856
+ end
857
+ end
858
+ end
859
+ end
860
+