Authored by Thomas Rasch

o Added UTM conversion functions to RMProjection

@@ -54,4 +54,20 @@ @@ -54,4 +54,20 @@
54 // forward project latitude/longitude, return meters 54 // forward project latitude/longitude, return meters
55 - (RMProjectedPoint)coordinateToProjectedPoint:(CLLocationCoordinate2D)aLatLong; 55 - (RMProjectedPoint)coordinateToProjectedPoint:(CLLocationCoordinate2D)aLatLong;
56 56
  57 +#pragma mark - UTM conversions
  58 +
  59 ++ (void)convertCoordinate:(CLLocationCoordinate2D)coordinate
  60 + toUTMZoneNumber:(int *)utmZoneNumber
  61 + utmZoneLetter:(NSString **)utmZoneLetter // may be NULL
  62 + isNorthernHemisphere:(BOOL *)isNorthernHemisphere // may be NULL
  63 + easting:(double *)easting
  64 + northing:(double *)northing;
  65 +
  66 ++ (void)convertUTMZoneNumber:(int)utmZoneNumber
  67 + utmZoneLetter:(NSString *)utmZoneLetter // #utmZoneLetter will be used for calculations if not nil,
  68 + isNorthernHemisphere:(BOOL)isNorthernHemisphere // otherwise #isNorthernHemisphere
  69 + easting:(double)easting
  70 + northing:(double)northing
  71 + toCoordinate:(CLLocationCoordinate2D *)coordinate;
  72 +
57 @end 73 @end
@@ -45,6 +45,45 @@ @@ -45,6 +45,45 @@
45 @synthesize planetBounds = _planetBounds; 45 @synthesize planetBounds = _planetBounds;
46 @synthesize projectionWrapsHorizontally = _projectionWrapsHorizontally; 46 @synthesize projectionWrapsHorizontally = _projectionWrapsHorizontally;
47 47
  48 +#pragma mark - Common projections
  49 +
  50 +static RMProjection *_googleProjection = nil;
  51 +static RMProjection *_latitudeLongitudeProjection = nil;
  52 +
  53 ++ (RMProjection *)googleProjection
  54 +{
  55 + if (_googleProjection)
  56 + {
  57 + return _googleProjection;
  58 + }
  59 + else
  60 + {
  61 + RMProjectedRect theBounds = RMProjectedRectMake(-20037508.34, -20037508.34, 20037508.34 * 2, 20037508.34 * 2);
  62 +
  63 + _googleProjection = [[RMProjection alloc] initWithString:@"+title= Google Mercator EPSG:900913 +proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +no_defs"
  64 + inBounds:theBounds];
  65 + return _googleProjection;
  66 + }
  67 +}
  68 +
  69 ++ (RMProjection *)EPSGLatLong
  70 +{
  71 + if (_latitudeLongitudeProjection)
  72 + {
  73 + return _latitudeLongitudeProjection;
  74 + }
  75 + else
  76 + {
  77 + RMProjectedRect theBounds = RMProjectedRectMake(-kMaxLong, -kMaxLat, 360.0, kMaxLong);
  78 +
  79 + _latitudeLongitudeProjection = [[RMProjection alloc] initWithString:@"+proj=latlong +ellps=WGS84"
  80 + inBounds:theBounds];
  81 + return _latitudeLongitudeProjection;
  82 + }
  83 +}
  84 +
  85 +#pragma mark -
  86 +
48 - (id)initWithString:(NSString *)proj4String inBounds:(RMProjectedRect)projectedBounds 87 - (id)initWithString:(NSString *)proj4String inBounds:(RMProjectedRect)projectedBounds
49 { 88 {
50 if (!(self = [super init])) 89 if (!(self = [super init]))
@@ -149,39 +188,187 @@ @@ -149,39 +188,187 @@
149 return result_coordinate; 188 return result_coordinate;
150 } 189 }
151 190
152 -static RMProjection *_googleProjection = nil;  
153 -static RMProjection *_latitudeLongitudeProjection = nil; 191 +#pragma mark - UTM conversions
154 192
155 -+ (RMProjection *)googleProjection 193 +// This uses code by Chuck Gantz, found at http://www.gpsy.com/gpsinfo/geotoutm/
  194 +// It is limited to WGS84, have a look at the original source code if you need more.
  195 +
  196 +//
  197 +// Source
  198 +// Defense Mapping Agency. 1987b. DMA Technical Report: Supplement to Department of Defense World Geodetic System
  199 +// 1984 Technical Report. Part I and II. Washington, DC: Defense Mapping Agency
  200 +//
  201 +
  202 +#define deg2rad (M_PI / 180.0)
  203 +#define rad2deg (180.0 / M_PI)
  204 +
  205 +// This routine determines the correct UTM letter designator for the given latitude.
  206 +// Returns 'Z' if latitude is outside the UTM limits of 84N to 80S
  207 +// Written by Chuck Gantz- chuck.gantz@globalstar.com
  208 ++ (NSString *)UTMLetterDesignatorForLatitude:(double)latitude
