Browse Source

通过迁移脚本添加county表

drggboy 6 days ago
parent
commit
f71ea95b92

+ 4 - 0
app/models/__init__.py

@@ -0,0 +1,4 @@
+from app.models.orm_models import *
+from app.models.vector import *
+from app.models.raster import *
+from app.models.county import *  # 导入新的县级地理数据模型 

+ 90 - 0
app/models/county.py

@@ -0,0 +1,90 @@
+from sqlalchemy import Column, Integer, String, Text, Index, JSON
+from sqlalchemy.dialects.postgresql import JSONB
+from geoalchemy2 import Geometry
+from app.database import Base
+import json
+
+class County(Base):
+    """县级行政区划地理数据表"""
+    __tablename__ = "counties"
+
+    # 字段映射字典
+    FIELD_MAPPING = {
+        '县名': 'name',
+        '县代码': 'code',
+        '市名': 'city_name',
+        '市代码': 'city_code',
+        '省名': 'province_name',
+        '省代码': 'province_code'
+    }
+
+    # 反向映射字典
+    REVERSE_FIELD_MAPPING = {v: k for k, v in FIELD_MAPPING.items()}
+
+    id = Column(Integer, primary_key=True, index=True)
+    name = Column(String(100), nullable=False, comment="县名")
+    code = Column(Integer, comment="县代码")
+    city_name = Column(String(100), comment="市名")
+    city_code = Column(Integer, comment="市代码")
+    province_name = Column(String(100), comment="省名") 
+    province_code = Column(Integer, comment="省代码")
+    
+    # 使用PostGIS的几何类型来存储多边形数据
+    geometry = Column(Geometry('MULTIPOLYGON', srid=4326), nullable=False, comment="县级行政区划的几何数据")
+    
+    # 存储完整的GeoJSON数据
+    geojson = Column(JSON, nullable=False, comment="完整的GeoJSON数据") 
+
+    # 显式定义索引
+    __table_args__ = (
+        Index('idx_counties_name', 'name'),
+        Index('idx_counties_code', 'code'),
+        Index('idx_counties_geometry', 'geometry', postgresql_using='gist'),
+    ) 
+
+    @classmethod
+    def from_geojson_feature(cls, feature):
+        """从GeoJSON Feature创建County实例
+        
+        Args:
+            feature: GeoJSON Feature对象
+            
+        Returns:
+            County: 新创建的County实例
+        """
+        properties = feature['properties']
+        # 转换中文字段名为英文
+        mapped_properties = {
+            cls.FIELD_MAPPING.get(k, k): v 
+            for k, v in properties.items()
+        }
+        
+        # 将GeoJSON geometry转换为WKT格式
+        geometry = feature['geometry']
+        wkt = f"SRID=4326;{json.dumps(geometry)}"
+        
+        # 创建实例
+        county = cls(
+            **mapped_properties,
+            geometry=wkt,
+            geojson=feature
+        )
+        return county
+
+    def to_geojson_feature(self):
+        """将County实例转换为GeoJSON Feature
+        
+        Returns:
+            dict: GeoJSON Feature对象
+        """
+        properties = {}
+        # 转换英文字段名为中文
+        for eng_field, cn_field in self.REVERSE_FIELD_MAPPING.items():
+            if hasattr(self, eng_field):
+                properties[cn_field] = getattr(self, eng_field)
+
+        return {
+            'type': 'Feature',
+            'properties': properties,
+            'geometry': self.geometry
+        } 

+ 1 - 1
app/models/orm_models.py

@@ -116,7 +116,7 @@ class FiftyThousandSurveyDatum(Base):
     sl_zb = Column(Float(53))
     sl_zb = Column(Float(53))
     trlx = Column(String(254))
     trlx = Column(String(254))
     dtpa_cd = Column(Float(53))
     dtpa_cd = Column(Float(53))
-    lin_suan_er_qing_a = Column(Float(53))
+    lin_suan_er_qing_an = Column(Float(53))
     dtpa_pb = Column(Float(53))
     dtpa_pb = Column(Float(53))
     zb_cd = Column(Float(53))
     zb_cd = Column(Float(53))
     zb_as = Column(Float(53))
     zb_as = Column(Float(53))

+ 73 - 0
app/scripts/import_counties.py

