convert_gis_road_coords.py 5.74 KB
#!/usr/bin/env python3
"""
WGS84 → GCJ02(高德坐标系)坐标转换脚本
目标表: garden_gis_road
转换字段: starting_latitude/longitude, end_latitude/longitude, gis_polygon_coords

使用方法:
    python3 convert_gis_road_coords.py

前提: pip install pymysql
"""
import pymysql
import math
import re

# ========== 数据库配置 ==========
DB_CONFIG = {
    'host': '172.17.16.15',
    'port': 3306,
    'user': 'root',
    'password': 'mysql2025!',
    'database': 'urban_ops_agent',
    'charset': 'utf8mb4',
}

# ========== WGS84 → GCJ02 算法 ==========
PI = math.pi
A = 6378245.0          # 长半轴
EE = 0.00669342162296594323  # 偏心率平方


def wgs84_to_gcj02(lng, lat):
    """WGS84 转 GCJ02(高德坐标系)"""
    if lng < 72.004 or lng > 137.8347 or lat < 0.8293 or lat > 55.8271:
        return lng, lat

    x, y = lng - 105.0, lat - 35.0

    dlat = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * math.sqrt(abs(x))
    dlat += (20.0 * math.sin(6.0 * x * PI) + 20.0 * math.sin(2.0 * x * PI)) * 2.0 / 3.0
    dlat += (20.0 * math.sin(y * PI) + 40.0 * math.sin(y / 3.0 * PI)) * 2.0 / 3.0
    dlat += (160.0 * math.sin(y / 12.0 * PI) + 320.0 * math.sin(y * PI / 30.0)) * 2.0 / 3.0

    dlng = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * math.sqrt(abs(x))
    dlng += (20.0 * math.sin(6.0 * x * PI) + 20.0 * math.sin(2.0 * x * PI)) * 2.0 / 3.0
    dlng += (20.0 * math.sin(x * PI) + 40.0 * math.sin(x / 3.0 * PI)) * 2.0 / 3.0
    dlng += (150.0 * math.sin(x / 12.0 * PI) + 300.0 * math.sin(x / 30.0 * PI)) * 2.0 / 3.0

    rad_lat = lat / 180.0 * PI
    magic = math.sin(rad_lat)
    magic = 1 - EE * magic * magic
    sqrt_magic = math.sqrt(magic)

    dlat = (dlat * 180.0) / ((A * (1 - EE)) / (magic * sqrt_magic) * PI)
    dlng = (dlng * 180.0) / (A / sqrt_magic * math.cos(rad_lat) * PI)

    return lng + dlng, lat + dlat


def transform_wkt(wkt):
    """转换 WKT 字符串中的所有坐标对(支持 POLYGON / MULTIPOLYGON)"""
    if not wkt or not wkt.strip():
        return wkt

    def convert_pair(m):
        nlng, nlat = wgs84_to_gcj02(float(m.group(1)), float(m.group(2)))
        return f"{nlng:.6f} {nlat:.6f}"

    return re.sub(r'(\d+\.?\d*)\s+(\d+\.?\d*)', convert_pair, wkt)


def main():
    conn = pymysql.connect(**DB_CONFIG)
    cur = conn.cursor()

    # 1. 转换起点/终点坐标(如果有的话)
    cur.execute(
        "SELECT id, starting_latitude, starting_longitude, end_latitude, end_longitude "
        "FROM garden_gis_road "
        "WHERE (starting_latitude IS NOT NULL AND starting_latitude != '') "
        "   OR (end_latitude IS NOT NULL AND end_latitude != '')"
    )
    rows = cur.fetchall()
    updated = 0
    for row in rows:
        id_, slat, slng, elat, elng = row
        updates = []
        params = []

        if slat and slng and slat.strip():
            try:
                nlng, nlat = wgs84_to_gcj02(float(slng), float(slat))
                updates.append('starting_longitude = %s')
                updates.append('starting_latitude = %s')
                params.append(str(round(nlng, 6)))
                params.append(str(round(nlat, 6)))
            except ValueError:
                pass

        if elat and elng and elat.strip():
            try:
                nlng, nlat = wgs84_to_gcj02(float(elng), float(elat))
                updates.append('end_longitude = %s')
                updates.append('end_latitude = %s')
                params.append(str(round(nlng, 6)))
                params.append(str(round(nlat, 6)))
            except ValueError:
                pass

        if updates:
            sql = 'UPDATE garden_gis_road SET ' + ', '.join(updates) + ' WHERE id = %s'
            params.append(id_)
            cur.execute(sql, params)
            updated += 1

    conn.commit()
    print(f'起点/终点坐标: 转换 {updated} 条')

    # 2. 转换围栏坐标
    cur.execute(
        "SELECT COUNT(*) FROM garden_gis_road "
        "WHERE gis_polygon_coords IS NOT NULL AND gis_polygon_coords != ''"
    )
    total = cur.fetchone()[0]
    print(f'围栏坐标记录: {total} 条')

    batch_size = 500
    updated = 0
    for offset in range(0, total, batch_size):
        cur.execute(
            "SELECT id, gis_polygon_coords FROM garden_gis_road "
            "WHERE gis_polygon_coords IS NOT NULL AND gis_polygon_coords != '' "
            "LIMIT %s OFFSET %s",
            (batch_size, offset)
        )
        for id_, wkt in cur.fetchall():
            new_wkt = transform_wkt(wkt)
            if new_wkt != wkt:
                cur.execute(
                    "UPDATE garden_gis_road SET gis_polygon_coords = %s WHERE id = %s",
                    (new_wkt, id_)
                )
                updated += 1
        conn.commit()
        print(f'  进度: {min(offset + batch_size, total)}/{total}, 已更新: {updated}')

    print(f'围栏坐标: 转换 {updated} 条')

    # 3. 验证
    cur.execute(
        "SELECT COUNT(*) FROM garden_gis_road "
        "WHERE gis_polygon_coords IS NOT NULL AND gis_polygon_coords != ''"
    )
    total = cur.fetchone()[0]
    unconverted = 0
    cur.execute(
        "SELECT id, gis_polygon_coords FROM garden_gis_road "
        "WHERE gis_polygon_coords IS NOT NULL AND gis_polygon_coords != ''"
    )
    for id_, wkt in cur.fetchall():
        m = re.search(r'(\d+\.?\d*)\s+(\d+\.?\d*)', wkt)
        if m:
            wlng, wlat = float(m.group(1)), float(m.group(2))
            nlng, nlat = wgs84_to_gcj02(wlng, wlat)
            if abs(wlng - nlng) < 0.00001:
                unconverted += 1

    print(f'\n验证: {unconverted}/{total} 条未转换')
    if unconverted == 0:
        print('所有坐标已成功转换为 GCJ02(高德坐标系)')

    cur.close()
    conn.close()


if __name__ == '__main__':
    main()