基于WPF实现绘制地铁路线图
作者:公子小六 发布时间:2023-09-08 13:23:12
经常坐地铁,却不知道地铁多少条线路?哪个站下车?今天就带领大家熟悉并绘制深圳地铁路线图。
WPF在绘制矢量图方面有非常强大的优势,利用WPF可以绘制出各种矢量图形,如线,圆,多边形,矩形,及组合图形。今天以绘制深圳地铁路线图为例,简述WPF在图形绘制方面的一些知识,仅供学习分享使用,如有不足之处,还请指正。
WPF图形概述
与传统的.NET开发使用GDI+进行绘图不同,WPF拥有自己的一套图形API,绘图为矢量图。绘图可以在任何一种布局控件中完成,wpf会根据容器计算相应坐标。最常用的是Canvas和Grid。基本图形包括以下几个,都是Shaper类的派生类。
Line,直线段,可以设置Stroke
Rectangle,有Stroke也有Fill
Ellipse,椭圆,同上
Polygon,多边形。由多条直线线段围成的闭合区域,同上。
Polyline,折线,不闭合,由多条首尾相接的直线段组成
Path,路径,闭合。可以由若干直线、圆弧、贝塞尔曲线(由线段与节点组成,节点是可拖动的支点,线段像可伸缩的皮筋)组成。很强大。
地铁官网效果
首先打开深圳地铁官网【https://www.szmc.net/map/】,可查看深圳地铁的路线图,如下所示:
获取地铁路线数据
通过对地铁官网的网络接口接收数据分析,可以获取地铁数据的原始JSON文件,将原始JSON文件保存到本地,在程序中进行引用,如下所示:
构建地铁数据模型
在得到shentie.json文件后,通过分析,构建模型类,如下所示:
namespace DemoSubway.Models
{
/// <summary>
/// 深圳地铁模型
/// </summary>
public class ShenTie
{
[JsonProperty("s")]
public string? Name { get; set; }
[JsonProperty("i")]
public string? Index { get; set; }
[JsonProperty("l")]
public SubwayLine[]? SubwayLines { get; set; }
[JsonProperty("o")]
public string? Obj { get;set; }
}
public class SubwayLine
{
[JsonProperty("st")]
public St[]? Sites { get; set; }
[JsonProperty("ln")]
public string? LineNumber { get; set; }
[JsonProperty("su")]
public string? Su { get; set; }
[JsonProperty("kn")]
public string? KName { get; set; }
[JsonProperty("c")]
public string[]? Circles { get;set; }
[JsonProperty("lo")]
public string? Lo { get; set; }
[JsonProperty("lp")]
public string[]? LinePosition { get; set; }
[JsonProperty("ls")]
public string? Ls { get; set; }
[JsonProperty("cl")]
public string? Color { get; set; }
[JsonProperty("la")]
public string? La { get; set; }
[JsonProperty("x")]
public string? X { get; set; }
[JsonProperty("li")]
public string? Li { get; set; }
}
public class St
{
[JsonProperty("rs")]
public string? Rs { get; set; }
[JsonProperty("udpx")]
public string? Udpx { get; set; }
[JsonProperty("su")]
public string? Su { get; set; }
[JsonProperty("udsu")]
public string? Udsu { get; set;}
[JsonProperty("n")]
public string? Name { get; set;}
[JsonProperty("en")]
public string? En { get; set; }
[JsonProperty("sid")]
public string? Sid { get; set; }
[JsonProperty("p")]
public string? Position { get; set; }
[JsonProperty("r")]
public string? R { get; set; }
[JsonProperty("udsi")]
public string? Udsi { get; set; }
[JsonProperty("t")]
public string? T { get; set;}
[JsonProperty("si")]
public string? Si { get; set; }
[JsonProperty("sl")]
public string? Sl { get; set;}
[JsonProperty("udli")]
public string? Udli { get; set; }
[JsonProperty("poiid")]
public string? Poiid { get; set; }
[JsonProperty("lg")]
public string? Lg { get; set; }
[JsonProperty("sp")]
public string? Sp { get; set; }
}
}
解析数据源
通过反序列化,将shentie.json文件内容,加载到内存并实例化为ShenTie对象,在此需要用到第三方库【Newtonsoft.Json】,如下所示:
绘制地铁路线图
地铁路线图在WPF主页面显示,用Grid作为容器【subwayBox】,如下所示:
<Window x:Class="DemoSubway.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:DemoSubway"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800" Loaded="Window_Loaded">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal" HorizontalAlignment="Center">
<TextBlock x:Name="tbTitle" FontSize="30" HorizontalAlignment="Center"></TextBlock>
</StackPanel>
<Viewbox Stretch="Fill" Grid.Row="1">
<Grid x:Name="subwayBox">
</Grid>
</Viewbox>
</Grid>
</Window>
ShenTie对象创建成功后,就可以获取路线数据,然后创建地铁路线元素,如下所示:
private void Window_Loaded(object sender, RoutedEventArgs e)
{
string jsonFile = "shentie.json";
JsonHelper jsonHelper = new JsonHelper();
var shentie = jsonHelper.Deserialize<ShenTie>(jsonFile);
this.tbTitle.Text = shentie.Name;
List<string> lstSites = new List<string>();
for(int i = 0; i < shentie.SubwayLines?.Length; i++)
{
var subwayLine= shentie.SubwayLines[i];
if(subwayLine != null)
{
//地铁线路
var color = ColorTranslator.FromHtml($"#{subwayLine.Color}");//线路颜色
var circles = subwayLine.Circles;//线路节点
Path line = new Path();
PathFigureCollection lineFigures = new PathFigureCollection();
PathFigure lineFigure = new PathFigure();
lineFigure.IsClosed= false;
var start = circles?[0].Split(" ");//线路起始位置
lineFigure.StartPoint = new System.Windows.Point(int.Parse(start[0]), int.Parse(start[1]));
for (int j= 0;j< circles?.Length;j++)
{
var circle= circles[j].Split(" ");
LineSegment lineSegment = new LineSegment(new System.Windows.Point(int.Parse(circle[0]), int.Parse(circle[1])),true);
lineFigure.Segments.Add(lineSegment);
}
lineFigures.Add(lineFigure);
line.Data = new PathGeometry(lineFigures, FillRule.Nonzero, null);
line.Stroke = new SolidColorBrush(System.Windows.Media.Color.FromArgb(color.A, color.R, color.G, color.B));
line.StrokeThickness = 4;
this.subwayBox.Children.Add(line);
//地铁站点
for (int j = 0; j < subwayLine.Sites?.Length; j++)
{
var site = subwayLine.Sites[j];
if (site != null)
{
//站点标识,圆圈
Path siteCirclePath = new Path();
var sitePosition = site?.Position?.Split(" ");
EllipseGeometry ellipse = new EllipseGeometry();
ellipse.Center = new System.Windows.Point(int.Parse(sitePosition[0]), int.Parse(sitePosition[1]));
ellipse.RadiusX = 4;
ellipse.RadiusY=4;
siteCirclePath.Data=ellipse;
siteCirclePath.Fill = Brushes.White;
siteCirclePath.Stroke = new SolidColorBrush(System.Windows.Media.Color.FromArgb(color.A, color.R, color.G, color.B));
siteCirclePath.Cursor= Cursors.Hand;
siteCirclePath.Focusable = true;
siteCirclePath.Tag = site?.Name;
siteCirclePath.MouseDown += SiteCirclePath_MouseDown;
this.subwayBox.Children.Add(siteCirclePath);
//站点名字
if (lstSites.Contains(site?.Name))
{
continue;//对于交汇站点,只绘制一次
}
//站点名称
Path siteTextPath = new Path();
FormattedText siteContent = new FormattedText(site?.Name,CultureInfo.CurrentCulture,FlowDirection.LeftToRight,new Typeface("Arial"),14,Brushes.Black, 1.25);
var x = int.Parse(sitePosition[0]);
var y = int.Parse(sitePosition[1]);
if (j + 1 < subwayLine.Sites?.Length)
{
//站点位置适当偏移
var next = subwayLine.Sites[j + 1]?.Position?.Split(" ");
var nextx = int.Parse(next[0]);
var nexty = int.Parse(next[1]);
if (x == nextx)
{
x = x + 6;
}
else if (y == nexty)
{
y = y + 6;
}
else
{
x = x + 1;
y = y + 1;
}
}
Geometry geometry = siteContent.BuildGeometry(new System.Windows.Point(x, y));
siteTextPath.Data = geometry;
siteTextPath.Stroke = Brushes.Black;
siteTextPath.Focusable = true;
siteTextPath.Cursor = Cursors.Hand;
siteTextPath.MouseDown += SiteTextPath_MouseDown;
siteTextPath.Tag = site?.Name;
this.subwayBox.Children.Add(siteTextPath);
lstSites.Add(site?.Name);
}
}
var kName = subwayLine.KName;//线路名称
var linePosition= subwayLine.LinePosition?[0].Split(" ");
if(kName != null)
{
Path lineNamePath = new Path();
FormattedText lineNameText = new FormattedText(kName, CultureInfo.CurrentCulture,FlowDirection.LeftToRight,new Typeface("Arial"),16,Brushes.Black,1.25);
var lineX = int.Parse(linePosition[0]);
var lineY = int.Parse(linePosition[1]);
if (subwayLine.LineNumber == "1")
{
lineX = lineX - 10;
lineY = lineY + 20;
}
Geometry geometry = lineNameText.BuildGeometry(new System.Windows.Point(lineX, lineY));
lineNamePath.Data=geometry;
lineNamePath.Stroke = new SolidColorBrush(System.Windows.Media.Color.FromArgb(color.A, color.R, color.G, color.B));
this.subwayBox.Children.Add(lineNamePath);
}
}
}
}
效果展示
本示例效果图如下所示:
在获取的JSON文件中,有些属性名都是简写,所以在编写示例代码过程中,对有些属性的理解并不准确,需要不断测试优化,绘制出的地铁路线图可能与实际存在稍微的差异,如站点名称,路线名称等内容的位置。
来源:https://www.cnblogs.com/hsiang/p/17447750.html
猜你喜欢
- 本文实例讲述了C#判断页面中的多个文本框输入值是否有重复的实现方法,分享给大家供大家参考。具体实现方法如下:List<string&g
- 日期格式化标准 DateTime 格式字符串如果格式字符串只包含下表列出的某个单个格式说明符,则它们被解释为标准格式说明符。如果指定的格式字
- BeanUtils.copyProperties忽略空值使用spring开发的人,对这行代码肯定不陌生,常用于DTO、VO、PO之间的复制。
- 多线程的创建,方式一:继承于Thread类1.创建一个继承于Thread类的子类2.重写Thread类的run()--->将此线程执行
- <%@ page language="java" contentType="text/html; cha
- 让我们来看看这段代码: import java.util.BitSet;import java.util.concurrent.C
- 1.场景介绍:开发过程中我们经常性的会用到许多的中间表,用于数据之间的对应和关联.这个时候我们关联最多的就是ID,我们在一张表中插入数据后级
- C#可以直接引用C++的DLL和转换JAVA写好的程序。最近由于工作原因接触这方面比较多,根据实际需求,我们通过一个具体例子把一个JAVA方
- 本节作为主要讲解Spring Data的环境搭建JPA Spring Data :致力于减少数据访问层(DAO)的开发量。开发者唯一要做的就
- Future接口是Java标准API的一部分,在java.util.concurrent包中。Future接口是Java线程Future模式
- 在窗体中添加DataGridView控件和ConTextMenuStrip1控件,修改DataGridView属性,将contextMenu
- 前言springboot提供了 spirng-boot-starter-test 以供开发者使用单元测试,在引入 spring-boot-s
- View绘制的三大流程,指的是measure(测量)、layout(布局)、draw(绘制) measure负责确定View的测量宽/高,也
- 本文实例讲述了Android开发实现的计时器功能。分享给大家供大家参考,具体如下:效果图:布局:三个按钮 加上一个Chronometer&l
- 前言当你编写一个应用时,你通常都会希望用户能够定制化他们和应用交互的方式,以及应用与系统进行交互的方式。这种方式通常被称为 &ldq
- 介绍在 .NET4.0 之前,如果我们需要在多线程环境下使用 Dictionary 类,除了自己实现线程同步来保证线程安全外,我们没有其他选
- 本篇实例内容是关于C#读取CAD文件的,直接看代码//在不使用任务插件的情况下读取DWG文件的缩略图,以便在没有安装AutoCAD的计算机上
- 这篇文章主要介绍了MyBatis传入数组集合类并使用foreach遍历,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学
- 我相信现在绝大部分App几乎避免不了消息推送,其实原理还是使用了长连接,通过服务端将消息推给客户端。市面上也有不少三方库,例如极光、友盟、个
- 本文实例为大家分享了java动态模拟时钟的具体代码,供大家参考,具体内容如下应用名称:java动态模拟时钟用到的知识:javaGUI,jav