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

1"""Geometry field.""" 

2 

3from typing import Optional, cast 

4 

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 

11 

12 

13gws.ext.new.modelField('geometry') 

14 

15 

16class Config(gws.base.model.scalar_field.Config): 

17 """Geometry field configuration.""" 

18 

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'.""" 

23 

24 

25class Props(gws.base.model.scalar_field.Props): 

26 geometryType: gws.GeometryType 

27 

28 

29class Object(gws.base.model.scalar_field.Object): 

30 attributeType = gws.AttributeType.geometry 

31 supportsGeometrySearch = True 

32 

33 geometryType: gws.GeometryType 

34 geometryCrs: gws.Crs 

35 

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}') 

44 

45 def configure_geometry_type(self): 

46 s = self.cfg('geometryType') 

47 if s: 

48 self.geometryType = s 

49 return True 

50 

51 col = self.describe() 

52 if col and col.geometryType: 

53 self.geometryType = col.geometryType 

54 return True 

55 

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 

61 

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 

68 

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 

73 

74 ## 

75 

76 def props(self, user): 

77 return gws.u.merge(super().props(user), geometryType=self.geometryType) 

78 

79 ## 

80 

81 def before_select(self, mc): 

82 super().before_select(mc) 

83 

84 shape = None 

85 

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) 

95 

96 if shape: 

97 shape = shape.transformed_to(self.geometryCrs) 

98 

99 model = cast(gws.base.database.model.Object, self.model) 

100 col = model.column(self.name) 

101 

102 mc.dbSelect.geometryWhere.append(sa.func.st_intersects(col, sa.cast(shape.to_ewkb_hex(), sa.geo.Geometry()))) 

103 

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)) 

107 

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 

113 

114 def python_to_raw(self, feature, value, mc): 

115 return value.to_ewkb_hex() 

116 

117 def python_to_prop(self, feature, value, mc): 

118 return cast(gws.Shape, value).to_props() 

119 

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