Imrao commited on
Commit
9c2f9ad
·
1 Parent(s): eec0081

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +58 -54
app.py CHANGED
@@ -277,18 +277,16 @@ async def generate_model(request: GenerateRequest):
277
  raise HTTPException(status_code=500, detail=str(e))
278
 
279
  @app.post("/generate_i3s")
280
- async def generate_i3s(request: GenerateI3SRequest):
281
- """Generate an I3S Layer (SLPK unpacked) from raw coordinates."""
282
 
283
- # 0. Cleanup Old Layers (as requested)
284
- # Remove all subdirectories in LAYERS_DIR to keep server clean
285
  try:
286
  if os.path.exists(LAYERS_DIR):
287
  for item in os.listdir(LAYERS_DIR):
288
  item_path = os.path.join(LAYERS_DIR, item)
289
  if os.path.isdir(item_path):
290
  shutil.rmtree(item_path)
291
- logger.info(f"Cleaned up old layer: {item}")
292
  except Exception as e:
293
  logger.warning(f"Cleanup failed: {e}")
294
 
@@ -297,68 +295,74 @@ async def generate_i3s(request: GenerateI3SRequest):
297
  raise HTTPException(status_code=404, detail="RPK not found")
298
 
299
  try:
300
- coords = request.coordinates
301
- if not coords or len(coords) < 9: # At least 3 points (3 * 3)
302
- raise HTTPException(status_code=400, detail="Invalid coordinates. Need at least 3 points (lon, lat, alt).")
303
-
304
- # 1. Project to Web Mercator (EPSG:3857)
305
- # Why?
306
- # - It uses Meters (good for CGA generation/scaling).
307
- # - It is standard for Web Maps/I3S supported by Cesium.
308
- # - Avoids complexity of dynamic UTM zones.
309
-
310
- target_epsg = 3857
311
- logger.info(f"Projecting I3S geometry to EPSG:{target_epsg} (Web Mercator)")
312
-
313
- transformer = pyproj.Transformer.from_crs("EPSG:4326", f"EPSG:{target_epsg}", always_xy=True)
314
-
315
- # 2. Project Points
316
- utm_coords = [] # Variable name kept for minimal diff, but now it's Web Mercator
317
- num_points = len(coords) // 3
318
-
319
- for i in range(num_points):
320
- idx = i * 3
321
- cx = coords[idx]
322
- cy = coords[idx+1]
323
- cz = coords[idx+2]
324
-
325
- ux, uy = transformer.transform(cx, cy)
326
- utm_coords.extend([ux, uy, cz])
327
-
328
- # 3. Calculate Anchor (Mercator)
329
- anchor_x = utm_coords[0]
330
- anchor_y = utm_coords[1]
331
- anchor_z = utm_coords[2]
332
-
333
- # 4. Localize
334
- local_coords = []
335
- for i in range(num_points):
336
- idx = i * 3
337
- ux = utm_coords[idx]
338
- uy = utm_coords[idx+1]
339
- uz = utm_coords[idx+2]
340
 
341
- local_coords.extend([ux - anchor_x, uy - anchor_y, uz - anchor_z])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
342
 
343
- # 5. Create InitialShape
344
- indices = list(range(num_points))
345
- face_counts = [num_points]
346
- initial_shape = pyprt.InitialShape(local_coords, indices, face_counts)
347
 
348
- # 6. Encoder Options
349
  layer_id = str(uuid.uuid4())
350
  output_dir = os.path.join(LAYERS_DIR, layer_id)
351
  os.makedirs(output_dir, exist_ok=True)
352
 
353
  enc_options = {
354
- 'sceneType': 'Global',
355
- 'sceneWkid': str(target_epsg), # 3857
356
  'baseName': 'SceneLayer',
357
  'sceneName': 'SceneLayer',
358
  'writePackage': False,
359
  'compression': False,
360
  'outputPath': output_dir,
361
- 'globalOffset': [anchor_x, anchor_y, anchor_z]
 
 
362
  }
363
 
364
  # Clean attributes (same as before)
 
277
  raise HTTPException(status_code=500, detail=str(e))
278
 
