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
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-05 13:35 +0200
1"""Features.
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.
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``).
13This package contains the feature implementation and the ``new`` function to
14create features.
16Example::
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"""
24from typing import Optional
26import gws
27import gws.lib.shape
28import gws.lib.style
29import gws.lib.svg
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.
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.
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
56class Feature(gws.Feature):
57 """Feature object."""
59 def __init__(self, model: gws.Model):
60 """Create an empty feature.
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 = ''
75 def __repr__(self):
76 try:
77 return f'<feature {self.model.uid}:{self.uid()}>'
78 except Exception:
79 return f'<feature ?>'
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)
88 def shape(self):
89 if self.model.geometryName:
90 return self.attributes.get(self.model.geometryName)
92 def get(self, name, default=None):
93 return self.attributes.get(name, default)
95 def has(self, name):
96 return name in self.attributes
98 def set(self, name, value):
99 self.attributes[name] = value
100 return self
102 def raw(self, name):
103 return self.record.attributes.get(name)
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
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
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)
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