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

56 statements  

« prev     ^ index     » next       coverage.py v7.16.2, created at 2026-10-05 13:35 +0200

1"""Default map object.""" 

2 

3from typing import Optional 

4 

5import gws 

6import gws.base.layer 

7import gws.lib.crs 

8import gws.lib.bounds 

9import gws.lib.extent 

10import gws.gis.zoom 

11 

12 

13@gws.ext.config.map('default') 

14class Config(gws.Config): 

15 """Map with its layers, extent, CRS and zoom levels.""" 

16 

17 center: Optional[gws.Point] 

18 """Initial map center.""" 

19 coordinatePrecision: Optional[int] 

20 """Decimal places for coordinates.""" 

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

22 """CRS of the map and its layers.""" 

23 extent: Optional[gws.Extent] 

24 """Map extent in map CRS coordinates.""" 

25 extentBuffer: Optional[int] 

26 """Buffer added around the configured extent, in map units.""" 

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

28 """Map layers.""" 

29 title: str = '' 

30 """Map title, used as the root layer title.""" 

31 wrapX: bool = False 

32 """Repeat the world horizontally in the client.""" 

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

34 """Allowed scales or zoom levels and the initial zoom.""" 

35 

36 

37@gws.ext.props.map('default') 

38class Props(gws.Data): 

39 crs: str 

40 crsDef: Optional[str] 

41 coordinatePrecision: int 

42 extent: gws.Extent 

43 center: gws.Point 

44 initResolution: float 

45 rootLayer: gws.base.layer.Props 

46 resolutions: list[float] 

47 title: str = '' 

48 wrapX: bool 

49 

50 

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

52 """Root layer of a map.""" 

53 

54 parent: 'Object' 

55 """The map.""" 

56 

57 

58@gws.ext.object.map('default') 

59class Object(gws.Map): 

60 """Default map.""" 

61 

62 wrapX: bool 

63 """Repeat the world horizontally in the client.""" 

64 

65 def configure(self): 

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

67 

68 p = self.cfg('crs') 

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

70 

71 p = self.cfg('extent') 

72 if p: 

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

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

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

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

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

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

79 else: 

80 self.wgsExtent = crs.wgsMaxExtent 

81 self.bounds = gws.Bounds(crs=crs, extent=gws.lib.extent.transform_from_wgs(self.wgsExtent, crs)) 

82 

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

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

85 

86 p = self.cfg('zoom') 

87 gws.gis.zoom.warn_deprecated_options(p, self.root) 

88 self.resolutions = gws.gis.zoom.resolutions_from_config(p, crs=self.bounds.crs) 

89 self.initResolution = gws.gis.zoom.init_resolution(p, self.resolutions, crs=self.bounds.crs) 

90 

91 p = self.cfg('coordinatePrecision') 

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

93 

94 self.rootLayer = self.create_child( 

95 gws.ext.object.layer, 

96 type='group', 

97 title=self.title, 

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

99 _parentWgsExtent=self.wgsExtent, 

100 _mapCrs=self.bounds.crs, 

101 _parentResolutions=self.resolutions, 

102 ) 

103 

104 if not self.rootLayer: 

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

106 

107 def props(self, user): 

108 return gws.Data( 

109 crs=self.bounds.crs.epsg, 

110 crsDef=self.bounds.crs.proj4text, 

111 coordinatePrecision=self.coordinatePrecision, 

112 extent=self.bounds.extent, 

113 center=self.center, 

114 initResolution=self.initResolution, 

115 rootLayer=self.rootLayer, 

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

117 title=self.title, 

118 wrapX=self.wrapX, 

119 )