├── 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 | --------------------------------------------------------------------------------