1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

286

287

288

289

290

291

292

293

294

295

296

297

298

299

300

301

302

303

304

305

306

307

308

309

310

311

312

313

314

315

316

317

318

319

320

321

322

323

324

325

326

from utm.error import OutOfRangeError 

 

# For most use cases in this module, numpy is indistinguishable 

# from math, except it also works on numpy arrays 

try: 

import numpy as mathlib 

use_numpy = True 

except ImportError: 

import math as mathlib 

use_numpy = False 

 

__all__ = ['to_latlon', 'from_latlon'] 

 

K0 = 0.9996 

 

E = 0.00669438 

E2 = E * E 

E3 = E2 * E 

E_P2 = E / (1.0 - E) 

 

SQRT_E = mathlib.sqrt(1 - E) 

_E = (1 - SQRT_E) / (1 + SQRT_E) 

_E2 = _E * _E 

_E3 = _E2 * _E 

_E4 = _E3 * _E 

_E5 = _E4 * _E 

 

M1 = (1 - E / 4 - 3 * E2 / 64 - 5 * E3 / 256) 

M2 = (3 * E / 8 + 3 * E2 / 32 + 45 * E3 / 1024) 

M3 = (15 * E2 / 256 + 45 * E3 / 1024) 

M4 = (35 * E3 / 3072) 

 

P2 = (3. / 2 * _E - 27. / 32 * _E3 + 269. / 512 * _E5) 

P3 = (21. / 16 * _E2 - 55. / 32 * _E4) 

P4 = (151. / 96 * _E3 - 417. / 128 * _E5) 

P5 = (1097. / 512 * _E4) 

 

R = 6378137 

 

ZONE_LETTERS = "CDEFGHJKLMNPQRSTUVWXX" 

 

 

def in_bounds(x, lower, upper, upper_strict=False): 

if upper_strict and use_numpy: 

return lower <= mathlib.min(x) and mathlib.max(x) < upper 

elif upper_strict and not use_numpy: 

return lower <= x < upper 

elif use_numpy: 

return lower <= mathlib.min(x) and mathlib.max(x) <= upper 

return lower <= x <= upper 

 

 

def check_valid_zone(zone_number, zone_letter): 

if not 1 <= zone_number <= 60: 

raise OutOfRangeError('zone number out of range (must be between 1 and 60)') 

 

if zone_letter: 

zone_letter = zone_letter.upper() 

 

if not 'C' <= zone_letter <= 'X' or zone_letter in ['I', 'O']: 

raise OutOfRangeError('zone letter out of range (must be between C and X)') 

 

 

def mixed_signs(x): 

return use_numpy and mathlib.min(x) < 0 and mathlib.max(x) >= 0 

 

 

def negative(x): 

if use_numpy: 

return mathlib.max(x) < 0 

return x < 0 

 

 

def mod_angle(value): 

"""Returns angle in radians to be between -pi and pi""" 

return (value + mathlib.pi) % (2 * mathlib.pi) - mathlib.pi 

 

 

def to_latlon(easting, northing, zone_number, zone_letter=None, northern=None, strict=True): 

"""This function converts UTM coordinates to Latitude and Longitude 

 

Parameters 

---------- 

easting: int or NumPy array 

Easting value of UTM coordinates 

 

northing: int or NumPy array 

Northing value of UTM coordinates 

 

zone_number: int 

Zone number is represented with global map numbers of a UTM zone 

numbers map. For more information see utmzones [1]_ 

 

zone_letter: str 

Zone letter can be represented as string values. UTM zone 

designators can be seen in [1]_ 

 

northern: bool 

You can set True or False to set this parameter. Default is None 

 

strict: bool 

Raise an OutOfRangeError if outside of bounds 

 

Returns 

------- 

latitude: float or NumPy array 

Latitude between 80 deg S and 84 deg N, e.g. (-80.0 to 84.0) 

 

longitude: float or NumPy array 

Longitude between 180 deg W and 180 deg E, e.g. (-180.0 to 180.0). 

 

 

.. _[1]: http://www.jaworski.ca/utmzones.htm 

 

""" 