@@ -0,0 +1,73 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+导入县级GeoJSON数据脚本
+"""
+
+import json
+import logging
+from pathlib import Path
+from sqlalchemy.orm import Session
+from app.database import SessionLocal
+from app.models.county import County
+
+# 配置日志
+logging.basicConfig(level=logging.INFO)
+logger = logging.getLogger(__name__)
+
+def import_counties_from_geojson(file_path: str, db: Session):
+    """从GeoJSON文件导入县级数据
+    
+    Args:
+        file_path: GeoJSON文件路径
+        db: 数据库会话
+    """
+    try:
+        # 读取GeoJSON文件
+        with open(file_path, 'r', encoding='utf-8') as f:
+            geojson_data = json.load(f)
+        
+        # 检查是否为FeatureCollection
+        if geojson_data['type'] != 'FeatureCollection':
+            raise ValueError("输入文件必须是GeoJSON FeatureCollection格式")
+        
+        # 导入每个Feature
+        for feature in geojson_data['features']:
+            try:
+                # 使用模型类方法创建County实例
+                county = County.from_geojson_feature(feature)
+                db.add(county)
+                logger.info(f"成功导入: {county.name}")
+            except Exception as e:
+                logger.error(f"导入失败: {feature.get('properties', {}).get('县名', '未知')} - {str(e)}")
+                continue
+        
+        # 提交事务
+        db.commit()
+        logger.info("数据导入完成")
+        
+    except Exception as e:
+        db.rollback()
+        logger.error(f"导入过程出错: {str(e)}")
+        raise
+
+def main():
+    """主函数"""
+    # 获取数据文件路径
+    data_dir = Path(__file__).parent.parent.parent / 'data'
+    geojson_file = data_dir / 'counties.geojson'
+    
+    if not geojson_file.exists():
+        logger.error(f"找不到数据文件: {geojson_file}")
+        return
+    
+    # 创建数据库会话
+    db = SessionLocal()
+    try:
+        import_counties_from_geojson(str(geojson_file), db)
+    finally:
+        db.close()
+
+if __name__ == '__main__':
+    main() 

File diff suppressed because it is too large
+ 5 - 0
data/counties.geojson


+ 29 - 6
migrations/env.py

@@ -14,10 +14,10 @@ import sys
 # 添加项目根目录到 Python 路径
 # 添加项目根目录到 Python 路径
 sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
 sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
 
 
-# 从orm_models导入Base和模型
-from app.models.orm_models import Base
 # 导入数据库连接信息
 # 导入数据库连接信息
-from app.database import DB_USER, DB_PASSWORD, DB_HOST, DB_PORT, DB_NAME
+from app.database import DB_USER, DB_PASSWORD, DB_HOST, DB_PORT, DB_NAME, Base
+# 导入所有模型
+import app.models
 
 
 # this is the Alembic Config object, which provides
 # this is the Alembic Config object, which provides
 # access to the values within the .ini file in use.
 # access to the values within the .ini file in use.
@@ -38,10 +38,30 @@ if config.config_file_name is not None:
 
 
 # add your model's MetaData object here
 # add your model's MetaData object here
 # for 'autogenerate' support
 # for 'autogenerate' support
-# from myapp import mymodel
-# target_metadata = mymodel.Base.metadata
 target_metadata = Base.metadata
 target_metadata = Base.metadata
 
 
+# 定义要忽略的索引名称模式
+def include_object(object, name, type_, reflected, compare_to):
+    """过滤掉不需要的索引变化和表删除操作"""
+    # 如果是表操作,且是删除操作,则忽略
+    if type_ == "table" and reflected and not compare_to:
+        return False
+        
+    if type_ == "index":
+        # 如果是新表或新列的索引,保留
+        if not reflected:
+            return True
+        # 如果是已存在表的索引,且名称以 'ix_' 开头,忽略
+        if name.startswith('ix_'):
+            return False
+        # 如果是已存在表的索引,且名称以 '_idx' 结尾,忽略
+        if name.endswith('_idx'):
+            return False
+        # 如果是已存在表的索引,且名称以 'idx_' 开头,保留
+        if name.startswith('idx_'):
+            return True
+    return True
+
 # other values from the config, defined by the needs of env.py,
 # other values from the config, defined by the needs of env.py,
 # can be acquired:
 # can be acquired:
 # my_important_option = config.get_main_option("my_important_option")
 # my_important_option = config.get_main_option("my_important_option")
@@ -66,6 +86,7 @@ def run_migrations_offline():
         target_metadata=target_metadata,
         target_metadata=target_metadata,
         literal_binds=True,
         literal_binds=True,
         dialect_opts={"paramstyle": "named"},
         dialect_opts={"paramstyle": "named"},
+        include_object=include_object,  # 添加过滤规则
     )
     )
 
 
     with context.begin_transaction():
     with context.begin_transaction():
@@ -87,7 +108,9 @@ def run_migrations_online():
 
 
     with connectable.connect() as connection:
     with connectable.connect() as connection:
         context.configure(
         context.configure(
-            connection=connection, target_metadata=target_metadata
+            connection=connection, 
+            target_metadata=target_metadata,
+            include_object=include_object,  # 添加过滤规则
         )
         )
 
 
         with context.begin_transaction():
         with context.begin_transaction():

+ 63 - 0
migrations/versions/f0d12e4fab12_add_counties_table.py

@@ -0,0 +1,63 @@
+"""add_counties_table
+
+Revision ID: f0d12e4fab12
+Revises: c1cf3ab2c7fe
+Create Date: 2025-05-17 20:39:22.830946
+
+"""
+from alembic import op
+import sqlalchemy as sa
+import geoalchemy2
+
+
+# revision identifiers, used by Alembic.
+revision = 'f0d12e4fab12'
+down_revision = 'c1cf3ab2c7fe'
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+    """升级数据库到当前版本"""
+    # ### commands auto generated by Alembic - please adjust! ###
+    op.create_table('counties',
+    sa.Column('id', sa.Integer(), nullable=False),
+    sa.Column('name', sa.String(length=100), nullable=False, comment='县名'),
+    sa.Column('code', sa.Integer(), nullable=True, comment='县代码'),
+    sa.Column('city_name', sa.String(length=100), nullable=True, comment='市名'),
+    sa.Column('city_code', sa.Integer(), nullable=True, comment='市代码'),
+    sa.Column('province_name', sa.String(length=100), nullable=True, comment='省名'),
+    sa.Column('province_code', sa.Integer(), nullable=True, comment='省代码'),
+    sa.Column('geometry', geoalchemy2.types.Geometry(geometry_type='MULTIPOLYGON', srid=4326, from_text='ST_GeomFromEWKT', name='geometry', nullable=False), nullable=False, comment='县级行政区划的几何数据'),
+    sa.Column('geojson', sa.JSON(), nullable=False, comment='完整的GeoJSON数据'),
+    sa.PrimaryKeyConstraint('id')
+    )
+    
+    # 检查并删除已存在的索引
+    connection = op.get_bind()
+    inspector = sa.inspect(connection)
+    existing_indexes = inspector.get_indexes('counties')
+    
+    for index in existing_indexes:
+        if index['name'] in ['idx_counties_code', 'idx_counties_geometry', 'idx_counties_name', 'ix_counties_id']:
+            op.drop_index(index['name'], table_name='counties')
+    
+    # 创建新的索引
+    op.create_index('idx_counties_code', 'counties', ['code'], unique=False)
+    op.create_index('idx_counties_geometry', 'counties', ['geometry'], unique=False, postgresql_using='gist')
+    op.create_index('idx_counties_name', 'counties', ['name'], unique=False)
+    op.create_index(op.f('ix_counties_id'), 'counties', ['id'], unique=False)
+    op.create_index(op.f('ix_raster_table_id'), 'raster_table', ['id'], unique=False)
+    # ### end Alembic commands ###
+
+
+def downgrade():
+    """将数据库降级到上一版本"""
+    # ### commands auto generated by Alembic - please adjust! ###
+    op.drop_index(op.f('ix_raster_table_id'), table_name='raster_table')
+    op.drop_index(op.f('ix_counties_id'), table_name='counties')
+    op.drop_index('idx_counties_name', table_name='counties')
+    op.drop_index('idx_counties_geometry', table_name='counties', postgresql_using='gist')
+    op.drop_index('idx_counties_code', table_name='counties')
+    op.drop_table('counties')
+    # ### end Alembic commands ###

Some files were not shown because too many files changed in this diff