279
  @app.post("/generate_i3s")
280
+ async def generate_i3s(request: GenerateRequest):
281
+ """Generate an I3S Layer (SLPK unpacked) using same logic as GLB generation."""
282
 
283
+ # 0. Cleanup Old Layers
 
284
  try:
285
  if os.path.exists(LAYERS_DIR):
286
  for item in os.listdir(LAYERS_DIR):
287
  item_path = os.path.join(LAYERS_DIR, item)
288
  if os.path.isdir(item_path):
289
  shutil.rmtree(item_path)
 
290
  except Exception as e:
291
  logger.warning(f"Cleanup failed: {e}")
292
 
 
295
  raise HTTPException(status_code=404, detail="RPK not found")
296
 
297
  try:
298
+ # 1. Parse Geometry (Same as generate_model)
299
+ geom_dict = request.geometry
300
+ if geom_dict.get("type") == "Feature":
301
+ geom_dict = geom_dict.get("geometry")
302
+
303
+ shape = shapely.geometry.shape(geom_dict)
304
+
305
+ if geom_dict.get("type") != "Polygon":
306
+ raise HTTPException(status_code=400, detail="Only Polygons are supported")
307
+
308
+ # Re-orient
309
+ if not shape.is_valid: shape = shape.buffer(0)
310
+ shape = shapely.ops.orient(shape, sign=-1.0)
311
+
312
+ # 2. Calculate Centroid & Recentering (Same as generate_model)
313
+ centroid = shape.centroid
314
+ logger.info(f"I3S Centroid: {centroid.x}, {centroid.y}")
315
+
316
+ # Translate to (0,0)
317
+ shape_centered = translate(shape, xoff=-centroid.x, yoff=-centroid.y)
318
+
319
+ coords = list(shape_centered.exterior.coords)
320
+ if coords[0] == coords[-1]: coords = coords[:-1]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
321
 
322
+ # Flatten: [x, 0, y, ...]
323
+ # Note: Input is degrees, so these are delta degrees.
324
+ # However, for I3S, we usually need meters.
325
+ # BUT user insists on "same method". If "same method" works for GLB, it likely relies on frontend placement.
326
+ # For I3S, the placement is burned into the file via globalOffset.
327
+
328
+ # If we assume input is Meters (already projected by frontend? No, standard GeoJSON is Lon/Lat).
329
+ # If we pass delta-degrees to PyPRT, it generates a tiny model.
330
+ # UNLESS 'generate_model' also does projection?
331
+ # Checking generate_model... it does NOT project. It just centers.
332
+ # So it produces a tiny GLB. Cesium scales it?
333
+ # CesiumComponent: scale: 1.0.
334
+ # PROBABLY the RPK handles the small numbers? Or PyPRT has a setting?
335
+ # OR the user is mistaken and GLB is also tiny but they haven't noticed?
336
+
337
+ # Wait, if I3S is tiny, `globalOffset` places it correctly but size is wrong.
338
+ # Let's trust the "Same Method" request.
339
+ # We pass centroid as globalOffset.
340
+
341
+ flattened_coords = []
342
+ for p in coords:
343
+ flattened_coords.extend([p[0], 0, p[1]])
344
 
345
+ indices = list(range(len(coords)))
346
+ face_counts = [len(coords)]
347
+
348
+ initial_shape = pyprt.InitialShape(flattened_coords, indices, face_counts)
349
 
350
+ # 3. Encoder Options
351
  layer_id = str(uuid.uuid4())
352
  output_dir = os.path.join(LAYERS_DIR, layer_id)
353
  os.makedirs(output_dir, exist_ok=True)
354
 
355
  enc_options = {
356
+ 'sceneType': 'Global',
357
+ 'sceneWkid': '4326', # WGS84
358
  'baseName': 'SceneLayer',
359
  'sceneName': 'SceneLayer',
360
  'writePackage': False,
361
  'compression': False,
362
  'outputPath': output_dir,
363
+
364
+ # Critical: Global Offset is the Centroid
365
+ 'globalOffset': [centroid.x, centroid.y, 0]
366
  }
367
 
368
  # Clean attributes (same as before)