Coverage for C: \ Users \ peaco \ OneDrive \ Documents \ GitHub \ mt_metadata \ mt_metadata \ utils \ location_helpers.py: 77%

66 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-01-10 00:11 -0800

1# =============================================================== 

2# imports 

3# =============================================================== 

4import numpy as np 

5from loguru import logger 

6 

7 

8# =============================================================== 

9 

10 

11def convert_position_float2str(position: float | int) -> str: 

12 """ 

13 Convert position float to a string in the format of DD:MM:SS.ms 

14 

15 Parameters 

16 ---------- 

17 position : float | int 

18 decimal degrees of latitude or longitude 

19 

20 Returns 

21 ------- 

22 str 

23 latitude or longitude in format of DD:MM:SS.ms 

24 """ 

25 if not isinstance(position, (float, int)): 

26 msg = f"position must be a float or int, not {type(position)}" 

27 logger.error(msg) 

28 raise TypeError(msg) 

29 

30 deg = int(position) 

31 sign = 1 

32 if deg < 0: 

33 sign = -1 

34 

35 deg = abs(deg) 

36 minutes = (abs(position) - deg) * 60.0 

37 # need to round seconds to 4 decimal places otherwise machine precision 

38 # keeps the 60 second roll over and the string is incorrect. 

39 sec = np.round((minutes - int(minutes)) * 60.0, 4) 

40 if sec >= 60.0: 

41 minutes += 1 

42 sec = 0 

43 

44 if int(minutes) == 60: 

45 deg += 1 

46 minutes = 0 

47 

48 position_str = f"{sign * int(deg)}:{int(minutes):02.0f}:{sec:05.6f}" 

49 logger.debug(f"Converted {position} to {position_str}") 

50 

51 return position_str 

52 

53 

54def convert_position_str2float(position_str: str) -> float: 

55 """ 

56 Convert a position string in the format of DD:MM:SS to decimal degrees 

57 

58 :param position: latitude or longitude om DD:MM:SS.ms 

59 :type position: float 

60 

61 :returns: latitude or longitude as a float 

62 

63 Parameters 

64 ---------- 

65 position_str : str 

66 latitude or longitude om DD:MM:SS.ms 

67 

68 Returns 

69 ------- 

70 float 

71 latitude or longitude as a float 

72 

73 Raises 

74 ------ 

75 ValueError 

76 If position string cannot be converted to a float or 

77 if the format is incorrect. 

78 """ 

79 

80 if position_str in [None, "None"]: 

81 return 0.0 

82 

83 p_list = position_str.split(":") 

84 if len(p_list) != 3: 

85 msg = f"{position_str} not correct format, should be DD:MM:SS" 

86 logger.error(msg) 

87 raise ValueError(msg) 

88 

89 deg = float(p_list[0]) 

90 minutes = _assert_minutes(float(p_list[1])) 

91 sec = _assert_seconds(float(p_list[2])) 

92 

93 # get the sign of the position so that when all are added together the 

94 # position is in the correct place 

95 sign = 1 

96 if deg < 0: 

97 sign = -1 

98 

99 position_value = sign * (abs(deg) + minutes / 60.0 + sec / 3600.0) 

100 

101 logger.debug(f"Converted {position_str} to {position_value}") 

102 

103 return position_value 

104 

105 

106def _assert_minutes(minutes: float | int) -> float: 

107 """ 

108 Assert minutes are between 0 and 60 

109 

110 Parameters 

111 ---------- 

112 minutes : float | int 

113 number of minutesto be checked 

114 

115 Returns 

116 ------- 

117 float 

118 number of minutes if valid 

119 """ 

120 if not 0 <= minutes < 60.0: 

121 msg = ( 

122 f"minutes should be 0 < > 60, currently {minutes:.0f} " 

123 "conversion will account for non-uniform " 

124 "time. Be sure to check accuracy." 

125 ) 

126 logger.warning(msg) 

127 

128 return minutes 

129 

130 

131def _assert_seconds(seconds: float | int) -> float: 

132 """ 

133 Assert seconds are between 0 and 60 

134 

135 Parameters 

136 ---------- 

137 seconds : float | int 

138 number of seconds to be checked 

139 

140 Returns 

141 ------- 

142 float 

143 number of seconds if valid 

144 """ 

145 if not 0 <= seconds < 60.0: 

146 msg = ( 

147 f"seconds should be 0 < > 60, currently {seconds:.0f} " 

148 "conversion will account for non-uniform " 

149 "time. Be sure to check accuracy." 

150 ) 

151 logger.warning(msg) 

152 

153 return seconds 

154 

155 

156def validate_position(value: str | float, position_type: str) -> float: 

157 """ 

158 Validate position value (latitude or longitude) and convert to float. 

159 

160 Parameters 

161 ---------- 

162 value : str | float 

163 The position value to validate and convert. 

164 position_type : str 

165 The type of position ('latitude' or 'longitude'). 

166 

167 Returns 

168 ------- 

169 float 

170 The validated and converted position value. 

171 

172 Raises 

173 ------ 

174 ValueError 

175 If the value is not a valid latitude or longitude. 

176 """ 

177 if position_type not in ["latitude", "longitude"]: 

178 raise ValueError("position_type must be 'latitude' or 'longitude'") 

179 

180 if value in [None, "None"]: 

181 return 0.0 

182 

183 elif isinstance(value, str) and ":" in value: 

184 value = convert_position_str2float(value) 

185 else: 

186 try: 

187 value = float(value) 

188 except ValueError: 

189 raise ValueError("latitude and longitude must be float or str") 

190 

191 if not (abs(value) <= 90) and position_type in ["latitude", "lat"]: 

192 raise ValueError("latitude must be between -90 and 90 degrees") 

193 if not (abs(value) <= 180) and position_type in ["longitude", "lon"]: 

194 raise ValueError("longitude must be between -180 and 180 degrees") 

195 return value