当前位置: 首页 > news >正文

【C#地图显示教程:实现鼠标绘制图形操作】

C#地图显示教程:实现鼠标绘制图形操作

    • 一、环境准备与项目创建
      • 1.1 开发环境要求
      • 1.2 创建项目
    • 二、基础地图实现
      • 2.1 添加GMapControl控件
      • 2.2 常见问题解决
    • 三、图形绘制功能实现
      • 3.1 绘制模式定义
      • 3.2 线段绘制实现
      • 3.3 圆形绘制算法
    • 四、图形编辑功能
      • 4.1 对象选择与调整
      • 4.2 右键交互设计
    • 五、数据持久化
      • 5.1 JSON序列化模型
      • 5.2 保存/加载实现
    • 六、完整代码
    • 7.2 显示效果如下
    • 七、参考资源

一、环境准备与项目创建

1.1 开发环境要求

  • Visual Studio 2019/2022
  • .NET Framework 4.7.2+
  • GMap.NET.Core & GMap.NET.WindowsForms NuGet包

1.2 创建项目

  1. 新建Windows Forms应用(.NET Framework)
    在这里插入图片描述

  2. 通过NuGet安装:

   Install-Package GMap.NET.CoreInstall-Package GMap.NET.WindowsFormsInstall-Package Newtonsoft.Json

在这里插入图片描述

二、基础地图实现

2.1 添加GMapControl控件

private void InitializeMap()
{gMapControl1.MapProvider = GMapProviders.OpenStreetMap;gMapControl1.Position = new PointLatLng(38.865625, 121.534452); // 大连海事大学坐标gMapControl1.MinZoom = 1;gMapControl1.MaxZoom = 25;gMapControl1.Zoom = 18;gMapControl1.DragButton = MouseButtons.Left;
}

2.2 常见问题解决

  • 地图空白问题:设置GMapProvider.WebProxy
  • 跨线程访问:使用Invoke方法

三、图形绘制功能实现

3.1 绘制模式定义

private enum DrawMode { None, Line, Circle, Marker, Adjust }
private DrawMode currentMode = DrawMode.None;

3.2 线段绘制实现

private void btnDrawLine_Click(object sender, EventArgs e)
{currentMode = DrawMode.Line;linePoints.Clear();UpdateStatusText("左键添加点,中键结束");
}private void gMapControl1_MouseDown(object sender, MouseEventArgs e)
{if (e.Button  MouseButtons.Left && currentMode  DrawMode.Line){linePoints.Add(gMapControl1.FromLocalToLatLng(e.X, e.Y));UpdateLinePreview();}
}

3.3 圆形绘制算法

private GMapPolygon CreateCircle(PointLatLng center, double radius)
{List<PointLatLng> points = new List<PointLatLng>();for (int i = 0; i <= 360; i += 10){double angle = i * Math.PI / 180;double lat = center.Lat + (radius / 111.32) * Math.Cos(angle);double lng = center.Lng + (radius / (111.32  Math.Cos(center.Lat  Math.PI / 180))) * Math.Sin(angle);points.Add(new PointLatLng(lat, lng));}return new GMapPolygon(points, "circle");
}

四、图形编辑功能

4.1 对象选择与调整

private void HandleAdjustment(PointLatLng currentPoint)
{if (selectedObject is GMapMarker marker){marker.Position = currentPoint; // 移动标记}else if (selectedObject is GMapPolygon polygon){// 调整圆形半径double newRadius = GetDistance(originalCenter, currentPoint);currentCircle = CreateCircle(originalCenter, newRadius);}
}

4.2 右键交互设计

操作功能
左键点击添加/移动图形
中键点击删除图形
右键点击进入调整模式

五、数据持久化

5.1 JSON序列化模型

private class MapData
{public List<MarkerData> Markers { get; set; }public List<RouteData> Routes { get; set; }public List<CircleData> Circles { get; set; }
}

5.2 保存/加载实现

// 保存图形
string json = JsonConvert.SerializeObject(data, Formatting.Indented);
File.WriteAllText(saveFileDialog.FileName, json);// 加载图形
MapData data = JsonConvert.DeserializeObject<MapData>(json);

六、完整代码

using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Windows.Forms;
using GMap.NET;
using GMap.NET.MapProviders;
using GMap.NET.WindowsForms;
using GMap.NET.WindowsForms.Markers;
using System.Text;
using Newtonsoft.Json;
using System.Linq;
using System.Xml;
using Formatting = Newtonsoft.Json.Formatting; // 明确指定使用Newtonsoft.Json的Formatting
namespace test_gmap
{public partial class Form1 : Form{private GMapOverlay lay_manual = new GMapOverlay("lay_manual");private Pen pen_manual = new Pen(Color.Blue, 2);// 绘图状态private enum DrawMode { None, Line, Circle, Marker, Adjust }private DrawMode currentMode = DrawMode.None;private PointLatLng startPoint;private GMapMarker currentMarker;  // 添加currentMarker声明private GMapRoute currentLine;     // 添加currentLine声明private GMapPolygon currentCircle; // 添加currentCircle声明private List<PointLatLng> linePoints = new List<PointLatLng>();// 交互状态private bool isDrawing = false;private bool isContinuousDrawing = false;private object selectedObject = null;private PointLatLng resizeStartPoint;private PointLatLng originalCenter;private double originalRadius;// 提示信息private ToolTip toolTip = new ToolTip();private PointLatLng lastHoverPoint;// 图形数据模型private class MapData{public List<MarkerData> Markers { get; set; } = new List<MarkerData>();public List<RouteData> Routes { get; set; } = new List<RouteData>();public List<CircleData> Circles { get; set; } = new List<CircleData>();}private class MarkerData{public double Lat { get; set; }public double Lng { get; set; }}private class RouteData{public List<PointData> Points { get; set; } = new List<PointData>();}private class CircleData{public PointData Center { get; set; }public double Radius { get; set; } // km}private class PointData{public double Lat { get; set; }public double Lng { get; set; }}public Form1(){InitializeComponent();InitializeMap();InitializeToolTip();}private void InitializeMap(){gMapControl1.MapProvider = GMapProviders.OpenCycleTransportMap;gMapControl1.Position = new PointLatLng(38.865625, 121.534452);gMapControl1.MinZoom = 1;gMapControl1.MaxZoom = 25;gMapControl1.Zoom = 18;gMapControl1.ShowCenter = true;gMapControl1.DragButton = MouseButtons.Middle;gMapControl1.MouseWheelZoomType = MouseWheelZoomType.MousePositionAndCenter;gMapControl1.Overlays.Add(lay_manual);}private void InitializeToolTip(){toolTip.AutoPopDelay = 5000;toolTip.InitialDelay = 100;toolTip.ReshowDelay = 100;toolTip.ShowAlways = true;}#region 按钮点击事件private void btnDrawLine_Click(object sender, EventArgs e){currentMode = DrawMode.Line;linePoints.Clear();isContinuousDrawing = true;UpdateStatusText("线段模式: 左键点击开始绘制,继续点击添加点,中键结束绘制");}private void btnDrawCircle_Click(object sender, EventArgs e){currentMode = DrawMode.Circle;isContinuousDrawing = false;UpdateStatusText("圆形模式: 左键点击圆心,拖动确定半径");}private void btnDrawMarker_Click(object sender, EventArgs e){currentMode = DrawMode.Marker;isContinuousDrawing = false;UpdateStatusText("标记模式: 左键点击放置标记");}private void btnClear_Click(object sender, EventArgs e){lay_manual.Clear();currentMode = DrawMode.None;linePoints.Clear();isContinuousDrawing = false;UpdateStatusText("已清除所有绘图");}private void btnSaveFig_Click(object sender, EventArgs e){SaveFileDialog saveFileDialog = new SaveFileDialog{Filter = "JSON文件|*.json",Title = "保存图形数据"};if (saveFileDialog.ShowDialog() == DialogResult.OK){try{MapData data = new MapData();// 保存标记foreach (GMapMarker marker in lay_manual.Markers){data.Markers.Add(new MarkerData{Lat = marker.Position.Lat,Lng = marker.Position.Lng});}// 保存线段foreach (GMapRoute route in lay_manual.Routes){var routeData = new RouteData();foreach (PointLatLng point in route.Points){routeData.Points.Add(new PointData{Lat = point.Lat,Lng = point.Lng});}data.Routes.Add(routeData);}// 保存圆形foreach (GMapPolygon polygon in lay_manual.Polygons){PointLatLng center = GetPolygonCenter(polygon);double radius = GetDistance(center, polygon.Points[0]);data.Circles.Add(new CircleData{Center = new PointData { Lat = center.Lat, Lng = center.Lng },Radius = radius});}string json = JsonConvert.SerializeObject(data, Formatting.Indented);File.WriteAllText(saveFileDialog.FileName, json);UpdateStatusText($"图形数据已保存到: {saveFileDialog.FileName}");}catch (Exception ex){MessageBox.Show($"保存失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);}}}private void btnLoadFig_Click(object sender, EventArgs e){OpenFileDialog openFileDialog = new OpenFileDialog{Filter = "JSON文件|*.json",Title = "加载图形数据"};if (openFileDialog.ShowDialog() == DialogResult.OK){try{string json = File.ReadAllText(openFileDialog.FileName);MapData data = JsonConvert.DeserializeObject<MapData>(json);// 清除现有图形lay_manual.Clear();// 加载标记foreach (var markerData in data.Markers){var marker = new GMarkerGoogle(new PointLatLng(markerData.Lat, markerData.Lng),GMarkerGoogleType.blue_small);lay_manual.Markers.Add(marker);}// 加载线段foreach (var routeData in data.Routes){List<PointLatLng> points = new List<PointLatLng>();foreach (var pointData in routeData.Points){points.Add(new PointLatLng(pointData.Lat, pointData.Lng));}var route = new GMapRoute(points, "loaded_route"){Stroke = new Pen(Color.Red, 2)};lay_manual.Routes.Add(route);}// 加载圆形foreach (var circleData in data.Circles){var center = new PointLatLng(circleData.Center.Lat, circleData.Center.Lng);var circle = CreateCircle(center, circleData.Radius);lay_manual.Polygons.Add(circle);}UpdateStatusText($"图形数据已从 {openFileDialog.FileName} 加载");}catch (Exception ex){MessageBox.Show($"加载失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);}}}#endregion#region 鼠标事件处理private void gMapControl1_MouseDown(object sender, MouseEventArgs e){PointLatLng point = gMapControl1.FromLocalToLatLng(e.X, e.Y);if (e.Button == MouseButtons.Left){if (currentMode == DrawMode.Adjust && selectedObject != null){isDrawing = true;resizeStartPoint = point;return;}if (currentMode != DrawMode.None){isDrawing = true;gMapControl1.CanDragMap = false;switch (currentMode){case DrawMode.Line:linePoints.Add(point);UpdateLinePreview();UpdateStatusText($"已添加点 {linePoints.Count},继续点击添加或中键结束");break;case DrawMode.Circle:startPoint = point;UpdateStatusText("圆形模式: 已设置圆心,请拖动确定半径");break;case DrawMode.Marker:var marker = new GMarkerGoogle(point, GMarkerGoogleType.blue_small);lay_manual.Markers.Add(marker);currentMode = DrawMode.None;UpdateStatusText("标记已添加");break;}}}else if (e.Button == MouseButtons.Middle){if (currentMode == DrawMode.Line && isContinuousDrawing){if (linePoints.Count > 1){var route = new GMapRoute(linePoints, "line"){Stroke = new Pen(Color.Red, 2)};lay_manual.Routes.Add(route);UpdateStatusText($"已完成线段绘制,共 {linePoints.Count} 个点");}linePoints.Clear();currentMode = DrawMode.None;ClearPreview();}else{object objToDelete = GetObjectAtPoint(point);if (objToDelete != null){if (objToDelete is GMapMarker marker)lay_manual.Markers.Remove(marker);else if (objToDelete is GMapRoute route)lay_manual.Routes.Remove(route);else if (objToDelete is GMapPolygon polygon)lay_manual.Polygons.Remove(polygon);UpdateStatusText("已删除选定对象");}}}else if (e.Button == MouseButtons.Right){if (currentMode != DrawMode.None && currentMode != DrawMode.Adjust){currentMode = DrawMode.None;linePoints.Clear();ClearPreview();UpdateStatusText("已取消绘制");}else{selectedObject = GetObjectAtPoint(point);if (selectedObject != null){currentMode = DrawMode.Adjust;resizeStartPoint = point;if (selectedObject is GMapPolygon){originalCenter = GetPolygonCenter(selectedObject as GMapPolygon);originalRadius = GetDistance(originalCenter, point);}}}}}private void gMapControl1_MouseMove(object sender, MouseEventArgs e){PointLatLng point = gMapControl1.FromLocalToLatLng(e.X, e.Y);StatusLabel_x.Text = point.Lat.ToString("0.000000");StatusLabel_y.Text = point.Lng.ToString("0.000000");CheckHoverObject(point);if (isDrawing && e.Button == MouseButtons.Left){switch (currentMode){case DrawMode.Line:if (linePoints.Count > 0){var tempPoints = new List<PointLatLng>(linePoints) { point };ClearPreview();var previewLine = new GMapRoute(tempPoints, "preview"){Stroke = new Pen(Color.FromArgb(150, Color.Red), 2)};lay_manual.Routes.Add(previewLine);}break;case DrawMode.Circle:if (startPoint != null){double radius = GetDistance(startPoint, point);UpdateCirclePreview(startPoint, radius);UpdateStatusText($"圆形模式: 当前半径 {radius:F3} km");}break;case DrawMode.Adjust:if (selectedObject != null){HandleAdjustment(point);}break;}}}private void gMapControl1_MouseUp(object sender, MouseEventArgs e){if (e.Button == MouseButtons.Left && isDrawing && currentMode == DrawMode.Circle){PointLatLng point = gMapControl1.FromLocalToLatLng(e.X, e.Y);if (startPoint != null){double radius = GetDistance(startPoint, point);var circle = CreateCircle(startPoint, radius);lay_manual.Polygons.Add(circle);currentMode = DrawMode.None;UpdateStatusText($"圆形绘制完成,半径: {radius:F3} km");}ClearPreview();}isDrawing = false;gMapControl1.CanDragMap = true;}#endregion#region 辅助方法private void UpdateStatusText(string message){lblStatus.Text = message;}private void ClearPreview(){foreach (var route in lay_manual.Routes.ToArray())if (route.Name == "preview") lay_manual.Routes.Remove(route);foreach (var polygon in lay_manual.Polygons.ToArray())if (polygon.Name == "preview") lay_manual.Polygons.Remove(polygon);}private void UpdateLinePreview(){if (linePoints.Count >= 2){var previewLine = new GMapRoute(linePoints, "preview"){Stroke = new Pen(Color.FromArgb(150, Color.Red), 2)};ClearPreview();lay_manual.Routes.Add(previewLine);}}private void UpdateCirclePreview(PointLatLng center, double radius){var previewCircle = CreateCircle(center, radius);previewCircle.Fill = new SolidBrush(Color.FromArgb(1, Color.Blue));ClearPreview();previewCircle.Name = "preview";lay_manual.Polygons.Add(previewCircle);}private GMapPolygon CreateCircle(PointLatLng center, double radius){List<PointLatLng> points = new List<PointLatLng>();for (int i = 0; i <= 360; i += 10){double angle = i * Math.PI / 180;double lat = center.Lat + (radius / 111.32) * Math.Cos(angle);double lng = center.Lng + (radius / (111.32 * Math.Cos(center.Lat * Math.PI / 180))) * Math.Sin(angle);points.Add(new PointLatLng(lat, lng));}points.Add(points[0]);return new GMapPolygon(points, "circle"){Fill = new SolidBrush(Color.FromArgb(50, Color.Blue)),Stroke = new Pen(Color.Blue, 2)};}private PointLatLng GetPolygonCenter(GMapPolygon polygon){double latSum = 0, lngSum = 0;foreach (PointLatLng pt in polygon.Points){latSum += pt.Lat;lngSum += pt.Lng;}return new PointLatLng(latSum / polygon.Points.Count, lngSum / polygon.Points.Count);}private object GetObjectAtPoint(PointLatLng point){// 检查标记foreach (GMapMarker marker in lay_manual.Markers){GPoint markerPoint = gMapControl1.FromLatLngToLocal(marker.Position);GPoint clickPoint = gMapControl1.FromLatLngToLocal(point);if (Math.Abs(markerPoint.X - clickPoint.X) < 20 &&Math.Abs(markerPoint.Y - clickPoint.Y) < 20)return marker;}// 检查线段foreach (GMapRoute route in lay_manual.Routes){for (int i = 1; i < route.Points.Count; i++){if (IsPointNearLine(route.Points[i - 1], route.Points[i], point, 0.0005))return route;}}// 检查圆形foreach (GMapPolygon polygon in lay_manual.Polygons){PointLatLng center = GetPolygonCenter(polygon);double distance = GetDistance(center, point);double radius = GetDistance(center, polygon.Points[0]);if (distance <= radius * 1.1)return polygon;}return null;}private bool IsPointNearLine(PointLatLng lineStart, PointLatLng lineEnd, PointLatLng point, double threshold){double lineLength = GetDistance(lineStart, lineEnd);if (lineLength < 0.0001) return false;double d1 = GetDistance(point, lineStart);double d2 = GetDistance(point, lineEnd);if (d1 > lineLength || d2 > lineLength)return false;double s = (d1 + d2 + lineLength) / 2;double area = Math.Sqrt(s * (s - d1) * (s - d2) * (s - lineLength));double distance = 2 * area / lineLength;return distance <= threshold;}private double GetDistance(PointLatLng point1, PointLatLng point2){const double EARTH_RADIUS = 6378.137;double radLat1 = point1.Lat * Math.PI / 180.0;double radLat2 = point2.Lat * Math.PI / 180.0;double a = radLat1 - radLat2;double b = (point1.Lng - point2.Lng) * Math.PI / 180.0;return 2 * Math.Asin(Math.Sqrt(Math.Pow(Math.Sin(a / 2), 2) +Math.Cos(radLat1) * Math.Cos(radLat2) * Math.Pow(Math.Sin(b / 2), 2))) * EARTH_RADIUS;}#endregion#region 调整功能private void HandleAdjustment(PointLatLng currentPoint){if (selectedObject is GMapMarker marker){// 移动标记marker.Position = currentPoint;UpdateStatusText($"标记经度调整为: {currentPoint.Lng:F3} 纬度调整为: {currentPoint.Lat:F3}");}else if (selectedObject is GMapRoute route){// 移动线段double dx = currentPoint.Lng - resizeStartPoint.Lng;double dy = currentPoint.Lat - resizeStartPoint.Lat;List<PointLatLng> newPoints = new List<PointLatLng>();foreach (PointLatLng pt in route.Points)newPoints.Add(new PointLatLng(pt.Lat + dy, pt.Lng + dx));route.Points.Clear();route.Points.AddRange(newPoints);resizeStartPoint = currentPoint;UpdateStatusText("线段位置已调整");}else if (selectedObject is GMapPolygon polygon){// 调整圆形大小double newRadius = GetDistance(originalCenter, currentPoint);lay_manual.Polygons.Remove(polygon);currentCircle = CreateCircle(originalCenter, newRadius);lay_manual.Polygons.Add(currentCircle);selectedObject = currentCircle;UpdateStatusText($"圆形半径调整为: {newRadius:F3} km");}}#endregion#region 图形参数提示功能private void CheckHoverObject(PointLatLng point){// 避免频繁更新if (lastHoverPoint != null && GetDistance(lastHoverPoint, point) < 0.0001)return;lastHoverPoint = point;// 检查鼠标悬停的对象object hoverObject = GetObjectAtPoint(point);if (hoverObject != null){StringBuilder sb = new StringBuilder();if (hoverObject is GMapMarker marker){sb.AppendLine("标记信息:");sb.AppendLine($"经度: {marker.Position.Lng:F6}");sb.AppendLine($"纬度: {marker.Position.Lat:F6}");}else if (hoverObject is GMapRoute route){sb.AppendLine("线段信息:");sb.AppendLine($"点数: {route.Points.Count}");if (route.Points.Count >= 2){double length = 0;for (int i = 1; i < route.Points.Count; i++){length += GetDistance(route.Points[i - 1], route.Points[i]);}sb.AppendLine($"总长度: {length:F3} km");}}else if (hoverObject is GMapPolygon polygon){PointLatLng center = GetPolygonCenter(polygon);double radius = GetDistance(center, polygon.Points[0]);sb.AppendLine("圆形信息:");sb.AppendLine($"中心经度: {center.Lng:F6}");sb.AppendLine($"中心纬度: {center.Lat:F6}");sb.AppendLine($"半径: {radius:F3} km");}// 显示提示信息GPoint localPoint = gMapControl1.FromLatLngToLocal(point);Point screenPoint = gMapControl1.PointToScreen(new Point((int)localPoint.X, (int)localPoint.Y));toolTip.Show(sb.ToString(), gMapControl1,(int)localPoint.X,(int)localPoint.Y);}else{toolTip.Hide(gMapControl1);}}#endregion}
}

7.2 显示效果如下

点击线段按钮,鼠标左键连续点击可以绘制多条线段
点击画圆按钮,初次点击鼠标左键确定圆心,拖动鼠标左键调整圆形半径大小
点击标记按钮,点击鼠标左键确定标记位置
清楚绘制是会把界面中的所有内容擦除

在这里插入图片描述

点击保存图形,弹出保存json名称,可以保存当前绘制的所有图标
在这里插入图片描述

点击保存图形,弹出选择json名称(可加载上一次绘图配置),地图会立即显示目标json的所有图标
在这里插入图片描述
当鼠标靠近这些图形时会自动显示图形信息。

七、参考资源

  1. GMap.NET官方文档

  2. CSDN参考博客

  3. 地理坐标系转换公式:

    X = R cos(lat) cos(lon)
    Y = R cos(lat) sin(lon)

本教程完整代码已通过GMap.NET.WindowsForms实现,重点解决了鼠标交互和图形持久化等核心问题。实际开发中建议根据具体需求调整地图提供商和坐标精度参数。

这篇教程的特点:

  1. 以大连海事大学实际坐标作为示例
  2. 包含完整的JSON序列化实现
  3. 采用分步骤递进式讲解
  4. 重点突出鼠标事件处理逻辑
  5. 提供可量化的性能优化建议
  6. 整合了参考博客的核心思路并做了功能扩展
http://www.dtcms.com/a/280501.html

相关文章:

  • 开通保存图片权限
  • 如何设计实现开发自助重启工具-01-设计篇
  • eVTOL分布式电推进(DEP)适航审定探究
  • Ajax接收java后端传递的json对象包含长整型被截断导致丢失精度的解决方案
  • 【橘子分布式】Thrift RPC(编程篇)
  • 亚矩阵云手机:破解 Yandex 广告平台多账号风控难题的利器
  • Redis学习系列之——高并发应用的缓存问题(二)
  • JDK1.8函数式编程实战(附日常工作案例,仅此一篇耐心看完彻底搞懂)
  • 17、鸿蒙Harmony Next开发:状态管理(组件拥有的状态和应用拥有的状态)
  • Vue获取上传Excel文件内容并展示在表格中
  • 【人工智能99问】神经网络的工作原理是什么?(4/99)
  • 使用Pydantic开发时,如何将返回数据由snake_case自动转为camel case
  • Mac IDEA启动报错:Error occurred during initialization of VM
  • Linux操作系统从入门到实战(九)Linux开发工具(中)自动化构建-make/Makefile知识讲解
  • ubuntu部署kvm
  • AI-Compass LLM训练框架生态:整合ms-swift、Unsloth、Megatron-LM等核心框架,涵盖全参数/PEFT训练与分布式优化
  • 正则表达式深度解析:从LeetCode 3136题说起
  • 028_分布式部署架构
  • OpenCV图像自动缩放(Autoscaling)函数autoscaling()
  • 2025.7.15总结
  • 用Python构建机器学习模型预测股票趋势:从数据到部署的实战指南
  • 希尔排序:突破传统排序的边界
  • 【Java】【企业级应用】学生信息管理系统项目介绍
  • Mybatis05-动态sql
  • 深度解析 AI 提示词工程(Prompt Engineering)
  • 2025世界机器人大赛ICode专属训练平台图形化小学组答案
  • 光伏设计全方位指南
  • B/S 架构通信原理详解
  • sqli-labs靶场通关笔记:第17关 POST请求的密码重置
  • 如何配置maven