quad_sphere 0.9.0 → 1.0.0

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