156 { 209 {
157 - if (_googleProjection) 210 + char letterDesignator;
  211 +
  212 + if ((84 >= latitude) && (latitude >= 72)) letterDesignator = 'X';
  213 + else if ((72 > latitude) && (latitude >= 64)) letterDesignator = 'W';
  214 + else if ((64 > latitude) && (latitude >= 56)) letterDesignator = 'V';
  215 + else if ((56 > latitude) && (latitude >= 48)) letterDesignator = 'U';
  216 + else if ((48 > latitude) && (latitude >= 40)) letterDesignator = 'T';
  217 + else if ((40 > latitude) && (latitude >= 32)) letterDesignator = 'S';
  218 + else if ((32 > latitude) && (latitude >= 24)) letterDesignator = 'R';
  219 + else if ((24 > latitude) && (latitude >= 16)) letterDesignator = 'Q';
  220 + else if ((16 > latitude) && (latitude >= 8)) letterDesignator = 'P';
  221 + else if (( 8 > latitude) && (latitude >= 0)) letterDesignator = 'N';
  222 + else if (( 0 > latitude) && (latitude >= -8)) letterDesignator = 'M';
  223 + else if ((-8> latitude) && (latitude >= -16)) letterDesignator = 'L';
  224 + else if ((-16 > latitude) && (latitude >= -24)) letterDesignator = 'K';
  225 + else if ((-24 > latitude) && (latitude >= -32)) letterDesignator = 'J';
  226 + else if ((-32 > latitude) && (latitude >= -40)) letterDesignator = 'H';
  227 + else if ((-40 > latitude) && (latitude >= -48)) letterDesignator = 'G';
  228 + else if ((-48 > latitude) && (latitude >= -56)) letterDesignator = 'F';
  229 + else if ((-56 > latitude) && (latitude >= -64)) letterDesignator = 'E';
  230 + else if ((-64 > latitude) && (latitude >= -72)) letterDesignator = 'D';
  231 + else if ((-72 > latitude) && (latitude >= -80)) letterDesignator = 'C';
  232 + else letterDesignator = 'Z'; //This is here as an error flag to show that the Latitude is outside the UTM limits
  233 +
  234 + return [NSString stringWithFormat:@"%c", letterDesignator];
  235 +}
  236 +
  237 +// Converts latitude/longitude to UTM coordinates. Equations from USGS Bulletin 1532.
  238 +// East longitudes are positive, West longitudes are negative.
  239 +// North latitudes are positive, South latitudes are negative.
  240 +// Latitude and longitude are in decimal degrees.
  241 +// Written by Chuck Gantz - chuck.gantz@globalstar.com
  242 ++ (void)convertCoordinate:(CLLocationCoordinate2D)coordinate
  243 + toUTMZoneNumber:(int *)utmZoneNumber
  244 + utmZoneLetter:(NSString **)utmZoneLetter
  245 + isNorthernHemisphere:(BOOL *)isNorthernHemisphere
  246 + easting:(double *)easting
  247 + northing:(double *)northing
  248 +{
  249 + double a = 6378137.0;
  250 + double eccSquared = 0.00669438;
  251 + double k0 = 0.9996;
  252 +
  253 + double longitudeOrigin, longitudeOriginRad;
  254 + double eccPrimeSquared;
  255 + double N, T, C, A, M;
  256 +
  257 + // Make sure the longitude is between -180.00 .. 179.9
  258 + double longitudeTemp = (coordinate.longitude + 180.0) - (floor((coordinate.longitude + 180.0) / 360.0) * 360.0) - 180.0;
  259 + double latitudeRad = coordinate.latitude * deg2rad;
  260 + double longitudeRad = longitudeTemp * deg2rad;
  261 +
  262 + *utmZoneNumber = floor((longitudeTemp + 180.0) / 6.0) + 1;
  263 +
  264 + if (coordinate.latitude >= 56.0 && coordinate.latitude < 64.0 && longitudeTemp >= 3.0 && longitudeTemp < 12.0)
  265 + *utmZoneNumber = 32;
  266 +
  267 + // Special zones for Svalbard
  268 + if (coordinate.latitude >= 72.0 && coordinate.latitude < 84.0)
158 { 269 {
159 - return _googleProjection; 270 + if (longitudeTemp >= 0.0 && longitudeTemp < 9.0) *utmZoneNumber = 31;
  271 + else if (longitudeTemp >= 9.0 && longitudeTemp < 21.0) *utmZoneNumber = 33;
  272 + else if (longitudeTemp >= 21.0 && longitudeTemp < 33.0) *utmZoneNumber = 35;
  273 + else if (longitudeTemp >= 33.0 && longitudeTemp < 42.0) *utmZoneNumber = 37;
160 } 274 }
161 - else  
162 - {  
163 - RMProjectedRect theBounds = RMProjectedRectMake(-20037508.34, -20037508.34, 20037508.34 * 2, 20037508.34 * 2);  
164 275
165 - _googleProjection = [[RMProjection alloc] initWithString:@"+title= Google Mercator EPSG:900913 +proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +no_defs"  
166 - inBounds:theBounds];  
167 - return _googleProjection; 276 + longitudeOrigin = (*utmZoneNumber - 1) * 6 - 180 + 3; //+3 puts origin in middle of zone
  277 + longitudeOriginRad = longitudeOrigin * deg2rad;
  278 +
  279 + // Compute the UTM Zone from the latitude and longitude
  280 + NSString *utmLetterDesignator = [self UTMLetterDesignatorForLatitude:coordinate.latitude];
  281 +
  282 + if (utmZoneLetter != NULL)
  283 + *utmZoneLetter = utmLetterDesignator;
  284 +
  285 + if (isNorthernHemisphere != NULL)
  286 + {
  287 + char zoneLetterChar = [utmLetterDesignator UTF8String][0];
  288 + *isNorthernHemisphere = (zoneLetterChar >= 'N' && zoneLetterChar <= 'X');
168 } 289 }
  290 +
  291 + eccPrimeSquared = (eccSquared) / (1.0 - eccSquared);
  292 +
  293 + N = a / sqrt(1.0 - eccSquared * sin(latitudeRad) * sin(longitudeRad));
  294 + T = tan(latitudeRad) * tan(latitudeRad);
  295 + C = eccPrimeSquared * cos(latitudeRad) * cos(latitudeRad);
  296 + A = cos(latitudeRad) * (longitudeRad - longitudeOriginRad);
  297 +
  298 + M = a * ((1.0 - eccSquared/4 - 3*eccSquared*eccSquared/64 - 5*eccSquared*eccSquared*eccSquared/256) * latitudeRad
  299 + - (3*eccSquared/8 + 3*eccSquared*eccSquared/32 + 45*eccSquared*eccSquared*eccSquared/1024) * sin(2*latitudeRad)
  300 + + (15*eccSquared*eccSquared/256 + 45*eccSquared*eccSquared*eccSquared/1024) * sin(4*latitudeRad)
  301 + - (35*eccSquared*eccSquared*eccSquared/3072) * sin(6*latitudeRad));
  302 +
  303 + *easting = (double)(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);
  304 +
  305 + *northing = (double)(k0 * (M + N*tan(latitudeRad) * (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)));
  306 +
  307 + if (coordinate.latitude < 0)
  308 + *northing += 10000000.0; //10000000 meter offset for southern hemisphere
169 } 309 }
170 310
171 -+ (RMProjection *)EPSGLatLong 311 +// Converts UTM coords to latitude/longitude. Equations from USGS Bulletin 1532.
  312 +// East longitudes are positive, West longitudes are negative.
  313 +// North latitudes are positive, South latitudes are negative.
  314 +// Latitude and longitude are in decimal degrees.
  315 +// Written by Chuck Gantz - chuck.gantz@globalstar.com
  316 ++ (void)convertUTMZoneNumber:(int)utmZoneNumber
  317 + utmZoneLetter:(NSString *)utmZoneLetter
  318 + isNorthernHemisphere:(BOOL)isNorthernHemisphere
  319 + easting:(double)easting
  320 + northing:(double)northing
  321 + toCoordinate:(CLLocationCoordinate2D *)coordinate
