Coverage for gws-app/gws/lib/uom/__init__.py: 35%

77 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2025-04-17 01:37 +0200

1import re 

2 

3import gws 

4 

5MM_PER_IN = 25.4 

6"""Conversion factor from inch to millimetre""" 

7 

8PT_PER_IN = 72 

9"""Conversion factor from inch to points""" 

10 

11OGC_M_PER_PX = 0.00028 

12"""OGC meter per pixel (OGC 06-042, 7.2.4.6.9: 1px = 0.28mm).""" 

13 

14OGC_SCREEN_PPI = MM_PER_IN / (OGC_M_PER_PX * 1000) # 90.71 

15"""Pixel per inch on screen using the Open Geospatial Consortium standard""" 

16 

17PDF_DPI = 96 

18"""Dots per inch in a pdf file""" 

19 

20# 1 centimeter precision 

21 

22DEFAULT_PRECISION = { 

23 gws.Uom.deg: 7, 

24 gws.Uom.m: 2, 

25} 

26 

27_number = int | float 

28 

29 

30def scale_to_res(x: _number) -> float: 

31 """Converts the scale to the user's resolution. 

32 

33 Args: 

34 x: Scale. 

35 

36 Returns: 

37 Resolution in pixel. 

38 """ 

39 # return round(x * OGC_M_PER_PX, 4) 

40 return x * OGC_M_PER_PX 

41 

42 

43def res_to_scale(x: _number) -> int: 

44 """Converts the user's resolution to the scale. 

45 

46 Args: 

47 x: Resolution in pixel per inch. 

48 

49 Returns: 

50 Scale. 

51 """ 

52 return int(x / OGC_M_PER_PX) 

53 

54 

55# @TODO imperial units not used yet 

56# 

57# def mm_to_in(x: _number) -> float: 

58# return x / MM_PER_IN 

59# 

60# 

61# def m_to_in(x: _number) -> float: 

62# return (x / MM_PER_IN) * 1000 

63# 

64# 

65# def in_to_mm(x: _number) -> float: 

66# return x * MM_PER_IN 

67# 

68# 

69# def in_to_m(x: _number) -> float: 

70# return (x * MM_PER_IN) / 1000 

71# 

72# 

73# def in_to_px(x, ppi): 

74# return x * ppi 

75# 

76# 

77# def mm_to_pt(x: _number) -> float: 

78# return (x / MM_PER_IN) * PT_PER_IN 

79# 

80# 

81# def pt_to_mm(x: _number) -> float: 

82# return (x / PT_PER_IN) * MM_PER_IN 

83# 

84 

85## 

86 

87def mm_to_px(x: _number, ppi: int) -> float: 

88 """Converts millimetres to pixel. 

89 

90 Args: 

91 x: Millimetres. 

92 ppi: Pixels per inch. 

93 

94 Returns: 

95 Amount of pixels.""" 

96 return x * (ppi / MM_PER_IN) 

97 

98 

99def to_px(xu: gws.UomValue, ppi: int) -> gws.UomValue: 

100 """Converts a measurement to amount of pixels. 

101 

102 Args: 

103 xu: A measurement to convert to pixels. 

104 ppi: Pixels per inch. 

105 

106 Returns: 

107 A measurement. 

108 """ 

109 x, u = xu 

110 if u == gws.Uom.px: 

111 return xu 

112 if u == gws.Uom.mm: 

113 return mm_to_px(x, ppi), gws.Uom.px 

114 raise ValueError(f'invalid unit {u!r}') 

115 

116 

117def size_mm_to_px(xy: gws.Size, ppi: int) -> gws.Size: 

118 """Converts a rectangle description in millimetres to pixels. 

119 

120 Args: 

121 xy: A rectangle measurements in mm. 

122 ppi: Pixels per inch. 

123 

124 Returns: 

125 A rectangle in pixel. 

126 """ 

127 x, y = xy 

128 return mm_to_px(x, ppi), mm_to_px(y, ppi) 

129 

130 

131def size_to_px(xyu: gws.UomSize, ppi: int) -> gws.UomSize: 

132 """Converts a rectangle description of any unit to pixels. 

133 

134 Args: 

135 xyu: A rectangle measurements with its unit. 

136 ppi: Pixels per inch. 

137 

138 Returns: 

139 The rectangle measurements in pixels. 

140 """ 

