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
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-17 01:37 +0200
1import re
3import gws
5MM_PER_IN = 25.4
6"""Conversion factor from inch to millimetre"""
8PT_PER_IN = 72
9"""Conversion factor from inch to points"""
11OGC_M_PER_PX = 0.00028
12"""OGC meter per pixel (OGC 06-042, 7.2.4.6.9: 1px = 0.28mm)."""
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"""
17PDF_DPI = 96
18"""Dots per inch in a pdf file"""
20# 1 centimeter precision
22DEFAULT_PRECISION = {
23 gws.Uom.deg: 7,
24 gws.Uom.m: 2,
25}
27_number = int | float
30def scale_to_res(x: _number) -> float:
31 """Converts the scale to the user's resolution.
33 Args:
34 x: Scale.
36 Returns:
37 Resolution in pixel.
38 """
39 # return round(x * OGC_M_PER_PX, 4)
40 return x * OGC_M_PER_PX
43def res_to_scale(x: _number) -> int:
44 """Converts the user's resolution to the scale.
46 Args:
47 x: Resolution in pixel per inch.
49 Returns:
50 Scale.
51 """
52 return int(x / OGC_M_PER_PX)
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#
85##
87def mm_to_px(x: _number, ppi: int) -> float:
88 """Converts millimetres to pixel.
90 Args:
91 x: Millimetres.
92 ppi: Pixels per inch.
94 Returns:
95 Amount of pixels."""
96 return x * (ppi / MM_PER_IN)
99def to_px(xu: gws.UomValue, ppi: int) -> gws.UomValue:
100 """Converts a measurement to amount of pixels.
102 Args:
103 xu: A measurement to convert to pixels.
104 ppi: Pixels per inch.
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}')
117def size_mm_to_px(xy: gws.Size, ppi: int) -> gws.Size:
118 """Converts a rectangle description in millimetres to pixels.
120 Args:
121 xy: A rectangle measurements in mm.
122 ppi: Pixels per inch.
124 Returns:
125 A rectangle in pixel.
126 """
127 x, y = xy
128 return mm_to_px(x, ppi), mm_to_px(y, ppi)
131def size_to_px(xyu: gws.UomSize, ppi: int) -> gws.UomSize:
132 """Converts a rectangle description of any unit to pixels.
134 Args:
135 xyu: A rectangle measurements with its unit.
136 ppi: Pixels per inch.
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}')
149##
152def px_to_mm(x: _number, ppi: int) -> float:
153 """Converts pixel to millimetres.
155 Args:
156 x: Amount of pixels.
157 ppi: Pixel per inch.
159 Returns:
160 Amount of millimetres.
161 """
162 return x * (MM_PER_IN / ppi)
165def to_mm(xu: gws.UomValue, ppi: int) -> gws.UomValue:
166 """Converts a measurement of any unit to millimetres.
168 Args:
169 xu: A measurement to convert.
170 ppi: Pixels per inch.
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}')
183def size_px_to_mm(xy: gws.Size, ppi: int) -> gws.Size:
184 """Converts a rectangle description in pixel to millimetres.
186 Args:
187 xy: A rectangle measurements in pixels.
188 ppi: Pixel per inch
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)
197def size_to_mm(xyu: gws.UomSize, ppi: int) -> gws.UomSize:
198 """Converts a rectangle description of any unit to millimetres.
200 Args:
201 xyu: A rectangle measurements with its unit.
202 ppi: Pixels per inch.
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}')
217def to_str(xu: gws.UomValue) -> str:
218 """Converts a to a string.
220 Args:
221 xu: A measurement to convert.
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)
230##
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''')
246def parse(s: str | int | float, default_unit: gws.Uom = None) -> gws.UomValue:
247 """Parse a measurement in the string or numeric form.
249 Args:
250 s: A measurement to parse.
251 default_unit: Default unit.
253 Returns:
254 A measurement.
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
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}')
269 n = float(m.group('number'))
270 u = getattr(gws.Uom, m.group('unit').strip().lower(), None)
272 if not u:
273 if not default_unit:
274 raise ValueError(f'invalid unit: {s!r}')
275 return n, default_unit
277 return n, u