172 { 322 {
173 - if (_latitudeLongitudeProjection) 323 + double k0 = 0.9996;
  324 + double a = 6378137.0;
  325 + double eccSquared = 0.00669438;
  326 +
  327 + double eccPrimeSquared;
  328 + double e1 = (1 - sqrt(1-eccSquared)) / (1 + sqrt(1-eccSquared));
  329 + double N1, T1, C1, R1, D, M;
  330 + double longitudeOrigin;
  331 + double mu, phi1, phi1Rad;
  332 + double x, y, latitude, longitude;
  333 +
  334 + x = easting - 500000.0; // remove 500,000 meter offset for longitude
  335 + y = northing;
  336 +
  337 + if (utmZoneLetter != nil)
174 { 338 {
175 - return _latitudeLongitudeProjection; 339 + char zoneLetter = [utmZoneLetter UTF8String][0];
  340 + if ((zoneLetter >= 'c' && zoneLetter <= 'm') || (zoneLetter >= 'C' && zoneLetter <= 'M'))
  341 + y -= 10000000.0; // remove 10,000,000 meter offset used for southern hemisphere
176 } 342 }
177 - else 343 + else if ( ! isNorthernHemisphere)
178 { 344 {
179 - RMProjectedRect theBounds = RMProjectedRectMake(-kMaxLong, -kMaxLat, 360.0, kMaxLong);  
180 -  
181 - _latitudeLongitudeProjection = [[RMProjection alloc] initWithString:@"+proj=latlong +ellps=WGS84"  
182 - inBounds:theBounds];  
183 - return _latitudeLongitudeProjection; 345 + y -= 10000000.0; // remove 10,000,000 meter offset used for southern hemisphere
184 } 346 }
  347 +
  348 + longitudeOrigin = (utmZoneNumber - 1)*6 - 180 + 3; //+3 puts origin in middle of zone
  349 +
  350 + eccPrimeSquared = (eccSquared) / (1-eccSquared);
  351 +
  352 + M = y / k0;
  353 + mu = M / (a * (1 - eccSquared/4 - 3*eccSquared*eccSquared/64 - 5*eccSquared*eccSquared*eccSquared/256));
  354 +
  355 + phi1Rad = 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);
  356 + phi1 = phi1Rad * rad2deg;
  357 +
  358 + N1 = a / sqrt(1 - eccSquared*sin(phi1Rad)*sin(phi1Rad));
  359 + T1 = tan(phi1Rad) * tan(phi1Rad);
  360 + C1 = eccPrimeSquared * cos(phi1Rad) * cos(phi1Rad);
  361 + R1 = a * (1 - eccSquared) / pow(1 - eccSquared*sin(phi1Rad)*sin(phi1Rad), 1.5);
  362 + D = x / (N1 * k0);
  363 +
  364 + latitude = phi1Rad - (N1 * tan(phi1Rad) / 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);
  365 + latitude = latitude * rad2deg;
  366 +
  367 + 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(phi1Rad);
  368 + longitude = longitudeOrigin + longitude * rad2deg;
  369 +
  370 + (*coordinate).latitude = latitude;
  371 + (*coordinate).longitude = longitude;
185 } 372 }
186 373
187 @end 374 @end
@@ -140,6 +140,30 @@ @@ -140,6 +140,30 @@
140 center.latitude = 47.5635; 140 center.latitude = 47.5635;
141 center.longitude = 10.20981; 141 center.longitude = 10.20981;
142 142
  143 +// int zoneNumber;
  144 +// BOOL isNorthernHemisphere;
  145 +// NSString *utmZone;
  146 +// double easting, northing;
  147 +//
  148 +// [RMProjection convertCoordinate:center
  149 +// toUTMZoneNumber:&zoneNumber
  150 +// utmZoneLetter:&utmZone
  151 +// isNorthernHemisphere:&isNorthernHemisphere
  152 +// easting:&easting
  153 +// northing:&northing];
  154 +//
  155 +// NSLog(@"{%f,%f} -> %d%@ %.0f %.0f (north: %@)", center.latitude, center.longitude, zoneNumber, utmZone, easting, northing, isNorthernHemisphere ? @"YES" : @"NO");
  156 +//
  157 +// CLLocationCoordinate2D coordinate;
  158 +// [RMProjection convertUTMZoneNumber:zoneNumber
  159 +// utmZoneLetter:utmZone
  160 +// isNorthernHemisphere:isNorthernHemisphere
  161 +// easting:easting
  162 +// northing:northing
  163 +// toCoordinate:&coordinate];
  164 +//
  165 +// NSLog(@"-> {%f,%f}", coordinate.latitude, coordinate.longitude);
  166 +
143 // [mapView zoomWithLatitudeLongitudeBoundsSouthWest:CLLocationCoordinate2DMake(47.5, 10.15) northEast:CLLocationCoordinate2DMake(47.6, 10.25) animated:NO]; 167 // [mapView zoomWithLatitudeLongitudeBoundsSouthWest:CLLocationCoordinate2DMake(47.5, 10.15) northEast:CLLocationCoordinate2DMake(47.6, 10.25) animated:NO];
144 168
145 [mapView setZoom:10.0]; 169 [mapView setZoom:10.0];