141 x, y, u = xyu 

142 if u == gws.Uom.px: 

143 return xyu 

144 if u == gws.Uom.mm: 

145 return mm_to_px(x, ppi), mm_to_px(y, ppi), gws.Uom.px 

146 raise ValueError(f'invalid unit {u!r}') 

147 

148 

149## 

150 

151 

152def px_to_mm(x: _number, ppi: int) -> float: 

153 """Converts pixel to millimetres. 

154 

155 Args: 

156 x: Amount of pixels. 

157 ppi: Pixel per inch. 

158 

159 Returns: 

160 Amount of millimetres. 

161 """ 

162 return x * (MM_PER_IN / ppi) 

163 

164 

165def to_mm(xu: gws.UomValue, ppi: int) -> gws.UomValue: 

166 """Converts a measurement of any unit to millimetres. 

167 

168 Args: 

169 xu: A measurement to convert. 

170 ppi: Pixels per inch. 

171 

172 Returns: 

173 A measurement. 

174 """ 

175 x, u = xu 

176 if u == gws.Uom.mm: 

177 return xu 

178 if u == gws.Uom.px: 

179 return px_to_mm(x, ppi), gws.Uom.mm 

180 raise ValueError(f'invalid unit {u!r}') 

181 

182 

183def size_px_to_mm(xy: gws.Size, ppi: int) -> gws.Size: 

184 """Converts a rectangle description in pixel to millimetres. 

185 

186 Args: 

187 xy: A rectangle measurements in pixels. 

188 ppi: Pixel per inch 

189 

190 Returns: 

191 The rectangle measurements in millimetres. 

192 """ 

193 x, y = xy 

194 return px_to_mm(x, ppi), px_to_mm(y, ppi) 

195 

196 

197def size_to_mm(xyu: gws.UomSize, ppi: int) -> gws.UomSize: 

198 """Converts a rectangle description of any unit to millimetres. 

199 

200 Args: 

201 xyu: A rectangle measurements with its unit. 

202 ppi: Pixels per inch. 

203 

204 Returns: 

205 The rectangle measurements in millimetres. 

206 Raises: 

207 ``ValueError``: if the unit is invalid. 

208 """ 

209 x, y, u = xyu 

210 if u == gws.Uom.mm: 

211 return xyu 

212 if u == gws.Uom.px: 

213 return px_to_mm(x, ppi), px_to_mm(y, ppi), gws.Uom.mm 

214 raise ValueError(f'invalid unit {u!r}') 

215 

216 

217def to_str(xu: gws.UomValue) -> str: 

218 """Converts a to a string. 

219 

220 Args: 

221 xu: A measurement to convert. 

222 

223 Returns: 

224 The input tuple as a string, like '5mm'.""" 

225 x, u = xu 

226 sx = str(int(x)) if (x % 1 == 0) else str(x) 

227 return sx + str(u) 

228 

229 

230## 

231 

232 

233_unit_re = re.compile(r'''(?x) 

234 ^ 

235 (?P<number> 

236 -? 

237 (\d+ (\.\d*)? ) 

238 | 

239 (\.\d+) 

240 ) 

241 (?P<unit> \s* [a-zA-Z]*) 

242 $ 

243''') 

244 

245 

246def parse(s: str | int | float, default_unit: gws.Uom = None) -> gws.UomValue: 

247 """Parse a measurement in the string or numeric form. 

248 

249 Args: 

250 s: A measurement to parse. 

251 default_unit: Default unit. 

252 

253 Returns: 

254 A measurement. 

255 

256 Raises: 

257 ``ValueError``: if the unit is missing, if the formatting is wrong or if the unit is invalid. 

258 """ 

259 if isinstance(s, (int, float)): 

260 if not default_unit: 

261 raise ValueError(f'missing unit: {s!r}') 

262 return s, default_unit 

263 

264 s = gws.u.to_str(s).strip() 

265 m = _unit_re.match(s) 

266 if not m: 

267 raise ValueError(f'invalid format: {s!r}') 

268 

269 n = float(m.group('number')) 

270 u = getattr(gws.Uom, m.group('unit').strip().lower(), None) 

271 

272 if not u: 

273 if not default_unit: 

274 raise ValueError(f'invalid unit: {s!r}') 

275 return n, default_unit 

276 

277 return n, u