1、在GMap地图上,如果要让添加的图标(Marker)有个高亮(highlight)的效果,可以在MouseOver到Marker的时候设置Marker外观效果。
如果要让图标有个报警闪烁的效果,可以设置一个定时器,在定时器中改变Marker的外观,或者是用GDI来画圆闪动,带报警效果的Marker如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using GMap.NET; using GMap.NET.WindowsForms; using System.Drawing; using System.Windows.Forms; namespace GMapWinFormDemo { class GMapMarkerImage : GMapMarker { private Image image; public Image Image { get { return image; } set { image = value; if (image != null) { this.Size = new Size(image.Width, image.Height); } } } public bool IsHighlight = true; public Pen HighlightPen { set; get; } public Pen FlashPen { set; get; } private Timer flashTimer = new Timer(); private int radius; private int flashRadius; public GMapMarkerImage(GMap.NET.PointLatLng p, Image image) : base(p) { Size = new System.Drawing.Size(image.Width, image.Height); Offset = new System.Drawing.Point(-Size.Width / 2, -Size.Height / 2); Image = image; HighlightPen = new System.Drawing.Pen(Brushes.Red,2); radius = Size.Width >= Size.Height ? Size.Width : Size.Height; flashTimer.Interval = 10; flashTimer.Tick += new EventHandler(flashTimer_Tick); } public void StartFlash() { flashTimer.Start(); } void flashTimer_Tick(object sender, EventArgs e) { if (FlashPen == null) { FlashPen = new Pen(Brushes.Red, 3); flashRadius = radius; } else { flashRadius += radius/4; if (flashRadius >= 2 * radius) { flashRadius = radius; FlashPen.Color = Color.FromArgb(255, Color.Red); } else { Random rand = new Random(); int alpha = rand.Next(255); FlashPen.Color = Color.FromArgb(alpha, Color.Red); } } this.Overlay.Control.Refresh(); } public void StopFlash() { flashTimer.Stop(); if (FlashPen != null) { FlashPen.Dispose(); FlashPen = null; } this.Overlay.Control.Refresh(); } public override void OnRender(Graphics g) { if (image == null) return; Rectangle rect = new Rectangle(LocalPosition.X, LocalPosition.Y, Size.Width, Size.Height); g.DrawImage(image, rect); if (IsMouseOver && IsHighlight) { g.DrawRectangle(HighlightPen,rect); } if (FlashPen != null) { g.DrawEllipse(FlashPen, new Rectangle(LocalPosition.X - flashRadius / 2 + Size.Width/2, LocalPosition.Y - flashRadius / 2+Size.Height/2, flashRadius, flashRadius)); } } public override void Dispose() { if (HighlightPen != null) { HighlightPen.Dispose(); HighlightPen = null; } if (FlashPen != null) { FlashPen.Dispose(); FlashPen = null; } base.Dispose(); } } }
2、可以旋转角度的Marker,比如可以将一个箭头图标旋转一定角度来指向一个轨迹路线,代码如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Drawing; using GMap.NET; using GMap.NET.WindowsForms; using GMapWinFormDemo.Properties; namespace GMapWinFormDemo { class GMapMarkerDirection : GMapMarker { private float Ang; private Image image; public Image Image { get { return image; } set { image = value; if (image != null) { this.Size = new Size(image.Width, image.Height); } } } public GMapMarkerDirection(PointLatLng p, Image image, float angle) : base(p) { Ang = angle; Image = image; Size = new System.Drawing.Size(image.Width, image.Height); Offset = new System.Drawing.Point(-Size.Width / 2, -Size.Height / 2); } public override void OnRender(Graphics g) { g.DrawImageUnscaled(RotateImage(Image, Ang), LocalPosition.X, LocalPosition.Y); } //http://www.codeproject.com/KB/graphics/rotateimage.aspx //Author : James T. Johnson private static Bitmap RotateImage(Image image, float angle) { if (image == null) throw new ArgumentNullException("image"); const double pi2 = Math.PI / 2.0; // Why can‘t C# allow these to be const, or at least readonly // *sigh* I‘m starting to talk like Christian Graus :omg: double oldWidth = (double)image.Width; double oldHeight = (double)image.Height; // Convert degrees to radians double theta = ((double)angle) * Math.PI / 180.0; double locked_theta = theta; // Ensure theta is now [0, 2pi) while (locked_theta < 0.0) locked_theta += 2 * Math.PI; double newWidth, newHeight; int nWidth, nHeight; // The newWidth/newHeight expressed as ints #region Explaination of the calculations /* * The trig involved in calculating the new width and height * is fairly simple; the hard part was remembering that when * PI/2 <= theta <= PI and 3PI/2 <= theta < 2PI the width and * height are switched. * * When you rotate a rectangle, r, the bounding box surrounding r * contains for right-triangles of empty space. Each of the * triangles hypotenuse‘s are a known length, either the width or * the height of r. Because we know the length of the hypotenuse * and we have a known angle of rotation, we can use the trig * function identities to find the length of the other two sides. * * sine = opposite/hypotenuse * cosine = adjacent/hypotenuse * * solving for the unknown we get * * opposite = sine * hypotenuse * adjacent = cosine * hypotenuse * * Another interesting point about these triangles is that there * are only two different triangles. The proof for which is easy * to see, but its been too long since I‘ve written a proof that * I can‘t explain it well enough to want to publish it. * * Just trust me when I say the triangles formed by the lengths * width are always the same (for a given theta) and the same * goes for the height of r. * * Rather than associate the opposite/adjacent sides with the * width and height of the original bitmap, I‘ll associate them * based on their position. * * adjacent/oppositeTop will refer to the triangles making up the * upper right and lower left corners * * adjacent/oppositeBottom will refer to the triangles making up * the upper left and lower right corners * * The names are based on the right side corners, because thats * where I did my work on paper (the right side). * * Now if you draw this out, you will see that the width of the * bounding box is calculated by adding together adjacentTop and * oppositeBottom while the height is calculate by adding * together adjacentBottom and oppositeTop. */ #endregion double adjacentTop, oppositeTop; double adjacentBottom, oppositeBottom; // We need to calculate the sides of the triangles based // on how much rotation is being done to the bitmap. // Refer to the first paragraph in the explaination above for // reasons why. if ((locked_theta >= 0.0 && locked_theta < pi2) || (locked_theta >= Math.PI && locked_theta < (Math.PI + pi2))) { adjacentTop = Math.Abs(Math.Cos(locked_theta)) * oldWidth; oppositeTop = Math.Abs(Math.Sin(locked_theta)) * oldWidth; adjacentBottom = Math.Abs(Math.Cos(locked_theta)) * oldHeight; oppositeBottom = Math.Abs(Math.Sin(locked_theta)) * oldHeight; } else { adjacentTop = Math.Abs(Math.Sin(locked_theta)) * oldHeight; oppositeTop = Math.Abs(Math.Cos(locked_theta)) * oldHeight; adjacentBottom = Math.Abs(Math.Sin(locked_theta)) * oldWidth; oppositeBottom = Math.Abs(Math.Cos(locked_theta)) * oldWidth; } newWidth = adjacentTop + oppositeBottom; newHeight = adjacentBottom + oppositeTop; nWidth = (int)Math.Ceiling(newWidth); nHeight = (int)Math.Ceiling(newHeight); Bitmap rotatedBmp = new Bitmap(nWidth, nHeight); using (Graphics g = Graphics.FromImage(rotatedBmp)) { // This array will be used to pass in the three points that // make up the rotated image Point[] points; /* * The values of opposite/adjacentTop/Bottom are referring to * fixed locations instead of in relation to the * rotating image so I need to change which values are used * based on the how much the image is rotating. * * For each point, one of the coordinates will always be 0, * nWidth, or nHeight. This because the Bitmap we are drawing on * is the bounding box for the rotated bitmap. If both of the * corrdinates for any of the given points wasn‘t in the set above * then the bitmap we are drawing on WOULDN‘T be the bounding box * as required. */ if (locked_theta >= 0.0 && locked_theta < pi2) { points = new Point[] { new Point( (int) oppositeBottom, 0 ), new Point( nWidth, (int) oppositeTop ), new Point( 0, (int) adjacentBottom ) }; } else if (locked_theta >= pi2 && locked_theta < Math.PI) { points = new Point[] { new Point( nWidth, (int) oppositeTop ), new Point( (int) adjacentTop, nHeight ), new Point( (int) oppositeBottom, 0 ) }; } else if (locked_theta >= Math.PI && locked_theta < (Math.PI + pi2)) { points = new Point[] { new Point( (int) adjacentTop, nHeight ), new Point( 0, (int) adjacentBottom ), new Point( nWidth, (int) oppositeTop ) }; } else { points = new Point[] { new Point( 0, (int) adjacentBottom ), new Point( (int) oppositeBottom, 0 ), new Point( (int) adjacentTop, nHeight ) }; } g.DrawImage(image, points); } return rotatedBmp; } } }
3、在点击图标Marker的时候出现ContextMenuStrip:
void mapControl_OnMarkerClick(GMapMarker item, MouseEventArgs e) { if (e.Button == System.Windows.Forms.MouseButtons.Left) { this.contextMenuStrip1.Show(Cursor.Position); if (item is GMapMarkerImage) { currentMarker = item as GMapMarkerImage; } } }
4、随地图放大缩小的圆,代码来自官方Demo:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Drawing; using GMap.NET; using GMap.NET.WindowsForms; namespace GMapWinFormDemo { public class GMapMarkerCircle : GMapMarker { /// <summary> /// In Meters /// </summary> public int Radius; /// <summary> /// specifies how the outline is painted /// </summary> public Pen Stroke = new Pen(Color.FromArgb(155, Color.MidnightBlue)); /// <summary> /// background color /// </summary> public Brush Fill = new SolidBrush(Color.FromArgb(155, Color.AliceBlue)); /// <summary> /// is filled /// </summary> public bool IsFilled = true; public GMapMarkerCircle(PointLatLng p) : base(p) { Radius = 100; // 100m IsHitTestVisible = false; } public override void OnRender(Graphics g) { int R = (int)((Radius) / Overlay.Control.MapProvider.Projection.GetGroundResolution((int)Overlay.Control.Zoom, Position.Lat)) * 2; if (IsFilled) { g.FillEllipse(Fill, new System.Drawing.Rectangle(LocalPosition.X - R / 2, LocalPosition.Y - R / 2, R, R)); } g.DrawEllipse(Stroke, new System.Drawing.Rectangle(LocalPosition.X - R / 2, LocalPosition.Y - R / 2, R, R)); } public override void Dispose() { if (Stroke != null) { Stroke.Dispose(); Stroke = null; } if (Fill != null) { Fill.Dispose(); Fill = null; } base.Dispose(); } } }
关键就是如何在放大缩小时确定圆的半径大小,半径大小为:
int R = (int)((Radius) / Overlay.Control.MapProvider.Projection.GetGroundResolution((int)Overlay.Control.Zoom, Position.Lat)) * 2;
通过当前的缩放比例zoom和圆心的纬度来得到地图在此条件下分辨率(resolution),分辨率的大小为一个像素大小所代表的距离(单位为米)。
所以当我采用画多边形的方式在地图上画圆时,实际得到的圆在小半径和地球赤道附近下是个圆,但是在纬度较大的地方画的圆就变成了椭圆,代码如下:
namespace GMapWinFormDemo { public static class CirclePolygon { public static GMapPolygon CreateCircle(PointLatLng center, double radius, string name) { List<PointLatLng> pList = new List<PointLatLng>(); int segments = 100000; double seg = 2 * Math.PI / segments; for (int i = 0; i < segments; ++i) { double theta = i * seg; double a = center.Lat + Math.Sin(theta) * radius; double b = center.Lng + Math.Cos(theta) * radius; pList.Add(new PointLatLng(a, b)); } GMapPolygon circle = new GMapPolygon(pList, name); circle.Stroke = new Pen(Brushes.Red, 1); return circle; } } }
5、保存地图为图片:
private void buttonSaveMap_Click(object sender, EventArgs e) { try { using (SaveFileDialog dialog = new SaveFileDialog()) { dialog.Filter = "PNG (*.png)|*.png"; dialog.FileName = "GMap.NET image"; Image image = this.mapControl.ToImage(); if (image != null) { using (image) { if (dialog.ShowDialog() == DialogResult.OK) { string fileName = dialog.FileName; if (!fileName.EndsWith(".png", StringComparison.OrdinalIgnoreCase)) { fileName += ".png"; } image.Save(fileName); MessageBox.Show("图片已保存: " + dialog.FileName, "GMap.NET", MessageBoxButtons.OK, MessageBoxIcon.Asterisk); } } } } } catch (Exception exception) { MessageBox.Show("图片保存失败: " + exception.Message, "GMap.NET", MessageBoxButtons.OK, MessageBoxIcon.Hand); } }
参考:
https://greatmaps.codeplex.com/