This commit is contained in:
2025-07-07 20:11:59 +08:00
parent ab0fdbc447
commit 06e3aa2eb3
2009 changed files with 193082 additions and 0 deletions

View File

@ -0,0 +1,88 @@
package solaranalyzer
import (
"encoding/json"
"os/exec"
"path/filepath"
"strings"
"github.com/tidwall/gjson"
)
const (
drillingHole = "hole" // 钻孔
pillar = "pile" // 桩基
bracket = "shelves" // 支架
solarPanel = "pho" // 光伏板
)
type Point struct {
Points []XYPosition // 光伏板的坐标点
ID int // pv_module 表中的自增ID
}
type XYPosition struct {
X float64 `json:"x"`
Y float64 `json:"y"`
}
// getSolarPanelRanges 解析光伏板 JSON
func getSolarPanelRanges(file string) []Point {
var points []Point
gjson.Parse(file).ForEach(func(_, record gjson.Result) bool {
record.Get("detail").ForEach(func(_, detail gjson.Result) bool {
id := record.Get("id").Int()
var longitudeAndLatitude []XYPosition
gjson.Get(detail.String(), "positions").ForEach(func(_, position gjson.Result) bool {
longitude := position.Get("lng").Float()
latitude := position.Get("lat").Float()
longitudeAndLatitude = append(longitudeAndLatitude, XYPosition{X: longitude, Y: latitude})
return true
})
points = append(points, Point{ID: int(id), Points: longitudeAndLatitude})
return true
})
return true
})
return points
}
// convertToLongitudeAndLatitude 根据 tif 文件将坐标转换为经纬度
func convertToLongitudeAndLatitude(tifFilePath string, coordinatePoints []XYPosition) []XYPosition {
positionsJSON, err := json.Marshal(coordinatePoints)
if err != nil {
panic(err)
}
convertPath, err := filepath.Abs("./convert")
if err != nil {
panic(err)
}
output, err := exec.Command(convertPath, filepath.Clean(tifFilePath), string(positionsJSON)).CombinedOutput()
if err != nil {
panic(err)
}
outputLines := strings.Split(string(output), "\n")
lastLine := outputLines[len(outputLines)-2] // 获取最后一行
// 解析二进制返回的 JSON, 并将其转换为 XYPosition 结构
var longitudeAndLatitude []XYPosition
gjson.Get(lastLine, "data").ForEach(func(_, position gjson.Result) bool {
longitudeAndLatitude = append(longitudeAndLatitude, XYPosition{
X: position.Get("y").Float(),
Y: position.Get("x").Float(),
})
return true
})
return longitudeAndLatitude
}

View File

@ -0,0 +1,57 @@
package solaranalyzer
import (
"math"
"github.com/tomchavakis/geojson/geometry"
)
const (
earthRadius = 6371000 // 地球半径(米)
radiusMeters = 0.07 // 半径7厘米
numPoints = 8 // 点的数量
)
type GeoPoint struct {
ID int
Coordinates Coordinates
}
type Coordinates struct {
Lng float64
Lat float64
Alt float64
}
// ExpandToCircle 用于桩点和支架
// 以一个点为中心,扩展出一个圆形
func (p Coordinates) ExpandToCircle() geometry.Polygon {
circle := make([]Coordinates, numPoints)
for i := 0; i < numPoints; i++ {
angle := 2 * math.Pi * float64(i) / float64(numPoints)
// 计算偏移量(米)
dx := radiusMeters * math.Cos(angle)
dy := radiusMeters * math.Sin(angle)
// 将米转换为经纬度
dLng := dx / (earthRadius * math.Cos(p.Lat*math.Pi/180)) * 180 / math.Pi
dLat := dy / earthRadius * 180 / math.Pi
circle[i] = Coordinates{
Lng: p.Lng + dLng,
Lat: p.Lat + dLat,
Alt: p.Alt,
}
}
var coords []geometry.Point
for _, point := range circle {
coords = append(coords, geometry.Point{Lat: point.Lat, Lng: point.Lng})
}
return geometry.Polygon{Coordinates: []geometry.LineString{{Coordinates: coords}}}
}

View File

