通过使用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");