aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am2
-rw-r--r--src/PJ_unitconvert.c495
-rw-r--r--src/lib_proj.cmake1
-rw-r--r--src/makefile.vc2
-rw-r--r--src/pj_list.h1
-rw-r--r--src/pj_units.c44
-rw-r--r--src/projects.h1
7 files changed, 522 insertions, 24 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index f9dee2a9..25a7eaf4 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -75,7 +75,7 @@ libproj_la_SOURCES = \
pj_strtod.c \
\
pj_obs_api.c PJ_cart.c PJ_pipeline.c PJ_horner.c PJ_helmert.c \
- PJ_vgridshift.c PJ_hgridshift.c
+ PJ_vgridshift.c PJ_hgridshift.c PJ_unitconvert.c
install-exec-local:
rm -f $(DESTDIR)$(bindir)/invproj$(EXEEXT)
diff --git a/src/PJ_unitconvert.c b/src/PJ_unitconvert.c
new file mode 100644
index 00000000..be9d2f70
--- /dev/null
+++ b/src/PJ_unitconvert.c
@@ -0,0 +1,495 @@
+/***********************************************************************
+
+ Unit conversion pseudo-projection for use with
+ transformation pipelines.
+
+ Kristian Evers, 2017-05-16
+
+************************************************************************
+
+A pseudo-projection that can be used to convert units of input and
+output data. Primarily useful in pipelines.
+
+Unit conversion is performed by means of a pivot unit. The pivot unit
+for distance units are the meter and for time we use the modified julian
+date. A time unit conversion is performed like
+
+ Unit A -> Modified Julian date -> Unit B
+
+distance units are converted in the same manner, with meter being the
+central unit.
+
+The modified Julian date is chosen as the pivout unit since it has a
+fairly high precision, goes sufficiently long backwards in time, has no
+danger of hitting the upper limit in the near future and it is a fairly
+common time unit in astronomy and geodesy. Note that we are using the
+Julian date and not day. The difference being that the latter is defined
+as an integer and is thus limited to days in resolution. This approach
+has been extended wherever it makes sense, e.g. the GPS week unit also
+has a fractional part that makes it possible to determine the day, hour
+and minute of an observation.
+
+In- and output units are controlled with the parameters
+
+ +xy_in, +xy_out, +z_in, +z_out, +t_in and +t_out
+
+where xy denotes horizontal units, z vertical units and t time units.
+
+************************************************************************
+
+Kristian Evers, kreve@sdfe.dk, 2017-05-09
+Last update: 2017-05-16
+
+************************************************************************
+* Copyright (c) 2017, Kristian Evers / SDFE
+*
+* Permission is hereby granted, free of charge, to any person obtaining a
+* copy of this software and associated documentation files (the "Software"),
+* to deal in the Software without restriction, including without limitation
+* the rights to use, copy, modify, merge, publish, distribute, sublicense,
+* and/or sell copies of the Software, and to permit persons to whom the
+* Software is furnished to do so, subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be included
+* in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+* DEALINGS IN THE SOFTWARE.
+*
+***********************************************************************/
+
+#define PJ_LIB__
+#include <time.h>
+#include <proj.h>
+#include <projects.h>
+
+PROJ_HEAD(unitconvert, "Unit conversion");
+
+typedef double (*tconvert)(double);
+
+struct TIME_UNITS {
+ char *id; /* units keyword */
+ tconvert t_in; /* unit -> mod. julian date funtion pointer */
+ tconvert t_out; /* mod. julian date > unit function pointer */
+ char *name; /* comments */
+};
+
+
+/***********************************************************************/
+static int is_leap_year(int year) {
+/***********************************************************************/
+ return ((year % 4 == 0 && year % 100 != 0) || year % 400 ==0);
+}
+
+
+/***********************************************************************/
+static int days_in_year(int year) {
+/***********************************************************************/
+ return is_leap_year(year) ? 366 : 365;
+}
+
+
+/***********************************************************************/
+static double mjd_to_mjd(double mjd) {
+/***********************************************************************
+ Modified julian date no-op function.
+
+ The Julian date is defined as (fractional) days since midnight
+ on 16th of November in 1858.
+************************************************************************/
+ return mjd;
+}
+
+
+/***********************************************************************/
+static double decimalyear_to_mjd(double decimalyear) {
+/***********************************************************************
+ Epoch of modified julian date is 1858-11-16 00:00
+************************************************************************/
+ int year = (int)floor(decimalyear);
+ double fractional_year = decimalyear - year;
+ double mjd = (year - 1859)*365 + 14 + 31;
+ mjd += fractional_year*days_in_year(year);
+
+ /* take care of leap days */
+ year--;
+ for (; year > 1858; year--)
+ if (is_leap_year(year))
+ mjd++;
+
+ return mjd;
+}
+
+
+/***********************************************************************/
+static double mjd_to_decimalyear(double mjd) {
+/***********************************************************************
+ Epoch of modified julian date is 1858-11-16 00:00
+************************************************************************/
+ double decimalyear = mjd;
+ double mjd_iter = 14 + 31;
+ int year = 1859;
+
+ /* a smarter brain than mine could probably to do this more elegantly
+ - I'll just brute-force my way out of this... */
+ for (; mjd >= mjd_iter; year++) {
+ mjd_iter += days_in_year(year);
+ }
+ year--;
+ mjd_iter -= days_in_year(year);
+
+ decimalyear = year + (mjd-mjd_iter)/days_in_year(year);
+ return decimalyear;
+}
+
+
+/***********************************************************************/
+static double gps_week_to_mjd(double gps_week) {
+/***********************************************************************
+ GPS weeks are defined as the number of weeks since January the 6th
+ 1980.
+
+ Epoch of gps weeks is 1980-01-06 00:00, which in modified Julian
+ date is 44244.
+************************************************************************/
+ return 44244.0 + gps_week*7.0;
+}
+
+
+/***********************************************************************/
+static double mjd_to_gps_week(double mjd) {
+/***********************************************************************
+ GPS weeks are defined as the number of weeks since January the 6th
+ 1980.
+
+ Epoch of gps weeks is 1980-01-06 00:00, which in modified Julian
+ date is 44244.
+************************************************************************/
+ return (mjd - 44244.0) / 7.0;
+}
+
+struct TIME_UNITS time_units[] = {
+ {"mjd", mjd_to_mjd, mjd_to_mjd, "Modified julian date"},
+ {"decimalyear", decimalyear_to_mjd, mjd_to_decimalyear, "Decimal year"},
+ {"gps_week", gps_week_to_mjd, mjd_to_gps_week, "GPS Week"},
+ {NULL, NULL, NULL, NULL}
+};
+
+struct pj_opaque_unitconvert {
+ int t_in_id; /* time unit id for the time input unit */
+ int t_out_id; /* time unit id for the time output unit */
+ int xy_in_id; /* unit id for the horizontal input unit */
+ int xy_out_id; /* unit id for the horizontal output unit */
+ int z_in_id; /* unit id for the vertical input unit */
+ int z_out_id; /* unit id for the vertical output unit */
+};
+
+
+/***********************************************************************/
+static void *freeup_msg (PJ *P, int errlev) {
+/***********************************************************************/
+ if (0==P)
+ return 0;
+
+ if (0!=P->ctx)
+ pj_ctx_set_errno (P->ctx, errlev);
+
+ return pj_dealloc(P);
+}
+
+
+/***********************************************************************/
+static void freeup (PJ *P) {
+/***********************************************************************/
+ freeup_msg (P, 0);
+ return;
+}
+
+/***********************************************************************/
+static XY forward_2d(LP lp, PJ *P) {
+/************************************************************************
+ Forward unit conversions in the plane
+************************************************************************/
+ struct pj_opaque_unitconvert *Q = (struct pj_opaque_unitconvert *) P->opaque;
+ PJ_TRIPLET point;
+ point.lp = lp;
+
+ point.xy.x *= pj_units[Q->xy_in_id].factor / pj_units[Q->xy_out_id].factor;
+ point.xy.y *= pj_units[Q->xy_in_id].factor / pj_units[Q->xy_out_id].factor;
+
+ return point.xy;
+}
+
+
+/***********************************************************************/
+static LP reverse_2d(XY xy, PJ *P) {
+/************************************************************************
+ Reverse unit conversions in the plane
+************************************************************************/
+ struct pj_opaque_unitconvert *Q = (struct pj_opaque_unitconvert *) P->opaque;
+ PJ_TRIPLET point;
+ point.xy = xy;
+
+ point.xy.x *= pj_units[Q->xy_out_id].factor / pj_units[Q->xy_in_id].factor;
+ point.xy.y *= pj_units[Q->xy_out_id].factor / pj_units[Q->xy_in_id].factor;
+
+ return point.lp;
+}
+
+
+/***********************************************************************/
+static XYZ forward_3d(LPZ lpz, PJ *P) {
+/************************************************************************
+ Forward unit conversions the vertical component
+************************************************************************/
+ struct pj_opaque_unitconvert *Q = (struct pj_opaque_unitconvert *) P->opaque;
+ PJ_TRIPLET point;
+ point.lpz = lpz;
+
+ /* take care of the horizontal components in the 2D function */
+ point.xy = forward_2d(point.lp, P);
+
+ point.xyz.z *= pj_units[Q->z_in_id].factor / pj_units[Q->z_out_id].factor;
+
+ return point.xyz;
+}
+
+/***********************************************************************/
+static LPZ reverse_3d(XYZ xyz, PJ *P) {
+/************************************************************************
+ Reverse unit conversions the vertical component
+************************************************************************/
+ struct pj_opaque_unitconvert *Q = (struct pj_opaque_unitconvert *) P->opaque;
+ PJ_TRIPLET point;
+ point.xyz = xyz;
+
+ /* take care of the horizontal components in the 2D function */
+ point.lp = reverse_2d(point.xy, P);
+
+ point.xyz.z *= pj_units[Q->z_out_id].factor / pj_units[Q->z_in_id].factor;
+
+ return point.lpz;
+}
+
+
+/***********************************************************************/
+static PJ_OBS forward_obs(PJ_OBS obs, PJ *P) {
+/************************************************************************
+ Forward conversion of time units
+************************************************************************/
+ struct pj_opaque_unitconvert *Q = (struct pj_opaque_unitconvert *) P->opaque;
+ PJ_OBS out;
+
+ /* delegate unit conversion of physical dimensions to the 3D function */
+ out.coo.xyz = forward_3d(obs.coo.lpz, P);
+
+ if (Q->t_in_id >= 0)
+ out.coo.xyzt.t = time_units[Q->t_in_id].t_in( obs.coo.xyzt.t );
+ if (Q->t_out_id >= 0)
+ out.coo.xyzt.t = time_units[Q->t_out_id].t_out( out.coo.xyzt.t );
+
+ return out;
+}
+
+
+/***********************************************************************/
+static PJ_OBS reverse_obs(PJ_OBS obs, PJ *P) {
+/************************************************************************
+ Reverse conversion of time units
+************************************************************************/
+ struct pj_opaque_unitconvert *Q = (struct pj_opaque_unitconvert *) P->opaque;
+ PJ_OBS out;
+
+ /* delegate unit conversion of physical dimensions to the 3D function */
+ out.coo.lpz = reverse_3d(obs.coo.xyz, P);
+
+ if (Q->t_in_id >= 0)
+ out.coo.xyzt.t = time_units[Q->t_out_id].t_in( obs.coo.xyzt.t );
+ if (Q->t_out_id >= 0)
+ out.coo.xyzt.t = time_units[Q->t_in_id].t_out( out.coo.xyzt.t );
+
+ return out;
+}
+
+
+/***********************************************************************/
+PJ *PROJECTION(unitconvert) {
+/***********************************************************************/
+ struct pj_opaque_unitconvert *Q = pj_calloc (1, sizeof (struct pj_opaque_unitconvert));
+ char *s, *name;
+ int i;
+
+ if (0==Q)
+ return freeup_msg (P, ENOMEM);
+ P->opaque = (void *) Q;
+
+ P->fwdobs = forward_obs;
+ P->invobs = reverse_obs;
+ P->fwd3d = forward_3d;
+ P->inv3d = reverse_3d;
+ P->fwd = forward_2d;
+ P->inv = reverse_2d;
+
+ P->left = PJ_IO_UNITS_RADIANS;
+ P->right = PJ_IO_UNITS_RADIANS;
+
+ /* if no time input/output unit is specified we can skip them */
+ Q->t_in_id = -1;
+ Q->t_out_id = -1;
+
+ if ((name = pj_param (P->ctx, P->params, "sxy_in").s) != NULL) {
+ for (i = 0; (s = pj_units[i].id) && strcmp(name, s) ; ++i);
+
+ if (!s) return freeup_msg(P, -8); /* unknown unit conversion id */
+
+ Q->xy_in_id = i;
+ pj_log_debug(P, "xy_in unit: %s", pj_units[i].name);
+ }
+
+ if ((name = pj_param (P->ctx, P->params, "sxy_out").s) != NULL) {
+ for (i = 0; (s = pj_units[i].id) && strcmp(name, s) ; ++i);
+
+ if (!s) return freeup_msg(P, -8); /* unknown unit conversion id */
+
+ Q->xy_out_id = i;
+ pj_log_debug(P, "xy_out unit: %s", pj_units[i].name);
+ }
+
+ if ((name = pj_param (P->ctx, P->params, "sz_in").s) != NULL) {
+ for (i = 0; (s = pj_units[i].id) && strcmp(name, s) ; ++i);
+
+ if (!s) return freeup_msg(P, -8); /* unknown unit conversion id */
+
+ Q->z_in_id = i;
+ pj_log_debug(P, "z_in unit: %s", pj_units[i].name);
+ }
+
+ if ((name = pj_param (P->ctx, P->params, "sz_out").s) != NULL) {
+ for (i = 0; (s = pj_units[i].id) && strcmp(name, s) ; ++i);
+
+ if (!s) return freeup_msg(P, -8); /* unknown unit conversion id */
+
+ Q->z_out_id = i;
+ pj_log_debug(P, "z_out unit: %s", pj_units[i].name);
+ }
+
+
+ if ((name = pj_param (P->ctx, P->params, "st_in").s) != NULL) {
+ for (i = 0; (s = time_units[i].id) && strcmp(name, s) ; ++i);
+
+ if (!s) return freeup_msg(P, -8); /* unknown unit conversion id */
+
+ Q->t_in_id = i;
+ pj_log_debug(P, "t_in unit: %s", time_units[i].name);
+ }
+
+ s = 0;
+ if ((name = pj_param (P->ctx, P->params, "st_out").s) != NULL) {
+ for (i = 0; (s = time_units[i].id) && strcmp(name, s) ; ++i);
+ if (!s) {
+ return freeup_msg(P, -8); /* unknown unit conversion id */
+ }
+ Q->t_out_id = i;
+ pj_log_debug(P, "t_out unit: %s", time_units[i].name);
+ }
+
+ return P;
+}
+
+#ifndef PJ_SELFTEST
+
+int pj_unitconvert_selftest (void) {return 0;}
+
+#else
+
+static int test_time(char* args, double tol, double t_in, double t_exp) {
+ PJ_OBS in, out;
+ PJ *P = pj_create(args);
+ int ret = 0;
+
+ if (P == 0)
+ return 5;
+
+ in.coo.xyzt.t = t_in;
+
+ out = pj_trans(P, PJ_FWD, in);
+ if (fabs(out.coo.xyzt.t - t_exp) > tol) {
+ pj_log_error(P, "out: %10.10g, expect: %10.10g", out.coo.xyzt.t, t_exp);
+ ret = 1;
+ }
+ out = pj_trans(P, PJ_INV, out);
+ if (fabs(out.coo.xyzt.t - t_in) > tol) {
+ pj_log_error(P, "out: %10.10g, expect: %10.10g", out.coo.xyzt.t, t_in);
+ ret = 2;
+ }
+ pj_free(P);
+
+ pj_log_level(NULL, 0);
+ return ret;
+}
+
+static int test_xyz(char* args, double tol, PJ_TRIPLET in, PJ_TRIPLET exp) {
+ PJ_OBS out, obs_in;
+ PJ *P = pj_create(args);
+ int ret = 0;
+
+ if (P == 0)
+ return 5;
+
+ obs_in.coo.xyz = in.xyz;
+ out = pj_trans(P, PJ_FWD, obs_in);
+ if (pj_xyz_dist(out.coo.xyz, exp.xyz) > tol) {
+ printf("exp: %10.10g, %10.10g, %10.10g\n", exp.xyz.x, exp.xyz.y, exp.xyz.z);
+ printf("out: %10.10g, %10.10g, %10.10g\n", out.coo.xyz.x, out.coo.xyz.y, out.coo.xyz.z);
+ ret = 1;
+ }
+
+ out = pj_trans(P, PJ_INV, out);
+ if (pj_xyz_dist(out.coo.xyz, in.xyz) > tol) {
+ printf("exp: %g, %g, %g\n", in.xyz.x, in.xyz.y, in.xyz.z);
+ printf("out: %g, %g, %g\n", out.coo.xyz.x, out.coo.xyz.y, out.coo.xyz.z);
+ ret += 2;
+ }
+ pj_free(P);
+ pj_log_level(NULL, 0);
+ return ret;
+}
+
+
+int pj_unitconvert_selftest (void) {
+ int ret = 0;
+ char args1[] = "+proj=unitconvert +t_in=decimalyear +t_out=decimalyear";
+ double in1 = 2004.25;
+
+ char args2[] = "+proj=unitconvert +t_in=gps_week +t_out=gps_week";
+ double in2 = 1782.0;
+
+ char args3[] = "+proj=unitconvert +t_in=mjd +t_out=mjd";
+ double in3 = 57390.0;
+
+ char args4[] = "+proj=unitconvert +t_in=gps_week +t_out=decimalyear";
+ double in4 = 1877.71428, exp4 = 2016.0;
+
+ char args5[] = "+proj=unitconvert +xy_in=m +xy_out=dm +z_in=cm +z_out=mm";
+ PJ_TRIPLET in5 = {{55.25, 23.23, 45.5}}, exp5 = {{552.5, 232.3, 455.0}};
+
+ char args6[] = "+proj=unitconvert +xy_in=m +xy_out=m +z_in=m +z_out=m";
+ PJ_TRIPLET in6 = {{12.3, 45.6, 7.89}};
+
+ ret = test_time(args1, 1e-6, in1, in1); if (ret) return ret + 10;
+ ret = test_time(args2, 1e-6, in2, in2); if (ret) return ret + 20;
+ ret = test_time(args3, 1e-6, in3, in3); if (ret) return ret + 30;
+ ret = test_time(args4, 1e-6, in4, exp4); if (ret) return ret + 40;
+ ret = test_xyz (args5, 1e-10, in5, exp5); if (ret) return ret + 50;
+ ret = test_xyz (args6, 1e-10, in6, in6); if (ret) return ret + 50;
+
+ return 0;
+
+}
+
+#endif
diff --git a/src/lib_proj.cmake b/src/lib_proj.cmake
index d1f75cb9..9ed1623c 100644
--- a/src/lib_proj.cmake
+++ b/src/lib_proj.cmake
@@ -135,6 +135,7 @@ SET(SRC_LIBPROJ_PJ
PJ_times.c
PJ_tmerc.c
PJ_tpeqd.c
+ PJ_unitconvert.c
PJ_urm5.c
PJ_urmfps.c
PJ_vandg.c
diff --git a/src/makefile.vc b/src/makefile.vc
index b0d8291d..8584d419 100644
--- a/src/makefile.vc
+++ b/src/makefile.vc
@@ -60,7 +60,7 @@ support = \
pipeline = \
pj_obs_api.obj PJ_cart.obj PJ_pipeline.obj PJ_horner.obj PJ_helmert.obj \
- PJ_vgridshift.obj PJ_hgridshift.obj
+ PJ_vgridshift.obj PJ_hgridshift.obj PJ_unitconvert.obj
geodesic = geodesic.obj
diff --git a/src/pj_list.h b/src/pj_list.h
index 7ed6bdac..247a073a 100644
--- a/src/pj_list.h
+++ b/src/pj_list.h
@@ -133,6 +133,7 @@ PROJ_HEAD(tissot, "Tissot Conic")
PROJ_HEAD(tmerc, "Transverse Mercator")
PROJ_HEAD(tpeqd, "Two Point Equidistant")
PROJ_HEAD(tpers, "Tilted perspective")
+PROJ_HEAD(unitconvert, "Unit conversion")
PROJ_HEAD(ups, "Universal Polar Stereographic")
PROJ_HEAD(urm5, "Urmaev V")
PROJ_HEAD(urmfps, "Urmaev Flat-Polar Sinusoidal")
diff --git a/src/pj_units.c b/src/pj_units.c
index c6b5ae37..7dbae34c 100644
--- a/src/pj_units.c
+++ b/src/pj_units.c
@@ -6,28 +6,28 @@
** numerator/denomenator values (e.g. 1/1000) */
C_NAMESPACE_VAR struct PJ_UNITS
pj_units[] = {
- {"km", "1000.", "Kilometer"},
- {"m", "1.", "Meter"},
- {"dm", "1/10", "Decimeter"},
- {"cm", "1/100", "Centimeter"},
- {"mm", "1/1000", "Millimeter"},
- {"kmi", "1852.0", "International Nautical Mile"},
- {"in", "0.0254", "International Inch"},
- {"ft", "0.3048", "International Foot"},
- {"yd", "0.9144", "International Yard"},
- {"mi", "1609.344", "International Statute Mile"},
- {"fath", "1.8288", "International Fathom"},
- {"ch", "20.1168", "International Chain"},
- {"link", "0.201168", "International Link"},
- {"us-in", "1./39.37", "U.S. Surveyor's Inch"},
- {"us-ft", "0.304800609601219", "U.S. Surveyor's Foot"},
- {"us-yd", "0.914401828803658", "U.S. Surveyor's Yard"},
- {"us-ch", "20.11684023368047", "U.S. Surveyor's Chain"},
- {"us-mi", "1609.347218694437", "U.S. Surveyor's Statute Mile"},
- {"ind-yd", "0.91439523", "Indian Yard"},
- {"ind-ft", "0.30479841", "Indian Foot"},
- {"ind-ch", "20.11669506", "Indian Chain"},
- {NULL, NULL, NULL}
+ {"km", "1000.", "Kilometer", 1000.0},
+ {"m", "1.", "Meter", 1.0},
+ {"dm", "1/10", "Decimeter", 0.1},
+ {"cm", "1/100", "Centimeter", 0.01},
+ {"mm", "1/1000", "Millimeter", 0.001},
+ {"kmi", "1852.0", "International Nautical Mile", 1852.0},
+ {"in", "0.0254", "International Inch", 0.0254},
+ {"ft", "0.3048", "International Foot", 0.3048},
+ {"yd", "0.9144", "International Yard", 0.9144},
+ {"mi", "1609.344", "International Statute Mile", 1609.344},
+ {"fath", "1.8288", "International Fathom", 1.8288},
+ {"ch", "20.1168", "International Chain", 20.1168},
+ {"link", "0.201168", "International Link", 0.201168},
+ {"us-in", "1./39.37", "U.S. Surveyor's Inch", 0.0254},
+ {"us-ft", "0.304800609601219", "U.S. Surveyor's Foot", 0.304800609601219},
+ {"us-yd", "0.914401828803658", "U.S. Surveyor's Yard", 0.914401828803658},
+ {"us-ch", "20.11684023368047", "U.S. Surveyor's Chain", 20.11684023368047},
+ {"us-mi", "1609.347218694437", "U.S. Surveyor's Statute Mile", 1609.347218694437},
+ {"ind-yd", "0.91439523", "Indian Yard", 0.91439523},
+ {"ind-ft", "0.30479841", "Indian Foot", 0.30479841},
+ {"ind-ch", "20.11669506", "Indian Chain", 20.11669506},
+ {NULL, NULL, NULL, 0.0}
};
struct PJ_UNITS *pj_get_units_ref()
diff --git a/src/projects.h b/src/projects.h
index 7a483d5f..e30468a1 100644
--- a/src/projects.h
+++ b/src/projects.h
@@ -408,6 +408,7 @@ struct PJ_UNITS {
char *id; /* units keyword */
char *to_meter; /* multiply by value to get meters */
char *name; /* comments */
+ double factor; /* to_meter factor in actual numbers */
};
struct PJ_DATUMS {