@ -0,0 +1,695 @@
package solaranalyzer
import (
"context"
"encoding/json"
"errors"
"fmt"
"strings"
"time"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
"github.com/samber/lo"
"github.com/tidwall/gjson"
"github.com/tiger1103/gfast/v3/internal/app/system/dao"
"github.com/tiger1103/gfast/v3/internal/app/system/model/do"
"github.com/tiger1103/gfast/v3/internal/app/system/model/entity"
"github.com/tiger1103/gfast/v3/library/liberr"
"github.com/tomchavakis/geojson/geometry"
"github.com/tomchavakis/turf-go"
)
type AIResult struct {
SolarPanels []XYPosition // 光伏板的坐标点
Brackets []XYPosition // 支架的坐标点
Pillars []XYPosition // 立柱的坐标点
Holes []XYPosition // 钻孔的坐标点
}
// ToGeoPoints 将坐标转换为经纬度坐标
func (r *AIResult) ToGeoPoints(tifPath string) *AIResult {
r.SolarPanels = convertToLongitudeAndLatitude(tifPath, r.SolarPanels) // 光伏板
r.Brackets = convertToLongitudeAndLatitude(tifPath, r.Brackets) // 支架
r.Pillars = convertToLongitudeAndLatitude(tifPath, r.Pillars) // 立柱
r.Holes = convertToLongitudeAndLatitude(tifPath, r.Holes) // 钻孔
return r
}
// IsCircleContainsPoint 判断圆形是否包含点返回桩点的主键ID
func (r *AIResult) IsCircleContainsPoint(m map[string]map[string]GeoPoint, usePillars string) []int {
var ids []int
var elements []XYPosition
switch usePillars {
case drillingHole:
elements = r.Holes
case pillar:
elements = r.Pillars
case bracket:
elements = r.Brackets
}
for _, aiPillar := range elements {
// AI 识别点
aiPoint := geometry.Point{Lng: aiPillar.X, Lat: aiPillar.Y}
// 方阵层
for _, points := range m {
// 立柱层
for _, dbPillar := range points {
circle := dbPillar.Coordinates.ExpandToCircle()
if ok, err := turf.PointInPolygon(aiPoint, circle); ok && err == nil {
ids = append(ids, dbPillar.ID)
}
}
}
}
return ids
}
// GetFromDB 从数据库中获取光伏板、支架、桩点的数据
func (r *AIResult) GetFromDB(projectID string) ([]Point, map[string]map[string]GeoPoint) {
// 光伏板、支架
var solarPanels []Point
var pillars map[string]map[string]GeoPoint
if len(r.SolarPanels) != 0 {
if points, err := getSolarPanelCenters(projectID); err == nil {
solarPanels = points
}
}
if len(r.Pillars) != 0 {
pillars = getPillars(projectID)
}
return solarPanels, pillars
}
// 传入 AI 结果
// 1. 将其周围的组串,全部标记为已完成
// 2. 根据其 【方阵ID】【Type = 13】更新其在 work_status 中的总数 ok
// 3. 根据当前的 Name Etc: G1.123.1.1 将 pv_module 中的 G1.123.1 状态修改为已完成
// 4. 记录该数据由 AI 识别。
func (r *AIResult) Run(projectID string, id int) {
// 获取光伏板和支架,桩点的数据
panels, brackets := r.GetFromDB(projectID)
fmt.Println("(内容)光伏板和支架: ", panels)
fmt.Println("(内容)桩点: ", brackets)
// 预处理光伏板为多边形
polygons := make([]geometry.Polygon, len(panels))
for i, rect := range panels {
coords := make([]geometry.Point, len(rect.Points))
for j, p := range rect.Points {
coords[j] = geometry.Point{Lng: p.X, Lat: p.Y}
}
polygons[i] = geometry.Polygon{Coordinates: []geometry.LineString{{Coordinates: coords}}}
}
fmt.Println("(内容)预处理光伏板为多边形: ", polygons)
fmt.Println("(内容)光伏板坐标后点: ", r.SolarPanels)
// 光伏板的主键ID
var matchedRectangles []int
// 匹配AI识别的光伏板中心点
for _, point := range r.SolarPanels {
for i, polygon := range polygons {
if ok, err := turf.PointInPolygon(geometry.Point{Lng: point.X, Lat: point.Y}, polygon); err == nil && ok {
matchedRectangles = append(matchedRectangles, panels[i].ID)
break
}
}
}
_, err := dao.ManageTaskResult.Ctx(context.Background()).Insert(
lo.Map(matchedRectangles, func(v int, _ int) do.ManageTaskResult {
return do.ManageTaskResult{
TaskId: id,
PvId: v,
}
}))
liberr.ErrIsNil(context.Background(), err)
if len(matchedRectangles) > 0 { // TODO: 去除写入数据库的逻辑
// if err := ProcessMatchedRectangles(matchedRectangles); err != nil {
// g.Log("uav").Error(context.Background(), "更新匹配到的光伏板失败: ", err)
// }
}
// 更新钻孔
if len(r.Holes) > 0 {
ids := r.IsCircleContainsPoint(brackets, drillingHole)
if len(ids) > 0 {
if err := processPillarIDs(ids, 12, id); err != nil {
g.Log("uav").Error(context.Background(), "更新匹配到的支架失败: ", err)
}
}
}
// 更新支架和桩点
if len(r.Pillars) > 0 {
ids := r.IsCircleContainsPoint(brackets, pillar)
if len(ids) > 0 {
if err := processPillarIDs(ids, 13, id); err != nil {
g.Log("uav").Error(context.Background(), "更新匹配到的支架失败: ", err)
}
}
}
if len(r.Brackets) > 0 {
ids := r.IsCircleContainsPoint(brackets, bracket)
if len(ids) > 0 {
if err := processPillarIDs(ids, 14, id); err != nil {
g.Log("uav").Error(context.Background(), "更新匹配到的支架失败: ", err)
}
}
}
}
// 通过主键ID可以从 qianqi_guangfuban_ids_zhijia 表中查询到对应的 “方阵ID” 和 "子项目ID"
// 将得到的结果分组,第一层为 方阵ID第二层 G01.12.18,第三层数组,[ G01.12.18.1,G01.12.18.2,G01.12.18.3 ]
// 如果第三层中有数据则从数据库中向前后查询相邻组串并将其组串修改为已完成同时取出第二层和方阵ID用于更新 pv_module 和 work_status 表中的数据。
func processPillarIDs(ids []int, t, id int) error {
return nil
// 查询 qianqi_guangfuban_ids_zhijia 表
var zhijias []entity.QianqiGuangfubanIdsZhijia
if err := dao.QianqiGuangfubanIdsZhijia.Ctx(context.Background()).
Fields("id,fangzhen_id,sub_projectid,name").
WhereIn(dao.QianqiGuangfubanIdsZhijia.Columns().Id, ids).
Scan(&zhijias); err != nil {
return err
}
// 第一层方阵ID, 第二层支架名称, 第三层支架信息
realGeoPoint := make(map[string]map[string][]entity.QianqiGuangfubanIdsZhijia)
for _, zhijia := range zhijias {
fangzhenID := zhijia.FangzhenId // 方阵ID
zhijiaName := zhijia.Name[:strings.LastIndex(zhijia.Name, ".")] // 支架名称
if _, ok := realGeoPoint[fangzhenID]; !ok {
realGeoPoint[fangzhenID] = make(map[string][]entity.QianqiGuangfubanIdsZhijia)
}
realGeoPoint[fangzhenID][zhijiaName] = append(realGeoPoint[fangzhenID][zhijiaName], zhijia)
}
// 查询支架及其相邻的组串
for _, geoPoints := range realGeoPoint {
for pointKey, pointList := range geoPoints {
if len(pointList) > 1 {
firstPoint := pointList[0]
simulationID := firstPoint.FangzhenId
namePattern := firstPoint.Name[:strings.LastIndex(firstPoint.Name, ".")] + ".%"
var updatedPoints []entity.QianqiGuangfubanIdsZhijia
if err := dao.QianqiGuangfubanIdsZhijia.Ctx(context.Background()).Where(dao.QianqiGuangfubanIdsZhijia.Columns().FangzhenId, simulationID).
Where(dao.QianqiGuangfubanIdsZhijia.Columns().Name+" LIKE ?", namePattern).
Scan(&updatedPoints); err == nil && len(updatedPoints) > 0 {
realGeoPoint[simulationID][pointKey] = updatedPoints
}
}
}
}
// 更新数据库状态
for _, geoPoints := range realGeoPoint {
for key, pointList := range geoPoints {
// 取出所有的ID
primaryIDs := lo.Map(pointList, func(v entity.QianqiGuangfubanIdsZhijia, _ int) int {
return v.Id
})
g.Try(context.Background(), func(ctx context.Context) {
// 根据主键修改为已完成
// 修改组串的状态
// G01.12.18.1 -- G01.12.18.2 -- G01.12.18.3
_, err := dao.QianqiGuangfubanIdsZhijia.Ctx(ctx).WhereIn(dao.QianqiGuangfubanIdsZhijia.Columns().Id, primaryIDs).
Data(g.Map{
dao.QianqiGuangfubanIdsZhijia.Columns().Status: 2,
}).Update()
liberr.ErrIsNil(ctx, err)
fangzhenID := pointList[0].FangzhenId
// 根据名字从 pv_module 中获取主键id
result, err := dao.PvModule.Ctx(ctx).Fields("id").Where(dao.PvModule.Columns().Name, key).
Where(dao.PvModule.Columns().FangzhenId, fangzhenID).
Where(dao.PvModule.Columns().Type, t).One()
liberr.ErrIsNil(ctx, err)
// 获取主键ID
keyID := result.Map()["id"].(int)
dao.ManageTaskResult.Ctx(ctx).Data(do.ManageTaskResult{
TaskId: id,
PvId: keyID,
}).Insert() // 记录
// 更新 PVModule 表中的状态
// G01.12.18
lastID, err := dao.PvModule.Ctx(ctx).WhereIn(dao.PvModule.Columns().Id, keyID).
Where(dao.PvModule.Columns().FangzhenId, fangzhenID).
Where(dao.PvModule.Columns().Type, t).
Data(g.Map{
dao.PvModule.Columns().Status: 2,
}).Update()
// 获取影响行数并更新 work_status 表
rows, err := lastID.RowsAffected()
liberr.ErrIsNil(ctx, err)
if rows > 0 {
_, err := dao.WorkStatus.Ctx(ctx).Where(dao.WorkStatus.Columns().FangzhenId, fangzhenID).
Where(dao.WorkStatus.Columns().Type, t).
Data(g.Map{
dao.WorkStatus.Columns().Finished: gdb.Raw(
"finished + 1",
),
}).Update()
liberr.ErrIsNil(ctx, err)
}
// 根据 类型和方阵ID 获取对应的 work_id
dd := entity.WorkStatus{}
err = dao.WorkStatus.Ctx(ctx).Fields("work_id").Where(dao.WorkStatus.Columns().FangzhenId, fangzhenID).
Where(dao.WorkStatus.Columns().Type, t).Scan(&dd)
liberr.ErrIsNil(ctx, err)
work_id := dd.WorkId
err = UpdateWorkSchedule(work_id)
liberr.ErrIsNil(ctx, err)
})
}
}
return nil
}
// 处理匹配到的光伏板
func ProcessMatchedRectangles(matchedRectangles []int) error {
if len(matchedRectangles) == 0 {
return nil
}
return g.Try(context.Background(), func(ctx context.Context) {
timeNow := time.Now().Format("2006-01-02")
// 更新光伏板状态
_, err := dao.PvModule.Ctx(ctx).
WhereIn(dao.PvModule.Columns().Id, matchedRectangles).
Data(g.Map{
dao.PvModule.Columns().Status: 3,
dao.PvModule.Columns().DoneTime: timeNow,
}).Update()
liberr.ErrIsNil(ctx, err, "更新光伏板状态失败")
// 获取所有的 work_id
pvModules := []entity.PvModule{}
err = dao.PvModule.Ctx(ctx).Fields("work_id").WhereIn("id", matchedRectangles).Scan(&pvModules)
liberr.ErrIsNil(ctx, err, "查询 pv_module 表失败")
// 提取所有的 work_id
workIds := lo.Map(pvModules, func(module entity.PvModule, _ int) string {
return module.WorkId
})
// 查询 work_status 表
var workStatus []entity.WorkStatus
err = dao.WorkStatus.Ctx(ctx).WhereIn(dao.WorkStatus.Columns().WorkId, workIds).Scan(&workStatus)
liberr.ErrIsNil(ctx, err, "查询 work_status 表失败")
statusMap := make(map[string]entity.WorkStatus)
for _, status := range workStatus {
statusMap[status.WorkId] = status
}
// 遍历 pv_module 表,根据 work_id 更新 work_status 表
for _, pvModule := range pvModules {
if _, ok := statusMap[pvModule.WorkId]; !ok {
continue
}
// 2. 更新计划表 work_schdule 表中计划
// timeNow := time.Now().Format("2006-01-02")
// 根据 work_id 查询所有的计划
var workSchedules []entity.WorkSchedule
err = dao.WorkSchedule.Ctx(ctx).Where(dao.WorkSchedule.Columns().WorkId, pvModule.WorkId).Scan(&workSchedules)
if len(workSchedules) == 0 {
liberr.ErrIsNil(ctx, errors.New("需要前往进度计划对应的方阵中,设置计划日期,随后再来实现进度复查功能!"))
return
}
liberr.ErrIsNil(ctx, err, "查询 work_schedule 表失败")
// 根据计划起始和结束时间,当天位于哪个计划中
for _, schedule := range workSchedules {
startAt := schedule.StartAt.Format("Y-m-d")
endAt := schedule.EndAt.Format("Y-m-d")
if timeNow >= startAt && timeNow <= endAt {
// 反序列化 Detail
var details []struct {
Date string `json:"date"`
PlanNum int `json:"planNum"`
FinishedNum int `json:"finishedNum"` // 手动填充的完成数量
AutoFill int `json:"autoFill"` // 是否自动填充
}
if err := json.Unmarshal([]byte(schedule.Detail), &details); err != nil {
continue
}
// 遍历 details找到当天的计划
for i := range details {
if details[i].Date == timeNow {
details[i].AutoFill++ // 标记为 AI 识别
}
}
// 序列化 details
detailBytes, err := json.Marshal(details)
if err != nil {
continue
}
// 更新 work_schedule 表
_, err = dao.WorkSchedule.Ctx(ctx).Where(dao.WorkSchedule.Columns().WorkId, schedule.WorkId).
Data(g.Map{
dao.WorkSchedule.Columns().Detail: string(detailBytes),
dao.WorkSchedule.Columns().FinishedNum: schedule.FinishedNum + 1,
}).Update()
liberr.ErrIsNil(ctx, err, "更新 work_schedule 表失败")
var currentStatus entity.WorkStatus
err = dao.WorkStatus.Ctx(ctx).Where(dao.WorkStatus.Columns().WorkId, schedule.WorkId).Scan(&currentStatus)
liberr.ErrIsNil(ctx, err, "查询 work_status 表失败")
if currentStatus.Finished+1 <= currentStatus.Total {
finished := currentStatus.Finished + 1
_, err = dao.WorkStatus.Ctx(ctx).Where(dao.WorkStatus.Columns().WorkId, currentStatus.WorkId).
Where(dao.WorkStatus.Columns().Type, currentStatus.Type).
Data(g.Map{
dao.WorkStatus.Columns().Finished: finished,
}).Update()
liberr.ErrIsNil(ctx, err, "更新 work_status 表失败")
}
}
}
}
})
}
// 传入一个 workID 修改其对应的计划数据
func UpdateWorkSchedule(workID string) error {
return g.Try(context.Background(), func(ctx context.Context) {
// 查询 work_status 表
var workStatus []entity.WorkStatus
err := dao.WorkStatus.Ctx(ctx).Where(dao.WorkStatus.Columns().WorkId, workID).Scan(&workStatus)
liberr.ErrIsNil(ctx, err, "查询 work_status 表失败")
// 遍历 work_status 表,如果自增后的完成量小于等于总数,则将完成量加一
for _, status := range workStatus {
if status.Finished+1 <= status.Total {
finished := status.Finished + 1
_, err = dao.WorkStatus.Ctx(ctx).Where(dao.WorkStatus.Columns().WorkId, status.WorkId).
Data(g.Map{
dao.WorkStatus.Columns().Finished: finished,
}).Update()
liberr.ErrIsNil(ctx, err, "更新 work_status 表失败")
}
// 2. 更新计划表 work_schdule 表中计划
timeNow := time.Now().Format("2006-01-02")
// 根据 work_id 查询所有的计划
var workSchedules []entity.WorkSchedule
err = dao.WorkSchedule.Ctx(ctx).Where(dao.WorkSchedule.Columns().WorkId, status.WorkId).Scan(&workSchedules)
liberr.ErrIsNil(ctx, err, "查询 work_schedule 表失败")
// 会出现多个计划的情况下,需要根据计划的起始和结束时间来确定具体的计划
for _, schedule := range workSchedules {
startAt := schedule.StartAt.Format("Y-m-d")
endAt := schedule.EndAt.Format("Y-m-d")
if timeNow >= startAt && timeNow <= endAt {
// 反序列化 Detail
var details []struct {
Date string `json:"date"`
PlanNum int `json:"planNum"`
FinishedNum int `json:"finishedNum"` // 手动填充的完成数量
AutoFill int `json:"autoFill"` // 是否自动填充
}
if err := json.Unmarshal([]byte(schedule.Detail), &details); err != nil {
continue
}
// 遍历 details找到当天的计划
for i := range details {
if details[i].Date == timeNow {
// 为自动填充的数量加一
details[i].AutoFill++
}
}
// 序列化 details
detailBytes, err := json.Marshal(details)
if err != nil {
continue
}
// 更新 work_schedule 表
_, err = dao.WorkSchedule.Ctx(ctx).Where(dao.WorkSchedule.Columns().WorkId, status.WorkId).
Where(dao.WorkSchedule.Columns().Id, schedule.Id).
Data(g.Map{
dao.WorkSchedule.Columns().Detail: string(detailBytes),
}).Update()
liberr.ErrIsNil(ctx, err, "更新 work_schedule 表失败")
}
}
}
})
}
// parseAIResult 解析识别的结果,提取出光伏板、支架和立柱的中心点
// 该函数解析的 JSON 格式如下:
//
// {
// "targets": [
// {
// "type": "pho", // 光伏板
// "size": [100, 100],
// "leftTopPoint": [10, 10]
// }
// ]
// }
func ParseAIResul2(fileContent string) *AIResult {
// 钻孔、桩基、支架、光伏板
var solarPanels, brackets, pillars, holes []XYPosition
gjson.Get(fileContent, "targets").ForEach(func(_, target gjson.Result) bool {
targetType := target.Get("type").String()
if targetType != solarPanel && targetType != bracket && targetType != pillar && targetType != drillingHole {
return true
}
size := lo.FilterMap(target.Get("size").Array(), func(size gjson.Result, _ int) (int, bool) {
return int(size.Int()), true
})
leftTopPoint := lo.FilterMap(target.Get("leftTopPoint").Array(), func(point gjson.Result, _ int) (int, bool) {
return int(point.Int()), true
})
// 获取中心点
centroidX := float64(leftTopPoint[0]) + float64(size[0])/2
centroidY := float64(leftTopPoint[1]) + float64(size[1])/2
position := XYPosition{X: centroidX, Y: centroidY}
switch targetType {
case solarPanel:
solarPanels = append(solarPanels, position)
case bracket:
brackets = append(brackets, position)
case pillar:
pillars = append(pillars, position)
case drillingHole:
holes = append(holes, position)
}
return true
})
marshal1, _ := json.Marshal(solarPanels)
marshal2, _ := json.Marshal(brackets)
marshal3, _ := json.Marshal(pillars)
marshal4, _ := json.Marshal(holes)
fmt.Println("光伏板的坐标点", string(marshal1))
fmt.Println("支架的坐标点", string(marshal2))
fmt.Println("立柱的坐标点", string(marshal3))
fmt.Println("钻孔的坐标点", string(marshal4))
return &AIResult{
SolarPanels: solarPanels,
Brackets: brackets,
Pillars: pillars,
Holes: holes,
}
}
// getSolarPanelCenters 获取数据库中光伏板的中心点
func getSolarPanelCenters(projectID string) ([]Point, error) {
// 获取所有的子项目
subProjectQuery := g.Model("sub_project").Fields("id").Where("project_id", projectID)
var pvModules []entity.PvModule
if err := dao.PvModule.Ctx(context.Background()).
WhereIn(dao.PvModule.Columns().SubProjectid, subProjectQuery).
Where(dao.PvModule.Columns().Type, 15).
WhereNot(dao.PvModule.Columns().Status, 2).
WhereNot(dao.PvModule.Columns().Status, 3).
Scan(&pvModules); err != nil {
return nil, err
}
var points []Point
// tojson
dd, _ := json.Marshal(pvModules)
gjson.Parse(string(dd)).ForEach(func(_, record gjson.Result) bool {
record.Get("detail").ForEach(func(_, detail gjson.Result) bool {
id := record.Get("id").Int()
var longitudeAndLatitude []XYPosition
gjson.Get(detail.String(), "positions").ForEach(func(_, position gjson.Result) bool {
longitude := position.Get("lng").Float()
latitude := position.Get("lat").Float()
longitudeAndLatitude = append(longitudeAndLatitude, XYPosition{X: longitude, Y: latitude})
return true
})
points = append(points, Point{ID: int(id), Points: longitudeAndLatitude})
return true
})
return true
})
return points, nil
}
func getPillars(projectID string) map[string]map[string]GeoPoint {
ctx := context.Background()
// 获取所有的子项目
subProjectQuery := g.Model("sub_project").Fields("id").Where("project_id", projectID)
// 查询 qianqi_guangfuban_ids_zhuangdian 表
var zhuangdians []entity.QianqiGuangfubanIdsZhuangdian
if err := dao.QianqiGuangfubanIdsZhuangdian.Ctx(ctx).
Fields("id,fangzhen_id,detail,name").
WhereIn(dao.QianqiGuangfubanIdsZhuangdian.Columns().SubProjectid, subProjectQuery).
Scan(&zhuangdians); err != nil {
panic(err)
}
// 第一层方阵ID, 第二层立柱名称, 第三层立柱信息
realGeoPoint := make(map[string]map[string]GeoPoint)
for _, zhuangdian := range zhuangdians {
fangzhenID := zhuangdian.FangzhenId
pillarName := zhuangdian.Name
var coordinates struct {
Position struct {
Lng float64 `json:"lng"`
Lat float64 `json:"lat"`
Alt float64 `json:"alt"`
}
}
if err := json.Unmarshal([]byte(zhuangdian.Detail), &coordinates); err != nil {
continue
}
if _, ok := realGeoPoint[fangzhenID]; !ok {
realGeoPoint[fangzhenID] = make(map[string]GeoPoint)
}
realGeoPoint[fangzhenID][pillarName] = GeoPoint{
ID: zhuangdian.Id,
Coordinates: Coordinates{
Lng: coordinates.Position.Lng,
Lat: coordinates.Position.Lat,
Alt: coordinates.Position.Alt,
},
}
}
return realGeoPoint
}
// UpdateTableData 更新表中的数据
func UpdateTableData(task_id int) error {
ctx := context.Background()
// 根据任务ID查询出所有的 PV Module ID
var pvlist []struct {
PvID int `orm:"pv_id"`
FangZhenID int `orm:"fangzhen_id"`
WorkID string `orm:"work_id"`
TypeID int `orm:"type"`
}
// 查询出基本信息
err := dao.PvModule.Ctx(ctx).As("pv").Fields("pv.id as pv_id", "pv.fangzhen_id", "pv.work_id", "pv.type").
LeftJoin("manage_task_result as mts", "pv.id = mts.pv_id").
Where("mts.task_id = ?", task_id).Scan(&pvlist)
if err != nil {
return fmt.Errorf("查询 PV Module 表失败: %w", err)
}
if len(pvlist) == 0 {
return nil
}
return g.Try(ctx, func(ctx context.Context) {
for _, v := range pvlist {
if v.TypeID == 15 { // 光伏板
if err := ProcessMatchedRectangles([]int{v.PvID}); err != nil {
return
}
continue
}
// 更新 PV Module 表
lastID, err := dao.PvModule.Ctx(ctx).Where(dao.PvModule.Columns().Id, v.PvID).
Where(dao.PvModule.Columns().FangzhenId, v.FangZhenID).
Where(dao.PvModule.Columns().Type, v.TypeID).
Data(g.Map{
dao.PvModule.Columns().Status: 2,
}).Update()
liberr.ErrIsNil(ctx, err)
rows, err := lastID.RowsAffected()
liberr.ErrIsNil(ctx, err)
if rows > 0 {
// 更新 work_status 表
_, err := dao.WorkStatus.Ctx(ctx).Where(dao.WorkStatus.Columns().FangzhenId, v.FangZhenID).
Where(dao.WorkStatus.Columns().Type, v.TypeID).
Data(g.Map{
dao.WorkStatus.Columns().Finished: gdb.Raw(
"finished + 1",
),
}).Update()
liberr.ErrIsNil(ctx, err)
UpdateWorkSchedule(v.WorkID)
}
}
})
}