Coverage for gws-app/gws/plugin/model_field/geometry/__init__.py: 83%
95 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-16 23:09 +0200
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-16 23:09 +0200
1"""Geometry field."""
3from typing import Optional, cast
5import gws
6import gws.base.database.model
7import gws.base.model.scalar_field
8import gws.base.shape
9import gws.lib.crs
10import gws.lib.sa as sa
13gws.ext.new.modelField('geometry')
16class Config(gws.base.model.scalar_field.Config):
17 """Geometry field configuration."""
19 geometryType: Optional[gws.GeometryType]
20 """Geometry type, e.g. point, line, polygon."""
21 crs: Optional[gws.CrsName]
22 """Coordinate Reference System (CRS) name, e.g. 'EPSG:4326'."""
25class Props(gws.base.model.scalar_field.Props):
26 geometryType: gws.GeometryType
29class Object(gws.base.model.scalar_field.Object):
30 attributeType = gws.AttributeType.geometry
31 supportsGeometrySearch = True
33 geometryType: gws.GeometryType
34 geometryCrs: gws.Crs
36 def configure(self):
37 setattr(self, 'geometryType', None)
38 setattr(self, 'geometryCrs', None)
39 self.configure_geometry_type()
40 self.configure_geometry_crs()
41 if not self.geometryCrs:
42 tab = getattr(self.model, 'tableName', '')
43 raise gws.ConfigurationError(f'unknown CRS for {tab!r}.{self.name!r}')
45 def configure_geometry_type(self):
46 s = self.cfg('geometryType')
47 if s:
48 self.geometryType = s
49 return True
51 col = self.describe()
52 if col and col.geometryType:
53 self.geometryType = col.geometryType
54 return True
56 def configure_geometry_crs(self):
57 s = self.cfg('crs')
58 if s:
59 self.geometryCrs = gws.lib.crs.get(s)
60 return True
62 col = self.describe()
63 if col and col.geometrySrid:
64 crs = gws.lib.crs.get(col.geometrySrid)
65 if crs:
66 self.geometryCrs = crs
67 return True
69 def configure_widget(self):
70 if not super().configure_widget():
71 self.widget = self.root.create_shared(gws.ext.object.modelWidget, type='geometry')
72 return True
74 ##
76 def props(self, user):
77 return gws.u.merge(super().props(user), geometryType=self.geometryType)
79 ##
81 def before_select(self, mc):
82 super().before_select(mc)
84 shape = None
86 if mc.search.shape:
87 shape = mc.search.shape
88 if mc.search.tolerance:
89 tol_value, tol_unit = mc.search.tolerance
90 if tol_unit == gws.Uom.px:
91 tol_value *= mc.search.resolution
92 shape = shape.tolerance_polygon(tol_value)
93 elif mc.search.bounds:
94 shape = gws.base.shape.from_bounds(mc.search.bounds)
96 if shape:
97 shape = shape.transformed_to(self.geometryCrs)
99 model = cast(gws.base.database.model.Object, self.model)
100 col = model.column(self.name)
102 mc.dbSelect.geometryWhere.append(sa.func.st_intersects(col, sa.cast(shape.to_ewkb_hex(), sa.geo.Geometry())))
104 def raw_to_python(self, feature, value, mc):
105 # here, value is a geosa WKBElement
106 return gws.base.shape.from_wkb_hex(str(value))
108 def prop_to_python(self, feature, value, mc):
109 shape = self._prop_to_shape(value)
110 if shape:
111 return shape.transformed_to(self.geometryCrs)
112 return gws.ErrorValue
114 def python_to_raw(self, feature, value, mc):
115 return value.to_ewkb_hex()
117 def python_to_prop(self, feature, value, mc):
118 return cast(gws.Shape, value).to_props()
120 def _prop_to_shape(self, value):
121 if isinstance(value, gws.base.shape.Shape):
122 return value
123 if gws.u.is_data_object(value):
124 try:
125 return gws.base.shape.from_props(value)
126 except gws.Error:
127 pass
128 if gws.u.is_dict(value):
129 try:
130 return gws.base.shape.from_dict(value)
131 except gws.Error:
132 pass