Coverage for gws-app/gws/base/map/core.py: 93%

70 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.base.layer 

5import gws.lib.crs 

6import gws.lib.bounds 

7import gws.lib.extent 

8import gws.gis.zoom 

9 

10gws.ext.new.map('default') 

11 

12 

13class Config(gws.Config): 

14 """Map configuration""" 

15 

16 center: Optional[gws.Point] 

17 """Map center.""" 

18 coordinatePrecision: Optional[int] 

19 """Precision for coordinates.""" 

20 crs: Optional[gws.CrsName] = 'EPSG:3857' 

21 """Crs for this map.""" 

22 extent: Optional[gws.Extent] 

23 """Map extent.""" 

24 extentBuffer: Optional[int] 

25 """Extent buffer.""" 

26 layers: list[gws.ext.config.layer] 

27 """Collection of layers for this map.""" 

28 title: str = '' 

29 """Map title.""" 

30 wrapX: bool = False 

31 """Wrap the world horizontally.""" 

32 zoom: Optional[gws.gis.zoom.Config] 

33 """Map scales and resolutions.""" 

34 

35 

36class Props(gws.Data): 

37 crs: str 

38 crsDef: Optional[str] 

39 coordinatePrecision: int 

40 extent: gws.Extent 

41 center: gws.Point 

42 initResolution: float 

43 rootLayer: gws.base.layer.Props 

44 resolutions: list[float] 

45 title: str = '' 

46 wrapX: bool 

47 

48 

49class _RootLayer(gws.base.layer.group.Object): 

50 parent: 'Object' 

51 

52 

53class Object(gws.Map): 

54 wrapX: bool 

55 

56 def configure(self): 

57 self.title = self.cfg('title') or self.cfg('_defaultTitle') or '' 

58 

59 p = self.cfg('crs') 

60 crs = gws.lib.crs.require(p) if p else gws.lib.crs.WEBMERCATOR 

61 

62 p = self.cfg('extent') 

63 if p: 

64 ext = gws.lib.extent.from_list(p) 

65 if not ext or not gws.lib.extent.is_valid(ext): 

66 raise gws.ConfigurationError(f'invalid extent {p!r}') 

67 buf = self.cfg('extentBuffer') or 0 

68 self.bounds = gws.Bounds(crs=crs, extent=gws.lib.extent.buffer(ext, buf)) 

69 else: 

70 self.bounds = gws.Bounds(crs=crs, extent=crs.extent) 

71 

72 self.center = self.cfg('center') or gws.lib.extent.center(self.bounds.extent) 

73 self.wgsExtent = gws.lib.extent.transform_to_wgs(self.bounds.extent, self.bounds.crs) 

74 self.wrapX = self.cfg('wrapX', default=False) 

75 

76 p = self.cfg('zoom') 

77 if p: 

78 self.resolutions = gws.gis.zoom.resolutions_from_config(p) 

79 self.initResolution = gws.gis.zoom.init_resolution(p, self.resolutions) 

80 else: 

81 self.resolutions = gws.gis.zoom.OSM_RESOLUTIONS 

82 self.initResolution = self.resolutions[len(self.resolutions) >> 1] 

83 

84 p = self.cfg('coordinatePrecision') 

85 self.coordinatePrecision = p or self.bounds.crs.coordinatePrecision 

86 

87 self.rootLayer = self.create_child( 

88 gws.ext.object.layer, 

89 type='group', 

90 title=self.title, 

91 layers=self.cfg('layers'), 

92 _parentBounds=self.bounds, 

93 _parentResolutions=self.resolutions, 

94 ) 

95 

96 if not self.rootLayer: 

97 raise gws.Error(f'missing or invalid root layer in {self!r}') 

98 

99 def props(self, user): 

100 return gws.Data( 

101 crs=self.bounds.crs.epsg, 

102 crsDef=self.bounds.crs.proj4text, 

103 coordinatePrecision=self.coordinatePrecision, 

104 extent=self.bounds.extent, 

105 center=self.center, 

106 initResolution=self.initResolution, 

107 rootLayer=self.rootLayer, 

108 resolutions=sorted(self.resolutions, reverse=True), 

109 title=self.title, 

110 wrapX=self.wrapX, 

111 )