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