1 /***********************************************************************
2 
3    A minimal example of a new proj.4 projection implementation
4 
5    ...and a verbose justification for some highly intrusive code
6    surgery
7 
8 ************************************************************************
9 
10 **The brief version:**
11 
12 In an attempt to make proj.4 code slightly more secure and much easier
13 to read and maintain, I'm trying to eliminate a few unfortunate design
14 decisions from the early days of proj.4
15 
16 The work will be *very* intrusive, especially in the PJ_xxx segment of
17 the code tree, but great care has been taken to design a process that
18 can be implemented stepwise and localized, one projection at a time,
19 then finalized with a relatively small and concentrated work package.
20 
21 **The (very) long version:**
22 
23 Gerald I. Evenden's original design for the proj.4 projection system
24 is a beautiful example of software architecture, where a very limited
25 set of policy rules leads to a well defined hierarchical structure and
26 a high degree of both encapsulation and internal interoperability.
27 
28 In the proj.4 code, the policy rules are *enforced* by a system of
29 preprocessor macros for building the scaffolding for implementation
30 of a new projection.
31 
32 While this system of macros undeniably possesses the property of both
33 reducing repetitive code and enforcing policy, unfortunately it also
34 possesses two much less desirable properties:
35 
36 First, while enforcing policy, it also *hides* policy: The "beauty in
37 simplicity" of Gerald's design is hidden behind layers of macros,
38 whose architectural clarity do not match that of proj.4 in general.
39 
40 Second (and related), the macros make the source code look like
41 something only vaguely related to C, making it hard to read (an effect
42 that gets amplified to the tune of syntax highlighters getting confused
43 by the macros).
44 
45 While the policy rule enforcement macros can be eliminated in relatively
46 non-intrusive ways, a more fundamental flaw in the proj.4 use of macros
47 is found in the PJ_xxx.c files implementing the individual projections:
48 The use of internal redefinition of PJ, the fundamental proj data object,
49 through the use of the PROJ_PARMS__ macro, makes the sizeof (PJ)
50 fundamentally unknown to the calling pj_init function.
51 
52 This leads to code that is probably not in full conformance with the
53 C standard.
54 
55 It is also a memory management catastrophe waiting to happen.
56 
57 But first and foremost, it leads to some very clumsy initialization code,
58 where pj_init (the constructor function), needs to start the constsruction
59 process by asking the PJ_xxx function to do the memory allocation (because
60 pj_init does not know the size of the PROJ_PARMS-mangled PJ object being
61 instantiated).
62 
63 Then, after doing some initialization work, pj_init returns control to
64 PJ_xxx, asking it to finalize the initialization with the projection
65 specific parameters specified by the PROJ_PARMS__ macro.
66 
67 Behind the scenes, hidden by two layers of macros, what happens is even
68 worse, as a lot of the initialization code is duplicated in every PJ_xxx
69 file, rather than being centralized in the pj_init function.
70 
71 **Solution procedure:**
72 
73 Evidently, the way to eliminate this clumsyness will be to introduce an
74 opaque object, that is managed by tne individual PJ_xxx projection code,
75 and represented as a simple void-pointer in the PJ object.
76 
77 This can be done one projection code file at a time, working through the
78 code base as time permits (it will take at least a month).
79 
80 When a PJ_xxx file is on the surgical bench, it will also have its
81 ENTRYA/ENTRY0/ENTRY1/ENTRY2/ENDENTRY/etc. etc. macro-guts torn out and
82 replaced by the PROJECTION macro (introduced in projects.h).
83 
84 This leads to code that looks a lot more like real C, and hence is much
85 less confusing to both syntax higlighters and humans. It also leads
86 to code that, after all projections have been processed, with a final
87 sweep over the code base can be brought into the style of the code in
88 PJ_minimal.c
89 
90 In my humble opinion the result wil be a code base that is not only easier
91 to maintain, but also more welcoming to new contributors.
92 
93 And if proj is to expand its strong basis in projections into the fields
94 of geodetic transformations and general geometric geodesy, we will need
95 to be able to attract quite a few expert geodesist contributors.
96 
97 And since expert geodesists are not necessarily expert coders, a welcoming
98 code base is a real asset (to put the icing on the cake of the already
99 welcoming user- and developer community).
100 
101 Note that the entire process does not touch the algorithmic/mathematical
102 parts of the code at all - it is actuallly an attempt to make this part
103 stand out more clearly.
104 
105 ---
106 
107 The attached material is an attempt to show what happens if we remove
108 the layers of macros, and introduce a more centralized approach to
109 memory allocation and initialization.
110 
111 Please note, however, that the level of cantralization achieved here
112 is not yet fully supported by the proj.4 infrastructure: It is an
113 example, intended to show what can be achieved through a smooth,
114 gradual and safe refactoring of the existing layered macro system.
115 
116 In my humble opinion, this version makes the beauty of Gerald's design
117 much more evident than the current layered-macro-version.
118 
119 Thomas Knudsen, thokn@sdfe.dk, 2016-03-31
120 
121 ***********************************************************************/
122 
123 #define PJ_LIB__
124 #include	<projects.h>
125 #include <assert.h>
126 PROJ_HEAD(minimal, "Minimal example (brief description goes here)");
127 
128 
129 /* Projection specific elements for the PJ object */
130 struct pj_opaque {
131 	double a;
132 	int b;
133 };
134 
135 
e_forward(LP lp,PJ * P)136 static XY e_forward (LP lp, PJ *P) {          /* Ellipsoidal, forward */
137     XY xy = {0.0,0.0};
138     /* Actual ellipsoidal forward code goes here */
139     xy.y = lp.lam + P->es;
140     xy.x = lp.phi + 42;
141 	return xy;
142 }
143 
144 
s_forward(LP lp,PJ * P)145 static XY s_forward (LP lp, PJ *P) {           /* Spheroidal, forward */
146     XY xy = {0.0,0.0};
147     /* Actual spheroidal forward code goes here */
148     xy.y = lp.lam + P->es;
149     xy.x = lp.phi + 42;
150 	return xy;
151 }
152 
153 
e_inverse(XY xy,PJ * P)154 static LP e_inverse (XY xy, PJ *P) {          /* Ellipsoidal, inverse */
155     LP lp = {0.0,0.0};
156     /* Actual ellipsoidal forward code goes here */
157     lp.lam = xy.x - P->es;
158     lp.phi = xy.y - P->opaque->b;
159 	return lp;
160 }
161 
162 
s_inverse(XY xy,PJ * P)163 static LP s_inverse (XY xy, PJ *P) {           /* Spheroidal, inverse */
164     LP lp = {0.0,0.0};
165     /* Actual spheroidal forward code goes here */
166     lp.lam = xy.x - P->es;
167     lp.phi = xy.y - P->opaque->b;
168 	return lp;
169 }
170 
171 
freeup(PJ * P)172 static void freeup(PJ *P) {                                    /* Destructor */
173     if (P==0)
174         return;
175     /* Projection specific deallocation goes here */
176     pj_dealloc (P->opaque);
177     pj_dealloc (P);
178     return;
179 }
180 
181 
pj_projection_specific_setup_minimal(PJ * P)182 PJ *pj_projection_specific_setup_minimal (PJ *P) {
183     pj_prepare (P, des_minimal, freeup, sizeof (struct pj_opaque));
184     if (0==P->opaque) {
185         freeup (P);
186         return 0;
187     }
188 
189     P->opaque->a = 42.42;
190     P->opaque->b = 42;
191 
192     /* Spheroidal? */
193 	if (0==P->es) {
194 		P->fwd = s_forward;
195 		P->inv = s_inverse;
196         return P;
197     }
198 
199     /* Otherwise it's ellipsoidal */
200     P->fwd = e_forward;
201 	P->inv = e_inverse;
202 
203     return P;
204 }
205