if not zone_letter and northern is None: 

raise ValueError('either zone_letter or northern needs to be set') 

 

elif zone_letter and northern is not None: 

raise ValueError('set either zone_letter or northern, but not both') 

 

if strict: 

if not in_bounds(easting, 100000, 1000000, upper_strict=True): 

raise OutOfRangeError('easting out of range (must be between 100,000 m and 999,999 m)') 

if not in_bounds(northing, 0, 10000000): 

raise OutOfRangeError('northing out of range (must be between 0 m and 10,000,000 m)') 

 

check_valid_zone(zone_number, zone_letter) 

 

if zone_letter: 

zone_letter = zone_letter.upper() 

northern = (zone_letter >= 'N') 

 

x = easting - 500000 

y = northing 

 

if not northern: 

y -= 10000000 

 

m = y / K0 

mu = m / (R * M1) 

 

p_rad = (mu + 

P2 * mathlib.sin(2 * mu) + 

P3 * mathlib.sin(4 * mu) + 

P4 * mathlib.sin(6 * mu) + 

P5 * mathlib.sin(8 * mu)) 

 

p_sin = mathlib.sin(p_rad) 

p_sin2 = p_sin * p_sin 

 

p_cos = mathlib.cos(p_rad) 

 

p_tan = p_sin / p_cos 

p_tan2 = p_tan * p_tan 

p_tan4 = p_tan2 * p_tan2 

 

ep_sin = 1 - E * p_sin2 

ep_sin_sqrt = mathlib.sqrt(1 - E * p_sin2) 

 

n = R / ep_sin_sqrt 

r = (1 - E) / ep_sin 

 

c = E_P2 * p_cos**2 

c2 = c * c 

 

d = x / (n * K0) 

d2 = d * d 

d3 = d2 * d 

d4 = d3 * d 

d5 = d4 * d 

d6 = d5 * d 

 

latitude = (p_rad - (p_tan / r) * 

(d2 / 2 - 

d4 / 24 * (5 + 3 * p_tan2 + 10 * c - 4 * c2 - 9 * E_P2)) + 

d6 / 720 * (61 + 90 * p_tan2 + 298 * c + 45 * p_tan4 - 252 * E_P2 - 3 * c2)) 

 

longitude = (d - 

d3 / 6 * (1 + 2 * p_tan2 + c) + 

d5 / 120 * (5 - 2 * c + 28 * p_tan2 - 3 * c2 + 8 * E_P2 + 24 * p_tan4)) / p_cos 

 

longitude = mod_angle(longitude + mathlib.radians(zone_number_to_central_longitude(zone_number))) 

 

return (mathlib.degrees(latitude), 

mathlib.degrees(longitude)) 

 

 

def from_latlon(latitude, longitude, force_zone_number=None, force_zone_letter=None): 

"""This function converts Latitude and Longitude to UTM coordinate 

 

Parameters 

---------- 

latitude: float or NumPy array 

Latitude between 80 deg S and 84 deg N, e.g. (-80.0 to 84.0) 

 

longitude: float or NumPy array 

Longitude between 180 deg W and 180 deg E, e.g. (-180.0 to 180.0). 

 

force_zone_number: int 

Zone number is represented by global map numbers of an UTM zone 

numbers map. You may force conversion to be included within one 

UTM zone number. For more information see utmzones [1]_ 

 

force_zone_letter: str 

You may force conversion to be included within one UTM zone 

letter. For more information see utmzones [1]_ 

 

Returns 

------- 

easting: float or NumPy array 

Easting value of UTM coordinates 

 

northing: float or NumPy array 

Northing value of UTM coordinates 

 

zone_number: int 

Zone number is represented by global map numbers of a UTM zone 

numbers map. More information see utmzones [1]_ 

 

zone_letter: str 

Zone letter is represented by a string value. UTM zone designators 

can be accessed in [1]_ 

 

 

.. _[1]: http://www.jaworski.ca/utmzones.htm 

""" 

