quad_sphere 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,236 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require 'quad_sphere'
4
+ require 'quad_sphere/tangential'
5
+
6
+ module QuadSphere
7
+
8
+ # Implements the quadrilateralised spherical cube projection.
9
+ #
10
+ # The quadrilateralised spherical cube projection, or "Quad Sphere"
11
+ # or "COBE Sky Cube" (CSC), applies a further curvilinear
12
+ # transformation to the tangential spherical cube projection, to
13
+ # make it approximately equal-area. This is useful for storing
14
+ # spherical data in a raster: then you can integrate directly on the
15
+ # cube face planes, without having to map your data back to the
16
+ # original sphere.
17
+ #
18
+ # This projection has been used extensively by the Cosmic Background
19
+ # Explorer project (COBE), and this implementation _should_ match
20
+ # results from their software. This can't be guaranteed, though, as
21
+ # this author hasn't pursued finding and testing raw COBE data.
22
+ # There's a to-do, right there.
23
+ #
24
+ # Mind that this implements only the spherical projection, not the
25
+ # scheme for storing pixels along a Z-order curve, that is described
26
+ # in length as part of the COBE data format. We don't deal with
27
+ # pixels at all.
28
+ #
29
+ # Also: this implements the projection described by Chan & O'Neill,
30
+ # 1975, _not_ the one by Laubscher & O'Neill, 1976, which is
31
+ # non-differentiable along the diagonals.
32
+ #
33
+ # As Chan's original report is not readily available, this
34
+ # implementation is based on formulae found in FITS WCS documents.
35
+ # Specifically: "Representations of celestial coordinates in FITS
36
+ # (Paper II)", Calabretta, M. R., and Greisen, E. W., <i>Astronomy &
37
+ # Astrophysics</i>, 395, 1077-1122, 2002. This is available from
38
+ # the FITS Support Office at NASA/GSFC; see reference below. And
39
+ # mind the possible implications of this on the accuracy of the
40
+ # transformation, discussed in the documentation of
41
+ # {forward_distort}.
42
+ #
43
+ # @see http://lambda.gsfc.nasa.gov/product/cobe/skymap_info_new.cfm
44
+ # COBE Quadrilateralized Spherical Cube at NASA's data center for
45
+ # Cosmic Microwave Background research.
46
+ # @see http://fits.gsfc.nasa.gov/fits_wcs.html
47
+ # FITS World Coordinate System Documents at NASA/GSFC.
48
+ # @see http://en.wikipedia.org/wiki/Quadrilateralized_spherical_cube
49
+ # Quadrilateralized spherical cube at Wikipedia.
50
+ # @see http://gis.stackexchange.com/questions/40957/is-the-quadrilateralized-spherical-cube-map-projection-the-same-as-snyders-cubi
51
+ # Remarks by (allegedly) Kenneth Chan regarding his and
52
+ # Laubscher's work, at the GIS StackExchange.
53
+ # @see http://www.progonos.com/furuti/MapProj/Normal/ProjPoly/projPoly2.html
54
+ # Pretty maps at Carlos A. Furuti's website.
55
+ #
56
+ # @author César Rincón
57
+ module CSC
58
+
59
+ # Computes the projection of a point on the surface of the sphere,
60
+ # given in spherical coordinates (φ,θ), to a point of cartesian
61
+ # coordinates (x,y) on one of the six cube faces.
62
+ #
63
+ # @param phi (Float) the φ angle in radians, from -π to π (or 0 to
64
+ # to 2π, if you like). This is the azimuth, or longitude
65
+ # (spherical, not geodetic).
66
+ # @param theta (Float) the θ angle in radians, from -π/2 to π/2.
67
+ # This is the elevation, or latitude (spherical, not geodetic).
68
+ #
69
+ # @return (Array) an array of three elements: the identifier of
70
+ # the face (see constants in {QuadSphere}), the _x_ coordinate
71
+ # of the projected point, and the _y_ coordinate of the
72
+ # projected point. Both coordinates will be in the range -1 to
73
+ # 1.
74
+ #
75
+ # @see inverse
76
+ def self.forward(phi, theta)
77
+ face, chi, psi = Tangential.forward(phi, theta)
78
+ [ face, forward_distort(chi,psi), forward_distort(psi,chi) ]
79
+ end
80
+
81
+ # Computes the projection of a point at cartesian coordinates
82
+ # (x,y) on one of the six cube faces, to a point at spherical
83
+ # coordinates (φ,θ) on the surface of the sphere.
84
+ #
85
+ # Note that, while the projection is reversible for points within
86
+ # each of the cube faces, it is not necessarily so for points
87
+ # located exactly on the edges. This is because points on the
88
+ # edges of the cube are shared amongst two or even three faces,
89
+ # and the forward projection may return an alternative mapping to
90
+ # a neighbouring face.
91
+ #
92
+ # @param face (Integer) the identifier of the cube face; see
93
+ # constants in {QuadSphere}.
94
+ # @param x (Float) the _x_ coordinate of the point within the
95
+ # face, from -1.0 to 1.0.
96
+ # @param y (Float) the _y_ coordinate of the point within the
97
+ # face, from -1.0 to 1.0.
98
+ #
99
+ # @return (Array) an array of two elements: the φ angle in radians
100
+ # (azimuth or longitude - spherical, not geodetic), from -π to
101
+ # π; and the θ angle in radians, from -π/2 to π/2 (elevation or
102
+ # latitude - spherical, not geodetic).
103
+ #
104
+ # @see forward
105
+ def self.inverse(face, x, y)
106
+ chi = inverse_distort(x,y)
107
+ psi = inverse_distort(y,x)
108
+ Tangential.inverse(face, chi, psi)
109
+ end
110
+
111
+ # This performs the forward curvilinear transformation of
112
+ # coordinates on a cube face, from the basic tangential to the CSC
113
+ # projection. Specifically, the function computes the adjusted
114
+ # cartesian coordinate _x_ from the original coordinates χ,ψ
115
+ # produced by tangential projection. To compute the _y_
116
+ # coordinate, evaluate again for ψ,χ.
117
+ #
118
+ # This is not an exact transformation, and its accuracy is further
119
+ # impaired by the fact that the math foundation behind the CSC
120
+ # projection is not publicly available - the closest we have to a
121
+ # "gold standard" is the polynomial approximation put forward in
122
+ # the Calabretta paper. Equivalent calculations appear almost
123
+ # verbatim in published COBE Data Analysis Software, available at
124
+ # http://lambda.gsfc.nasa.gov/product/cobe/cgis.cfm
125
+ # (brush up on your FORTRAN and download +cgis-for.tar.gz+).
126
+ #
127
+ # Of interest: a user of the the GIS StackExchange, allegedly
128
+ # Kenneth Chan, the original designer of this projection, had this
129
+ # to say on this particular topic:
130
+ #
131
+ # "Alex, When you get my report, you will notice that my direct
132
+ # and inverse mappings are accurate to 4 or 5 significant
133
+ # figures. Laubscher changed my coefficients in his report. A
134
+ # comparison reveals that the transformations given in
135
+ # Calabretta's paper do not bear any resemblance to mine. The
136
+ # coefficients are not from my original report but are based on
137
+ # Laubscher's report. Because of this, Calabretta states that
138
+ # my transformations are accurate to 1%. Moreover, it is also
139
+ # stated that the code disagrees with the formulas. Please bear
140
+ # this in mind. My advice is to go by the report in the original
141
+ # for [...]"
142
+ #
143
+ # (link to the thread in the module doc above)
144
+ #
145
+ # So our coefficients may not be Chan's - ours are indeed the ones
146
+ # in the Calabretta paper. We are trying to contact Ken Chan for
147
+ # clarification... and/or a copy of his 1975 paper, that would be
148
+ # brilliant. On the other hand, this is the exact same
149
+ # computation done by actual COBE software, apparently, so it
150
+ # probably matches their results. Go figure.
151
+ #
152
+ # In our own tests of this implementation, when transforming (χ,ψ)
153
+ # to (x,y) and back to (χ',ψ'), where the domain of all four
154
+ # variables is -1.0 to 1.0, we've found the mean closure error
155
+ # (mean distance from point (χ,ψ) to point (χ',ψ')) to be
156
+ # 4.152E-05, with a standard deviation of 3.72E-05, and a maximum
157
+ # error of 2.33E-04 in small regions around the origin and
158
+ # corners.
159
+ #
160
+ # To put this in perspective: if you were mapping the Earth, then
161
+ # the domain of this transformation is a cube face, then the mean
162
+ # closure error would be around 415 metres, with a standard
163
+ # deviation of 372 m, and small zones of high error near the cube
164
+ # face centres where it can get as bad as 2.3 km (very very quick
165
+ # approximation there, don't quote me on those figures). So aye,
166
+ # we could use more precision.
167
+ #
168
+ # There is code in the +extras+ directory to compute the error
169
+ # distribution, and even generate a picture showing the areas of
170
+ # low and high error on the plane. Peek there if you're curious.
171
+ #
172
+ # @see inverse_distort
173
+ def self.forward_distort(chi, psi)
174
+ chi2 = chi**2
175
+ chi3 = chi**3
176
+ psi2 = psi**2
177
+ omchi2 = 1.0 - chi2
178
+ chi*(1.37484847732 - 0.37484847732*chi2) +
179
+ chi*psi2*omchi2*(-0.13161671474 +
180
+ 0.136486206721*chi2 +
181
+ (1.0 - psi2) *
182
+ (0.141189631152 +
183
+ psi2*(-0.281528535557 + 0.106959469314*psi2) +
184
+ chi2*(0.0809701286525 +
185
+ 0.15384112876*psi2 -
186
+ 0.178251207466*chi2))) +
187
+ chi3*omchi2*(-0.159596235474 -
188
+ (omchi2 * (0.0759196200467 - 0.0217762490699*chi2)))
189
+ end
190
+
191
+ # This performs the inverse curvilinear transformation of
192
+ # coordinates on a cube face, from the CSC to the basic tangential
193
+ # projection. Specifically, the function computes the original
194
+ # cartesian coordinate χ produced by tangential projection, from
195
+ # the adjusted coordinates x,y. To compute the coordinate ψ,
196
+ # evaluate again for y,x.
197
+ #
198
+ # @see forward_distort Notes regarding accuracy in the
199
+ # documentation of forward_distort.
200
+ def self.inverse_distort(x, y)
201
+ # This is the sum Σ, from j=0 to 6, of the sum Σ, from i=0 to 6-j,
202
+ # of Pij * (X**(2*i)) * (Y**(2*j))
203
+
204
+ # We unroll.
205
+ x2 = x*x
206
+ x4 = x**4
207
+ x6 = x**6
208
+ x8 = x**8
209
+ x10 = x**10
210
+ x12 = x**12
211
+ y2 = y*y
212
+ y4 = y**4
213
+ y6 = y**6
214
+ y8 = y**8
215
+ y10 = y**10
216
+ y12 = y**12
217
+
218
+ x + x*(1 - x2) *
219
+ (-0.27292696 - 0.07629969 * x2 -
220
+ 0.22797056 * x4 + 0.54852384 * x6 -
221
+ 0.62930065 * x8 + 0.25795794 * x10 +
222
+ 0.02584375 * x12 - 0.02819452 * y2 -
223
+ 0.01471565 * x2 * y2 + 0.48051509 * x4 * y2 -
224
+ 1.74114454 * x6 * y2 + 1.71547508 * x8 * y2 -
225
+ 0.53022337 * x10 * y2 + 0.27058160 * y4 -
226
+ 0.56800938 * x2 * y4 + 0.30803317 * x4 * y4 +
227
+ 0.98938102 * x6 * y4 - 0.83180469 * x8 * y4 -
228
+ 0.60441560 * y6 + 1.50880086 * x2 * y6 -
229
+ 0.93678576 * x4 * y6 + 0.08693841 * x6 * y6 +
230
+ 0.93412077 * y8 - 1.41601920 * x2 * y8 +
231
+ 0.33887446 * x4 * y8 - 0.63915306 * y10 +
232
+ 0.52032238 * x2 * y10 + 0.14381585 * y12)
233
+ end
234
+
235
+ end # module CSC
236
+ end #module QuadSphere
@@ -0,0 +1,131 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ module QuadSphere
4
+
5
+ # Implements the tangential spherical cube projection.
6
+ #
7
+ # In this projection, points on the sphere are projected from the
8
+ # centre of the sphere onto the six faces of an inscribed cube.
9
+ # Thus, the projection consists of six planar faces, each of which
10
+ # is a gnomonic projection of a portion of the sphere.
11
+ #
12
+ # This module exists solely because the CSC projection is a
13
+ # distortion of the mapping produced by a tangential projection.
14
+ # Thus, the forward and inverse computations here are required, but
15
+ # they have been segregated on the oft chance that they could be
16
+ # useful on their own.
17
+ #
18
+ # @see http://en.wikipedia.org/wiki/Gnomonic_projection
19
+ # Gnomonic projection at Wikipedia.
20
+ #
21
+ # @author Cesar Rincon
22
+ module Tangential
23
+
24
+ # Information for each face. Faces are given in the order: top,
25
+ # front, left, back, right, bottom.
26
+ #
27
+ # These procedures rearrange the direction cosines as appropriate
28
+ # for the face we're projecting. The 3 values returned are ξ, η,
29
+ # and ζ.
30
+ FORWARD_PARAMETERS =
31
+ [ Proc.new{ |l, m, n| [ m, -l, n ] },
32
+ Proc.new{ |l, m, n| [ m, n, l ] },
33
+ Proc.new{ |l, m, n| [ -l, n, m ] },
34
+ Proc.new{ |l, m, n| [ -m, n, -l ] },
35
+ Proc.new{ |l, m, n| [ l, n, -m ] },
36
+ Proc.new{ |l, m, n| [ m, l, -n ] } ]
37
+ private_constant :FORWARD_PARAMETERS \
38
+ if self.respond_to?(:private_constant) # don't die for this in 1.8
39
+
40
+ # Computes the projection of a point on the surface of the sphere,
41
+ # given in spherical coordinates (φ,θ), to a point of cartesian
42
+ # coordinates (χ,ψ) on one of the six cube faces.
43
+ #
44
+ # @param phi (Float) the φ angle in radians, from -π to π (or 0 to
45
+ # to 2π, if you like). This is the azimuth, or longitude
46
+ # (spherical, not geodetic).
47
+ # @param theta (Float) the θ angle in radians, from -π/2 to π/2.
48
+ # This is the elevation, or latitude (spherical, not geodetic).
49
+ #
50
+ # @return (Array) an array of three elements: the identifier of
51
+ # the face (see constants in {QuadSphere}), the χ coordinate of
52
+ # the projected point, and the ψ coordinate of the projected
53
+ # point. Both coordinates will be in the range -1 to 1.
54
+ def self.forward(phi, theta)
55
+ # compute the direction cosines
56
+ l = Math::cos(theta) * Math::cos(phi)
57
+ m = Math::cos(theta) * Math::sin(phi)
58
+ n = Math::sin(theta)
59
+
60
+ # identify the face, and adjust our parameters.
61
+ max, face = nil, -1
62
+ [ n, l, m, -l, -m, -n ].each_with_index do |v, i|
63
+ max, face = v, i if max.nil? || v > max
64
+ end
65
+
66
+ xi, eta, zeta = FORWARD_PARAMETERS[face].call(l,m,n)
67
+
68
+ # Compute χ and ψ.
69
+ # XXX - This will blow up if ζ is zero... can it happen?
70
+ chi = xi / zeta
71
+ psi = eta / zeta
72
+
73
+ # Out of curiosity: why does Calabretta do this?
74
+ # x = phi_c + Math::PI/4 * chi
75
+ # y = theta_c + Math::PI/4 * psi
76
+
77
+ [face,chi,psi]
78
+ end
79
+
80
+ # Information for each face. Faces are given in the order:
81
+ # top, front, left, back, right, bottom.
82
+ #
83
+ # These procedures return the direction cosines:
84
+ # 1. l (cos(θ)*cos(φ))
85
+ # 2. m (cos(θ)*sin(φ))
86
+ # 3. n (sin(θ))
87
+ INVERSE_PARAMETERS =
88
+ [ Proc.new{ |xi, eta, zeta| [ -eta, xi, zeta ] },
89
+ Proc.new{ |xi, eta, zeta| [ zeta, xi, eta ] },
90
+ Proc.new{ |xi, eta, zeta| [ -xi, zeta, eta ] },
91
+ Proc.new{ |xi, eta, zeta| [ -zeta, -xi, eta ] },
92
+ Proc.new{ |xi, eta, zeta| [ xi, -zeta, eta ] },
93
+ Proc.new{ |xi, eta, zeta| [ eta, xi, -zeta ] } ]
94
+ private_constant :INVERSE_PARAMETERS \
95
+ if self.respond_to?(:private_constant) # don't die for this in 1.8
96
+
97
+ # Computes the projection of a point at cartesian coordinates
98
+ # (χ,ψ) on one of the six cube faces, to a point at spherical
99
+ # coordinates (φ,θ) on the surface of the sphere.
100
+ #
101
+ # Note that, while the projection is reversible for points within
102
+ # each of the cube faces, it is not necessarily so for points
103
+ # located exactly on the edges. This is because points on the
104
+ # edges of the cube are shared amongst two or even three faces,
105
+ # and the forward projection may return an alternative mapping to
106
+ # a neighbouring face.
107
+ #
108
+ # @param face (Integer) the identifier of the cube face; see
109
+ # constants in {QuadSphere}.
110
+ # @param chi (Float) the χ coordinate of the point within the
111
+ # face, from -1.0 to 1.0.
112
+ # @param psi (Float) the ψ coordinate of the point within the
113
+ # face, from -1.0 to 1.0.
114
+ #
115
+ # @return (Array) an array of two elements: the φ angle in radians
116
+ # (azimuth or longitude - spherical, not geodetic), from -π to
117
+ # π; and the θ angle in radians, from -π/2 to π/2 (elevation or
118
+ # latitude - spherical, not geodetic).
119
+ def self.inverse(face, chi, psi)
120
+ zeta = 1.0 / Math.sqrt(1.0 + chi**2 + psi**2)
121
+ xi = chi*zeta
122
+ eta = psi*zeta
123
+
124
+ # get the direction cosines
125
+ l, m, n = INVERSE_PARAMETERS[face].call(xi, eta, zeta)
126
+
127
+ [ Math.atan2(m,l), Math.asin(n) ] # φ,θ
128
+ end
129
+
130
+ end # module Tangential
131
+ end # module QuadSphere
@@ -0,0 +1,34 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ # Please mind: in this documentation, and in the code comments, we use
4
+ # φ (phi) to denote longitude, or azimuthal angle, and θ (theta) for
5
+ # latitude, or elevation. This is the convention commonly used in
6
+ # physics. We apologise to all mathheads out there for any
7
+ # inconvenience this may cause.
8
+ module QuadSphere
9
+
10
+ # The identifier of the cube face whose centre maps to θ=π/2
11
+ # (90°N, the North Pole).
12
+ TOP_FACE = 0
13
+
14
+ # The identifier of the cube face whose centre maps to φ=0, θ=0
15
+ # (0° 0°, think Ghana).
16
+ FRONT_FACE = 1
17
+
18
+ # The identifier of the cube face whose centre maps to φ=π/2, θ=0
19
+ # (0° 90°E, think India).
20
+ EAST_FACE = 2
21
+
22
+ # The identifier of the cube face whose centre maps to φ=π, θ=0
23
+ # (0° 180°, think Fiji).
24
+ BACK_FACE = 3
25
+
26
+ # The identifier of the cube face whose centre maps to φ=-π/2, θ=0
27
+ # (0° 90°W, think Central America).
28
+ WEST_FACE = 4
29
+
30
+ # The identifier of the cube face whose centre maps to θ=-π/2
31
+ # (90°S, the South Pole).
32
+ BOTTOM_FACE = 5
33
+
34
+ end
metadata ADDED
@@ -0,0 +1,68 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: quad_sphere
3
+ version: !ruby/object:Gem::Version
4
+ hash: 59
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 9
9
+ - 0
10
+ version: 0.9.0
11
+ platform: ruby
12
+ authors:
13
+ - Cesar Rincon
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2013-02-02 00:00:00 Z
19
+ dependencies: []
20
+
21
+ description: " This is an implementation of the quadrilateralized spherical cube\n projection, an approximately equal-area projection (to within a\n few percent) in which a sphere is projected onto an inscribed\n cube.\n"
22
+ email: crincon@gmail.com
23
+ executables: []
24
+
25
+ extensions: []
26
+
27
+ extra_rdoc_files: []
28
+
29
+ files:
30
+ - lib/quad_sphere.rb
31
+ - lib/quad_sphere/tangential.rb
32
+ - lib/quad_sphere/csc.rb
33
+ homepage: http://rubygems.org/gems/quad_sphere
34
+ licenses: []
35
+
36
+ post_install_message:
37
+ rdoc_options: []
38
+
39
+ require_paths:
40
+ - lib
41
+ required_ruby_version: !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ hash: 3
47
+ segments:
48
+ - 0
49
+ version: "0"
50
+ required_rubygems_version: !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ hash: 3
56
+ segments:
57
+ - 0
58
+ version: "0"
59
+ requirements: []
60
+
61
+ rubyforge_project:
62
+ rubygems_version: 1.8.25
63
+ signing_key:
64
+ specification_version: 3
65
+ summary: Quadrilateralized spherical cube projection
66
+ test_files: []
67
+
68
+ has_rdoc: