UNCLASSIFIED

GeographicTranslator
 All Classes Namespaces Files Functions Variables Enumerations Enumerator Friends Macros
BritishNationalGrid.cpp
Go to the documentation of this file.
1 // CLASSIFICATION: UNCLASSIFIED
2 
3 /***************************************************************************/
4 /* RSC IDENTIFIER: BRITISH NATIONAL GRID
5  *
6  * ABSTRACT
7  *
8  * This component provides conversions between Geodetic coordinates
9  * (latitude and longitude) and British National Grid coordinates.
10  *
11  * ERROR HANDLING
12  *
13  * This component checks parameters for valid values. If an invalid value
14  * is found the error code is combined with the current error code using
15  * the bitwise or. This combining allows multiple error codes to be
16  * returned. The possible error codes are:
17  *
18  * BNG_NO_ERROR : No errors occurred in function
19  * BNG_LAT_ERROR : Latitude outside of valid range
20  * (49.5 to 61.5 degrees)
21  * BNG_LON_ERROR : Longitude outside of valid range
22  * (-10.0 to 3.5 degrees)
23  * BNG_EASTING_ERROR : Easting outside of valid range
24  * (depending on ellipsoid and
25  * projection parameters)
26  * BNG_NORTHING_ERROR : Northing outside of valid range
27  * (depending on ellipsoid and
28  * projection parameters)
29  * BNG_STRING_ERROR : A BNG string error: string too long,
30  * too short, or badly formed
31  * BNG_INVALID_AREA_ERROR : Coordinate is outside of valid area
32  * BNG_ELLIPSOID_ERROR : Invalid ellipsoid - must be Airy
33  *
34  * REUSE NOTES
35  *
36  * BRITISH NATIONAL GRID is intended for reuse by any application that
37  * performs a British National Grid projection or its inverse.
38  *
39  * REFERENCES
40  *
41  * Further information on BRITISH NATIONAL GRID can be found in the
42  * Reuse Manual.
43  *
44  * BRITISH NATIONAL GRID originated from :
45  * U.S. Army Topographic Engineering Center
46  * Geospatial Information Division
47  * 7701 Telegraph Road
48  * Alexandria, VA 22310-3864
49  *
50  * LICENSES
51  *
52  * None apply to this component.
53  *
54  * RESTRICTIONS
55  *
56  * BRITISH NATIONAL GRID has no restrictions.
57  *
58  * ENVIRONMENT
59  *
60  * BRITISH NATIONAL GRID was tested and certified in the following
61  * environments:
62  *
63  * 1. Solaris 2.5 with GCC, version 2.8.1
64  * 2. Windows 95 with MS Visual C++, version 6
65  *
66  * MODIFICATIONS
67  *
68  * Date Description
69  * ---- -----------
70  * 09-06-00 Original Code
71  * 03-02-07 Original C++ Code
72  * 3/23/2011 NGL BAEts28583 Updated for memory leaks in convertToGeodetic
73  * and convertFromGeodetic.
74  *
75  * 1/19/2016 A. Layne MSP_DR30125 Updated to pass ellipsoid code to
76  * TransverseMercator
77  *
78  *
79  */
80 
81 
82 /***************************************************************************/
83 /*
84  * INCLUDES
85  */
86 
87 #include <ctype.h>
88 #include <math.h>
89 #include <stdio.h>
90 #include <string.h>
91 #include "TransverseMercator.h"
92 #include "BritishNationalGrid.h"
93 #include "BNGCoordinates.h"
94 #include "EllipsoidParameters.h"
96 #include "GeodeticCoordinates.h"
98 #include "ErrorMessages.h"
99 
100 /*
101  * ctype.h - Standard C character handling library
102  * math.h - Standard C math library
103  * stdio.h - Standard C input/output library
104  * string.h - Standard C string handling library
105  * TransverseMercator.h - Is used to convert transverse mercator coordinates
106  * BritishNationalGrid.h - Is for prototype error checking
107  * BNGCoordinates.h - defines bng coordinates
108  * MapProjectionCoordinates.h - defines map projection coordinates
109  * GeodeticCoordinates.h - defines geodetic coordinates
110  * CoordinateConversionException.h - Exception handler
111  * ErrorMessages.h - Contains exception messages
112  */
113 
114 
115 using namespace MSP::CCS;
116 
117 
118 /***************************************************************************/
119 /* DEFINES */
120 
121 const double PI = 3.14159265358979323e0; /* PI */
122 const double PI_OVER_2 = (PI / 2.0e0); /* PI over 2 */
123 const double TWO_PI = (2.0e0 * PI); /* 2 * PI */
124 const double MAX_LAT = (61.5 * PI / 180.0); /* 61.5 degrees */
125 const double MIN_LAT = (49.5 * PI / 180.0); /* 49.5 degrees */
126 const double MAX_LON = (3.5 * PI / 180.0); /* 3.5 degrees */
127 const double MIN_LON = (-10.0 * PI / 180.0); /* -10 degrees */
128 const char* BNG500GRID = "STNOHJ"; /* 500,000 unit square identifications */
129 const char* BNG100GRID = "VWXYZQRSTULMNOPFGHJKABCDE"; /* 100,000 unit square identifications */
130 
131 /* BNG projection Parameters */
132 const double BNG_Origin_Lat = (49.0 * PI / 180.0); // Latitude of origin, in radians
133 const double BNG_Origin_Long = (-2.0 * PI / 180.0); // Longitude of origin, in radians
134 const double BNG_False_Northing = -100000.0; // False northing, in meters
135 const double BNG_False_Easting = 400000.0; // False easting, in meters
136 const double BNG_Scale_Factor = .9996012717; // Scale factor
137 
138 /* Maximum variance for easting and northing values for Airy. */
139 const double BNG_Max_Easting = 759961.0;
140 const double BNG_Max_Northing = 1257875.0;
141 const double BNG_Min_Easting = -133134.0;
142 const double BNG_Min_Northing = -14829.0;
143 
144 static const char* Airy = "AA";
145 
146 /************************************************************************/
147 // LOCAL FUNCTIONS
148 
149 void findIndex( char letter, const char* letterArray, long *index )
150 {
151 /*
152  * The function findIndex searches for a given letter in an array.
153  * It returns the index of the letter in the array if the letter is found.
154  * If the letter is not found, an error code is returned, otherwise
155  * BNG_NO_ERROR is returned.
156  *
157  * letter : Letter being searched for
158  * letter_Array : Array being searched
159  * index : Index of letter in array
160  */
161  long i = 0;
162  long not_Found = 1;
163  long length = strlen(letterArray);
164 
165  while ((i < length) && (not_Found))
166  {
167  if (letter == letterArray[i])
168  {
169  *index = i;
170  not_Found = 0;
171  }
172  i++;
173  }
174  if (not_Found)
176 }
177 
178 
179 long roundBNG( double value )
180 {
181 /* Round value to nearest integer, using standard engineering rule */
182  double ivalue;
183  long ival;
184  double fraction = modf (value, &ivalue);
185  ival = (long)(ivalue);
186  if ((fraction > 0.5) || ((fraction == 0.5) && (ival%2 == 1)))
187  ival++;
188 
189  return (ival);
190 }
191 
192 
193 void makeBNGString( char ltrnum[4], long easting, long northing, char* BNGString, long precision )
194 /* Construct a BNG string from its component parts */
195 {
196 
197  double divisor;
198  long east;
199  long north;
200  long i;
201  long j;
202  double unitInterval;
203 
204  i = 0;
205  for (j = 0; j < 3; j++)
206  BNGString[i++] = ltrnum[j];
207  divisor = pow (10.0, (5.0 - precision));
208  unitInterval = pow (10.0, (double)precision);
209 
210  east = roundBNG (easting/divisor);
211  if (east == unitInterval)
212  east -= 1;
213  if ((precision == 0) && (east == 1))
214  east = 0;
215  i += sprintf (BNGString + i, "%*.*ld", precision, precision, east);
216 
217  north = roundBNG (northing/divisor);
218  if (north == unitInterval)
219  north -= 1;
220  if ((precision == 0) && (north == 1))
221  north = 0;
222  i += sprintf (BNGString + i, "%*.*ld", precision, precision, north);
223 }
224 
225 
226 bool checkOutOfArea( char BNG500, char BNG100 )
227 {
228 /*
229  * The function checkOutOfArea checks if the 500,000 and
230  * 100,000 unit square identifications are within the valid area. If
231  * they are not, an error code is returned, otherwise BNG_NO_ERROR
232  * is returned.
233  *
234  * BNG500 : 500,000 square unit identification
235  * BNG100 : 100,000 square unit identification
236  */
237 
238  bool outOfArea = false;
239 
240  switch (BNG500)
241  {
242  case 'S':
243  switch (BNG100)
244  {
245  case 'A':
246  case 'F':
247  case 'L':
248  outOfArea = true;
249  break;
250  default:
251  break;
252  }
253  break;
254  case 'N':
255  switch (BNG100)
256  {
257  case 'V':
258  outOfArea = true;
259  break;
260  default:
261  break;
262  }
263  break;
264 
265  case 'H':
266  if (BNG100 < 'L')
267  outOfArea = true;
268  break;
269  case 'T':
270  switch (BNG100)
271  {
272  case 'D':
273  case 'E':
274  case 'J':
275  case 'K':
276  case 'O':
277  case 'P':
278  case 'T':
279  case 'U':
280  case 'X':
281  case 'Y':
282  case 'Z':
283  outOfArea = true;
284  break;
285  default:
286  break;
287  }
288  break;
289  case 'O':
290  switch (BNG100)
291  {
292  case 'C':
293  case 'D':
294  case 'E':
295  case 'J':
296  case 'K':
297  case 'O':
298  case 'P':
299  case 'T':
300  case 'U':
301  case 'Y':
302  case 'Z':
303  outOfArea = true;
304  break;
305  default:
306  break;
307  }
308  break;
309  case 'J':
310  switch (BNG100)
311  {
312  case 'L':
313  case 'M':
314  case 'Q':
315  case 'R':
316  case 'V':
317  case 'W':
319  break;
320  default:
321  outOfArea = true;
322  break;
323  }
324  break;
325  default:
326  outOfArea = true;
327  break;
328  }
329 
330  return outOfArea;
331 }
332 
333 
334 void breakBNGString( char* BNGString, char letters[3], double* easting, double* northing, long* precision )
335 {
336 /* Break down a BNG string into its component parts */
337 
338  long i = 0;
339  long j;
340  long num_digits = 0;
341  long num_letters;
342  long length = strlen(BNGString);
343 
344  while (BNGString[i] == ' ')
345  i++; /* skip any leading blanks */
346  j = i;
347  while (isalpha(BNGString[i]))
348  i++;
349  num_letters = i - j;
350  if (num_letters == 2)
351  {
352  /* get letters */
353  letters[0] = (char)toupper(BNGString[j]);
354  letters[1] = (char)toupper(BNGString[j+1]);
355  letters[2] = 0;
356  }
357  else
359 
360  checkOutOfArea(letters[0], letters[1]);
361  while (BNGString[i] == ' ')
362  i++;
363  j = i;
364  if (BNGString[length-1] == ' ')
365  length --;
366  while (i < length)
367  {
368  if (isdigit(BNGString[i]))
369  i++;
370  else
372  }
373 
374  num_digits = i - j;
375  if ((num_digits <= 10) && (num_digits%2 == 0))
376  {
377  long n;
378  char east_string[6];
379  char north_string[6];
380  long east;
381  long north;
382  double multiplier;
383 
384  /* get easting & northing */
385  n = num_digits / 2;
386  *precision = n;
387  if (n > 0)
388  {
389  strncpy (east_string, BNGString+j, n);
390  east_string[n] = 0;
391  sscanf (east_string, "%ld", &east);
392  strncpy (north_string, BNGString+j+n, n);
393  north_string[n] = 0;
394  sscanf (north_string, "%ld", &north);
395  multiplier = pow (10.0, 5.0 - n);
396  *easting = east * multiplier;
397  *northing = north * multiplier;
398  }
399  else
400  {
401  *easting = 0.0;
402  *northing = 0.0;
403  }
404  }
405  else
407 }
408 
409 
410 /************************************************************************/
411 /* FUNCTIONS
412  *
413  */
414 
416  CoordinateSystem( 6377563.396, 1 / 299.324964600 ),
417  transverseMercator( 0 ),
418  BNG_Easting( 0.0 ),
419  BNG_Northing( 0.0 )
420 {
421 /*
422  * The constructor receives the ellipsoid code and sets
423  * the corresponding state variables. If any errors occur, an exception is thrown
424  * with a description of the error.
425  *
426  * ellipsoidCode : 2-letter code for ellipsoid (input)
427  */
428 
429  strcpy( BNG_Letters, "SV" );
430  strcpy( BNG_Ellipsoid_Code, "AA");
431 
432  if ( strcmp( ellipsoidCode, Airy ) != 0 )
433  { /* Ellipsoid must be Airy */
435  }
436 
437  strcpy( BNG_Ellipsoid_Code, ellipsoidCode );
438  transverseMercator = new TransverseMercator(
440  BNG_False_Easting, BNG_False_Northing, BNG_Scale_Factor, BNG_Ellipsoid_Code );
441 }
442 
443 
445 {
446  transverseMercator = new TransverseMercator( *( bng.transverseMercator ) );
448  flattening = bng.flattening;
449  BNG_Easting = bng.BNG_Easting;
450  BNG_Northing = bng.BNG_Northing;
451 }
452 
453 
455 {
456  delete transverseMercator;
457  transverseMercator = 0;
458 }
459 
460 
462 {
463  if( this != &bng )
464  {
465  transverseMercator->operator=( *bng.transverseMercator );
467  flattening = bng.flattening;
468  BNG_Easting = bng.BNG_Easting;
469  BNG_Northing = bng.BNG_Northing;
470  }
471 
472  return *this;
473 }
474 
475 
477 {
478 /*
479  * The function getParameters returns the current ellipsoid
480  * code.
481  *
482  * ellipsoidCode : 2-letter code for ellipsoid (output)
483  */
484 
485  return new EllipsoidParameters( semiMajorAxis, flattening, (char*)BNG_Ellipsoid_Code );
486 }
487 
488 
490 {
491 /*
492  * The function convertFromGeodetic converts geodetic (latitude and
493  * longitude) coordinates to a BNG coordinate string, according to the
494  * current ellipsoid parameters. If any errors occur, an exception is thrown
495  * with a description of the error.
496  *
497  * longitude : Longitude, in radians (input)
498  * latitude : Latitude, in radians (input)
499  * precision : Precision level of BNG string (input)
500  * BNGString : British National Grid coordinate string (output)
501  *
502  */
503 
504  double TMEasting, TMNorthing;
505 
506  double longitude = geodeticCoordinates->longitude();
507  double latitude = geodeticCoordinates->latitude();
508 
509  if ((latitude < MIN_LAT) || (latitude > MAX_LAT))
510  { /* Latitude out of range */
512  }
513  if ((longitude < MIN_LON) || (longitude > MAX_LON))
514  { /* Longitude out of range */
516  }
517 
518  MapProjectionCoordinates* transverseMercatorCoordinates =
519  transverseMercator->convertFromGeodetic( geodeticCoordinates );
520 
521  TMEasting = transverseMercatorCoordinates->easting();
522  TMNorthing = transverseMercatorCoordinates->northing();
523 
524  if ((TMEasting < 0.0) && (TMEasting > -2.0))
525  transverseMercatorCoordinates->setEasting( 0.0 );
526  if ((TMNorthing < 0.0) && (TMNorthing > -2.0))
527  transverseMercatorCoordinates->setNorthing( 0.0 );
528 
529  TMEasting = transverseMercatorCoordinates->easting();
530  TMNorthing = transverseMercatorCoordinates->northing();
531 
532  if ((TMEasting < BNG_Min_Easting) || (TMEasting > BNG_Max_Easting))
533  {
534  delete transverseMercatorCoordinates;
536  }
537  if ((TMNorthing < BNG_Min_Northing) || (TMNorthing > BNG_Max_Northing))
538  {
539  delete transverseMercatorCoordinates;
541  }
542 
543  BNGCoordinates* bngCoordinates = 0;
544  try {
545  bngCoordinates = convertFromTransverseMercator( transverseMercatorCoordinates, precision );
546  delete transverseMercatorCoordinates;
547  }
548  catch ( CoordinateConversionException e ){
549  delete transverseMercatorCoordinates;
550  throw e;
551  }
552 
553  return bngCoordinates;
554 }
555 
556 
558 {
559 /*
560  * The function convertToGeodetic converts a BNG coordinate string
561  * to geodetic (latitude and longitude) coordinates, according to the current
562  * ellipsoid parameters. If any errors occur, an exception is thrown
563  * with a description of the error.
564  *
565  * BNGString : British National Grid coordinate string (input)
566  * longitude : Longitude, in radians (output)
567  * latitude : Latitude, in radians (output)
568  *
569  */
570 
571  double TMEasting, TMNorthing;
572  long in_Precision;
573 
574  char* BNGString = bngCoordinates->BNGString();
575 
576  breakBNGString( BNGString, BNG_Letters, &BNG_Easting, &BNG_Northing, &in_Precision );
577 
578  MapProjectionCoordinates* transverseMercatorCoordinates = convertToTransverseMercator( bngCoordinates );
579  TMEasting = transverseMercatorCoordinates->easting();
580  TMNorthing = transverseMercatorCoordinates->northing();
581 
582  if ((TMEasting < BNG_Min_Easting) || (TMEasting > BNG_Max_Easting))
583  {
584  delete transverseMercatorCoordinates;
586  }
587  if ((TMNorthing < BNG_Min_Northing) || (TMNorthing > BNG_Max_Northing))
588  {
589  delete transverseMercatorCoordinates;
591  }
592 
593  GeodeticCoordinates* geodeticCoordinates = 0;
594  double latitude;
595  double longitude;
596  try {
597  geodeticCoordinates = transverseMercator->convertToGeodetic( transverseMercatorCoordinates );
598  latitude = geodeticCoordinates->latitude();
599  longitude = geodeticCoordinates->longitude();
600  delete transverseMercatorCoordinates;
601  }
602  catch ( CoordinateConversionException e ){
603  delete transverseMercatorCoordinates;
604  throw e;
605  }
606 
607  if ((latitude < MIN_LAT) || (latitude > MAX_LAT))
608  {
609  delete geodeticCoordinates;
611  }
612  if ((longitude < MIN_LON) || (longitude > MAX_LON))
613  {
614  delete geodeticCoordinates;
616  }
617 
618  return geodeticCoordinates;
619 }
620 
621 
623 {
624 /*
625  * The function convertFromTransverseMercator converts Transverse Mercator
626  * (easting and northing) coordinates to a BNG coordinate string, according
627  * to the current ellipsoid parameters. If any errors occur, an exception is thrown
628  * with a description of the error.
629  *
630  * easting : Easting (X), in meters (input)
631  * northing : Northing (Y), in meters (input)
632  * precision : Precision level of BNG string (input)
633  * BNGString : British National Grid coordinate string (output)
634  */
635 
636  char letters[4];
637  long x, y;
638  long index;
639  long temp_Easting, temp_Northing;
640  char BNGString[21];
641 
642  double easting = mapProjectionCoordinates->easting();
643  double northing = mapProjectionCoordinates->northing();
644 
645  if ((easting < BNG_Min_Easting) || (easting > BNG_Max_Easting))
646  { /* Easting out of range */
648  }
649  if ((northing < BNG_Min_Northing) || (northing > BNG_Max_Northing))
650  { /* Northing out of range */
652  }
653 
654  temp_Easting = roundBNG(easting);
655  temp_Northing = roundBNG(northing);
656 
657  temp_Easting += 1000000;
658  temp_Northing += 500000;
659 
660  x = temp_Easting / 500000;
661  y = temp_Northing / 500000;
662 
663  index = y * 5 + x;
664  if ((index >= 0) && (index < 25))
665  {
666  letters[0] = BNG100GRID[index];
667  temp_Easting %= 500000;
668  temp_Northing %= 500000;
669  x = temp_Easting / 100000;
670  y = temp_Northing / 100000;
671  index = y * 5 + x;
672  if ((index >= 0) && (index < 25))
673  {
674  letters[1] = BNG100GRID[index];
675 
676  if( checkOutOfArea(letters[0], letters[1]) )
677  {
679  }
680 
681  letters[2] = ' ';
682  letters[3] = 0;
683  temp_Easting %= 100000;
684  temp_Northing %= 100000;
685  makeBNGString(letters, temp_Easting, temp_Northing, BNGString, precision);
686  }
687  else
688  {
689  long five_y = 5 * y;
690  if ((x >= (25 - five_y)) || (x < -five_y))
692  if ((five_y >= (25 - x)) || (five_y < -x))
693  {
695  }
696  }
697  }
698  else
699  {
700  long five_y = 5 * y;
701  if ((x >= (25 - five_y)) || (x < -five_y))
703  if ((five_y >= (25 - x)) || (five_y < -x))
704  {
706  }
707  }
708 
709  return new BNGCoordinates( CoordinateType::britishNationalGrid, BNGString );
710 }
711 
712 
714 {
715 /*
716  * The function convertToTransverseMercator converts a BNG coordinate string
717  * to Transverse Mercator projection (easting and northing) coordinates
718  * according to the current ellipsoid parameters. If any errors occur, an exception is thrown
719  * with a description of the error.
720  * is returned.
721  *
722  * BNGString : British National Grid coordinate string (input)
723  * easting : Easting (X), in meters (output)
724  * northing : Northing (Y), in meters (output)
725  */
726 
727  long northing_Offset, easting_Offset;
728  long i = 0;
729  long j = 0;
730  long in_Precision;
731 
732  char* BNGString = bngCoordinates->BNGString();
733 
734  breakBNGString( BNGString, BNG_Letters, &BNG_Easting, &BNG_Northing, &in_Precision );
735 
736  findIndex(BNG_Letters[0], BNG500GRID, &i);
737 
738  northing_Offset = 500000 * (i / 2);
739  easting_Offset = 500000 * (i % 2);
740 
741  findIndex(BNG_Letters[1], BNG100GRID, &j);
742 
743  northing_Offset += 100000 * (j / 5);
744  easting_Offset += 100000 * (j % 5);
745 
746  double easting = BNG_Easting + easting_Offset;
747  double northing = BNG_Northing + northing_Offset;
748 
749  return new MapProjectionCoordinates( CoordinateType::transverseMercator, easting, northing );
750 }
751 
752 
753 
754 
755 // CLASSIFICATION: UNCLASSIFIED