]> git.street.me.uk Git - andy/viking.git/blob - src/coords.c
Enable using an optional file cache for Mapnik Renderings.
[andy/viking.git] / src / coords.c
1 /*
2 coords.c
3 borrowed from:
4 http://acme.com/software/coords/
5 I (Evan Battaglia <viking@greentorch.org>) have only made some small changes such as
6 renaming functions and defining LatLon and UTM structs.
7 2004-02-10 -- I also added a function of my own -- a_coords_utm_diff() -- that I felt belonged in coords.c
8 2004-02-21 -- I also added a_coords_utm_equal().
9 2005-11-23 -- Added a_coords_dtostr() for lack of a better place.
10
11 */
12 /* coords.h - include file for coords routines
13 **
14 ** Copyright © 2001 by Jef Poskanzer <jef@acme.com>.
15 ** All rights reserved.
16 **
17 ** Redistribution and use in source and binary forms, with or without
18 ** modification, are permitted provided that the following conditions
19 ** are met:
20 ** 1. Redistributions of source code must retain the above copyright
21 **    notice, this list of conditions and the following disclaimer.
22 ** 2. Redistributions in binary form must reproduce the above copyright
23 **    notice, this list of conditions and the following disclaimer in the
24 **    documentation and/or other materials provided with the distribution.
25 **
26 ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
27 ** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
28 ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
29 ** ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
30 ** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
31 ** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
32 ** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
33 ** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
34 ** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
35 ** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
36 ** SUCH DAMAGE.
37 */
38 #ifdef HAVE_CONFIG_H
39 #include "config.h"
40 #endif
41
42 #ifdef HAVE_STDLIB_H
43 #include <stdlib.h>
44 #endif
45 #ifdef HAVE_STRING_H
46 #include <string.h>
47 #endif
48 #ifdef HAVE_MATH_H
49 #include <math.h>
50 #endif
51
52 #include "coords.h"
53 #ifdef HAVE_VIKING
54 #include "viking.h"
55 #include "globals.h"
56 #else
57 #define DEG2RAD(x) ((x)*(M_PI/180))
58 #define RAD2DEG(x) ((x)*(180/M_PI))
59 #endif
60 #include "degrees_converters.h"
61
62 /**
63  * Convert a double to a string WITHOUT LOCALE.
64  *
65  * Following GPX specifications, decimal values are xsd:decimal
66  * So, they must use the period separator, not the localized one.
67  *
68  * The returned value must be freed by g_free.
69  */
70 char *a_coords_dtostr ( double d )
71 {
72   gchar *buffer = g_malloc(G_ASCII_DTOSTR_BUF_SIZE*sizeof(gchar));
73   g_ascii_dtostr (buffer, G_ASCII_DTOSTR_BUF_SIZE, (gdouble) d);
74   return buffer;
75 }
76
77 #define PIOVER180 0.01745329252
78
79 #define K0 0.9996
80
81 /* WGS-84 */
82 #define EquatorialRadius 6378137
83 #define EccentricitySquared 0.00669438
84
85 static char coords_utm_letter( double latitude );
86
87 int a_coords_utm_equal( const struct UTM *utm1, const struct UTM *utm2 )
88 {
89   return ( utm1->easting == utm2->easting && utm1->northing == utm2->northing && utm1->zone == utm2->zone );
90 }
91
92 double a_coords_utm_diff( const struct UTM *utm1, const struct UTM *utm2 )
93 {
94   static struct LatLon tmp1, tmp2;
95   if ( utm1->zone == utm2->zone ) {
96     return sqrt ( pow ( utm1->easting - utm2->easting, 2 ) + pow ( utm1->northing - utm2->northing, 2 ) );
97   } else {
98     a_coords_utm_to_latlon ( utm1, &tmp1 );
99     a_coords_utm_to_latlon ( utm2, &tmp2 );
100     return a_coords_latlon_diff ( &tmp1, &tmp2 );
101   }
102 }
103
104 double a_coords_latlon_diff ( const struct LatLon *ll1, const struct LatLon *ll2 )
105 {
106   static struct LatLon tmp1, tmp2;
107   gdouble tmp3;
108   tmp1.lat = ll1->lat * PIOVER180;
109   tmp1.lon = ll1->lon * PIOVER180;
110   tmp2.lat = ll2->lat * PIOVER180;
111   tmp2.lon = ll2->lon * PIOVER180;
112   tmp3 = EquatorialRadius * acos(sin(tmp1.lat)*sin(tmp2.lat)+cos(tmp1.lat)*cos(tmp2.lat)*cos(tmp1.lon-tmp2.lon));
113   // For very small differences we can sometimes get NaN returned
114   return isnan(tmp3)?0:tmp3;
115 }
116
117 void a_coords_latlon_to_utm( const struct LatLon *latlon, struct UTM *utm )
118     {
119     double latitude;
120     double longitude;
121     double lat_rad, long_rad;
122     double long_origin, long_origin_rad;
123     double eccPrimeSquared;
124     double N, T, C, A, M;
125     int zone;
126     double northing, easting;
127
128     longitude = latlon->lon;
129     latitude = latlon->lat;
130
131     /* We want the longitude within -180..180. */
132     if ( longitude < -180.0 )
133         longitude += 360.0;
134     if ( longitude > 180.0 )
135         longitude -= 360.0;
136
137     /* Now convert. */
138     lat_rad = DEG2RAD(latitude);
139     long_rad = DEG2RAD(longitude);
140     zone = (int) ( ( longitude + 180 ) / 6 ) + 1;
141     if ( latitude >= 56.0 && latitude < 64.0 &&
142          longitude >= 3.0 && longitude < 12.0 )
143         zone = 32;
144     /* Special zones for Svalbard. */
145     if ( latitude >= 72.0 && latitude < 84.0 )
146         {
147         if      ( longitude >= 0.0  && longitude <  9.0 ) zone = 31;
148         else if ( longitude >= 9.0  && longitude < 21.0 ) zone = 33;
149         else if ( longitude >= 21.0 && longitude < 33.0 ) zone = 35;
150         else if ( longitude >= 33.0 && longitude < 42.0 ) zone = 37;
151         }
152     long_origin = ( zone - 1 ) * 6 - 180 + 3;   /* +3 puts origin in middle of zone */
153     long_origin_rad = DEG2RAD(long_origin);
154     eccPrimeSquared = EccentricitySquared / ( 1.0 - EccentricitySquared );
155     N = EquatorialRadius / sqrt( 1.0 - EccentricitySquared * sin( lat_rad ) * sin( lat_rad ) );
156     T = tan( lat_rad ) * tan( lat_rad );
157     C = eccPrimeSquared * cos( lat_rad ) * cos( lat_rad );
158     A = cos( lat_rad ) * ( long_rad - long_origin_rad );
159     M = EquatorialRadius * ( ( 1.0 - EccentricitySquared / 4 - 3 * EccentricitySquared * EccentricitySquared / 64 - 5 * EccentricitySquared * EccentricitySquared * EccentricitySquared / 256 ) * lat_rad - ( 3 * EccentricitySquared / 8 + 3 * EccentricitySquared * EccentricitySquared / 32 + 45 * EccentricitySquared * EccentricitySquared * EccentricitySquared / 1024 ) * sin( 2 * lat_rad ) + ( 15 * EccentricitySquared * EccentricitySquared / 256 + 45 * EccentricitySquared * EccentricitySquared * EccentricitySquared / 1024 ) * sin( 4 * lat_rad ) - ( 35 * EccentricitySquared * EccentricitySquared * EccentricitySquared / 3072 ) * sin( 6 * lat_rad ) );
160     easting =
161         K0 * N * ( A + ( 1 - T + C ) * A * A * A / 6 + ( 5 - 18 * T + T * T + 72 * C - 58 * eccPrimeSquared ) * A * A * A * A * A / 120 ) + 500000.0;
162     northing =
163         K0 * ( M + N * tan( lat_rad ) * ( A * A / 2 + ( 5 - T + 9 * C + 4 * C * C ) * A * A * A * A / 24 + ( 61 - 58 * T + T * T + 600 * C - 330 * eccPrimeSquared ) * A * A * A * A * A * A / 720 ) );
164     if ( latitude < 0.0 )
165         northing += 10000000.0;  /* 1e7 meter offset for southern hemisphere */
166
167     utm->northing = northing;
168     utm->easting = easting;
169     utm->zone = zone;
170     utm->letter = coords_utm_letter( latitude );
171
172     /* All done. */
173     }
174
175
176 static char coords_utm_letter( double latitude )
177     {
178     /* This routine determines the correct UTM letter designator for the
179     ** given latitude.  It returns 'Z' if the latitude is outside the UTM
180     ** limits of 84N to 80S.
181     */
182     if ( latitude <= 84.0 && latitude >= 72.0 ) return 'X';
183     else if ( latitude < 72.0 && latitude >= 64.0 ) return 'W';
184     else if ( latitude < 64.0 && latitude >= 56.0 ) return 'V';
185     else if ( latitude < 56.0 && latitude >= 48.0 ) return 'U';
186     else if ( latitude < 48.0 && latitude >= 40.0 ) return 'T';
187     else if ( latitude < 40.0 && latitude >= 32.0 ) return 'S';
188     else if ( latitude < 32.0 && latitude >= 24.0 ) return 'R';
189     else if ( latitude < 24.0 && latitude >= 16.0 ) return 'Q';
190     else if ( latitude < 16.0 && latitude >= 8.0 ) return 'P';
191     else if ( latitude <  8.0 && latitude >= 0.0 ) return 'N';
192     else if ( latitude <  0.0 && latitude >= -8.0 ) return 'M';
193     else if ( latitude < -8.0 && latitude >= -16.0 ) return 'L';
194     else if ( latitude < -16.0 && latitude >= -24.0 ) return 'K';
195     else if ( latitude < -24.0 && latitude >= -32.0 ) return 'J';
196     else if ( latitude < -32.0 && latitude >= -40.0 ) return 'H';
197     else if ( latitude < -40.0 && latitude >= -48.0 ) return 'G';
198     else if ( latitude < -48.0 && latitude >= -56.0 ) return 'F';
199     else if ( latitude < -56.0 && latitude >= -64.0 ) return 'E';
200     else if ( latitude < -64.0 && latitude >= -72.0 ) return 'D';
201     else if ( latitude < -72.0 && latitude >= -80.0 ) return 'C';
202     else return 'Z';
203     }
204
205
206
207 void a_coords_utm_to_latlon( const struct UTM *utm, struct LatLon *latlon )
208     {
209     double northing, easting;
210     int zone;
211     char letter[100];
212     double x, y;
213     double eccPrimeSquared;
214     double e1;
215     double N1, T1, C1, R1, D, M;
216     double long_origin;
217     double mu, phi1_rad;
218     int northernHemisphere;     /* 1 for northern hemisphere, 0 for southern */
219     double latitude, longitude;
220
221     northing = utm->northing;
222     easting = utm->easting;
223     zone = utm->zone;
224     letter[0] = utm->letter;
225
226     /* Now convert. */
227     x = easting - 500000.0;     /* remove 500000 meter offset */
228     y = northing;
229     if ( ( *letter - 'N' ) >= 0 )
230         northernHemisphere = 1; /* northern hemisphere */
231     else
232         {
233         northernHemisphere = 0; /* southern hemisphere */
234         y -= 10000000.0;        /* remove 1e7 meter offset */
235         }
236     long_origin = ( zone - 1 ) * 6 - 180 + 3;   /* +3 puts origin in middle of zone */
237     eccPrimeSquared = EccentricitySquared / ( 1.0 - EccentricitySquared );
238     e1 = ( 1.0 - sqrt( 1.0 - EccentricitySquared ) ) / ( 1.0 + sqrt( 1.0 - EccentricitySquared ) );
239     M = y / K0;
240     mu = M / ( EquatorialRadius * ( 1.0 - EccentricitySquared / 4 - 3 * EccentricitySquared * EccentricitySquared / 64 - 5 * EccentricitySquared * EccentricitySquared * EccentricitySquared / 256 ) );
241     phi1_rad = mu + ( 3 * e1 / 2 - 27 * e1 * e1 * e1 / 32 )* sin( 2 * mu ) + ( 21 * e1 * e1 / 16 - 55 * e1 * e1 * e1 * e1 / 32 ) * sin( 4 * mu ) + ( 151 * e1 * e1 * e1 / 96 ) * sin( 6 *mu );
242     N1 = EquatorialRadius / sqrt( 1.0 - EccentricitySquared * sin( phi1_rad ) * sin( phi1_rad ) );
243     T1 = tan( phi1_rad ) * tan( phi1_rad );
244     C1 = eccPrimeSquared * cos( phi1_rad ) * cos( phi1_rad );
245     R1 = EquatorialRadius * ( 1.0 - EccentricitySquared ) / pow( 1.0 - EccentricitySquared * sin( phi1_rad ) * sin( phi1_rad ), 1.5 );
246     D = x / ( N1 * K0 );
247     latitude = phi1_rad - ( N1 * tan( phi1_rad ) / R1 ) * ( D * D / 2 -( 5 + 3 * T1 + 10 * C1 - 4 * C1 * C1 - 9 * eccPrimeSquared ) * D * D * D * D / 24 + ( 61 + 90 * T1 + 298 * C1 + 45 * T1 * T1 - 252 * eccPrimeSquared - 3 * C1 * C1 ) * D * D * D * D * D * D / 720 );
248     latitude = RAD2DEG(latitude);
249     longitude = ( D - ( 1 + 2 * T1 + C1 ) * D * D * D / 6 + ( 5 - 2 * C1 + 28 * T1 - 3 * C1 * C1 + 8 * eccPrimeSquared + 24 * T1 * T1 ) * D * D * D * D * D / 120 ) / cos( phi1_rad );
250     longitude = long_origin + RAD2DEG(longitude);
251
252     /* Show results. */
253
254     latlon->lat = latitude;
255     latlon->lon = longitude;
256
257     }
258
259 void a_coords_latlon_to_string ( const struct LatLon *latlon,
260                                  gchar **lat,
261                                  gchar **lon )
262 {
263   g_return_if_fail ( latlon != NULL );
264 #ifdef HAVE_VIKING
265   vik_degree_format_t format = a_vik_get_degree_format ();
266
267   switch (format) {
268   case VIK_DEGREE_FORMAT_DDD:
269     *lat = convert_lat_dec_to_ddd ( latlon->lat );
270     *lon = convert_lon_dec_to_ddd ( latlon->lon );
271     break;
272   case VIK_DEGREE_FORMAT_DMM:
273     *lat = convert_lat_dec_to_dmm ( latlon->lat );
274     *lon = convert_lon_dec_to_dmm ( latlon->lon );
275     break;
276   case VIK_DEGREE_FORMAT_DMS:
277     *lat = convert_lat_dec_to_dms ( latlon->lat );
278     *lon = convert_lon_dec_to_dms ( latlon->lon );
279     break;
280   case VIK_DEGREE_FORMAT_RAW:
281     *lat = g_strdup_printf ( "%.6f", latlon->lat );
282     *lon = g_strdup_printf ( "%.6f", latlon->lon );
283     break;
284   default:
285     g_critical("Houston, we've had a problem. format=%d", format);
286   }
287 #else
288   *lat = convert_lat_dec_to_ddd ( latlon->lat );
289   *lon = convert_lon_dec_to_ddd ( latlon->lon );
290 #endif
291 }