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