@@ -1094,8 +1094,8 @@ def _post_primal_members(self, collection_pk:int, feature_pk:int, layer_pk:int,
10941094 sql = """
10951095 INSERT INTO cell_space_n_boundary
10961096 (id_str, type, collection_id, indoorfeature_id, thematiclayer_id,
1097- cell_name, level, "2D_geometry","3D_geometry", poi)
1098- VALUES (%s, 'space', %s, %s, %s, %s, %s, ST_GeomFromText(%s, 0), %s, %s)
1097+ cell_name, level, "2D_geometry","3D_geometry", poi, external_reference )
1098+ VALUES (%s, 'space', %s, %s, %s, %s, %s, ST_GeomFromText(%s, 0), %s, %s, %s )
10991099 RETURNING id
11001100 """
11011101 cur .execute (sql , (
@@ -1107,7 +1107,8 @@ def _post_primal_members(self, collection_pk:int, feature_pk:int, layer_pk:int,
11071107 str (cell .get ('level' )),
11081108 self .json_to_wkt (geom_2d ),
11091109 json .dumps (geom_3d ),
1110- cell .get ('poi' )
1110+ cell .get ('poi' ),
1111+ json .dumps (cell .get ('externalReference' )) if cell .get ('externalReference' ) else None
11111112 ))
11121113 # Store cell pk for duality
11131114 cell_pk = cur .fetchone ()
@@ -1124,7 +1125,7 @@ def _post_primal_members(self, collection_pk:int, feature_pk:int, layer_pk:int,
11241125 geom_raw = bound .get ('cellBoundaryGeom' , {})
11251126 geom_2d = geom_raw .get ('geometry2D' , None )
11261127 geom_3d = geom_raw .get ('geometry3D' , None )
1127-
1128+ external_ref = json . dumps ( bound . get ( 'externalReference' )) if bound . get ( 'externalReference' ) else None
11281129 duplicate_sql = """
11291130 SELECT n.id_str
11301131 FROM node_n_edge n
@@ -1142,8 +1143,8 @@ def _post_primal_members(self, collection_pk:int, feature_pk:int, layer_pk:int,
11421143 sql = """
11431144 INSERT INTO cell_space_n_boundary
11441145 (id_str, type, collection_id, indoorfeature_id, thematiclayer_id,
1145- is_virtual, "2D_geometry", "3D_geometry", bounded_by_cell_id)
1146- VALUES (%s, 'boundary', %s, %s, %s, %s, ST_GeomFromText(%s, 0), %s, %s)
1146+ is_virtual, "2D_geometry", "3D_geometry", bounded_by_cell_id, external_reference )
1147+ VALUES (%s, 'boundary', %s, %s, %s, %s, ST_GeomFromText(%s, 0), %s, %s, %s )
11471148 RETURNING id
11481149 """
11491150 cur .execute (sql , (
@@ -1154,7 +1155,8 @@ def _post_primal_members(self, collection_pk:int, feature_pk:int, layer_pk:int,
11541155 bound .get ('isVirtual' , False ),
11551156 self .json_to_wkt (geom_2d ),
11561157 json .dumps (geom_3d ),
1157- boundingCell
1158+ boundingCell ,
1159+ external_ref
11581160 ))
11591161
11601162 # Store boundary pk for duality
@@ -1224,7 +1226,82 @@ def _post_primal_members(self, collection_pk:int, feature_pk:int, layer_pk:int,
12241226 WHERE c.id = u.id
12251227 AND c.thematiclayer_id = %s;
12261228 """
1227- cur .execute (sql_project_shell ,(layer_pk ,layer_pk ))
1229+ sql_project_shell = """
1230+ WITH targets AS (
1231+ SELECT id, "3D_geometry"
1232+ FROM cell_space_n_boundary
1233+ WHERE thematiclayer_id = %s
1234+ AND type='space'
1235+ AND "2D_geometry" IS NULL
1236+ AND "3D_geometry" IS NOT NULL
1237+ ),
1238+ faces AS (
1239+ SELECT
1240+ t.id,
1241+ s.shell_idx,
1242+ f.face
1243+ FROM targets t
1244+ CROSS JOIN LATERAL jsonb_array_elements(t."3D_geometry"->'coordinates')
1245+ WITH ORDINALITY AS s(shell, shell_idx)
1246+ CROSS JOIN LATERAL jsonb_array_elements(s.shell) AS f(face)
1247+ ),
1248+ face_z AS (
1249+ SELECT
1250+ id, shell_idx, face,
1251+ (SELECT min((pt->>2)::float) FROM jsonb_array_elements(face) pt) AS zmin,
1252+ (SELECT max((pt->>2)::float) FROM jsonb_array_elements(face) pt) AS zmax
1253+ FROM faces
1254+ ),
1255+ floor_faces AS (
1256+ -- horizontal faces only + choose the global minimum z (floor) per id
1257+ SELECT f.*
1258+ FROM face_z f
1259+ JOIN (
1260+ SELECT id, min(zmin) AS floor_z
1261+ FROM face_z
1262+ WHERE zmin = zmax
1263+ GROUP BY id
1264+ ) m USING (id)
1265+ WHERE f.zmin = f.zmax
1266+ AND f.zmin = m.floor_z
1267+ ),
1268+ proj AS (
1269+ SELECT
1270+ id,
1271+ shell_idx,
1272+ ST_Force2D(
1273+ ST_GeomFromGeoJSON(
1274+ jsonb_build_object(
1275+ 'type','Polygon',
1276+ 'coordinates',
1277+ CASE
1278+ WHEN jsonb_typeof(face->0->0) = 'array' THEN face
1279+ ELSE jsonb_build_array(face)
1280+ END
1281+ )::text
1282+ )
1283+ ) AS g2d
1284+ FROM floor_faces
1285+ ),
1286+ u AS (
1287+ SELECT
1288+ id,
1289+ ST_UnaryUnion(ST_Collect(g2d) FILTER (WHERE shell_idx = 1)) AS ext2d,
1290+ ST_UnaryUnion(ST_Collect(g2d) FILTER (WHERE shell_idx > 1)) AS int2d
1291+ FROM proj
1292+ GROUP BY id
1293+ )
1294+ UPDATE cell_space_n_boundary c
1295+ SET "2D_geometry" = ST_SetSRID(
1296+ CASE
1297+ WHEN u.int2d IS NULL THEN u.ext2d
1298+ ELSE ST_Difference(u.ext2d, u.int2d)
1299+ END
1300+ , 0)
1301+ FROM u
1302+ WHERE c.id = u.id;
1303+ """
1304+ cur .execute (sql_project_shell ,(layer_pk ,))
12281305
12291306 return dual_cell , dual_boundary
12301307
@@ -1915,15 +1992,15 @@ def post_primal_member(self, collection_id:str, feature_id:str, layer_id:str, da
19151992 update_bounded_by .append (row .id )
19161993
19171994 geom_root = data .get ('cellSpaceGeom' , {})
1918- geom_2d_wkt = self .json_to_wkt (geom_root ['geometry2D' ])
1919- geom_3d_json = json .dumps (geom_root ['geometry3D' ])
1995+ geom_2d_wkt = self .json_to_wkt (geom_root ['geometry2D' ]) if geom_root . get ( 'geometry2D' ) else None
1996+ geom_3d_json = json .dumps (geom_root ['geometry3D' ]) if geom_root . get ( 'geometry3D' ) else None
19201997
19211998 elif f_type == 'CellBoundary' :
19221999 db_type = 'boundary'
19232000 is_virtual = data .get ('isVirtual' , False )
19242001 geom_root = data .get ('cellBoundaryGeom' , {})
1925- geom_2d_wkt = self .json_to_wkt (geom_root .get ('geometry2D' ))
1926- geom_3d_json = json .dumps (geom_root .get ('geometry3D' ))
2002+ geom_2d_wkt = self .json_to_wkt (geom_root .get ('geometry2D' )) if geom_root . get ( 'geometry2D' ) else None
2003+ geom_3d_json = json .dumps (geom_root .get ('geometry3D' )) if geom_root . get ( 'geometry3D' ) else None
19272004
19282005 if duality_raw : # validation check: CellBoundary can have duality to edge which has no duality
19292006 duality_id = duality_raw .split (':' )[- 1 ]
@@ -1983,7 +2060,8 @@ def post_primal_member(self, collection_id:str, feature_id:str, layer_id:str, da
19832060 update_bounded_by ,
19842061 layer_row [0 ]
19852062 ))
1986- if f_type == 'CellBoundary' and duality_edge_pk :
2063+
2064+ elif f_type == 'CellBoundary' and duality_edge_pk :
19872065 update_edgeDuality_sql = """
19882066 UPDATE node_n_edge
19892067 SET duality_id = %s
@@ -1994,65 +2072,87 @@ def post_primal_member(self, collection_id:str, feature_id:str, layer_id:str, da
19942072 # project cellspace's 3D geometry to 2D if it has no 2D geometry.
19952073 LOGGER .debug ("Project geometry 3D to 2D " )
19962074 sql_project_shell = """
1997- WITH faces AS (
2075+ WITH targets AS (
2076+ SELECT id, "3D_geometry"
2077+ FROM cell_space_n_boundary
2078+ WHERE id = %s
2079+ AND type='space'
2080+ AND "2D_geometry" IS NULL
2081+ AND "3D_geometry" IS NOT NULL
2082+ ),
2083+ faces AS (
19982084 SELECT
1999- c .id,
2085+ t .id,
20002086 s.shell_idx,
20012087 f.face
2002- FROM cell_space_n_boundary c
2003- CROSS JOIN LATERAL jsonb_array_elements(c ."3D_geometry"->'coordinates')
2088+ FROM targets t
2089+ CROSS JOIN LATERAL jsonb_array_elements(t ."3D_geometry"->'coordinates')
20042090 WITH ORDINALITY AS s(shell, shell_idx)
20052091 CROSS JOIN LATERAL jsonb_array_elements(s.shell) AS f(face)
2006- WHERE c."3D_geometry" IS NOT NULL
2007- AND c.type = 'space'
2008- AND c."2D_geometry" IS NULL
2009- AND c.thematiclayer_id = %s
2010- AND (
2011- s.shell_idx = 1
2012- OR jsonb_array_length(c."3D_geometry"->'coordinates') > 1
2013- )
2092+ ),
2093+ face_z AS (
2094+ SELECT
2095+ id, shell_idx, face,
2096+ (SELECT min((pt->>2)::float) FROM jsonb_array_elements(face) pt) AS zmin,
2097+ (SELECT max((pt->>2)::float) FROM jsonb_array_elements(face) pt) AS zmax
2098+ FROM faces
2099+ ),
2100+ floor_faces AS (
2101+ SELECT f.*
2102+ FROM face_z f
2103+ JOIN (
2104+ SELECT id, min(zmin) AS floor_z
2105+ FROM face_z
2106+ WHERE zmin = zmax
2107+ GROUP BY id
2108+ ) m USING (id)
2109+ WHERE f.zmin = f.zmax
2110+ AND f.zmin = m.floor_z
2111+ ),
2112+ use_faces AS (
2113+ SELECT * FROM floor_faces
2114+ UNION ALL
2115+ SELECT * FROM face_z
2116+ WHERE NOT EXISTS (SELECT 1 FROM floor_faces)
20142117 ),
20152118 proj AS (
20162119 SELECT
20172120 id,
20182121 shell_idx,
2019- ST_SetSRID(
20202122 ST_Force2D(
20212123 ST_GeomFromGeoJSON(
20222124 jsonb_build_object(
2023- 'type', 'Polygon',
2125+ 'type','Polygon',
20242126 'coordinates',
20252127 CASE
2026- -- if face is already [ring,...], keep it; else wrap to [ring]
20272128 WHEN jsonb_typeof(face->0->0) = 'array' THEN face
20282129 ELSE jsonb_build_array(face)
20292130 END
20302131 )::text
20312132 )
2032- ),
2033- 0
20342133 ) AS g2d
2035- FROM faces
2134+ FROM use_faces
20362135 ),
20372136 u AS (
20382137 SELECT
20392138 id,
2040- ST_UnaryUnion( ST_Collect(g2d) FILTER (WHERE shell_idx = 1) ) AS ext2d,
2041- ST_UnaryUnion( ST_Collect(g2d) FILTER (WHERE shell_idx > 1) ) AS int2d
2139+ ST_UnaryUnion(ST_Collect(g2d) FILTER (WHERE shell_idx = 1)) AS ext2d,
2140+ ST_UnaryUnion(ST_Collect(g2d) FILTER (WHERE shell_idx > 1)) AS int2d
20422141 FROM proj
20432142 GROUP BY id
20442143 )
20452144 UPDATE cell_space_n_boundary c
2046- SET "2D_geometry" =
2145+ SET "2D_geometry" = ST_SetSRID(
20472146 CASE
20482147 WHEN u.int2d IS NULL THEN u.ext2d
20492148 ELSE ST_Difference(u.ext2d, u.int2d)
20502149 END
2150+ , 0)
20512151 FROM u
20522152 WHERE c.id = u.id
2053- AND c.thematiclayer_id = %s;
2054- """
2055- cur .execute (sql_project_shell ,(new_internal_id , new_internal_id ))
2153+ AND c.id = %s
2154+ """
2155+ cur .execute (sql_project_shell ,(new_internal_id ,new_internal_id ))
20562156 # 4. FIX: Commit only if we get here successfully
20572157 self .connection .commit ()
20582158 return new_str_id
0 commit comments