Coverage for gws-app/gws/plugin/model_field/geometry/__init__.py: 84%
93 statements
« prev ^ index » next coverage.py v7.17.0, created at 2026-10-23 20:06 +0200
« prev ^ index » next coverage.py v7.17.0, created at 2026-10-23 20:06 +0200
1"""Geometry field.
3A scalar field for geometries, stored as ``gws.Shape`` objects. The geometry
4type and the CRS are taken from the config or, if not configured, from the
5database column. A field without a known CRS is a configuration error.
7The field supports geometry searches: when a model is searched with a shape
8(optionally widened by a tolerance) or with bounds, the field adds an
9``ST_Intersects`` condition on its column. Shapes from the client are
10transformed to the field CRS. Without a configured widget, the field uses a
11``geometry`` widget.
13Example::
15 fields+ {
16 name "geom"
17 type "geometry"
18 geometryType "polygon"
19 crs "EPSG:25832"
20 }
21"""
23from typing import Optional, cast
25import gws
26import gws.base.database.model
27import gws.base.database.util
28import gws.base.model.scalar_field
29import gws.lib.shape
30import gws.lib.crs
33@gws.ext.config.modelField('geometry')
34class Config(gws.base.model.scalar_field.Config):
35 """Field for geometries."""
37 geometryType: Optional[gws.GeometryType]
38 """Geometry type."""
39 crs: Optional[gws.CrsName]
40 """CRS of the geometries."""
43@gws.ext.props.modelField('geometry')
44class Props(gws.base.model.scalar_field.Props):
45 geometryType: gws.GeometryType
48@gws.ext.object.modelField('geometry')
49class Object(gws.base.model.scalar_field.Object):
50 """Geometry field object."""
52 attributeType = gws.AttributeType.geometry
53 supportsGeometrySearch = True
55 geometryType: gws.GeometryType
56 """Geometry type of the field."""
57 geometryCrs: gws.Crs
58 """CRS of the stored geometries."""
60 def configure(self):
61 setattr(self, 'geometryType', None)
62 setattr(self, 'geometryCrs', None)
63 self.configure_geometry_type()
64 self.configure_geometry_crs()
65 if not self.geometryCrs:
66 tab = getattr(self.model, 'tableName', '')
67 raise gws.ConfigurationError(f'unknown CRS for {tab!r}.{self.name!r}')
69 def configure_geometry_type(self):
70 """Set the geometry type from the config or from the database column.
72 Returns:
73 True if the geometry type was set, None otherwise.
74 """
75 s = self.cfg('geometryType')
76 if s:
77 self.geometryType = s
78 return True
80 col = self.describe()
81 if col and col.geometryType:
82 self.geometryType = col.geometryType
83 return True
85 def configure_geometry_crs(self):
86 """Set the CRS from the config or from the SRID of the database column.
88 Returns:
89 True if the CRS was set, None otherwise.
90 """
91 s = self.cfg('crs')
92 if s:
93 self.geometryCrs = gws.lib.crs.get(s)
94 return True
96 col = self.describe()
97 if col and col.geometrySrid:
98 crs = gws.lib.crs.get(col.geometrySrid)
99 if crs:
100 self.geometryCrs = crs
101 return True
103 def configure_widget(self):
104 if not super().configure_widget():
105 self.widget = self.root.create_shared(gws.ext.object.modelWidget, type='geometry')
106 return True
108 ##
110 def props(self, user):
111 return gws.u.merge(super().props(user), geometryType=self.geometryType)
113 ##
115 def before_select(self, mc):
116 super().before_select(mc)
118 shape = None
120 if mc.search.shape:
121 shape = mc.search.shape
122 if mc.search.tolerance:
123 tol_value, tol_unit = mc.search.tolerance
124 if tol_unit == gws.Uom.px:
125 tol_value *= mc.search.resolution
126 shape = shape.tolerance_polygon(tol_value)
127 elif mc.search.bounds:
128 shape = gws.lib.shape.from_bounds(mc.search.bounds)
130 if shape:
131 model = cast(gws.base.database.model.Object, self.model)
132 col = model.column(self.name)
133 mc.dbSelect.geometryWhere.append(gws.base.database.util.shape_intersects_clause(col, shape, self.geometryCrs))
135 def raw_to_python(self, feature, value, mc):
136 # here, value is a geosa WKBElement
137 return gws.lib.shape.from_wkb_hex(str(value))
139 def prop_to_python(self, feature, value, mc):
140 shape = self._prop_to_shape(value)
141 if shape:
142 return shape.transformed_to(self.geometryCrs)
143 return gws.ErrorValue
145 def python_to_raw(self, feature, value, mc):
146 return value.to_ewkb_hex()
148 def python_to_prop(self, feature, value, mc):
149 return cast(gws.Shape, value).to_props()
151 def _prop_to_shape(self, value):
152 """Convert a shape, shape props or a dict to a shape, or return None."""
153 if isinstance(value, gws.lib.shape.Shape):
154 return value
155 if gws.u.is_data_object(value):
156 try:
157 return gws.lib.shape.from_props(value)
158 except gws.Error:
159 pass
160 if gws.u.is_dict(value):
161 try:
162 return gws.lib.shape.from_dict(value)
163 except gws.Error:
164 pass