if not in_bounds(latitude, -80.0, 84.0): 

raise OutOfRangeError('latitude out of range (must be between 80 deg S and 84 deg N)') 

if not in_bounds(longitude, -180.0, 180.0): 

raise OutOfRangeError('longitude out of range (must be between 180 deg W and 180 deg E)') 

if force_zone_number is not None: 

check_valid_zone(force_zone_number, force_zone_letter) 

 

lat_rad = mathlib.radians(latitude) 

lat_sin = mathlib.sin(lat_rad) 

lat_cos = mathlib.cos(lat_rad) 

 

lat_tan = lat_sin / lat_cos 

lat_tan2 = lat_tan * lat_tan 

lat_tan4 = lat_tan2 * lat_tan2 

 

if force_zone_number is None: 

zone_number = latlon_to_zone_number(latitude, longitude) 

else: 

zone_number = force_zone_number 

 

if force_zone_letter is None: 

zone_letter = latitude_to_zone_letter(latitude) 

else: 

zone_letter = force_zone_letter 

 

lon_rad = mathlib.radians(longitude) 

central_lon = zone_number_to_central_longitude(zone_number) 

central_lon_rad = mathlib.radians(central_lon) 

 

n = R / mathlib.sqrt(1 - E * lat_sin**2) 

c = E_P2 * lat_cos**2 

 

a = lat_cos * mod_angle(lon_rad - central_lon_rad) 

a2 = a * a 

a3 = a2 * a 

a4 = a3 * a 

a5 = a4 * a 

a6 = a5 * a 

 

m = R * (M1 * lat_rad - 

M2 * mathlib.sin(2 * lat_rad) + 

M3 * mathlib.sin(4 * lat_rad) - 

M4 * mathlib.sin(6 * lat_rad)) 

 

easting = K0 * n * (a + 

a3 / 6 * (1 - lat_tan2 + c) + 

a5 / 120 * (5 - 18 * lat_tan2 + lat_tan4 + 72 * c - 58 * E_P2)) + 500000 

 

northing = K0 * (m + n * lat_tan * (a2 / 2 + 

a4 / 24 * (5 - lat_tan2 + 9 * c + 4 * c**2) + 

a6 / 720 * (61 - 58 * lat_tan2 + lat_tan4 + 600 * c - 330 * E_P2))) 

 

if mixed_signs(latitude): 

raise ValueError("latitudes must all have the same sign") 

elif negative(latitude): 

northing += 10000000 

 

return easting, northing, zone_number, zone_letter 

 

 

def latitude_to_zone_letter(latitude): 

# If the input is a numpy array, just use the first element 

# User responsibility to make sure that all points are in one zone 

if use_numpy and isinstance(latitude, mathlib.ndarray): 

latitude = latitude.flat[0] 

 

if -80 <= latitude <= 84: 

return ZONE_LETTERS[int(latitude + 80) >> 3] 

else: 

return None 

 

 

def latlon_to_zone_number(latitude, longitude): 

# If the input is a numpy array, just use the first element 

# User responsibility to make sure that all points are in one zone 

if use_numpy: 

if isinstance(latitude, mathlib.ndarray): 

latitude = latitude.flat[0] 

if isinstance(longitude, mathlib.ndarray): 

longitude = longitude.flat[0] 

 

if 56 <= latitude < 64 and 3 <= longitude < 12: 

return 32 

 

if 72 <= latitude <= 84 and longitude >= 0: 

if longitude < 9: 

return 31 

elif longitude < 21: 

return 33 

elif longitude < 33: 

return 35 

elif longitude < 42: 

return 37 

 

return int((longitude + 180) / 6) + 1 

 

 

def zone_number_to_central_longitude(zone_number): 

return (zone_number - 1) * 6 - 180 + 3