Browse Source

修复unit_ceil访问报错问题

drggboy 3 weeks ago
parent
commit
dfd2d36bd0
4 changed files with 157 additions and 32 deletions
  1. 1 1
      app/api/vector.py
  2. 153 29
      app/services/vector_service.py
  3. 1 1
      config.env
  4. 2 1
      main.py

+ 1 - 1
app/api/vector.py

@@ -43,7 +43,7 @@ async def export_vector_data(vector_id: int, db: Session = Depends(get_db)):
         background=BackgroundTasks().add_task(shutil.rmtree, result["temp_dir"]) if "temp_dir" in result else None
     )
 
-@router.get("/export/{table_name}", summary="导出矢量数据", description="将指定的矢量数据表导出为GeoJSON文件")
+@router.get("/export/all", summary="导出矢量数据", description="将指定的矢量数据表导出为GeoJSON文件")
 async def export_all_vector_data_api(table_name: str = "vector_data", db: Session = Depends(get_db)):
     """导出指定矢量数据表为GeoJSON文件
     

+ 153 - 29
app/services/vector_service.py

@@ -10,6 +10,15 @@ import uuid
 import tempfile
 import struct
 from sqlalchemy.sql import text
+import binascii
+
+# 导入shapely库用于解析WKB
+try:
+    from shapely import wkb
+    from shapely.geometry import mapping
+    SHAPELY_AVAILABLE = True
+except ImportError:
+    SHAPELY_AVAILABLE = False
 
 
 class DecimalEncoder(json.JSONEncoder):
@@ -146,13 +155,13 @@ async def import_vector_data(file: UploadFile, db: Session) -> dict:
 def export_vector_data(db: Session, vector_id: int):
     """导出指定ID的矢量数据为GeoJSON格式并保存到文件"""
     vector_data = get_vector_data(db, vector_id)
-    return _export_vector_data_to_file([vector_data], f"export_{vector_id}")
+    return _export_vector_data_to_file([vector_data], f"export_{vector_id}", "surveydata")
 
 
 def export_vector_data_batch(db: Session, vector_ids: List[int]):
     """批量导出矢量数据为GeoJSON格式并保存到文件"""
     vector_data_list = get_vector_data_batch(db, vector_ids)
-    return _export_vector_data_to_file(vector_data_list, f"export_batch_{'_'.join(map(str, vector_ids))}")
+    return _export_vector_data_to_file(vector_data_list, f"export_batch_{'_'.join(map(str, vector_ids))}", "surveydata")
 
 
 def export_all_vector_data(db: Session, table_name: str = "surveydata"):
@@ -174,7 +183,7 @@ def export_all_vector_data(db: Session, table_name: str = "surveydata"):
         raise HTTPException(status_code=404, detail=f"表 {table_name} 中没有矢量数据")
 
     # 调用现有的导出函数
-    return _export_vector_data_to_file(vector_data_list, f"export_{table_name}")
+    return _export_vector_data_to_file(vector_data_list, f"export_{table_name}", table_name)
 
 
 def parse_geom_field(geom_value) -> dict:
@@ -185,6 +194,8 @@ def parse_geom_field(geom_value) -> dict:
     try:
         # 将 geom_value 转换为字符串
         geom_str = str(geom_value)
+        
+        # 处理PostGIS WKB格式的点数据
         if geom_str and geom_str.startswith('0101000020'):
             # 去掉前两个字符(字节序标记),并转换为字节对象
             binary_geom = bytes.fromhex(geom_str[2:])
@@ -203,41 +214,154 @@ def parse_geom_field(geom_value) -> dict:
                     "coordinates": [x, y]
                 }
             else:
-                print(f"Insufficient data in geom value: {geom_str}. Length: {len(binary_geom)}")
+                print(f"数据长度不足: {geom_str}. 长度: {len(binary_geom)}")
+        # 处理PostgreSQL/PostGIS的Well-Known Text (WKT)格式
+        elif geom_str and (geom_str.startswith('POINT') or 
+                          geom_str.startswith('LINESTRING') or 
+                          geom_str.startswith('POLYGON') or
+                          geom_str.startswith('MULTIPOINT') or
+                          geom_str.startswith('MULTILINESTRING') or
+                          geom_str.startswith('MULTIPOLYGON')):
+            # 这里我们需要依赖PostgreSQL服务器将WKT转换为GeoJSON
+            # 在实际部署中,应该使用数据库函数如ST_AsGeoJSON()
+            print(f"检测到WKT格式几何数据: {geom_str[:30]}...")
+            return None
+        # 处理EWKT (Extended Well-Known Text)格式,如SRID=4326;POINT(...)
+        elif geom_str and geom_str.startswith('SRID='):
+            print(f"检测到EWKT格式几何数据: {geom_str[:30]}...")
+            return None
+        # 处理十六进制WKB格式
+        elif geom_str and all(c in '0123456789ABCDEFabcdef' for c in geom_str):
+            print(f"检测到十六进制WKB格式几何数据: {geom_str[:30]}...")
+            
+            # 使用Shapely库解析WKB (首选方法)
+            if SHAPELY_AVAILABLE:
+                try:
+                    # 如果字符串长度为奇数,可能需要在前面添加一个"0"
+                    if len(geom_str) % 2 != 0:
+                        geom_str = '0' + geom_str
+                    
+                    # 将十六进制字符串转换为二进制数据
+                    binary_data = binascii.unhexlify(geom_str)
+                    
+                    # 使用Shapely解析WKB并转换为GeoJSON
+                    shape = wkb.loads(binary_data)
+                    return mapping(shape)
+                except Exception as e:
+                    print(f"Shapely解析WKB失败: {e}")
+            
+            # 使用PostGIS函数进行解析
+            try:
+                from ..database import engine
+                
+                with engine.connect() as connection:
+                    # 使用PostgreSQL/PostGIS的ST_GeomFromWKB函数和ST_AsGeoJSON函数
+                    query = text("SELECT ST_AsGeoJSON(ST_GeomFromWKB(decode($1, 'hex'))) AS geojson")
+                    result = connection.execute(query, [geom_str]).fetchone()
+                    
+                    if result and result.geojson:
+                        return json.loads(result.geojson)
+            except Exception as e:
+                print(f"使用PostgreSQL解析WKB失败: {e}")
+                
+            return None
+        else:
+            # 可能是使用PostGIS扩展的内部二进制格式
+            from sqlalchemy.sql import text
+            from ..database import engine
+            
+            try:
+                # 使用PostgreSQL/PostGIS的ST_AsGeoJSON函数直接转换
+                with engine.connect() as connection:
+                    # 尝试安全地传递geom_value
+                    # 注意:这种方法依赖于数据库连接和PostGIS扩展
+                    query = text("SELECT ST_AsGeoJSON(ST_Force2D($1::geometry)) AS geojson")
+                    result = connection.execute(query, [geom_value]).fetchone()
+                    
+                    if result and result.geojson:
+                        return json.loads(result.geojson)
+            except Exception as e:
+                print(f"使用ST_AsGeoJSON转换几何数据失败: {e}")
+                
+            print(f"未识别的几何数据格式: {geom_str[:50]}...")
+            
     except (ValueError, IndexError, struct.error) as e:
-        print(f"Failed to parse geom field: {geom_str if 'geom_str' in locals() else geom_value}. Error: {e}")
+        print(f"解析几何字段失败: {geom_str if 'geom_str' in locals() else geom_value}. 错误: {e}")
+    
     return None
 
 
-def _export_vector_data_to_file(vector_data_list: List[VectorData], base_filename: str):
-    """将矢量数据列表导出为 GeoJSON 文件"""
+def _export_vector_data_to_file(vector_data_list, base_filename: str, table_name: str = "surveydata"):
+    """将矢量数据列表导出为 GeoJSON 文件
+    
+    Args:
+        vector_data_list: 矢量数据列表,可能是ORM对象或SQLAlchemy行对象
+        base_filename: 基础文件名
+        table_name: 表名,用于判断应该使用哪个ORM模型,默认为"surveydata"
+    """
     features = []
-
+    
+    # 导入所需的ORM模型
+    from ..models.orm_models import UnitCeil, Surveydatum, FiftyThousandSurveyDatum
+    
+    # 根据表名获取对应的ORM模型和几何字段名
+    model_mapping = {
+        "surveydata": (Surveydatum, "geom"),
+        "unit_ceil": (UnitCeil, "geom"),
+        "fifty_thousand_survey_data": (FiftyThousandSurveyDatum, "geom")
+    }
+    
+    # 获取对应的模型和几何字段名
+    model_class, geom_field = model_mapping.get(table_name, (Surveydatum, "geom"))
+    
+    # 检查数据类型
+    is_orm_object = len(vector_data_list) == 0 or hasattr(vector_data_list[0], '__table__')
+    
     for vector_data in vector_data_list:
-        # 获取表的所有列名
-        columns = [column.name for column in VectorData.__table__.columns]
-
         # 构建包含所有列数据的字典
         data_dict = {}
-        for column in columns:
-            value = getattr(vector_data, column)
-            # 如果值是字符串且可能是 JSON,尝试解析
-            if isinstance(value, str) and (value.startswith('{') or value.startswith('[')):
-                try:
-                    value = json.loads(value)
-                except:
-                    pass
-            # 跳过 geom 字段,后续单独处理
-            if column != 'geom':
-                data_dict[column] = value
-
-        # 解析 geom 字段为 GeoJSON 格式的 geometry
+        
+        if is_orm_object:
+            # 如果是ORM对象,使用模型的列获取数据
+            columns = [column.name for column in model_class.__table__.columns]
+            for column in columns:
+                if hasattr(vector_data, column):
+                    value = getattr(vector_data, column)
+                    # 如果值是字符串且可能是 JSON,尝试解析
+                    if isinstance(value, str) and (value.startswith('{') or value.startswith('[')):
+                        try:
+                            value = json.loads(value)
+                        except:
+                            pass
+                    # 跳过几何字段,后续单独处理
+                    if column != geom_field:
+                        data_dict[column] = value
+        else:
+            # 如果是SQLAlchemy行对象,获取所有键
+            for key in vector_data.keys():
+                if key != geom_field:
+                    value = vector_data[key]
+                    # 如果值是字符串且可能是 JSON,尝试解析
+                    if isinstance(value, str) and (value.startswith('{') or value.startswith('[')):
+                        try:
+                            value = json.loads(value)
+                        except:
+                            pass
+                    data_dict[key] = value
+
+        # 解析几何字段为GeoJSON格式的geometry
         geometry = None
-        if hasattr(vector_data, 'geom'):
-            geom_value = getattr(vector_data, 'geom')
+        geom_value = None
+        
+        if is_orm_object and hasattr(vector_data, geom_field):
+            geom_value = getattr(vector_data, geom_field)
+        elif not is_orm_object and geom_field in vector_data.keys():
+            geom_value = vector_data[geom_field]
+            
+        if geom_value:
             geometry = parse_geom_field(geom_value)
 
-        # 创建 Feature
+        # 创建Feature
         feature = {
             "type": "Feature",
             "properties": data_dict,
@@ -245,7 +369,7 @@ def _export_vector_data_to_file(vector_data_list: List[VectorData], base_filenam
         }
         features.append(feature)
 
-    # 创建 GeoJSON 对象
+    # 创建GeoJSON对象
     geojson = {
         "type": "FeatureCollection",
         "features": features
@@ -259,7 +383,7 @@ def _export_vector_data_to_file(vector_data_list: List[VectorData], base_filenam
     filename = f"{base_filename}_{timestamp}.geojson"
     file_path = os.path.join(temp_dir, filename)
 
-    # 保存到文件,使用自定义编码器处理 Decimal 类型
+    # 保存到文件,使用自定义编码器处理Decimal类型
     with open(file_path, "w", encoding="utf-8") as f:
         json.dump(geojson, f, ensure_ascii=False, indent=2, cls=DecimalEncoder)
 

+ 1 - 1
config.env

@@ -2,4 +2,4 @@ DB_HOST=localhost
 DB_PORT=5432
 DB_NAME=testdb
 DB_USER=postgres
-DB_PASSWORD=123456789Qq 
+DB_PASSWORD=root

+ 2 - 1
main.py

@@ -2,4 +2,5 @@ from app.main import app
 
 if __name__ == "__main__":
     import uvicorn
-    uvicorn.run(app, host="0.0.0.0", port=8000, ssl_keyfile="ssl/cert.key", ssl_certfile="ssl/cert.crt")
+    uvicorn.run(app, host="0.0.0.0", port=8000, ssl_keyfile="ssl/cert.key", ssl_certfile="ssl/cert.crt")
+    # uvicorn.run(app, host="0.0.0.0", port=8000)