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 / , -Size.Height / );
Image = image;
HighlightPen = new System.Drawing.Pen(Brushes.Red,);
radius = Size.Width >= Size.Height ? Size.Width : Size.Height;
flashTimer.Interval = ;
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, );
flashRadius = radius;
}
else
{
flashRadius += radius/;
if (flashRadius >= * radius)
{
flashRadius = radius;
FlashPen.Color = Color.FromArgb(, Color.Red);
}
else
{
Random rand = new Random();
int alpha = rand.Next();
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 / + Size.Width/, LocalPosition.Y - flashRadius / +Size.Height/, 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 / , -Size.Height / );
} 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 += * 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, ),
new Point( nWidth, (int) oppositeTop ),
new Point( , (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, )
};
}
else if (locked_theta >= Math.PI && locked_theta < (Math.PI + pi2))
{
points = new Point[] {
new Point( (int) adjacentTop, nHeight ),
new Point( , (int) adjacentBottom ),
new Point( nWidth, (int) oppositeTop )
};
}
else
{
points = new Point[] {
new Point( , (int) adjacentBottom ),
new Point( (int) oppositeBottom, ),
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(, Color.MidnightBlue)); /// <summary>
/// background color
/// </summary>
public Brush Fill = new SolidBrush(Color.FromArgb(, Color.AliceBlue)); /// <summary>
/// is filled
/// </summary>
public bool IsFilled = true; public GMapMarkerCircle(PointLatLng p)
: base(p)
{
Radius = ; // 100m
IsHitTestVisible = false;
} public override void OnRender(Graphics g)
{
int R = (int)((Radius) / Overlay.Control.MapProvider.Projection.GetGroundResolution((int)Overlay.Control.Zoom, Position.Lat)) * ; if (IsFilled)
{
g.FillEllipse(Fill, new System.Drawing.Rectangle(LocalPosition.X - R / , LocalPosition.Y - R / , R, R));
}
g.DrawEllipse(Stroke, new System.Drawing.Rectangle(LocalPosition.X - R / , LocalPosition.Y - R / , 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)) * ;
通过当前的缩放比例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 = ;
double seg = * Math.PI / segments;
for (int i = ; i < segments; ++i)
{
double theta = i * seg;
double a = center.Lat + Math.Cos(theta) * radius;
double b = center.Lng + Math.Sin(theta) * radius;
pList.Add(new PointLatLng(a, b));
}
GMapPolygon circle = new GMapPolygon(pList, name);
circle.Stroke = new Pen(Brushes.Red, );
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://github.com/luxiaoxun/MapDownloader
参考:
https://greatmaps.codeplex.com/