@@ -89,7 +89,6 @@ def get_collections_list(self):
8989
9090 clean_list .append ({
9191 'id' : c_id ,
92- # Default to the ID if title is missing
9392 'title' : props .get ('title' , c_id ),
9493 'itemType' : props .get ('itemType' , 'indoorfeature' )
9594 })
@@ -163,7 +162,6 @@ def post_collection(self, collection):
163162 }
164163 with self .connection .cursor () as cur :
165164 try :
166- # 2. Insert (Let Postgres handle the 'id' column automatically)
167165 insert_query = """
168166 INSERT INTO collection (id_str, collection_property)
169167 VALUES (%s, %s)
@@ -228,8 +226,47 @@ def delete_collection(self, collection_id:str):
228226 self .connection .rollback ()
229227 LOGGER .error (f"Error creating collection: { e } " )
230228 raise e
229+
230+
231+ def patch_collection (self , collection_id :str , data ):
232+ with self .connection .cursor () as cur :
233+ try :
234+ cur .execute ("SELECT id FROM collection WHERE id_str = %s" , (collection_id ,))
235+ row = cur .fetchone ()
236+
237+ if not row :
238+ return False # Collection not found
239+
240+ coll_pk = row [0 ]
241+
242+ sql = "SELECT collection_property FROM collection WHERE id = %s"
243+ cur .execute (sql , (coll_pk ,))
244+
245+ collection_json = cur .fetchone ()
246+
247+ fields = []
248+ values = []
249+
250+ if 'title' in data :
251+ collection_json ['title' ] = data ['title' ]
252+
253+ if 'description' in data :
254+ collection_json ['description' ] = data ['description' ]
255+
256+ update_sql = "UPDATE collection SET collection_property = %s WHERE id = %s"
257+ cur .execute (update_sql , (json .dumps (collection_json ),coll_pk ,))
258+
259+ self .connection .commit ()
260+ return True
261+
262+ except Exception as e :
263+ self .connection .rollback ()
264+ LOGGER .error (f"Error updating collection: { e } " )
265+ raise e
266+
231267# endregion
232268
269+
233270# region IndoorFeatures
234271 def is_indoor_collection (self , collection_id :str ):
235272 """
@@ -260,7 +297,6 @@ def get_collection_items(
260297 """
261298 Retrieve the indoor feature collection /collections/{collectionId}/items
262299 Optimized to fetch data and total count in a single query.
263- /collections/{collectionId}/items
264300 """
265301 try :
266302 # 1. Prepare Filter Strings
@@ -363,7 +399,6 @@ def get_feature(self, collection_id: str, feature_id:str, level:str=None, bbox:l
363399 properties = props or {}
364400 thematic_layers = []
365401 interlayer_connections = []
366- # Initialize Skeleton
367402 result_feature = {
368403 "type" : "Feature" ,
369404 "id" : feature_id_str ,
@@ -512,7 +547,6 @@ def delete_indoorfeature(self, collection_id:str, feature_id:str):
512547 res = cur .fetchone ()
513548
514549 if not res :
515- # Item not found, usually returns 404 in API, but here we can just return
516550 msg = f"Feature { feature_id } not found."
517551 LOGGER .warning (msg )
518552 raise ValueError (msg )
@@ -543,7 +577,6 @@ def delete_indoorfeature(self, collection_id:str, feature_id:str):
543577 # 3. DELETE PARENT (The IndoorFeature itself)
544578 cur .execute ("DELETE FROM indoorfeature WHERE id = %s" , (feature_pk ,))
545579
546- # Commit is handled by the context manager
547580 self .connection .commit ()
548581 return True
549582 except Exception as e :
@@ -660,7 +693,7 @@ def get_layers(self, collection_id:str, feature_id:str, theme: str = None, level
660693
661694 def _get_layer (self , layer_pk :int , level :str = None , bbox :list = None ):
662695 """
663- Retrieves a single Thematic Layer.
696+ Retrieves a single Thematic Layer with Integer ID .
664697 If not filtered, just the meta data is given.
665698 - PrimalSpace: Filtered by 'level' if provided.
666699 - DualSpace: Returns the ENTIRE network (unfiltered) for connectivity.
@@ -703,7 +736,7 @@ def _get_layer(self, layer_pk:int, level:str=None, bbox:list=None):
703736
704737 def get_layer (self , collection_id :str , feature_id :str , layer_id :str , level :str = None , bbox :list = None ):
705738 """
706- Retrieves a single Thematic Layer.
739+ Retrieves a single Thematic Layer with String ID .
707740 If not filtered, just the meta data is given.
708741 - PrimalSpace: Filtered by 'level' if provided.
709742 - DualSpace: Returns the ENTIRE network (unfiltered) for connectivity.
@@ -817,7 +850,7 @@ def get_layer(self, collection_id:str, feature_id:str, layer_id:str, level:str=N
817850 def _get_primal_space (self , layer_pk :int , primalspace_id :str , p_create :str = None , p_termination :str = None , level :str = None , bbox :list = None ):
818851 """
819852 Helper to build PrimalSpaceLayer.
820- Supports optional filtering by 'level'.
853+ Supports optional filtering by 'level' and 'bbox' .
821854 """
822855 primal_space = {
823856 "id" : primalspace_id ,
@@ -1053,7 +1086,7 @@ def _post_thematic_layer(self, collection_pk:int, feature_pk:int, layer_data):
10531086
10541087 layer_new = cur .fetchone ()
10551088
1056- # Insert Primal Members (Cells/Boundaries) - returns duality dict
1089+ # Insert Primal Members (Cells/Boundaries) - returns duality dictionary
10571090 d_c , d_b = self ._post_primal_members (collection_pk , feature_pk , layer_new [0 ], primal )
10581091
10591092 # Insert Dual Members (Nodes/Edges)
@@ -1168,65 +1201,6 @@ def _post_primal_members(self, collection_pk:int, feature_pk:int, layer_pk:int,
11681201 # If there is no 2D geometry but 3D, project 3D to 2D geometry
11691202 LOGGER .debug ("Project geometry 3D to 2D " )
11701203 sql_project_shell = """
1171- WITH faces AS (
1172- SELECT
1173- c.id,
1174- s.shell_idx,
1175- f.face
1176- FROM cell_space_n_boundary c
1177- CROSS JOIN LATERAL jsonb_array_elements(c."3D_geometry"->'coordinates')
1178- WITH ORDINALITY AS s(shell, shell_idx)
1179- CROSS JOIN LATERAL jsonb_array_elements(s.shell) AS f(face)
1180- WHERE c."3D_geometry" IS NOT NULL
1181- AND c.type = 'space'
1182- AND c."2D_geometry" IS NULL
1183- AND c.thematiclayer_id = %s
1184- AND (
1185- s.shell_idx = 1
1186- OR jsonb_array_length(c."3D_geometry"->'coordinates') > 1
1187- )
1188- ),
1189- proj AS (
1190- SELECT
1191- id,
1192- shell_idx,
1193- ST_SetSRID(
1194- ST_Force2D(
1195- ST_GeomFromGeoJSON(
1196- jsonb_build_object(
1197- 'type', 'Polygon',
1198- 'coordinates',
1199- CASE
1200- -- if face is already [ring,...], keep it; else wrap to [ring]
1201- WHEN jsonb_typeof(face->0->0) = 'array' THEN face
1202- ELSE jsonb_build_array(face)
1203- END
1204- )::text
1205- )
1206- ),
1207- 0
1208- ) AS g2d
1209- FROM faces
1210- ),
1211- u AS (
1212- SELECT
1213- id,
1214- ST_UnaryUnion( ST_Collect(g2d) FILTER (WHERE shell_idx = 1) ) AS ext2d,
1215- ST_UnaryUnion( ST_Collect(g2d) FILTER (WHERE shell_idx > 1) ) AS int2d
1216- FROM proj
1217- GROUP BY id
1218- )
1219- UPDATE cell_space_n_boundary c
1220- SET "2D_geometry" =
1221- CASE
1222- WHEN u.int2d IS NULL THEN u.ext2d
1223- ELSE ST_Difference(u.ext2d, u.int2d)
1224- END
1225- FROM u
1226- WHERE c.id = u.id
1227- AND c.thematiclayer_id = %s;
1228- """
1229- sql_project_shell = """
12301204 WITH targets AS (
12311205 SELECT id, "3D_geometry"
12321206 FROM cell_space_n_boundary
0 commit comments