├── config.xml
├── go.mod
├── LICENSE
├── configs.go
├── osgeo_utils.h
├── PrintSummary.go
├── go.sum
├── RaterReader.go
├── GeojsonEdit.go
├── cgo_header.go
├── Tiler.go
├── test_gogeo.go
├── Clip.go
├── Erase.go
├── SymDifference.go
├── Update.go
├── SHPEdit.go
├── RasterClip.go
├── Identity.go
├── MbtilesGen.go
└── Union.go
/config.xml:
--------------------------------------------------------------------------------
1 |
2 | postgres
3 | localhost
4 | 5432
5 | postgres
6 | 1
7 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/GrainArc/Gogeo
2 |
3 | go 1.24.2
4 |
5 | require (
6 | github.com/google/uuid v1.6.0
7 | github.com/mattn/go-sqlite3 v1.14.32
8 | github.com/paulmach/orb v0.11.1
9 | gorm.io/gorm v1.30.5
10 | )
11 |
12 | require (
13 | github.com/jinzhu/inflection v1.0.0 // indirect
14 | github.com/jinzhu/now v1.1.5 // indirect
15 | go.mongodb.org/mongo-driver v1.11.4 // indirect
16 | golang.org/x/text v0.29.0 // indirect
17 | )
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU AFFERO GENERAL PUBLIC LICENSE
2 | Version 3, 19 November 2007
3 |
4 | Copyright (C) 2024 [GrainArc]
5 |
6 | This program is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU Affero General Public License as published
8 | by the Free Software Foundation, either version 3 of the License, or
9 | (at your option) any later version.
10 |
11 | This program is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU Affero General Public License for more details.
15 |
16 | You should have received a copy of the GNU Affero General Public License
17 | along with this program. If not, see .
18 |
19 | For the full license text, visit: https://www.gnu.org/licenses/agpl-3.0.html
20 |
--------------------------------------------------------------------------------
/configs.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2025 [GrainArc]
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published
6 | by the Free Software Foundation, either version 3 of the License, or
7 | (at your option) any later version.
8 |
9 | This program is distributed in the hope that it will be useful,
10 | but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | GNU Affero General Public License for more details.
13 |
14 | You should have received a copy of the GNU Affero General Public License
15 | along with this program. If not, see .
16 | */
17 | package Gogeo
18 |
19 | import (
20 | "encoding/xml"
21 | "fmt"
22 | "os"
23 | )
24 |
25 | var MainConfig PGConfig
26 |
27 | type PGConfig struct {
28 | XMLName xml.Name `xml:"config"`
29 | Dbname string `xml:"dbname"`
30 | Host string `xml:"host"`
31 | Port string `xml:"port"`
32 | Username string `xml:"user"`
33 | Password string `xml:"password"`
34 | }
35 |
36 | func init() {
37 |
38 | xmlFile, err := os.Open("config.xml")
39 | if err != nil {
40 | fmt.Println("Error opening file:", err)
41 | return
42 | }
43 | defer xmlFile.Close()
44 |
45 | xmlDecoder := xml.NewDecoder(xmlFile)
46 | err = xmlDecoder.Decode(&MainConfig)
47 | if err != nil {
48 | fmt.Println("Error decoding XML:", err)
49 | return
50 | }
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/osgeo_utils.h:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2025 [GrainArc]
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published
6 | by the Free Software Foundation, either version 3 of the License, or
7 | (at your option) any later version.
8 |
9 | This program is distributed in the hope that it will be useful,
10 | but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | GNU Affero General Public License for more details.
13 |
14 | You should have received a copy of the GNU Affero General Public License
15 | along with this program. If not, see .
16 | */
17 | // osgeo_utils.h
18 | #ifndef OSGEO_UTILS_H
19 | #define OSGEO_UTILS_H
20 | #include
21 | #include
22 | #include
23 | #include
24 | #include
25 | #include
26 | #include
27 | #include
28 | #include
29 | #include
30 | #include
31 | #include
32 | #include
33 | #ifdef __cplusplus
34 | extern "C" {
35 | #endif
36 |
37 | // 声明外部函数,避免重复定义
38 | extern int handleProgressUpdate(double, char*, void*);
39 | // 获取数据集信息
40 | typedef struct {
41 | int width;
42 | int height;
43 | int bandCount;
44 | double geoTransform[6];
45 | char projection[2048];
46 | } DatasetInfo;
47 | typedef struct {
48 | unsigned char* data;
49 | size_t size;
50 | } ImageBuffer;
51 | OGRLayerH createMemoryLayer(const char* layerName, OGRwkbGeometryType geomType, OGRSpatialReferenceH srs);
52 | int check_isnan(double x);
53 | int check_isinf(double x);
54 | int addFieldToLayer(OGRLayerH layer, const char* fieldName, OGRFieldType fieldType);
55 | void copyFieldValue(OGRFeatureH srcFeature, OGRFeatureH dstFeature, int srcFieldIndex, int dstFieldIndex);
56 | int progressCallback(double dfComplete, const char *pszMessage, void *pProgressArg);
57 | OGRLayerH cloneLayerToMemory(OGRLayerH sourceLayer, const char* layerName);
58 | int copyFeaturesWithSpatialFilter(OGRLayerH sourceLayer, OGRLayerH targetLayer, OGRGeometryH filterGeom);
59 | int copyAllFeatures(OGRLayerH sourceLayer, OGRLayerH targetLayer);
60 | int isFeatureOnBorder(OGRFeatureH feature, double minX, double minY, double maxX, double maxY, double buffer);
61 | int geometryWKTEqual(OGRGeometryH geom1, OGRGeometryH geom2);
62 | OGRGeometryH setPrecisionIfNeeded(OGRGeometryH geom, double gridSize, int flags);
63 | int setLayerGeometryPrecision(OGRLayerH layer, double gridSize, int flags);
64 | OGRFeatureH setFeatureGeometryPrecision(OGRFeatureH feature, double gridSize, int flags);
65 | OGRGeometryH forceGeometryType(OGRGeometryH geom, OGRwkbGeometryType targetType);
66 | OGRGeometryH mergeGeometryCollection(OGRGeometryH geomCollection, OGRwkbGeometryType targetType);
67 | OGRGeometryH normalizeGeometryType(OGRGeometryH geom, OGRwkbGeometryType expectedType);
68 | OGRGeometryH createTileClipGeometry(double minX, double minY, double maxX, double maxY);
69 | OGRLayerH clipLayerToTile(OGRLayerH sourceLayer, double minX, double minY, double maxX, double maxY, const char* layerName, const char* sourceIdentifier);
70 | void getTileBounds(int x, int y, int zoom, double* minX, double* minY, double* maxX, double* maxY);
71 | GDALDatasetH reprojectToWebMercator(GDALDatasetH hSrcDS);
72 | int readTileData(GDALDatasetH hDS, double minX, double minY, double maxX, double maxY,
73 | int tileSize, unsigned char* buffer);
74 | int getDatasetInfo(GDALDatasetH hDS, DatasetInfo* info);
75 | GDALDatasetH clipRasterByGeometry(GDALDatasetH srcDS, OGRGeometryH geom, double *bounds);
76 | int writeJPEG(GDALDatasetH ds, const char* filename, int quality);
77 | int writeImage(GDALDatasetH ds, const char* filename, const char* format, int quality);
78 | ImageBuffer* writeImageToMemory(GDALDatasetH ds, const char* format, int quality);
79 | GDALDatasetH clipPixelRasterByMask(GDALDatasetH srcDS, OGRGeometryH geom, double *bounds);
80 | void freeImageBuffer(ImageBuffer *buffer);
81 | #ifdef __cplusplus
82 | }
83 | #endif
84 |
85 | #endif // OSGEO_UTILS_H
86 |
--------------------------------------------------------------------------------
/PrintSummary.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2025 [GrainArc]
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published
6 | by the Free Software Foundation, either version 3 of the License, or
7 | (at your option) any later version.
8 |
9 | This program is distributed in the hope that it will be useful,
10 | but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | GNU Affero General Public License for more details.
13 |
14 | You should have received a copy of the GNU Affero General Public License
15 | along with this program. If not, see .
16 | */
17 | package Gogeo
18 |
19 | /*
20 | #include "osgeo_utils.h"
21 | */
22 | import "C"
23 | import (
24 | "fmt"
25 | "unsafe"
26 | )
27 |
28 | // 打印相交分析摘要
29 | func (result *GeosAnalysisResult) PrintIntersectionSummary() {
30 | fmt.Printf("\n=== 空间分析结果摘要 ===\n")
31 | fmt.Printf("要素数量: %d\n", result.ResultCount)
32 |
33 | if result.OutputLayer != nil {
34 | fmt.Printf("结果图层字段数量: %d\n", result.OutputLayer.GetFieldCount())
35 | fmt.Printf("结果图层几何类型: %s\n", result.OutputLayer.GetGeometryType())
36 |
37 | // 打印字段信息
38 | result.printFieldInfo()
39 |
40 | // 打印前5条要素的详细信息
41 | result.printSampleFeatures(5)
42 | }
43 | fmt.Printf("========================\n\n")
44 | }
45 |
46 | // printFieldInfo 打印字段信息
47 | func (result *GeosAnalysisResult) printFieldInfo() {
48 | if result.OutputLayer == nil {
49 | return
50 | }
51 |
52 | layerDefn := result.OutputLayer.GetLayerDefn()
53 | fieldCount := int(C.OGR_FD_GetFieldCount(layerDefn))
54 |
55 | fmt.Printf("\n--- 字段信息 ---\n")
56 | for i := 0; i < fieldCount; i++ {
57 | fieldDefn := C.OGR_FD_GetFieldDefn(layerDefn, C.int(i))
58 | fieldName := C.GoString(C.OGR_Fld_GetNameRef(fieldDefn))
59 | fieldType := C.OGR_Fld_GetType(fieldDefn)
60 | fieldWidth := int(C.OGR_Fld_GetWidth(fieldDefn))
61 | fieldPrecision := int(C.OGR_Fld_GetPrecision(fieldDefn))
62 |
63 | typeStr := getFieldTypeString(fieldType)
64 | if fieldWidth > 0 {
65 | if fieldPrecision > 0 {
66 | fmt.Printf(" [%d] %s (%s, %d.%d)\n", i, fieldName, typeStr, fieldWidth, fieldPrecision)
67 | } else {
68 | fmt.Printf(" [%d] %s (%s, %d)\n", i, fieldName, typeStr, fieldWidth)
69 | }
70 | } else {
71 | fmt.Printf(" [%d] %s (%s)\n", i, fieldName, typeStr)
72 | }
73 | }
74 | }
75 |
76 | // printSampleFeatures 打印前N条要素的详细信息
77 | func (result *GeosAnalysisResult) printSampleFeatures(maxCount int) {
78 | if result.OutputLayer == nil || result.ResultCount == 0 {
79 | fmt.Printf("\n--- 样本要素 ---\n")
80 | fmt.Printf("无要素数据\n")
81 | return
82 | }
83 |
84 | // 限制显示数量
85 | displayCount := maxCount
86 | if result.ResultCount < maxCount {
87 | displayCount = result.ResultCount
88 | }
89 |
90 | fmt.Printf("\n--- 前 %d 条要素详情 ---\n", displayCount)
91 |
92 | // 重置读取位置
93 | result.OutputLayer.ResetReading()
94 |
95 | layerDefn := result.OutputLayer.GetLayerDefn()
96 | fieldCount := int(C.OGR_FD_GetFieldCount(layerDefn))
97 |
98 | count := 0
99 | result.OutputLayer.IterateFeatures(func(feature C.OGRFeatureH) {
100 | if count >= displayCount {
101 | return
102 | }
103 |
104 | fmt.Printf("\n要素 #%d:\n", count+1)
105 |
106 | // 打印几何信息
107 | result.printFeatureGeometry(feature)
108 |
109 | // 打印属性信息
110 | result.printFeatureAttributes(feature, layerDefn, fieldCount)
111 |
112 | count++
113 | })
114 | }
115 |
116 | // printFeatureGeometry 打印要素几何信息
117 | func (result *GeosAnalysisResult) printFeatureGeometry(feature C.OGRFeatureH) {
118 | geom := C.OGR_F_GetGeometryRef(feature)
119 | if geom == nil {
120 | fmt.Printf(" 几何: <空>\n")
121 | return
122 | }
123 |
124 | // 获取几何类型
125 | geomType := C.OGR_G_GetGeometryType(geom)
126 | geomTypeName := C.GoString(C.OGR_G_GetGeometryName(geom))
127 |
128 | // 获取WKT
129 | var wktPtr *C.char
130 | err := C.OGR_G_ExportToWkt(geom, &wktPtr)
131 | if err != C.OGRERR_NONE {
132 | fmt.Printf(" 几何: %s \n", geomTypeName)
133 | return
134 | }
135 | defer C.CPLFree(unsafe.Pointer(wktPtr))
136 |
137 | wkt := C.GoString(wktPtr)
138 |
139 | // 如果WKT太长,进行截断显示
140 | const maxWKTLength = 200
141 | if len(wkt) > maxWKTLength {
142 | wkt = wkt[:maxWKTLength] + "..."
143 | }
144 |
145 | fmt.Printf(" 几何类型: %s\n", geomTypeName)
146 | fmt.Printf(" WKT: %s\n", wkt)
147 |
148 | // 打印几何统计信息
149 | result.printGeometryStats(geom, geomType)
150 | }
151 |
152 | // printGeometryStats 打印几何统计信息
153 | func (result *GeosAnalysisResult) printGeometryStats(geom C.OGRGeometryH, geomType C.OGRwkbGeometryType) {
154 | // 获取包络
155 | var envelope C.OGREnvelope
156 | C.OGR_G_GetEnvelope(geom, &envelope)
157 |
158 | fmt.Printf(" 包络: (%.6f, %.6f) - (%.6f, %.6f)\n",
159 | float64(envelope.MinX), float64(envelope.MinY),
160 | float64(envelope.MaxX), float64(envelope.MaxY))
161 |
162 | // 根据几何类型显示不同的统计信息
163 | switch geomType {
164 | case C.wkbPoint, C.wkbMultiPoint:
165 | // 点几何:显示坐标
166 | if geomType == C.wkbPoint {
167 | x := C.OGR_G_GetX(geom, 0)
168 | y := C.OGR_G_GetY(geom, 0)
169 | fmt.Printf(" 坐标: (%.6f, %.6f)\n", float64(x), float64(y))
170 | } else {
171 | pointCount := int(C.OGR_G_GetGeometryCount(geom))
172 | fmt.Printf(" 点数量: %d\n", pointCount)
173 | }
174 |
175 | case C.wkbLineString, C.wkbMultiLineString:
176 | // 线几何:显示长度
177 | length := C.OGR_G_Length(geom)
178 | fmt.Printf(" 长度: %.6f\n", float64(length))
179 |
180 | case C.wkbPolygon, C.wkbMultiPolygon:
181 | // 面几何:显示面积
182 | area := C.OGR_G_Area(geom)
183 | fmt.Printf(" 面积: %.6f\n", float64(area))
184 | }
185 | }
186 |
187 | // printFeatureAttributes 打印要素属性信息
188 | func (result *GeosAnalysisResult) printFeatureAttributes(feature C.OGRFeatureH, layerDefn C.OGRFeatureDefnH, fieldCount int) {
189 | fmt.Printf(" 属性:\n")
190 |
191 | hasAttributes := false
192 | for i := 0; i < fieldCount; i++ {
193 | fieldDefn := C.OGR_FD_GetFieldDefn(layerDefn, C.int(i))
194 | fieldName := C.GoString(C.OGR_Fld_GetNameRef(fieldDefn))
195 |
196 | // 检查字段是否已设置
197 | if C.OGR_F_IsFieldSet(feature, C.int(i)) == 0 {
198 | continue
199 | }
200 |
201 | hasAttributes = true
202 | fieldType := C.OGR_Fld_GetType(fieldDefn)
203 |
204 | // 根据字段类型获取并格式化值
205 | var valueStr string
206 | switch fieldType {
207 | case C.OFTInteger:
208 | value := C.OGR_F_GetFieldAsInteger(feature, C.int(i))
209 | valueStr = fmt.Sprintf("%d", int(value))
210 |
211 | case C.OFTReal:
212 | value := C.OGR_F_GetFieldAsDouble(feature, C.int(i))
213 | valueStr = fmt.Sprintf("%.6f", float64(value))
214 |
215 | case C.OFTString:
216 | value := C.OGR_F_GetFieldAsString(feature, C.int(i))
217 | valueStr = C.GoString(value)
218 | // 如果字符串太长,进行截断
219 | if len(valueStr) > 100 {
220 | valueStr = valueStr[:100] + "..."
221 | }
222 |
223 | case C.OFTDate:
224 | year := C.OGR_F_GetFieldAsInteger(feature, C.int(i))
225 | month := C.OGR_F_GetFieldAsInteger(feature, C.int(i+1))
226 | day := C.OGR_F_GetFieldAsInteger(feature, C.int(i+2))
227 | valueStr = fmt.Sprintf("%04d-%02d-%02d", int(year), int(month), int(day))
228 |
229 | case C.OFTDateTime:
230 | value := C.OGR_F_GetFieldAsString(feature, C.int(i))
231 | valueStr = C.GoString(value)
232 |
233 | default:
234 | // 其他类型转为字符串
235 | value := C.OGR_F_GetFieldAsString(feature, C.int(i))
236 | valueStr = C.GoString(value)
237 | }
238 |
239 | fmt.Printf(" %s: %s\n", fieldName, valueStr)
240 | }
241 |
242 | if !hasAttributes {
243 | fmt.Printf(" <无属性数据>\n")
244 | }
245 | }
246 |
247 | // getFieldTypeString 获取字段类型的字符串表示
248 | func getFieldTypeString(fieldType C.OGRFieldType) string {
249 | switch fieldType {
250 | case C.OFTInteger:
251 | return "Integer"
252 | case C.OFTReal:
253 | return "Real"
254 | case C.OFTString:
255 | return "String"
256 | case C.OFTDate:
257 | return "Date"
258 | case C.OFTTime:
259 | return "Time"
260 | case C.OFTDateTime:
261 | return "DateTime"
262 | case C.OFTBinary:
263 | return "Binary"
264 | case C.OFTIntegerList:
265 | return "IntegerList"
266 | case C.OFTRealList:
267 | return "RealList"
268 | case C.OFTStringList:
269 | return "StringList"
270 | default:
271 | return fmt.Sprintf("Unknown(%d)", int(fieldType))
272 | }
273 | }
274 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
5 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
6 | github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
7 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
8 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
9 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
10 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
11 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
12 | github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
13 | github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
14 | github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
15 | github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
16 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
17 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
18 | github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
19 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
20 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
21 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
22 | github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs=
23 | github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
24 | github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
25 | github.com/paulmach/orb v0.11.1 h1:3koVegMC4X/WeiXYz9iswopaTwMem53NzTJuTF20JzU=
26 | github.com/paulmach/orb v0.11.1/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/EnuLaLU=
27 | github.com/paulmach/protoscan v0.2.1/go.mod h1:SpcSwydNLrxUGSDvXvO0P7g7AuhJ7lcKfDlhJCDw2gY=
28 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
29 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
30 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
31 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
32 | github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
33 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
34 | github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
35 | github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
36 | github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
37 | github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g=
38 | github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8=
39 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
40 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
41 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
42 | go.mongodb.org/mongo-driver v1.11.4 h1:4ayjakA013OdpGyL2K3ZqylTac/rMjrJOMZ1EHizXas=
43 | go.mongodb.org/mongo-driver v1.11.4/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g=
44 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
45 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
46 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
47 | golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
48 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
49 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
50 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
51 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
52 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
53 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
54 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
55 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
56 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
57 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
58 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
59 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
60 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
61 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
62 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
63 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
64 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
65 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
66 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
67 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
68 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
69 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
70 | golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
71 | golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
72 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
73 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
74 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
75 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
76 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
77 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
78 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
79 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
80 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
81 | google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
82 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
83 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
84 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
85 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
86 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
87 | gorm.io/gorm v1.30.5 h1:dvEfYwxL+i+xgCNSGGBT1lDjCzfELK8fHZxL3Ee9X0s=
88 | gorm.io/gorm v1.30.5/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
89 |
--------------------------------------------------------------------------------
/RaterReader.go:
--------------------------------------------------------------------------------
1 | package Gogeo
2 |
3 | /*
4 | #include "osgeo_utils.h"
5 |
6 |
7 | */
8 | import "C"
9 |
10 | import (
11 | "bytes"
12 | "fmt"
13 | "image"
14 | "image/png"
15 | "math"
16 | "runtime"
17 | "unsafe"
18 | )
19 |
20 | // RasterDataset 栅格数据集
21 | type RasterDataset struct {
22 | dataset C.GDALDatasetH
23 | warpedDS C.GDALDatasetH
24 | width int
25 | height int
26 | bandCount int
27 | bounds [4]float64 // minX, minY, maxX, maxY (Web Mercator)
28 | projection string
29 | isReprojected bool // 标记是否已重投影
30 | hasGeoInfo bool // 标记是否有地理信息
31 | }
32 |
33 | // DatasetInfo 数据集信息
34 | type DatasetInfo struct {
35 | Width int
36 | Height int
37 | BandCount int
38 | GeoTransform [6]float64
39 | Projection string
40 | HasGeoInfo bool
41 | }
42 |
43 | // imagePath: 影像文件路径
44 | func OpenRasterDataset(imagePath string, reProj bool) (*RasterDataset, error) {
45 | cPath := C.CString(imagePath)
46 | defer C.free(unsafe.Pointer(cPath))
47 | InitializeGDAL()
48 |
49 | // 打开数据集
50 | dataset := C.GDALOpen(cPath, C.GA_ReadOnly)
51 | if dataset == nil {
52 | return nil, fmt.Errorf("failed to open image: %s", imagePath)
53 | }
54 |
55 | var warpedDS C.GDALDatasetH
56 | var activeDS C.GDALDatasetH // 实际使用的数据集
57 |
58 | // 获取基本信息
59 | width := int(C.GDALGetRasterXSize(dataset))
60 | height := int(C.GDALGetRasterYSize(dataset))
61 | bandCount := int(C.GDALGetRasterCount(dataset))
62 |
63 | // 检查是否有地理信息
64 | var geoTransform [6]C.double
65 | hasGeoInfo := C.GDALGetGeoTransform(dataset, &geoTransform[0]) == C.CE_None
66 |
67 | // 获取投影信息
68 | projection := C.GoString(C.GDALGetProjectionRef(dataset))
69 |
70 | // 如果没有地理信息,检查是否有投影信息
71 | if !hasGeoInfo && projection == "" {
72 | hasGeoInfo = false
73 | } else if !hasGeoInfo && projection != "" {
74 | // 有投影但没有地理变换,仍然认为没有完整的地理信息
75 | hasGeoInfo = false
76 | }
77 |
78 | // 根据参数和地理信息决定是否重投影
79 | if reProj && hasGeoInfo {
80 | // 重投影到Web墨卡托
81 | warpedDS = C.reprojectToWebMercator(dataset)
82 | if warpedDS == nil {
83 | C.GDALClose(dataset)
84 | return nil, fmt.Errorf("failed to reproject image to Web Mercator")
85 | }
86 | activeDS = warpedDS
87 |
88 | // 重新获取重投影后的地理变换
89 | if C.GDALGetGeoTransform(activeDS, &geoTransform[0]) != C.CE_None {
90 | C.GDALClose(warpedDS)
91 | C.GDALClose(dataset)
92 | return nil, fmt.Errorf("failed to get geotransform from reprojected dataset")
93 | }
94 | } else {
95 | // 不重投影,直接使用原始数据集
96 | activeDS = dataset
97 | warpedDS = nil
98 |
99 | // 如果没有地理信息,创建默认的地理变换
100 | if !hasGeoInfo {
101 | // 创建像素坐标系的地理变换 (0,0) 到 (width, height)
102 | geoTransform[0] = 0.0 // 左上角X坐标
103 | geoTransform[1] = 1.0 // X方向像素分辨率
104 | geoTransform[2] = 0.0 // 旋转参数
105 | geoTransform[3] = 0.0 // 左上角Y坐标
106 | geoTransform[4] = 0.0 // 旋转参数
107 | geoTransform[5] = 1.0 //
108 | }
109 | }
110 |
111 | // 计算边界
112 | minX := float64(geoTransform[0])
113 | maxY := float64(geoTransform[3])
114 | maxX := minX + float64(width)*float64(geoTransform[1])
115 | minY := maxY + float64(height)*float64(geoTransform[5])
116 |
117 | // 如果没有地理信息,更新投影信息
118 | if !hasGeoInfo {
119 | projection = "PIXEL" // 标记为像素坐标系
120 | } else if reProj {
121 | // 获取重投影后的投影信息
122 | projection = C.GoString(C.GDALGetProjectionRef(activeDS))
123 | }
124 |
125 | rd := &RasterDataset{
126 | dataset: dataset,
127 | warpedDS: warpedDS,
128 | width: width,
129 | height: height,
130 | bandCount: bandCount,
131 | bounds: [4]float64{minX, minY, maxX, maxY},
132 | projection: projection,
133 | isReprojected: reProj && hasGeoInfo,
134 | hasGeoInfo: hasGeoInfo,
135 | }
136 |
137 | runtime.SetFinalizer(rd, (*RasterDataset).Close)
138 |
139 | return rd, nil
140 | }
141 |
142 | // Close 关闭数据集
143 | func (rd *RasterDataset) Close() {
144 | if rd.warpedDS != nil {
145 | C.GDALClose(rd.warpedDS)
146 | rd.warpedDS = nil
147 | }
148 | if rd.dataset != nil {
149 | C.GDALClose(rd.dataset)
150 | rd.dataset = nil
151 | }
152 | }
153 |
154 | // GetInfo 获取数据集信息
155 | func (rd *RasterDataset) GetInfo() DatasetInfo {
156 | var cInfo C.DatasetInfo
157 | C.getDatasetInfo(rd.warpedDS, &cInfo)
158 |
159 | info := DatasetInfo{
160 | Width: int(cInfo.width),
161 | Height: int(cInfo.height),
162 | BandCount: int(cInfo.bandCount),
163 | }
164 |
165 | for i := 0; i < 6; i++ {
166 | info.GeoTransform[i] = float64(cInfo.geoTransform[i])
167 | }
168 |
169 | info.Projection = C.GoString(&cInfo.projection[0])
170 |
171 | return info
172 | }
173 |
174 | // GetBounds 获取边界(Web墨卡托坐标)
175 | func (rd *RasterDataset) GetBounds() (minX, minY, maxX, maxY float64) {
176 | return rd.bounds[0], rd.bounds[1], rd.bounds[2], rd.bounds[3]
177 | }
178 |
179 | // GetBoundsLatLon 获取边界(经纬度)
180 | func (rd *RasterDataset) GetBoundsLatLon() (minLon, minLat, maxLon, maxLat float64) {
181 | minX, minY, maxX, maxY := rd.GetBounds()
182 |
183 | minLon, minLat = WebMercatorToLatLon(minX, minY)
184 | maxLon, maxLat = WebMercatorToLatLon(maxX, maxY)
185 |
186 | return
187 | }
188 |
189 | // GetTileRange 获取指定缩放级别的瓦片范围(符合Mapbox规范)
190 | func (rd *RasterDataset) GetTileRange(zoom int) (minTileX, minTileY, maxTileX, maxTileY int) {
191 | minX, minY, maxX, maxY := rd.GetBounds()
192 |
193 | const (
194 | EarthRadius = 6378137.0
195 | OriginShift = math.Pi * EarthRadius // 20037508.342789244
196 | )
197 |
198 | // 🔥 修正:计算该缩放级别的瓦片总数
199 | numTiles := math.Exp2(float64(zoom))
200 |
201 | // 🔥 修正:计算单个瓦片的世界尺寸(米)
202 | tileWorldSize := (2 * OriginShift) / numTiles
203 |
204 | // 计算瓦片行列号(XYZ方案)
205 | minTileX = int(math.Floor((minX + OriginShift) / tileWorldSize))
206 | maxTileX = int(math.Floor((maxX + OriginShift) / tileWorldSize))
207 |
208 | // Y坐标:XYZ方案,Y轴向下
209 | minTileY = int(math.Floor((OriginShift - maxY) / tileWorldSize))
210 | maxTileY = int(math.Floor((OriginShift - minY) / tileWorldSize))
211 |
212 | // 边界检查
213 | maxTiles := int(numTiles) - 1
214 | if minTileX < 0 {
215 | minTileX = 0
216 | }
217 | if minTileY < 0 {
218 | minTileY = 0
219 | }
220 | if maxTileX > maxTiles {
221 | maxTileX = maxTiles
222 | }
223 | if maxTileY > maxTiles {
224 | maxTileY = maxTiles
225 | }
226 |
227 | return
228 | }
229 |
230 | // ReadTile 读取瓦片数据(黑色背景转透明)
231 | func (rd *RasterDataset) ReadTile(zoom, x, y, tileSize int) ([]byte, error) {
232 | var minX, minY, maxX, maxY C.double
233 |
234 | C.getTileBounds(C.int(x), C.int(y), C.int(zoom), &minX, &minY, &maxX, &maxY)
235 |
236 | // 分配缓冲区(最多4个波段)
237 | bufferSize := tileSize * tileSize * 4
238 | buffer := make([]byte, bufferSize)
239 |
240 | bands := int(C.readTileData(
241 | rd.warpedDS,
242 | minX, minY, maxX, maxY,
243 | C.int(tileSize),
244 | (*C.uchar)(unsafe.Pointer(&buffer[0])),
245 | ))
246 |
247 | if bands == 0 {
248 | return nil, fmt.Errorf("failed to read tile data")
249 | }
250 |
251 | // 创建 RGBA 图像(始终包含 Alpha 通道)
252 | rgbaImg := image.NewRGBA(image.Rect(0, 0, tileSize, tileSize))
253 |
254 | if bands == 3 {
255 | // RGB -> RGBA(黑色转透明)
256 | for i := 0; i < tileSize*tileSize; i++ {
257 | r := buffer[i]
258 | g := buffer[i+tileSize*tileSize]
259 | b := buffer[i+2*tileSize*tileSize]
260 |
261 | rgbaImg.Pix[i*4] = r
262 | rgbaImg.Pix[i*4+1] = g
263 | rgbaImg.Pix[i*4+2] = b
264 |
265 | // 黑色背景转透明(可以设置阈值,比如 r+g+b < 10)
266 | if r == 0 && g == 0 && b == 0 {
267 | rgbaImg.Pix[i*4+3] = 0 // 完全透明
268 | } else {
269 | rgbaImg.Pix[i*4+3] = 255 // 完全不透明
270 | }
271 | }
272 | } else if bands == 4 {
273 | // RGBA(直接使用)
274 | for i := 0; i < tileSize*tileSize; i++ {
275 | r := buffer[i]
276 | g := buffer[i+tileSize*tileSize]
277 | b := buffer[i+2*tileSize*tileSize]
278 | a := buffer[i+3*tileSize*tileSize]
279 |
280 | rgbaImg.Pix[i*4] = r
281 | rgbaImg.Pix[i*4+1] = g
282 | rgbaImg.Pix[i*4+2] = b
283 |
284 | // 如果是黑色,强制设为透明
285 | if r == 0 && g == 0 && b == 0 {
286 | rgbaImg.Pix[i*4+3] = 0
287 | } else {
288 | rgbaImg.Pix[i*4+3] = a
289 | }
290 | }
291 | } else {
292 | return nil, fmt.Errorf("unsupported band count: %d", bands)
293 | }
294 |
295 | // 编码为 PNG(PNG 支持透明度)
296 | var buf bytes.Buffer
297 | if err := png.Encode(&buf, rgbaImg); err != nil {
298 | return nil, err
299 | }
300 |
301 | return buf.Bytes(), nil
302 | }
303 |
304 | // LatLonToWebMercator 经纬度转Web墨卡托(符合Mapbox规范)
305 | func LatLonToWebMercator(lon, lat float64) (x, y float64) {
306 | const (
307 | EarthRadius = 6378137.0
308 | OriginShift = math.Pi * EarthRadius
309 | )
310 |
311 | x = lon * OriginShift / 180.0
312 | y = math.Log(math.Tan((90+lat)*math.Pi/360.0)) * OriginShift / math.Pi
313 | return
314 | }
315 |
316 | // WebMercatorToLatLon Web墨卡托转经纬度(符合Mapbox规范)
317 | func WebMercatorToLatLon(x, y float64) (lon, lat float64) {
318 | const (
319 | EarthRadius = 6378137.0
320 | OriginShift = math.Pi * EarthRadius
321 | )
322 |
323 | lon = x * 180.0 / OriginShift
324 | lat = math.Atan(math.Exp(y*math.Pi/OriginShift))*360.0/math.Pi - 90.0
325 | return
326 | }
327 |
328 | // LonLatToTile 经纬度转瓦片坐标(符合Mapbox规范)
329 | func LonLatToTile(lon, lat float64, zoom int) (x, y int) {
330 | const (
331 | EarthRadius = 6378137.0
332 | OriginShift = math.Pi * EarthRadius
333 | )
334 |
335 | // 转换为Web墨卡托
336 | mercX := lon * OriginShift / 180.0
337 | mercY := math.Log(math.Tan((90+lat)*math.Pi/360.0)) * OriginShift / math.Pi
338 |
339 | // **关键修复:使用整数位运算**
340 | numTiles := int64(1 << uint(zoom))
341 | tileSize := (2.0 * OriginShift) / float64(numTiles)
342 |
343 | x = int(math.Floor((mercX + OriginShift) / tileSize))
344 | y = int(math.Floor((OriginShift - mercY) / tileSize))
345 |
346 | // 边界检查
347 | maxTile := int(numTiles) - 1
348 | if x < 0 {
349 | x = 0
350 | } else if x > maxTile {
351 | x = maxTile
352 | }
353 | if y < 0 {
354 | y = 0
355 | } else if y > maxTile {
356 | y = maxTile
357 | }
358 |
359 | return
360 | }
361 |
362 | // TileToWebMercatorBounds 瓦片坐标转Web墨卡托边界
363 | func TileToWebMercatorBounds(x, y, zoom int) (minX, minY, maxX, maxY float64) {
364 | const (
365 | EarthRadius = 6378137.0
366 | OriginShift = math.Pi * EarthRadius
367 | )
368 |
369 | // **关键修复:使用整数位运算**
370 | numTiles := int64(1 << uint(zoom))
371 | tileSize := (2.0 * OriginShift) / float64(numTiles)
372 |
373 | minX = float64(x)*tileSize - OriginShift
374 | maxX = float64(x+1)*tileSize - OriginShift
375 | maxY = OriginShift - float64(y)*tileSize
376 | minY = OriginShift - float64(y+1)*tileSize
377 |
378 | return
379 | }
380 |
--------------------------------------------------------------------------------
/GeojsonEdit.go:
--------------------------------------------------------------------------------
1 | package Gogeo
2 |
3 | /*
4 | #include "osgeo_utils.h"
5 | */
6 | import "C"
7 | import (
8 | "fmt"
9 | "github.com/paulmach/orb"
10 | "github.com/paulmach/orb/geojson"
11 | )
12 |
13 | // RemoveLinePolygonBoundaryOverlapFromGeoJSON 使用geojson feature处理线与面的边界重叠
14 | // lineFeature: 输入的线geojson要素
15 | // polygonFeature: 输入的面geojson要素
16 | // 返回:处理后最长线段的geojson要素
17 | func RemoveLinePolygonBoundaryOverlapFromGeoJSON(lineFeature, polygonFeature *geojson.Feature, tolerance float64) (*geojson.Feature, error) {
18 | if lineFeature == nil || polygonFeature == nil {
19 | return nil, fmt.Errorf("输入的geojson要素为空")
20 | }
21 |
22 | if lineFeature.Geometry == nil || polygonFeature.Geometry == nil {
23 | return nil, fmt.Errorf("geojson要素的几何体为空")
24 | }
25 |
26 | // 将geojson几何体转换为GDAL几何体
27 | lineGeom, err := orbGeometryToOGRGeometry(lineFeature.Geometry)
28 | if err != nil {
29 | return nil, fmt.Errorf("转换线geojson几何体失败: %v", err)
30 | }
31 | if lineGeom == nil {
32 | return nil, fmt.Errorf("线geojson几何体转换结果为空")
33 | }
34 | defer C.OGR_G_DestroyGeometry(lineGeom)
35 |
36 | polygonGeom, err := orbGeometryToOGRGeometry(polygonFeature.Geometry)
37 | if err != nil {
38 | return nil, fmt.Errorf("转换面geojson几何体失败: %v", err)
39 | }
40 | if polygonGeom == nil {
41 | return nil, fmt.Errorf("面geojson几何体转换结果为空")
42 | }
43 | defer C.OGR_G_DestroyGeometry(polygonGeom)
44 |
45 | // 处理重叠部分
46 | resultGeom := RemoveLinePolygonBoundaryOverlapGeometryAndReturnLongest(lineGeom, polygonGeom, tolerance)
47 | if resultGeom == nil {
48 | return nil, fmt.Errorf("处理结果为空")
49 | }
50 | defer C.OGR_G_DestroyGeometry(resultGeom)
51 |
52 | // 将GDAL几何体转换回geojson几何体
53 | resultOrbGeom, err := ogrGeometryToOrbGeometry(resultGeom)
54 | if err != nil {
55 | return nil, fmt.Errorf("转换结果几何体失败: %v", err)
56 | }
57 |
58 | // 创建结果feature,保留原始属性和ID
59 | resultFeature := &geojson.Feature{
60 | ID: lineFeature.ID,
61 | Type: "Feature",
62 | Geometry: resultOrbGeom,
63 | Properties: lineFeature.Properties,
64 | }
65 |
66 | // 如果有长度属性,添加处理后的长度
67 | if resultFeature.Properties == nil {
68 | resultFeature.Properties = make(geojson.Properties)
69 | }
70 |
71 | length := float64(C.OGR_G_Length(resultGeom))
72 | resultFeature.Properties["length"] = length
73 | resultFeature.Properties["processed_at"] = "RemoveLinePolygonBoundaryOverlap"
74 |
75 | return resultFeature, nil
76 | }
77 |
78 | // orbGeometryToOGRGeometry 将orb.Geometry转换为GDAL OGRGeometry
79 | func orbGeometryToOGRGeometry(geometry orb.Geometry) (C.OGRGeometryH, error) {
80 | if geometry == nil {
81 | return nil, fmt.Errorf("geometry为空")
82 | }
83 |
84 | switch geom := geometry.(type) {
85 | case orb.Point:
86 | return createOGRPoint(geom), nil
87 |
88 | case orb.LineString:
89 | return createOGRLineString(geom), nil
90 |
91 | case orb.Ring:
92 | return createOGRLinearRing(geom), nil
93 |
94 | case orb.Polygon:
95 | return createOGRPolygon(geom), nil
96 |
97 | case orb.MultiPoint:
98 | return createOGRMultiPoint(geom), nil
99 |
100 | case orb.MultiLineString:
101 | return createOGRMultiLineString(geom), nil
102 |
103 | case orb.MultiPolygon:
104 | return createOGRMultiPolygon(geom), nil
105 |
106 | case orb.Collection:
107 | return createOGRGeometryCollection(geom), nil
108 |
109 | default:
110 | return nil, fmt.Errorf("不支持的几何类型: %T", geometry)
111 | }
112 | }
113 |
114 | // ogrGeometryToOrbGeometry 将GDAL OGRGeometry转换为orb.Geometry
115 | func ogrGeometryToOrbGeometry(geometry C.OGRGeometryH) (orb.Geometry, error) {
116 | if geometry == nil {
117 | return nil, fmt.Errorf("OGRGeometry为空")
118 | }
119 |
120 | geomType := C.OGR_G_GetGeometryType(geometry)
121 |
122 | switch geomType {
123 | case C.wkbPoint:
124 | return ogrPointToOrbPoint(geometry), nil
125 |
126 | case C.wkbLineString, C.wkbLineString25D:
127 | return ogrLineStringToOrbLineString(geometry), nil
128 |
129 | case C.wkbPolygon, C.wkbPolygon25D:
130 | return ogrPolygonToOrbPolygon(geometry), nil
131 |
132 | case C.wkbMultiPoint:
133 | return ogrMultiPointToOrbMultiPoint(geometry), nil
134 |
135 | case C.wkbMultiLineString, C.wkbMultiLineString25D:
136 | return ogrMultiLineStringToOrbMultiLineString(geometry), nil
137 |
138 | case C.wkbMultiPolygon, C.wkbMultiPolygon25D:
139 | return ogrMultiPolygonToOrbMultiPolygon(geometry), nil
140 |
141 | case C.wkbGeometryCollection:
142 | return ogrGeometryCollectionToOrbCollection(geometry), nil
143 |
144 | default:
145 | return nil, fmt.Errorf("不支持的OGR几何类型: %v", geomType)
146 | }
147 | }
148 |
149 | // ============================================================================
150 | // 辅助函数:orb -> OGR
151 | // ============================================================================
152 |
153 | func createOGRPoint(p orb.Point) C.OGRGeometryH {
154 | geom := C.OGR_G_CreateGeometry(C.wkbPoint)
155 | if geom == nil {
156 | return nil
157 | }
158 | C.OGR_G_SetPoint_2D(geom, 0, C.double(p.X()), C.double(p.Y()))
159 | return geom
160 | }
161 |
162 | func createOGRLineString(line orb.LineString) C.OGRGeometryH {
163 | geom := C.OGR_G_CreateGeometry(C.wkbLineString)
164 | if geom == nil {
165 | return nil
166 | }
167 |
168 | for i, p := range line {
169 | C.OGR_G_SetPoint_2D(geom, C.int(i), C.double(p.X()), C.double(p.Y()))
170 | }
171 |
172 | return geom
173 | }
174 |
175 | func createOGRLinearRing(ring orb.Ring) C.OGRGeometryH {
176 | geom := C.OGR_G_CreateGeometry(C.wkbLinearRing)
177 | if geom == nil {
178 | return nil
179 | }
180 |
181 | for i, p := range ring {
182 | C.OGR_G_SetPoint_2D(geom, C.int(i), C.double(p.X()), C.double(p.Y()))
183 | }
184 |
185 | return geom
186 | }
187 |
188 | func createOGRPolygon(poly orb.Polygon) C.OGRGeometryH {
189 | geom := C.OGR_G_CreateGeometry(C.wkbPolygon)
190 | if geom == nil {
191 | return nil
192 | }
193 |
194 | for _, ring := range poly {
195 | ringGeom := createOGRLinearRing(ring)
196 | if ringGeom != nil {
197 | C.OGR_G_AddGeometryDirectly(geom, ringGeom)
198 | }
199 | }
200 |
201 | return geom
202 | }
203 |
204 | func createOGRMultiPoint(mp orb.MultiPoint) C.OGRGeometryH {
205 | geom := C.OGR_G_CreateGeometry(C.wkbMultiPoint)
206 | if geom == nil {
207 | return nil
208 | }
209 |
210 | for _, p := range mp {
211 | pointGeom := createOGRPoint(p)
212 | if pointGeom != nil {
213 | C.OGR_G_AddGeometryDirectly(geom, pointGeom)
214 | }
215 | }
216 |
217 | return geom
218 | }
219 |
220 | func createOGRMultiLineString(mls orb.MultiLineString) C.OGRGeometryH {
221 | geom := C.OGR_G_CreateGeometry(C.wkbMultiLineString)
222 | if geom == nil {
223 | return nil
224 | }
225 |
226 | for _, line := range mls {
227 | lineGeom := createOGRLineString(line)
228 | if lineGeom != nil {
229 | C.OGR_G_AddGeometryDirectly(geom, lineGeom)
230 | }
231 | }
232 |
233 | return geom
234 | }
235 |
236 | func createOGRMultiPolygon(mp orb.MultiPolygon) C.OGRGeometryH {
237 | geom := C.OGR_G_CreateGeometry(C.wkbMultiPolygon)
238 | if geom == nil {
239 | return nil
240 | }
241 |
242 | for _, poly := range mp {
243 | polyGeom := createOGRPolygon(poly)
244 | if polyGeom != nil {
245 | C.OGR_G_AddGeometryDirectly(geom, polyGeom)
246 | }
247 | }
248 |
249 | return geom
250 | }
251 |
252 | func createOGRGeometryCollection(collection orb.Collection) C.OGRGeometryH {
253 | geom := C.OGR_G_CreateGeometry(C.wkbGeometryCollection)
254 | if geom == nil {
255 | return nil
256 | }
257 |
258 | for _, g := range collection {
259 | subGeom, err := orbGeometryToOGRGeometry(g)
260 | if err == nil && subGeom != nil {
261 | C.OGR_G_AddGeometryDirectly(geom, subGeom)
262 | }
263 | }
264 |
265 | return geom
266 | }
267 |
268 | // ============================================================================
269 | // 辅助函数:OGR -> orb
270 | // ============================================================================
271 |
272 | func ogrPointToOrbPoint(geometry C.OGRGeometryH) orb.Point {
273 | x := float64(C.OGR_G_GetX(geometry, 0))
274 | y := float64(C.OGR_G_GetY(geometry, 0))
275 | return orb.Point{x, y}
276 | }
277 |
278 | func ogrLineStringToOrbLineString(geometry C.OGRGeometryH) orb.LineString {
279 | pointCount := int(C.OGR_G_GetPointCount(geometry))
280 | line := make(orb.LineString, pointCount)
281 |
282 | for i := 0; i < pointCount; i++ {
283 | x := float64(C.OGR_G_GetX(geometry, C.int(i)))
284 | y := float64(C.OGR_G_GetY(geometry, C.int(i)))
285 | line[i] = orb.Point{x, y}
286 | }
287 |
288 | return line
289 | }
290 |
291 | func ogrLinearRingToOrbRing(geometry C.OGRGeometryH) orb.Ring {
292 | pointCount := int(C.OGR_G_GetPointCount(geometry))
293 | ring := make(orb.Ring, pointCount)
294 |
295 | for i := 0; i < pointCount; i++ {
296 | x := float64(C.OGR_G_GetX(geometry, C.int(i)))
297 | y := float64(C.OGR_G_GetY(geometry, C.int(i)))
298 | ring[i] = orb.Point{x, y}
299 | }
300 |
301 | return ring
302 | }
303 |
304 | func ogrPolygonToOrbPolygon(geometry C.OGRGeometryH) orb.Polygon {
305 | ringCount := int(C.OGR_G_GetGeometryCount(geometry))
306 | poly := make(orb.Polygon, ringCount)
307 |
308 | for i := 0; i < ringCount; i++ {
309 | ringGeom := C.OGR_G_GetGeometryRef(geometry, C.int(i))
310 | if ringGeom != nil {
311 | poly[i] = ogrLinearRingToOrbRing(ringGeom)
312 | }
313 | }
314 |
315 | return poly
316 | }
317 |
318 | func ogrMultiPointToOrbMultiPoint(geometry C.OGRGeometryH) orb.MultiPoint {
319 | pointCount := int(C.OGR_G_GetGeometryCount(geometry))
320 | mp := make(orb.MultiPoint, pointCount)
321 |
322 | for i := 0; i < pointCount; i++ {
323 | pointGeom := C.OGR_G_GetGeometryRef(geometry, C.int(i))
324 | if pointGeom != nil {
325 | mp[i] = ogrPointToOrbPoint(pointGeom)
326 | }
327 | }
328 |
329 | return mp
330 | }
331 |
332 | func ogrMultiLineStringToOrbMultiLineString(geometry C.OGRGeometryH) orb.MultiLineString {
333 | lineCount := int(C.OGR_G_GetGeometryCount(geometry))
334 | mls := make(orb.MultiLineString, lineCount)
335 |
336 | for i := 0; i < lineCount; i++ {
337 | lineGeom := C.OGR_G_GetGeometryRef(geometry, C.int(i))
338 | if lineGeom != nil {
339 | mls[i] = ogrLineStringToOrbLineString(lineGeom)
340 | }
341 | }
342 |
343 | return mls
344 | }
345 |
346 | func ogrMultiPolygonToOrbMultiPolygon(geometry C.OGRGeometryH) orb.MultiPolygon {
347 | polyCount := int(C.OGR_G_GetGeometryCount(geometry))
348 | mp := make(orb.MultiPolygon, polyCount)
349 |
350 | for i := 0; i < polyCount; i++ {
351 | polyGeom := C.OGR_G_GetGeometryRef(geometry, C.int(i))
352 | if polyGeom != nil {
353 | mp[i] = ogrPolygonToOrbPolygon(polyGeom)
354 | }
355 | }
356 |
357 | return mp
358 | }
359 |
360 | func ogrGeometryCollectionToOrbCollection(geometry C.OGRGeometryH) orb.Collection {
361 | geomCount := int(C.OGR_G_GetGeometryCount(geometry))
362 | collection := make(orb.Collection, 0, geomCount)
363 |
364 | for i := 0; i < geomCount; i++ {
365 | subGeom := C.OGR_G_GetGeometryRef(geometry, C.int(i))
366 | if subGeom != nil {
367 | if orbGeom, err := ogrGeometryToOrbGeometry(subGeom); err == nil {
368 | collection = append(collection, orbGeom)
369 | }
370 | }
371 | }
372 |
373 | return collection
374 | }
375 |
--------------------------------------------------------------------------------
/cgo_header.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2025 [GrainArc]
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published
6 | by the Free Software Foundation, either version 3 of the License, or
7 | (at your option) any later version.
8 |
9 | This program is distributed in the hope that it will be useful,
10 | but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | GNU Affero General Public License for more details.
13 |
14 | You should have received a copy of the GNU Affero General Public License
15 | along with this program. If not, see .
16 | */
17 |
18 | package Gogeo
19 |
20 | /*
21 | #cgo windows CFLAGS: -IC:/OSGeo4W/include -IC:/OSGeo4W/include/gdal -Wno-unused-result
22 | #cgo windows LDFLAGS: -LC:/OSGeo4W/lib -lgdal_i -lstdc++ -static-libgcc -static-libstdc++
23 | #cgo linux CFLAGS: -I/usr/include/gdal -Wno-unused-result
24 | #cgo linux LDFLAGS: -L/usr/lib -lgdal -lstdc++
25 | #cgo android CFLAGS: -I/data/data/com.termux/files/usr/include -Wno-unused-result
26 | #cgo android LDFLAGS: -L/data/data/com.termux/files/usr/lib -lgdal
27 | #include "osgeo_utils.h"
28 |
29 | // 初始化GDAL并修复PROJ配置问题
30 | void initializeGDALWithProjFix(const char* projDataPath, const char* shapeEncoding) {
31 | // 动态设置PROJ数据路径,支持自定义路径
32 | if (projDataPath && strlen(projDataPath) > 0) {
33 | CPLSetConfigOption("PROJ_DATA", projDataPath); // 设置PROJ数据目录
34 | CPLSetConfigOption("PROJ_LIB", projDataPath); // 设置PROJ库路径(兼容性)
35 | }
36 |
37 | // 强制使用传统的GIS轴顺序(经度,纬度),避免坐标轴混乱
38 | CPLSetConfigOption("OSR_DEFAULT_AXIS_MAPPING_STRATEGY", "TRADITIONAL_GIS_ORDER");
39 |
40 |
41 | // 动态设置Shapefile编码,支持不同字符集
42 | if (shapeEncoding && strlen(shapeEncoding) > 0) {
43 | CPLSetConfigOption("SHAPE_ENCODING", shapeEncoding); // 设置Shapefile文件编码
44 | }
45 | CPLSetErrorHandler(CPLQuietErrorHandler);
46 | // 注册所有GDAL驱动程序,启用栅格数据支持
47 | GDALAllRegister();
48 | // 注册所有OGR驱动程序,启用矢量数据支持
49 | OGRRegisterAll();
50 |
51 | }
52 |
53 | // 创建EPSG:4490坐标系并设置正确的轴顺序
54 | OGRSpatialReferenceH createEPSG4490WithCorrectAxis() {
55 | // 创建新的空间参考系统对象
56 | OGRSpatialReferenceH hSRS = OSRNewSpatialReference(NULL);
57 | if (!hSRS) {
58 | return NULL; // 内存分配失败时返回NULL
59 | }
60 |
61 | // 导入EPSG:4490坐标系定义(中国大地坐标系2000)
62 | if (OSRImportFromEPSG(hSRS, 4490) != OGRERR_NONE) {
63 | OSRDestroySpatialReference(hSRS); // 失败时清理资源
64 | return NULL; // 返回NULL表示创建失败
65 | }
66 |
67 | // 设置传统的GIS轴顺序(经度在前,纬度在后)
68 | OSRSetAxisMappingStrategy(hSRS, OAMS_TRADITIONAL_GIS_ORDER);
69 |
70 | return hSRS; // 返回成功创建的空间参考系统
71 | }
72 |
73 | // 从十六进制WKB字符串创建几何对象
74 | OGRGeometryH createGeometryFromWKBHex(const char* wkbHex) {
75 | // 验证输入参数有效性
76 | if (!wkbHex || strlen(wkbHex) == 0) {
77 | return NULL; // 空输入时返回NULL
78 | }
79 |
80 | int hexLen = strlen(wkbHex); // 获取十六进制字符串长度
81 | // 验证十六进制字符串长度必须为偶数
82 | if (hexLen % 2 != 0) {
83 | return NULL; // 奇数长度无效
84 | }
85 |
86 | int wkbLen = hexLen / 2; // 计算WKB二进制数据长度
87 | // 分配内存存储WKB二进制数据
88 | unsigned char* wkbData = (unsigned char*)malloc(wkbLen);
89 | if (!wkbData) {
90 | return NULL; // 内存分配失败
91 | }
92 |
93 | // 将十六进制字符串转换为字节数组
94 | for (int i = 0; i < wkbLen; i++) {
95 | char hex[3] = {wkbHex[i*2], wkbHex[i*2+1], '\0'}; // 提取两个十六进制字符
96 | wkbData[i] = (unsigned char)strtol(hex, NULL, 16); // 转换为字节值
97 | }
98 |
99 | OGRGeometryH hGeometry = NULL; // 初始化几何对象指针
100 | OGRErr err; // 错误代码变量
101 |
102 | // 检查是否是EWKB格式(至少需要9字节:1字节序+4字节类型+4字节SRID)
103 | if (wkbLen >= 9) {
104 | uint32_t geomType; // 几何类型变量
105 | // 根据字节序读取几何类型
106 | if (wkbData[0] == 1) { // 小端序(LSB)
107 | geomType = wkbData[1] | (wkbData[2] << 8) | (wkbData[3] << 16) | (wkbData[4] << 24);
108 | } else { // 大端序(MSB)
109 | geomType = (wkbData[1] << 24) | (wkbData[2] << 16) | (wkbData[3] << 8) | wkbData[4];
110 | }
111 |
112 | // 检查是否包含SRID信息(EWKB格式标志位)
113 | if (geomType & 0x20000000) { // 包含SRID的EWKB格式
114 | // 创建不包含SRID的标准WKB数据
115 | unsigned char* standardWkb = (unsigned char*)malloc(wkbLen - 4);
116 | if (standardWkb) {
117 | // 复制字节序标识
118 | standardWkb[0] = wkbData[0];
119 |
120 | // 复制几何类型(移除SRID标志位)
121 | uint32_t cleanGeomType = geomType & ~0x20000000;
122 | if (wkbData[0] == 1) { // 小端序写入
123 | standardWkb[1] = cleanGeomType & 0xFF;
124 | standardWkb[2] = (cleanGeomType >> 8) & 0xFF;
125 | standardWkb[3] = (cleanGeomType >> 16) & 0xFF;
126 | standardWkb[4] = (cleanGeomType >> 24) & 0xFF;
127 | } else { // 大端序写入
128 | standardWkb[1] = (cleanGeomType >> 24) & 0xFF;
129 | standardWkb[2] = (cleanGeomType >> 16) & 0xFF;
130 | standardWkb[3] = (cleanGeomType >> 8) & 0xFF;
131 | standardWkb[4] = cleanGeomType & 0xFF;
132 | }
133 |
134 | // 跳过SRID(4字节),复制剩余几何数据
135 | memcpy(standardWkb + 5, wkbData + 9, wkbLen - 9);
136 |
137 | // 从标准WKB创建几何对象
138 | err = OGR_G_CreateFromWkb(standardWkb, NULL, &hGeometry, wkbLen - 4);
139 | free(standardWkb); // 释放临时缓冲区
140 | }
141 | } else {
142 | // 标准WKB格式,直接解析
143 | err = OGR_G_CreateFromWkb(wkbData, NULL, &hGeometry, wkbLen);
144 | }
145 | } else {
146 | // 数据长度不足,尝试直接解析(可能是简单几何)
147 | err = OGR_G_CreateFromWkb(wkbData, NULL, &hGeometry, wkbLen);
148 | }
149 |
150 | free(wkbData); // 释放WKB数据缓冲区
151 |
152 | // 检查几何对象创建是否成功
153 | if (err != OGRERR_NONE) {
154 | if (hGeometry) {
155 | OGR_G_DestroyGeometry(hGeometry); // 失败时清理已分配的几何对象
156 | }
157 | return NULL; // 返回NULL表示创建失败
158 | }
159 |
160 | return hGeometry; // 返回成功创建的几何对象
161 | }
162 |
163 | // 从WKB数据中提取几何类型信息
164 | uint32_t getGeometryTypeFromWKBData(const unsigned char* wkbData, int wkbLen) {
165 | // 验证输入参数(至少需要5字节:1字节序+4字节类型)
166 | if (!wkbData || wkbLen < 5) {
167 | return 0; // 无效输入返回0
168 | }
169 |
170 | // 读取字节序标识(第一个字节)
171 | unsigned char byteOrder = wkbData[0];
172 |
173 | // 根据字节序读取几何类型(接下来4个字节)
174 | uint32_t geomType;
175 | if (byteOrder == 1) { // 小端序(LSB first)
176 | geomType = wkbData[1] | (wkbData[2] << 8) | (wkbData[3] << 16) | (wkbData[4] << 24);
177 | } else { // 大端序(MSB first)
178 | geomType = (wkbData[1] << 24) | (wkbData[2] << 16) | (wkbData[3] << 8) | wkbData[4];
179 | }
180 |
181 | // 移除SRID标志位,返回纯几何类型
182 | return geomType & ~0x20000000;
183 | }
184 |
185 | // 清理GDAL资源的辅助函数
186 | void cleanupGDAL() {
187 | GDALDestroyDriverManager(); // 清理GDAL驱动管理器
188 | OGRCleanupAll(); // 清理所有OGR资源
189 | }
190 |
191 | void goErrorHandler(CPLErr eErrClass, int err_no, const char *msg) {
192 | // 可以在这里处理GDAL错误
193 | }
194 | */
195 | import "C"
196 |
197 | import (
198 | "errors" // 用于创建错误对象
199 | "log"
200 | "os" // 用于环境变量操作
201 | "runtime" // 用于运行时信息获取
202 | "unsafe" // 用于C指针操作
203 | )
204 |
205 | type GeometryPrecisionConfig struct {
206 | GridSize float64 // 精度网格大小,0表示浮点精度
207 | PreserveTopo bool // 是否保持拓扑结构
208 | KeepCollapsed bool // 是否保留折叠的元素
209 | Enabled bool // 是否启用精度设置
210 | }
211 |
212 | // SpatialReference 表示空间参考系统的Go包装器
213 | type SpatialReference struct {
214 | cPtr C.OGRSpatialReferenceH // C语言空间参考系统指针
215 | }
216 |
217 | // Geometry 表示几何对象的Go包装器
218 | type Geometry struct {
219 | cPtr C.OGRGeometryH // C语言几何对象指针
220 | }
221 |
222 | // InitializeGDAL 初始化GDAL库,支持自定义配置
223 |
224 | // shapeEncoding: Shapefile编码,为空时使用默认编码
225 | func InitializeGDAL() error {
226 | // 如果未指定PROJ路径,根据操作系统设置默认路径
227 | projDataPath := ""
228 | if runtime.GOOS == "windows" {
229 | // Windows下优先使用环境变量,否则使用默认路径
230 | if envPath := os.Getenv("PROJ_DATA"); envPath != "" {
231 | projDataPath = envPath
232 | } else {
233 | projDataPath = "C:/OSGeo4W/share/proj" // Windows默认路径
234 | }
235 | } else if runtime.GOOS == "linux" {
236 | // Linux下优先使用环境变量,否则使用系统路径
237 | if envPath := os.Getenv("PROJ_DATA"); envPath != "" {
238 | projDataPath = envPath
239 | } else {
240 | projDataPath = "/usr/share/proj" // Linux默认路径
241 | }
242 | } else if runtime.GOOS == "android" {
243 | if envPath := os.Getenv("PROJ_DATA"); envPath != "" {
244 | projDataPath = envPath
245 | } else {
246 | projDataPath = "/data/data/com.termux/files/usr/share/proj" // Linux默认路径
247 | }
248 | }
249 |
250 | // 如果未指定编码,使用默认编码
251 | shapeEncoding := ""
252 |
253 | // 转换Go字符串为C字符串
254 | cProjPath := C.CString(projDataPath)
255 | cEncoding := C.CString(shapeEncoding)
256 |
257 | // 确保C字符串资源被释放
258 | defer C.free(unsafe.Pointer(cProjPath))
259 | defer C.free(unsafe.Pointer(cEncoding))
260 |
261 | // 调用C函数初始化GDAL
262 | C.initializeGDALWithProjFix(cProjPath, cEncoding)
263 |
264 | return nil // 初始化成功
265 | }
266 | func CreateEPSG4490WithCorrectAxis() (*SpatialReference, error) {
267 | // 调用C函数创建空间参考系统
268 | cPtr := C.createEPSG4490WithCorrectAxis()
269 | if cPtr == nil {
270 | return nil, errors.New("failed to create EPSG:4490 spatial reference system") // 创建失败
271 | }
272 |
273 | // 创建Go包装器对象
274 | srs := &SpatialReference{cPtr: cPtr}
275 |
276 | // 设置终结器,确保C资源被正确释放
277 | runtime.SetFinalizer(srs, (*SpatialReference).destroy)
278 |
279 | return srs, nil // 返回成功创建的对象
280 | }
281 |
282 | // CreateGeometryFromWKBHex 从十六进制WKB字符串创建几何对象
283 | // wkbHex: 十六进制格式的WKB字符串
284 | // 返回几何对象和可能的错误
285 | func CreateGeometryFromWKBHex(wkbHex string) (*Geometry, error) {
286 | // 验证输入参数
287 | if wkbHex == "" {
288 | return nil, errors.New("WKB hex string cannot be empty") // 空字符串错误
289 | }
290 |
291 | // 转换Go字符串为C字符串
292 | cWkbHex := C.CString(wkbHex)
293 | defer C.free(unsafe.Pointer(cWkbHex)) // 确保C字符串被释放
294 |
295 | // 调用C函数创建几何对象
296 | cPtr := C.createGeometryFromWKBHex(cWkbHex)
297 | if cPtr == nil {
298 | return nil, errors.New("failed to create geometry from WKB hex string") // 创建失败
299 | }
300 | // 创建Go包装器对象
301 | geom := &Geometry{cPtr: cPtr}
302 | return geom, nil // 返回成功创建的对象
303 | }
304 |
305 | // GetGeometryTypeFromWKBData 从WKB数据中提取几何类型
306 | // wkbData: WKB二进制数据
307 | // 返回几何类型代码
308 | func GetGeometryTypeFromWKBData(wkbData []byte) uint32 {
309 | // 验证输入数据长度
310 | if len(wkbData) < 5 {
311 | return 0 // 数据长度不足
312 | }
313 |
314 | // 调用C函数提取几何类型
315 | return uint32(C.getGeometryTypeFromWKBData((*C.uchar)(&wkbData[0]), C.int(len(wkbData))))
316 | }
317 |
318 | // CleanupGDAL 清理GDAL资源,程序退出前调用
319 | func CleanupGDAL() {
320 | C.cleanupGDAL() // 调用C函数清理资源
321 | }
322 |
323 | // destroy 销毁空间参考系统的C资源(终结器函数)
324 | func (srs *SpatialReference) destroy() {
325 | if srs.cPtr != nil {
326 | C.OSRDestroySpatialReference(srs.cPtr) // 释放C资源
327 | srs.cPtr = nil // 避免重复释放
328 | }
329 | }
330 |
331 | // destroy 销毁几何对象的C资源(终结器函数)
332 | func (geom *Geometry) destroy() {
333 | if geom.cPtr != nil {
334 | // 添加错误恢复机制
335 | defer func() {
336 | if r := recover(); r != nil {
337 | log.Printf("警告: 销毁几何对象时发生错误: %v", r)
338 | }
339 | geom.cPtr = nil
340 | }()
341 |
342 | C.OGR_G_DestroyGeometry(geom.cPtr)
343 | geom.cPtr = nil
344 | }
345 | }
346 |
347 | // Destroy 手动销毁空间参考系统资源
348 | func (srs *SpatialReference) Destroy() {
349 | runtime.SetFinalizer(srs, nil) // 移除终结器
350 | srs.destroy() // 立即释放资源
351 | }
352 |
353 | // Destroy 手动销毁几何对象资源
354 | func (geom *Geometry) Destroy() {
355 | runtime.SetFinalizer(geom, nil) // 移除终结器
356 | geom.destroy() // 立即释放资源
357 | }
358 |
--------------------------------------------------------------------------------
/Tiler.go:
--------------------------------------------------------------------------------
1 | package Gogeo
2 |
3 | /*
4 | #include "osgeo_utils.h"
5 | */
6 | import "C"
7 |
8 | import (
9 | "fmt"
10 | "math"
11 | "unsafe"
12 | )
13 |
14 | // TileOptions 切片选项
15 | type TileOptions struct {
16 | TileWidth int // 切片宽度(像素)
17 | TileHeight int // 切片高度(像素)
18 | OverlapX int // X方向重叠像素数
19 | OverlapY int // Y方向重叠像素数
20 | NamePrefix string // 切片名称前缀
21 | StartIndex int // 起始索引
22 | BufferDist float64 // 缓冲距离(可选)
23 | }
24 |
25 | // TileInfo 切片信息
26 | type TileInfo2 struct {
27 | Name string // 切片名称
28 | Index int // 切片索引
29 | Row int // 行号
30 | Col int // 列号
31 | Bounds [4]float64 // 切片边界 [minX, minY, maxX, maxY]
32 | PixelBounds [4]int // 像素边界 [minX, minY, maxX, maxY]
33 | Width int // 切片宽度(像素)
34 | Height int // 切片高度(像素)
35 | }
36 |
37 | // RasterTiler 栅格切片器
38 | type RasterTiler struct {
39 | raster *RasterDataset
40 | options *TileOptions
41 | tiles []TileInfo2
42 | numRows int
43 | numCols int
44 | geoLayer *GDALLayer // 生成的矢量图层
45 | }
46 |
47 | // NewRasterTiler 创建栅格切片器
48 | func NewRasterTiler(raster *RasterDataset, options *TileOptions) (*RasterTiler, error) {
49 | if raster == nil {
50 | return nil, fmt.Errorf("raster dataset is nil")
51 | }
52 |
53 | // 设置默认选项
54 | if options == nil {
55 | options = &TileOptions{
56 | TileWidth: 1024,
57 | TileHeight: 1024,
58 | OverlapX: 0,
59 | OverlapY: 0,
60 | NamePrefix: "tile",
61 | StartIndex: 0,
62 | }
63 | }
64 |
65 | if options.TileWidth <= 0 {
66 | options.TileWidth = 1024
67 | }
68 | if options.TileHeight <= 0 {
69 | options.TileHeight = 1024
70 | }
71 | if options.NamePrefix == "" {
72 | options.NamePrefix = "tile"
73 | }
74 |
75 | tiler := &RasterTiler{
76 | raster: raster,
77 | options: options,
78 | }
79 |
80 | return tiler, nil
81 | }
82 |
83 | // GenerateTiles 生成切片信息
84 | func (t *RasterTiler) GenerateTiles() error {
85 | // 计算步长(考虑重叠)
86 | stepX := t.options.TileWidth - t.options.OverlapX
87 | stepY := t.options.TileHeight - t.options.OverlapY
88 |
89 | if stepX <= 0 || stepY <= 0 {
90 | return fmt.Errorf("invalid step size: overlap is too large")
91 | }
92 |
93 | // 计算需要多少行和列
94 | t.numCols = int(math.Ceil(float64(t.raster.width) / float64(stepX)))
95 | t.numRows = int(math.Ceil(float64(t.raster.height) / float64(stepY)))
96 |
97 | // 获取地理变换参数
98 | bounds := t.raster.bounds
99 | geoWidth := bounds[2] - bounds[0] // maxX - minX
100 | geoHeight := bounds[3] - bounds[1] // maxY - minY
101 |
102 | // 计算像素分辨率
103 | pixelResX := geoWidth / float64(t.raster.width)
104 | pixelResY := geoHeight / float64(t.raster.height)
105 |
106 | // 生成切片信息
107 | t.tiles = make([]TileInfo2, 0, t.numRows*t.numCols)
108 | index := t.options.StartIndex
109 |
110 | for row := 0; row < t.numRows; row++ {
111 | for col := 0; col < t.numCols; col++ {
112 | // 计算像素坐标范围
113 | pixelMinX := col * stepX
114 | pixelMinY := row * stepY
115 | pixelMaxX := pixelMinX + t.options.TileWidth
116 | pixelMaxY := pixelMinY + t.options.TileHeight
117 |
118 | // 确保不超出图像边界
119 | if pixelMaxX > t.raster.width {
120 | pixelMaxX = t.raster.width
121 | }
122 | if pixelMaxY > t.raster.height {
123 | pixelMaxY = t.raster.height
124 | }
125 |
126 | // 计算实际切片大小
127 | tileWidth := pixelMaxX - pixelMinX
128 | tileHeight := pixelMaxY - pixelMinY
129 |
130 | // 跳过太小的切片
131 | if tileWidth < t.options.TileWidth/4 || tileHeight < t.options.TileHeight/4 {
132 | continue
133 | }
134 |
135 | // 计算地理坐标范围
136 | var geoMinX, geoMinY, geoMaxX, geoMaxY float64
137 |
138 | if t.raster.hasGeoInfo {
139 | // 有地理信息:使用实际坐标
140 | geoMinX = bounds[0] + float64(pixelMinX)*pixelResX
141 | maxY := bounds[3] - float64(pixelMinY)*math.Abs(pixelResY)
142 | geoMaxX = bounds[0] + float64(pixelMaxX)*pixelResX
143 | geoMinY = bounds[3] - float64(pixelMaxY)*math.Abs(pixelResY)
144 | geoMaxY = maxY
145 |
146 | // 应用缓冲区
147 | if t.options.BufferDist > 0 {
148 | geoMinX -= t.options.BufferDist
149 | geoMinY -= t.options.BufferDist
150 | geoMaxX += t.options.BufferDist
151 | geoMaxY += t.options.BufferDist
152 | }
153 | } else {
154 | // 像素坐标系:直接使用像素坐标
155 | geoMinX = float64(pixelMinX)
156 | geoMinY = float64(pixelMinY)
157 | geoMaxX = float64(pixelMaxX)
158 | geoMaxY = float64(pixelMaxY)
159 |
160 | // 应用缓冲区(像素单位)
161 | if t.options.BufferDist > 0 {
162 | geoMinX -= t.options.BufferDist
163 | geoMinY -= t.options.BufferDist
164 | geoMaxX += t.options.BufferDist
165 | geoMaxY += t.options.BufferDist
166 | }
167 | }
168 |
169 | // 创建切片信息
170 | tile := TileInfo2{
171 | Name: fmt.Sprintf("%s_%d_r%d_c%d", t.options.NamePrefix, index, row, col),
172 | Index: index,
173 | Row: row,
174 | Col: col,
175 | Bounds: [4]float64{geoMinX, geoMinY, geoMaxX, geoMaxY},
176 | PixelBounds: [4]int{pixelMinX, pixelMinY, pixelMaxX, pixelMaxY},
177 | Width: tileWidth,
178 | Height: tileHeight,
179 | }
180 |
181 | t.tiles = append(t.tiles, tile)
182 | index++
183 | }
184 | }
185 |
186 | return nil
187 | }
188 |
189 | // GetTiles 获取切片信息列表
190 | func (t *RasterTiler) GetTiles() []TileInfo2 {
191 | return t.tiles
192 | }
193 |
194 | // GetTileCount 获取切片数量
195 | func (t *RasterTiler) GetTileCount() int {
196 | return len(t.tiles)
197 | }
198 |
199 | // CreateTileLayer 创建切片矢量图层(内存图层)
200 | func (t *RasterTiler) CreateTileLayer() (*GDALLayer, error) {
201 | if len(t.tiles) == 0 {
202 | return nil, fmt.Errorf("no tiles generated, call GenerateTiles() first")
203 | }
204 |
205 | // 创建内存驱动
206 | cDriverName := C.CString("Memory")
207 | defer C.free(unsafe.Pointer(cDriverName))
208 |
209 | driver := C.OGRGetDriverByName(cDriverName)
210 | if driver == nil {
211 | return nil, fmt.Errorf("failed to get Memory driver")
212 | }
213 |
214 | // 创建内存数据源
215 | cDatasetName := C.CString("")
216 | defer C.free(unsafe.Pointer(cDatasetName))
217 |
218 | dataset := C.OGR_Dr_CreateDataSource(driver, cDatasetName, nil)
219 | if dataset == nil {
220 | return nil, fmt.Errorf("failed to create memory data source")
221 | }
222 |
223 | // 确定空间参考
224 | var srs C.OGRSpatialReferenceH
225 | if t.raster.hasGeoInfo && t.raster.projection != "" && t.raster.projection != "PIXEL" {
226 | cProjection := C.CString(t.raster.projection)
227 | defer C.free(unsafe.Pointer(cProjection))
228 |
229 | srs = C.OSRNewSpatialReference(cProjection)
230 | defer C.OSRDestroySpatialReference(srs)
231 | }
232 |
233 | // 创建图层
234 | cLayerName := C.CString("tiles")
235 | defer C.free(unsafe.Pointer(cLayerName))
236 |
237 | layer := C.OGR_DS_CreateLayer(dataset, cLayerName, srs, C.wkbPolygon, nil)
238 | if layer == nil {
239 | C.OGR_DS_Destroy(dataset)
240 | return nil, fmt.Errorf("failed to create layer")
241 | }
242 |
243 | // 添加属性字段
244 | cNameFieldName := C.CString("NAME")
245 | defer C.free(unsafe.Pointer(cNameFieldName))
246 | nameField := C.OGR_Fld_Create(cNameFieldName, C.OFTString)
247 | C.OGR_Fld_SetWidth(nameField, 255)
248 | C.OGR_L_CreateField(layer, nameField, 1)
249 | C.OGR_Fld_Destroy(nameField)
250 |
251 | cIndexFieldName := C.CString("INDEX")
252 | defer C.free(unsafe.Pointer(cIndexFieldName))
253 | indexField := C.OGR_Fld_Create(cIndexFieldName, C.OFTInteger)
254 | C.OGR_L_CreateField(layer, indexField, 1)
255 | C.OGR_Fld_Destroy(indexField)
256 |
257 | cRowFieldName := C.CString("ROW")
258 | defer C.free(unsafe.Pointer(cRowFieldName))
259 | rowField := C.OGR_Fld_Create(cRowFieldName, C.OFTInteger)
260 | C.OGR_L_CreateField(layer, rowField, 1)
261 | C.OGR_Fld_Destroy(rowField)
262 |
263 | cColFieldName := C.CString("COL")
264 | defer C.free(unsafe.Pointer(cColFieldName))
265 | colField := C.OGR_Fld_Create(cColFieldName, C.OFTInteger)
266 | C.OGR_L_CreateField(layer, colField, 1)
267 | C.OGR_Fld_Destroy(colField)
268 |
269 | cWidthFieldName := C.CString("WIDTH")
270 | defer C.free(unsafe.Pointer(cWidthFieldName))
271 | widthField := C.OGR_Fld_Create(cWidthFieldName, C.OFTInteger)
272 | C.OGR_L_CreateField(layer, widthField, 1)
273 | C.OGR_Fld_Destroy(widthField)
274 |
275 | cHeightFieldName := C.CString("HEIGHT")
276 | defer C.free(unsafe.Pointer(cHeightFieldName))
277 | heightField := C.OGR_Fld_Create(cHeightFieldName, C.OFTInteger)
278 | C.OGR_L_CreateField(layer, heightField, 1)
279 | C.OGR_Fld_Destroy(heightField)
280 |
281 | // 添加要素
282 | for _, tile := range t.tiles {
283 | // 创建矩形几何体
284 | ring := C.OGR_G_CreateGeometry(C.wkbLinearRing)
285 | C.OGR_G_AddPoint_2D(ring, C.double(tile.Bounds[0]), C.double(tile.Bounds[1]))
286 | C.OGR_G_AddPoint_2D(ring, C.double(tile.Bounds[2]), C.double(tile.Bounds[1]))
287 | C.OGR_G_AddPoint_2D(ring, C.double(tile.Bounds[2]), C.double(tile.Bounds[3]))
288 | C.OGR_G_AddPoint_2D(ring, C.double(tile.Bounds[0]), C.double(tile.Bounds[3]))
289 | C.OGR_G_AddPoint_2D(ring, C.double(tile.Bounds[0]), C.double(tile.Bounds[1]))
290 |
291 | polygon := C.OGR_G_CreateGeometry(C.wkbPolygon)
292 | C.OGR_G_AddGeometryDirectly(polygon, ring)
293 |
294 | // 创建要素
295 | featureDefn := C.OGR_L_GetLayerDefn(layer)
296 | feature := C.OGR_F_Create(featureDefn)
297 |
298 | // 设置几何体
299 | C.OGR_F_SetGeometry(feature, polygon)
300 |
301 | // 设置属性
302 | cName := C.CString(tile.Name)
303 | C.OGR_F_SetFieldString(feature, 0, cName) // NAME
304 | C.free(unsafe.Pointer(cName))
305 |
306 | C.OGR_F_SetFieldInteger(feature, 1, C.int(tile.Index)) // INDEX
307 | C.OGR_F_SetFieldInteger(feature, 2, C.int(tile.Row)) // ROW
308 | C.OGR_F_SetFieldInteger(feature, 3, C.int(tile.Col)) // COL
309 | C.OGR_F_SetFieldInteger(feature, 4, C.int(tile.Width)) // WIDTH
310 | C.OGR_F_SetFieldInteger(feature, 5, C.int(tile.Height)) // HEIGHT
311 |
312 | // 添加要素到图层
313 | C.OGR_L_CreateFeature(layer, feature)
314 |
315 | // 清理
316 | C.OGR_F_Destroy(feature)
317 | C.OGR_G_DestroyGeometry(polygon)
318 | }
319 |
320 | // 创建 GDALLayer 对象(修正字段名)
321 | gdalLayer := &GDALLayer{
322 | layer: layer,
323 | dataset: dataset, // 修正:使用 dataset 而不是 dataSource
324 | driver: driver, // 添加:driver 字段
325 | }
326 |
327 | t.geoLayer = gdalLayer
328 | return gdalLayer, nil
329 | }
330 |
331 | // GetTileLayer 获取已创建的切片图层
332 | func (t *RasterTiler) GetTileLayer() *GDALLayer {
333 | return t.geoLayer
334 | }
335 |
336 | // ClipByTiles 使用切片图层裁剪栅格并返回二进制数据
337 | func (t *RasterTiler) ClipByTiles(clipOptions *ClipOptions) ([]ClipResultByte, error) {
338 | if t.geoLayer == nil {
339 | return nil, fmt.Errorf("tile layer not created, call CreateTileLayer() first")
340 | }
341 |
342 | // 设置裁剪选项
343 | if clipOptions == nil {
344 | clipOptions = &ClipOptions{}
345 | }
346 |
347 | // 确保使用 NAME 字段
348 | clipOptions.NameField = "NAME"
349 |
350 | // 设置固定的输出尺寸
351 | clipOptions.TileSize = t.options.TileWidth
352 |
353 | // 根据是否有地理信息选择裁剪方法
354 | if t.raster.hasGeoInfo {
355 | return t.raster.ClipRasterByLayerByte(t.geoLayer, clipOptions)
356 | } else {
357 | return t.raster.ClipPixelRasterByLayerByte(t.geoLayer, clipOptions)
358 | }
359 | }
360 |
361 | // Close 关闭切片器并释放资源
362 | func (t *RasterTiler) Close() {
363 | if t.geoLayer != nil {
364 | t.geoLayer.Close()
365 | t.geoLayer = nil
366 | }
367 | }
368 |
369 | // GetGridInfo 获取网格信息
370 | func (t *RasterTiler) GetGridInfo() (rows, cols int) {
371 | return t.numRows, t.numCols
372 | }
373 |
374 | // GetTileByRowCol 根据行列号获取切片信息
375 | func (t *RasterTiler) GetTileByRowCol(row, col int) (*TileInfo2, error) {
376 | for i := range t.tiles {
377 | if t.tiles[i].Row == row && t.tiles[i].Col == col {
378 | return &t.tiles[i], nil
379 | }
380 | }
381 | return nil, fmt.Errorf("tile not found at row=%d, col=%d", row, col)
382 | }
383 |
384 | // GetTileByIndex 根据索引获取切片信息
385 | func (t *RasterTiler) GetTileByIndex(index int) (*TileInfo2, error) {
386 | for i := range t.tiles {
387 | if t.tiles[i].Index == index {
388 | return &t.tiles[i], nil
389 | }
390 | }
391 | return nil, fmt.Errorf("tile not found with index=%d", index)
392 | }
393 |
--------------------------------------------------------------------------------
/test_gogeo.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2024 [Your Name]
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published
6 | by the Free Software Foundation, either version 3 of the License, or
7 | (at your option) any later version.
8 |
9 | This program is distributed in the hope that it will be useful,
10 | but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | GNU Affero General Public License for more details.
13 |
14 | You should have received a copy of the GNU Affero General Public License
15 | along with this program. If not, see .
16 | */
17 |
18 | package Gogeo
19 | /*
20 | #include "osgeo_utils.h"
21 |
22 | */
23 | import "C"
24 | import (
25 | "fmt"
26 | "path/filepath"
27 | "io/ioutil"
28 | "os"
29 | "unsafe"
30 | "strings"
31 | "time"
32 | )
33 |
34 | func PerformSpatialIntersectionTest(shpFile1, shpFile2, outputFile string) error {
35 | startTime := time.Now()
36 |
37 | // 1. 读取第一个shapefile
38 | fmt.Println("正在读取第一个shapefile...")
39 | reader1, err := NewFileGeoReader(shpFile1)
40 | if err != nil {
41 | return fmt.Errorf("创建第一个文件读取器失败: %v", err)
42 | }
43 |
44 | layer1, err := reader1.ReadShapeFile()
45 | if err != nil {
46 | return fmt.Errorf("读取第一个shapefile失败: %v", err)
47 | }
48 |
49 | // 打印第一个图层信息
50 | fmt.Println("第一个图层信息:")
51 | layer1.PrintLayerInfo()
52 |
53 | // 2. 读取第二个shapefile
54 | fmt.Println("\n正在读取第二个shapefile...")
55 | reader2, err := NewFileGeoReader(shpFile2)
56 | if err != nil {
57 | layer1.Close()
58 | return fmt.Errorf("创建第二个文件读取器失败: %v", err)
59 | }
60 |
61 | layer2, err := reader2.ReadShapeFile()
62 | if err != nil {
63 | layer1.Close()
64 | return fmt.Errorf("读取第二个shapefile失败: %v", err)
65 | }
66 |
67 | // 打印第二个图层信息
68 | fmt.Println("第二个图层信息:")
69 | layer2.PrintLayerInfo()
70 |
71 | // 3. 配置并行相交分析参数
72 | config := &ParallelGeosConfig{
73 | TileCount: 50, // 4x4分块
74 | MaxWorkers: 32, // 使用所有CPU核心
75 | BufferDistance: 0.0001, // 分块缓冲距离
76 | IsMergeTile: true, // 合并分块结果
77 | ProgressCallback: progressCallback, // 进度回调函数
78 | PrecisionConfig: &GeometryPrecisionConfig{
79 | Enabled: true,
80 | GridSize: 0.000000001, // 几何精度网格大小
81 | PreserveTopo: true, // 保持拓扑
82 | KeepCollapsed: false, // 不保留退化几何
83 | },
84 | }
85 |
86 |
87 | // 5. 执行空间相交分析
88 | result, err := SpatialIdentityAnalysis(layer1, layer2, config)
89 | if err != nil {
90 | return fmt.Errorf("空间分析执行失败: %v", err)
91 | }
92 |
93 | analysisTime := time.Since(startTime)
94 | fmt.Printf("\n分析完成! 耗时: %v\n", analysisTime)
95 | fmt.Printf("结果要素数量: %d\n", result.ResultCount)
96 |
97 | // 6. 将结果写出为shapefile
98 | fmt.Println("正在写出结果到shapefile...")
99 | writeStartTime := time.Now()
100 |
101 | // 获取输出文件的图层名称(不含扩展名)
102 | layerName := getFileNameWithoutExt(outputFile)
103 |
104 | err = WriteShapeFileLayer(result.OutputLayer, outputFile, layerName, true)
105 | if err != nil {
106 | result.OutputLayer.Close()
107 | return fmt.Errorf("写出shapefile失败: %v", err)
108 | }
109 |
110 | writeTime := time.Since(writeStartTime)
111 | totalTime := time.Since(startTime)
112 |
113 | fmt.Printf("结果写出完成! 耗时: %v\n", writeTime)
114 | fmt.Printf("总耗时: %v\n", totalTime)
115 | fmt.Printf("输出文件: %s\n", outputFile)
116 |
117 | // 7. 验证输出文件
118 | err = verifyOutputFile(outputFile)
119 | if err != nil {
120 | fmt.Printf("警告: 输出文件验证失败: %v\n", err)
121 | } else {
122 | fmt.Println("输出文件验证成功!")
123 | }
124 |
125 | // 清理资源
126 | result.OutputLayer.Close()
127 |
128 | return nil
129 | }
130 |
131 | // progressCallback 进度回调函数
132 | func progressCallback(complete float64, message string) bool {
133 | // 显示进度信息
134 | fmt.Printf("\r进度: %.1f%% - %s", complete*100, message)
135 |
136 | // 如果进度完成,换行
137 | if complete >= 1.0 {
138 | fmt.Println()
139 | }
140 |
141 | // 返回true继续执行,返回false取消执行
142 | return true
143 | }
144 |
145 | // getFileNameWithoutExt 获取不含扩展名的文件名
146 | func getFileNameWithoutExt(filePath string) string {
147 | fileName := filepath.Base(filePath)
148 | return fileName[:len(fileName)-len(filepath.Ext(fileName))]
149 | }
150 |
151 | // verifyOutputFile 验证输出文件
152 | func verifyOutputFile(filePath string) error {
153 | // 读取输出文件验证
154 | reader, err := NewFileGeoReader(filePath)
155 | if err != nil {
156 | return fmt.Errorf("无法读取输出文件: %v", err)
157 | }
158 |
159 | layer, err := reader.ReadShapeFile()
160 | if err != nil {
161 | return fmt.Errorf("无法读取输出图层: %v", err)
162 | }
163 | defer layer.Close()
164 |
165 | // 打印输出图层信息
166 | fmt.Println("\n输出图层信息:")
167 | layer.PrintLayerInfo()
168 |
169 | // 检查要素数量
170 | featureCount := layer.GetFeatureCount()
171 | if featureCount == 0 {
172 | return fmt.Errorf("输出文件中没有要素")
173 | }
174 |
175 | fmt.Printf("验证通过: 输出文件包含 %d 个要素\n", featureCount)
176 | return nil
177 | }
178 |
179 | // ReadBinFilesAndConvertToGDB 读取文件夹内所有bin文件并转换为GDB
180 | func ReadBinFilesAndConvertToGDB(folderPath string, outputGDBPath string) error {
181 | // 检查文件夹是否存在
182 | if _, err := os.Stat(folderPath); os.IsNotExist(err) {
183 | return fmt.Errorf("文件夹不存在: %s", folderPath)
184 | }
185 |
186 | // 读取文件夹内容
187 | files, err := ioutil.ReadDir(folderPath)
188 | if err != nil {
189 | return fmt.Errorf("无法读取文件夹 %s: %v", folderPath, err)
190 | }
191 |
192 | // 过滤出所有.bin文件
193 | var binFiles []string
194 | for _, file := range files {
195 | if !file.IsDir() && strings.ToLower(filepath.Ext(file.Name())) == ".bin" {
196 | binFiles = append(binFiles, filepath.Join(folderPath, file.Name()))
197 | }
198 | }
199 |
200 | if len(binFiles) == 0 {
201 | return fmt.Errorf("文件夹 %s 中没有找到.bin文件", folderPath)
202 | }
203 |
204 | fmt.Printf("找到 %d 个.bin文件\n", len(binFiles))
205 |
206 | // 创建GDB写入器
207 | NewFileGeoWriter(outputGDBPath, true) // true表示覆盖已存在的文件
208 | if err != nil {
209 | return fmt.Errorf("无法创建GDB写入器: %v", err)
210 | }
211 |
212 | // 初始化GDAL
213 | InitializeGDAL()
214 |
215 | // 获取FileGDB驱动
216 | driver := C.OGRGetDriverByName(C.CString("FileGDB"))
217 | if driver == nil {
218 | // 如果FileGDB驱动不可用,尝试OpenFileGDB驱动
219 | driver = C.OGRGetDriverByName(C.CString("OpenFileGDB"))
220 | if driver == nil {
221 | return fmt.Errorf("无法获取GDB驱动(需要FileGDB或OpenFileGDB驱动)")
222 | }
223 | }
224 |
225 | // 如果GDB已存在且需要覆盖,先删除
226 | if _, err := os.Stat(outputGDBPath); err == nil {
227 | os.RemoveAll(outputGDBPath)
228 | }
229 |
230 | // 创建GDB数据源
231 | cGDBPath := C.CString(outputGDBPath)
232 | defer C.free(unsafe.Pointer(cGDBPath))
233 |
234 | dataset := C.OGR_Dr_CreateDataSource(driver, cGDBPath, nil)
235 | if dataset == nil {
236 | return fmt.Errorf("无法创建GDB文件: %s", outputGDBPath)
237 | }
238 | defer C.OGR_DS_Destroy(dataset)
239 |
240 | // 处理每个bin文件
241 | successCount := 0
242 | errorCount := 0
243 |
244 | for i, binFile := range binFiles {
245 | fmt.Printf("处理文件 %d/%d: %s\n", i+1, len(binFiles), filepath.Base(binFile))
246 |
247 | // 反序列化图层
248 | layer, err := DeserializeLayerFromFile(binFile)
249 | if err != nil {
250 | fmt.Printf(" 错误: 无法反序列化文件 %s: %v\n", binFile, err)
251 | errorCount++
252 | continue
253 | }
254 |
255 | // 生成图层名称(使用文件名,去掉扩展名)
256 | layerName := strings.TrimSuffix(filepath.Base(binFile), ".bin")
257 | layerName = sanitizeLayerName(layerName)
258 |
259 | // 将图层写入GDB
260 | err = writeLayerToDataset(layer, dataset, layerName)
261 | if err != nil {
262 | fmt.Printf(" 错误: 无法写入图层 %s: %v\n", layerName, err)
263 | errorCount++
264 | } else {
265 | fmt.Printf(" 成功: 图层 %s 已写入GDB\n", layerName)
266 | successCount++
267 | }
268 |
269 | // 关闭图层以释放资源
270 | layer.Close()
271 | }
272 |
273 | fmt.Printf("\n转换完成:\n")
274 | fmt.Printf(" 成功: %d 个图层\n", successCount)
275 | fmt.Printf(" 失败: %d 个图层\n", errorCount)
276 | fmt.Printf(" 输出GDB: %s\n", outputGDBPath)
277 |
278 | if successCount == 0 {
279 | return fmt.Errorf("没有成功转换任何图层")
280 | }
281 |
282 | return nil
283 | }
284 |
285 | // writeLayerToDataset 将GDALLayer写入到指定的数据集
286 | func writeLayerToDataset(sourceLayer *GDALLayer, dataset C.OGRDataSourceH, layerName string) error {
287 | // 获取源图层信息
288 | sourceDefn := sourceLayer.GetLayerDefn()
289 | geomType := C.OGR_FD_GetGeomType(sourceDefn)
290 | srs := sourceLayer.GetSpatialRef()
291 |
292 | // 创建图层
293 | cLayerName := C.CString(layerName)
294 | defer C.free(unsafe.Pointer(cLayerName))
295 |
296 | newLayer := C.OGR_DS_CreateLayer(dataset, cLayerName, srs, geomType, nil)
297 | if newLayer == nil {
298 | return fmt.Errorf("无法创建图层: %s", layerName)
299 | }
300 |
301 | // 创建临时写入器用于复制字段和要素
302 | tempWriter := &FileGeoWriter{
303 | FileType: "gdb",
304 | }
305 |
306 | // 复制字段定义
307 | err := tempWriter.copyFieldDefinitions(sourceDefn, newLayer)
308 | if err != nil {
309 | return fmt.Errorf("复制字段定义失败: %v", err)
310 | }
311 |
312 | // 复制要素
313 | err = tempWriter.copyFeatures(sourceLayer, newLayer)
314 | if err != nil {
315 | return fmt.Errorf("复制要素失败: %v", err)
316 | }
317 |
318 | return nil
319 | }
320 |
321 | // sanitizeLayerName 清理图层名称以符合GDB要求
322 | func sanitizeLayerName(name string) string {
323 | // 移除特殊字符,替换为下划线
324 | sanitized := strings.ReplaceAll(name, " ", "_")
325 | sanitized = strings.ReplaceAll(sanitized, "-", "_")
326 | sanitized = strings.ReplaceAll(sanitized, ".", "_")
327 | sanitized = strings.ReplaceAll(sanitized, "(", "_")
328 | sanitized = strings.ReplaceAll(sanitized, ")", "_")
329 |
330 | // 确保图层名不以数字开头
331 | if len(sanitized) > 0 && sanitized[0] >= '0' && sanitized[0] <= '9' {
332 | sanitized = "layer_" + sanitized
333 | }
334 |
335 | // 确保图层名不为空
336 | if len(sanitized) == 0 {
337 | sanitized = "unknown_layer"
338 | }
339 |
340 | return sanitized
341 | }
342 |
343 | // ProcessBinFolderToGDB 处理bin文件夹并转换为GDB的便捷函数
344 | func ProcessBinFolderToGDB(binFolderPath string, outputGDBPath string) error {
345 | return ReadBinFilesAndConvertToGDB(binFolderPath, outputGDBPath)
346 | }
347 |
348 | // BatchConvertBinToGDB 批量转换bin文件到GDB(支持子文件夹)
349 | func BatchConvertBinToGDB(rootFolderPath string, outputGDBPath string, includeSubfolders bool) error {
350 | var allBinFiles []string
351 |
352 | // 遍历文件夹
353 | err := filepath.Walk(rootFolderPath, func(path string, info os.FileInfo, err error) error {
354 | if err != nil {
355 | return err
356 | }
357 |
358 | // 如果不包含子文件夹,跳过子目录
359 | if !includeSubfolders && info.IsDir() && path != rootFolderPath {
360 | return filepath.SkipDir
361 | }
362 |
363 | // 检查是否为bin文件
364 | if !info.IsDir() && strings.ToLower(filepath.Ext(info.Name())) == ".bin" {
365 | allBinFiles = append(allBinFiles, path)
366 | }
367 |
368 | return nil
369 | })
370 |
371 | if err != nil {
372 | return fmt.Errorf("遍历文件夹失败: %v", err)
373 | }
374 |
375 | if len(allBinFiles) == 0 {
376 | return fmt.Errorf("没有找到.bin文件")
377 | }
378 |
379 | fmt.Printf("找到 %d 个.bin文件\n", len(allBinFiles))
380 |
381 | // 创建GDB写入器
382 | NewFileGeoWriter(outputGDBPath, true)
383 | if err != nil {
384 | return fmt.Errorf("无法创建GDB写入器: %v", err)
385 | }
386 |
387 | // 初始化GDAL
388 | InitializeGDAL()
389 |
390 | // 获取FileGDB驱动
391 | driver := C.OGRGetDriverByName(C.CString("FileGDB"))
392 | if driver == nil {
393 | driver = C.OGRGetDriverByName(C.CString("OpenFileGDB"))
394 | if driver == nil {
395 | return fmt.Errorf("无法获取GDB驱动")
396 | }
397 | }
398 |
399 | // 删除已存在的GDB
400 | if _, err := os.Stat(outputGDBPath); err == nil {
401 | os.RemoveAll(outputGDBPath)
402 | }
403 |
404 | // 创建GDB数据源
405 | cGDBPath := C.CString(outputGDBPath)
406 | defer C.free(unsafe.Pointer(cGDBPath))
407 |
408 | dataset := C.OGR_Dr_CreateDataSource(driver, cGDBPath, nil)
409 | if dataset == nil {
410 | return fmt.Errorf("无法创建GDB文件: %s", outputGDBPath)
411 | }
412 | defer C.OGR_DS_Destroy(dataset)
413 |
414 | // 处理每个bin文件
415 | successCount := 0
416 | errorCount := 0
417 |
418 | for i, binFile := range allBinFiles {
419 | fmt.Printf("处理文件 %d/%d: %s\n", i+1, len(allBinFiles), binFile)
420 |
421 | // 反序列化图层
422 | layer, err := DeserializeLayerFromFile(binFile)
423 | if err != nil {
424 | fmt.Printf(" 错误: %v\n", err)
425 | errorCount++
426 | continue
427 | }
428 |
429 | // 生成唯一的图层名称
430 | relPath, _ := filepath.Rel(rootFolderPath, binFile)
431 | layerName := generateUniqueLayerName(relPath)
432 |
433 | // 写入图层
434 | err = writeLayerToDataset(layer, dataset, layerName)
435 | if err != nil {
436 | fmt.Printf(" 错误: %v\n", err)
437 | errorCount++
438 | } else {
439 | fmt.Printf(" 成功: %s\n", layerName)
440 | successCount++
441 | }
442 |
443 | layer.Close()
444 | }
445 |
446 | fmt.Printf("\n批量转换完成:\n")
447 | fmt.Printf(" 成功: %d 个图层\n", successCount)
448 | fmt.Printf(" 失败: %d 个图层\n", errorCount)
449 |
450 | return nil
451 | }
452 |
453 | // generateUniqueLayerName 生成唯一的图层名称
454 | func generateUniqueLayerName(filePath string) string {
455 | // 将路径分隔符替换为下划线
456 | name := strings.ReplaceAll(filePath, string(filepath.Separator), "_")
457 | name = strings.ReplaceAll(name, "/", "_")
458 | name = strings.ReplaceAll(name, "\\", "_")
459 |
460 | // 移除.bin扩展名
461 | name = strings.TrimSuffix(name, ".bin")
462 |
463 | // 清理名称
464 | return sanitizeLayerName(name)
465 | }
--------------------------------------------------------------------------------
/Clip.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2025 [GrainArc]
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published
6 | by the Free Software Foundation, either version 3 of the License, or
7 | (at your option) any later version.
8 |
9 | This program is distributed in the hope that it will be useful,
10 | but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | GNU Affero General Public License for more details.
13 |
14 | You should have received a copy of the GNU Affero General Public License
15 | along with this program. If not, see .
16 | */
17 | package Gogeo
18 |
19 | /*
20 | #include "osgeo_utils.h"
21 | // 执行带进度监测的裁剪分析
22 | static OGRErr performClipWithProgress(OGRLayerH inputLayer,
23 | OGRLayerH methodLayer,
24 | OGRLayerH resultLayer,
25 | char **options,
26 | void *progressData) {
27 | return OGR_L_Clip(inputLayer, methodLayer, resultLayer, options,
28 | progressCallback, progressData);
29 | }
30 | */
31 | import "C"
32 |
33 | import (
34 | "fmt"
35 | "github.com/google/uuid"
36 | "log"
37 | "runtime"
38 | "sync"
39 | "sync/atomic"
40 | "time"
41 | "unsafe"
42 | )
43 |
44 | // SpatialClipAnalysis并行空间裁剪分析
45 | func SpatialClipAnalysis(inputLayer, methodlayer *GDALLayer, config *ParallelGeosConfig) (*GeosAnalysisResult, error) {
46 |
47 | defer inputLayer.Close()
48 | defer methodlayer.Close()
49 |
50 | err := addIdentifierField(inputLayer, "gogeo_analysis_id")
51 | if err != nil {
52 | return nil, fmt.Errorf("添加唯一标识字段失败: %v", err)
53 | }
54 | //执行裁剪分析
55 | resultLayer, err := performParallelClipAnalysis(inputLayer, methodlayer, config)
56 | if err != nil {
57 | return nil, fmt.Errorf("执行并行裁剪分析失败: %v", err)
58 | }
59 |
60 | resultCount := resultLayer.GetFeatureCount()
61 |
62 | if config.IsMergeTile == true {
63 | unionResult, err := performUnionByFields(resultLayer, config.PrecisionConfig, config.ProgressCallback)
64 | if err != nil {
65 | return nil, fmt.Errorf("执行融合操作失败: %v", err)
66 | }
67 |
68 | fmt.Printf("融合操作完成,最终生成 %d 个要素\n", unionResult.ResultCount)
69 | // 删除临时的字段
70 | err = deleteFieldFromLayer(unionResult.OutputLayer, "gogeo_analysis_id")
71 | if err != nil {
72 | fmt.Printf("警告: 删除临时标识字段失败: %v\n", err)
73 | }
74 | return unionResult, nil
75 | } else {
76 |
77 | return &GeosAnalysisResult{
78 | OutputLayer: resultLayer,
79 | ResultCount: resultCount,
80 | }, nil
81 | }
82 |
83 | }
84 |
85 | func performParallelClipAnalysis(inputLayer, methodLayer *GDALLayer, config *ParallelGeosConfig) (*GDALLayer, error) {
86 | if config.PrecisionConfig != nil {
87 | // 创建内存副本
88 | inputMemLayer, err := createMemoryLayerCopy(inputLayer, "input_mem_layer")
89 | if err != nil {
90 | return nil, fmt.Errorf("创建输入图层内存副本失败: %v", err)
91 | }
92 |
93 | methodMemLayer, err := createMemoryLayerCopy(methodLayer, "erase_mem_layer")
94 | if err != nil {
95 | inputMemLayer.Close()
96 | return nil, fmt.Errorf("创建擦除图层内存副本失败: %v", err)
97 | }
98 |
99 | // 在内存图层上设置精度
100 | if config.PrecisionConfig.Enabled {
101 | flags := config.PrecisionConfig.getFlags()
102 | gridSize := C.double(config.PrecisionConfig.GridSize)
103 |
104 | C.setLayerGeometryPrecision(inputMemLayer.layer, gridSize, flags)
105 | C.setLayerGeometryPrecision(methodMemLayer.layer, gridSize, flags)
106 | }
107 |
108 | // 使用内存图层进行后续处理
109 | inputLayer = inputMemLayer
110 | methodLayer = methodMemLayer
111 | }
112 | resultLayer, err := createClipResultLayer(inputLayer, methodLayer)
113 | if err != nil {
114 | return nil, fmt.Errorf("创建结果图层失败: %v", err)
115 | }
116 | taskid := uuid.New().String()
117 | //对A B图层进行分块,并创建bin文件
118 | GenerateTiles(inputLayer, methodLayer, config.TileCount, taskid)
119 | //读取文件列表,并发执行操作
120 | GPbins, err := ReadAndGroupBinFiles(taskid)
121 | if err != nil {
122 | return nil, fmt.Errorf("提取分组文件失败: %v", err)
123 | }
124 | // 并发执行分析
125 | err = executeConcurrentClipAnalysis(GPbins, resultLayer, config)
126 | if err != nil {
127 | resultLayer.Close()
128 | return nil, fmt.Errorf("并发擦除分析失败: %v", err)
129 | }
130 | // 清理临时文件
131 | defer func() {
132 | err := cleanupTileFiles(taskid)
133 | if err != nil {
134 | log.Printf("清理临时文件失败: %v", err)
135 | }
136 | }()
137 |
138 | return resultLayer, nil
139 | }
140 |
141 | // createClipResultLayer 创建裁剪结果图层
142 | func createClipResultLayer(layer1, layer2 *GDALLayer) (*GDALLayer, error) {
143 | layerName := C.CString("clip_result")
144 | defer C.free(unsafe.Pointer(layerName))
145 |
146 | // 获取空间参考系统 - 使用输入图层的SRS
147 | srs := layer1.GetSpatialRef()
148 |
149 | // 创建结果图层 - 保持输入图层的几何类型
150 | inputGeomType := C.OGR_L_GetGeomType(layer1.layer)
151 | resultLayerPtr := C.createMemoryLayer(layerName, inputGeomType, srs)
152 | if resultLayerPtr == nil {
153 | return nil, fmt.Errorf("创建结果图层失败")
154 | }
155 |
156 | resultLayer := &GDALLayer{layer: resultLayerPtr}
157 | runtime.SetFinalizer(resultLayer, (*GDALLayer).cleanup)
158 |
159 | // 根据策略添加字段定义 - 裁剪通常保留输入图层的字段
160 | err := addLayerFields(resultLayer, layer1, "")
161 | if err != nil {
162 | resultLayer.Close()
163 | return nil, fmt.Errorf("添加字段失败: %v", err)
164 | }
165 |
166 | return resultLayer, nil
167 | }
168 |
169 | func executeConcurrentClipAnalysis(tileGroups []GroupTileFiles, resultLayer *GDALLayer, config *ParallelGeosConfig) error {
170 | maxWorkers := config.MaxWorkers
171 | if maxWorkers <= 0 {
172 | maxWorkers = runtime.NumCPU()
173 | }
174 |
175 | totalTasks := len(tileGroups)
176 | if totalTasks == 0 {
177 | return fmt.Errorf("没有分块需要处理")
178 | }
179 |
180 | // 创建任务队列和结果队列
181 | taskQueue := make(chan GroupTileFiles, totalTasks)
182 | results := make(chan taskResult, totalTasks)
183 |
184 | // 启动固定数量的工作协程
185 | var wg sync.WaitGroup
186 | for i := 0; i < maxWorkers; i++ {
187 | wg.Add(1)
188 | go worker_clip(i, taskQueue, results, config, &wg)
189 | }
190 |
191 | // 发送所有任务到队列
192 | go func() {
193 | for _, tileGroup := range tileGroups {
194 | taskQueue <- tileGroup
195 | }
196 | close(taskQueue) // 关闭任务队列,通知工作协程没有更多任务
197 | }()
198 |
199 | // 启动结果收集协程
200 | var resultWg sync.WaitGroup
201 | resultWg.Add(1)
202 | var processingError error
203 | completed := 0
204 |
205 | go func() {
206 | defer resultWg.Done()
207 |
208 | var totalDuration time.Duration
209 | var minDuration, maxDuration time.Duration
210 |
211 | for i := 0; i < totalTasks; i++ {
212 | result := <-results
213 | completed++
214 |
215 | if result.err != nil {
216 | processingError = fmt.Errorf("分块 %d 处理失败: %v", result.index, result.err)
217 | log.Printf("错误: %v", processingError)
218 | return
219 | }
220 |
221 | // 统计执行时间
222 | totalDuration += result.duration
223 | if i == 0 {
224 | minDuration = result.duration
225 | maxDuration = result.duration
226 | } else {
227 | if result.duration < minDuration {
228 | minDuration = result.duration
229 | }
230 | if result.duration > maxDuration {
231 | maxDuration = result.duration
232 | }
233 | }
234 |
235 | // 将结果合并到主图层
236 | if result.layer != nil {
237 | err := mergeResultsToMainLayer(result.layer, resultLayer)
238 | if err != nil {
239 | processingError = fmt.Errorf("合并分块 %d 结果失败: %v", result.index, err)
240 | log.Printf("错误: %v", processingError)
241 | return
242 | }
243 |
244 | // 释放临时图层资源
245 | result.layer.Close()
246 | }
247 |
248 | // 进度回调
249 | if config.ProgressCallback != nil {
250 | progress := float64(completed) / float64(totalTasks)
251 | avgDuration := totalDuration / time.Duration(completed)
252 |
253 | var memStats runtime.MemStats
254 | runtime.ReadMemStats(&memStats)
255 |
256 | message := fmt.Sprintf("已完成: %d/%d, 平均耗时: %v, 内存: %.2fMB, 协程数: %d",
257 | completed, totalTasks, avgDuration,
258 | float64(memStats.Alloc)/1024/1024, runtime.NumGoroutine())
259 |
260 | config.ProgressCallback(progress, message)
261 | }
262 |
263 | // 每处理50个任务输出一次详细统计
264 | if completed%50 == 0 || completed == totalTasks {
265 | avgDuration := totalDuration / time.Duration(completed)
266 | log.Printf("进度统计 - 已完成: %d/%d, 平均耗时: %v, 最快: %v, 最慢: %v",
267 | completed, totalTasks, avgDuration, minDuration, maxDuration)
268 | }
269 | }
270 |
271 | log.Printf("所有分块处理完成,总计: %d", completed)
272 | }()
273 |
274 | // 等待所有工作协程完成
275 | wg.Wait()
276 | close(results) // 关闭结果队列
277 |
278 | // 等待结果收集完成
279 | resultWg.Wait()
280 |
281 | if processingError != nil {
282 | return processingError
283 | }
284 |
285 | return nil
286 | }
287 |
288 | func worker_clip(workerID int, taskQueue <-chan GroupTileFiles, results chan<- taskResult, config *ParallelGeosConfig, wg *sync.WaitGroup) {
289 | defer wg.Done()
290 |
291 | tasksProcessed := 0
292 |
293 | for tileGroup := range taskQueue {
294 |
295 | start := time.Now()
296 |
297 | // 处理单个分块
298 | layer, err := processTileGroupforClip(tileGroup, config)
299 |
300 | duration := time.Since(start)
301 |
302 | tasksProcessed++
303 |
304 | // 发送结果
305 | results <- taskResult{
306 | layer: layer,
307 | err: err,
308 | duration: duration,
309 | index: tileGroup.Index,
310 | }
311 |
312 | // 定期强制垃圾回收
313 |
314 | runtime.GC()
315 |
316 | }
317 |
318 | }
319 |
320 | func processTileGroupforClip(tileGroup GroupTileFiles, config *ParallelGeosConfig) (*GDALLayer, error) {
321 |
322 | // 加载layer1的bin文件
323 | inputTileLayer, err := DeserializeLayerFromFile(tileGroup.GPBin.Layer1)
324 | if err != nil {
325 | return nil, fmt.Errorf("加载输入分块文件失败: %v", err)
326 | }
327 |
328 | // 加载layer2的bin文件
329 | eraseTileLayer, err := DeserializeLayerFromFile(tileGroup.GPBin.Layer2)
330 | if err != nil {
331 | return nil, fmt.Errorf("加载擦除分块文件失败: %v", err)
332 | }
333 | defer func() {
334 | inputTileLayer.Close()
335 | eraseTileLayer.Close()
336 |
337 | }()
338 |
339 | // 为当前分块创建临时结果图层
340 | tileName := fmt.Sprintf("tile_result_%d", tileGroup.Index)
341 | tileResultLayer, err := createTileResultLayer(inputTileLayer, tileName)
342 | if err != nil {
343 | return nil, fmt.Errorf("创建分块结果图层失败: %v", err)
344 | }
345 |
346 | // 执行裁剪分析 - 不使用进度回调
347 | err = executeClipAnalysis(inputTileLayer, eraseTileLayer, tileResultLayer, nil)
348 | if err != nil {
349 | tileResultLayer.Close()
350 | return nil, fmt.Errorf("执行擦除分析失败: %v", err)
351 | }
352 | return tileResultLayer, nil
353 | }
354 |
355 | // executeClipAnalysis 执行裁剪分析
356 | func executeClipAnalysis(inputLayer, eraseLayer, resultLayer *GDALLayer, progressCallback ProgressCallback) error {
357 | // 设置GDAL选项
358 | var options **C.char
359 | defer func() {
360 | if options != nil {
361 | C.CSLDestroy(options)
362 | }
363 | }()
364 |
365 | skipFailuresOpt := C.CString("SKIP_FAILURES=YES")
366 | promoteToMultiOpt := C.CString("PROMOTE_TO_MULTI=YES")
367 | keepLowerDimOpt := C.CString("KEEP_LOWER_DIMENSION_GEOMETRIES=NO")
368 | usePreparatedGeomOpt := C.CString("USE_PREPARED_GEOMETRIES=YES")
369 | defer C.free(unsafe.Pointer(skipFailuresOpt))
370 | defer C.free(unsafe.Pointer(promoteToMultiOpt))
371 | defer C.free(unsafe.Pointer(keepLowerDimOpt))
372 | defer C.free(unsafe.Pointer(usePreparatedGeomOpt))
373 | options = C.CSLAddString(options, skipFailuresOpt)
374 | options = C.CSLAddString(options, promoteToMultiOpt)
375 | options = C.CSLAddString(options, keepLowerDimOpt)
376 | options = C.CSLAddString(options, usePreparatedGeomOpt)
377 |
378 | // 执行擦除操作
379 | return executeGDALClipWithProgress(inputLayer, eraseLayer, resultLayer, options, progressCallback)
380 | }
381 |
382 | // 执行函数
383 | func executeGDALClipWithProgress(inputLayer, methodLayer, resultLayer *GDALLayer, options **C.char, progressCallback ProgressCallback) error {
384 | // 首先修复几何体拓扑
385 |
386 | fixGeometryTopology(inputLayer)
387 | fixGeometryTopology(methodLayer)
388 |
389 | var err C.OGRErr
390 |
391 | if progressCallback != nil {
392 | // 创建进度数据结构
393 | progressData := &ProgressData{
394 | callback: progressCallback,
395 | cancelled: false,
396 | }
397 |
398 | // 生成唯一ID
399 | progressID := atomic.AddInt64(&progressIDCounter, 1)
400 | progressKey := uintptr(progressID)
401 |
402 | progressDataMutex.Lock()
403 | progressDataMap[progressKey] = progressData
404 | progressDataMutex.Unlock()
405 |
406 | // 清理函数
407 | defer func() {
408 | progressDataMutex.Lock()
409 | delete(progressDataMap, progressKey)
410 | progressDataMutex.Unlock()
411 | }()
412 |
413 | // 传递ID值
414 | err = C.performClipWithProgress(inputLayer.layer, methodLayer.layer, resultLayer.layer, options, unsafe.Pointer(progressKey))
415 | } else {
416 | err = C.OGR_L_Clip(inputLayer.layer, methodLayer.layer, resultLayer.layer, options, nil, nil)
417 | }
418 |
419 | // 执行后立即清理GDAL内部缓存
420 | defer func() {
421 | // 强制清理图层缓存
422 | C.OGR_L_ResetReading(inputLayer.layer)
423 | C.OGR_L_ResetReading(methodLayer.layer)
424 |
425 | }()
426 |
427 | if err != C.OGRERR_NONE {
428 |
429 | return fmt.Errorf("GDAL分析操作失败,错误代码: %d", int(err))
430 | }
431 |
432 | return nil
433 | }
434 |
--------------------------------------------------------------------------------
/Erase.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2025 [GrainArc]
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published
6 | by the Free Software Foundation, either version 3 of the License, or
7 | (at your option) any later version.
8 |
9 | This program is distributed in the hope that it will be useful,
10 | but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | GNU Affero General Public License for more details.
13 |
14 | You should have received a copy of the GNU Affero General Public License
15 | along with this program. If not, see .
16 | */
17 | package Gogeo
18 |
19 | /*
20 | #include "osgeo_utils.h"
21 | static OGRErr performEraseWithProgress(OGRLayerH inputLayer,
22 | OGRLayerH methodLayer,
23 | OGRLayerH resultLayer,
24 | char **options,
25 | void *progressData) {
26 | return OGR_L_Erase(inputLayer, methodLayer, resultLayer, options,
27 | progressCallback, progressData);
28 | }
29 |
30 |
31 | */
32 | import "C"
33 | import (
34 | "fmt"
35 | "github.com/google/uuid"
36 | "log"
37 | "runtime"
38 | "sync"
39 | "sync/atomic"
40 | "time"
41 | "unsafe"
42 | )
43 |
44 | // SpatialEraseAnalysis执行并行空间擦除分析
45 | func SpatialEraseAnalysis(inputLayer, methodlayer *GDALLayer, config *ParallelGeosConfig) (*GeosAnalysisResult, error) {
46 | // 读取输入图层
47 |
48 | defer inputLayer.Close()
49 |
50 | // 读取擦除图层
51 |
52 | defer methodlayer.Close()
53 |
54 | // 为输入图层添加唯一标识字段(用于后续融合)
55 | err := addIdentifierField(inputLayer, "gogeo_analysis_id")
56 | if err != nil {
57 | return nil, fmt.Errorf("添加唯一标识字段失败: %v", err)
58 | }
59 | inputTable := inputLayer.GetLayerName()
60 | eraseTable := methodlayer.GetLayerName()
61 |
62 | // 执行基于瓦片裁剪的并行擦除分析
63 | resultLayer, err := performTileClipEraseAnalysis(inputLayer, methodlayer, inputTable, eraseTable, config)
64 | if err != nil {
65 | return nil, fmt.Errorf("执行瓦片裁剪擦除分析失败: %v", err)
66 | }
67 |
68 | // 计算结果数量
69 | resultCount := resultLayer.GetFeatureCount()
70 |
71 | if config.IsMergeTile == true {
72 | fmt.Println("配置要求执行融合操作,开始融合...")
73 | unionResult, err := performUnionByFields(resultLayer, config.PrecisionConfig, config.ProgressCallback)
74 | if err != nil {
75 | return nil, fmt.Errorf("执行融合操作失败: %v", err)
76 | }
77 |
78 | fmt.Printf("融合操作完成,最终生成 %d 个要素\n", unionResult.ResultCount)
79 | // 删除临时的字段
80 | err = deleteFieldFromLayer(unionResult.OutputLayer, "gogeo_analysis_id")
81 | if err != nil {
82 | fmt.Printf("警告: 删除临时标识字段失败: %v\n", err)
83 | }
84 | return unionResult, nil
85 | } else {
86 |
87 | return &GeosAnalysisResult{
88 | OutputLayer: resultLayer,
89 | ResultCount: resultCount,
90 | }, nil
91 | }
92 |
93 | }
94 |
95 | // performTileClipEraseAnalysis 执行基于瓦片裁剪的并行擦除分析
96 | func performTileClipEraseAnalysis(inputLayer, eraseLayer *GDALLayer, inputTableName, eraseTableName string, config *ParallelGeosConfig) (*GDALLayer, error) {
97 | // 如果启用了精度设置,在分块裁剪前对原始图层进行精度处理
98 |
99 | if config.PrecisionConfig != nil {
100 | // 创建内存副本
101 | inputMemLayer, err := createMemoryLayerCopy(inputLayer, "input_mem_layer")
102 | if err != nil {
103 | return nil, fmt.Errorf("创建输入图层内存副本失败: %v", err)
104 | }
105 |
106 | eraseMemLayer, err := createMemoryLayerCopy(eraseLayer, "erase_mem_layer")
107 | if err != nil {
108 | inputMemLayer.Close()
109 | return nil, fmt.Errorf("创建擦除图层内存副本失败: %v", err)
110 | }
111 |
112 | // 在内存图层上设置精度
113 | if config.PrecisionConfig.Enabled {
114 | flags := config.PrecisionConfig.getFlags()
115 | gridSize := C.double(config.PrecisionConfig.GridSize)
116 |
117 | C.setLayerGeometryPrecision(inputMemLayer.layer, gridSize, flags)
118 | C.setLayerGeometryPrecision(eraseMemLayer.layer, gridSize, flags)
119 | }
120 | // 使用内存图层进行后续处理
121 | inputLayer = inputMemLayer
122 | eraseLayer = eraseMemLayer
123 | }
124 | // 创建结果图层
125 | resultLayer, err := createEraseAnalysisResultLayer(inputLayer)
126 | if err != nil {
127 | return nil, fmt.Errorf("创建结果图层失败: %v", err)
128 | }
129 | taskid := uuid.New().String()
130 |
131 | //对A B图层进行分块,并创建bin文件
132 | GenerateTiles(inputLayer, eraseLayer, config.TileCount, taskid)
133 | //读取文件列表,并发执行擦除操作
134 | GPbins, err := ReadAndGroupBinFiles(taskid)
135 | if err != nil {
136 | return nil, fmt.Errorf("提取分组文件失败: %v", err)
137 | }
138 |
139 | // 并发执行擦除分析
140 | err = executeConcurrentEraseAnalysis(GPbins, resultLayer, config)
141 | if err != nil {
142 | resultLayer.Close()
143 | return nil, fmt.Errorf("并发擦除分析失败: %v", err)
144 | }
145 |
146 | // 清理临时文件
147 | defer func() {
148 | err := cleanupTileFiles(taskid)
149 | if err != nil {
150 | log.Printf("清理临时文件失败: %v", err)
151 | }
152 | }()
153 |
154 | return resultLayer, nil
155 | }
156 |
157 | func executeConcurrentEraseAnalysis(tileGroups []GroupTileFiles, resultLayer *GDALLayer, config *ParallelGeosConfig) error {
158 | maxWorkers := config.MaxWorkers
159 | if maxWorkers <= 0 {
160 | maxWorkers = runtime.NumCPU()
161 | }
162 |
163 | totalTasks := len(tileGroups)
164 | if totalTasks == 0 {
165 | return fmt.Errorf("没有分块需要处理")
166 | }
167 |
168 | // 创建任务队列和结果队列
169 | taskQueue := make(chan GroupTileFiles, totalTasks)
170 | results := make(chan taskResult, totalTasks)
171 |
172 | // 启动固定数量的工作协程
173 | var wg sync.WaitGroup
174 | for i := 0; i < maxWorkers; i++ {
175 | wg.Add(1)
176 | go worker(i, taskQueue, results, config, &wg)
177 | }
178 |
179 | // 发送所有任务到队列
180 | go func() {
181 | for _, tileGroup := range tileGroups {
182 | taskQueue <- tileGroup
183 | }
184 | close(taskQueue) // 关闭任务队列,通知工作协程没有更多任务
185 | }()
186 |
187 | // 启动结果收集协程
188 | var resultWg sync.WaitGroup
189 | resultWg.Add(1)
190 | var processingError error
191 | completed := 0
192 |
193 | go func() {
194 | defer resultWg.Done()
195 |
196 | var totalDuration time.Duration
197 | var minDuration, maxDuration time.Duration
198 |
199 | for i := 0; i < totalTasks; i++ {
200 | result := <-results
201 | completed++
202 |
203 | if result.err != nil {
204 | processingError = fmt.Errorf("分块 %d 处理失败: %v", result.index, result.err)
205 | log.Printf("错误: %v", processingError)
206 | return
207 | }
208 |
209 | // 统计执行时间
210 | totalDuration += result.duration
211 | if i == 0 {
212 | minDuration = result.duration
213 | maxDuration = result.duration
214 | } else {
215 | if result.duration < minDuration {
216 | minDuration = result.duration
217 | }
218 | if result.duration > maxDuration {
219 | maxDuration = result.duration
220 | }
221 | }
222 |
223 | // 将结果合并到主图层
224 | if result.layer != nil {
225 | err := mergeResultsToMainLayer(result.layer, resultLayer)
226 | if err != nil {
227 | processingError = fmt.Errorf("合并分块 %d 结果失败: %v", result.index, err)
228 | log.Printf("错误: %v", processingError)
229 | return
230 | }
231 |
232 | // 释放临时图层资源
233 | result.layer.Close()
234 | }
235 |
236 | // 进度回调
237 | if config.ProgressCallback != nil {
238 | progress := float64(completed) / float64(totalTasks)
239 | avgDuration := totalDuration / time.Duration(completed)
240 |
241 | var memStats runtime.MemStats
242 | runtime.ReadMemStats(&memStats)
243 |
244 | message := fmt.Sprintf("已完成: %d/%d, 平均耗时: %v, 内存: %.2fMB, 协程数: %d",
245 | completed, totalTasks, avgDuration,
246 | float64(memStats.Alloc)/1024/1024, runtime.NumGoroutine())
247 |
248 | config.ProgressCallback(progress, message)
249 | }
250 |
251 | // 每处理50个任务输出一次详细统计
252 | if completed%50 == 0 || completed == totalTasks {
253 | avgDuration := totalDuration / time.Duration(completed)
254 | log.Printf("进度统计 - 已完成: %d/%d, 平均耗时: %v, 最快: %v, 最慢: %v",
255 | completed, totalTasks, avgDuration, minDuration, maxDuration)
256 | }
257 | }
258 |
259 | log.Printf("所有分块处理完成,总计: %d", completed)
260 | }()
261 |
262 | // 等待所有工作协程完成
263 | wg.Wait()
264 | close(results) // 关闭结果队列
265 |
266 | // 等待结果收集完成
267 | resultWg.Wait()
268 |
269 | if processingError != nil {
270 | return processingError
271 | }
272 |
273 | return nil
274 | }
275 |
276 | func worker(workerID int, taskQueue <-chan GroupTileFiles, results chan<- taskResult, config *ParallelGeosConfig, wg *sync.WaitGroup) {
277 | defer wg.Done()
278 |
279 | tasksProcessed := 0
280 |
281 | for tileGroup := range taskQueue {
282 |
283 | start := time.Now()
284 |
285 | // 处理单个分块
286 | layer, err := processTileGroup(tileGroup, config)
287 |
288 | duration := time.Since(start)
289 |
290 | tasksProcessed++
291 |
292 | // 发送结果
293 | results <- taskResult{
294 | layer: layer,
295 | err: err,
296 | duration: duration,
297 | index: tileGroup.Index,
298 | }
299 |
300 | // 定期强制垃圾回收
301 |
302 | runtime.GC()
303 |
304 | }
305 |
306 | }
307 |
308 | func processTileGroup(tileGroup GroupTileFiles, config *ParallelGeosConfig) (*GDALLayer, error) {
309 |
310 | // 加载layer1的bin文件
311 | inputTileLayer, err := DeserializeLayerFromFile(tileGroup.GPBin.Layer1)
312 | if err != nil {
313 | return nil, fmt.Errorf("加载输入分块文件失败: %v", err)
314 | }
315 |
316 | // 加载layer2的bin文件
317 | eraseTileLayer, err := DeserializeLayerFromFile(tileGroup.GPBin.Layer2)
318 | if err != nil {
319 | return nil, fmt.Errorf("加载擦除分块文件失败: %v", err)
320 | }
321 | defer func() {
322 | inputTileLayer.Close()
323 | eraseTileLayer.Close()
324 |
325 | }()
326 |
327 | // 为当前分块创建临时结果图层
328 | tileName := fmt.Sprintf("tile_result_%d", tileGroup.Index)
329 | tileResultLayer, err := createTileResultLayer(inputTileLayer, tileName)
330 | if err != nil {
331 | return nil, fmt.Errorf("创建分块结果图层失败: %v", err)
332 | }
333 |
334 | // 执行擦除分析 - 不使用进度回调
335 | err = executeEraseAnalysis(inputTileLayer, eraseTileLayer, tileResultLayer, nil)
336 | if err != nil {
337 | tileResultLayer.Close()
338 | return nil, fmt.Errorf("执行擦除分析失败: %v", err)
339 | }
340 | return tileResultLayer, nil
341 | }
342 |
343 | // createEraseAnalysisResultLayer 创建擦除分析结果图层
344 | func createEraseAnalysisResultLayer(inputLayer *GDALLayer) (*GDALLayer, error) {
345 | layerName := C.CString("erase_result")
346 | defer C.free(unsafe.Pointer(layerName))
347 |
348 | // 获取空间参考系统
349 | srs := inputLayer.GetSpatialRef()
350 |
351 | // 创建结果图层
352 | resultLayerPtr := C.createMemoryLayer(layerName, C.wkbMultiPolygon, srs)
353 | if resultLayerPtr == nil {
354 | return nil, fmt.Errorf("创建结果图层失败")
355 | }
356 |
357 | resultLayer := &GDALLayer{layer: resultLayerPtr}
358 | runtime.SetFinalizer(resultLayer, (*GDALLayer).cleanup)
359 |
360 | // 添加字段定义 - 只需要输入图层的字段
361 | err := addLayerFields(resultLayer, inputLayer, "")
362 |
363 | if err != nil {
364 | resultLayer.Close()
365 | return nil, fmt.Errorf("添加字段失败: %v", err)
366 | }
367 |
368 | return resultLayer, nil
369 | }
370 |
371 | // executeEraseAnalysis 执行擦除分析
372 | func executeEraseAnalysis(inputLayer, eraseLayer, resultLayer *GDALLayer, progressCallback ProgressCallback) error {
373 | // 设置GDAL选项
374 | var options **C.char
375 | defer func() {
376 | if options != nil {
377 | C.CSLDestroy(options)
378 | }
379 | }()
380 |
381 | skipFailuresOpt := C.CString("SKIP_FAILURES=YES")
382 | promoteToMultiOpt := C.CString("PROMOTE_TO_MULTI=YES")
383 | keepLowerDimOpt := C.CString("KEEP_LOWER_DIMENSION_GEOMETRIES=NO")
384 | usePreparatedGeomOpt := C.CString("USE_PREPARED_GEOMETRIES=YES")
385 | methodOpt := C.CString("METHOD=FAST") // 或者 "ACCURATE"
386 | defer C.free(unsafe.Pointer(skipFailuresOpt))
387 | defer C.free(unsafe.Pointer(promoteToMultiOpt))
388 | defer C.free(unsafe.Pointer(keepLowerDimOpt))
389 | defer C.free(unsafe.Pointer(usePreparatedGeomOpt))
390 | defer C.free(unsafe.Pointer(methodOpt))
391 | options = C.CSLAddString(options, skipFailuresOpt)
392 | options = C.CSLAddString(options, promoteToMultiOpt)
393 | options = C.CSLAddString(options, keepLowerDimOpt)
394 | options = C.CSLAddString(options, usePreparatedGeomOpt)
395 | options = C.CSLAddString(options, methodOpt)
396 |
397 | // 执行擦除操作
398 | return executeGDALEraseWithProgress(inputLayer, eraseLayer, resultLayer, options, progressCallback)
399 | }
400 |
401 | // 修改执行函数
402 | func executeGDALEraseWithProgress(inputLayer, eraseLayer, resultLayer *GDALLayer, options **C.char, progressCallback ProgressCallback) error {
403 | // 首先修复几何体拓扑
404 |
405 | fixGeometryTopology(inputLayer)
406 | fixGeometryTopology(eraseLayer)
407 |
408 | var err C.OGRErr
409 |
410 | if progressCallback != nil {
411 | // 创建进度数据结构
412 | progressData := &ProgressData{
413 | callback: progressCallback,
414 | cancelled: false,
415 | }
416 |
417 | // 生成唯一ID
418 | progressID := atomic.AddInt64(&progressIDCounter, 1)
419 | progressKey := uintptr(progressID)
420 |
421 | progressDataMutex.Lock()
422 | progressDataMap[progressKey] = progressData
423 | progressDataMutex.Unlock()
424 |
425 | // 清理函数
426 | defer func() {
427 | progressDataMutex.Lock()
428 | delete(progressDataMap, progressKey)
429 | progressDataMutex.Unlock()
430 | }()
431 |
432 | // 传递ID值
433 | err = C.performEraseWithProgress(inputLayer.layer, eraseLayer.layer, resultLayer.layer, options, unsafe.Pointer(progressKey))
434 | } else {
435 | err = C.OGR_L_Erase(inputLayer.layer, eraseLayer.layer, resultLayer.layer, options, nil, nil)
436 | }
437 |
438 | // 执行后立即清理GDAL内部缓存
439 | defer func() {
440 | // 强制清理图层缓存
441 | C.OGR_L_ResetReading(inputLayer.layer)
442 | C.OGR_L_ResetReading(eraseLayer.layer)
443 |
444 | }()
445 |
446 | if err != C.OGRERR_NONE {
447 | return fmt.Errorf("GDAL擦除操作失败,错误代码: %d", int(err))
448 | }
449 |
450 | return nil
451 | }
452 |
--------------------------------------------------------------------------------
/SymDifference.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2025 [GrainArc]
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published
6 | by the Free Software Foundation, either version 3 of the License, or
7 | (at your option) any later version.
8 |
9 | This program is distributed in the hope that it will be useful,
10 | but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | GNU Affero General Public License for more details.
13 |
14 | You should have received a copy of the GNU Affero General Public License
15 | along with this program. If not, see .
16 | */
17 | package Gogeo
18 |
19 | /*
20 | #include "osgeo_utils.h"
21 | static OGRErr performSymDifferenceWithProgress(OGRLayerH inputLayer,
22 | OGRLayerH methodLayer,
23 | OGRLayerH resultLayer,
24 | char **options,
25 | void *progressData) {
26 | return OGR_L_SymDifference(inputLayer, methodLayer, resultLayer, options,
27 | progressCallback, progressData);
28 | }
29 |
30 |
31 |
32 | */
33 | import "C"
34 | import (
35 | "fmt"
36 | "github.com/google/uuid"
37 | "log"
38 | "runtime"
39 | "sync"
40 | "time"
41 | "unsafe"
42 | )
43 |
44 | func SpatialSymDifferenceAnalysis(inputLayer, methodLayer *GDALLayer, config *ParallelGeosConfig) (*GeosAnalysisResult, error) {
45 |
46 | defer inputLayer.Close()
47 |
48 | defer methodLayer.Close()
49 |
50 | // 为两个图层添加唯一标识字段
51 | err := addIdentifierField(inputLayer, "gogeo_analysis_id")
52 | if err != nil {
53 | return nil, fmt.Errorf("添加唯一标识字段失败: %v", err)
54 | }
55 | err = addIdentifierField(methodLayer, "gogeo_analysis_id2")
56 | if err != nil {
57 | return nil, fmt.Errorf("添加唯一标识字段失败: %v", err)
58 | }
59 |
60 | // 执行基于瓦片裁剪的并行对称差异分析
61 | resultLayer, err := performSymDifferenceAnalysis(inputLayer, methodLayer, config)
62 | if err != nil {
63 | return nil, fmt.Errorf("执行瓦片裁剪对称差异分析失败: %v", err)
64 | }
65 |
66 | // 计算结果数量
67 | resultCount := resultLayer.GetFeatureCount()
68 |
69 | fmt.Printf("对称差异分析完成,共生成 %d 个要素\n", resultCount)
70 |
71 | if config.IsMergeTile {
72 | // 执行按标识字段的融合操作
73 | unionResult, err := performUnionByFields(resultLayer, config.PrecisionConfig, config.ProgressCallback)
74 | if err != nil {
75 | return nil, fmt.Errorf("执行融合操作失败: %v", err)
76 | }
77 |
78 | // 删除临时的_identityID字段
79 | err = deleteFieldFromLayerFuzzy(unionResult.OutputLayer, "gogeo_analysis_id")
80 | if err != nil {
81 | fmt.Printf("警告: 删除临时标识字段失败: %v\n", err)
82 | }
83 |
84 | return unionResult, nil
85 | } else {
86 |
87 | return &GeosAnalysisResult{
88 | OutputLayer: resultLayer,
89 | ResultCount: resultCount,
90 | }, nil
91 | }
92 | }
93 |
94 | func performSymDifferenceAnalysis(inputLayer, methodLayer *GDALLayer, config *ParallelGeosConfig) (*GDALLayer, error) {
95 | if config.PrecisionConfig != nil {
96 | // 创建内存副本
97 | inputMemLayer, err := createMemoryLayerCopy(inputLayer, "input_mem_layer")
98 | if err != nil {
99 | return nil, fmt.Errorf("创建输入图层内存副本失败: %v", err)
100 | }
101 |
102 | methodMemLayer, err := createMemoryLayerCopy(methodLayer, "erase_mem_layer")
103 | if err != nil {
104 | inputMemLayer.Close()
105 | return nil, fmt.Errorf("创建擦除图层内存副本失败: %v", err)
106 | }
107 |
108 | // 在内存图层上设置精度
109 | if config.PrecisionConfig.Enabled {
110 | flags := config.PrecisionConfig.getFlags()
111 | gridSize := C.double(config.PrecisionConfig.GridSize)
112 |
113 | C.setLayerGeometryPrecision(inputMemLayer.layer, gridSize, flags)
114 | C.setLayerGeometryPrecision(methodMemLayer.layer, gridSize, flags)
115 | }
116 | // 使用内存图层进行后续处理
117 | inputLayer = inputMemLayer
118 | methodLayer = methodMemLayer
119 | }
120 | resultLayer, err := createSymDifferenceResultLayer(inputLayer, methodLayer)
121 | if err != nil {
122 | return nil, fmt.Errorf("创建结果图层失败: %v", err)
123 | }
124 | taskid := uuid.New().String()
125 | //对A B图层进行分块,并创建bin文件
126 | GenerateTiles(inputLayer, methodLayer, config.TileCount, taskid)
127 | //读取文件列表,并发执行操作
128 | GPbins, err := ReadAndGroupBinFiles(taskid)
129 | if err != nil {
130 | return nil, fmt.Errorf("提取分组文件失败: %v", err)
131 | }
132 | // 并发执行分析
133 | err = executeConcurrentSymDifferenceAnalysis(GPbins, resultLayer, config)
134 | if err != nil {
135 | resultLayer.Close()
136 | return nil, fmt.Errorf("并发擦除分析失败: %v", err)
137 | }
138 | // 清理临时文件
139 | defer func() {
140 | err := cleanupTileFiles(taskid)
141 | if err != nil {
142 | log.Printf("清理临时文件失败: %v", err)
143 | }
144 | }()
145 |
146 | return resultLayer, nil
147 | }
148 | func executeConcurrentSymDifferenceAnalysis(tileGroups []GroupTileFiles, resultLayer *GDALLayer, config *ParallelGeosConfig) error {
149 | maxWorkers := config.MaxWorkers
150 | if maxWorkers <= 0 {
151 | maxWorkers = runtime.NumCPU()
152 | }
153 |
154 | totalTasks := len(tileGroups)
155 | if totalTasks == 0 {
156 | return fmt.Errorf("没有分块需要处理")
157 | }
158 |
159 | // 创建任务队列和结果队列
160 | taskQueue := make(chan GroupTileFiles, totalTasks)
161 | results := make(chan taskResult, totalTasks)
162 |
163 | // 启动固定数量的工作协程
164 | var wg sync.WaitGroup
165 | for i := 0; i < maxWorkers; i++ {
166 | wg.Add(1)
167 | go worker_symDifference(i, taskQueue, results, config, &wg)
168 | }
169 |
170 | // 发送所有任务到队列
171 | go func() {
172 | for _, tileGroup := range tileGroups {
173 | taskQueue <- tileGroup
174 | }
175 | close(taskQueue) // 关闭任务队列,通知工作协程没有更多任务
176 | }()
177 |
178 | // 启动结果收集协程
179 | var resultWg sync.WaitGroup
180 | resultWg.Add(1)
181 | var processingError error
182 | completed := 0
183 |
184 | go func() {
185 | defer resultWg.Done()
186 |
187 | var totalDuration time.Duration
188 | var minDuration, maxDuration time.Duration
189 |
190 | for i := 0; i < totalTasks; i++ {
191 | result := <-results
192 | completed++
193 |
194 | if result.err != nil {
195 | processingError = fmt.Errorf("分块 %d 处理失败: %v", result.index, result.err)
196 | log.Printf("错误: %v", processingError)
197 | return
198 | }
199 |
200 | // 统计执行时间
201 | totalDuration += result.duration
202 | if i == 0 {
203 | minDuration = result.duration
204 | maxDuration = result.duration
205 | } else {
206 | if result.duration < minDuration {
207 | minDuration = result.duration
208 | }
209 | if result.duration > maxDuration {
210 | maxDuration = result.duration
211 | }
212 | }
213 |
214 | // 将结果合并到主图层
215 | if result.layer != nil {
216 | err := mergeResultsToMainLayer(result.layer, resultLayer)
217 | if err != nil {
218 | processingError = fmt.Errorf("合并分块 %d 结果失败: %v", result.index, err)
219 | log.Printf("错误: %v", processingError)
220 | return
221 | }
222 |
223 | // 释放临时图层资源
224 | result.layer.Close()
225 | }
226 |
227 | // 进度回调
228 | if config.ProgressCallback != nil {
229 | progress := float64(completed) / float64(totalTasks)
230 | avgDuration := totalDuration / time.Duration(completed)
231 |
232 | var memStats runtime.MemStats
233 | runtime.ReadMemStats(&memStats)
234 |
235 | message := fmt.Sprintf("已完成: %d/%d, 平均耗时: %v, 内存: %.2fMB, 协程数: %d",
236 | completed, totalTasks, avgDuration,
237 | float64(memStats.Alloc)/1024/1024, runtime.NumGoroutine())
238 |
239 | config.ProgressCallback(progress, message)
240 | }
241 |
242 | // 每处理50个任务输出一次详细统计
243 | if completed%50 == 0 || completed == totalTasks {
244 | avgDuration := totalDuration / time.Duration(completed)
245 | log.Printf("进度统计 - 已完成: %d/%d, 平均耗时: %v, 最快: %v, 最慢: %v",
246 | completed, totalTasks, avgDuration, minDuration, maxDuration)
247 | }
248 | }
249 |
250 | log.Printf("所有分块处理完成,总计: %d", completed)
251 | }()
252 |
253 | // 等待所有工作协程完成
254 | wg.Wait()
255 | close(results) // 关闭结果队列
256 |
257 | // 等待结果收集完成
258 | resultWg.Wait()
259 |
260 | if processingError != nil {
261 | return processingError
262 | }
263 |
264 | return nil
265 | }
266 |
267 | func worker_symDifference(workerID int, taskQueue <-chan GroupTileFiles, results chan<- taskResult,
268 | config *ParallelGeosConfig, wg *sync.WaitGroup) {
269 | defer wg.Done()
270 |
271 | tasksProcessed := 0
272 |
273 | for tileGroup := range taskQueue {
274 |
275 | start := time.Now()
276 |
277 | // 处理单个分块
278 | layer, err := processTileGroupforSymDifference(tileGroup, config)
279 |
280 | duration := time.Since(start)
281 |
282 | tasksProcessed++
283 |
284 | // 发送结果
285 | results <- taskResult{
286 | layer: layer,
287 | err: err,
288 | duration: duration,
289 | index: tileGroup.Index,
290 | }
291 |
292 | // 定期强制垃圾回收
293 |
294 | runtime.GC()
295 |
296 | }
297 |
298 | }
299 | func processTileGroupforSymDifference(tileGroup GroupTileFiles, config *ParallelGeosConfig) (*GDALLayer, error) {
300 |
301 | // 加载layer1的bin文件
302 | inputTileLayer, err := DeserializeLayerFromFile(tileGroup.GPBin.Layer1)
303 | if err != nil {
304 | return nil, fmt.Errorf("加载输入分块文件失败: %v", err)
305 | }
306 |
307 | // 加载layer2的bin文件
308 | methodTileLayer, err := DeserializeLayerFromFile(tileGroup.GPBin.Layer2)
309 | if err != nil {
310 | return nil, fmt.Errorf("加载擦除分块文件失败: %v", err)
311 | }
312 | defer func() {
313 | inputTileLayer.Close()
314 | methodTileLayer.Close()
315 |
316 | }()
317 |
318 | // 为当前分块创建临时结果图层
319 | tileName := fmt.Sprintf("tile_result_%d", tileGroup.Index)
320 | tileResultLayer, err := createSymDifferenceTileResultLayer(inputTileLayer, methodTileLayer, tileName)
321 | if err != nil {
322 | return nil, fmt.Errorf("创建分块结果图层失败: %v", err)
323 | }
324 |
325 | // 执行裁剪分析 - 不使用进度回调
326 | err = executeSymDifferenceAnalysis(inputTileLayer, methodTileLayer, tileResultLayer, nil)
327 | if err != nil {
328 | tileResultLayer.Close()
329 | return nil, fmt.Errorf("执行擦除分析失败: %v", err)
330 | }
331 | return tileResultLayer, nil
332 | }
333 |
334 | func createSymDifferenceTileResultLayer(inputLayer, methodLayer *GDALLayer, layerName string) (*GDALLayer, error) {
335 | layerNameC := C.CString(layerName)
336 | defer C.free(unsafe.Pointer(layerNameC))
337 |
338 | // 获取空间参考系统
339 | srs := inputLayer.GetSpatialRef()
340 |
341 | // 创建结果图层
342 | resultLayerPtr := C.createMemoryLayer(layerNameC, C.wkbMultiPolygon, srs)
343 | if resultLayerPtr == nil {
344 | return nil, fmt.Errorf("创建结果图层失败")
345 | }
346 |
347 | resultLayer := &GDALLayer{layer: resultLayerPtr}
348 | runtime.SetFinalizer(resultLayer, (*GDALLayer).cleanup)
349 |
350 | // 添加字段定义 - 使用默认策略(合并字段,带前缀区分来源)
351 | err := addSymDifferenceFields(resultLayer, inputLayer, methodLayer)
352 | if err != nil {
353 | resultLayer.Close()
354 | return nil, fmt.Errorf("添加字段失败: %v", err)
355 | }
356 |
357 | return resultLayer, nil
358 | }
359 |
360 | // createGeosAnalysisResultLayer 创建对称差异结果图层
361 | func createSymDifferenceResultLayer(layer1, layer2 *GDALLayer) (*GDALLayer, error) {
362 | layerName := C.CString("symdifference_result")
363 | defer C.free(unsafe.Pointer(layerName))
364 |
365 | // 获取空间参考系统
366 | srs := layer1.GetSpatialRef()
367 | if srs == nil {
368 | srs = layer2.GetSpatialRef()
369 | }
370 |
371 | // 创建结果图层
372 | resultLayerPtr := C.createMemoryLayer(layerName, C.wkbMultiPolygon, srs)
373 | if resultLayerPtr == nil {
374 | return nil, fmt.Errorf("创建结果图层失败")
375 | }
376 |
377 | resultLayer := &GDALLayer{layer: resultLayerPtr}
378 | runtime.SetFinalizer(resultLayer, (*GDALLayer).cleanup)
379 |
380 | // 添加字段定义 - 使用默认策略(合并字段,带前缀区分来源)
381 | err := addSymDifferenceFields(resultLayer, layer1, layer2)
382 | if err != nil {
383 | resultLayer.Close()
384 | return nil, fmt.Errorf("添加字段失败: %v", err)
385 | }
386 |
387 | return resultLayer, nil
388 | }
389 |
390 | // addSymDifferenceFields 添加对称差异分析的字段(修改版本)
391 | func addSymDifferenceFields(resultLayer, layer1, layer2 *GDALLayer) error {
392 |
393 | // 合并两个图层的字段(不使用前缀)
394 | err1 := addLayerFields(resultLayer, layer1, "")
395 | if err1 != nil {
396 | return fmt.Errorf("添加图层1字段失败: %v", err1)
397 | }
398 |
399 | err2 := addLayerFields(resultLayer, layer2, "")
400 | if err2 != nil {
401 | return fmt.Errorf("添加图层2字段失败: %v", err2)
402 | }
403 |
404 | return nil
405 | }
406 |
407 | // executeSymDifferenceAnalysis 执行对称差异分析
408 | func executeSymDifferenceAnalysis(layer1, layer2, resultLayer *GDALLayer, progressCallback ProgressCallback) error {
409 | // 设置GDAL选项
410 | var options **C.char
411 | defer func() {
412 | if options != nil {
413 | C.CSLDestroy(options)
414 | }
415 | }()
416 |
417 | skipFailuresOpt := C.CString("SKIP_FAILURES=YES")
418 | promoteToMultiOpt := C.CString("PROMOTE_TO_MULTI=YES")
419 | keepLowerDimOpt := C.CString("KEEP_LOWER_DIMENSION_GEOMETRIES=NO")
420 | defer C.free(unsafe.Pointer(skipFailuresOpt))
421 | defer C.free(unsafe.Pointer(promoteToMultiOpt))
422 | defer C.free(unsafe.Pointer(keepLowerDimOpt))
423 |
424 | options = C.CSLAddString(options, skipFailuresOpt)
425 | options = C.CSLAddString(options, promoteToMultiOpt)
426 | options = C.CSLAddString(options, keepLowerDimOpt)
427 |
428 | // 执行对称差异操作
429 | return executeGDALSymDifference(layer1, layer2, resultLayer, options, progressCallback)
430 | }
431 |
432 | // executeGDALSymDifference 执行带进度的GDAL对称差异操作
433 | func executeGDALSymDifference(layer1, layer2, resultLayer *GDALLayer, options **C.char, progressCallback ProgressCallback) error {
434 | var progressData *ProgressData
435 | var progressArg unsafe.Pointer
436 |
437 | // 设置进度回调
438 | if progressCallback != nil {
439 | progressData = &ProgressData{
440 | callback: progressCallback,
441 | cancelled: false,
442 | }
443 | progressArg = unsafe.Pointer(uintptr(unsafe.Pointer(progressData)))
444 |
445 | progressDataMutex.Lock()
446 | progressDataMap[uintptr(progressArg)] = progressData
447 | progressDataMutex.Unlock()
448 |
449 | defer func() {
450 | progressDataMutex.Lock()
451 | delete(progressDataMap, uintptr(progressArg))
452 | progressDataMutex.Unlock()
453 | }()
454 | }
455 |
456 | // 调用GDAL的对称差异函数
457 | var err C.OGRErr
458 | if progressCallback != nil {
459 | err = C.performSymDifferenceWithProgress(layer1.layer, layer2.layer, resultLayer.layer, options, progressArg)
460 | } else {
461 | err = C.OGR_L_SymDifference(layer1.layer, layer2.layer, resultLayer.layer, options, nil, nil)
462 | }
463 |
464 | if err != C.OGRERR_NONE {
465 | return fmt.Errorf("GDAL对称差异操作失败,错误代码: %d", int(err))
466 | }
467 |
468 | return nil
469 | }
470 |
--------------------------------------------------------------------------------
/Update.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2024 [GrainArc]
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published
6 | by the Free Software Foundation, either version 3 of the License, or
7 | (at your option) any later version.
8 |
9 | This program is distributed in the hope that it will be useful,
10 | but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | GNU Affero General Public License for more details.
13 |
14 | You should have received a copy of the GNU Affero General Public License
15 | along with this program. If not, see .
16 | */
17 | package Gogeo
18 |
19 | /*
20 | #include "osgeo_utils.h"
21 | static OGRErr performUpdateWithProgress(OGRLayerH inputLayer,
22 | OGRLayerH updateLayer,
23 | OGRLayerH resultLayer,
24 | char **options,
25 | void *progressData) {
26 | return OGR_L_Update(inputLayer, updateLayer, resultLayer, options,
27 | progressCallback, progressData);
28 | }
29 |
30 | // 修改 clipLayerToTile 函数,添加来源标识参数(复用原有函数)
31 | */
32 | import "C"
33 | import (
34 | "fmt"
35 | "github.com/google/uuid"
36 | "log"
37 | "runtime"
38 | "sync"
39 | "time"
40 | "unsafe"
41 | )
42 |
43 | // SpatialUpdateAnalysisParallel 执行并行空间更新分析
44 | func SpatialUpdateAnalysis(inputLayer, methodLayer *GDALLayer, config *ParallelGeosConfig) (*GeosAnalysisResult, error) {
45 | defer inputLayer.Close()
46 |
47 | defer methodLayer.Close()
48 |
49 | // 为两个图层添加唯一标识字段
50 | err := addIdentifierField(inputLayer, "gogeo_analysis_id")
51 | if err != nil {
52 | return nil, fmt.Errorf("添加唯一标识字段失败: %v", err)
53 | }
54 | err = addIdentifierField(methodLayer, "gogeo_analysis_id2")
55 | if err != nil {
56 | return nil, fmt.Errorf("添加唯一标识字段失败: %v", err)
57 | }
58 |
59 | resultLayer, err := performUpdateAnalysis(inputLayer, methodLayer, config)
60 | if err != nil {
61 | return nil, fmt.Errorf("执行瓦片裁剪分析失败: %v", err)
62 | }
63 | // 计算结果数量
64 | resultCount := resultLayer.GetFeatureCount()
65 |
66 | fmt.Printf("分析完成,共生成 %d 个要素\n", resultCount)
67 |
68 | if config.IsMergeTile {
69 | // 执行按标识字段的融合操作
70 | unionResult, err := performUnionByFields(resultLayer, config.PrecisionConfig, config.ProgressCallback)
71 | if err != nil {
72 | return nil, fmt.Errorf("执行融合操作失败: %v", err)
73 | }
74 |
75 | // 删除临时的_identityID字段
76 | err = deleteFieldFromLayerFuzzy(unionResult.OutputLayer, "gogeo_analysis_id")
77 | if err != nil {
78 | fmt.Printf("警告: 删除临时标识字段失败: %v\n", err)
79 | }
80 |
81 | return unionResult, nil
82 | } else {
83 | // 删除临时的_identityID字段
84 | err = deleteFieldFromLayerFuzzy(resultLayer, "gogeo_analysis_id")
85 | if err != nil {
86 | fmt.Printf("警告: 删除临时标识字段失败: %v\n", err)
87 | }
88 |
89 | return &GeosAnalysisResult{
90 | OutputLayer: resultLayer,
91 | ResultCount: resultCount,
92 | }, nil
93 | }
94 | }
95 |
96 | func performUpdateAnalysis(inputLayer, methodLayer *GDALLayer, config *ParallelGeosConfig) (*GDALLayer, error) {
97 | if config.PrecisionConfig != nil {
98 | // 创建内存副本
99 | inputMemLayer, err := createMemoryLayerCopy(inputLayer, "input_mem_layer")
100 | if err != nil {
101 | return nil, fmt.Errorf("创建输入图层内存副本失败: %v", err)
102 | }
103 |
104 | methodMemLayer, err := createMemoryLayerCopy(methodLayer, "erase_mem_layer")
105 | if err != nil {
106 | inputMemLayer.Close()
107 | return nil, fmt.Errorf("图层内存副本失败: %v", err)
108 | }
109 |
110 | // 在内存图层上设置精度
111 | if config.PrecisionConfig.Enabled {
112 | flags := config.PrecisionConfig.getFlags()
113 | gridSize := C.double(config.PrecisionConfig.GridSize)
114 |
115 | C.setLayerGeometryPrecision(inputMemLayer.layer, gridSize, flags)
116 | C.setLayerGeometryPrecision(methodMemLayer.layer, gridSize, flags)
117 | }
118 | // 使用内存图层进行后续处理
119 | inputLayer = inputMemLayer
120 | methodLayer = methodMemLayer
121 | }
122 | resultLayer, err := createUpdateAnalysisResultLayer(inputLayer, methodLayer)
123 | if err != nil {
124 | return nil, fmt.Errorf("创建结果图层失败: %v", err)
125 | }
126 | taskid := uuid.New().String()
127 | //对A B图层进行分块,并创建bin文件
128 | GenerateTiles(inputLayer, methodLayer, config.TileCount, taskid)
129 | //读取文件列表,并发执行操作
130 | GPbins, err := ReadAndGroupBinFiles(taskid)
131 | if err != nil {
132 | return nil, fmt.Errorf("提取分组文件失败: %v", err)
133 | }
134 | // 并发执行分析
135 | err = executeConcurrentUpdateAnalysis(GPbins, resultLayer, config)
136 | if err != nil {
137 | resultLayer.Close()
138 | return nil, fmt.Errorf("并发擦除分析失败: %v", err)
139 | }
140 | // 清理临时文件
141 | defer func() {
142 | err := cleanupTileFiles(taskid)
143 | if err != nil {
144 | log.Printf("清理临时文件失败: %v", err)
145 | }
146 | }()
147 |
148 | return resultLayer, nil
149 | }
150 |
151 | func executeConcurrentUpdateAnalysis(tileGroups []GroupTileFiles, resultLayer *GDALLayer, config *ParallelGeosConfig) error {
152 | maxWorkers := config.MaxWorkers
153 | if maxWorkers <= 0 {
154 | maxWorkers = runtime.NumCPU()
155 | }
156 |
157 | totalTasks := len(tileGroups)
158 | if totalTasks == 0 {
159 | return fmt.Errorf("没有分块需要处理")
160 | }
161 |
162 | // 创建任务队列和结果队列
163 | taskQueue := make(chan GroupTileFiles, totalTasks)
164 | results := make(chan taskResult, totalTasks)
165 |
166 | // 启动固定数量的工作协程
167 | var wg sync.WaitGroup
168 | for i := 0; i < maxWorkers; i++ {
169 | wg.Add(1)
170 | go worker_update(i, taskQueue, results, config, &wg)
171 | }
172 |
173 | // 发送所有任务到队列
174 | go func() {
175 | for _, tileGroup := range tileGroups {
176 | taskQueue <- tileGroup
177 | }
178 | close(taskQueue) // 关闭任务队列,通知工作协程没有更多任务
179 | }()
180 |
181 | // 启动结果收集协程
182 | var resultWg sync.WaitGroup
183 | resultWg.Add(1)
184 | var processingError error
185 | completed := 0
186 |
187 | go func() {
188 | defer resultWg.Done()
189 |
190 | var totalDuration time.Duration
191 | var minDuration, maxDuration time.Duration
192 |
193 | for i := 0; i < totalTasks; i++ {
194 | result := <-results
195 | completed++
196 |
197 | if result.err != nil {
198 | processingError = fmt.Errorf("分块 %d 处理失败: %v", result.index, result.err)
199 | log.Printf("错误: %v", processingError)
200 | return
201 | }
202 |
203 | // 统计执行时间
204 | totalDuration += result.duration
205 | if i == 0 {
206 | minDuration = result.duration
207 | maxDuration = result.duration
208 | } else {
209 | if result.duration < minDuration {
210 | minDuration = result.duration
211 | }
212 | if result.duration > maxDuration {
213 | maxDuration = result.duration
214 | }
215 | }
216 |
217 | // 将结果合并到主图层
218 | if result.layer != nil {
219 | err := mergeResultsToMainLayer(result.layer, resultLayer)
220 | if err != nil {
221 | processingError = fmt.Errorf("合并分块 %d 结果失败: %v", result.index, err)
222 | log.Printf("错误: %v", processingError)
223 | return
224 | }
225 |
226 | // 释放临时图层资源
227 | result.layer.Close()
228 | }
229 |
230 | // 进度回调
231 | if config.ProgressCallback != nil {
232 | progress := float64(completed) / float64(totalTasks)
233 | avgDuration := totalDuration / time.Duration(completed)
234 |
235 | var memStats runtime.MemStats
236 | runtime.ReadMemStats(&memStats)
237 |
238 | message := fmt.Sprintf("已完成: %d/%d, 平均耗时: %v, 内存: %.2fMB, 协程数: %d",
239 | completed, totalTasks, avgDuration,
240 | float64(memStats.Alloc)/1024/1024, runtime.NumGoroutine())
241 |
242 | config.ProgressCallback(progress, message)
243 | }
244 |
245 | // 每处理50个任务输出一次详细统计
246 | if completed%50 == 0 || completed == totalTasks {
247 | avgDuration := totalDuration / time.Duration(completed)
248 | log.Printf("进度统计 - 已完成: %d/%d, 平均耗时: %v, 最快: %v, 最慢: %v",
249 | completed, totalTasks, avgDuration, minDuration, maxDuration)
250 | }
251 | }
252 |
253 | log.Printf("所有分块处理完成,总计: %d", completed)
254 | }()
255 |
256 | // 等待所有工作协程完成
257 | wg.Wait()
258 | close(results) // 关闭结果队列
259 |
260 | // 等待结果收集完成
261 | resultWg.Wait()
262 |
263 | if processingError != nil {
264 | return processingError
265 | }
266 |
267 | return nil
268 | }
269 | func worker_update(workerID int, taskQueue <-chan GroupTileFiles, results chan<- taskResult,
270 | config *ParallelGeosConfig, wg *sync.WaitGroup) {
271 | defer wg.Done()
272 | tasksProcessed := 0
273 |
274 | for tileGroup := range taskQueue {
275 |
276 | start := time.Now()
277 |
278 | // 处理单个分块
279 | layer, err := processTileGroupforUpdate(tileGroup, config)
280 |
281 | duration := time.Since(start)
282 |
283 | tasksProcessed++
284 |
285 | // 发送结果
286 | results <- taskResult{
287 | layer: layer,
288 | err: err,
289 | duration: duration,
290 | index: tileGroup.Index,
291 | }
292 |
293 | // 定期强制垃圾回收
294 |
295 | runtime.GC()
296 |
297 | }
298 |
299 | }
300 | func processTileGroupforUpdate(tileGroup GroupTileFiles, config *ParallelGeosConfig) (*GDALLayer, error) {
301 |
302 | // 加载layer1的bin文件
303 | inputTileLayer, err := DeserializeLayerFromFile(tileGroup.GPBin.Layer1)
304 | if err != nil {
305 | return nil, fmt.Errorf("加载输入分块文件失败: %v", err)
306 | }
307 |
308 | // 加载layer2的bin文件
309 | methodTileLayer, err := DeserializeLayerFromFile(tileGroup.GPBin.Layer2)
310 | if err != nil {
311 | return nil, fmt.Errorf("加载擦除分块文件失败: %v", err)
312 | }
313 | defer func() {
314 | inputTileLayer.Close()
315 | methodTileLayer.Close()
316 |
317 | }()
318 |
319 | // 为当前分块创建临时结果图层
320 | tileName := fmt.Sprintf("tile_result_%d", tileGroup.Index)
321 | tileResultLayer, err := createUpdateTileResultLayer(inputTileLayer, methodTileLayer, tileName)
322 | if err != nil {
323 | return nil, fmt.Errorf("创建分块结果图层失败: %v", err)
324 | }
325 |
326 | // 执行裁剪分析 - 不使用进度回调
327 | err = executeUpdateAnalysis(inputTileLayer, methodTileLayer, tileResultLayer, nil)
328 | if err != nil {
329 | tileResultLayer.Close()
330 | return nil, fmt.Errorf("执行擦除分析失败: %v", err)
331 | }
332 | return tileResultLayer, nil
333 | }
334 | func createUpdateTileResultLayer(layer1, layer2 *GDALLayer, layerName string) (*GDALLayer, error) {
335 | layerNameC := C.CString(layerName)
336 | defer C.free(unsafe.Pointer(layerNameC))
337 |
338 | // 获取空间参考系统
339 | srs := layer1.GetSpatialRef()
340 | if srs == nil {
341 | srs = layer2.GetSpatialRef()
342 | }
343 |
344 | // 创建结果图层
345 | resultLayerPtr := C.createMemoryLayer(layerNameC, C.wkbMultiPolygon, srs)
346 | if resultLayerPtr == nil {
347 | return nil, fmt.Errorf("创建结果图层失败")
348 | }
349 |
350 | resultLayer := &GDALLayer{layer: resultLayerPtr}
351 | runtime.SetFinalizer(resultLayer, (*GDALLayer).cleanup)
352 |
353 | // 添加字段定义 - 使用默认策略(合并字段,带前缀区分来源)
354 | err := addUpdateFields(resultLayer, layer1, layer2)
355 | if err != nil {
356 | resultLayer.Close()
357 | return nil, fmt.Errorf("添加字段失败: %v", err)
358 | }
359 | return resultLayer, nil
360 | }
361 |
362 | // createUpdateAnalysisResultLayer 创建更新分析结果图层
363 | func createUpdateAnalysisResultLayer(inputLayer, updateLayer *GDALLayer) (*GDALLayer, error) {
364 | layerName := C.CString("update_result")
365 | defer C.free(unsafe.Pointer(layerName))
366 |
367 | // 获取空间参考系统
368 | srs := inputLayer.GetSpatialRef()
369 | if srs == nil {
370 | srs = updateLayer.GetSpatialRef()
371 | }
372 |
373 | // 创建结果图层
374 | resultLayerPtr := C.createMemoryLayer(layerName, C.wkbMultiPolygon, srs)
375 | if resultLayerPtr == nil {
376 | return nil, fmt.Errorf("创建结果图层失败")
377 | }
378 |
379 | resultLayer := &GDALLayer{layer: resultLayerPtr}
380 | runtime.SetFinalizer(resultLayer, (*GDALLayer).cleanup)
381 |
382 | // 添加字段定义
383 | err := addUpdateFields(resultLayer, inputLayer, updateLayer)
384 | if err != nil {
385 | resultLayer.Close()
386 | return nil, fmt.Errorf("添加字段失败: %v", err)
387 | }
388 |
389 | return resultLayer, nil
390 | }
391 |
392 | // addUpdateFields 添加更新分析的字段
393 | func addUpdateFields(resultLayer, inputLayer, updateLayer *GDALLayer) error {
394 |
395 | // 合并两个图层的字段(不使用前缀)
396 | err1 := addLayerFields(resultLayer, inputLayer, "")
397 | if err1 != nil {
398 | return fmt.Errorf("添加输入图层字段失败: %v", err1)
399 | }
400 |
401 | err2 := addLayerFields(resultLayer, updateLayer, "")
402 | if err2 != nil {
403 | return fmt.Errorf("添加更新图层字段失败: %v", err2)
404 | }
405 |
406 | return nil
407 | }
408 |
409 | // executeUpdateAnalysis 执行更新分析
410 | func executeUpdateAnalysis(inputLayer, updateLayer, resultLayer *GDALLayer,
411 | progressCallback ProgressCallback) error {
412 | // 设置GDAL选项
413 | var options **C.char
414 | defer func() {
415 | if options != nil {
416 | C.CSLDestroy(options)
417 | }
418 | }()
419 |
420 | skipFailuresOpt := C.CString("SKIP_FAILURES=YES")
421 | promoteToMultiOpt := C.CString("PROMOTE_TO_MULTI=YES")
422 | keepLowerDimOpt := C.CString("KEEP_LOWER_DIMENSION_GEOMETRIES=NO")
423 | defer C.free(unsafe.Pointer(skipFailuresOpt))
424 | defer C.free(unsafe.Pointer(promoteToMultiOpt))
425 | defer C.free(unsafe.Pointer(keepLowerDimOpt))
426 |
427 | options = C.CSLAddString(options, skipFailuresOpt)
428 | options = C.CSLAddString(options, promoteToMultiOpt)
429 | options = C.CSLAddString(options, keepLowerDimOpt)
430 |
431 | // 执行更新操作
432 | return executeGDALUpdateWithProgress(inputLayer, updateLayer, resultLayer, options, progressCallback)
433 | }
434 |
435 | // executeGDALUpdateWithProgress 执行带进度的GDAL更新操作
436 | func executeGDALUpdateWithProgress(inputLayer, updateLayer, resultLayer *GDALLayer, options **C.char, progressCallback ProgressCallback) error {
437 | var progressData *ProgressData
438 | var progressArg unsafe.Pointer
439 |
440 | // 设置进度回调
441 | if progressCallback != nil {
442 | progressData = &ProgressData{
443 | callback: progressCallback,
444 | cancelled: false,
445 | }
446 | progressArg = unsafe.Pointer(uintptr(unsafe.Pointer(progressData)))
447 |
448 | progressDataMutex.Lock()
449 | progressDataMap[uintptr(progressArg)] = progressData
450 | progressDataMutex.Unlock()
451 |
452 | defer func() {
453 | progressDataMutex.Lock()
454 | delete(progressDataMap, uintptr(progressArg))
455 | progressDataMutex.Unlock()
456 | }()
457 | }
458 |
459 | // 调用GDAL的更新函数
460 | var err C.OGRErr
461 | if progressCallback != nil {
462 | err = C.performUpdateWithProgress(inputLayer.layer, updateLayer.layer, resultLayer.layer, options, progressArg)
463 | } else {
464 | err = C.OGR_L_Update(inputLayer.layer, updateLayer.layer, resultLayer.layer, options, nil, nil)
465 | }
466 |
467 | if err != C.OGRERR_NONE {
468 | return fmt.Errorf("GDAL更新操作失败,错误代码: %d", int(err))
469 | }
470 |
471 | return nil
472 | }
473 |
--------------------------------------------------------------------------------
/SHPEdit.go:
--------------------------------------------------------------------------------
1 | package Gogeo
2 |
3 | /*
4 | #include "osgeo_utils.h"
5 | */
6 | import "C"
7 | import (
8 | "fmt"
9 | "unsafe"
10 | )
11 |
12 | // DeleteShapeFeatureByFID 删除Shapefile图层中指定FID的要素
13 | // shpPath: Shapefile文件路径(.shp文件)
14 | // fid: 要删除的要素ID
15 | func DeleteShapeFeatureByFID(shpPath string, fid int64) error {
16 | // 初始化GDAL
17 | InitializeGDAL()
18 |
19 | cFilePath := C.CString(shpPath)
20 | defer C.free(unsafe.Pointer(cFilePath))
21 |
22 | // 以可写模式打开Shapefile
23 | dataset := C.OGROpen(cFilePath, C.int(1), nil) // 1表示可写
24 | if dataset == nil {
25 | return fmt.Errorf("无法以可写模式打开Shapefile: %s", shpPath)
26 | }
27 | defer C.OGR_DS_Destroy(dataset)
28 |
29 | // 获取第一个图层(Shapefile通常只有一个图层)
30 | layer := C.OGR_DS_GetLayer(dataset, 0)
31 | if layer == nil {
32 | return fmt.Errorf("无法获取Shapefile图层")
33 | }
34 |
35 | // 检查图层是否支持删除操作
36 | cDeleteCap := C.CString("DeleteFeature")
37 | defer C.free(unsafe.Pointer(cDeleteCap))
38 |
39 | if C.OGR_L_TestCapability(layer, cDeleteCap) == 0 {
40 | return fmt.Errorf("Shapefile图层不支持删除要素操作")
41 | }
42 |
43 | // 删除要素
44 | result := C.OGR_L_DeleteFeature(layer, C.GIntBig(fid))
45 | if result != C.OGRERR_NONE {
46 | return fmt.Errorf("删除要素失败,FID=%d,错误代码: %d", fid, int(result))
47 | }
48 |
49 | // 同步更改到磁盘
50 | syncResult := C.OGR_L_SyncToDisk(layer)
51 | if syncResult != C.OGRERR_NONE {
52 | return fmt.Errorf("同步到磁盘失败,错误代码: %d", int(syncResult))
53 | }
54 |
55 | fmt.Printf("成功删除Shapefile中 FID=%d 的要素\n", fid)
56 | return nil
57 | }
58 |
59 | // DeleteShapeFeaturesByFilter 根据SQL过滤条件删除Shapefile中的多个要素
60 | // shpPath: Shapefile文件路径
61 | // whereClause: SQL WHERE条件,如 "ID > 100" 或 "NAME = 'test'"
62 | func DeleteShapeFeaturesByFilter(shpPath string, whereClause string) (int, error) {
63 | // 初始化GDAL
64 | InitializeGDAL()
65 |
66 | cFilePath := C.CString(shpPath)
67 | defer C.free(unsafe.Pointer(cFilePath))
68 |
69 | // 以可写模式打开数据源
70 | dataset := C.OGROpen(cFilePath, C.int(1), nil)
71 | if dataset == nil {
72 | return 0, fmt.Errorf("无法以可写模式打开Shapefile: %s", shpPath)
73 | }
74 | defer C.OGR_DS_Destroy(dataset)
75 |
76 | // 获取图层
77 | layer := C.OGR_DS_GetLayer(dataset, 0)
78 | if layer == nil {
79 | return 0, fmt.Errorf("无法获取Shapefile图层")
80 | }
81 |
82 | // 检查图层是否支持删除操作
83 | cDeleteCap := C.CString("DeleteFeature")
84 | defer C.free(unsafe.Pointer(cDeleteCap))
85 |
86 | if C.OGR_L_TestCapability(layer, cDeleteCap) == 0 {
87 | return 0, fmt.Errorf("Shapefile图层不支持删除要素操作")
88 | }
89 |
90 | // 设置属性过滤器
91 | cWhereClause := C.CString(whereClause)
92 | defer C.free(unsafe.Pointer(cWhereClause))
93 |
94 | result := C.OGR_L_SetAttributeFilter(layer, cWhereClause)
95 | if result != C.OGRERR_NONE {
96 | return 0, fmt.Errorf("设置属性过滤器失败: %s", whereClause)
97 | }
98 |
99 | // 收集所有要删除的FID
100 | var fidsToDelete []C.GIntBig
101 | C.OGR_L_ResetReading(layer)
102 |
103 | for {
104 | feature := C.OGR_L_GetNextFeature(layer)
105 | if feature == nil {
106 | break
107 | }
108 | fid := C.OGR_F_GetFID(feature)
109 | fidsToDelete = append(fidsToDelete, fid)
110 | C.OGR_F_Destroy(feature)
111 | }
112 |
113 | // 清除过滤器
114 | C.OGR_L_SetAttributeFilter(layer, nil)
115 |
116 | if len(fidsToDelete) == 0 {
117 | return 0, fmt.Errorf("未找到符合条件的要素: %s", whereClause)
118 | }
119 |
120 | // 删除收集到的要素
121 | deletedCount := 0
122 | for _, fid := range fidsToDelete {
123 | deleteResult := C.OGR_L_DeleteFeature(layer, fid)
124 | if deleteResult == C.OGRERR_NONE {
125 | deletedCount++
126 | } else {
127 | fmt.Printf("警告: 删除FID=%d失败\n", int64(fid))
128 | }
129 | }
130 |
131 | // 同步更改到磁盘
132 | syncResult := C.OGR_L_SyncToDisk(layer)
133 | if syncResult != C.OGRERR_NONE {
134 | return deletedCount, fmt.Errorf("同步到磁盘失败,错误代码: %d", int(syncResult))
135 | }
136 |
137 | fmt.Printf("成功删除 %d 个要素\n", deletedCount)
138 | return deletedCount, nil
139 | }
140 |
141 | // InsertLayerToShapefile 将GDALLayer插入到Shapefile中,并进行坐标转换
142 | // sourceLayer: 源图层(4326坐标系)
143 | // shpPath: 目标Shapefile文件路径
144 | // options: 插入选项(可选)
145 | func InsertLayerToShapefile(sourceLayer *GDALLayer, shpPath string, options *InsertOptions) error {
146 | if sourceLayer == nil || sourceLayer.layer == nil {
147 | return fmt.Errorf("源图层为空")
148 | }
149 |
150 | // 初始化GDAL
151 | InitializeGDAL()
152 |
153 | cFilePath := C.CString(shpPath)
154 | defer C.free(unsafe.Pointer(cFilePath))
155 |
156 | // 以可写模式打开Shapefile数据源
157 | targetDataset := C.OGROpen(cFilePath, C.int(1), nil)
158 | if targetDataset == nil {
159 | return fmt.Errorf("无法以可写模式打开Shapefile: %s", shpPath)
160 | }
161 | defer C.OGR_DS_Destroy(targetDataset)
162 |
163 | // 获取目标图层(Shapefile只有一个图层)
164 | targetLayer := C.OGR_DS_GetLayer(targetDataset, 0)
165 | if targetLayer == nil {
166 | return fmt.Errorf("无法获取Shapefile图层")
167 | }
168 |
169 | // 获取源图层的空间参考(假设为4326)
170 | sourceSRS := C.OGR_L_GetSpatialRef(sourceLayer.layer)
171 | if sourceSRS == nil {
172 | // 如果源图层没有空间参考,创建4326空间参考
173 | sourceSRS = C.OSRNewSpatialReference(nil)
174 | defer C.OSRDestroySpatialReference(sourceSRS)
175 | C.OSRImportFromEPSG(sourceSRS, 4326)
176 | }
177 |
178 | // 获取目标图层的空间参考
179 | targetSRS := C.OGR_L_GetSpatialRef(targetLayer)
180 | if targetSRS == nil {
181 | return fmt.Errorf("目标Shapefile没有空间参考系统")
182 | }
183 |
184 | // 创建坐标转换对象
185 | var transform C.OGRCoordinateTransformationH
186 | needTransform := C.OSRIsSame(sourceSRS, targetSRS) == 0
187 |
188 | if needTransform {
189 | transform = C.OCTNewCoordinateTransformation(sourceSRS, targetSRS)
190 | if transform == nil {
191 | return fmt.Errorf("无法创建坐标转换对象")
192 | }
193 | defer C.OCTDestroyCoordinateTransformation(transform)
194 | }
195 |
196 | // 获取图层定义
197 | targetLayerDefn := C.OGR_L_GetLayerDefn(targetLayer)
198 | sourceLayerDefn := C.OGR_L_GetLayerDefn(sourceLayer.layer)
199 |
200 | // 创建字段映射
201 | fieldMapping, err := createFieldMapping(sourceLayerDefn, targetLayerDefn)
202 | if err != nil {
203 | return fmt.Errorf("创建字段映射失败: %v", err)
204 | }
205 |
206 | // 开始事务(Shapefile通常不支持事务,但尝试一下)
207 | cTransactionCap := C.CString("Transactions")
208 | defer C.free(unsafe.Pointer(cTransactionCap))
209 |
210 | useTransaction := C.OGR_L_TestCapability(targetLayer, cTransactionCap) != 0
211 | if useTransaction {
212 | result := C.OGR_L_StartTransaction(targetLayer)
213 | if result != C.OGRERR_NONE {
214 | useTransaction = false
215 | }
216 | }
217 |
218 | // 遍历源图层的所有要素
219 | C.OGR_L_ResetReading(sourceLayer.layer)
220 |
221 | insertedCount := 0
222 | failedCount := 0
223 |
224 | for {
225 | sourceFeature := C.OGR_L_GetNextFeature(sourceLayer.layer)
226 | if sourceFeature == nil {
227 | break
228 | }
229 |
230 | // 创建新要素
231 | targetFeature := C.OGR_F_Create(targetLayerDefn)
232 | if targetFeature == nil {
233 | C.OGR_F_Destroy(sourceFeature)
234 | failedCount++
235 | continue
236 | }
237 |
238 | // 复制并转换几何
239 | sourceGeom := C.OGR_F_GetGeometryRef(sourceFeature)
240 | if sourceGeom != nil {
241 | // 克隆几何对象
242 | clonedGeom := C.OGR_G_Clone(sourceGeom)
243 | if clonedGeom != nil {
244 | // 检查几何有效性(可选)
245 | if options != nil && options.SkipInvalidGeometry {
246 | if C.OGR_G_IsValid(clonedGeom) == 0 {
247 | fmt.Printf("警告: 几何无效,跳过该要素\n")
248 | C.OGR_G_DestroyGeometry(clonedGeom)
249 | C.OGR_F_Destroy(targetFeature)
250 | C.OGR_F_Destroy(sourceFeature)
251 | failedCount++
252 | continue
253 | }
254 | }
255 |
256 | // 执行坐标转换
257 | if needTransform && transform != nil {
258 | transformResult := C.OGR_G_Transform(clonedGeom, transform)
259 | if transformResult != C.OGRERR_NONE {
260 | fmt.Printf("警告: 几何转换失败,跳过该要素\n")
261 | C.OGR_G_DestroyGeometry(clonedGeom)
262 | C.OGR_F_Destroy(targetFeature)
263 | C.OGR_F_Destroy(sourceFeature)
264 | failedCount++
265 | continue
266 | }
267 | }
268 |
269 | // 设置几何到目标要素
270 | C.OGR_F_SetGeometry(targetFeature, clonedGeom)
271 | C.OGR_G_DestroyGeometry(clonedGeom)
272 | }
273 | }
274 |
275 | // 复制属性字段
276 | err := copyFeatureFields(sourceFeature, targetFeature, fieldMapping)
277 | if err != nil && options != nil && options.StrictMode {
278 | fmt.Printf("警告: 复制字段失败: %v\n", err)
279 | C.OGR_F_Destroy(targetFeature)
280 | C.OGR_F_Destroy(sourceFeature)
281 | failedCount++
282 | continue
283 | }
284 |
285 | // 插入要素到目标图层
286 | result := C.OGR_L_CreateFeature(targetLayer, targetFeature)
287 | if result == C.OGRERR_NONE {
288 | insertedCount++
289 | } else {
290 | failedCount++
291 | fmt.Printf("警告: 插入要素失败,错误代码: %d\n", int(result))
292 | }
293 |
294 | C.OGR_F_Destroy(targetFeature)
295 | C.OGR_F_Destroy(sourceFeature)
296 |
297 | // 定期同步(可选,提高性能)
298 | if options != nil && options.SyncInterval > 0 && insertedCount%options.SyncInterval == 0 {
299 | C.OGR_L_SyncToDisk(targetLayer)
300 | }
301 | }
302 |
303 | // 提交事务
304 | if useTransaction {
305 | commitResult := C.OGR_L_CommitTransaction(targetLayer)
306 | if commitResult != C.OGRERR_NONE {
307 | C.OGR_L_RollbackTransaction(targetLayer)
308 | return fmt.Errorf("提交事务失败")
309 | }
310 | }
311 |
312 | // 最终同步到磁盘
313 | C.OGR_L_SyncToDisk(targetLayer)
314 |
315 | fmt.Printf("插入完成: 成功 %d 个,失败 %d 个\n", insertedCount, failedCount)
316 |
317 | if failedCount > 0 && options != nil && options.StrictMode {
318 | return fmt.Errorf("部分要素插入失败: %d/%d", failedCount, insertedCount+failedCount)
319 | }
320 |
321 | return nil
322 | }
323 |
324 | // PackShapefile 压缩Shapefile以回收删除要素后的空间
325 | // shpPath: Shapefile文件路径
326 | // 注意:此操作会重建Shapefile,FID可能会改变
327 | func PackShapefile(shpPath string) error {
328 | InitializeGDAL()
329 |
330 | cFilePath := C.CString(shpPath)
331 | defer C.free(unsafe.Pointer(cFilePath))
332 |
333 | // 以可写模式打开
334 | dataset := C.OGROpen(cFilePath, C.int(1), nil)
335 | if dataset == nil {
336 | return fmt.Errorf("无法打开Shapefile: %s", shpPath)
337 | }
338 | defer C.OGR_DS_Destroy(dataset)
339 |
340 | layer := C.OGR_DS_GetLayer(dataset, 0)
341 | if layer == nil {
342 | return fmt.Errorf("无法获取图层")
343 | }
344 |
345 | // 执行SQL REPACK命令(如果驱动支持)
346 | cSQL := C.CString("REPACK " + shpPath)
347 | defer C.free(unsafe.Pointer(cSQL))
348 |
349 | result := C.OGR_DS_ExecuteSQL(dataset, cSQL, nil, nil)
350 | if result != nil {
351 | C.OGR_DS_ReleaseResultSet(dataset, result)
352 | }
353 |
354 | // 同步到磁盘
355 | C.OGR_L_SyncToDisk(layer)
356 |
357 | fmt.Printf("Shapefile压缩完成: %s\n", shpPath)
358 | return nil
359 | }
360 |
361 | // EnsureObjectIDField 确保shp文件包含objectid字段(不区分大小写)
362 | // 如果不存在,则创建该字段并填充唯一值
363 | // shpPath: Shapefile文件路径
364 | // 返回: 是否创建了新字段, error
365 | func EnsureObjectIDField(shpPath string) (bool, error) {
366 | // 初始化GDAL
367 | InitializeGDAL()
368 |
369 | cFilePath := C.CString(shpPath)
370 | defer C.free(unsafe.Pointer(cFilePath))
371 |
372 | // 以可写模式打开Shapefile
373 | dataset := C.OGROpen(cFilePath, C.int(1), nil) // 1表示可写
374 | if dataset == nil {
375 | return false, fmt.Errorf("无法以可写模式打开Shapefile: %s", shpPath)
376 | }
377 | defer C.OGR_DS_Destroy(dataset)
378 |
379 | // 获取第一个图层(Shapefile通常只有一个图层)
380 | layer := C.OGR_DS_GetLayer(dataset, 0)
381 | if layer == nil {
382 | return false, fmt.Errorf("无法获取Shapefile图层")
383 | }
384 |
385 | // 获取图层定义
386 | layerDefn := C.OGR_L_GetLayerDefn(layer)
387 | if layerDefn == nil {
388 | return false, fmt.Errorf("无法获取图层定义")
389 | }
390 |
391 | // 检查是否已存在objectid字段(不区分大小写)
392 | fieldCount := int(C.OGR_FD_GetFieldCount(layerDefn))
393 | objectIDFieldIndex := -1
394 |
395 | for i := 0; i < fieldCount; i++ {
396 | fieldDefn := C.OGR_FD_GetFieldDefn(layerDefn, C.int(i))
397 | if fieldDefn == nil {
398 | continue
399 | }
400 |
401 | fieldName := C.GoString(C.OGR_Fld_GetNameRef(fieldDefn))
402 | // 不区分大小写比较
403 | if len(fieldName) == 8 &&
404 | (fieldName == "objectid" || fieldName == "OBJECTID" ||
405 | fieldName == "ObjectID" || fieldName == "ObjectId" ||
406 | fieldName == "objectId" || fieldName == "Objectid") {
407 | objectIDFieldIndex = i
408 | fmt.Printf("找到已存在的ObjectID字段: %s (索引: %d)\n", fieldName, i)
409 | break
410 | }
411 | }
412 |
413 | // 如果字段已存在,不需要创建
414 | if objectIDFieldIndex >= 0 {
415 | fmt.Println("ObjectID字段已存在,无需创建")
416 | return false, nil
417 | }
418 |
419 | // 检查图层是否支持字段创建
420 | if C.OGR_L_TestCapability(layer, C.CString("CreateField")) == 0 {
421 | return false, fmt.Errorf("图层不支持创建字段操作")
422 | }
423 |
424 | // 创建新的objectid字段定义
425 | cFieldName := C.CString("objectid")
426 | defer C.free(unsafe.Pointer(cFieldName))
427 |
428 | fieldDefn := C.OGR_Fld_Create(cFieldName, C.OFTInteger)
429 | if fieldDefn == nil {
430 | return false, fmt.Errorf("无法创建字段定义")
431 | }
432 | defer C.OGR_Fld_Destroy(fieldDefn)
433 |
434 | // 设置字段宽度(可选)
435 | C.OGR_Fld_SetWidth(fieldDefn, 10)
436 |
437 | // 添加字段到图层
438 | result := C.OGR_L_CreateField(layer, fieldDefn, C.int(1)) // 1表示强制创建
439 | if result != C.OGRERR_NONE {
440 | return false, fmt.Errorf("创建objectid字段失败,错误代码: %d", int(result))
441 | }
442 |
443 | fmt.Println("成功创建objectid字段")
444 |
445 | // 开始事务(如果支持)
446 | useTransaction := C.OGR_L_TestCapability(layer, C.CString("Transactions")) != 0
447 | if useTransaction {
448 | transResult := C.OGR_L_StartTransaction(layer)
449 | if transResult != C.OGRERR_NONE {
450 | useTransaction = false
451 | fmt.Println("警告: 无法开始事务,将直接更新")
452 | }
453 | }
454 |
455 | // 重新获取图层定义(因为添加了新字段)
456 | layerDefn = C.OGR_L_GetLayerDefn(layer)
457 |
458 | // 获取新创建字段的索引
459 | newFieldIndex := C.OGR_FD_GetFieldIndex(layerDefn, cFieldName)
460 | if newFieldIndex < 0 {
461 | return false, fmt.Errorf("无法找到新创建的objectid字段")
462 | }
463 |
464 | // 遍历所有要素,填充唯一的objectid值
465 | C.OGR_L_ResetReading(layer)
466 |
467 | objectIDValue := 1
468 | updatedCount := 0
469 | failedCount := 0
470 |
471 | for {
472 | feature := C.OGR_L_GetNextFeature(layer)
473 | if feature == nil {
474 | break
475 | }
476 |
477 | // 设置objectid字段值
478 | C.OGR_F_SetFieldInteger(feature, newFieldIndex, C.int(objectIDValue))
479 |
480 | // 更新要素
481 | updateResult := C.OGR_L_SetFeature(layer, feature)
482 | if updateResult == C.OGRERR_NONE {
483 | updatedCount++
484 | objectIDValue++
485 | } else {
486 | failedCount++
487 | fmt.Printf("警告: 更新要素失败,FID=%d, 错误代码: %d\n",
488 | int64(C.OGR_F_GetFID(feature)), int(updateResult))
489 | }
490 |
491 | C.OGR_F_Destroy(feature)
492 | }
493 |
494 | // 提交事务
495 | if useTransaction {
496 | commitResult := C.OGR_L_CommitTransaction(layer)
497 | if commitResult != C.OGRERR_NONE {
498 | C.OGR_L_RollbackTransaction(layer)
499 | return false, fmt.Errorf("提交事务失败")
500 | }
501 | }
502 |
503 | // 同步到磁盘
504 | syncResult := C.OGR_L_SyncToDisk(layer)
505 | if syncResult != C.OGRERR_NONE {
506 | return false, fmt.Errorf("同步到磁盘失败,错误代码: %d", int(syncResult))
507 | }
508 |
509 | fmt.Printf("ObjectID字段填充完成: 成功 %d 个,失败 %d 个\n", updatedCount, failedCount)
510 |
511 | if failedCount > 0 {
512 | return true, fmt.Errorf("部分要素更新失败: %d/%d", failedCount, updatedCount+failedCount)
513 | }
514 |
515 | return true, nil
516 | }
517 |
--------------------------------------------------------------------------------
/RasterClip.go:
--------------------------------------------------------------------------------
1 | package Gogeo
2 |
3 | /*
4 | #include "osgeo_utils.h"
5 | #include
6 | */
7 | import "C"
8 |
9 | import (
10 | "fmt"
11 | "os"
12 | "path/filepath"
13 | "strings"
14 | "unsafe"
15 | )
16 |
17 | // ClipOptions 裁剪选项
18 | type ClipOptions struct {
19 | OutputDir string // 输出目录
20 | NameField string // 名称字段(默认 "NAME")
21 | JPEGQuality int // JPEG质量 (1-100,默认85)
22 | TileSize int // 输出瓦片大小(像素,0表示原始分辨率)
23 | BufferDist float64 // 缓冲距离(单位:米,0表示不缓冲)
24 | OverwriteExisting bool // 是否覆盖已存在的文件
25 | ImageFormat string
26 | }
27 |
28 | // ClipResult 裁剪结果
29 | type ClipResult struct {
30 | Name string
31 | OutputPath string
32 | Bounds [4]float64 // minX, minY, maxX, maxY
33 | Width int
34 | Height int
35 | Error error
36 | }
37 |
38 | func (rd *RasterDataset) GetActiveDataset() C.GDALDatasetH {
39 | if rd.warpedDS != nil {
40 | return rd.warpedDS
41 | }
42 | return rd.dataset
43 | }
44 |
45 | // ClipRasterByLayer 使用矢量图层裁剪栅格数据
46 | func (rd *RasterDataset) ClipRasterByLayer(layer *GDALLayer, options *ClipOptions) ([]ClipResult, error) {
47 | if layer == nil || layer.layer == nil {
48 | return nil, fmt.Errorf("invalid layer")
49 | }
50 |
51 | // 设置默认选项
52 | if options == nil {
53 | options = &ClipOptions{}
54 | }
55 | if options.NameField == "" {
56 | options.NameField = "NAME"
57 | }
58 | if options.JPEGQuality <= 0 || options.JPEGQuality > 100 {
59 | options.JPEGQuality = 85
60 | }
61 | if options.OutputDir == "" {
62 | options.OutputDir = "./output"
63 | }
64 |
65 | // 创建输出目录
66 | if err := os.MkdirAll(options.OutputDir, 0755); err != nil {
67 | return nil, fmt.Errorf("failed to create output directory: %w", err)
68 | }
69 |
70 | // ✅ 获取正确的数据集
71 | activeDS := rd.GetActiveDataset()
72 |
73 | // 重置图层读取
74 | C.OGR_L_ResetReading(layer.layer)
75 |
76 | // 获取要素数量
77 | featureCount := int(C.OGR_L_GetFeatureCount(layer.layer, 1))
78 | results := make([]ClipResult, 0, featureCount)
79 |
80 | // 获取名称字段索引
81 | cFieldName := C.CString(options.NameField)
82 | defer C.free(unsafe.Pointer(cFieldName))
83 |
84 | // 遍历所有要素
85 | for {
86 | feature := C.OGR_L_GetNextFeature(layer.layer)
87 | if feature == nil {
88 | break
89 | }
90 |
91 | // ✅ 传入正确的数据集
92 | result := rd.clipByFeature(feature, activeDS, options, cFieldName)
93 | results = append(results, result)
94 |
95 | C.OGR_F_Destroy(feature)
96 | }
97 |
98 | return results, nil
99 | }
100 |
101 | // clipByFeature 裁剪单个要素
102 | func (rd *RasterDataset) clipByFeature(
103 | feature C.OGRFeatureH,
104 | srcDS C.GDALDatasetH, // ✅ 新增参数
105 | options *ClipOptions,
106 | cFieldName *C.char,
107 | ) ClipResult {
108 | result := ClipResult{}
109 |
110 | // 获取名称字段
111 | fieldIndex := C.OGR_F_GetFieldIndex(feature, cFieldName)
112 | if fieldIndex < 0 {
113 | result.Error = fmt.Errorf("field '%s' not found", options.NameField)
114 | return result
115 | }
116 |
117 | namePtr := C.OGR_F_GetFieldAsString(feature, fieldIndex)
118 | if namePtr == nil {
119 | result.Error = fmt.Errorf("failed to get field value")
120 | return result
121 | }
122 | result.Name = C.GoString(namePtr)
123 |
124 | // 清理文件名
125 | result.Name = sanitizeFilename(result.Name)
126 | if result.Name == "" {
127 | result.Name = fmt.Sprintf("feature_%d", C.OGR_F_GetFID(feature))
128 | }
129 |
130 | // 构建输出路径
131 | result.OutputPath = filepath.Join(options.OutputDir, result.Name+"."+options.ImageFormat)
132 |
133 | // 检查文件是否已存在
134 | if !options.OverwriteExisting {
135 | if _, err := os.Stat(result.OutputPath); err == nil {
136 | result.Error = fmt.Errorf("file already exists: %s", result.OutputPath)
137 | return result
138 | }
139 | }
140 |
141 | // 获取几何体
142 | geom := C.OGR_F_GetGeometryRef(feature)
143 | if geom == nil {
144 | result.Error = fmt.Errorf("feature has no geometry")
145 | return result
146 | }
147 |
148 | // 应用缓冲区(如果需要)
149 | if options.BufferDist > 0 {
150 | geom = C.OGR_G_Buffer(geom, C.double(options.BufferDist), 30)
151 | defer C.OGR_G_DestroyGeometry(geom)
152 | }
153 |
154 | // ✅ 使用传入的数据集进行裁剪
155 | var bounds [4]C.double
156 | clippedDS := C.clipRasterByGeometry(srcDS, geom, &bounds[0])
157 | if clippedDS == nil {
158 | result.Error = fmt.Errorf("failed to clip raster")
159 | return result
160 | }
161 | defer C.GDALClose(clippedDS)
162 |
163 | // 保存边界信息
164 | result.Bounds = [4]float64{
165 | float64(bounds[0]),
166 | float64(bounds[1]),
167 | float64(bounds[2]),
168 | float64(bounds[3]),
169 | }
170 |
171 | // 获取裁剪后的尺寸
172 | result.Width = int(C.GDALGetRasterXSize(clippedDS))
173 | result.Height = int(C.GDALGetRasterYSize(clippedDS))
174 |
175 | // 如果需要调整大小
176 | var outputDS C.GDALDatasetH = clippedDS
177 | if options.TileSize > 0 {
178 | outputDS = rd.resizeDataset(clippedDS, options.TileSize)
179 | if outputDS == nil {
180 | result.Error = fmt.Errorf("failed to resize dataset")
181 | return result
182 | }
183 | defer C.GDALClose(outputDS)
184 | result.Width = options.TileSize
185 | result.Height = options.TileSize
186 | }
187 |
188 | // 写入JPEG
189 | cOutputPath := C.CString(result.OutputPath)
190 | defer C.free(unsafe.Pointer(cOutputPath))
191 |
192 | success := C.writeJPEG(outputDS, cOutputPath, C.int(options.JPEGQuality))
193 | if success == 0 {
194 | result.Error = fmt.Errorf("failed to write JPEG: %s", result.OutputPath)
195 | return result
196 | }
197 |
198 | return result
199 | }
200 |
201 | // resizeDataset 调整数据集大小
202 | func (rd *RasterDataset) resizeDataset(srcDS C.GDALDatasetH, size int) C.GDALDatasetH {
203 | driver := C.GDALGetDriverByName(C.CString("MEM"))
204 | if driver == nil {
205 | return nil
206 | }
207 |
208 | bandCount := int(C.GDALGetRasterCount(srcDS))
209 |
210 | // 创建内存数据集
211 | dstDS := C.GDALCreate(driver, C.CString(""), C.int(size), C.int(size),
212 | C.int(bandCount), C.GDT_Byte, nil)
213 | if dstDS == nil {
214 | return nil
215 | }
216 |
217 | // 复制地理变换和投影
218 | var geoTransform [6]C.double
219 | if C.GDALGetGeoTransform(srcDS, &geoTransform[0]) == C.CE_None {
220 | C.GDALSetGeoTransform(dstDS, &geoTransform[0])
221 | }
222 |
223 | projection := C.GDALGetProjectionRef(srcDS)
224 | if projection != nil {
225 | C.GDALSetProjection(dstDS, projection)
226 | }
227 |
228 | // 重采样
229 | C.GDALReprojectImage(srcDS, nil, dstDS, nil, C.GRA_Bilinear, 0, 0, nil, nil, nil)
230 |
231 | return dstDS
232 | }
233 |
234 | // sanitizeFilename 清理文件名
235 | func sanitizeFilename(name string) string {
236 | // 移除或替换非法字符
237 | replacer := strings.NewReplacer(
238 | "/", "_",
239 | "\\", "_",
240 | ":", "_",
241 | "*", "_",
242 | "?", "_",
243 | "\"", "_",
244 | "<", "_",
245 | ">", "_",
246 | "|", "_",
247 | " ", "_",
248 | )
249 | name = replacer.Replace(name)
250 |
251 | // 移除开头和结尾的点和空格
252 | name = strings.Trim(name, ". ")
253 |
254 | return name
255 | }
256 |
257 | // ClipResultByte 裁剪结果(二进制数据版本)
258 | type ClipResultByte struct {
259 | Name string
260 | ImageData []byte // 图片二进制数据
261 | Bounds [4]float64 // minX, minY, maxX, maxY
262 | Width int
263 | Height int
264 | Error error
265 | }
266 |
267 | // ClipRasterByLayerByte 使用矢量图层裁剪栅格数据并返回二进制数据
268 | func (rd *RasterDataset) ClipRasterByLayerByte(layer *GDALLayer, options *ClipOptions) ([]ClipResultByte, error) {
269 | if layer == nil || layer.layer == nil {
270 | return nil, fmt.Errorf("invalid layer")
271 | }
272 |
273 | // 设置默认选项
274 | if options == nil {
275 | options = &ClipOptions{}
276 | }
277 | if options.NameField == "" {
278 | options.NameField = "NAME"
279 | }
280 | if options.JPEGQuality <= 0 || options.JPEGQuality > 100 {
281 | options.JPEGQuality = 85
282 | }
283 | if options.ImageFormat == "" {
284 | options.ImageFormat = "JPEG"
285 | }
286 |
287 | // 获取正确的数据集
288 | activeDS := rd.GetActiveDataset()
289 |
290 | // 重置图层读取
291 | C.OGR_L_ResetReading(layer.layer)
292 |
293 | // 获取要素数量
294 | featureCount := int(C.OGR_L_GetFeatureCount(layer.layer, 1))
295 | results := make([]ClipResultByte, 0, featureCount)
296 |
297 | // 获取名称字段索引
298 | cFieldName := C.CString(options.NameField)
299 | defer C.free(unsafe.Pointer(cFieldName))
300 |
301 | // 遍历所有要素
302 | for {
303 | feature := C.OGR_L_GetNextFeature(layer.layer)
304 | if feature == nil {
305 | break
306 | }
307 |
308 | result := rd.clipByFeatureToByte(feature, activeDS, options, cFieldName)
309 | results = append(results, result)
310 |
311 | C.OGR_F_Destroy(feature)
312 | }
313 |
314 | return results, nil
315 | }
316 |
317 | // clipByFeatureToByte 裁剪单个要素并返回二进制数据
318 | func (rd *RasterDataset) clipByFeatureToByte(
319 | feature C.OGRFeatureH,
320 | srcDS C.GDALDatasetH,
321 | options *ClipOptions,
322 | cFieldName *C.char,
323 | ) ClipResultByte {
324 | result := ClipResultByte{}
325 |
326 | // 获取名称字段
327 | fieldIndex := C.OGR_F_GetFieldIndex(feature, cFieldName)
328 | if fieldIndex < 0 {
329 | result.Error = fmt.Errorf("field '%s' not found", options.NameField)
330 | return result
331 | }
332 |
333 | namePtr := C.OGR_F_GetFieldAsString(feature, fieldIndex)
334 | if namePtr == nil {
335 | result.Error = fmt.Errorf("failed to get field value")
336 | return result
337 | }
338 | result.Name = C.GoString(namePtr)
339 |
340 | // 清理文件名
341 | result.Name = sanitizeFilename(result.Name)
342 | if result.Name == "" {
343 | result.Name = fmt.Sprintf("feature_%d", C.OGR_F_GetFID(feature))
344 | }
345 |
346 | // 获取几何体
347 | geom := C.OGR_F_GetGeometryRef(feature)
348 | if geom == nil {
349 | result.Error = fmt.Errorf("feature has no geometry")
350 | return result
351 | }
352 |
353 | // 应用缓冲区(如果需要)
354 | if options.BufferDist > 0 {
355 | geom = C.OGR_G_Buffer(geom, C.double(options.BufferDist), 30)
356 | defer C.OGR_G_DestroyGeometry(geom)
357 | }
358 |
359 | // 使用传入的数据集进行裁剪
360 | var bounds [4]C.double
361 | clippedDS := C.clipRasterByGeometry(srcDS, geom, &bounds[0])
362 | if clippedDS == nil {
363 | result.Error = fmt.Errorf("failed to clip raster")
364 | return result
365 | }
366 | defer C.GDALClose(clippedDS)
367 |
368 | // 保存边界信息
369 | result.Bounds = [4]float64{
370 | float64(bounds[0]),
371 | float64(bounds[1]),
372 | float64(bounds[2]),
373 | float64(bounds[3]),
374 | }
375 |
376 | // 获取裁剪后的尺寸
377 | result.Width = int(C.GDALGetRasterXSize(clippedDS))
378 | result.Height = int(C.GDALGetRasterYSize(clippedDS))
379 |
380 | // 如果需要调整大小
381 | var outputDS C.GDALDatasetH = clippedDS
382 | if options.TileSize > 0 {
383 | outputDS = rd.resizeDataset(clippedDS, options.TileSize)
384 | if outputDS == nil {
385 | result.Error = fmt.Errorf("failed to resize dataset")
386 | return result
387 | }
388 | defer C.GDALClose(outputDS)
389 | result.Width = options.TileSize
390 | result.Height = options.TileSize
391 | }
392 |
393 | // 写入到内存缓冲区
394 | cFormat := C.CString(options.ImageFormat)
395 | defer C.free(unsafe.Pointer(cFormat))
396 |
397 | imageBuffer := C.writeImageToMemory(outputDS, cFormat, C.int(options.JPEGQuality))
398 | if imageBuffer == nil {
399 | result.Error = fmt.Errorf("failed to write image to memory")
400 | return result
401 | }
402 | defer C.freeImageBuffer(imageBuffer)
403 |
404 | // 将 C 数据转换为 Go []byte
405 | if imageBuffer.size > 0 && imageBuffer.data != nil {
406 | result.ImageData = C.GoBytes(unsafe.Pointer(imageBuffer.data), C.int(imageBuffer.size))
407 | } else {
408 | result.Error = fmt.Errorf("empty image data")
409 | return result
410 | }
411 |
412 | return result
413 | }
414 |
415 | // ClipPixelRasterByLayerByte 使用矢量图层裁剪像素坐标系栅格数据并返回二进制数据
416 | // 此方法不使用gdalwarp,而是将矢量栅格化为掩膜后进行裁剪
417 | func (rd *RasterDataset) ClipPixelRasterByLayerByte(layer *GDALLayer, options *ClipOptions) ([]ClipResultByte, error) {
418 | if layer == nil || layer.layer == nil {
419 | return nil, fmt.Errorf("invalid layer")
420 | }
421 |
422 | // 检查是否为像素坐标系
423 | if rd.hasGeoInfo {
424 | return nil, fmt.Errorf("this method is only for pixel coordinate images, use ClipRasterByLayerByte for geo-referenced images")
425 | }
426 |
427 | // 设置默认选项
428 | if options == nil {
429 | options = &ClipOptions{}
430 | }
431 | if options.NameField == "" {
432 | options.NameField = "NAME"
433 | }
434 | if options.JPEGQuality <= 0 || options.JPEGQuality > 100 {
435 | options.JPEGQuality = 85
436 | }
437 | if options.ImageFormat == "" {
438 | options.ImageFormat = "JPEG"
439 | }
440 |
441 | // 获取数据集
442 | activeDS := rd.GetActiveDataset()
443 |
444 | // 重置图层读取
445 | C.OGR_L_ResetReading(layer.layer)
446 |
447 | // 获取要素数量
448 | featureCount := int(C.OGR_L_GetFeatureCount(layer.layer, 1))
449 | results := make([]ClipResultByte, 0, featureCount)
450 |
451 | // 获取名称字段索引
452 | cFieldName := C.CString(options.NameField)
453 | defer C.free(unsafe.Pointer(cFieldName))
454 |
455 | // 遍历所有要素
456 | for {
457 | feature := C.OGR_L_GetNextFeature(layer.layer)
458 | if feature == nil {
459 | break
460 | }
461 |
462 | result := rd.clipPixelByFeatureToByte(feature, activeDS, options, cFieldName)
463 | results = append(results, result)
464 |
465 | C.OGR_F_Destroy(feature)
466 | }
467 |
468 | return results, nil
469 | }
470 |
471 | // clipPixelByFeatureToByte 使用掩膜方式裁剪像素坐标系的单个要素
472 | func (rd *RasterDataset) clipPixelByFeatureToByte(
473 | feature C.OGRFeatureH,
474 | srcDS C.GDALDatasetH,
475 | options *ClipOptions,
476 | cFieldName *C.char,
477 | ) ClipResultByte {
478 | result := ClipResultByte{}
479 |
480 | // 获取名称字段
481 | fieldIndex := C.OGR_F_GetFieldIndex(feature, cFieldName)
482 | if fieldIndex < 0 {
483 | result.Error = fmt.Errorf("field '%s' not found", options.NameField)
484 | return result
485 | }
486 |
487 | namePtr := C.OGR_F_GetFieldAsString(feature, fieldIndex)
488 | if namePtr == nil {
489 | result.Error = fmt.Errorf("failed to get field value")
490 | return result
491 | }
492 | result.Name = C.GoString(namePtr)
493 |
494 | // 清理文件名
495 | result.Name = sanitizeFilename(result.Name)
496 | if result.Name == "" {
497 | result.Name = fmt.Sprintf("feature_%d", C.OGR_F_GetFID(feature))
498 | }
499 |
500 | // 获取几何体
501 | geom := C.OGR_F_GetGeometryRef(feature)
502 | if geom == nil {
503 | result.Error = fmt.Errorf("feature has no geometry")
504 | return result
505 | }
506 |
507 | // 应用缓冲区(如果需要)- 注意:像素坐标系下缓冲距离就是像素数
508 | if options.BufferDist > 0 {
509 | geom = C.OGR_G_Buffer(geom, C.double(options.BufferDist), 30)
510 | defer C.OGR_G_DestroyGeometry(geom)
511 | }
512 |
513 | // 使用掩膜方式裁剪像素坐标系图像
514 | var bounds [4]C.double
515 | clippedDS := C.clipPixelRasterByMask(srcDS, geom, &bounds[0])
516 | if clippedDS == nil {
517 | result.Error = fmt.Errorf("failed to clip raster by mask")
518 | return result
519 | }
520 | defer C.GDALClose(clippedDS)
521 |
522 | // 保存边界信息
523 | result.Bounds = [4]float64{
524 | float64(bounds[0]),
525 | float64(bounds[1]),
526 | float64(bounds[2]),
527 | float64(bounds[3]),
528 | }
529 |
530 | // 获取裁剪后的尺寸
531 | result.Width = int(C.GDALGetRasterXSize(clippedDS))
532 | result.Height = int(C.GDALGetRasterYSize(clippedDS))
533 |
534 | // 如果需要调整大小
535 | var outputDS C.GDALDatasetH = clippedDS
536 | if options.TileSize > 0 {
537 | outputDS = rd.resizeDataset(clippedDS, options.TileSize)
538 | if outputDS == nil {
539 | result.Error = fmt.Errorf("failed to resize dataset")
540 | return result
541 | }
542 | defer C.GDALClose(outputDS)
543 | result.Width = options.TileSize
544 | result.Height = options.TileSize
545 | }
546 |
547 | // 写入到内存缓冲区
548 | cFormat := C.CString(options.ImageFormat)
549 | defer C.free(unsafe.Pointer(cFormat))
550 |
551 | imageBuffer := C.writeImageToMemory(outputDS, cFormat, C.int(options.JPEGQuality))
552 | if imageBuffer == nil {
553 | result.Error = fmt.Errorf("failed to write image to memory")
554 | return result
555 | }
556 | defer C.freeImageBuffer(imageBuffer)
557 |
558 | // 将 C 数据转换为 Go []byte
559 | if imageBuffer.size > 0 && imageBuffer.data != nil {
560 | result.ImageData = C.GoBytes(unsafe.Pointer(imageBuffer.data), C.int(imageBuffer.size))
561 | } else {
562 | result.Error = fmt.Errorf("empty image data")
563 | return result
564 | }
565 |
566 | return result
567 | }
568 |
--------------------------------------------------------------------------------
/Identity.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2025 [GrainArc]
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published
6 | by the Free Software Foundation, either version 3 of the License, or
7 | (at your option) any later version.
8 |
9 | This program is distributed in the hope that it will be useful,
10 | but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | GNU Affero General Public License for more details.
13 |
14 | You should have received a copy of the GNU Affero General Public License
15 | along with this program. If not, see .
16 | */
17 | package Gogeo
18 |
19 | /*
20 | #include "osgeo_utils.h"
21 | static OGRErr performIdentityWithProgress(OGRLayerH inputLayer,
22 | OGRLayerH methodLayer,
23 | OGRLayerH resultLayer,
24 | char **options,
25 | void *progressData) {
26 | return OGR_L_Identity(inputLayer, methodLayer, resultLayer, options,
27 | progressCallback, progressData);
28 | }
29 | */
30 | import "C"
31 | import (
32 | "fmt"
33 | "github.com/google/uuid"
34 | "log"
35 | "runtime"
36 | "sync"
37 | "time"
38 | "unsafe"
39 | )
40 |
41 | func SpatialIdentityAnalysis(inputLayer, methodLayer *GDALLayer, config *ParallelGeosConfig) (*GeosAnalysisResult, error) {
42 |
43 | defer inputLayer.Close()
44 |
45 | defer methodLayer.Close()
46 |
47 | // 为两个图层添加唯一标识字段
48 | err := addIdentifierField(inputLayer, "gogeo_analysis_id")
49 | if err != nil {
50 | return nil, fmt.Errorf("添加唯一标识字段失败: %v", err)
51 | }
52 | err = addIdentifierField(methodLayer, "gogeo_analysis_id2")
53 | if err != nil {
54 | return nil, fmt.Errorf("添加唯一标识字段失败: %v", err)
55 | }
56 |
57 | resultLayer, err := performTileIdentityAnalysis(inputLayer, methodLayer, config, 1)
58 | if err != nil {
59 | return nil, fmt.Errorf("执行瓦片裁剪Identity分析失败: %v", err)
60 | }
61 |
62 | // 计算结果数量
63 | resultCount := resultLayer.GetFeatureCount()
64 |
65 | if config.IsMergeTile {
66 | // 执行按标识字段的融合操作
67 | unionResult, err := performUnionByFields(resultLayer, config.PrecisionConfig, config.ProgressCallback)
68 | if err != nil {
69 | return nil, fmt.Errorf("执行融合操作失败: %v", err)
70 | }
71 |
72 | // 删除临时的_identityID字段
73 | err = deleteFieldFromLayerFuzzy(unionResult.OutputLayer, "gogeo_analysis_id")
74 | if err != nil {
75 | fmt.Printf("警告: 删除临时标识字段失败: %v\n", err)
76 | }
77 |
78 | return unionResult, nil
79 | } else {
80 |
81 | return &GeosAnalysisResult{
82 | OutputLayer: resultLayer,
83 | ResultCount: resultCount,
84 | }, nil
85 | }
86 |
87 | }
88 |
89 | // performTileClipIdentityAnalysis 执行基于瓦片裁剪的并行Identity分析
90 | func performTileIdentityAnalysis(inputLayer, methodLayer *GDALLayer, config *ParallelGeosConfig, strategy FieldMergeStrategy) (*GDALLayer, error) {
91 | if config.PrecisionConfig != nil {
92 | // 创建内存副本
93 | inputMemLayer, err := createMemoryLayerCopy(inputLayer, "input_mem_layer")
94 | if err != nil {
95 | return nil, fmt.Errorf("创建输入图层内存副本失败: %v", err)
96 | }
97 |
98 | methodMemLayer, err := createMemoryLayerCopy(methodLayer, "erase_mem_layer")
99 | if err != nil {
100 | inputMemLayer.Close()
101 | return nil, fmt.Errorf("创建图层内存副本失败: %v", err)
102 | }
103 | if config.PrecisionConfig.Enabled {
104 | // 在内存图层上设置精度
105 | flags := config.PrecisionConfig.getFlags()
106 | gridSize := C.double(config.PrecisionConfig.GridSize)
107 |
108 | C.setLayerGeometryPrecision(inputMemLayer.layer, gridSize, flags)
109 | C.setLayerGeometryPrecision(methodMemLayer.layer, gridSize, flags)
110 | }
111 |
112 | // 使用内存图层进行后续处理
113 | inputLayer = inputMemLayer
114 | methodLayer = methodMemLayer
115 | }
116 |
117 | // 创建结果图层
118 | resultLayer, err := createIdentityAnalysisResultLayer(inputLayer, methodLayer, strategy)
119 | if err != nil {
120 | return nil, fmt.Errorf("创建结果图层失败: %v", err)
121 | }
122 | taskid := uuid.New().String()
123 | //对A B图层进行分块,并创建bin文件
124 | GenerateTiles(inputLayer, methodLayer, config.TileCount, taskid)
125 | //读取文件列表,并发执行擦除操作
126 | GPbins, err := ReadAndGroupBinFiles(taskid)
127 | if err != nil {
128 | return nil, fmt.Errorf("提取分组文件失败: %v", err)
129 | }
130 | // 并发执行分析
131 | err = executeConcurrentIdentityAnalysis(GPbins, resultLayer, config, strategy)
132 | if err != nil {
133 | resultLayer.Close()
134 | return nil, fmt.Errorf("并发分析失败: %v", err)
135 | }
136 | // 清理临时文件
137 | defer func() {
138 | err := cleanupTileFiles(taskid)
139 | if err != nil {
140 | log.Printf("清理临时文件失败: %v", err)
141 | }
142 | }()
143 |
144 | return resultLayer, nil
145 | }
146 | func executeConcurrentIdentityAnalysis(tileGroups []GroupTileFiles, resultLayer *GDALLayer, config *ParallelGeosConfig, strategy FieldMergeStrategy) error {
147 | maxWorkers := config.MaxWorkers
148 | if maxWorkers <= 0 {
149 | maxWorkers = runtime.NumCPU()
150 | }
151 |
152 | totalTasks := len(tileGroups)
153 | if totalTasks == 0 {
154 | return fmt.Errorf("没有分块需要处理")
155 | }
156 |
157 | // 创建任务队列和结果队列
158 | taskQueue := make(chan GroupTileFiles, totalTasks)
159 | results := make(chan taskResult, totalTasks)
160 |
161 | // 启动固定数量的工作协程
162 | var wg sync.WaitGroup
163 | for i := 0; i < maxWorkers; i++ {
164 | wg.Add(1)
165 | go worker_identity(i, taskQueue, results, config, &wg, strategy)
166 | }
167 |
168 | // 发送所有任务到队列
169 | go func() {
170 | for _, tileGroup := range tileGroups {
171 | taskQueue <- tileGroup
172 | }
173 | close(taskQueue) // 关闭任务队列,通知工作协程没有更多任务
174 | }()
175 |
176 | // 启动结果收集协程
177 | var resultWg sync.WaitGroup
178 | resultWg.Add(1)
179 | var processingError error
180 | completed := 0
181 |
182 | go func() {
183 | defer resultWg.Done()
184 |
185 | var totalDuration time.Duration
186 | var minDuration, maxDuration time.Duration
187 |
188 | for i := 0; i < totalTasks; i++ {
189 | result := <-results
190 | completed++
191 |
192 | if result.err != nil {
193 | processingError = fmt.Errorf("分块 %d 处理失败: %v", result.index, result.err)
194 | log.Printf("错误: %v", processingError)
195 | return
196 | }
197 |
198 | // 统计执行时间
199 | totalDuration += result.duration
200 | if i == 0 {
201 | minDuration = result.duration
202 | maxDuration = result.duration
203 | } else {
204 | if result.duration < minDuration {
205 | minDuration = result.duration
206 | }
207 | if result.duration > maxDuration {
208 | maxDuration = result.duration
209 | }
210 | }
211 |
212 | // 将结果合并到主图层
213 | if result.layer != nil {
214 | err := mergeResultsToMainLayer(result.layer, resultLayer)
215 | if err != nil {
216 | processingError = fmt.Errorf("合并分块 %d 结果失败: %v", result.index, err)
217 | log.Printf("错误: %v", processingError)
218 | return
219 | }
220 |
221 | // 释放临时图层资源
222 | result.layer.Close()
223 | }
224 |
225 | // 进度回调
226 | if config.ProgressCallback != nil {
227 | progress := float64(completed) / float64(totalTasks)
228 | avgDuration := totalDuration / time.Duration(completed)
229 |
230 | var memStats runtime.MemStats
231 | runtime.ReadMemStats(&memStats)
232 |
233 | message := fmt.Sprintf("已完成: %d/%d, 平均耗时: %v, 内存: %.2fMB, 协程数: %d",
234 | completed, totalTasks, avgDuration,
235 | float64(memStats.Alloc)/1024/1024, runtime.NumGoroutine())
236 |
237 | config.ProgressCallback(progress, message)
238 | }
239 |
240 | // 每处理50个任务输出一次详细统计
241 | if completed%50 == 0 || completed == totalTasks {
242 | avgDuration := totalDuration / time.Duration(completed)
243 | log.Printf("进度统计 - 已完成: %d/%d, 平均耗时: %v, 最快: %v, 最慢: %v",
244 | completed, totalTasks, avgDuration, minDuration, maxDuration)
245 | }
246 | }
247 |
248 | log.Printf("所有分块处理完成,总计: %d", completed)
249 | }()
250 |
251 | // 等待所有工作协程完成
252 | wg.Wait()
253 | close(results) // 关闭结果队列
254 |
255 | // 等待结果收集完成
256 | resultWg.Wait()
257 |
258 | if processingError != nil {
259 | return processingError
260 | }
261 |
262 | return nil
263 | }
264 |
265 | func worker_identity(workerID int, taskQueue <-chan GroupTileFiles, results chan<- taskResult, config *ParallelGeosConfig, wg *sync.WaitGroup, strategy FieldMergeStrategy) {
266 | defer wg.Done()
267 |
268 | tasksProcessed := 0
269 |
270 | for tileGroup := range taskQueue {
271 |
272 | start := time.Now()
273 |
274 | // 处理单个分块
275 | layer, err := processTileGroupforIdentity(tileGroup, config, strategy)
276 |
277 | duration := time.Since(start)
278 |
279 | tasksProcessed++
280 |
281 | // 发送结果
282 | results <- taskResult{
283 | layer: layer,
284 | err: err,
285 | duration: duration,
286 | index: tileGroup.Index,
287 | }
288 |
289 | // 定期强制垃圾回收
290 |
291 | runtime.GC()
292 |
293 | }
294 |
295 | }
296 | func processTileGroupforIdentity(tileGroup GroupTileFiles, config *ParallelGeosConfig, strategy FieldMergeStrategy) (*GDALLayer, error) {
297 |
298 | // 加载layer1的bin文件
299 | inputTileLayer, err := DeserializeLayerFromFile(tileGroup.GPBin.Layer1)
300 | if err != nil {
301 | return nil, fmt.Errorf("加载输入分块文件失败: %v", err)
302 | }
303 |
304 | // 加载layer2的bin文件
305 | methodTileLayer, err := DeserializeLayerFromFile(tileGroup.GPBin.Layer2)
306 | if err != nil {
307 | return nil, fmt.Errorf("加载擦除分块文件失败: %v", err)
308 | }
309 | defer func() {
310 | inputTileLayer.Close()
311 | methodTileLayer.Close()
312 |
313 | }()
314 |
315 | // 为当前分块创建临时结果图层
316 | tileName := fmt.Sprintf("tile_result_%d", tileGroup.Index)
317 | tileResultLayer, err := createIdentityTileResultLayer(inputTileLayer, methodTileLayer, tileName, strategy)
318 | if err != nil {
319 | return nil, fmt.Errorf("创建分块结果图层失败: %v", err)
320 | }
321 |
322 | // 执行裁剪分析 - 不使用进度回调
323 | err = executeIdentidyAnalysis(inputTileLayer, methodTileLayer, tileResultLayer, nil, strategy)
324 | if err != nil {
325 | tileResultLayer.Close()
326 | return nil, fmt.Errorf("执行擦除分析失败: %v", err)
327 | }
328 | return tileResultLayer, nil
329 | }
330 |
331 | // executeIdentityAnalysis 执行Identity分析
332 | func executeIdentidyAnalysis(inputLayer, methodLayer, resultLayer *GDALLayer, progressCallback ProgressCallback, strategy FieldMergeStrategy) error {
333 | // 设置GDAL选项
334 | var options **C.char
335 | defer func() {
336 | if options != nil {
337 | C.CSLDestroy(options)
338 | }
339 | }()
340 |
341 | skipFailuresOpt := C.CString("SKIP_FAILURES=YES")
342 | promoteToMultiOpt := C.CString("PROMOTE_TO_MULTI=YES")
343 | keepLowerDimOpt := C.CString("KEEP_LOWER_DIMENSION_GEOMETRIES=NO")
344 | defer C.free(unsafe.Pointer(skipFailuresOpt))
345 | defer C.free(unsafe.Pointer(promoteToMultiOpt))
346 | defer C.free(unsafe.Pointer(keepLowerDimOpt))
347 |
348 | options = C.CSLAddString(options, skipFailuresOpt)
349 | options = C.CSLAddString(options, promoteToMultiOpt)
350 | options = C.CSLAddString(options, keepLowerDimOpt)
351 | switch strategy {
352 | case MergeWithPrefix:
353 | inputPrefixOpt := C.CString(fmt.Sprintf("INPUT_PREFIX="))
354 | methodPrefixOpt := C.CString(fmt.Sprintf("METHOD_PREFIX=l2_"))
355 | defer C.free(unsafe.Pointer(methodPrefixOpt))
356 |
357 | defer C.free(unsafe.Pointer(inputPrefixOpt))
358 | options = C.CSLAddString(options, methodPrefixOpt)
359 | options = C.CSLAddString(options, methodPrefixOpt)
360 | }
361 |
362 | // 执行Identity操作
363 | return executeGDALIdentityWithProgress(inputLayer, methodLayer, resultLayer, options, progressCallback)
364 | }
365 |
366 | // executeGDALIdentityWithProgress 执行带进度的GDAL Identity操作
367 | func executeGDALIdentityWithProgress(inputLayer, methodLayer, resultLayer *GDALLayer, options **C.char, progressCallback ProgressCallback) error {
368 | var progressData *ProgressData
369 | var progressArg unsafe.Pointer
370 | // 启用多线程处理
371 | C.CPLSetConfigOption(C.CString("GDAL_NUM_THREADS"), C.CString("ALL_CPUS"))
372 | defer C.CPLSetConfigOption(C.CString("GDAL_NUM_THREADS"), nil)
373 | // 设置进度回调
374 | if progressCallback != nil {
375 | progressData = &ProgressData{
376 | callback: progressCallback,
377 | cancelled: false,
378 | }
379 | progressArg = unsafe.Pointer(uintptr(unsafe.Pointer(progressData)))
380 |
381 | progressDataMutex.Lock()
382 | progressDataMap[uintptr(progressArg)] = progressData
383 | progressDataMutex.Unlock()
384 |
385 | defer func() {
386 | progressDataMutex.Lock()
387 | delete(progressDataMap, uintptr(progressArg))
388 | progressDataMutex.Unlock()
389 | }()
390 | }
391 |
392 | // 调用GDAL的Identity函数
393 | var err C.OGRErr
394 | if progressCallback != nil {
395 | err = C.performIdentityWithProgress(inputLayer.layer, methodLayer.layer, resultLayer.layer, options, progressArg)
396 | } else {
397 | err = C.OGR_L_Identity(inputLayer.layer, methodLayer.layer, resultLayer.layer, options, nil, nil)
398 | }
399 |
400 | if err != C.OGRERR_NONE {
401 |
402 | return fmt.Errorf("GDAL相交分析失败,错误代码: %d", int(err))
403 | }
404 |
405 | return nil
406 | }
407 |
408 | // createIdentityAnalysisResultLayer 创建Identity结果图层
409 | func createIdentityAnalysisResultLayer(inputLayer, methodLayer *GDALLayer, strategy FieldMergeStrategy) (*GDALLayer, error) {
410 | layerName := C.CString("identity_result")
411 | defer C.free(unsafe.Pointer(layerName))
412 |
413 | // 获取空间参考系统
414 | srs := inputLayer.GetSpatialRef()
415 | if srs == nil {
416 | srs = methodLayer.GetSpatialRef()
417 | }
418 |
419 | // 创建结果图层
420 | resultLayerPtr := C.createMemoryLayer(layerName, C.wkbMultiPolygon, srs)
421 | if resultLayerPtr == nil {
422 | return nil, fmt.Errorf("创建结果图层失败")
423 | }
424 |
425 | resultLayer := &GDALLayer{layer: resultLayerPtr}
426 | runtime.SetFinalizer(resultLayer, (*GDALLayer).cleanup)
427 |
428 | // 添加字段定义
429 | err := addIdentityFields(resultLayer, inputLayer, methodLayer, strategy)
430 | if err != nil {
431 | resultLayer.Close()
432 | return nil, fmt.Errorf("添加字段失败: %v", err)
433 | }
434 |
435 | return resultLayer, nil
436 | }
437 |
438 | // addIdentityFields 添加Identity分析的字段
439 | func addIdentityFields(resultLayer, inputLayer, methodLayer *GDALLayer, strategy FieldMergeStrategy) error {
440 | switch strategy {
441 | case MergePreferTable2:
442 | // 添加输入图层的字段(不使用前缀)
443 | err1 := addLayerFields(resultLayer, inputLayer, "")
444 | if err1 != nil {
445 | return fmt.Errorf("添加输入图层字段失败: %v", err1)
446 | }
447 |
448 | // 添加方法图层的字段(不使用前缀,处理重名字段)
449 | err2 := addLayerFields(resultLayer, methodLayer, "")
450 | if err2 != nil {
451 | return fmt.Errorf("添加方法图层字段失败: %v", err2)
452 | }
453 | case MergeWithPrefix:
454 | err1 := addLayerFields(resultLayer, inputLayer, "")
455 | if err1 != nil {
456 | return fmt.Errorf("添加输入图层字段失败: %v", err1)
457 | }
458 |
459 | // 添加方法图层的字段(不使用前缀,处理重名字段)
460 | err2 := addLayerFields(resultLayer, methodLayer, "l2_")
461 | if err2 != nil {
462 | return fmt.Errorf("添加方法图层字段失败: %v", err2)
463 | }
464 | default:
465 | err1 := addLayerFields(resultLayer, inputLayer, "")
466 | if err1 != nil {
467 | return fmt.Errorf("添加输入图层字段失败: %v", err1)
468 | }
469 |
470 | // 添加方法图层的字段(不使用前缀,处理重名字段)
471 | err2 := addLayerFields(resultLayer, methodLayer, "")
472 | if err2 != nil {
473 | return fmt.Errorf("添加方法图层字段失败: %v", err2)
474 | }
475 | }
476 |
477 | return nil
478 | }
479 |
480 | func createIdentityTileResultLayer(inputLayer, methodLayer *GDALLayer, layerName string, strategy FieldMergeStrategy) (*GDALLayer, error) {
481 | layerNameC := C.CString(layerName)
482 | defer C.free(unsafe.Pointer(layerNameC))
483 |
484 | // 获取空间参考系统
485 | srs := inputLayer.GetSpatialRef()
486 |
487 | // 创建内存图层
488 | resultLayerPtr := C.createMemoryLayer(layerNameC, C.wkbMultiPolygon, srs)
489 | if resultLayerPtr == nil {
490 | return nil, fmt.Errorf("创建分块结果图层失败")
491 | }
492 |
493 | resultLayer := &GDALLayer{layer: resultLayerPtr}
494 | runtime.SetFinalizer(resultLayer, (*GDALLayer).cleanup)
495 |
496 | // 添加字段定义
497 | err := addIdentityFields(resultLayer, inputLayer, methodLayer, strategy)
498 | if err != nil {
499 | resultLayer.Close()
500 | return nil, fmt.Errorf("添加字段失败: %v", err)
501 | }
502 |
503 | return resultLayer, nil
504 | }
505 |
--------------------------------------------------------------------------------
/MbtilesGen.go:
--------------------------------------------------------------------------------
1 | package Gogeo
2 |
3 | import (
4 | "database/sql"
5 | "fmt"
6 | "log"
7 | "runtime"
8 | "runtime/debug"
9 | "sync"
10 | "sync/atomic"
11 | "time"
12 |
13 | _ "github.com/mattn/go-sqlite3"
14 | )
15 |
16 | // MBTilesGenerator MBTiles生成器
17 | type MBTilesGenerator struct {
18 | dataset *RasterDataset
19 | tileSize int
20 | imagePath string
21 | minZoom int
22 | maxZoom int
23 |
24 | progressCallback ProgressCallback
25 | }
26 |
27 | // MBTilesOptions MBTiles生成选项
28 | type MBTilesOptions struct {
29 | TileSize int // 瓦片大小,默认256
30 | MinZoom int // 最小缩放级别,默认0
31 | MaxZoom int // 最大缩放级别,默认18
32 | Metadata map[string]string // 自定义元数据
33 |
34 | Concurrency int // 并发数,默认为CPU核心数
35 | ProgressCallback ProgressCallback // 进度回调函数
36 | BatchSize int // 批量插入大小,默认1000
37 | }
38 |
39 | // TileTask 瓦片任务
40 | type TileTask struct {
41 | Zoom int
42 | X int
43 | Y int
44 | }
45 |
46 | // RasterTileResult 瓦片结果
47 | type RasterTileResult struct {
48 | Zoom int
49 | X int
50 | Y int
51 | Data []byte
52 | Error error
53 | }
54 |
55 | // NewMBTilesGenerator 创建MBTiles生成器
56 | func NewMBTilesGenerator(imagePath string, options *MBTilesOptions) (*MBTilesGenerator, error) {
57 | dataset, err := OpenRasterDataset(imagePath, true)
58 | if err != nil {
59 | return nil, err
60 | }
61 |
62 | if options == nil {
63 | options = &MBTilesOptions{}
64 | }
65 | if options.TileSize <= 0 {
66 | options.TileSize = 256
67 | }
68 | if options.MinZoom < 0 {
69 | options.MinZoom = 0
70 | }
71 | if options.MaxZoom <= 0 || options.MaxZoom > 22 {
72 | options.MaxZoom = 18
73 | }
74 | if options.BatchSize <= 0 {
75 | options.BatchSize = 100
76 | }
77 |
78 | gen := &MBTilesGenerator{
79 | dataset: dataset,
80 | imagePath: imagePath,
81 | tileSize: options.TileSize,
82 | minZoom: options.MinZoom,
83 | maxZoom: options.MaxZoom,
84 |
85 | progressCallback: options.ProgressCallback,
86 | }
87 |
88 | return gen, nil
89 | }
90 |
91 | // Close 关闭生成器
92 | func (gen *MBTilesGenerator) Close() {
93 | if gen.dataset != nil {
94 | gen.dataset.Close()
95 | gen.dataset = nil
96 | }
97 | }
98 |
99 | // Generate 生成MBTiles文件
100 | func (gen *MBTilesGenerator) Generate(outputPath string, metadata map[string]string) error {
101 | // 创建SQLite数据库
102 | db, err := sql.Open("sqlite3", outputPath)
103 | if err != nil {
104 | return fmt.Errorf("failed to create database: %w", err)
105 | }
106 | defer db.Close()
107 |
108 | // 优化数据库配置
109 | if err := gen.optimizeDatabase(db); err != nil {
110 | return fmt.Errorf("failed to optimize database: %w", err)
111 | }
112 |
113 | // 创建表结构
114 | if err := gen.createTables(db); err != nil {
115 | return fmt.Errorf("failed to create tables: %w", err)
116 | }
117 |
118 | // 写入元数据
119 | if err := gen.writeMetadata(db, metadata); err != nil {
120 | return fmt.Errorf("failed to write meta%w", err)
121 | }
122 |
123 | // 生成瓦片
124 | if err := gen.generateTiles(db); err != nil {
125 | return fmt.Errorf("failed to generate tiles: %w", err)
126 | }
127 |
128 | log.Printf("MBTiles generation completed: %s", outputPath)
129 | return nil
130 | }
131 |
132 | // GenerateWithConcurrency 并发生成MBTiles文件
133 | func (gen *MBTilesGenerator) GenerateWithConcurrency(outputPath string, metadata map[string]string, concurrency int) error {
134 | // 创建SQLite数据库
135 | db, err := sql.Open("sqlite3", outputPath)
136 | if err != nil {
137 | return fmt.Errorf("failed to create database: %w", err)
138 | }
139 |
140 | // 优化数据库配置
141 | if err := gen.optimizeDatabase(db); err != nil {
142 | db.Close()
143 | return fmt.Errorf("failed to optimize database: %w", err)
144 | }
145 |
146 | // 创建表结构
147 | if err := gen.createTables(db); err != nil {
148 | db.Close()
149 | return fmt.Errorf("failed to create tables: %w", err)
150 | }
151 |
152 | // 写入元数据
153 | if err := gen.writeMetadata(db, metadata); err != nil {
154 | db.Close()
155 | return fmt.Errorf("failed to write meta%w", err)
156 | }
157 |
158 | // 并发生成瓦片
159 | if err := gen.generateTilesConcurrent(db, concurrency, gen.imagePath); err != nil {
160 | db.Close()
161 | return fmt.Errorf("failed to generate tiles: %w", err)
162 | }
163 |
164 | return nil
165 | }
166 |
167 | // optimizeDatabase 优化数据库配置
168 | func (gen *MBTilesGenerator) optimizeDatabase(db *sql.DB) error {
169 | pragmas := []string{
170 | "PRAGMA journal_mode=WAL", // 启用WAL模式,允许并发读写
171 | "PRAGMA synchronous=NORMAL", // 降低同步级别,提升性能
172 | "PRAGMA cache_size=10000", // 增加缓存大小
173 | "PRAGMA page_size=4096", // 设置页面大小
174 | "PRAGMA temp_store=MEMORY", // 临时表存储在内存
175 | "PRAGMA mmap_size=268435456", // 启用内存映射(256MB)
176 | "PRAGMA locking_mode=EXCLUSIVE", // 独占模式(可选)
177 | "PRAGMA auto_vacuum=INCREMENTAL", // 增量自动清理
178 | }
179 |
180 | for _, pragma := range pragmas {
181 | if _, err := db.Exec(pragma); err != nil {
182 | log.Printf("Warning: failed to execute %s: %v", pragma, err)
183 | // 不返回错误,继续执行
184 | }
185 | }
186 |
187 | return nil
188 | }
189 |
190 | // createTables 创建MBTiles数据库表
191 | func (gen *MBTilesGenerator) createTables(db *sql.DB) error {
192 | schemas := []string{
193 | `CREATE TABLE IF NOT EXISTS metadata (
194 | name TEXT PRIMARY KEY,
195 | value TEXT
196 | )`,
197 | `CREATE TABLE IF NOT EXISTS tiles (
198 | zoom_level INTEGER,
199 | tile_column INTEGER,
200 | tile_row INTEGER,
201 | tile_data BLOB,
202 | PRIMARY KEY (zoom_level, tile_column, tile_row)
203 | )`,
204 | `CREATE INDEX IF NOT EXISTS tiles_idx ON tiles(zoom_level, tile_column, tile_row)`,
205 | }
206 |
207 | for _, schema := range schemas {
208 | if _, err := db.Exec(schema); err != nil {
209 | return err
210 | }
211 | }
212 |
213 | return nil
214 | }
215 |
216 | // writeMetadata 写入MBTiles元数据
217 | func (gen *MBTilesGenerator) writeMetadata(db *sql.DB, customMetadata map[string]string) error {
218 | minLon, minLat, maxLon, maxLat := gen.dataset.GetBoundsLatLon()
219 |
220 | // 默认元数据
221 | defaultMetadata := map[string]string{
222 | "name": "Generated Tiles",
223 | "type": "baselayer",
224 | "version": "1.0",
225 | "description": "Tiles generated from raster image",
226 | "format": "png",
227 | "bounds": fmt.Sprintf("%.6f,%.6f,%.6f,%.6f", minLon, minLat, maxLon, maxLat),
228 | "center": fmt.Sprintf("%.6f,%.6f,%d", (minLon+maxLon)/2, (minLat+maxLat)/2, gen.minZoom),
229 | "minzoom": fmt.Sprintf("%d", gen.minZoom),
230 | "maxzoom": fmt.Sprintf("%d", gen.maxZoom),
231 | }
232 |
233 | // 合并自定义元数据
234 | for k, v := range customMetadata {
235 | defaultMetadata[k] = v
236 | }
237 |
238 | // 使用事务批量插入
239 | tx, err := db.Begin()
240 | if err != nil {
241 | return err
242 | }
243 | defer tx.Rollback()
244 |
245 | stmt, err := tx.Prepare("INSERT OR REPLACE INTO metadata (name, value) VALUES (?, ?)")
246 | if err != nil {
247 | return err
248 | }
249 | defer stmt.Close()
250 |
251 | for k, v := range defaultMetadata {
252 | if _, err := stmt.Exec(k, v); err != nil {
253 | return err
254 | }
255 | }
256 |
257 | return tx.Commit()
258 | }
259 |
260 | // generateTiles 生成所有瓦片(单线程版本,使用批量插入)
261 | func (gen *MBTilesGenerator) generateTiles(db *sql.DB) error {
262 | totalTiles := 0
263 | estimatedTotal := gen.EstimateTileCount()
264 |
265 | // 调用进度回调 - 开始
266 | if gen.progressCallback != nil {
267 | if !gen.progressCallback(0, "Starting tile generation") {
268 | return fmt.Errorf("operation cancelled by user")
269 | }
270 | }
271 |
272 | // 批量插入缓冲区
273 | const batchSize = 100
274 | batch := make([]RasterTileResult, 0, batchSize)
275 |
276 | // 遍历缩放级别
277 | for zoom := gen.minZoom; zoom <= gen.maxZoom; zoom++ {
278 | minTileX, minTileY, maxTileX, maxTileY := gen.dataset.GetTileRange(zoom)
279 |
280 | // 遍历瓦片
281 | for x := minTileX; x <= maxTileX; x++ {
282 | for y := minTileY; y <= maxTileY; y++ {
283 | // 读取瓦片数据
284 | tileData, err := gen.dataset.ReadTile(zoom, x, y, gen.tileSize)
285 | if err != nil {
286 | log.Printf("Warning: failed to generate tile %d/%d/%d: %v", zoom, x, y, err)
287 | continue
288 | }
289 |
290 | batch = append(batch, RasterTileResult{
291 | Zoom: zoom,
292 | X: x,
293 | Y: y,
294 | Data: tileData,
295 | })
296 |
297 | // 批量插入
298 | if len(batch) >= batchSize {
299 | if err := gen.batchInsertTiles(db, batch); err != nil {
300 | return err
301 | }
302 | totalTiles += len(batch)
303 | batch = batch[:0]
304 |
305 | // 输出进度
306 | progress := float64(totalTiles) / float64(estimatedTotal)
307 | message := fmt.Sprintf("Generated %d/%d tiles (%.2f%%)", totalTiles, estimatedTotal, progress*100)
308 |
309 | if gen.progressCallback != nil {
310 | if !gen.progressCallback(progress, message) {
311 | return fmt.Errorf("operation cancelled by user")
312 | }
313 | }
314 | }
315 | }
316 | }
317 | }
318 |
319 | // 插入剩余的瓦片
320 | if len(batch) > 0 {
321 | if err := gen.batchInsertTiles(db, batch); err != nil {
322 | return err
323 | }
324 | totalTiles += len(batch)
325 | }
326 |
327 | // 调用进度回调 - 完成
328 | if gen.progressCallback != nil {
329 | gen.progressCallback(1.0, fmt.Sprintf("Successfully generated %d tiles", totalTiles))
330 | }
331 |
332 | log.Printf("Successfully generated %d tiles", totalTiles)
333 | return nil
334 | }
335 |
336 | // batchInsertTiles 批量插入瓦片
337 | func (gen *MBTilesGenerator) batchInsertTiles(db *sql.DB, tiles []RasterTileResult) error {
338 | if len(tiles) == 0 {
339 | return nil
340 | }
341 |
342 | tx, err := db.Begin()
343 | if err != nil {
344 | return err
345 | }
346 | defer tx.Rollback()
347 |
348 | stmt, err := tx.Prepare("INSERT OR REPLACE INTO tiles (zoom_level, tile_column, tile_row, tile_data) VALUES (?, ?, ?, ?)")
349 | if err != nil {
350 | return err
351 | }
352 | defer stmt.Close()
353 |
354 | for _, tile := range tiles {
355 | if _, err := stmt.Exec(tile.Zoom, tile.X, tile.Y, tile.Data); err != nil {
356 | return err
357 | }
358 | }
359 |
360 | return tx.Commit()
361 | }
362 |
363 | // tileWriter 瓦片写入协程(使用批量插入)
364 | func (gen *MBTilesGenerator) tileWriter(db *sql.DB, results <-chan RasterTileResult, totalTiles, errorCount, cancelled, earlyReturn *int32, estimatedTotal int, batchSize int) error {
365 | batch := make([]RasterTileResult, 0, batchSize)
366 | ticker := time.NewTicker(100 * time.Millisecond) // 定时刷新
367 | defer ticker.Stop()
368 |
369 | flush := func() error {
370 | if len(batch) == 0 {
371 | return nil
372 | }
373 |
374 | if err := gen.batchInsertTiles(db, batch); err != nil {
375 | return err
376 | }
377 |
378 | current := atomic.AddInt32(totalTiles, int32(len(batch)))
379 | batch = batch[:0]
380 |
381 | // 输出进度
382 | if atomic.LoadInt32(earlyReturn) == 0 {
383 | progress := float64(current) / float64(estimatedTotal)
384 | message := fmt.Sprintf("Progress: %d/%d tiles (%.2f%%)", current, estimatedTotal, progress*100)
385 | if gen.progressCallback != nil {
386 | if !gen.progressCallback(progress, message) {
387 | atomic.StoreInt32(cancelled, 1)
388 | return fmt.Errorf("operation cancelled by user")
389 | }
390 | }
391 | }
392 |
393 | return nil
394 | }
395 |
396 | for {
397 | select {
398 | case result, ok := <-results:
399 | if !ok {
400 | // 通道关闭,刷新剩余数据
401 | return flush()
402 | }
403 |
404 | // 检查是否已取消
405 | if atomic.LoadInt32(cancelled) == 1 {
406 | return fmt.Errorf("operation cancelled by user")
407 | }
408 |
409 | if result.Error != nil {
410 | log.Printf("Warning: failed to generate tile %d/%d/%d: %v",
411 | result.Zoom, result.X, result.Y, result.Error)
412 | atomic.AddInt32(errorCount, 1)
413 | continue
414 | }
415 |
416 | batch = append(batch, result)
417 |
418 | // 达到批量大小,执行插入
419 | if len(batch) >= batchSize {
420 | if err := flush(); err != nil {
421 | return err
422 | }
423 | }
424 |
425 | case <-ticker.C:
426 | // 定时刷新
427 | if err := flush(); err != nil {
428 | return err
429 | }
430 | }
431 | }
432 | }
433 |
434 | // generateTilesConcurrent 并发生成所有瓦片
435 | func (gen *MBTilesGenerator) generateTilesConcurrent(db *sql.DB, concurrency int, imagePath string) error {
436 | if concurrency <= 0 {
437 | concurrency = 4
438 | }
439 |
440 | const batchSize = 500 // 批量插入大小
441 |
442 | // 创建任务通道和结果通道
443 | taskChan := make(chan TileTask, concurrency*10)
444 | resultChan := make(chan RasterTileResult, concurrency*10)
445 |
446 | // 统计变量
447 | var totalTiles int32
448 | var errorCount int32
449 | var cancelled int32
450 | var earlyReturn int32
451 | estimatedTotal := gen.EstimateTileCount()
452 |
453 | // 调用进度回调 - 开始
454 | if gen.progressCallback != nil {
455 | if !gen.progressCallback(0, "Starting concurrent tile generation") {
456 | db.Close()
457 | return fmt.Errorf("operation cancelled by user")
458 | }
459 | }
460 |
461 | // 启动工作协程
462 | var wg sync.WaitGroup
463 | for i := 0; i < concurrency; i++ {
464 | wg.Add(1)
465 | go func(workerID int) {
466 | defer wg.Done()
467 | gen.tileWorker(workerID, imagePath, taskChan, resultChan, &cancelled, &earlyReturn)
468 | }(i)
469 | }
470 |
471 | // 启动结果写入协程
472 | writerDone := make(chan error, 1)
473 | go func() {
474 | writerDone <- gen.tileWriter(db, resultChan, &totalTiles, &errorCount, &cancelled, &earlyReturn, estimatedTotal, batchSize)
475 | }()
476 |
477 | // 生成任务
478 | go func() {
479 | defer close(taskChan)
480 |
481 | for zoom := gen.minZoom; zoom <= gen.maxZoom; zoom++ {
482 | if atomic.LoadInt32(&cancelled) == 1 || atomic.LoadInt32(&earlyReturn) == 1 {
483 | log.Printf("Task generation stopped due to early return")
484 | return
485 | }
486 |
487 | minTileX, minTileY, maxTileX, maxTileY := gen.dataset.GetTileRange(zoom)
488 |
489 | tileCount := (maxTileX - minTileX + 1) * (maxTileY - minTileY + 1)
490 | log.Printf("Queuing zoom level %d: tiles %d-%d, %d-%d (total: %d)",
491 | zoom, minTileX, maxTileX, minTileY, maxTileY, tileCount)
492 |
493 | for x := minTileX; x <= maxTileX; x++ {
494 | for y := minTileY; y <= maxTileY; y++ {
495 | if atomic.LoadInt32(&cancelled) == 1 || atomic.LoadInt32(&earlyReturn) == 1 {
496 | return
497 | }
498 |
499 | taskChan <- TileTask{
500 | Zoom: zoom,
501 | X: x,
502 | Y: y,
503 | }
504 | }
505 | }
506 | }
507 | }()
508 |
509 | // 监控进度并在99%时强制返回
510 | ticker := time.NewTicker(100 * time.Millisecond)
511 | defer ticker.Stop()
512 |
513 | for {
514 | select {
515 | case <-ticker.C:
516 | current := atomic.LoadInt32(&totalTiles)
517 | progress := float64(current) / float64(estimatedTotal)
518 |
519 | // 当进度达到99%时,强制关闭并返回
520 | if progress >= 0.99 && atomic.LoadInt32(&earlyReturn) == 0 {
521 | atomic.StoreInt32(&earlyReturn, 1)
522 |
523 | log.Printf("Progress reached 99%% (%d/%d tiles), forcing completion...", current, estimatedTotal)
524 |
525 | // 回调通知99%完成
526 | if gen.progressCallback != nil {
527 | gen.progressCallback(0.99, fmt.Sprintf("99%% completed (%d/%d tiles), forcing shutdown...", current, estimatedTotal))
528 | }
529 |
530 | // 等待一小段时间让当前批次写入完成
531 | time.Sleep(200 * time.Millisecond)
532 |
533 | // 强制关闭数据库
534 | if err := db.Close(); err != nil {
535 | log.Printf("Warning: Error closing database: %v", err)
536 | } else {
537 | log.Printf("Database closed successfully")
538 | }
539 |
540 | // 强制GC回收
541 | log.Printf("Forcing garbage collection...")
542 | runtime.GC()
543 | runtime.GC() // 调用两次确保彻底回收
544 | debug.FreeOSMemory() // 释放内存给操作系统
545 |
546 | log.Printf("Memory cleanup completed")
547 |
548 | // 立即返回,视为完成
549 | if gen.progressCallback != nil {
550 | gen.progressCallback(1.0, fmt.Sprintf("Force completed with %d tiles", current))
551 | }
552 |
553 | return nil
554 | }
555 |
556 | // 正常完成检查
557 | if current >= int32(estimatedTotal) {
558 | // 等待所有协程完成
559 | wg.Wait()
560 | close(resultChan)
561 |
562 | if err := <-writerDone; err != nil {
563 | db.Close()
564 | return err
565 | }
566 |
567 | if err := db.Close(); err != nil {
568 | return fmt.Errorf("error closing database: %w", err)
569 | }
570 |
571 | if gen.progressCallback != nil {
572 | gen.progressCallback(1.0, fmt.Sprintf("Successfully generated %d tiles with %d errors", totalTiles, errorCount))
573 | }
574 |
575 | log.Printf("Successfully generated %d tiles with %d errors", totalTiles, errorCount)
576 | return nil
577 | }
578 |
579 | // 取消检查
580 | if atomic.LoadInt32(&cancelled) == 1 {
581 | db.Close()
582 | return fmt.Errorf("operation cancelled by user")
583 | }
584 | }
585 | }
586 | }
587 |
588 | // tileWorker 瓦片生成工作协程 (修改签名,添加earlyReturn参数)
589 | func (gen *MBTilesGenerator) tileWorker(workerID int, imagePath string, tasks <-chan TileTask, results chan<- RasterTileResult, cancelled *int32, earlyReturn *int32) {
590 | dataset, err := OpenRasterDataset(imagePath, true)
591 | if err != nil {
592 | log.Printf("Worker %d failed to open dataset: %v", workerID, err)
593 | return
594 | }
595 | defer dataset.Close()
596 |
597 | for task := range tasks {
598 | // 检查是否需要提前返回
599 | if atomic.LoadInt32(cancelled) == 1 || atomic.LoadInt32(earlyReturn) == 1 {
600 | return
601 | }
602 |
603 | tileData, err := dataset.ReadTile(task.Zoom, task.X, task.Y, gen.tileSize)
604 |
605 | // 尝试发送结果,如果通道已关闭则退出
606 | select {
607 | case results <- RasterTileResult{
608 | Zoom: task.Zoom,
609 | X: task.X,
610 | Y: task.Y,
611 | Data: tileData,
612 | Error: err,
613 | }:
614 | default:
615 | // 通道可能已关闭,直接返回
616 | if atomic.LoadInt32(earlyReturn) == 1 {
617 | return
618 | }
619 | }
620 | }
621 | }
622 |
623 | // GetDatasetInfo 获取数据集信息
624 | func (gen *MBTilesGenerator) GetDatasetInfo() DatasetInfo {
625 | return gen.dataset.GetInfo()
626 | }
627 |
628 | // GetBounds 获取边界(经纬度)
629 | func (gen *MBTilesGenerator) GetBounds() (minLon, minLat, maxLon, maxLat float64) {
630 | return gen.dataset.GetBoundsLatLon()
631 | }
632 |
633 | // EstimateTileCount 估算瓦片数量
634 | func (gen *MBTilesGenerator) EstimateTileCount() int {
635 | total := 0
636 | for zoom := gen.minZoom; zoom <= gen.maxZoom; zoom++ {
637 | minTileX, minTileY, maxTileX, maxTileY := gen.dataset.GetTileRange(zoom)
638 | count := (maxTileX - minTileX + 1) * (maxTileY - minTileY + 1)
639 | total += count
640 | }
641 | return total
642 | }
643 |
--------------------------------------------------------------------------------
/Union.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2024 [GrainArc]
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published
6 | by the Free Software Foundation, either version 3 of the License, or
7 | (at your option) any later version.
8 |
9 | This program is distributed in the hope that it will be useful,
10 | but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | GNU Affero General Public License for more details.
13 |
14 | You should have received a copy of the GNU Affero General Public License
15 | along with this program. If not, see .
16 | */
17 | package Gogeo
18 |
19 | /*
20 | #include "osgeo_utils.h"
21 | #include
22 | */
23 | import "C"
24 | import (
25 | "context"
26 | "fmt"
27 | "runtime"
28 | "sort"
29 | "strings"
30 | "sync"
31 | "unsafe"
32 | )
33 |
34 | // UnionConfig Union操作配置
35 | type UnionConfig struct {
36 | GroupFields []string // 分组字段列表
37 | OutputLayerName string // 输出图层名称
38 | GeomType C.OGRwkbGeometryType // 输出几何类型
39 | PrecisionConfig *GeometryPrecisionConfig // 几何精度配置
40 | ProgressCallback ProgressCallback // 进度回调
41 | }
42 |
43 | // FeatureGroup 要素分组
44 | type FeatureGroup struct {
45 | GroupKey string // 分组键(由多个字段值组成)
46 | Features []C.OGRFeatureH // 该组的所有要素
47 | Fields map[string]string // 分组字段的值
48 | }
49 |
50 | // 在 UnionProcessor 结构体中添加并发控制字段
51 | type UnionProcessor struct {
52 | config *UnionConfig
53 | // 添加并发控制
54 | maxWorkers int
55 | semaphore chan struct{}
56 | }
57 |
58 | // 修改 NewUnionProcessor 函数
59 | func NewUnionProcessor(config *UnionConfig) *UnionProcessor {
60 | maxWorkers := runtime.NumCPU()
61 | if maxWorkers > 8 {
62 | maxWorkers = 8 // 限制最大并发数,避免过多的内存使用
63 | }
64 |
65 | return &UnionProcessor{
66 | config: config,
67 | maxWorkers: maxWorkers,
68 | semaphore: make(chan struct{}, maxWorkers),
69 | }
70 | }
71 |
72 | // 添加并发Union结果结构
73 | type unionResult struct {
74 | groupKey string
75 | geometry C.OGRGeometryH
76 | group *FeatureGroup
77 | err error
78 | }
79 |
80 | // UnionAnalysis 对数据进行融合分析
81 | func UnionAnalysis(inputLayer *GDALLayer, groupFields []string, outputTableName string,
82 | precisionConfig *GeometryPrecisionConfig, progressCallback ProgressCallback) (*GeosAnalysisResult, error) {
83 |
84 | tableName := inputLayer.GetLayerName()
85 |
86 | // 确保资源清理
87 | defer inputLayer.Close()
88 |
89 | // 3. 验证输入参数
90 | if len(groupFields) == 0 {
91 | return nil, fmt.Errorf("分组字段不能为空")
92 | }
93 |
94 | if outputTableName == "" {
95 | outputTableName = fmt.Sprintf("%s_union", tableName)
96 | }
97 |
98 | // 4. 验证分组字段是否存在
99 | fieldCount := inputLayer.GetFieldCount()
100 | fieldNames := make(map[string]bool)
101 | for i := 0; i < fieldCount; i++ {
102 | fieldNames[inputLayer.GetFieldName(i)] = true
103 | }
104 |
105 | for _, field := range groupFields {
106 | if !fieldNames[field] {
107 | return nil, fmt.Errorf("分组字段 '%s' 在表 '%s' 中不存在", field, tableName)
108 | }
109 | }
110 |
111 | // 5. 设置默认精度配置
112 | if precisionConfig == nil {
113 | precisionConfig = &GeometryPrecisionConfig{
114 | GridSize: 0.0, // 使用浮点精度
115 | PreserveTopo: true, // 保持拓扑结构
116 | KeepCollapsed: false, // 不保留折叠元素
117 | Enabled: true, // 启用精度设置
118 | }
119 | }
120 |
121 | // 7. 执行Union分析
122 | result, err := UnionByFieldsWithPrecision(
123 | inputLayer,
124 | groupFields,
125 | outputTableName,
126 | precisionConfig,
127 | progressCallback,
128 | )
129 |
130 | if err != nil {
131 | return nil, fmt.Errorf("Union分析失败: %v", err)
132 | }
133 |
134 | return result, nil
135 | }
136 |
137 | // ProcessUnion 执行Union操作
138 | func (up *UnionProcessor) ProcessUnion(inputLayer *GDALLayer) (*GeosAnalysisResult, error) {
139 | if inputLayer == nil {
140 | return nil, fmt.Errorf("输入图层不能为空")
141 | }
142 |
143 | // 验证分组字段
144 | if err := up.validateGroupFields(inputLayer); err != nil {
145 | return nil, err
146 | }
147 |
148 | // 分组要素
149 | groups, err := up.groupFeatures(inputLayer)
150 | if err != nil {
151 | return nil, fmt.Errorf("分组要素失败: %v", err)
152 | }
153 |
154 | if up.config.ProgressCallback != nil {
155 | if !up.config.ProgressCallback(0.3, fmt.Sprintf("完成要素分组,共 %d 个组", len(groups))) {
156 | return nil, fmt.Errorf("操作被用户取消")
157 | }
158 | }
159 |
160 | // 创建输出图层
161 | outputLayer, err := up.createOutputLayer(inputLayer)
162 | if err != nil {
163 | return nil, fmt.Errorf("创建输出图层失败: %v", err)
164 | }
165 |
166 | // 执行Union操作
167 | processedCount, err := up.performUnion(groups, outputLayer)
168 | if err != nil {
169 | outputLayer.Close()
170 | return nil, fmt.Errorf("执行Union操作失败: %v", err)
171 | }
172 |
173 | result := &GeosAnalysisResult{
174 | OutputLayer: outputLayer,
175 | ResultCount: processedCount,
176 | }
177 |
178 | if up.config.ProgressCallback != nil {
179 | up.config.ProgressCallback(1.0, fmt.Sprintf("Union操作完成,处理了 %d 个组,生成 %d 个要素", len(groups), processedCount))
180 | }
181 |
182 | return result, nil
183 | }
184 |
185 | // validateGroupFields 验证分组字段是否存在
186 | func (up *UnionProcessor) validateGroupFields(layer *GDALLayer) error {
187 | if len(up.config.GroupFields) == 0 {
188 | return fmt.Errorf("必须指定至少一个分组字段")
189 | }
190 |
191 | layerDefn := layer.GetLayerDefn()
192 | fieldCount := int(C.OGR_FD_GetFieldCount(layerDefn))
193 |
194 | // 获取所有字段名
195 | existingFields := make(map[string]bool)
196 | for i := 0; i < fieldCount; i++ {
197 | fieldDefn := C.OGR_FD_GetFieldDefn(layerDefn, C.int(i))
198 | fieldName := C.GoString(C.OGR_Fld_GetNameRef(fieldDefn))
199 | existingFields[fieldName] = true
200 | }
201 |
202 | // 检查分组字段是否存在
203 | for _, groupField := range up.config.GroupFields {
204 | if !existingFields[groupField] {
205 | return fmt.Errorf("分组字段 '%s' 在图层中不存在", groupField)
206 | }
207 | }
208 |
209 | return nil
210 | }
211 |
212 | // groupFeatures 按指定字段分组要素
213 | func (up *UnionProcessor) groupFeatures(layer *GDALLayer) (map[string]*FeatureGroup, error) {
214 | groups := make(map[string]*FeatureGroup)
215 | featureCount := layer.GetFeatureCount()
216 | processedCount := 0
217 |
218 | layer.ResetReading()
219 |
220 | for {
221 | feature := layer.GetNextFeature()
222 | if feature == nil {
223 | break
224 | }
225 |
226 | // 生成分组键
227 | groupKey, fieldValues, err := up.generateGroupKey(feature)
228 | if err != nil {
229 | C.OGR_F_Destroy(feature)
230 | return nil, fmt.Errorf("生成分组键失败: %v", err)
231 | }
232 |
233 | // 获取或创建分组
234 | group, exists := groups[groupKey]
235 | if !exists {
236 | group = &FeatureGroup{
237 | GroupKey: groupKey,
238 | Features: make([]C.OGRFeatureH, 0),
239 | Fields: fieldValues,
240 | }
241 | groups[groupKey] = group
242 | }
243 |
244 | // 克隆要素并添加到分组
245 | clonedFeature := C.OGR_F_Clone(feature)
246 | if clonedFeature != nil {
247 | group.Features = append(group.Features, clonedFeature)
248 | }
249 |
250 | C.OGR_F_Destroy(feature)
251 | processedCount++
252 |
253 | // 更新进度
254 | if up.config.ProgressCallback != nil && processedCount%1000 == 0 {
255 | progress := 0.3 * float64(processedCount) / float64(featureCount)
256 | message := fmt.Sprintf("正在分组要素: %d/%d", processedCount, featureCount)
257 | if !up.config.ProgressCallback(progress, message) {
258 | // 清理已分组的要素
259 | up.cleanupGroups(groups)
260 | return nil, fmt.Errorf("操作被用户取消")
261 | }
262 | }
263 | }
264 |
265 | return groups, nil
266 | }
267 |
268 | // generateGroupKey 生成分组键
269 | func (up *UnionProcessor) generateGroupKey(feature C.OGRFeatureH) (string, map[string]string, error) {
270 | keyParts := make([]string, 0, len(up.config.GroupFields))
271 | fieldValues := make(map[string]string)
272 |
273 | for _, fieldName := range up.config.GroupFields {
274 | cFieldName := C.CString(fieldName)
275 | fieldIndex := C.OGR_F_GetFieldIndex(feature, cFieldName)
276 | C.free(unsafe.Pointer(cFieldName))
277 |
278 | if fieldIndex < 0 {
279 | return "", nil, fmt.Errorf("字段 '%s' 不存在", fieldName)
280 | }
281 |
282 | // 获取字段值作为字符串
283 | fieldValue := C.GoString(C.OGR_F_GetFieldAsString(feature, fieldIndex))
284 | keyParts = append(keyParts, fieldValue)
285 | fieldValues[fieldName] = fieldValue
286 | }
287 |
288 | // 使用分隔符连接所有字段值作为分组键
289 | groupKey := strings.Join(keyParts, "||")
290 | return groupKey, fieldValues, nil
291 | }
292 |
293 | // createOutputLayer 创建输出图层
294 | func (up *UnionProcessor) createOutputLayer(inputLayer *GDALLayer) (*GDALLayer, error) {
295 | // 获取输入图层信息
296 | inputDefn := inputLayer.GetLayerDefn()
297 | geomType := up.config.GeomType
298 | if geomType == 0 {
299 | geomType = C.OGR_FD_GetGeomType(inputDefn)
300 | }
301 | srs := inputLayer.GetSpatialRef()
302 |
303 | // 创建输出图层名称
304 | outputName := up.config.OutputLayerName
305 | if outputName == "" {
306 | outputName = "union_result"
307 | }
308 |
309 | // 创建内存图层
310 | cOutputName := C.CString(outputName)
311 | defer C.free(unsafe.Pointer(cOutputName))
312 |
313 | outputLayerH := C.createMemoryLayer(cOutputName, geomType, srs)
314 | if outputLayerH == nil {
315 | return nil, fmt.Errorf("创建输出图层失败")
316 | }
317 |
318 | // 复制字段定义
319 | fieldCount := int(C.OGR_FD_GetFieldCount(inputDefn))
320 | for i := 0; i < fieldCount; i++ {
321 | fieldDefn := C.OGR_FD_GetFieldDefn(inputDefn, C.int(i))
322 | fieldName := C.OGR_Fld_GetNameRef(fieldDefn)
323 | fieldType := C.OGR_Fld_GetType(fieldDefn)
324 |
325 | newFieldDefn := C.OGR_Fld_Create(fieldName, fieldType)
326 | C.OGR_Fld_SetWidth(newFieldDefn, C.OGR_Fld_GetWidth(fieldDefn))
327 | C.OGR_Fld_SetPrecision(newFieldDefn, C.OGR_Fld_GetPrecision(fieldDefn))
328 |
329 | err := C.OGR_L_CreateField(outputLayerH, newFieldDefn, 1)
330 | C.OGR_Fld_Destroy(newFieldDefn)
331 |
332 | if err != C.OGRERR_NONE {
333 | return nil, fmt.Errorf("创建字段失败,错误代码: %d", int(err))
334 | }
335 | }
336 |
337 | // 包装为GDALLayer
338 | outputLayer := &GDALLayer{
339 | layer: outputLayerH,
340 | dataset: nil, // 内存图层不需要dataset
341 | driver: nil,
342 | }
343 |
344 | runtime.SetFinalizer(outputLayer, (*GDALLayer).cleanup)
345 | return outputLayer, nil
346 | }
347 |
348 | // 替换原来的 performUnion 函数
349 | func (up *UnionProcessor) performUnion(groups map[string]*FeatureGroup, outputLayer *GDALLayer) (int, error) {
350 | totalGroups := len(groups)
351 | if totalGroups == 0 {
352 | return 0, nil
353 | }
354 |
355 | // 按分组键排序以确保一致的处理顺序
356 | groupKeys := make([]string, 0, len(groups))
357 | for key := range groups {
358 | groupKeys = append(groupKeys, key)
359 | }
360 | sort.Strings(groupKeys)
361 |
362 | // 创建结果通道和工作通道
363 | resultChan := make(chan unionResult, totalGroups)
364 | workChan := make(chan string, totalGroups)
365 |
366 | // 启动worker goroutines
367 | ctx, cancel := context.WithCancel(context.Background())
368 | defer cancel()
369 |
370 | var wg sync.WaitGroup
371 | for i := 0; i < up.maxWorkers; i++ {
372 | wg.Add(1)
373 | go up.unionWorker(ctx, &wg, workChan, resultChan, groups)
374 | }
375 |
376 | // 发送工作任务
377 | go func() {
378 | defer close(workChan)
379 | for _, groupKey := range groupKeys {
380 | select {
381 | case workChan <- groupKey:
382 | case <-ctx.Done():
383 | return
384 | }
385 | }
386 | }()
387 |
388 | // 等待所有worker完成
389 | go func() {
390 | wg.Wait()
391 | close(resultChan)
392 | }()
393 |
394 | // 收集结果并写入输出图层
395 | processedCount := 0
396 | completedGroups := 0
397 | outputDefn := outputLayer.GetLayerDefn()
398 |
399 | for result := range resultChan {
400 | completedGroups++
401 |
402 | if result.err != nil {
403 | fmt.Printf("警告: 分组 '%s' Union操作失败: %v\n", result.groupKey, result.err)
404 | continue
405 | }
406 |
407 | if result.geometry == nil {
408 | continue
409 | }
410 |
411 | // 应用几何精度设置
412 | finalGeometry := result.geometry
413 | if up.config.PrecisionConfig != nil && up.config.PrecisionConfig.Enabled {
414 | processedGeom, err := up.applyPrecisionSettings(result.geometry)
415 | if err != nil {
416 | fmt.Printf("警告: 分组 '%s' 精度处理失败: %v\n", result.groupKey, err)
417 | } else if processedGeom != result.geometry {
418 | C.OGR_G_DestroyGeometry(result.geometry)
419 | finalGeometry = processedGeom
420 | }
421 | }
422 |
423 | // 创建输出要素(这部分必须串行,因为GDAL写操作不是线程安全的)
424 | if up.createOutputFeature(outputLayer, outputDefn, finalGeometry, result.group) {
425 | processedCount++
426 | }
427 |
428 | // 清理几何体
429 | C.OGR_G_DestroyGeometry(finalGeometry)
430 |
431 | // 更新进度
432 | if up.config.ProgressCallback != nil && completedGroups%10 == 0 {
433 | progress := 0.1 + 0.9*float64(completedGroups)/float64(totalGroups)
434 | message := fmt.Sprintf("正在合并数据: %d/%d 组", completedGroups, totalGroups)
435 | if !up.config.ProgressCallback(progress, message) {
436 | cancel() // 取消所有worker
437 | break
438 | }
439 | }
440 | }
441 |
442 | // 清理分组数据
443 | up.cleanupGroups(groups)
444 |
445 | if ctx.Err() != nil {
446 | return processedCount, fmt.Errorf("操作被用户取消")
447 | }
448 |
449 | return processedCount, nil
450 | }
451 |
452 | // 添加worker函数
453 | func (up *UnionProcessor) unionWorker(ctx context.Context, wg *sync.WaitGroup, workChan <-chan string, resultChan chan<- unionResult, groups map[string]*FeatureGroup) {
454 | defer wg.Done()
455 |
456 | for {
457 | select {
458 | case groupKey, ok := <-workChan:
459 | if !ok {
460 | return
461 | }
462 |
463 | group := groups[groupKey]
464 | geometry, err := up.unionGroupGeometries(group.Features)
465 |
466 | result := unionResult{
467 | groupKey: groupKey,
468 | geometry: geometry,
469 | group: group,
470 | err: err,
471 | }
472 |
473 | select {
474 | case resultChan <- result:
475 | case <-ctx.Done():
476 | // 如果操作被取消,清理几何体
477 | if geometry != nil {
478 | C.OGR_G_DestroyGeometry(geometry)
479 | }
480 | return
481 | }
482 |
483 | case <-ctx.Done():
484 | return
485 | }
486 | }
487 | }
488 |
489 | // 添加创建输出要素的辅助函数(串行执行,确保GDAL写操作的线程安全)
490 | func (up *UnionProcessor) createOutputFeature(outputLayer *GDALLayer, outputDefn C.OGRFeatureDefnH, geometry C.OGRGeometryH, group *FeatureGroup) bool {
491 | // 创建输出要素
492 | outputFeature := C.OGR_F_Create(outputDefn)
493 | if outputFeature == nil {
494 | return false
495 | }
496 | defer C.OGR_F_Destroy(outputFeature)
497 |
498 | // 设置几何体
499 | setGeomErr := C.OGR_F_SetGeometry(outputFeature, geometry)
500 | if setGeomErr != C.OGRERR_NONE {
501 | return false
502 | }
503 |
504 | // 复制分组字段的值到输出要素
505 | up.copyGroupFieldsToFeature(outputFeature, group, outputDefn)
506 |
507 | // 添加要素到输出图层
508 | createErr := C.OGR_L_CreateFeature(outputLayer.layer, outputFeature)
509 | return createErr == C.OGRERR_NONE
510 | }
511 |
512 | func (up *UnionProcessor) applyPrecisionSettings(geom C.OGRGeometryH) (C.OGRGeometryH, error) {
513 | if up.config.PrecisionConfig == nil || !up.config.PrecisionConfig.Enabled {
514 | return geom, nil
515 | }
516 |
517 | // 检查几何体是否有效
518 | if C.OGR_G_IsValid(geom) == 0 {
519 | // 尝试修复无效几何体
520 | fixedGeom := C.OGR_G_MakeValid(geom)
521 | if fixedGeom != nil {
522 | return fixedGeom, nil
523 | }
524 | return geom, fmt.Errorf("无法修复无效几何体")
525 | }
526 |
527 | // 如果设置了网格大小,使用正确的精度设置方法
528 | if up.config.PrecisionConfig.GridSize > 0 {
529 | // 构建精度设置标志
530 | flags := up.config.PrecisionConfig.getFlags()
531 |
532 | // 使用C函数设置精度
533 | preciseGeom := C.setPrecisionIfNeeded(geom, C.double(up.config.PrecisionConfig.GridSize), flags)
534 | if preciseGeom != nil && preciseGeom != geom {
535 | return preciseGeom, nil
536 | }
537 | }
538 |
539 | return geom, nil
540 | }
541 |
542 | // unionGroupGeometries 对一组要素的几何体执行Union操作
543 | func (up *UnionProcessor) unionGroupGeometries(features []C.OGRFeatureH) (C.OGRGeometryH, error) {
544 | if len(features) == 0 {
545 | return nil, fmt.Errorf("要素列表为空")
546 | }
547 |
548 | if len(features) == 1 {
549 | // 只有一个要素,直接克隆其几何体
550 | geom := C.OGR_F_GetGeometryRef(features[0])
551 | if geom == nil {
552 | return nil, fmt.Errorf("要素几何体为空")
553 | }
554 | return C.OGR_G_Clone(geom), nil
555 | }
556 |
557 | // 获取第一个几何体作为起始
558 | firstGeom := C.OGR_F_GetGeometryRef(features[0])
559 | if firstGeom == nil {
560 | return nil, fmt.Errorf("第一个要素几何体为空")
561 | }
562 |
563 | resultGeom := C.OGR_G_Clone(firstGeom)
564 | if resultGeom == nil {
565 | return nil, fmt.Errorf("克隆第一个几何体失败")
566 | }
567 |
568 | // 逐个与其他几何体进行Union
569 | for i := 1; i < len(features); i++ {
570 | currentGeom := C.OGR_F_GetGeometryRef(features[i])
571 | if currentGeom == nil {
572 | continue
573 | }
574 |
575 | // 执行Union操作
576 | unionResult := C.OGR_G_Union(resultGeom, currentGeom)
577 | if unionResult == nil {
578 | fmt.Printf("警告: Union操作失败,跳过要素 %d\n", i)
579 | continue
580 | }
581 |
582 | // 规范化几何类型
583 | expectedType := C.OGR_G_GetGeometryType(resultGeom)
584 | normalizedGeom := C.normalizeGeometryType(unionResult, expectedType)
585 |
586 | // 更新结果几何体
587 | C.OGR_G_DestroyGeometry(resultGeom)
588 |
589 | if normalizedGeom != unionResult {
590 | C.OGR_G_DestroyGeometry(unionResult)
591 | resultGeom = normalizedGeom
592 | } else {
593 | resultGeom = unionResult
594 | }
595 |
596 | if resultGeom == nil {
597 | return nil, fmt.Errorf("Union操作后几何体为空")
598 | }
599 | }
600 |
601 | return resultGeom, nil
602 | }
603 |
604 | // copyGroupFieldsToFeature 复制分组字段值到输出要素
605 | func (up *UnionProcessor) copyGroupFieldsToFeature(outputFeature C.OGRFeatureH, group *FeatureGroup, outputDefn C.OGRFeatureDefnH) {
606 | if len(group.Features) == 0 {
607 | return
608 | }
609 |
610 | // 使用第一个要素作为字段值的来源
611 | sourceFeature := group.Features[0]
612 |
613 | // 复制所有字段值
614 | fieldCount := int(C.OGR_FD_GetFieldCount(outputDefn))
615 | for i := 0; i < fieldCount; i++ {
616 | fieldDefn := C.OGR_FD_GetFieldDefn(outputDefn, C.int(i))
617 | fieldName := C.GoString(C.OGR_Fld_GetNameRef(fieldDefn))
618 |
619 | // 获取源要素中对应字段的索引
620 | cFieldName := C.CString(fieldName)
621 | sourceFieldIndex := C.OGR_F_GetFieldIndex(sourceFeature, cFieldName)
622 | C.free(unsafe.Pointer(cFieldName))
623 |
624 | // 修复:将C函数返回值转换为bool
625 | if sourceFieldIndex >= 0 && C.OGR_F_IsFieldSet(sourceFeature, sourceFieldIndex) != 0 {
626 | // 复制字段值
627 | C.copyFieldValue(sourceFeature, outputFeature, sourceFieldIndex, C.int(i))
628 | }
629 | }
630 | }
631 |
632 | // cleanupGroups 清理分组数据
633 | func (up *UnionProcessor) cleanupGroups(groups map[string]*FeatureGroup) {
634 | for _, group := range groups {
635 | for _, feature := range group.Features {
636 | if feature != nil {
637 | C.OGR_F_Destroy(feature)
638 | }
639 | }
640 | group.Features = nil
641 | }
642 | }
643 |
644 | // UnionByFieldsWithPrecision 便捷函数:按指定字段执行Union操作(带精度控制)
645 | func UnionByFieldsWithPrecision(inputLayer *GDALLayer, groupFields []string, outputLayerName string,
646 | precisionConfig *GeometryPrecisionConfig, progressCallback ProgressCallback) (*GeosAnalysisResult, error) {
647 |
648 | config := &UnionConfig{
649 | GroupFields: groupFields,
650 | OutputLayerName: outputLayerName,
651 | PrecisionConfig: precisionConfig,
652 | ProgressCallback: progressCallback,
653 | }
654 |
655 | processor := NewUnionProcessor(config)
656 | return processor.ProcessUnion(inputLayer)
657 | }
658 |
659 | // Close 关闭Union结果,释放资源
660 | func (ur *GeosAnalysisResult) Close() {
661 | if ur.OutputLayer != nil {
662 | ur.OutputLayer.Close()
663 | ur.OutputLayer = nil
664 | }
665 | }
666 |
--------------------------------------------------------------------------------