Coverage for gws-app/gws/base/legend/core.py: 89%

54 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-10-16 23:09 +0200

1from typing import Optional 

2 

3import gws 

4import gws.lib.image 

5import gws.lib.mime 

6 

7 

8class Props(gws.Props): 

9 type: str 

10 

11 

12class Config(gws.ConfigWithAccess): 

13 """Layer legend confuguration.""" 

14 

15 cacheMaxAge: gws.Duration = '1d' 

16 """Max cache age for remote legends.""" 

17 options: Optional[dict] 

18 """Provider-dependent legend options.""" 

19 

20 

21class Object(gws.Legend): 

22 """Generic legend object.""" 

23 

24 cacheMaxAge: int 

25 options: dict 

26 

27 def configure(self): 

28 self.options = self.cfg('options', default={}) 

29 self.cacheMaxAge = self.cfg('cacheMaxAge', default=3600 * 24) 

30 

31 

32def output_to_bytes(lro: gws.LegendRenderOutput) -> Optional[bytes]: 

33 """Convert a LegendRenderOutput to raw image bytes. 

34 

35 Args: 

36 lro: The legend render output object. 

37 

38 Returns: 

39 The image encoded as bytes if available, otherwise None. 

40 """ 

41 img = output_to_image(lro) 

42 return img.to_bytes() if img else None 

43 

44 

45def output_to_image(lro: gws.LegendRenderOutput) -> Optional[gws.Image]: 

46 """Extract an image object from a LegendRenderOutput. 

47 

48 Args: 

49 lro: The legend render output object. 

50 

51 Returns: 

52 The image object if available, otherwise None (e.g. when only HTML is set). 

53 """ 

54 if lro.image: 

55 return lro.image 

56 if lro.image_path: 

57 return gws.lib.image.from_path(lro.image_path) 

58 if lro.html: 

59 return None 

60 

61 

62def output_to_image_path(lro: gws.LegendRenderOutput) -> Optional[str]: 

63 """Resolve the file path to the image of a LegendRenderOutput. 

64 

65 Args: 

66 lro: The legend render output object. 

67 

68 Returns: 

69 Path to the image file if available, otherwise None. 

70 """ 

71 if lro.image: 

72 img_path = gws.u.ephemeral_path('legend.png') 

73 return lro.image.to_path(img_path, gws.lib.mime.PNG) 

74 if lro.image_path: 

75 return lro.image_path 

76 if lro.html: 

77 return None 

78 

79 

80def combine_outputs(lros: list[gws.LegendRenderOutput], options: dict = None) -> Optional[gws.LegendRenderOutput]: 

81 """Combine multiple LegendRenderOutputs into a single output. 

82 

83 Args: 

84 lros: A list of legend render outputs to combine. 

85 options: Optional combination settings (currently unused). 

86 

87 Returns: 

88 A new LegendRenderOutput containing the combined image, 

89 or None if no images were provided. 

90 """ 

91 imgs = gws.u.compact(output_to_image(lro) for lro in lros) 

92 img = _combine_images(imgs, options) 

93 if not img: 

94 return None 

95 return gws.LegendRenderOutput(image=img, size=img.size()) 

96 

97 

98def _combine_images(images: list[gws.Image], options: dict = None) -> Optional[gws.Image]: 

99 if not images: 

100 return None 

101 # @TODO other combination options 

102 return _combine_vertically(images) 

103 

104 

105def _combine_vertically(images: list[gws.Image]): 

106 ws = [img.size()[0] for img in images] 

107 hs = [img.size()[1] for img in images] 

108 

109 comp = gws.lib.image.from_size((max(ws), sum(hs))) 

110 y = 0 

111 for img in images: 

112 comp.paste(img, (0, y)) 

113 y += img.size()[1] 

114 

115 return comp