aboutsummaryrefslogtreecommitdiff
path: root/src/conversions/unitconvert.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/conversions/unitconvert.cpp')
-rw-r--r--src/conversions/unitconvert.cpp552
1 files changed, 552 insertions, 0 deletions
diff --git a/src/conversions/unitconvert.cpp b/src/conversions/unitconvert.cpp
new file mode 100644
index 00000000..b25fd5d2
--- /dev/null
+++ b/src/conversions/unitconvert.cpp
@@ -0,0 +1,552 @@
+/***********************************************************************
+
+ 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 pivot 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 <errno.h>
+#include <math.h>
+#include <string.h>
+#include <time.h>
+
+#include "proj_internal.h"
+#include "proj_math.h"
+#include "projects.h"
+
+PROJ_HEAD(unitconvert, "Unit conversion");
+
+typedef double (*tconvert)(double);
+
+namespace { // anonymous namespace
+struct TIME_UNITS {
+ const char *id; /* units keyword */
+ tconvert t_in; /* unit -> mod. julian date function pointer */
+ tconvert t_out; /* mod. julian date > unit function pointer */
+ const char *name; /* comments */
+};
+} // anonymous namespace
+
+namespace { // anonymous namespace
+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 */
+ double xy_factor; /* unit conversion factor for horizontal components */
+ double z_factor; /* unit conversion factor for vertical components */
+};
+} // anonymous namespace
+
+
+/***********************************************************************/
+static int is_leap_year(long year) {
+/***********************************************************************/
+ return ((year % 4 == 0 && year % 100 != 0) || year % 400 ==0);
+}
+
+
+/***********************************************************************/
+static int days_in_year(long year) {
+/***********************************************************************/
+ return is_leap_year(year) ? 366 : 365;
+}
+
+/***********************************************************************/
+static unsigned int days_in_month(unsigned long year, unsigned long month) {
+/***********************************************************************/
+ const unsigned int month_table[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
+ unsigned int days;
+
+ if (month > 12) month = 12;
+ if (month == 0) month = 1;
+
+ days = month_table[month-1];
+ if (is_leap_year(year) && month == 2) days++;
+
+ return days;
+}
+
+
+/***********************************************************************/
+static int daynumber_in_year(unsigned long year, unsigned long month, unsigned long day) {
+/***********************************************************************/
+ unsigned int daynumber=0, i;
+
+ if (month > 12) month = 12;
+ if (month == 0) month = 1;
+ if (day > days_in_month(year, month)) day = days_in_month(year, month);
+
+ for (i = 1; i < month; i++)
+ daynumber += days_in_month(year, i);
+
+ daynumber += day;
+
+ return daynumber;
+
+}
+
+/***********************************************************************/
+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
+************************************************************************/
+ long year;
+ double fractional_year;
+ double mjd;
+
+ if( decimalyear < -10000 || decimalyear > 10000 )
+ return 0;
+
+ year = lround(floor(decimalyear));
+ fractional_year = decimalyear - year;
+ mjd = (year - 1859)*365 + 14 + 31;
+ mjd += (double)fractional_year*(double)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;
+}
+
+
+/***********************************************************************/
+static double yyyymmdd_to_mjd(double yyyymmdd) {
+/************************************************************************
+ Date given in YYYY-MM-DD format.
+************************************************************************/
+
+ long year = lround(floor( yyyymmdd / 10000 ));
+ long month = lround(floor((yyyymmdd - year*10000) / 100));
+ long day = lround(floor( yyyymmdd - year*10000 - month*100));
+ double mjd = daynumber_in_year(year, month, day);
+
+ for (year -= 1; year > 1858; year--)
+ mjd += days_in_year(year);
+
+ return mjd + 13 + 31;
+}
+
+
+/***********************************************************************/
+static double mjd_to_yyyymmdd(double mjd) {
+/************************************************************************
+ Date given in YYYY-MM-DD format.
+************************************************************************/
+ double mjd_iter = 14 + 31;
+ int year = 1859, month=0, day=0;
+
+ for (; mjd >= mjd_iter; year++) {
+ mjd_iter += days_in_year(year);
+ }
+ year--;
+ mjd_iter -= days_in_year(year);
+
+ for (month=1; mjd_iter + days_in_month(year, month) <= mjd; month++)
+ mjd_iter += days_in_month(year, month);
+
+ day = (int)(mjd - mjd_iter + 1);
+
+ return year*10000.0 + month*100.0 + day;
+}
+
+static const 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"},
+ {"yyyymmdd", yyyymmdd_to_mjd, mjd_to_yyyymmdd, "YYYYMMDD date"},
+ {nullptr, nullptr, nullptr, nullptr}
+};
+
+
+/***********************************************************************/
+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_COORD point = {{0,0,0,0}};
+ point.lp = lp;
+
+ point.xy.x *= Q->xy_factor;
+ point.xy.y *= Q->xy_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_COORD point = {{0,0,0,0}};
+ point.xy = xy;
+
+ point.xy.x /= Q->xy_factor;
+ point.xy.y /= Q->xy_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_COORD point = {{0,0,0,0}};
+ point.lpz = lpz;
+
+ /* take care of the horizontal components in the 2D function */
+ point.xy = forward_2d(point.lp, P);
+
+ point.xyz.z *= Q->z_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_COORD point = {{0,0,0,0}};
+ point.xyz = xyz;
+
+ /* take care of the horizontal components in the 2D function */
+ point.lp = reverse_2d(point.xy, P);
+
+ point.xyz.z /= Q->z_factor;
+
+ return point.lpz;
+}
+
+
+/***********************************************************************/
+static PJ_COORD forward_4d(PJ_COORD obs, PJ *P) {
+/************************************************************************
+ Forward conversion of time units
+************************************************************************/
+ struct pj_opaque_unitconvert *Q = (struct pj_opaque_unitconvert *) P->opaque;
+ PJ_COORD out = obs;
+
+ /* delegate unit conversion of physical dimensions to the 3D function */
+ out.xyz = forward_3d(obs.lpz, P);
+
+ if (Q->t_in_id >= 0)
+ out.xyzt.t = time_units[Q->t_in_id].t_in( obs.xyzt.t );
+ if (Q->t_out_id >= 0)
+ out.xyzt.t = time_units[Q->t_out_id].t_out( out.xyzt.t );
+
+ return out;
+}
+
+
+/***********************************************************************/
+static PJ_COORD reverse_4d(PJ_COORD obs, PJ *P) {
+/************************************************************************
+ Reverse conversion of time units
+************************************************************************/
+ struct pj_opaque_unitconvert *Q = (struct pj_opaque_unitconvert *) P->opaque;
+ PJ_COORD out = obs;
+
+ /* delegate unit conversion of physical dimensions to the 3D function */
+ out.lpz = reverse_3d(obs.xyz, P);
+
+ if (Q->t_out_id >= 0)
+ out.xyzt.t = time_units[Q->t_out_id].t_in( obs.xyzt.t );
+ if (Q->t_in_id >= 0)
+ out.xyzt.t = time_units[Q->t_in_id].t_out( out.xyzt.t );
+
+ return out;
+}
+
+/***********************************************************************/
+static double get_unit_conversion_factor(const char* name,
+ int* p_is_linear,
+ const char** p_normalized_name) {
+/***********************************************************************/
+ int i;
+ const char* s;
+ const PJ_UNITS *units;
+
+ units = proj_list_units();
+
+ /* Try first with linear units */
+ for (i = 0; (s = units[i].id) ; ++i) {
+ if ( strcmp(s, name) == 0 ) {
+ if( p_normalized_name ) {
+ *p_normalized_name = units[i].name;
+ }
+ if( p_is_linear ) {
+ *p_is_linear = 1;
+ }
+ return units[i].factor;
+ }
+ }
+
+ /* And then angular units */
+ units = proj_list_angular_units();
+ for (i = 0; (s = units[i].id) ; ++i) {
+ if ( strcmp(s, name) == 0 ) {
+ if( p_normalized_name ) {
+ *p_normalized_name = units[i].name;
+ }
+ if( p_is_linear ) {
+ *p_is_linear = 0;
+ }
+ return units[i].factor;
+ }
+ }
+ if( p_normalized_name ) {
+ *p_normalized_name = nullptr;
+ }
+ if( p_is_linear ) {
+ *p_is_linear = -1;
+ }
+ return 0.0;
+}
+
+/***********************************************************************/
+PJ *CONVERSION(unitconvert,0) {
+/***********************************************************************/
+ struct pj_opaque_unitconvert *Q = static_cast<struct pj_opaque_unitconvert*>(pj_calloc (1, sizeof (struct pj_opaque_unitconvert)));
+ const char *s, *name;
+ int i;
+ double f;
+ int xy_in_is_linear = -1; /* unknown */
+ int xy_out_is_linear = -1; /* unknown */
+ int z_in_is_linear = -1; /* unknown */
+ int z_out_is_linear = -1; /* unknown */
+
+ if (nullptr==Q)
+ return pj_default_destructor (P, ENOMEM);
+ P->opaque = (void *) Q;
+
+ P->fwd4d = forward_4d;
+ P->inv4d = reverse_4d;
+ P->fwd3d = forward_3d;
+ P->inv3d = reverse_3d;
+ P->fwd = forward_2d;
+ P->inv = reverse_2d;
+
+ P->left = PJ_IO_UNITS_WHATEVER;
+ P->right = PJ_IO_UNITS_WHATEVER;
+
+ /* if no time input/output unit is specified we can skip them */
+ Q->t_in_id = -1;
+ Q->t_out_id = -1;
+
+ Q->xy_factor = 1.0;
+ Q->z_factor = 1.0;
+
+ if ((name = pj_param (P->ctx, P->params, "sxy_in").s) != nullptr) {
+ const char* normalized_name = nullptr;
+ f = get_unit_conversion_factor(name, &xy_in_is_linear, &normalized_name);
+ if (f != 0.0) {
+ proj_log_debug(P, "xy_in unit: %s", normalized_name);
+ } else {
+ if ( (f = pj_param (P->ctx, P->params, "dxy_in").f) == 0.0)
+ return pj_default_destructor(P, PJD_ERR_UNKNOWN_UNIT_ID);
+ }
+ if (f != 0.0)
+ Q->xy_factor *= f;
+ }
+
+ if ((name = pj_param (P->ctx, P->params, "sxy_out").s) != nullptr) {
+ const char* normalized_name = nullptr;
+ f = get_unit_conversion_factor(name, &xy_out_is_linear, &normalized_name);
+ if (f != 0.0) {
+ proj_log_debug(P, "xy_out unit: %s", normalized_name);
+ } else {
+ if ( (f = pj_param (P->ctx, P->params, "dxy_out").f) == 0.0)
+ return pj_default_destructor(P, PJD_ERR_UNKNOWN_UNIT_ID);
+ }
+ if (f != 0.0)
+ Q->xy_factor /= f;
+ }
+
+ if( xy_in_is_linear >= 0 && xy_out_is_linear >= 0 &&
+ xy_in_is_linear != xy_out_is_linear ) {
+ proj_log_debug(P, "inconsistent unit type between xy_in and xy_out");
+ return pj_default_destructor(P, PJD_ERR_INCONSISTENT_UNIT);
+ }
+
+ if ((name = pj_param (P->ctx, P->params, "sz_in").s) != nullptr) {
+ const char* normalized_name = nullptr;
+ f = get_unit_conversion_factor(name, &z_in_is_linear, &normalized_name);
+ if (f != 0.0) {
+ proj_log_debug(P, "z_in unit: %s", normalized_name);
+ } else {
+ if ( (f = pj_param (P->ctx, P->params, "dz_in").f) == 0.0)
+ return pj_default_destructor(P, PJD_ERR_UNKNOWN_UNIT_ID);
+ }
+ if (f != 0.0)
+ Q->z_factor *= f;
+ }
+
+ if ((name = pj_param (P->ctx, P->params, "sz_out").s) != nullptr) {
+ const char* normalized_name = nullptr;
+ f = get_unit_conversion_factor(name, &z_out_is_linear, &normalized_name);
+ if (f != 0.0) {
+ proj_log_debug(P, "z_out unit: %s", normalized_name);
+ } else {
+ if ( (f = pj_param (P->ctx, P->params, "dz_out").f) == 0.0)
+ return pj_default_destructor(P, PJD_ERR_UNKNOWN_UNIT_ID);
+ }
+ if (f != 0.0)
+ Q->z_factor /= f;
+ }
+
+ if( z_in_is_linear >= 0 && z_out_is_linear >= 0 &&
+ z_in_is_linear != z_out_is_linear ) {
+ proj_log_debug(P, "inconsistent unit type between z_in and z_out");
+ return pj_default_destructor(P, PJD_ERR_INCONSISTENT_UNIT);
+ }
+
+ if ((name = pj_param (P->ctx, P->params, "st_in").s) != nullptr) {
+ for (i = 0; (s = time_units[i].id) && strcmp(name, s) ; ++i);
+
+ if (!s) return pj_default_destructor(P, PJD_ERR_UNKNOWN_UNIT_ID); /* unknown unit conversion id */
+
+ Q->t_in_id = i;
+ proj_log_debug(P, "t_in unit: %s", time_units[i].name);
+ }
+
+ s = nullptr;
+ if ((name = pj_param (P->ctx, P->params, "st_out").s) != nullptr) {
+ for (i = 0; (s = time_units[i].id) && strcmp(name, s) ; ++i);
+
+ if (!s) return pj_default_destructor(P, PJD_ERR_UNKNOWN_UNIT_ID); /* unknown unit conversion id */
+
+ Q->t_out_id = i;
+ proj_log_debug(P, "t_out unit: %s", time_units[i].name);
+ }
+
+ return P;
+}