Coverage for gws-app/gws/base/feature/__init__.py: 75%

69 statements  

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

1"""Features. 

2 

3A feature is a set of named attributes that belongs to a model. The model 

4defines which attribute is the unique id (``uidName``) and which one holds the 

5geometry (``geometryName``), as a ``gws.Shape``. See ``gws.base.model`` for how 

6models read and write features. 

7 

8Besides its attributes, a feature carries the raw data from its source 

9(``record``), the data from or for the client (``props``), rendered views 

10(``views``, e.g. ``title`` or ``label`` from feature templates) and validation 

11errors (``errors``). 

12 

13This package contains the feature implementation and the ``new`` function to 

14create features. 

15 

16Example:: 

17 

18 f = gws.base.feature.new(model=model, attributes={'id': 1, 'name': 'A'}) 

19 f.set('geom', gws.lib.shape.from_wkt('POINT(1 2)', crs)) 

20 f.uid() # '1', if the model's uidName is 'id' 

21 f.to_geojson() 

22""" 

23 

24from typing import Optional 

25 

26import gws 

27import gws.lib.shape 

28import gws.lib.style 

29import gws.lib.svg 

30 

31 

32def new( 

33 model: gws.Model, 

34 attributes: Optional[dict] = None, 

35 record: Optional[gws.FeatureRecord] = None, 

36 props: Optional[gws.FeatureProps] = None, 

37) -> gws.Feature: 

38 """Create a feature. 

39 

40 Args: 

41 model: Model the feature belongs to. 

42 attributes: Feature attributes. 

43 record: Raw data from the source. Defaults to an empty record. 

44 props: Data from the client. Defaults to empty props. 

45 

46 Returns: 

47 The feature. 

48 """ 

49 f = Feature(model) 

50 f.attributes = attributes or {} 

51 f.record = record or gws.FeatureRecord(attributes={}) 

52 f.props = props or gws.FeatureProps(attributes={}) 

53 return f 

54 

55 

56class Feature(gws.Feature): 

57 """Feature object.""" 

58 

59 def __init__(self, model: gws.Model): 

60 """Create an empty feature. 

61 

62 Args: 

63 model: Model the feature belongs to. 

64 """ 

65 self.attributes = {} 

66 self.category = '' 

67 self.cssSelector = '' 

68 self.errors = [] 

69 self.isNew = False 

70 self.model = model 

71 self.views = {} 

72 self.createWithFeatures = [] 

73 self.insertedPrimaryKey = '' 

74 

75 def __repr__(self): 

76 try: 

77 return f'<feature {self.model.uid}:{self.uid()}>' 

78 except Exception: 

79 return f'<feature ?>' 

80 

81 def uid(self): 

82 if not self.model.uidName: 

83 return '' 

84 v = self.attributes.get(self.model.uidName) 

85 if v is not None: 

86 return str(v) 

87 

88 def shape(self): 

89 if self.model.geometryName: 

90 return self.attributes.get(self.model.geometryName) 

91 

92 def get(self, name, default=None): 

93 return self.attributes.get(name, default) 

94 

95 def has(self, name): 

96 return name in self.attributes 

97 

98 def set(self, name, value): 

99 self.attributes[name] = value 

100 return self 

101 

102 def raw(self, name): 

103 return self.record.attributes.get(name) 

104 

105 def render_views(self, templates, **kwargs): 

106 tri = gws.TemplateRenderInput( 

107 args=gws.u.merge( 

108 self.attributes, 

109 kwargs, 

110 feature=self, 

111 ) 

112 ) 

113 for tpl in templates: 

114 view_name = tpl.subject.split('.')[-1] 

115 self.views[view_name] = tpl.render(tri).content 

116 return self 

117 

118 def transform_to(self, crs) -> gws.Feature: 

119 s = self.shape() 

120 if s: 

121 self.attributes[self.model.geometryName] = s.transformed_to(crs) 

122 return self 

123 

124 def to_svg(self, view, label=None, style=None): 

125 s = self.shape() 

126 if not s: 

127 return [] 

128 shape = s.transformed_to(view.bounds.crs) 

129 return gws.lib.svg.shape_to_fragment(shape, view, label, style) 

130 

131 def to_geojson(self, keep_crs=False) -> dict: 

132 d = { 

133 'type': 'Feature', 

134 'properties': { 

135 k: v # noqa 

136 for k, v in self.attributes.items() 

137 if k != self.model.geometryName 

138 }, 

139 } 

140 d['properties']['id'] = self.uid() 

141 s = self.shape() 

142 if s: 

143 d['geometry'] = s.to_geojson(keep_crs=keep_crs) 

144 return d