將 MapInfo (.tab) 空間數據遷移至 SQL Server 2008 空間類型時,遭遇兩大核心問題:
geography 要求多邊形外環逆時針、內環順時針,且單一實例不超過半球,違規即拋出 24205 錯誤。geography::STGeomFromText(wkt, 4326) 插入腳本核心邏輯:計算環有向面積(鞋帶公式),判斷順逆時針;按 Geography 規則調整:
public static class WktFixer
{
// 鞋帶公式計算有向面積,返回 true 表示順時針
private static bool IsRingClockwise(IList ring)
{
double area = 0;
for (int i = 1; i < ring.Count; i++)
area += ring[i-1].X * ring[i].Y - ring[i].X * ring[i-1].Y;
area += ring[ring.Count-1].X * ring[0].Y - ring[0].X * ring[ring.Count-1].Y;
return area < 0; // 負值為順時針
}
public static string FixPolygonOrientation(string wkt)
{
if (!Regex.IsMatch(wkt, @"POLYGON", RegexOptions.IgnoreCase))
return wkt; // 僅處理 POLYGON
var matches = Regex.Matches(wkt, @"\(([^()]+)\)");
string result = wkt;
for (int i = matches.Count - 1; i >= 0; i--)
{
var m = matches[i];
string coords = m.Value.Trim('(', ')');
var pts = coords.Split(',')
.Select(s => s.Trim().Split(' '))
.Where(p => p.Length == 2)
.Select(p => new Point(double.Parse(p[0]), double.Parse(p[1])))
.ToList();
if (pts.Count < 4 || pts.First().X != pts.Last().X || pts.First().Y != pts.Last().Y)
continue; // 非閉合環
bool isClockwise = IsRingClockwise(pts);
// 外環(i=0)應逆時針,內環應順時針
bool needReverse = (i == 0) ? isClockwise : !isClockwise;
if (needReverse)
{
pts.Reverse();
string fixedRing = "(" + string.Join(",", pts.Select(p => $"{p.X:F8} {p.Y:F8}")) + ")";
result = result.Remove(m.Index, m.Length).Insert(m.Index, fixedRing);
}
}
return result;
}
}
POLYGON,實際 WKT 為 GEOMETRYCOLLECTION。對策:解析 WKT 類型標籤,分類處理。TODO 註釋指出內環不應整體反轉。修正:分別判斷每個環方向,僅在違規時單環反轉。geometry 平面類型,或拆分為多個 geography 實例。List.ToArray()、Lambda 等 3.5+ 特性,改用 ArrayList、匿名委託。CharacterSet=UTF-8)。WktFixer.FixPolygonOrientation → geometry::STGeomFromText(平面類型,無半球限制)。UPDATE 修正殘留拓撲錯誤(MakeValid())。經驗總結:空間數據遷移 80% 工作量在數據清洗與拓撲修正。GDAL/OGR 是強大的讀取引擎,但 WKT 輸出需二次驗證;SQL Server 空間類型選型(Geometry vs Geography)應由業務範圍決定,全球/大範圍數據首選 Geometry。