From a8d4e5e87284b236acccad2475bc8023738a8a76 Mon Sep 17 00:00:00 2001 From: 王彪总 Date: Wed, 27 May 2026 19:14:09 +0800 Subject: [PATCH] feat(garden): 添加GIS道路管理和坐标转换功能 --- deploy-test.sh | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ pom.xml | 2 +- script/convert_gis_road_coords.py | 177 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ script/shell/deploy.sh | 35 +++++++++++++++++++++-------------- sql/dis-check-menu.sql | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ sql/garden_road_add_gis_fields.sql | 5 +++++ sql/sync_land_forest_to_road.sql | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/gis/GisRoadController.java | 114 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/gis/GisTypeController.java | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/gis/vo/GisRoadAssociateReqVO.java | 19 +++++++++++++++++++ urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/gis/vo/GisRoadDisassociateReqVO.java | 15 +++++++++++++++ urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/gis/vo/GisRoadExportVO.java | 42 ++++++++++++++++++++++++++++++++++++++++++ urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/gis/vo/GisRoadPageReqVO.java | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/gis/vo/GisRoadRespVO.java | 192 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/road/vo/RoadPageReqVO.java | 3 +++ urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/road/vo/RoadRespVO.java | 13 +++++++++++++ urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/problemtype/AppProblemTypeController.java | 14 +++++--------- urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/problemtype/vo/ProblemTypeCascadeRespVO.java | 22 ++++++++++++++++++++++ urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/dataobject/gis/GisRoadDO.java | 149 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/dataobject/gis/GisTypeDO.java | 37 +++++++++++++++++++++++++++++++++++++ urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/dataobject/road/RoadDO.java | 11 +++++++++++ urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/mysql/gis/GisRoadMapper.java | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/mysql/gis/GisTypeMapper.java | 9 +++++++++ urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/mysql/road/RoadMapper.java | 1 + urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/gis/GisRoadService.java | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/gis/GisRoadServiceImpl.java | 238 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/gis/GisTypeService.java | 13 +++++++++++++ urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/gis/GisTypeServiceImpl.java | 33 +++++++++++++++++++++++++++++++++ urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/problemtype/GardenProblemTypeService.java | 10 ++++++++++ urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/problemtype/GardenProblemTypeServiceImpl.java | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/util/GisCoordinateUtil.java | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ urbanops-server/pom.xml | 10 +++++----- urbanops-server/src/main/resources/application-local.yaml | 3 +++ 33 files changed, 1703 insertions(+), 29 deletions(-) create mode 100755 deploy-test.sh create mode 100644 script/convert_gis_road_coords.py create mode 100644 sql/dis-check-menu.sql create mode 100644 sql/garden_road_add_gis_fields.sql create mode 100644 sql/sync_land_forest_to_road.sql create mode 100644 urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/gis/GisRoadController.java create mode 100644 urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/gis/GisTypeController.java create mode 100644 urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/gis/vo/GisRoadAssociateReqVO.java create mode 100644 urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/gis/vo/GisRoadDisassociateReqVO.java create mode 100644 urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/gis/vo/GisRoadExportVO.java create mode 100644 urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/gis/vo/GisRoadPageReqVO.java create mode 100644 urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/gis/vo/GisRoadRespVO.java create mode 100644 urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/problemtype/vo/ProblemTypeCascadeRespVO.java create mode 100644 urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/dataobject/gis/GisRoadDO.java create mode 100644 urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/dataobject/gis/GisTypeDO.java create mode 100644 urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/mysql/gis/GisRoadMapper.java create mode 100644 urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/mysql/gis/GisTypeMapper.java create mode 100644 urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/gis/GisRoadService.java create mode 100644 urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/gis/GisRoadServiceImpl.java create mode 100644 urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/gis/GisTypeService.java create mode 100644 urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/gis/GisTypeServiceImpl.java create mode 100644 urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/util/GisCoordinateUtil.java diff --git a/deploy-test.sh b/deploy-test.sh new file mode 100755 index 0000000..bfea996 --- /dev/null +++ b/deploy-test.sh @@ -0,0 +1,68 @@ +#!/bin/bash +set -e + +# ==================== 配置 ==================== +SERVER_HOST="172.17.16.14" +SERVER_USER="root" +SERVER_PASS="JCSS@2025!@#$" +UPLOAD_DIR="/home/user/test_jar" +RESTART_SCRIPT="restart.sh" +JAR_NAME="urbanops-server.jar" +PROJECT_DIR="$(cd "$(dirname "$0")" && pwd)" +JAR_PATH="$PROJECT_DIR/urbanops-server/target/$JAR_NAME" + +# ==================== 函数 ==================== + +check_sshpass() { + if ! command -v sshpass &> /dev/null; then + echo "[check] sshpass 未安装,尝试安装..." + if command -v brew &> /dev/null; then + brew install hudochenkov/sshpass/sshpass + else + echo "[ERROR] 请手动安装 sshpass: brew install hudochenkov/sshpass/sshpass" + exit 1 + fi + fi +} + +build() { + echo "[build] 开始 Maven 打包(跳过测试)..." + cd "$PROJECT_DIR" + mvn clean package -DskipTests + if [ -f "$JAR_PATH" ]; then + echo "[build] 打包完成: $JAR_PATH ($(du -sh "$JAR_PATH" | cut -f1))" + else + echo "[ERROR] 打包失败,未找到 $JAR_PATH" + exit 1 + fi +} + +upload() { + echo "[upload] 上传 JAR 到 $SERVER_HOST:$UPLOAD_DIR/ ..." + sshpass -p "$SERVER_PASS" scp -o StrictHostKeyChecking=no "$JAR_PATH" "$SERVER_USER@$SERVER_HOST:$UPLOAD_DIR/" + echo "[upload] 上传完成" +} + +restart() { + echo "[restart] 调用远程重启脚本..." + sshpass -p "$SERVER_PASS" ssh -o StrictHostKeyChecking=no "$SERVER_USER@$SERVER_HOST" \ + "cd $UPLOAD_DIR && chmod +x $RESTART_SCRIPT && ./$RESTART_SCRIPT" + echo "[restart] 远程重启完成" +} + +# ==================== 主流程 ==================== + +echo "========================================" +echo " UrbanOps 测试环境部署" +echo " 目标: $SERVER_USER@$SERVER_HOST:$UPLOAD_DIR" +echo "========================================" + +check_sshpass +build +upload +restart + +echo "" +echo "========================================" +echo " 部署完成" +echo "========================================" diff --git a/pom.xml b/pom.xml index c23e6ca..74308ca 100644 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ urbanops-module-workorder urbanops-module-bpm - urbanops-module-report + urbanops-module-api diff --git a/script/convert_gis_road_coords.py b/script/convert_gis_road_coords.py new file mode 100644 index 0000000..879e8f5 --- /dev/null +++ b/script/convert_gis_road_coords.py @@ -0,0 +1,177 @@ +#!/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() diff --git a/script/shell/deploy.sh b/script/shell/deploy.sh index 1f7a314..4d2a94e 100644 --- a/script/shell/deploy.sh +++ b/script/shell/deploy.sh @@ -1,5 +1,5 @@ #!/bin/bash -set -e +set -eo pipefail DATE=$(date +%Y%m%d%H%M) # 基础路径 @@ -9,9 +9,9 @@ SOURCE_PATH=$BASE_PATH/build # 服务名称。同时约定部署服务的 jar 包名字也为它。 SERVER_NAME=urbanops-server # 环境 -PROFILES_ACTIVE=development +PROFILES_ACTIVE=dev # 健康检查 URL -HEALTH_CHECK_URL=http://127.0.0.1:48080/actuator/health/ +HEALTH_CHECK_URL=http://127.0.0.1:48081/actuator/health/ # heapError 存放路径 HEAP_ERROR_PATH=$BASE_PATH/heapError @@ -33,7 +33,8 @@ function backup() { # 如果存在,则备份到 backup 目录下,使用时间作为后缀 else echo "[backup] 开始备份 $SERVER_NAME ..." - cp $BASE_PATH/$SERVER_NAME.jar $BASE_PATH/backup/$SERVER_NAME-$DATE.jar + mkdir -p "$BASE_PATH/backup" + cp "$BASE_PATH/$SERVER_NAME.jar" "$BASE_PATH/backup/$SERVER_NAME-$DATE.jar" echo "[backup] 备份 $SERVER_NAME 完成" fi } @@ -47,12 +48,12 @@ function transfer() { echo "[transfer] $BASE_PATH/$SERVER_NAME.jar 不存在,跳过删除" else echo "[transfer] 移除 $BASE_PATH/$SERVER_NAME.jar 完成" - rm $BASE_PATH/$SERVER_NAME.jar + rm "$BASE_PATH/$SERVER_NAME.jar" fi # 复制新 jar 包 echo "[transfer] 从 $SOURCE_PATH 中获取 $SERVER_NAME.jar 并迁移至 $BASE_PATH ...." - cp $SOURCE_PATH/$SERVER_NAME.jar $BASE_PATH + cp "$SOURCE_PATH/$SERVER_NAME.jar" "$BASE_PATH" echo "[transfer] 转移 $SERVER_NAME.jar 完成" } @@ -60,7 +61,7 @@ function transfer() { # 停止:优雅关闭之前已经启动的服务 function stop() { echo "[stop] 开始停止 $BASE_PATH/$SERVER_NAME" - PID=$(ps -ef | grep $BASE_PATH/$SERVER_NAME | grep -v "grep" | awk '{print $2}') + PID=$(ps -ef | grep "$BASE_PATH/$SERVER_NAME.jar" | grep -v "grep" | awk '{print $2}') # 如果 Java 服务启动中,则进行关闭 if [ -n "$PID" ]; then # 正常关闭 @@ -70,7 +71,7 @@ function stop() { for ((i = 0; i < 120; i++)) do sleep 1 - PID=$(ps -ef | grep $BASE_PATH/$SERVER_NAME | grep -v "grep" | awk '{print $2}') + PID=$(ps -ef | grep "$BASE_PATH/$SERVER_NAME.jar" | grep -v "grep" | awk '{print $2}') if [ -n "$PID" ]; then echo -e ".\c" else @@ -99,7 +100,8 @@ function start() { echo "[start] PROFILES: $PROFILES_ACTIVE" # 开始启动 - BUILD_ID=dontKillMe nohup java -server $JAVA_OPS $JAVA_AGENT -jar $BASE_PATH/$SERVER_NAME.jar --spring.profiles.active=$PROFILES_ACTIVE & + mkdir -p "$HEAP_ERROR_PATH" + nohup java -server $JAVA_OPS $JAVA_AGENT -jar "$BASE_PATH/$SERVER_NAME.jar" --spring.profiles.active=$PROFILES_ACTIVE >> "$BASE_PATH/nohup.out" 2>&1 & echo "[start] 启动 $BASE_PATH/$SERVER_NAME 完成" } @@ -112,7 +114,7 @@ function healthCheck() { for ((i = 0; i < 120; i++)) do # 请求健康检查地址,只获取状态码。 - result=`curl -I -m 10 -o /dev/null -s -w %{http_code} $HEALTH_CHECK_URL || echo "000"` + result=$(curl -I -m 10 -o /dev/null -s -w %{http_code} "$HEALTH_CHECK_URL" || echo "000") # 如果状态码为 200,则说明健康检查通过 if [ "$result" == "200" ]; then echo "[healthCheck] 健康检查通过"; @@ -127,24 +129,29 @@ function healthCheck() { # 健康检查未通过,则异常退出 shell 脚本,不继续部署。 if [ ! "$result" == "200" ]; then echo "[healthCheck] 健康检查不通过,可能部署失败。查看日志,自行判断是否启动成功"; - tail -n 10 nohup.out + tail -n 10 "$BASE_PATH/nohup.out" exit 1; # 健康检查通过,打印最后 10 行日志,可能部署的人想看下日志。 else - tail -n 10 nohup.out + tail -n 10 "$BASE_PATH/nohup.out" fi # 如果未配置健康检查,则 sleep 120 秒,人工看日志是否部署成功。 else echo "[healthCheck] HEALTH_CHECK_URL 未配置,开始 sleep 120 秒"; sleep 120 echo "[healthCheck] sleep 120 秒完成,查看日志,自行判断是否启动成功"; - tail -n 50 nohup.out + tail -n 50 "$BASE_PATH/nohup.out" fi } # 部署 function deploy() { - cd $BASE_PATH + cd "$BASE_PATH" || { echo "[deploy] 错误:$BASE_PATH 目录不存在"; exit 1; } + if [ ! -d "$SOURCE_PATH" ]; then + echo "[deploy] 错误:构建目录 $SOURCE_PATH 不存在" + exit 1 + fi + mkdir -p "$BASE_PATH/backup" "$HEAP_ERROR_PATH" # 备份原 jar backup # 停止 Java 服务 diff --git a/sql/dis-check-menu.sql b/sql/dis-check-menu.sql new file mode 100644 index 0000000..8b3ea59 --- /dev/null +++ b/sql/dis-check-menu.sql @@ -0,0 +1,74 @@ +-- ===================================================== +-- dis核对 完整部署脚本 +-- 数据库: urban_ops_agent +-- 包含: 表结构变更 + 菜单创建 + 角色授权 +-- 执行方式: 在对应环境的数据库中执行此 SQL +-- ===================================================== + +-- ========================================== +-- Part 0: garden_gis_road 表结构变更 +-- ========================================== +-- 新增多边形围栏边界坐标字段(MySQL 8.x 兼容写法) +SET @col_exists = ( + SELECT COUNT(*) FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'garden_gis_road' + AND COLUMN_NAME = 'gis_polygon_coords' +); + +SET @sql = IF(@col_exists = 0, + 'ALTER TABLE garden_gis_road ADD COLUMN gis_polygon_coords LONGTEXT NOT NULL COMMENT ''多边形/矩形围栏边界坐标,格式:POLYGON((x1 y1, x2 y2, ..., xn yn, x1 y1)) 存储的是WGS84坐标系''', + 'SELECT ''COLUMN gis_polygon_coords already exists'' AS info' +); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +-- ========================================== +-- Part 1: 菜单创建 +-- ========================================== + +-- 1. 新增「dis核对」子菜单(位于电子围栏下) +INSERT INTO system_menu +(name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) +VALUES +('dis核对', '', 2, 1, 5698, 'disCheck', 'ep:view', 'fence/dis-check/index', 'DisCheck', 0, 1, 0, 1, '1', NOW(), '1', NOW(), 0); + +-- 2. 获取上一步插入的菜单ID,用于后续按钮权限插入 +SET @dis_check_menu_id = LAST_INSERT_ID(); + +-- 3. 新增「导出」按钮权限(挂在新菜单下) +INSERT INTO system_menu +(name, permission, type, sort, parent_id, path, icon, component, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) +VALUES +('导出', 'garden:gis-road:export', 3, 0, @dis_check_menu_id, '', '', '', 0, 1, 0, 1, '1', NOW(), '1', NOW(), 0); + +-- 4. 新增「查询」按钮权限 +INSERT INTO system_menu +(name, permission, type, sort, parent_id, path, icon, component, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) +VALUES +('查询', 'garden:gis-road:query', 3, 1, @dis_check_menu_id, '', '', '', 0, 1, 0, 1, '1', NOW(), '1', NOW(), 0); + +-- 5. 给超级管理员(role_id=1)分配菜单权限 +INSERT INTO system_role_menu (role_id, menu_id, creator, create_time, updater, update_time) +VALUES +(1, @dis_check_menu_id, '1', NOW(), '1', NOW()); + +INSERT INTO system_role_menu (role_id, menu_id, creator, create_time, updater, update_time) +SELECT 1, id, '1', NOW(), '1', NOW() +FROM system_menu +WHERE parent_id = @dis_check_menu_id AND deleted = 0; + +-- ===================================================== +-- 验证插入结果 +-- ===================================================== +SELECT id, name, type, parent_id, path, component, permission +FROM system_menu +WHERE parent_id = 5698 OR id = @dis_check_menu_id +ORDER BY parent_id, sort; + +SELECT rm.role_id, rm.menu_id, m.name, m.permission +FROM system_role_menu rm +JOIN system_menu m ON rm.menu_id = m.id +WHERE rm.menu_id = @dis_check_menu_id + OR rm.menu_id IN (SELECT id FROM system_menu WHERE parent_id = @dis_check_menu_id); diff --git a/sql/garden_road_add_gis_fields.sql b/sql/garden_road_add_gis_fields.sql new file mode 100644 index 0000000..7673298 --- /dev/null +++ b/sql/garden_road_add_gis_fields.sql @@ -0,0 +1,5 @@ +-- garden_road 表新增 GIS 关联字段 +ALTER TABLE garden_road + ADD COLUMN gis_plot_name VARCHAR(200) DEFAULT NULL COMMENT 'GIS 图斑名称', + ADD COLUMN gis_plot_area FLOAT DEFAULT NULL COMMENT 'GIS 图斑面积', + ADD COLUMN gis_is_related TINYINT DEFAULT 0 COMMENT '是否关联GIS (0=未关联, 1=已关联)'; diff --git a/sql/sync_land_forest_to_road.sql b/sql/sync_land_forest_to_road.sql new file mode 100644 index 0000000..011fc63 --- /dev/null +++ b/sql/sync_land_forest_to_road.sql @@ -0,0 +1,59 @@ +-- ============================================================ +-- 将 garden_gis_land_forest_data 数据同步到 garden_gis_road +-- 按 greenname + greentype 分组聚合 +-- - xbmj 求和 → gis_plot_area +-- - geometry 所有 MULTIPOLYGON 环拼接 → gis_polygon_coords (WKT) +-- - 其他 gis_ 字段取分组内 MAX 值 +-- +-- 注意:MySQL 不支持 ST_Union 聚合函数,geometry 并集通过 +-- GROUP_CONCAT 拼接各 MULTIPOLYGON 的内部环来实现,不做空间融合。 +-- 如需真正融合重叠区域,建议在 Java 层用 JTS 处理。 +-- ============================================================ + +-- 避免 GROUP_CONCAT 默认 1024 字节限制导致 WKT 截断 +SET SESSION group_concat_max_len = 10485760; -- 10MB + +INSERT INTO garden_gis_road ( + gis_plot_name, + gis_green_type_name, + gis_plot_area, + gis_polygon_coords, + gis_owner_unit, + gis_property_unit, + gis_manage_unit, + gis_street, + gis_plot_code, + gis_green_type_code, + street_id, + company_id, + dept_id +) +SELECT + greenname AS gis_plot_name, + greentype AS gis_green_type_name, + ROUND(SUM(xbmj), 2) AS gis_plot_area, + CONCAT( + 'MULTIPOLYGON(', + GROUP_CONCAT( + SUBSTR( + ST_AsText(geometry), + 14, -- 跳过 'MULTIPOLYGON('(13字符) + LENGTH(ST_AsText(geometry)) - 14 -- 去掉首尾各1个括号 + ) + SEPARATOR ',' + ), + ')' + ) AS gis_polygon_coords, + MAX(cqdw) AS gis_owner_unit, + MAX(cqdw) AS gis_property_unit, + MAX(gl_dw) AS gis_manage_unit, + MAX(CONCAT_WS('-', sheng, shi, xian, xiang)) AS gis_street, + '' AS gis_plot_code, + '' AS gis_green_type_code, + '' AS street_id, + 0 AS company_id, + 0 AS dept_id +FROM garden_gis_land_forest_data +WHERE greenname IS NOT NULL + AND greentype IS NOT NULL +GROUP BY greenname, greentype; diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/gis/GisRoadController.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/gis/GisRoadController.java new file mode 100644 index 0000000..cb7cc6a --- /dev/null +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/gis/GisRoadController.java @@ -0,0 +1,114 @@ +package com.zteits.urbanops.module.garden.controller.admin.gis; + +import com.zteits.urbanops.framework.common.pojo.PageParam; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.annotation.*; +import jakarta.annotation.Resource; +import org.springframework.validation.annotation.Validated; +import org.springframework.security.access.prepost.PreAuthorize; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Operation; + +import jakarta.validation.*; +import jakarta.servlet.http.*; +import java.io.IOException; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.*; + +import com.zteits.urbanops.framework.common.pojo.PageResult; +import com.zteits.urbanops.framework.common.pojo.CommonResult; +import static com.zteits.urbanops.framework.common.pojo.CommonResult.success; + +import com.zteits.urbanops.framework.excel.core.util.ExcelUtils; +import com.zteits.urbanops.framework.apilog.core.annotation.ApiAccessLog; +import static com.zteits.urbanops.framework.apilog.core.enums.OperateTypeEnum.*; + +import com.zteits.urbanops.module.garden.controller.admin.gis.vo.*; +import com.zteits.urbanops.module.garden.service.gis.GisRoadService; + +@Tag(name = "管理后台 - GIS 道路管理") +@RestController +@RequestMapping("/garden/gis/road") +@Validated +public class GisRoadController { + private static final Logger log = LoggerFactory.getLogger(GisRoadController.class); + + @Resource + private GisRoadService gisRoadService; + + @GetMapping("/list") + @Operation(summary = "获得 GIS 道路分页列表") + @PreAuthorize("@ss.hasPermission('garden:gis-road:query')") + public CommonResult> getGisRoadList(@Valid GisRoadPageReqVO pageReqVO) { + PageResult pageResult = gisRoadService.getGisRoadPage(pageReqVO); + return success(pageResult); + } + + @GetMapping("/list-all") + @Operation(summary = "获得全部 GIS 道路列表(不分页,用于地图渲染)") + @PreAuthorize("@ss.hasPermission('garden:gis-road:query')") + public CommonResult> getGisRoadAllList(GisRoadPageReqVO reqVO) { + return success(gisRoadService.getGisRoadAllList(reqVO)); + } + + @GetMapping("/get") + @Operation(summary = "获得 GIS 道路详情") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('garden:gis-road:query')") + public CommonResult getGisRoad(@RequestParam("id") Long id) { + return success(gisRoadService.getGisRoad(id)); + } + + @GetMapping("/export-excel") + @Operation(summary = "导出 GIS 道路 Excel") + @PreAuthorize("@ss.hasPermission('garden:gis-road:export')") + @ApiAccessLog(operateType = EXPORT) + public void exportGisRoadExcel(@Valid GisRoadPageReqVO pageReqVO, + HttpServletResponse response) throws IOException { + pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + List list = gisRoadService.getGisRoadPage(pageReqVO).getList(); + // 只导出表格展示的 6 列数据 + List exportList = new ArrayList<>(); + for (GisRoadRespVO vo : list) { + GisRoadExportVO exportVO = new GisRoadExportVO(); + exportVO.setGisPlotName(vo.getGisPlotName()); + exportVO.setRoadName(vo.getRoadName()); + exportVO.setGisPlotArea(vo.getGisPlotArea()); + exportVO.setTotalArea(vo.getTotalArea()); + exportVO.setGisIsRelatedStr(vo.getGisIsRelated() != null && vo.getGisIsRelated() == 1 ? "已关联" : "未关联"); + exportVO.setGisDiffArea(vo.getGisDiffArea()); + exportList.add(exportVO); + } + String filename = "GIS核对" + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")) + ".xls"; + ExcelUtils.write(response, filename, "数据", GisRoadExportVO.class, exportList); + } + + @PostMapping("/associate") + @Operation(summary = "关联道路到 GIS 图斑") + @PreAuthorize("@ss.hasPermission('garden:gis-road:update')") + public CommonResult associateRoad(@Valid @RequestBody GisRoadAssociateReqVO reqVO) { + gisRoadService.associateRoad(reqVO); + return success(true); + } + + @PostMapping("/disassociate") + @Operation(summary = "取消 GIS 图斑与道路的关联") + @PreAuthorize("@ss.hasPermission('garden:gis-road:update')") + public CommonResult disassociateRoad(@Valid @RequestBody GisRoadDisassociateReqVO reqVO) { + gisRoadService.disassociateRoad(reqVO); + return success(true); + } + + @PostMapping("/convert-coordinates") + @Operation(summary = "将全部道路坐标从 WGS84 转换为 GCJ02(高德坐标系)") + @PreAuthorize("@ss.hasPermission('garden:gis-road:update')") + public CommonResult convertCoordinates() { + int count = gisRoadService.convertCoordinatesToGcj02(); + log.info("坐标转换完成,共转换 {} 条记录", count); + return success("坐标转换完成,共转换 " + count + " 条记录"); + } + +} diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/gis/GisTypeController.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/gis/GisTypeController.java new file mode 100644 index 0000000..cba4017 --- /dev/null +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/gis/GisTypeController.java @@ -0,0 +1,55 @@ +package com.zteits.urbanops.module.garden.controller.admin.gis; + +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.web.bind.annotation.*; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import java.util.*; + +import com.zteits.urbanops.framework.common.pojo.CommonResult; +import static com.zteits.urbanops.framework.common.pojo.CommonResult.success; +import com.zteits.urbanops.module.garden.dal.dataobject.gis.GisTypeDO; +import com.zteits.urbanops.module.garden.service.gis.GisTypeService; + +@Tag(name = "管理后台 - GIS 绿地类型") +@RestController +@RequestMapping("/garden/gis/type") +public class GisTypeController { + + @Resource + private GisTypeService gisTypeService; + + @GetMapping("/list-by-parent") + @Operation(summary = "按父级编码查子类型(级联下拉用)") + public CommonResult>> listByParentCode( + @RequestParam(required = false) String parentCode) { + List list = gisTypeService.listByParentCode(parentCode); + List> result = new ArrayList<>(); + for (GisTypeDO t : list) { + Map m = new HashMap<>(); + m.put("code", t.getTypeCode()); + m.put("name", t.getTypeName()); + m.put("level", t.getLevel()); + m.put("parentCode", t.getParentCode()); + result.add(m); + } + return success(result); + } + + @GetMapping("/list-by-level") + @Operation(summary = "按层级查类型列表") + public CommonResult>> listByLevel(@RequestParam Integer level) { + List list = gisTypeService.listByLevel(level); + List> result = new ArrayList<>(); + for (GisTypeDO t : list) { + Map m = new HashMap<>(); + m.put("code", t.getTypeCode()); + m.put("name", t.getTypeName()); + m.put("level", t.getLevel()); + m.put("parentCode", t.getParentCode()); + result.add(m); + } + return success(result); + } +} diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/gis/vo/GisRoadAssociateReqVO.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/gis/vo/GisRoadAssociateReqVO.java new file mode 100644 index 0000000..dddea3f --- /dev/null +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/gis/vo/GisRoadAssociateReqVO.java @@ -0,0 +1,19 @@ +package com.zteits.urbanops.module.garden.controller.admin.gis.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import jakarta.validation.constraints.NotNull; + +@Schema(description = "管理后台 - GIS 道路关联 Request VO") +@Data +public class GisRoadAssociateReqVO { + + @Schema(description = "GIS道路ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "GIS道路ID不能为空") + private Long id; + + @Schema(description = "关联的道路ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "100") + @NotNull(message = "道路ID不能为空") + private Long roadId; + +} diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/gis/vo/GisRoadDisassociateReqVO.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/gis/vo/GisRoadDisassociateReqVO.java new file mode 100644 index 0000000..47bd74e --- /dev/null +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/gis/vo/GisRoadDisassociateReqVO.java @@ -0,0 +1,15 @@ +package com.zteits.urbanops.module.garden.controller.admin.gis.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import jakarta.validation.constraints.NotNull; + +@Schema(description = "管理后台 - GIS 道路取消关联 Request VO") +@Data +public class GisRoadDisassociateReqVO { + + @Schema(description = "GIS道路ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "GIS道路ID不能为空") + private Long id; + +} diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/gis/vo/GisRoadExportVO.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/gis/vo/GisRoadExportVO.java new file mode 100644 index 0000000..ce178a9 --- /dev/null +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/gis/vo/GisRoadExportVO.java @@ -0,0 +1,42 @@ +package com.zteits.urbanops.module.garden.controller.admin.gis.vo; + +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.zteits.urbanops.module.garden.util.TwoDecimalFloatSerializer; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import cn.idev.excel.annotation.ExcelIgnoreUnannotated; +import cn.idev.excel.annotation.ExcelProperty; + +@Schema(description = "GIS 道路核对导出 VO") +@Data +@ExcelIgnoreUnannotated +public class GisRoadExportVO { + + @Schema(description = "图斑名称") + @ExcelProperty("图斑名称") + private String gisPlotName; + + @Schema(description = "道路名称") + @ExcelProperty("道路名称") + private String roadName; + + @Schema(description = "图斑面积") + @ExcelProperty("图斑面积(㎡)") + @JsonSerialize(using = TwoDecimalFloatSerializer.class) + private Float gisPlotArea; + + @Schema(description = "道路面积") + @ExcelProperty("道路面积(㎡)") + @JsonSerialize(using = TwoDecimalFloatSerializer.class) + private Float totalArea; + + @Schema(description = "是否关联") + @ExcelProperty("是否关联") + private String gisIsRelatedStr; + + @Schema(description = "差异面积") + @ExcelProperty("差异面积(㎡)") + @JsonSerialize(using = TwoDecimalFloatSerializer.class) + private Float gisDiffArea; + +} diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/gis/vo/GisRoadPageReqVO.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/gis/vo/GisRoadPageReqVO.java new file mode 100644 index 0000000..8032502 --- /dev/null +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/gis/vo/GisRoadPageReqVO.java @@ -0,0 +1,58 @@ +package com.zteits.urbanops.module.garden.controller.admin.gis.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import com.zteits.urbanops.framework.common.pojo.PageParam; +import org.springframework.format.annotation.DateTimeFormat; +import java.time.LocalDateTime; + +import static com.zteits.urbanops.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - GIS 道路核对分页 Request VO") +@Data +public class GisRoadPageReqVO extends PageParam { + + @Schema(description = "道路名称") + private String roadName; + + @Schema(description = "业务线") + private String busiLine; + + @Schema(description = "街道ID") + private String streetId; + + @Schema(description = "街道名称") + private String streetName; + + @Schema(description = "养护级别") + private Integer levelId; + + @Schema(description = "道路属性") + private String roadAttr; + + @Schema(description = "道路方位") + private String direction; + + @Schema(description = "GIS 图斑编号") + private String gisPlotCode; + + @Schema(description = "GIS 图斑名称") + private String gisPlotName; + + @Schema(description = "是否已关联 (0=未关联, 1=已关联)") + private Integer gisIsRelated; + + @Schema(description = "归属公司ID") + private Long companyId; + + @Schema(description = "部门ID") + private Long deptId; + + @Schema(description = "GIS绿地类型编码(级联查询用)") + private String gisTypeCode; + + @Schema(description = "创建时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; + +} diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/gis/vo/GisRoadRespVO.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/gis/vo/GisRoadRespVO.java new file mode 100644 index 0000000..8ffcbf8 --- /dev/null +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/gis/vo/GisRoadRespVO.java @@ -0,0 +1,192 @@ +package com.zteits.urbanops.module.garden.controller.admin.gis.vo; + +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.zteits.urbanops.module.garden.util.TwoDecimalFloatSerializer; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; +import org.springframework.format.annotation.DateTimeFormat; +import java.time.LocalDateTime; +import cn.idev.excel.annotation.*; + +@Schema(description = "管理后台 - GIS 道路核对 Response VO") +@Data +@ExcelIgnoreUnannotated +public class GisRoadRespVO { + + @Schema(description = "ID", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("ID") + private Long id; + + @Schema(description = "关联道路ID") + @ExcelProperty("关联道路ID") + private Long gisRoadId; + + @Schema(description = "道路名称") + @ExcelProperty("道路名称") + private String roadName; + + @Schema(description = "业务线") + @ExcelProperty("业务线") + private String busiLine; + + @Schema(description = "街道ID") + @ExcelProperty("街道ID") + private String streetId; + + @Schema(description = "街道名称") + @ExcelProperty("街道名称") + private String streetName; + + @Schema(description = "养护级别") + @ExcelProperty("养护级别") + private Integer levelId; + + @Schema(description = "道路属性") + @ExcelProperty("道路属性") + private String roadAttr; + + @Schema(description = "道路方位") + @ExcelProperty("道路方位") + private String direction; + + @Schema(description = "行道树绿化面积") + @ExcelProperty("行道树绿化面积") + @JsonSerialize(using = TwoDecimalFloatSerializer.class) + private Float greenBeltTreeArea; + + @Schema(description = "绿化带面积") + @ExcelProperty("绿化带面积") + @JsonSerialize(using = TwoDecimalFloatSerializer.class) + private Float greenBeltArea; + + @Schema(description = "总绿化带面积") + @ExcelProperty("总绿化带面积") + @JsonSerialize(using = TwoDecimalFloatSerializer.class) + private Float totalArea; + + @Schema(description = "行道树面积") + @ExcelProperty("行道树面积") + @JsonSerialize(using = TwoDecimalFloatSerializer.class) + private Float treeArea; + + @Schema(description = "起点经度") + @ExcelProperty("起点经度") + private String startingLatitude; + + @Schema(description = "起点纬度") + @ExcelProperty("起点纬度") + private String startingLongitude; + + @Schema(description = "起点描述") + @ExcelProperty("起点描述") + private String startingRemark; + + @Schema(description = "终点经度") + @ExcelProperty("终点经度") + private String endLatitude; + + @Schema(description = "终点纬度") + @ExcelProperty("终点纬度") + private String endLongitude; + + @Schema(description = "终点描述") + @ExcelProperty("终点描述") + private String endRemark; + + @Schema(description = "备注") + @ExcelProperty("备注") + private String remark; + + @Schema(description = "归属公司ID") + @ExcelProperty("归属公司ID") + private Long companyId; + + @Schema(description = "部门ID") + @ExcelProperty("部门ID") + private Long deptId; + + @Schema(description = "创建者名称") + @ExcelProperty("创建者") + private String creatorName; + + @Schema(description = "创建时间") + @ExcelProperty("创建时间") + private LocalDateTime createTime; + + @Schema(description = "更新者名称") + @ExcelProperty("更新者") + private String updaterName; + + // ========== GIS 核对相关字段 ========== + + @Schema(description = "GIS 图斑编号") + @ExcelProperty("GIS图斑编号") + private String gisPlotCode; + + @Schema(description = "GIS 图斑名称") + @ExcelProperty("GIS图斑名称") + private String gisPlotName; + + @Schema(description = "GIS 图斑面积") + @ExcelProperty("GIS图斑面积") + @JsonSerialize(using = TwoDecimalFloatSerializer.class) + private Float gisPlotArea; + + @Schema(description = "GIS 绿地类型编码") + @ExcelProperty("GIS绿地类型编码") + private String gisGreenTypeCode; + + @Schema(description = "GIS 绿地类型名称") + @ExcelProperty("GIS绿地类型名称") + private String gisGreenTypeName; + + @Schema(description = "GIS 所属街道") + @ExcelProperty("GIS所属街道") + private String gisStreet; + + @Schema(description = "GIS 权属单位") + @ExcelProperty("GIS权属单位") + private String gisOwnerUnit; + + @Schema(description = "GIS 物业单位") + @ExcelProperty("GIS物业单位") + private String gisPropertyUnit; + + @Schema(description = "GIS 养护单位") + @ExcelProperty("GIS养护单位") + private String gisManageUnit; + + @Schema(description = "GIS 差异面积") + @ExcelProperty("GIS差异面积") + @JsonSerialize(using = TwoDecimalFloatSerializer.class) + private Float gisDiffArea; + + @Schema(description = "GIS 差异备注") + @ExcelProperty("GIS差异备注") + private String gisDiffRemark; + + @Schema(description = "是否已关联 (0=未关联, 1=已关联)") + @ExcelProperty("是否已关联") + private Integer gisIsRelated; + + @Schema(description = "关联人") + @ExcelProperty("关联人") + private String gisRelatedUser; + + @Schema(description = "关联时间") + @ExcelProperty("关联时间") + private LocalDateTime gisRelatedTime; + + @Schema(description = "多边形围栏边界坐标 WKT格式") + @ExcelProperty("围栏边界坐标") + private String gisPolygonCoords; + + // ========== 翻译字段 ========== + + @Schema(description = "公司名称") + private String companyName; + + @Schema(description = "部门名称") + private String deptName; + +} diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/road/vo/RoadPageReqVO.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/road/vo/RoadPageReqVO.java index 2f224ed..5cd5de5 100644 --- a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/road/vo/RoadPageReqVO.java +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/road/vo/RoadPageReqVO.java @@ -86,6 +86,9 @@ public class RoadPageReqVO extends PageParam { @Schema(description = "更新者", example = "芋艿") private String updaterName; + @Schema(description = "是否关联GIS (0=未关联, 1=已关联)") + private Integer gisIsRelated; + //对应前端排序字段 public static final Map> SORT_FIELD_MAP = Map.of( "id", RoadDO::getId, diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/road/vo/RoadRespVO.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/road/vo/RoadRespVO.java index 51de8b8..786bbcd 100644 --- a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/road/vo/RoadRespVO.java +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/road/vo/RoadRespVO.java @@ -122,4 +122,17 @@ public class RoadRespVO { @ExcelProperty("部门(道路负责部门)") private String deptName; + @Schema(description = "GIS 图斑名称") + @ExcelProperty("GIS图斑名称") + private String gisPlotName; + + @Schema(description = "GIS 图斑面积") + @ExcelProperty("GIS图斑面积") + @JsonSerialize(using = TwoDecimalFloatSerializer.class) + private Float gisPlotArea; + + @Schema(description = "是否关联GIS (0=未关联, 1=已关联)") + @ExcelProperty("是否关联GIS") + private Integer gisIsRelated; + } \ No newline at end of file diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/problemtype/AppProblemTypeController.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/problemtype/AppProblemTypeController.java index d783b1b..383c6f2 100644 --- a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/problemtype/AppProblemTypeController.java +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/problemtype/AppProblemTypeController.java @@ -1,9 +1,7 @@ package com.zteits.urbanops.module.garden.controller.app.problemtype; import com.zteits.urbanops.framework.common.pojo.CommonResult; -import com.zteits.urbanops.framework.common.util.object.BeanUtils; -import com.zteits.urbanops.module.garden.controller.admin.problemtype.vo.ProblemTypeRespVO; -import com.zteits.urbanops.module.garden.dal.dataobject.problemtype.GardenProblemTypeDO; +import com.zteits.urbanops.module.garden.controller.app.problemtype.vo.ProblemTypeCascadeRespVO; import com.zteits.urbanops.module.garden.service.problemtype.GardenProblemTypeService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; @@ -28,11 +26,9 @@ public class AppProblemTypeController { private GardenProblemTypeService gardenProblemTypeService; @GetMapping("/list-by-parent") - @Operation(summary = "根据父级编码和层级查询子类型列表(级联下拉用)") - public CommonResult> getProblemTypeListByParent( - @RequestParam(value = "parentCode", required = false) String parentCode, - @RequestParam("level") Integer level) { - List list = gardenProblemTypeService.getProblemTypeByParentCode(parentCode, level); - return success(BeanUtils.toBean(list, ProblemTypeRespVO.class)); + @Operation(summary = "查询问题类型级联列表(级联下拉用)") + public CommonResult> getProblemTypeListByParent( + @RequestParam(value = "parentCode", required = false) String parentCode) { + return success(gardenProblemTypeService.getProblemTypeTree(parentCode)); } } diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/problemtype/vo/ProblemTypeCascadeRespVO.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/problemtype/vo/ProblemTypeCascadeRespVO.java new file mode 100644 index 0000000..64e77cf --- /dev/null +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/problemtype/vo/ProblemTypeCascadeRespVO.java @@ -0,0 +1,22 @@ +package com.zteits.urbanops.module.garden.controller.app.problemtype.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Schema(description = "APP - 问题类型级联响应 VO") +@Data +public class ProblemTypeCascadeRespVO { + + @Schema(description = "问题类型名称", example = "树木倒伏") + private String label; + + @Schema(description = "问题类型编码", example = "PT001") + private String value; + + @Schema(description = "子类型列表") + private List children = new ArrayList<>(); + +} \ No newline at end of file diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/dataobject/gis/GisRoadDO.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/dataobject/gis/GisRoadDO.java new file mode 100644 index 0000000..bcf6b03 --- /dev/null +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/dataobject/gis/GisRoadDO.java @@ -0,0 +1,149 @@ +package com.zteits.urbanops.module.garden.dal.dataobject.gis; + +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.zteits.urbanops.module.garden.util.TwoDecimalFloatSerializer; +import lombok.*; +import com.baomidou.mybatisplus.annotation.*; +import com.zteits.urbanops.framework.mybatis.core.dataobject.BaseDO; + +/** + * GIS 道路核对 DO - garden_gis_road 表 + * + * @author UrbanOps + */ +@TableName("garden_gis_road") +@KeySequence("garden_gis_road_seq") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class GisRoadDO extends BaseDO { + + @TableId + private Long id; + + /** 关联道路ID */ + private Long gisRoadId; + + /** 道路名称 */ + private String roadName; + + /** 业务线:yl-园林;wy-物业;sz-市政 */ + private String busiLine; + + /** 街道ID */ + private String streetId; + + /** 街道名称 */ + private String streetName; + + /** 养护级别: 10:特级;11:一级:12:二级;13:三级 */ + private Integer levelId; + + /** 道路属性 */ + private String roadAttr; + + /** 道路方位 W-西 E-东 N-北 S-南 */ + private String direction; + + /** 行道树绿化面积 */ + @JsonSerialize(using = TwoDecimalFloatSerializer.class) + private Float greenBeltTreeArea; + + /** 绿化带面积 */ + @JsonSerialize(using = TwoDecimalFloatSerializer.class) + private Float greenBeltArea; + + /** 总绿化带面积 */ + @JsonSerialize(using = TwoDecimalFloatSerializer.class) + private Float totalArea; + + /** 行道树(棵)面积 */ + @JsonSerialize(using = TwoDecimalFloatSerializer.class) + private Float treeArea; + + /** 起点经度 */ + private String startingLatitude; + + /** 起点纬度 */ + private String startingLongitude; + + /** 起点描述 */ + private String startingRemark; + + /** 终点经度 */ + private String endLatitude; + + /** 终点纬度 */ + private String endLongitude; + + /** 终点描述 */ + private String endRemark; + + /** 备注 */ + private String remark; + + /** 归属公司ID */ + private Long companyId; + + /** 部门ID */ + private Long deptId; + + /** 创建者名称 */ + private String creatorName; + + /** 更新者名称 */ + private String updaterName; + + // ========== GIS 核对相关字段 ========== + + /** GIS 图斑编号 */ + private String gisPlotCode; + + /** GIS 图斑名称 */ + private String gisPlotName; + + /** GIS 图斑面积 */ + @JsonSerialize(using = TwoDecimalFloatSerializer.class) + private Float gisPlotArea; + + /** GIS 绿地类型编码 */ + private String gisGreenTypeCode; + + /** GIS 绿地类型名称 */ + private String gisGreenTypeName; + + /** GIS 所属街道 */ + private String gisStreet; + + /** GIS 权属单位 */ + private String gisOwnerUnit; + + /** GIS 物业单位 */ + private String gisPropertyUnit; + + /** GIS 养护单位 */ + private String gisManageUnit; + + /** GIS 差异面积 */ + @JsonSerialize(using = TwoDecimalFloatSerializer.class) + private Float gisDiffArea; + + /** GIS 差异备注 */ + private String gisDiffRemark; + + /** 是否已关联 (0=未关联, 1=已关联) */ + private Integer gisIsRelated; + + /** 关联人 */ + private String gisRelatedUser; + + /** 关联时间 */ + private java.time.LocalDateTime gisRelatedTime; + + /** 多边形围栏边界坐标 WKT格式: POLYGON((x1 y1, x2 y2, ...)) WGS84坐标系 */ + private String gisPolygonCoords; + +} diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/dataobject/gis/GisTypeDO.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/dataobject/gis/GisTypeDO.java new file mode 100644 index 0000000..197ce5b --- /dev/null +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/dataobject/gis/GisTypeDO.java @@ -0,0 +1,37 @@ +package com.zteits.urbanops.module.garden.dal.dataobject.gis; + +import lombok.*; +import com.baomidou.mybatisplus.annotation.*; +import com.zteits.urbanops.framework.mybatis.core.dataobject.BaseDO; + +/** + * 公园绿地类型字典 DO + */ +@TableName("garden_gis_type") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class GisTypeDO extends BaseDO { + + @TableId + private Long id; + + /** 类型编码 */ + private String typeCode; + + /** 类型名称 */ + private String typeName; + + /** 父级编码 */ + private String parentCode; + + /** 层级 */ + private Integer level; + + /** 排序 */ + private Integer sort; + +} diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/dataobject/road/RoadDO.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/dataobject/road/RoadDO.java index 14a06b7..34ea43b 100644 --- a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/dataobject/road/RoadDO.java +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/dataobject/road/RoadDO.java @@ -122,5 +122,16 @@ public class RoadDO extends BaseDO { */ private String updaterName; + // ========== GIS 关联字段 ========== + + /** GIS 图斑名称 */ + private String gisPlotName; + + /** GIS 图斑面积 */ + @JsonSerialize(using = TwoDecimalFloatSerializer.class) + private Float gisPlotArea; + + /** 是否关联GIS (0=未关联, 1=已关联) */ + private Integer gisIsRelated; } \ No newline at end of file diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/mysql/gis/GisRoadMapper.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/mysql/gis/GisRoadMapper.java new file mode 100644 index 0000000..3f5c1da --- /dev/null +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/mysql/gis/GisRoadMapper.java @@ -0,0 +1,55 @@ +package com.zteits.urbanops.module.garden.dal.mysql.gis; + +import com.zteits.urbanops.framework.common.pojo.PageResult; +import com.zteits.urbanops.framework.mybatis.core.query.LambdaQueryWrapperX; +import com.zteits.urbanops.framework.mybatis.core.mapper.BaseMapperX; +import com.zteits.urbanops.module.garden.dal.dataobject.gis.GisRoadDO; +import com.zteits.urbanops.module.garden.controller.admin.gis.vo.*; +import org.apache.ibatis.annotations.Mapper; +import java.util.*; + +/** + * GIS 道路核对 Mapper + */ +@Mapper +public interface GisRoadMapper extends BaseMapperX { + + /** XG 大类的子代码(不以 XG 开头,需特殊处理) */ + List XG_CHILD_CODES = Arrays.asList("RG", "AG", "BG", "MG", "WG", "SG", "UG"); + + /** 构建公共查询条件 */ + private LambdaQueryWrapperX buildQueryWrapper(GisRoadPageReqVO reqVO) { + LambdaQueryWrapperX wrapper = new LambdaQueryWrapperX() + .likeIfPresent(GisRoadDO::getRoadName, reqVO.getRoadName()) + .eqIfPresent(GisRoadDO::getBusiLine, reqVO.getBusiLine()) + .eqIfPresent(GisRoadDO::getStreetId, reqVO.getStreetId()) + .likeIfPresent(GisRoadDO::getStreetName, reqVO.getStreetName()) + .eqIfPresent(GisRoadDO::getLevelId, reqVO.getLevelId()) + .eqIfPresent(GisRoadDO::getRoadAttr, reqVO.getRoadAttr()) + .eqIfPresent(GisRoadDO::getDirection, reqVO.getDirection()) + .likeIfPresent(GisRoadDO::getGisPlotCode, reqVO.getGisPlotCode()) + .likeIfPresent(GisRoadDO::getGisPlotName, reqVO.getGisPlotName()) + .eqIfPresent(GisRoadDO::getGisIsRelated, reqVO.getGisIsRelated()) + .eqIfPresent(GisRoadDO::getCompanyId, reqVO.getCompanyId()) + .eqIfPresent(GisRoadDO::getDeptId, reqVO.getDeptId()); + + if (reqVO.getGisTypeCode() != null) { + if ("XG".equals(reqVO.getGisTypeCode())) { + wrapper.in(GisRoadDO::getGisGreenTypeCode, XG_CHILD_CODES); + } else { + wrapper.likeRight(GisRoadDO::getGisGreenTypeCode, reqVO.getGisTypeCode()); + } + } + return wrapper.orderByDesc(GisRoadDO::getId); + } + + default PageResult selectPage(GisRoadPageReqVO reqVO) { + return selectPage(reqVO, buildQueryWrapper(reqVO)); + } + + /** 不分页查询全部记录(用于地图渲染) */ + default List selectAllList(GisRoadPageReqVO reqVO) { + return selectList(buildQueryWrapper(reqVO)); + } + +} diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/mysql/gis/GisTypeMapper.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/mysql/gis/GisTypeMapper.java new file mode 100644 index 0000000..0227bc9 --- /dev/null +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/mysql/gis/GisTypeMapper.java @@ -0,0 +1,9 @@ +package com.zteits.urbanops.module.garden.dal.mysql.gis; + +import com.zteits.urbanops.framework.mybatis.core.mapper.BaseMapperX; +import com.zteits.urbanops.module.garden.dal.dataobject.gis.GisTypeDO; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface GisTypeMapper extends BaseMapperX { +} diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/mysql/road/RoadMapper.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/mysql/road/RoadMapper.java index 3cb4265..fe78a2b 100644 --- a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/mysql/road/RoadMapper.java +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/mysql/road/RoadMapper.java @@ -42,6 +42,7 @@ public interface RoadMapper extends BaseMapperX { .likeIfPresent(RoadDO::getCreatorName, reqVO.getCreatorName()) .betweenIfPresent(RoadDO::getCreateTime, reqVO.getCreateTime()) .likeIfPresent(RoadDO::getUpdaterName, reqVO.getUpdaterName()) + .eqIfPresent(RoadDO::getGisIsRelated, reqVO.getGisIsRelated()) .orderByDesc(RoadDO::getId)); } diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/gis/GisRoadService.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/gis/GisRoadService.java new file mode 100644 index 0000000..5c96957 --- /dev/null +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/gis/GisRoadService.java @@ -0,0 +1,59 @@ +package com.zteits.urbanops.module.garden.service.gis; + +import java.util.*; +import com.zteits.urbanops.module.garden.controller.admin.gis.vo.*; +import com.zteits.urbanops.framework.common.pojo.PageResult; + +/** + * GIS 道路 Service 接口 + * + * @author UrbanOps + */ +public interface GisRoadService { + + /** + * 获得 GIS 道路分页 + * + * @param pageReqVO 分页查询 + * @return GIS 道路分页 + */ + PageResult getGisRoadPage(GisRoadPageReqVO pageReqVO); + + /** + * 获得 GIS 道路 + * + * @param id 编号 + * @return GIS 道路 + */ + GisRoadRespVO getGisRoad(Long id); + + /** + * 获得全部 GIS 道路列表(不分页,用于地图渲染) + * + * @param reqVO 查询条件 + * @return GIS 道路列表 + */ + List getGisRoadAllList(GisRoadPageReqVO reqVO); + + /** + * 关联道路到 GIS 图斑 + * + * @param reqVO 关联请求 + */ + void associateRoad(GisRoadAssociateReqVO reqVO); + + /** + * 取消 GIS 图斑与道路的关联 + * + * @param reqVO 取消关联请求 + */ + void disassociateRoad(GisRoadDisassociateReqVO reqVO); + + /** + * 将 garden_gis_road 表中所有坐标从 WGS84 转换为 GCJ02(高德坐标系) + * + * @return 转换记录数 + */ + int convertCoordinatesToGcj02(); + +} diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/gis/GisRoadServiceImpl.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/gis/GisRoadServiceImpl.java new file mode 100644 index 0000000..4d05507 --- /dev/null +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/gis/GisRoadServiceImpl.java @@ -0,0 +1,238 @@ +package com.zteits.urbanops.module.garden.service.gis; + +import com.zteits.urbanops.module.system.api.dept.DeptApi; +import com.zteits.urbanops.module.system.api.dept.dto.DeptRespDTO; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import jakarta.annotation.Resource; +import org.springframework.util.CollectionUtils; +import org.springframework.validation.annotation.Validated; + +import java.time.LocalDateTime; +import java.util.*; +import java.util.stream.Collectors; + +import com.zteits.urbanops.module.garden.controller.admin.gis.vo.*; +import com.zteits.urbanops.module.garden.dal.dataobject.gis.GisRoadDO; +import com.zteits.urbanops.module.garden.dal.dataobject.road.RoadDO; +import com.zteits.urbanops.framework.common.pojo.PageResult; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.zteits.urbanops.framework.common.util.object.BeanUtils; +import com.zteits.urbanops.framework.security.core.util.SecurityFrameworkUtils; +import com.zteits.urbanops.module.garden.dal.mysql.gis.GisRoadMapper; +import com.zteits.urbanops.module.garden.dal.mysql.road.RoadMapper; +import com.zteits.urbanops.module.garden.util.GisCoordinateUtil; + +/** + * GIS 道路 Service 实现类 + * + * @author UrbanOps + */ +@Service +@Validated +public class GisRoadServiceImpl implements GisRoadService { + + @Resource + private GisRoadMapper gisRoadMapper; + + @Resource + private RoadMapper roadMapper; + + @Resource + private DeptApi deptApi; + + @Override + public PageResult getGisRoadPage(GisRoadPageReqVO pageReqVO) { + PageResult pageResult = gisRoadMapper.selectPage(pageReqVO); + PageResult page = BeanUtils.toBean(pageResult, GisRoadRespVO.class); + fillDeptAndCompanyName(page.getList()); + return page; + } + + @Override + public GisRoadRespVO getGisRoad(Long id) { + GisRoadDO gisRoadDO = gisRoadMapper.selectById(id); + if (gisRoadDO == null) { + return null; + } + GisRoadRespVO respVO = BeanUtils.toBean(gisRoadDO, GisRoadRespVO.class); + List list = new ArrayList<>(); + list.add(respVO); + fillDeptAndCompanyName(list); + return respVO; + } + + @Override + public List getGisRoadAllList(GisRoadPageReqVO reqVO) { + List list = gisRoadMapper.selectAllList(reqVO); + List voList = BeanUtils.toBean(list, GisRoadRespVO.class); + fillDeptAndCompanyName(voList); + return voList; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void associateRoad(GisRoadAssociateReqVO reqVO) { + GisRoadDO gisRoad = gisRoadMapper.selectById(reqVO.getId()); + if (gisRoad == null) { + throw new IllegalArgumentException("GIS道路不存在: id=" + reqVO.getId()); + } + RoadDO road = roadMapper.selectById(reqVO.getRoadId()); + if (road == null) { + throw new IllegalArgumentException("道路不存在: id=" + reqVO.getRoadId()); + } + + gisRoad.setGisRoadId(road.getId()); + gisRoad.setRoadName(road.getRoadName()); + gisRoad.setTotalArea(road.getTotalArea()); + gisRoad.setCompanyId(road.getCompanyId()); + gisRoad.setDeptId(road.getDeptId()); + + // 差异面积 = GIS面积 - 道路面积 + float gisArea = gisRoad.getGisPlotArea() != null ? gisRoad.getGisPlotArea() : 0; + float roadArea = road.getTotalArea() != null ? road.getTotalArea() : 0; + gisRoad.setGisDiffArea(gisArea - roadArea); + + gisRoad.setGisIsRelated(1); + gisRoad.setGisRelatedUser(SecurityFrameworkUtils.getLoginUserNickname()); + gisRoad.setGisRelatedTime(LocalDateTime.now()); + + gisRoadMapper.updateById(gisRoad); + + // 同步更新 garden_road 的 GIS 关联字段 + road.setGisPlotName(gisRoad.getGisPlotName()); + road.setGisPlotArea(gisRoad.getGisPlotArea()); + road.setGisIsRelated(1); + roadMapper.updateById(road); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void disassociateRoad(GisRoadDisassociateReqVO reqVO) { + GisRoadDO gisRoad = gisRoadMapper.selectById(reqVO.getId()); + if (gisRoad == null) { + throw new IllegalArgumentException("GIS道路不存在: id=" + reqVO.getId()); + } + + // 同步清空 garden_road 的 GIS 关联字段(LambdaUpdateWrapper 才能将字段设为 null) + if (gisRoad.getGisRoadId() != null) { + roadMapper.update(null, + new LambdaUpdateWrapper() + .eq(RoadDO::getId, gisRoad.getGisRoadId()) + .set(RoadDO::getGisPlotName, null) + .set(RoadDO::getGisPlotArea, null) + .set(RoadDO::getGisIsRelated, 0)); + } + + // 清空 garden_gis_road 的道路关联信息 + gisRoadMapper.update(null, + new LambdaUpdateWrapper() + .eq(GisRoadDO::getId, gisRoad.getId()) + .set(GisRoadDO::getGisRoadId, null) + .set(GisRoadDO::getRoadName, null) + .set(GisRoadDO::getTotalArea, null) + .set(GisRoadDO::getCompanyId, null) + .set(GisRoadDO::getDeptId, null) + .set(GisRoadDO::getGisDiffArea, null) + .set(GisRoadDO::getGisIsRelated, 0) + .set(GisRoadDO::getGisRelatedUser, null) + .set(GisRoadDO::getGisRelatedTime, null)); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public int convertCoordinatesToGcj02() { + List all = gisRoadMapper.selectList(); + if (CollectionUtils.isEmpty(all)) { + return 0; + } + int count = 0; + for (GisRoadDO road : all) { + boolean updated = false; + + // 转换起点坐标 + if (road.getStartingLatitude() != null && !road.getStartingLatitude().isBlank() + && road.getStartingLongitude() != null && !road.getStartingLongitude().isBlank()) { + try { + double lng = Double.parseDouble(road.getStartingLongitude()); + double lat = Double.parseDouble(road.getStartingLatitude()); + double[] gcj = GisCoordinateUtil.wgs84ToGcj02(lng, lat); + road.setStartingLongitude(String.format("%.6f", gcj[0])); + road.setStartingLatitude(String.format("%.6f", gcj[1])); + updated = true; + } catch (NumberFormatException ignored) { + } + } + + // 转换终点坐标 + if (road.getEndLatitude() != null && !road.getEndLatitude().isBlank() + && road.getEndLongitude() != null && !road.getEndLongitude().isBlank()) { + try { + double lng = Double.parseDouble(road.getEndLongitude()); + double lat = Double.parseDouble(road.getEndLatitude()); + double[] gcj = GisCoordinateUtil.wgs84ToGcj02(lng, lat); + road.setEndLongitude(String.format("%.6f", gcj[0])); + road.setEndLatitude(String.format("%.6f", gcj[1])); + updated = true; + } catch (NumberFormatException ignored) { + } + } + + // 转换多边形围栏坐标 + if (road.getGisPolygonCoords() != null && !road.getGisPolygonCoords().isBlank()) { + String converted = GisCoordinateUtil.transformPolygonWkt(road.getGisPolygonCoords()); + if (!converted.equals(road.getGisPolygonCoords())) { + road.setGisPolygonCoords(converted); + updated = true; + } + } + + if (updated) { + gisRoadMapper.updateById(road); + count++; + } + } + return count; + } + + /** + * 回填部门/公司名称 + */ + private void fillDeptAndCompanyName(List list) { + if (CollectionUtils.isEmpty(list)) { + return; + } + // 收集所有公司ID和部门ID + Set companyIds = list.stream() + .map(GisRoadRespVO::getCompanyId) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + Set deptIds = list.stream() + .map(GisRoadRespVO::getDeptId) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + deptIds.addAll(companyIds); + + if (deptIds.isEmpty()) { + return; + } + + Map deptMap = deptApi.getDeptMap(deptIds); + + list.forEach(vo -> { + if (vo.getDeptId() != null) { + DeptRespDTO dept = deptMap.get(vo.getDeptId()); + if (dept != null) { + vo.setDeptName(dept.getName()); + } + } + if (vo.getCompanyId() != null) { + DeptRespDTO company = deptMap.get(vo.getCompanyId()); + if (company != null) { + vo.setCompanyName(company.getName()); + } + } + }); + } + +} diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/gis/GisTypeService.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/gis/GisTypeService.java new file mode 100644 index 0000000..ea83516 --- /dev/null +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/gis/GisTypeService.java @@ -0,0 +1,13 @@ +package com.zteits.urbanops.module.garden.service.gis; + +import java.util.List; +import com.zteits.urbanops.module.garden.dal.dataobject.gis.GisTypeDO; + +public interface GisTypeService { + + /** 按父级编码查子类型列表 */ + List listByParentCode(String parentCode); + + /** 按层级查类型列表 */ + List listByLevel(Integer level); +} diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/gis/GisTypeServiceImpl.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/gis/GisTypeServiceImpl.java new file mode 100644 index 0000000..25ac9db --- /dev/null +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/gis/GisTypeServiceImpl.java @@ -0,0 +1,33 @@ +package com.zteits.urbanops.module.garden.service.gis; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.zteits.urbanops.module.garden.dal.dataobject.gis.GisTypeDO; +import com.zteits.urbanops.module.garden.dal.mysql.gis.GisTypeMapper; +import org.springframework.stereotype.Service; +import jakarta.annotation.Resource; +import java.util.List; + +@Service +public class GisTypeServiceImpl implements GisTypeService { + + @Resource + private GisTypeMapper gisTypeMapper; + + @Override + public List listByParentCode(String parentCode) { + return gisTypeMapper.selectList( + new LambdaQueryWrapper() + .eq(GisTypeDO::getParentCode, parentCode == null ? "" : parentCode) + .orderByAsc(GisTypeDO::getSort) + ); + } + + @Override + public List listByLevel(Integer level) { + return gisTypeMapper.selectList( + new LambdaQueryWrapper() + .eq(GisTypeDO::getLevel, level) + .orderByAsc(GisTypeDO::getSort) + ); + } +} diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/problemtype/GardenProblemTypeService.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/problemtype/GardenProblemTypeService.java index 3c3bcc7..fc18530 100644 --- a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/problemtype/GardenProblemTypeService.java +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/problemtype/GardenProblemTypeService.java @@ -2,7 +2,9 @@ package com.zteits.urbanops.module.garden.service.problemtype; import com.zteits.urbanops.module.garden.controller.admin.problemtype.vo.ProblemTypePageReqVO; import com.zteits.urbanops.module.garden.controller.admin.problemtype.vo.ProblemTypeSaveReqVO; +import com.zteits.urbanops.module.garden.controller.app.problemtype.vo.ProblemTypeCascadeRespVO; import com.zteits.urbanops.module.garden.dal.dataobject.problemtype.GardenProblemTypeDO; +import com.zteits.urbanops.framework.common.pojo.CommonResult; import com.zteits.urbanops.framework.common.pojo.PageResult; import jakarta.validation.Valid; @@ -63,6 +65,14 @@ public interface GardenProblemTypeService { List getProblemTypeByParentCode(String parentCode, Integer level); /** + * 获取问题类型级联树 + * + * @param parentCode 父级编码(为空则返回顶级树) + * @return 级联树列表 + */ + List getProblemTypeTree(String parentCode); + + /** * 根据父级编码生成下一个类型编码 * * @param parentCode 父级编码 diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/problemtype/GardenProblemTypeServiceImpl.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/problemtype/GardenProblemTypeServiceImpl.java index 89e4928..7089996 100644 --- a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/problemtype/GardenProblemTypeServiceImpl.java +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/problemtype/GardenProblemTypeServiceImpl.java @@ -3,13 +3,17 @@ package com.zteits.urbanops.module.garden.service.problemtype; import com.zteits.urbanops.framework.common.enums.CommonStatusEnum; import com.zteits.urbanops.module.garden.controller.admin.problemtype.vo.ProblemTypePageReqVO; import com.zteits.urbanops.module.garden.controller.admin.problemtype.vo.ProblemTypeSaveReqVO; +import com.zteits.urbanops.module.garden.controller.app.problemtype.vo.ProblemTypeCascadeRespVO; import com.zteits.urbanops.module.system.dal.dataobject.dict.DictDataDO; import com.zteits.urbanops.module.system.service.dict.DictDataService; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; import jakarta.annotation.Resource; +import java.util.ArrayList; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; import com.zteits.urbanops.module.garden.dal.dataobject.problemtype.GardenProblemTypeDO; import com.zteits.urbanops.framework.common.pojo.PageResult; @@ -93,6 +97,53 @@ public class GardenProblemTypeServiceImpl implements GardenProblemTypeService { } @Override + public List getProblemTypeTree(String parentCode) { + List allList = gardenProblemTypeMapper.selectList( + new com.zteits.urbanops.framework.mybatis.core.query.LambdaQueryWrapperX() + .eq(GardenProblemTypeDO::getStatus, 1) + .eq(GardenProblemTypeDO::getParentCode, parentCode) + .orderByAsc(GardenProblemTypeDO::getSort) + .orderByAsc(GardenProblemTypeDO::getId)); + + Map> childrenMap = allList.stream() + .filter(item -> item.getParentCode() != null && !item.getParentCode().equals(item.getTypeCode())) + .collect(Collectors.groupingBy(GardenProblemTypeDO::getParentCode)); + + List result = new ArrayList<>(); + if (parentCode == null) { + List rootList = allList.stream() + .filter(item -> item.getParentCode() == null || item.getParentCode().equals(item.getTypeCode())) + .collect(Collectors.toList()); + for (GardenProblemTypeDO root : rootList) { + ProblemTypeCascadeRespVO vo = buildTreeNode(root, childrenMap); + result.add(vo); + } + } else { + List parentList = allList.stream() + .filter(item -> parentCode.equals(item.getTypeCode())) + .collect(Collectors.toList()); + for (GardenProblemTypeDO parent : parentList) { + ProblemTypeCascadeRespVO vo = buildTreeNode(parent, childrenMap); + result.add(vo); + } + } + return result; + } + + private ProblemTypeCascadeRespVO buildTreeNode(GardenProblemTypeDO item, Map> childrenMap) { + ProblemTypeCascadeRespVO vo = new ProblemTypeCascadeRespVO(); + vo.setLabel(item.getTypeName()); + vo.setValue(item.getTypeCode()); + List children = childrenMap.get(item.getTypeCode()); + if (children != null && !children.isEmpty()) { + for (GardenProblemTypeDO child : children) { + vo.getChildren().add(buildTreeNode(child, childrenMap)); + } + } + return vo; + } + + @Override public String generateNextCode(String parentCode) { return gardenProblemTypeMapper.generateNextCode(parentCode); } diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/util/GisCoordinateUtil.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/util/GisCoordinateUtil.java new file mode 100644 index 0000000..d4719f5 --- /dev/null +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/util/GisCoordinateUtil.java @@ -0,0 +1,86 @@ +package com.zteits.urbanops.module.garden.util; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * GIS 坐标转换工具 — WGS84 ↔ GCJ02(高德坐标系) + */ +public class GisCoordinateUtil { + + private static final double PI = Math.PI; + private static final double A = 6378245.0; // 长半轴 + private static final double EE = 0.00669342162296594323; // 偏心率平方 + + /** + * WGS84 转 GCJ02(高德) + */ + public static double[] wgs84ToGcj02(double lng, double lat) { + if (outOfChina(lng, lat)) { + return new double[]{lng, lat}; + } + double dLat = transformLat(lng - 105.0, lat - 35.0); + double dLng = transformLng(lng - 105.0, lat - 35.0); + double radLat = lat / 180.0 * PI; + double magic = Math.sin(radLat); + magic = 1 - EE * magic * magic; + double sqrtMagic = Math.sqrt(magic); + dLat = (dLat * 180.0) / ((A * (1 - EE)) / (magic * sqrtMagic) * PI); + dLng = (dLng * 180.0) / (A / sqrtMagic * Math.cos(radLat) * PI); + return new double[]{lng + dLng, lat + dLat}; + } + + /** + * GCJ02 转 WGS84 + */ + public static double[] gcj02ToWgs84(double lng, double lat) { + if (outOfChina(lng, lat)) { + return new double[]{lng, lat}; + } + double[] gcj = wgs84ToGcj02(lng, lat); + return new double[]{lng * 2 - gcj[0], lat * 2 - gcj[1]}; + } + + /** 匹配 WKT 中的坐标对:数字 空格 数字 */ + private static final Pattern COORD_PAIR = Pattern.compile("(\\d+\\.?\\d*)\\s+(\\d+\\.?\\d*)"); + + /** + * 转换 WKT 字符串中的所有坐标对(WGS84 → GCJ02) + * 支持 POLYGON、MULTIPOLYGON 等任意 WKT 格式 + */ + public static String transformPolygonWkt(String wkt) { + if (wkt == null || wkt.isBlank()) { + return wkt; + } + Matcher m = COORD_PAIR.matcher(wkt); + StringBuilder sb = new StringBuilder(); + while (m.find()) { + double lng = Double.parseDouble(m.group(1)); + double lat = Double.parseDouble(m.group(2)); + double[] gcj = wgs84ToGcj02(lng, lat); + m.appendReplacement(sb, String.format("%.6f %.6f", gcj[0], gcj[1])); + } + m.appendTail(sb); + return sb.toString(); + } + + private static boolean outOfChina(double lng, double lat) { + return lng < 72.004 || lng > 137.8347 || lat < 0.8293 || lat > 55.8271; + } + + private static double transformLat(double x, double y) { + double ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x)); + ret += (20.0 * Math.sin(6.0 * x * PI) + 20.0 * Math.sin(2.0 * x * PI)) * 2.0 / 3.0; + ret += (20.0 * Math.sin(y * PI) + 40.0 * Math.sin(y / 3.0 * PI)) * 2.0 / 3.0; + ret += (160.0 * Math.sin(y / 12.0 * PI) + 320.0 * Math.sin(y * PI / 30.0)) * 2.0 / 3.0; + return ret; + } + + private static double transformLng(double x, double y) { + double ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x)); + ret += (20.0 * Math.sin(6.0 * x * PI) + 20.0 * Math.sin(2.0 * x * PI)) * 2.0 / 3.0; + ret += (20.0 * Math.sin(x * PI) + 40.0 * Math.sin(x / 3.0 * PI)) * 2.0 / 3.0; + ret += (150.0 * Math.sin(x / 12.0 * PI) + 300.0 * Math.sin(x / 30.0 * PI)) * 2.0 / 3.0; + return ret; + } +} diff --git a/urbanops-server/pom.xml b/urbanops-server/pom.xml index 8e96d6f..27dd08a 100644 --- a/urbanops-server/pom.xml +++ b/urbanops-server/pom.xml @@ -58,11 +58,11 @@ - - com.zteits.boot - urbanops-module-report - ${revision} - + + + + + com.zteits.boot diff --git a/urbanops-server/src/main/resources/application-local.yaml b/urbanops-server/src/main/resources/application-local.yaml index ff41af5..231aeaf 100644 --- a/urbanops-server/src/main/resources/application-local.yaml +++ b/urbanops-server/src/main/resources/application-local.yaml @@ -186,6 +186,9 @@ urbanops: refund-notify-url: http://yunai.natapp1.cc/admin-api/pay/notify/refund # 支付渠道的【退款】回调地址 transfer-notify-url: https://yunai.natapp1.cc/admin-api/pay/notify/transfer # 支付渠道的【转账】回调地址 demo: false # 开启演示模式 + security: + mock-enable: true # 本地调试启用 Mock 认证,Token 格式: test{userId}(如 test1 表示用户ID=1) + mock-secret: test tencent-lbs-key: TVDBZ-TDILD-4ON4B-PFDZA-RNLKH-VVF6E # QQ 地图的密钥 https://lbs.qq.com/service/staticV2/staticGuide/staticDoc justauth: -- libgit2 0.21.4