通过使用ZipFile解压KMZ文件,获取其中的KML文件,并解析KML文件,输出解析后的坐标数据集。
KML文件:地理信息的标准格式
解析后的坐标数据集输出格式(GEOJSON坐标数据集):[[[经度,纬度],[经度,纬度]]]
解析类
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO.Compression;
using System.IO;
using System.Xml;
using System.Linq;namespace WaterCloud.Code
{public static class KMZHelper{/// <summary>/// 解压 KMZ 文件到指定目录。/// </summary>/// <param name="kmzFilePath">kmz文件地址</param>/// <param name="extractDirectory">解压路径</param>/// <returns></returns>public static PlotCoordinateClass HandleKMZ(string kmzFilePath, string extractDirectory){PlotCoordinateClass plotCoordinate = new PlotCoordinateClass();try{if (!File.Exists(kmzFilePath)){Console.WriteLine($"错误: 文件 '{kmzFilePath}' 不存在。");LogHelper.WriteWithTime($"错误: 文件 '{kmzFilePath}' 不存在。");plotCoordinate.message = $"错误: 文件 '{kmzFilePath}' 不存在。";plotCoordinate.code = 1;return plotCoordinate;}if (string.IsNullOrWhiteSpace(extractDirectory)){// 使用临时目录extractDirectory = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());Console.WriteLine($"使用临时目录: {extractDirectory}");LogHelper.WriteWithTime($"使用临时目录: {extractDirectory}");}// 确保目录存在Directory.CreateDirectory(extractDirectory);// 解压 KMZ 文件ExtractKMZFile(kmzFilePath, extractDirectory);Console.WriteLine("KMZ 文件解压完成。");LogHelper.WriteWithTime($"KMZ 文件解压完成。");// 查找并读取 KML 文件string[] kmlFiles = Directory.GetFiles(extractDirectory, "*.kml", SearchOption.AllDirectories);if (kmlFiles.Length == 0){Console.WriteLine("警告: 在解压文件中未找到 KML 文件。");LogHelper.WriteWithTime("警告: 在解压文件中未找到 KML 文件。");plotCoordinate.message = ("警告: 在解压文件中未找到 KML 文件。");plotCoordinate.code = 1;return plotCoordinate;}Console.WriteLine($"找到 {kmlFiles.Length} 个 KML 文件:");LogHelper.WriteWithTime($"找到 {kmlFiles.Length} 个 KML 文件:");// 处理第一个 KML 文件plotCoordinate = KMLHelper.HandleKML(kmlFiles[0]);Console.WriteLine("\n处理完成。");LogHelper.WriteWithTime("\n处理完成。");return plotCoordinate;}catch (Exception ex){Console.WriteLine($"发生错误: {ex.Message}");LogHelper.WriteWithTime($"发生错误: {ex.Message}");plotCoordinate.message = $"发生错误: {ex.Message}";plotCoordinate.code = 1;return plotCoordinate;}}/// <summary>/// 解压 KMZ 文件到指定目录。/// </summary>/// <param name="kmzFilePath">kmz文件地址</param>/// <param name="extractDirectory">解压路径</param>public static void ExtractKMZFile(string kmzFilePath, string extractDirectory){try{using (ZipArchive archive = ZipFile.OpenRead(kmzFilePath)){foreach (ZipArchiveEntry entry in archive.Entries){string entryPath = Path.Combine(extractDirectory, entry.FullName);string entryDirectory = Path.GetDirectoryName(entryPath);// 确保目录存在if (!Directory.Exists(entryDirectory)){Directory.CreateDirectory(entryDirectory);}// 跳过目录条目if (entry.Length == 0){continue;}// 提取文件entry.ExtractToFile(entryPath, true);}}}catch (Exception ex){Console.WriteLine($"解压错误: {ex.Message}");throw;}}/// <summary>/// 截断字符串,如果长度超过指定最大长度则添加省略号。/// </summary>/// <param name="value"></param>/// <param name="maxLength"></param>/// <returns></returns>public static string TruncateString(string value, int maxLength){if (string.IsNullOrEmpty(value)){return value;}return value.Length <= maxLength? value: value.Substring(0, maxLength) + "...";}}public static class KMLHelper{/// <summary>/// 解析 KML 文件并提取坐标信息。/// </summary>/// <param name="kmlFilePath">kml文件地址</param>/// <returns></returns>public static PlotCoordinateClass HandleKML(string kmlFilePath){PlotCoordinateClass plotCoordinate = new PlotCoordinateClass();try{if (!File.Exists(kmlFilePath)){Console.WriteLine($"错误: 文件 '{kmlFilePath}' 不存在。");LogHelper.WriteWithTime($"错误: 文件 '{kmlFilePath}' 不存在。");plotCoordinate.message = ($"错误: 文件 '{kmlFilePath}' 不存在。");plotCoordinate.code = 1;return plotCoordinate;}// 加载 KML 文件XmlDocument kmlDoc = new XmlDocument();kmlDoc.Load(kmlFilePath);// 创建命名空间管理器,处理 KML 命名空间XmlNamespaceManager nsmgr = new XmlNamespaceManager(kmlDoc.NameTable);nsmgr.AddNamespace("kml", "http://www.opengis.net/kml/2.2");// 解析基本信息List<PlotCoordinateClass> plotCoordinates = ParseKmlDocument(kmlDoc, nsmgr);if (plotCoordinates != null && plotCoordinates.Count > 0) {// 取最大的坐标点数作为结果,如果有多个则取最大的一个plotCoordinate = plotCoordinates.OrderByDescending(a => a.PlotCoordinateCount).First();}Console.WriteLine("\n解析完成。");LogHelper.WriteWithTime("\n解析完成。");return plotCoordinate;}catch (Exception ex){Console.WriteLine($"发生错误: {ex.Message}");LogHelper.WriteWithTime($"发生错误: {ex.Message}");plotCoordinate.message = ($"发生错误: {ex.Message}");plotCoordinate.code = 1;return plotCoordinate;}}static List<PlotCoordinateClass> ParseKmlDocument(XmlDocument kmlDoc, XmlNamespaceManager nsmgr){List<PlotCoordinateClass> plotCoordinates = new List<PlotCoordinateClass>();// 获取文档名称XmlNode documentNode = kmlDoc.SelectSingleNode("//kml:Document", nsmgr);if (documentNode != null){XmlNode nameNode = documentNode.SelectSingleNode("kml:name", nsmgr);if (nameNode != null){Console.WriteLine($"文档名称: {nameNode.InnerText}");}// 解析文件夹plotCoordinates =ParseFolders(documentNode, nsmgr);// 解析地点标记//plotCoordinates = ParsePlacemarks(documentNode, nsmgr);}return plotCoordinates;}/// <summary>/// 解析文件夹及其子节点(地点标记和嵌套的文件夹)/// </summary>/// <param name="parentNode"></param>/// <param name="nsmgr"></param>/// <returns></returns>static List<PlotCoordinateClass> ParseFolders(XmlNode parentNode, XmlNamespaceManager nsmgr){List<PlotCoordinateClass> plotCoordinates = new List<PlotCoordinateClass>();XmlNodeList folderNodes = parentNode.SelectNodes("kml:Folder", nsmgr);if (folderNodes != null && folderNodes.Count > 0){Console.WriteLine("\n发现 {0} 个文件夹:", folderNodes.Count);foreach (XmlNode folderNode in folderNodes){XmlNode nameNode = folderNode.SelectSingleNode("kml:name", nsmgr);string folderName = nameNode != null ? nameNode.InnerText : "[未命名文件夹]";Console.WriteLine($" - 文件夹: {folderName}");// 递归解析文件夹中的地点标记List<PlotCoordinateClass> result = ParsePlacemarks(folderNode, nsmgr);plotCoordinates.AddRange(result);// 递归解析子文件夹plotCoordinates.AddRange(ParseFolders(folderNode, nsmgr));}}return plotCoordinates;}/// <summary>/// 解析地点标记(点、线、多边形)/// </summary>/// <param name="parentNode"></param>/// <param name="nsmgr"></param>/// <returns></returns>static List<PlotCoordinateClass> ParsePlacemarks(XmlNode parentNode, XmlNamespaceManager nsmgr){List<PlotCoordinateClass> plotCoordinateClasses = new List<PlotCoordinateClass>();XmlNodeList placemarkNodes = parentNode.SelectNodes("kml:Placemark", nsmgr);if (placemarkNodes != null && placemarkNodes.Count > 0){Console.WriteLine("\n发现 {0} 个地点标记:", placemarkNodes.Count);foreach (XmlNode placemarkNode in placemarkNodes){PlotCoordinateClass plotCoordinate = null;// 获取地点标记名称XmlNode nameNode = placemarkNode.SelectSingleNode("kml:name", nsmgr);string placemarkName = nameNode != null ? nameNode.InnerText : "[未命名地点]";Console.WriteLine($" - 地点: {placemarkName}");// 解析点XmlNode pointNode = placemarkNode.SelectSingleNode("kml:c", nsmgr);if (pointNode != null){ParsePoint(pointNode, nsmgr);}// 解析多边形XmlNode MultiGeometryNode = placemarkNode.SelectSingleNode("kml:MultiGeometry", nsmgr);if (MultiGeometryNode != null){XmlNode polygonNode = MultiGeometryNode.SelectSingleNode("kml:Polygon", nsmgr);if (polygonNode != null){plotCoordinate = ParsePolygon(polygonNode, nsmgr);if (plotCoordinate != null) plotCoordinateClasses.Add(plotCoordinate);}// 解析线XmlNodeList lineStringNodes = MultiGeometryNode.SelectNodes("kml:LineString", nsmgr);if (lineStringNodes != null && lineStringNodes.Count > 0){List< PlotCoordinateClass > tempList = new List<PlotCoordinateClass>();//获取所有线串节点数据foreach (XmlNode node in lineStringNodes) {plotCoordinate = ParseLineString(node, nsmgr);if (plotCoordinate != null) tempList.Add(plotCoordinate);}//闭合多边形区域,将多个线串整合为一个闭合的多边形区域,并添加处理结果到返回集合中。plotCoordinateClasses.Add(ClosePolygon(tempList));}}}}return plotCoordinateClasses;}/// <summary>/// 点处理方法/// </summary>/// <param name="pointNode"></param>/// <param name="nsmgr"></param>static void ParsePoint(XmlNode pointNode, XmlNamespaceManager nsmgr){XmlNode coordinatesNode = pointNode.SelectSingleNode("kml:coordinates", nsmgr);if (coordinatesNode != null && !string.IsNullOrWhiteSpace(coordinatesNode.InnerText)){string[] coords = coordinatesNode.InnerText.Trim().Split(',');if (coords.Length >= 2){double longitude = double.Parse(coords[0]);double latitude = double.Parse(coords[1]);double? altitude = coords.Length > 2 ? (double?)double.Parse(coords[2]) : null;Console.WriteLine($" 点坐标: 经度={longitude}, 纬度={latitude}{(altitude.HasValue ? $", 高度={altitude.Value}" : "")}");}}}/// <summary>/// 线处理方法/// </summary>/// <param name="lineStringNode"></param>/// <param name="nsmgr"></param>/// <returns></returns>private static PlotCoordinateClass ParseLineString(XmlNode lineStringNode, XmlNamespaceManager nsmgr){PlotCoordinateClass plotCoordinate = null;XmlNode coordinatesNode = lineStringNode.SelectSingleNode("kml:coordinates", nsmgr);if (coordinatesNode != null && !string.IsNullOrWhiteSpace(coordinatesNode.InnerText)){plotCoordinate = GetPlotCoordinate(coordinatesNode.InnerText);//string[] pointStrings = coordinatesNode.InnerText.Trim().Split(new[] { ' ', '\t', '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries);//Console.WriteLine($" 线串: {pointStrings.Length} 个点");}return plotCoordinate;}/// <summary>/// 多线闭环处理方法,将多个线串整合为一个闭合的多边形区域/// </summary>/// <param name="plotCoordinateClasses">多线集合</param>/// <returns></returns>private static PlotCoordinateClass ClosePolygon(List<PlotCoordinateClass> plotCoordinateClasses){PlotCoordinateClass plotCoordinate = new PlotCoordinateClass();//plotCoordinateClasses中的PlotCoordinates数据格式为[[[经度,纬度],[经度,纬度]]],需要将这些数据整合载入到一个List中List<List<List<double>>> plotCoordinates = new List<List<List<double>>>();if (plotCoordinateClasses != null && plotCoordinateClasses.Count > 0) {if (plotCoordinateClasses.Count == 1) {//如果只有一个区域,则直接返回第一个区域数据plotCoordinate = plotCoordinateClasses.First();}else {//处理多区域数据,将多个区域整合为一个区域foreach (PlotCoordinateClass item in plotCoordinateClasses){List<List<List<double>>> temp = JsonConvert.DeserializeObject<List<List<List<double>>>>(item.PlotCoordinates);if (temp != null && temp.Count > 0) {plotCoordinates.Add(temp.First());}}//调用序列化坐标数据方法获取处理结果List<List<List<double>>> result = SerializePlotCoordinate(plotCoordinates, plotCoordinates.First());if(result != null && result.Count > 0) {//创建新的坐标数据集合并将序列化结果赋值给新的坐标数据List<List<List<double>>> newCoordinates = new List<List<List<double>>>();List<List<double>> newNodes = new List<List<double>>();foreach (List<List<double>> item in result) {foreach (List<double> node in item) {newNodes.Add(node);}}newCoordinates.Add(newNodes);//将处理后的坐标数据赋值给PlotCoordinate对象plotCoordinate.PlotCoordinates = JsonConvert.SerializeObject(newCoordinates);plotCoordinate.CententCoordinate = JsonConvert.SerializeObject(newCoordinates.First().First());plotCoordinate.PlotCoordinateCount = newCoordinates.First().Count;plotCoordinate.code = 0;plotCoordinate.message = "处理成功";}}}return plotCoordinate;}/// <summary>/// 递归序列化区域坐标数据/// </summary>/// <param name="plotCoordinates">坐标数据集合,格式为"[[[经度,纬度],[经度,纬度]],[[经度,纬度],[经度,纬度]]]"</param>/// <param name="coordinates">初始依据坐标数据集,根据该参数处理后续节点内容</param>/// <returns></returns>private static List<List<List<double>>> SerializePlotCoordinate(List<List<List<double>>> plotCoordinates, List<List<double>> coordinates) {List<List<List<double>>> result = new List<List<List<double>>>();//将传入的坐标数据添加到结果中result.Add(coordinates);List<List<double>> nextCoordinates = new List<List<double>>();int deleteIndex = -1;//数据集删除索引//遍历传入的坐标数据集,判断当前传入坐标数据的后一个数据List<double> coordinatesLastNode = coordinates.Last();for (int i = 0; i < plotCoordinates.Count; i++) {List < List<double> > temp = plotCoordinates[i];temp = temp.Distinct().ToList();List<double> tempLastNode = temp.Last();List<double> tempFirstNode = temp.First();if (coordinatesLastNode.First().Equals(tempFirstNode.First())&& coordinatesLastNode.Last().Equals(tempFirstNode.Last())){//说明当前遍历数据temp的第一个节点与coordinates的最后一个节点相同,直接将temp的第一个节点删除,并将剩余数据添加到nextCoordinates中temp.RemoveAt(0);nextCoordinates.AddRange(temp);deleteIndex = i;//获取删除索引break;}else if (coordinatesLastNode.First().Equals(tempLastNode.First())&& coordinatesLastNode.Last().Equals(tempLastNode.Last())){//说明当前遍历数据temp的最后一个节点与coordinates的最后一个节点相同,需要将temp翻转后,删除第一个节点,并将剩余数据添加到nextCoordinates中temp.Reverse();temp.RemoveAt(0);nextCoordinates.AddRange(temp);deleteIndex = i;//获取删除索引break;}else {//未匹配的情况,跳出当前循环,遍历下一个节点continue;}}if (deleteIndex != -1) { //如果存在删除索引,则从数据集中删除该节点plotCoordinates.RemoveAt(deleteIndex);}if (plotCoordinates != null && plotCoordinates.Count > 0){//如果数据集不为空,则继续递归调用该方法result.AddRange(SerializePlotCoordinate(plotCoordinates, nextCoordinates));}else {//如果数据集为空,则将nextCoordinates添加到结果中result.Add(nextCoordinates);}return result;}/// <summary>/// 区域处理方法/// </summary>/// <param name="polygonNode"></param>/// <param name="nsmgr"></param>/// <returns></returns>private static PlotCoordinateClass ParsePolygon(XmlNode polygonNode, XmlNamespaceManager nsmgr){PlotCoordinateClass plotCoordinate = null;XmlNode outerBoundaryNode = polygonNode.SelectSingleNode("kml:outerBoundaryIs", nsmgr);if (outerBoundaryNode != null){XmlNode linearRingNode = outerBoundaryNode.SelectSingleNode("kml:LinearRing", nsmgr);if (linearRingNode != null){XmlNode coordinatesNode = linearRingNode.SelectSingleNode("kml:coordinates", nsmgr);if (coordinatesNode != null && !string.IsNullOrWhiteSpace(coordinatesNode.InnerText)){//string[] pointStrings = coordinatesNode.InnerText.Trim().Split(new[] { '0',' ', '\t', '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries);//获取区域坐标集合plotCoordinate = GetPlotCoordinate(coordinatesNode.InnerText);}}}// 解析内圈(洞)XmlNodeList innerBoundaryNodes = polygonNode.SelectNodes("kml:innerBoundaryIs", nsmgr);if (innerBoundaryNodes != null && innerBoundaryNodes.Count > 0){Console.WriteLine($" 多边形内圈: {innerBoundaryNodes.Count} 个");foreach (XmlNode innerBoundaryNode in innerBoundaryNodes){XmlNode linearRingNode = innerBoundaryNode.SelectSingleNode("kml:LinearRing", nsmgr);if (linearRingNode != null){XmlNode coordinatesNode = linearRingNode.SelectSingleNode("kml:coordinates", nsmgr);if (coordinatesNode != null && !string.IsNullOrWhiteSpace(coordinatesNode.InnerText)){plotCoordinate = GetPlotCoordinate(coordinatesNode.InnerText);string[] pointStrings = coordinatesNode.InnerText.Trim().Split(new[] { ' ', '\t', '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries);Console.WriteLine($" 内圈: {pointStrings.Length} 个点");}}}}return plotCoordinate;}/// <summary>/// 获取区域坐标/// </summary>/// <param name="InnerText"></param>/// <returns></returns>private static PlotCoordinateClass GetPlotCoordinate(string InnerText){PlotCoordinateClass plotCoordinate = new PlotCoordinateClass();List<string> coordinates = InnerText.Trim().Split(",0").ToList();if (coordinates != null && coordinates.Count > 0){coordinates = coordinates.Where(a => !string.IsNullOrEmpty(a)).ToList();coordinates.ForEach(a =>{a = a.Trim();});}plotCoordinate.code = 0;List<List<List<double>>> PlotCoordinates = GetCoordinatesList(coordinates);if ((PlotCoordinates != null && PlotCoordinates.Count > 0) &&(PlotCoordinates[0] != null && PlotCoordinates[0].Count > 0)){plotCoordinate.PlotCoordinates = JsonConvert.SerializeObject(PlotCoordinates);plotCoordinate.CententCoordinate = JsonConvert.SerializeObject(PlotCoordinates[0][0]);plotCoordinate.PlotCoordinateCount = PlotCoordinates[0].Count;plotCoordinate.code = 0;plotCoordinate.message = "数据获取成功";}else{plotCoordinate.code = 1;plotCoordinate.message = "数据获取失败";}return plotCoordinate;}/// <summary>/// 获取坐标集合/// </summary>/// <param name="coordinates"></param>/// <returns></returns>private static List<List<List<double>>> GetCoordinatesList(List<string> coordinates){List<List<List<double>>> result = new List<List<List<double>>>();List<List<double>> secondList = new List<List<double>>();foreach (string item in coordinates){//List<double> ints = item.Split(",").Select(a => Math.Round(a.ToDouble(), 6)).ToList();List<double> ints = item.Split(",").Select(a => a.ToDouble()).ToList();if (ints != null && ints.Count > 1) {var gcjCoord = WGS84ToGCJ02Helper.Wgs84ToGcj02(ints[0], ints[1]);ints[0] = gcjCoord.lng;ints[1] = gcjCoord.lat;secondList.Add(ints);}}result.Add(secondList);return result;}}/// <summary>/// 区域坐标类/// </summary>public class PlotCoordinateClass{/// <summary>/// 地块坐标集合,格式为"[[[经度,纬度],[经度,纬度]]]"/// </summary>public string PlotCoordinates { get; set; }/// <summary>/// 地块中心坐标,格式为"[经度,纬度]"/// </summary>public string CententCoordinate { get; set; }/// <summary>/// 区域坐标数量/// </summary>public int PlotCoordinateCount { get; set; }/// <summary>/// 解析状态码,0为成功,1为失败。默认为0/// </summary>public int code { get; set; }/// <summary>/// 解析状态信息,默认为"成功"。失败时为具体错误描述。/// </summary>public string message { get; set; }}}
调用
PlotCoordinateClass plotCoordinate = null;
//调用解压KMZ文件并解析解压后的KML文件并返回结果
plotCoordinate = KMZHelper.HandleKMZ("/your/kmzFile/path","/your/kmzFile/extractDirectory");
//调用解析KML文件并返回解析结果
plotCoordinate = KMLHelper.HandleKML("/your/kmlFile/path");