using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using OSGeo.MapGuide;
using System.Drawing;
using System.ComponentModel;
using System.IO;
using System.Drawing.Drawing2D;
using System.Diagnostics;
using System.Threading;
using System.Xml;
using System.Collections.Specialized;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Reflection;
namespace OSGeo.MapGuide.Viewer
{
///
/// A map viewer component for displaying/rendering a instance
///
///
///
/// For any non-modal instances displayed over this control, if you call on the
/// form without specifying the owner, this control will automatically be sent to the back of the draw order by this control. To avoid this behavior always call
/// . You can choose to pass an actual owner or a null owner, but this particular overload
/// must be used.
///
///
/// If you are using instances with set to NewWindow,
/// be sure to set the property to your top-level form. This form will be automatically set as
/// the owner form for any new window displayed by the respective component. This property is optional. All forms displayed by this component will not exhibit the
/// "sent to back of draw order" display problem.
///
///
/// For applications using the AppLayout engine, such components do not need to have the OwnerParent set. As long as the Target is set to NewWindow, the AppLayout
/// engine will automatically set the OwnerParent of all defined instances to the main
/// AppLayout main window.
///
///
[ToolboxItem(true)]
public class MgMapViewer : Control, IMapViewer
{
private BackgroundWorker renderWorker;
private MgResourceService _resSvc;
private MgMapBase _map;
private MgSelectionBase _selection;
private MgMapViewerProvider _provider;
private MgViewerRenderingOptions _overlayRenderOpts;
private MgViewerRenderingOptions _selectionRenderOpts;
private MgWktReaderWriter _wktRW;
private MgAgfReaderWriter _agfRW;
private MgGeometryFactory _geomFact;
private MgCoordinateSystemFactory _csFact;
private MgMeasure _mapMeasure;
private Color _mapBgColor;
private double _orgX1;
private double _orgX2;
private double _orgY1;
private double _orgY2;
private double _extX1;
private double _extX2;
private double _extY1;
private double _extY2;
private Image _selectionImage;
private Image _mapImage;
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
internal Image Image
{
get { return _mapImage; }
set
{
_mapImage = value;
//Invalidate();
}
}
#if VIEWER_DEBUG
private MgdLayer _debugLayer;
private void CreateDebugFeatureSource()
{
var id = new MgDataPropertyDefinition("ID"); //NOXLATE
id.DataType = MgPropertyType.Int32;
id.Nullable = false;
id.SetAutoGeneration(true);
var geom = new MgGeometricPropertyDefinition("Geometry"); //NOXLATE
geom.GeometryTypes = MgFeatureGeometricType.Point;
geom.SpatialContextAssociation = "MapCs"; //NOXLATE
var cls = new MgClassDefinition();
cls.Name = "Debug"; //NOXLATE
var props = cls.GetProperties();
props.Add(id);
props.Add(geom);
var idProps = cls.GetIdentityProperties();
idProps.Add(id);
cls.DefaultGeometryPropertyName = "Geometry"; //NOXLATE
var schema = new MgFeatureSchema("Default", "Default schema"); //NOXLATE
var classes = schema.GetClasses();
classes.Add(cls);
//We can make anything up here, there's no real concept of sessions
var sessionId = Guid.NewGuid().ToString();
var debugFsId = new MgResourceIdentifier("Session:" + sessionId + "//Debug" + Guid.NewGuid().ToString() + ".FeatureSource"); //NOXLATE
var createSdf = new MgCreateSdfParams("MapCs", _map.GetMapSRS(), schema); //NOXLATE
var featureSvc = (MgdFeatureService)fact.CreateService(MgServiceType.FeatureService);
var resSvc = (MgResourceService)fact.CreateService(MgServiceType.ResourceService);
featureSvc.CreateFeatureSource(debugFsId, createSdf);
byte[] bytes = Encoding.UTF8.GetBytes(string.Format(Debug.DebugLayer, debugFsId.ToString(), "Default:Debug", "Geometry")); //NOXLATE
var source = new MgByteSource(bytes, bytes.Length);
var debugLayerId = new MgResourceIdentifier("Session:" + sessionId + "//" + debugFsId.Name + ".LayerDefinition"); //NOXLATE
var breader = source.GetReader();
resSvc.SetResource(debugLayerId, breader, null);
_debugLayer = new MgdLayer(debugLayerId, resSvc);
_debugLayer.SetLegendLabel("Debug Layer"); //NOXLATE
_debugLayer.SetVisible(true);
_debugLayer.SetDisplayInLegend(true);
var mapLayers = _map.GetLayers();
mapLayers.Insert(0, _debugLayer);
UpdateCenterDebugPoint();
}
private MgPropertyCollection _debugCenter;
private void UpdateCenterDebugPoint()
{
if (_debugCenter == null)
_debugCenter = new MgPropertyCollection();
var center = _wktRW.Read("POINT (" + _map.ViewCenter.Coordinate.X.ToString(CultureInfo.InvariantCulture) + " " + _map.ViewCenter.Coordinate.Y.ToString(CultureInfo.InvariantCulture) + ")"); //NOXLATE
var agf = _agfRW.Write(center);
if (!_debugCenter.Contains("Geometry")) //NOXLATE
{
MgGeometryProperty geom = new MgGeometryProperty("Geometry", agf); //NOXLATE
_debugCenter.Add(geom);
}
else
{
MgGeometryProperty geom = (MgGeometryProperty)_debugCenter.GetItem("Geometry"); //NOXLATE
geom.SetValue(agf);
}
int deleted = _debugLayer.DeleteFeatures(string.Empty);
Trace.TraceInformation("Deleted {0} debug points", deleted); //NOXLATE
var reader = _debugLayer.InsertFeatures(_debugCenter);
int inserted = 0;
while (reader.ReadNext())
{
inserted++;
}
reader.Close();
Trace.TraceInformation("Added {0} debug points", inserted); //NOXLATE
_debugLayer.ForceRefresh();
}
#endif
private MgCoordinateSystem _mapCs;
private int _viewHistoryIndex;
private List _viewHistory;
///
/// Initializes a new instance of the class.
///
public MgMapViewer()
{
_viewHistory = new List();
_viewHistoryIndex = -1;
this.ShowVertexCoordinatesWhenDigitizing = false;
this.FeatureTooltipsEnabled = false;
this.TooltipsEnabled = false;
this.ZoomInFactor = 0.5;
this.ZoomOutFactor = 2.0;
this.SelectionColor = Color.Blue;
this.PointPixelBuffer = 2;
this.MinScale = 10;
this.MaxScale = 1000000000;
this.DigitizingFillTransparency = 100;
this.DigitizingOutline = Brushes.Red;
this.DigitzingFillColor = Color.White;
this.TooltipFillColor = Color.LightYellow;
this.TooltipFillTransparency = 200;
this.MouseWheelDelayRenderInterval = 800;
this.TooltipDelayInterval = 1000;
this.ActiveTool = MapActiveTool.None;
this.DoubleBuffered = true;
SetStyle(ControlStyles.UserPaint, true);
SetStyle(ControlStyles.AllPaintingInWmPaint, true);
SetStyle(ControlStyles.DoubleBuffer, true);
SetStyle(ControlStyles.UserPaint, true);
SetStyle(ControlStyles.AllPaintingInWmPaint, true);
SetStyle(ControlStyles.DoubleBuffer, true);
_mapBgColor = Color.Transparent;
/*
_defaultDigitizationInstructions = Strings.GeneralDigitizationInstructions;
_defaultMultiSegmentDigitizationInstructions = Strings.MultiSegmentDigitzationInstructions;
_defaultPointDigitizationPrompt = Strings.PointDigitizationPrompt;
_defaultLineDigitizationPrompt = Strings.LineDigitizationPrompt;
_defaultCircleDigitizationPrompt = Strings.CircleDigitizationPrompt;
_defaultLineStringDigitizationPrompt = Strings.LineStringDigitizationPrompt;
_defaultPolygonDigitizationPrompt = Strings.PolygonDigitizationPrompt;
_defaultRectangleDigitizationPrompt = Strings.RectangleDigitizationPrompt;
*/
renderWorker = new BackgroundWorker();
renderWorker.DoWork += renderWorker_DoWork;
renderWorker.RunWorkerCompleted += renderWorker_RunWorkerCompleted;
base.MouseUp += OnMapMouseUp;
base.MouseMove += OnMapMouseMove;
base.MouseDown += OnMapMouseDown;
base.MouseClick += OnMapMouseClick;
base.MouseDoubleClick += OnMapMouseDoubleClick;
base.MouseHover += OnMapMouseHover;
base.MouseEnter += OnMouseEnter;
base.MouseWheel += OnMapMouseWheel;
base.MouseLeave += OnMapMouseLeave;
}
///
/// Raises the event.
///
/// A that contains the event data.
protected override void OnKeyUp(KeyEventArgs e)
{
if (e.KeyCode == Keys.Escape)
{
CancelDigitization();
}
else if (e.KeyCode == Keys.Z && e.Modifiers == Keys.Control)
{
if (this.DigitizingType == MapDigitizationType.LineString ||
this.DigitizingType == MapDigitizationType.Polygon)
{
if (dPath.Count > 2) //Slice off the last recorded point
{
dPath.RemoveAt(dPath.Count - 1);
Invalidate();
}
}
}
}
public void CancelDigitization()
{
if (this.DigitizingType != MapDigitizationType.None)
{
dPath.Clear();
dPtStart.X = 0;
dPtStart.Y = 0;
this.DigitizingType = MapDigitizationType.None;
//Trace.TraceInformation("Digitization cancelled");
this.Invalidate();
}
}
void OnMouseEnter(object sender, EventArgs e)
{
this.Focus();
}
void OnMapMouseHover(object sender, EventArgs e)
{
HandleMouseHover(e);
}
private void HandleMouseHover(EventArgs e)
{
}
///
/// Releases the unmanaged resources used by the and its child controls and optionally releases the managed resources.
///
/// true to release both managed and unmanaged resources; false to release only unmanaged resources.
protected override void Dispose(bool disposing)
{
if (disposing)
{
base.MouseUp -= OnMapMouseUp;
base.MouseMove -= OnMapMouseMove;
base.MouseDown -= OnMapMouseDown;
base.MouseClick -= OnMapMouseClick;
base.MouseDoubleClick -= OnMapMouseDoubleClick;
base.MouseHover -= OnMapMouseHover;
base.MouseEnter -= OnMouseEnter;
base.MouseLeave -= OnMapMouseLeave;
if (renderWorker != null)
{
renderWorker.DoWork -= renderWorker_DoWork;
renderWorker.RunWorkerCompleted -= renderWorker_RunWorkerCompleted;
}
if (_csFact != null)
{
_csFact.Dispose();
_csFact = null;
}
if (_resSvc != null)
{
_resSvc.Dispose();
_resSvc = null;
}
if (_selection != null)
{
_selection.Dispose();
_selection = null;
}
if (_mapCs != null)
{
_mapCs.Dispose();
_mapCs = null;
}
if (_agfRW != null)
{
_agfRW.Dispose();
_agfRW = null;
}
if (_wktRW != null)
{
_wktRW.Dispose();
_wktRW = null;
}
if (_geomFact != null)
{
_geomFact.Dispose();
_geomFact = null;
}
if (_mapMeasure != null)
{
_mapMeasure.Dispose();
_mapMeasure = null;
}
}
base.Dispose(disposing);
}
///
/// Gets or sets the minimum allowed zoom scale for this viewer
///
[Category("MapGuide Viewer")] //NOXLATE
[Description("The minimum allowed zoom scale for this viewer")] //NOXLATE
[MgComponentProperty]
public int MinScale { get; set; }
///
/// Gets or sets the maximum allowed zoom scale for this viewer
///
[Category("MapGuide Viewer")] //NOXLATE
[Description("The maximum allowed zoom scale for this viewer")] //NOXLATE
[MgComponentProperty]
public int MaxScale { get; set; }
///
/// The amount of time (in ms) to wait to re-render after a mouse wheel scroll
///
[Category("MapGuide Viewer")] //NOXLATE
[Description("The amount of time (in ms) to wait to re-render after a mouse wheel scroll")] //NOXLATE
[MgComponentProperty]
public int MouseWheelDelayRenderInterval { get; set; }
///
/// The amount of time (in ms) to wait to re-render after a mouse wheel scroll
///
[Category("MapGuide Viewer")] //NOXLATE
[Description("The amount of time (in ms) to wait to fire off a tooltip request after the mouse pointer becomes stationary")] //NOXLATE
[MgComponentProperty]
public int TooltipDelayInterval { get; set; }
private Color _selColor;
///
/// Gets or sets the color used to render selected features
///
[Category("MapGuide Viewer")] //NOXLATE
[Description("The color to use for active selections")] //NOXLATE
[MgComponentProperty]
public Color SelectionColor
{
get { return _selColor; }
set
{
_selColor = value;
OnPropertyChanged("SelectionColor"); //NOXLATE
}
}
private Color _tooltipFillColor;
///
/// Gets or sets the color of the tooltip fill.
///
///
/// The color of the tooltip fill.
///
[Category("MapGuide Viewer")] //NOXLATE
[Description("The color background for feature tooltips")] //NOXLATE
internal Color TooltipFillColor
{
get { return _tooltipFillColor; }
set
{
if (!value.Equals(_tooltipFillColor))
{
_tooltipFillColor = value;
OnPropertyChanged("TooltipFillColor"); //NOXLATE
}
}
}
private int _tooltipFillTransparency;
[Category("MapGuide Viewer")] //NOXLATE
[Description("The color background transparency for feature tooltips")] //NOXLATE
[DefaultValue(200)]
internal int TooltipFillTransparency
{
get { return _tooltipFillTransparency; }
set
{
if (!value.Equals(_tooltipFillTransparency))
{
_tooltipFillTransparency = value;
OnPropertyChanged("TooltipFillTransparency"); //NOXLATE
}
}
}
private void UpdateSelectionRenderingOptions()
{
var value = this.SelectionColor;
if (_selectionRenderOpts != null)
{
var color = _selectionRenderOpts.Color;
if (color != null && (color.Red != value.R ||
color.Green != value.G ||
color.Blue != value.B))
{
_selectionRenderOpts = null;
_selectionRenderOpts = CreateSelectionRenderingOptions(value.R, value.G, value.B);
//Trace.TraceInformation("Selection color updated to ({0}, {1}, {2})", value.R, value.G, value.B);
}
}
}
///
/// Gets the coordinate system of the runtime map
///
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public MgCoordinateSystem CoordinateSystem { get { return _mapCs; } }
private bool _showVertexCoords;
///
/// Gets or sets a value indicating whether [show vertex coordinates when digitizing].
///
///
/// true if [show vertex coordinates when digitizing]; otherwise, false.
///
[Category("MapGuide Viewer")] //NOXLATE
[Description("Indicates whether coordinate values are shown when digitizing geometry")] //NOXLATE
[DefaultValue(false)]
[MgComponentProperty]
public bool ShowVertexCoordinatesWhenDigitizing
{
get { return _showVertexCoords; }
set
{
if (!value.Equals(_showVertexCoords))
{
_showVertexCoords = value;
OnPropertyChanged("ShowVertexCoordinatesWhenDigitizing"); //NOXLATE
}
}
}
/*
private string _defaultDigitizationInstructions;
private string _defaultMultiSegmentDigitizationInstructions;
private string _defaultPointDigitizationPrompt;
private string _defaultLineDigitizationPrompt;
private string _defaultCircleDigitizationPrompt;
private string _defaultLineStringDigitizationPrompt;
private string _defaultPolygonDigitizationPrompt;
private string _defaultRectangleDigitizationPrompt;
*/
private string _pointCustomDigitizationPrompt;
private string _lineCustomDigitizationPrompt;
private string _circleCustomDigitizationPrompt;
private string _lineStringCustomDigitizationPrompt;
private string _polygonCustomDigitizationPrompt;
private string _rectangleCustomDigitizationPrompt;
///
/// Raises the event.
///
/// A that contains the event data.
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
//Trace.TraceInformation("OnPaint(e)");
ApplyPaintTranslateTransform(e);
if (mouseWheelSx.HasValue && mouseWheelSy.HasValue && mouseWheelSx.Value != 0.0 && mouseWheelSy.Value != 0.0)
{
e.Graphics.ScaleTransform(mouseWheelSx.Value, mouseWheelSy.Value);
}
var pre = this.PreMapRender;
if (pre != null)
pre(this, e);
if (_mapImage != null)
{
//Trace.TraceInformation("Render buffered map image");
e.Graphics.DrawImage(_mapImage, new PointF(0, 0));
}
//Thread.Sleep(100);
if (_selectionImage != null)
{
//Trace.TraceInformation("Render buffered map selection");
e.Graphics.DrawImage(_selectionImage, new PointF(0, 0));
}
//TODO: We could add support here for map-space persistent digitizations
if (isDragging && (this.ActiveTool == MapActiveTool.Select || this.ActiveTool == MapActiveTool.ZoomIn))
{
DrawDragRectangle(e);
}
else
{
if (this.DigitizingType != MapDigitizationType.None)
{
if (this.DigitizingType == MapDigitizationType.Point)
{
string str = (_pointCustomDigitizationPrompt ?? Strings.PointDigitizationPrompt) + Environment.NewLine + Strings.GeneralDigitizationInstructions;
DrawTrackingTooltip(e, str);
}
else
{
if (!dPtStart.IsEmpty)
{
switch (this.DigitizingType)
{
case MapDigitizationType.Circle:
DrawTracingCircle(e);
break;
case MapDigitizationType.Line:
DrawTracingLine(e);
break;
case MapDigitizationType.Rectangle:
DrawTracingRectangle(e);
break;
}
}
else if (dPath.Count > 0)
{
switch (this.DigitizingType)
{
case MapDigitizationType.LineString:
DrawTracingLineString(e);
break;
case MapDigitizationType.Polygon:
DrawTracingPolygon(e);
break;
}
}
}
}
else //None
{
if (this.ActiveTool != MapActiveTool.None)
{
if (!string.IsNullOrEmpty(_activeTooltipText))
DrawTrackingTooltip(e, _activeTooltipText);
}
}
}
var post = this.PostMapRender;
if (post != null)
post(this, e);
}
private void ApplyPaintTranslateTransform(PaintEventArgs e)
{
if (!translate.IsEmpty)
{
if (mouseWheelTx.HasValue && mouseWheelTy.HasValue)
e.Graphics.TranslateTransform(translate.X + mouseWheelTx.Value, translate.Y + mouseWheelTy.Value);
else
e.Graphics.TranslateTransform(translate.X, translate.Y);
}
else
{
if (mouseWheelTx.HasValue && mouseWheelTy.HasValue)
e.Graphics.TranslateTransform(mouseWheelTx.Value, mouseWheelTy.Value);
}
}
private Brush _digitizingOutline;
[Category("MapGuide Viewer")] //NOXLATE
[Description("The outline color for geometries being digitized")] //NOXLATE
internal Brush DigitizingOutline
{
get { return _digitizingOutline; }
set
{
_digitizingOutline = value;
OnPropertyChanged("DigitizingOutline"); //NOXLATE
}
}
private int _digitizingFillTransparency;
[Category("MapGuide Viewer")] //NOXLATE
[Description("The fill color transparency for geometries being digitized")] //NOXLATE
[DefaultValue(100)]
internal int DigitizingFillTransparency
{
get { return _digitizingFillTransparency; }
set
{
if (!value.Equals(_digitizingFillTransparency))
{
_digitizingFillTransparency = value;
OnPropertyChanged("DigitizingFillTransparency"); //NOXLATE
}
}
}
[Category("MapGuide Viewer")] //NOXLATE
[Description("The amount of pixels to buffer out by when doing point-based selections with the Select tool")] //NOXLATE
[MgComponentProperty]
public int PointPixelBuffer { get; set; }
private Color _digitizingFillColor;
[Category("MapGuide Viewer")] //NOXLATE
[Description("The fill color for geometries being digitized")] //NOXLATE
internal Color DigitzingFillColor
{
get { return _digitizingFillColor; }
set
{
_digitizingFillColor = value;
OnPropertyChanged("DigitzingFillColor"); //NOXLATE
}
}
private Pen CreateOutlinePen()
{
return new Pen(this.DigitizingOutline, 2.0f);
}
private Brush CreateFillBrush()
{
return new SolidBrush(Color.FromArgb(this.DigitizingFillTransparency, this.DigitzingFillColor));
}
private static double GetDistanceBetween(PointF a, PointF b)
{
return (Math.Sqrt(Math.Pow(Math.Abs(a.X - b.X), 2) + Math.Pow(Math.Abs(a.Y - b.Y), 2)));
}
private void DrawVertexCoordinates(PaintEventArgs e, double devX, double devY, bool mapSpace)
{
if (!this.ShowVertexCoordinatesWhenDigitizing)
return;
string text = string.Empty;
if (mapSpace)
{
var mapPt = ScreenToMapUnits(devX, devY);
text = string.Format("X: {0}, Y: {1}", mapPt.X, mapPt.Y); //NOXLATE
}
else
{
text = string.Format("X: {0}, Y: {1}", devX, devY); //NOXLATE
}
var f = Control.DefaultFont;
SizeF size = e.Graphics.MeasureString(text, Font);
var vertex = new PointF((float)devX, (float)devY);
//Offset so that the "box" for this string is centered on the vertex itself
vertex.X -= (size.Width / 2);
//Fill the surrounding box
e.Graphics.FillRectangle(new SolidBrush(Color.FromArgb(200, Color.WhiteSmoke)), vertex.X, vertex.Y, size.Width, size.Height);
e.Graphics.DrawRectangle(Pens.Red, vertex.X, vertex.Y, size.Width, size.Height);
//Draw the string
e.Graphics.DrawString(text, f, Brushes.Black, vertex);
}
private void DrawTrackingTooltip(PaintEventArgs e, string text)
{
if (string.IsNullOrEmpty(text)) //Nothing to draw
return;
var f = Control.DefaultFont;
int height = 0;
int width = 0;
string[] tokens = text.Split(new string[] { "\\n", "\\r\\n", "\n", Environment.NewLine }, StringSplitOptions.None); //NOXLATE
foreach(string t in tokens)
{
var size = e.Graphics.MeasureString(t, f);
height += (int)size.Height;
width = Math.Max(width, (int)size.Width);
}
e.Graphics.FillRectangle(new SolidBrush(Color.FromArgb(this.TooltipFillTransparency, this.TooltipFillColor)), new Rectangle(_mouseX, _mouseY, width + 10, height + 4));
float y = 2.0f;
float heightPerLine = height / tokens.Length;
foreach (string t in tokens)
{
e.Graphics.DrawString(t, f, Brushes.Black, new PointF(_mouseX + 5.0f, _mouseY + y));
y += heightPerLine;
}
}
private void DrawTracingCircle(PaintEventArgs e)
{
var pt2 = new Point(dPtStart.X, dPtStart.Y);
var diameter = (float)GetDistanceBetween(dPtStart, new PointF(_mouseX, _mouseY)) * 2.0f;
//Trace.TraceInformation("Diameter ({0}, {1} -> {2}, {3}): {4}", dPtStart.X, dPtStart.Y, _mouseX, _mouseY, diameter); //NOXLATE
pt2.Offset((int)-(diameter / 2), (int)-(diameter / 2));
e.Graphics.DrawEllipse(CreateOutlinePen(), pt2.X, pt2.Y, diameter, diameter);
e.Graphics.FillEllipse(CreateFillBrush(), pt2.X, pt2.Y, diameter, diameter);
string str = (_circleCustomDigitizationPrompt ?? Strings.CircleDigitizationPrompt) + Environment.NewLine + Strings.GeneralDigitizationInstructions;
DrawTrackingTooltip(e, str);
}
private void DrawTracingLine(PaintEventArgs e)
{
e.Graphics.DrawLine(CreateOutlinePen(), dPtStart, new Point(_mouseX, _mouseY));
DrawVertexCoordinates(e, dPtStart.X, dPtStart.Y, true);
DrawVertexCoordinates(e, _mouseX, _mouseY, true);
string str = (_lineCustomDigitizationPrompt ?? Strings.LineDigitizationPrompt) + Environment.NewLine + Strings.GeneralDigitizationInstructions;
DrawTrackingTooltip(e, str);
}
private void DrawTracingLineString(PaintEventArgs e)
{
//Not enough points to constitute a line string or polygon
if (dPath.Count < 2)
return;
e.Graphics.DrawLines(CreateOutlinePen(), dPath.ToArray());
foreach (var pt in dPath)
{
DrawVertexCoordinates(e, pt.X, pt.Y, true);
}
string str = (_lineStringCustomDigitizationPrompt ?? Strings.LineStringDigitizationPrompt) + Environment.NewLine + Strings.MultiSegmentDigitzationInstructions;
DrawTrackingTooltip(e, str);
}
private void DrawTracingPolygon(PaintEventArgs e)
{
//Not enough points to constitute a line string or polygon
if (dPath.Count < 2)
return;
e.Graphics.DrawPolygon(CreateOutlinePen(), dPath.ToArray());
e.Graphics.FillPolygon(CreateFillBrush(), dPath.ToArray());
foreach (var pt in dPath)
{
DrawVertexCoordinates(e, pt.X, pt.Y, true);
}
string str = (_polygonCustomDigitizationPrompt ?? Strings.PolygonDigitizationPrompt) + Environment.NewLine + Strings.MultiSegmentDigitzationInstructions;
DrawTrackingTooltip(e, str);
}
private void DrawTracingRectangle(PaintEventArgs e)
{
var rect = GetRectangle(dragStart, new Point(_mouseX, _mouseY));
if (rect.HasValue)
{
var r = rect.Value;
//Trace.TraceInformation("Draw rangle ({0} {1}, {2} {3})", r.Left, r.Top, r.Right, r.Bottom); //NOXLATE
e.Graphics.DrawRectangle(CreateOutlinePen(), r);
//Trace.TraceInformation("Fill rangle ({0} {1}, {2} {3})", r.Left, r.Top, r.Right, r.Bottom); //NOXLATE
e.Graphics.FillRectangle(CreateFillBrush(), r);
DrawVertexCoordinates(e, r.Left, r.Top, true);
DrawVertexCoordinates(e, r.Left, r.Bottom, true);
DrawVertexCoordinates(e, r.Right, r.Top, true);
DrawVertexCoordinates(e, r.Right, r.Bottom, true);
string str = (_rectangleCustomDigitizationPrompt ?? Strings.RectangleDigitizationPrompt) + Environment.NewLine + Strings.GeneralDigitizationInstructions;
DrawTrackingTooltip(e, str);
}
}
private void DrawDragRectangle(PaintEventArgs e)
{
var rect = GetRectangle(dragStart, new Point(_mouseX, _mouseY));
if (rect.HasValue)
{
var r = rect.Value;
//Trace.TraceInformation("Draw rangle ({0} {1}, {2} {3})", r.Left, r.Top, r.Right, r.Bottom); //NOXLATE
e.Graphics.DrawRectangle(CreateOutlinePen(), r);
//Trace.TraceInformation("Fill rangle ({0} {1}, {2} {3})", r.Left, r.Top, r.Right, r.Bottom); //NOXLATE
e.Graphics.FillRectangle(CreateFillBrush(), r);
}
}
private bool _featTooltipsEnabled;
///
/// Gets or sets whether feature tooltips are enabled. If set to true, tooltip queries are
/// executed at the current mouse position if the active tool is Pan or Select
///
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
[MgComponentProperty]
public bool FeatureTooltipsEnabled
{
get { return _featTooltipsEnabled; }
set
{
if (value.Equals(_featTooltipsEnabled))
return;
_featTooltipsEnabled = value;
if (!value)
{
_activeTooltipText = null;
Invalidate();
}
OnPropertyChanged("FeatureTooltipsEnabled"); //NOXLATE
}
}
///
/// Internally determines whether a tooltip query can be executed. Must be true along
/// with in order for a tooltip query to be executed
///
internal bool TooltipsEnabled
{
get;
set;
}
#region Digitization
/*
* Digitization behaviour with respect to mouse and paint events
*
* Point:
* MouseClick -> Invoke Callback
*
* Rectangle:
* MouseClick -> set start, temp end
* MouseMove -> update temp end
* OnPaint -> Draw rectangle from start/temp end
* MouseClick -> set end -> Invoke Callback
*
* Line:
* MouseClick -> set start, temp end
* MouseMove -> update temp end
* OnPaint -> Draw line from start/temp end
* MouseClick -> set end -> Invoke Callback
*
* LineString:
* MouseClick -> append point to path
* MouseMove -> update temp end
* OnPaint -> Draw line with points in path + temp end
* MouseDoubleClick -> append point to path -> Invoke Callback
*
* Polygon:
* MouseClick -> append point to path
* MouseMove -> update temp end
* OnPaint -> Draw polygon fill with points in path + temp end
* MouseDoubleClick -> append point to path -> Invoke Callback
*
* Circle:
* MouseClick -> set start, temp end
* MouseMove -> update temp end
* OnPaint -> Draw circle from start with radius = (dist from start to temp end)
* MouseClick -> set end -> Invoke Callback
*/
private Point dPtStart; //Rectangle, Line, Circle
private Point dPtEnd; //Rectangle, Line, Circle
private List dPath = new List(); //LineString, Polygon
private Delegate _digitzationCallback;
private bool _digitizationYetToStart = true;
///
/// Starts the digitization process for a circle
///
/// The callback to be invoked when the digitization process completes
/// The custom prompt to use for the tracking tooltip
public void DigitizeCircle(CircleDigitizationCallback callback, string customPrompt)
{
this.DigitizingType = MapDigitizationType.Circle;
_digitzationCallback = callback;
_digitizationYetToStart = true;
_circleCustomDigitizationPrompt = customPrompt;
}
///
/// Starts the digitization process for a line
///
/// The callback to be invoked when the digitization process completes
/// The custom prompt to use for the tracking tooltip
public void DigitizeLine(LineDigitizationCallback callback, string customPrompt)
{
this.DigitizingType = MapDigitizationType.Line;
_digitzationCallback = callback;
_digitizationYetToStart = true;
_lineCustomDigitizationPrompt = customPrompt;
}
///
/// Starts the digitization process for a point
///
/// The callback to be invoked when the digitization process completes
/// The custom prompt to use for the tracking tooltip
public void DigitizePoint(PointDigitizationCallback callback, string customPrompt)
{
this.DigitizingType = MapDigitizationType.Point;
_digitzationCallback = callback;
_digitizationYetToStart = true;
_pointCustomDigitizationPrompt = customPrompt;
}
///
/// Starts the digitization process for a polygon
///
/// The callback to be invoked when the digitization process completes
/// The custom prompt to use for the tracking tooltip
public void DigitizePolygon(PolygonDigitizationCallback callback, string customPrompt)
{
this.DigitizingType = MapDigitizationType.Polygon;
_digitzationCallback = callback;
_digitizationYetToStart = true;
_polygonCustomDigitizationPrompt = customPrompt;
}
///
/// Starts the digitization process for a line string (polyline)
///
/// The callback to be invoked when the digitization process completes
/// The custom prompt to use for the tracking tooltip
public void DigitizeLineString(LineStringDigitizationCallback callback, string customPrompt)
{
this.DigitizingType = MapDigitizationType.LineString;
_digitzationCallback = callback;
_digitizationYetToStart = true;
_lineStringCustomDigitizationPrompt = customPrompt;
}
private LineDigitizationCallback _segmentCallback;
///
/// Starts the digitization process for a line string (polyline)
///
/// The callback to be invoked when the digitization process completes
/// The callback to be invoked when a new segment of the current line string is digitized
/// The custom prompt to use for the tracking tooltip
public void DigitizeLineString(LineStringDigitizationCallback callback, LineDigitizationCallback segmentCallback, string customPrompt)
{
this.DigitizingType = MapDigitizationType.LineString;
_digitzationCallback = callback;
_segmentCallback = segmentCallback;
_digitizationYetToStart = true;
_lineStringCustomDigitizationPrompt = customPrompt;
}
///
/// Starts the digitization process for a rectangle
///
/// The callback to be invoked when the digitization process completes
/// The custom prompt to use for the tracking tooltip
public void DigitizeRectangle(RectangleDigitizationCallback callback, string customPrompt)
{
this.DigitizingType = MapDigitizationType.Rectangle;
_digitzationCallback = callback;
_digitizationYetToStart = true;
_rectangleCustomDigitizationPrompt = customPrompt;
}
public void DigitizeCircle(CircleDigitizationCallback callback)
{
DigitizeCircle(callback, null);
}
public void DigitizeLine(LineDigitizationCallback callback)
{
DigitizeLine(callback, null);
}
public void DigitizePoint(PointDigitizationCallback callback)
{
DigitizePoint(callback, null);
}
public void DigitizePolygon(PolygonDigitizationCallback callback)
{
DigitizePolygon(callback, null);
}
public void DigitizeLineString(LineStringDigitizationCallback callback)
{
DigitizeLineString(callback, (string)null);
}
public void DigitizeLineString(LineStringDigitizationCallback callback, LineDigitizationCallback segmentDigitized)
{
DigitizeLineString(callback, segmentDigitized, null);
}
public void DigitizeRectangle(RectangleDigitizationCallback callback)
{
DigitizeRectangle(callback, null);
}
private void ResetDigitzationState()
{
_digitzationCallback = null;
_segmentCallback = null;
dPath.Clear();
dPtEnd.X = dPtStart.Y = 0;
dPtStart.X = dPtStart.Y = 0;
this.DigitizingType = MapDigitizationType.None;
_circleCustomDigitizationPrompt = null;
_lineCustomDigitizationPrompt = null;
_lineStringCustomDigitizationPrompt = null;
_polygonCustomDigitizationPrompt = null;
_pointCustomDigitizationPrompt = null;
_rectangleCustomDigitizationPrompt = null;
Invalidate();
}
private void OnCircleDigitized(Point ptStart, Point ptEnd)
{
var mapPt = ScreenToMapUnits(ptStart.X, ptStart.Y);
var mapEnd = ScreenToMapUnits(ptEnd.X, ptEnd.Y);
var radius = Math.Sqrt(Math.Pow(mapEnd.X - mapPt.X, 2) + Math.Pow(mapEnd.Y - mapPt.Y, 2));
var cb = (CircleDigitizationCallback)_digitzationCallback;
ResetDigitzationState();
cb(mapPt.X, mapPt.Y, radius);
}
private void OnPolygonDigitized(List path)
{
double[,] coords = new double[path.Count, 2];
for (int i = 0; i < path.Count; i++)
{
var pt = ScreenToMapUnits(path[i].X, path[i].Y);
coords[i, 0] = pt.X;
coords[i, 1] = pt.Y;
}
var cb = (PolygonDigitizationCallback)_digitzationCallback;
ResetDigitzationState();
cb(coords);
}
private void OnLineStringSegmentDigitized(Point p1, Point p2)
{
if (_segmentCallback != null)
{
var ptx1 = ScreenToMapUnits(p1.X, p1.Y);
var ptx2 = ScreenToMapUnits(p2.X, p2.Y);
_segmentCallback.Invoke(ptx1.X, ptx1.Y, ptx2.X, ptx2.Y);
}
}
private void OnLineStringDigitized(List path)
{
double[,] coords = new double[path.Count, 2];
for (int i = 0; i < path.Count; i++)
{
var pt = ScreenToMapUnits(path[i].X, path[i].Y);
coords[i, 0] = pt.X;
coords[i, 1] = pt.Y;
}
var cb = (LineStringDigitizationCallback)_digitzationCallback;
ResetDigitzationState();
cb(coords);
}
private void OnLineDigitized(Point start, Point end)
{
var mapStart = ScreenToMapUnits(start.X, start.Y);
var mapEnd = ScreenToMapUnits(end.X, end.Y);
var cb = (LineDigitizationCallback)_digitzationCallback;
ResetDigitzationState();
cb(mapStart.X, mapStart.Y, mapEnd.X, mapEnd.Y);
}
private void OnRectangleDigitized(Rectangle rect)
{
var mapLL = ScreenToMapUnits(rect.Left, rect.Bottom);
var mapUR = ScreenToMapUnits(rect.Right, rect.Top);
var cb = (RectangleDigitizationCallback)_digitzationCallback;
ResetDigitzationState();
cb(mapLL.X, mapLL.Y, mapUR.X, mapUR.Y);
}
private void OnPointDigitizationCompleted(Point p)
{
var mapPt = ScreenToMapUnits(p.X, p.Y);
var cb = (PointDigitizationCallback)_digitzationCallback;
ResetDigitzationState();
cb(mapPt.X, mapPt.Y);
}
#endregion
static MgViewerRenderingOptions CreateMapRenderingOptions(short red, short green, short blue)
{
return new MgViewerRenderingOptions("PNG", 2, new MgColor(red, green, blue)); //NOXLATE
}
static MgViewerRenderingOptions CreateSelectionRenderingOptions(short red, short green, short blue)
{
return new MgViewerRenderingOptions("PNG", (1 | 4), new MgColor(red, green, blue)); //NOXLATE
}
public void LoadMap(MgMapBase map)
{
if (_provider == null)
throw new InvalidOperationException(Strings.ErrorViewerNotInitialized);
_provider.LoadMap(map);
}
///
/// Initializes this viewer with the specified viewer provider, if the provider contains a map
/// it will load that as well. Otherwise a call to is required afterwards
///
/// The provider.
public void Init(MgMapViewerProvider provider)
{
if (_agfRW == null)
_agfRW = new MgAgfReaderWriter();
if (_wktRW == null)
_wktRW = new MgWktReaderWriter();
if (_geomFact == null)
_geomFact = new MgGeometryFactory();
_provider = provider;
if (_resSvc != null) //Forward looking, dispose the existing one as it may be of a different implementation
{
_resSvc.Dispose();
_resSvc = null;
}
_resSvc = (MgResourceService)_provider.CreateService(MgServiceType.ResourceService);
_overlayRenderOpts = CreateMapRenderingOptions(0, 0, 255);
_selectionRenderOpts = CreateSelectionRenderingOptions(0, 0, 255);
if (_provider != null)
{
_provider.MapLoaded -= OnMapSetOnProvider;
_provider = null;
}
_provider = provider;
_provider.MapLoaded += OnMapSetOnProvider;
var map = _provider.GetMap();
if (map != null)
OnMapSetOnProvider(this, EventArgs.Empty);
}
private void OnMapSetOnProvider(object sender, EventArgs e)
{
_hasTiledLayers = null;
_map = _provider.GetMap();
_mapCs = _provider.GetMapCoordinateSystem();
_mapMeasure = _mapCs.GetMeasure();
var bgColor = _map.GetBackgroundColor();
if (bgColor.Length == 8 || bgColor.Length == 6)
{
_mapBgColor = ColorTranslator.FromHtml("#" + bgColor); //NOXLATE
this.BackColor = _mapBgColor;
}
_provider.SetDisplaySize(this.Width, this.Height);
_selection = _provider.CreateSelectionForMap();
var env = _map.GetMapExtent();
var ll = env.LowerLeftCoordinate;
var ur = env.UpperRightCoordinate;
_extX1 = _orgX1 = ll.X;
_extY2 = _orgY2 = ll.Y;
_extX2 = _orgX2 = ur.X;
_extY1 = _orgY1 = ur.Y;
if ((_orgX1 - _orgX2) == 0 || (_orgY1 - _orgY2) == 0)
{
_extX1 = _orgX1 = -.1;
_extY2 = _orgX2 = .1;
_extX2 = _orgY1 = -.1;
_extY1 = _orgY2 = .1;
}
if (this.ConvertTiledGroupsToNonTiled)
{
var groups = _map.GetLayerGroups();
for (int i = 0; i < groups.GetCount(); i++)
{
var group = groups.GetItem(i);
_provider.MakeGroupNormal(group);
}
}
#if VIEWER_DEBUG
CreateDebugFeatureSource();
#endif
this.Focus();
//Reset history stack
_viewHistory.Clear();
OnPropertyChanged("ViewHistory"); //NOXLATE
_viewHistoryIndex = -1;
OnPropertyChanged("ViewHistoryIndex"); //NOXLATE
var handler = this.MapLoaded;
if (handler != null)
handler(this, EventArgs.Empty);
InitialMapView();
}
internal double MetersPerUnit
{
get
{
return _provider.GetMetersPerUnit();
}
}
private double CalculateScale(double mcsW, double mcsH, int devW, int devH)
{
var mpu = this.MetersPerUnit;
var mpp = GetMetersPerPixel(_map.DisplayDpi);
if (devH * mcsW > devW * mcsH)
return mcsW * mpu / (devW * mpp); //width-limited
else
return mcsH * mpu / (devH * mpp); //height-limited
}
///
/// Gets or sets a value indicating whether tiled groups are converted to normal groups. Must be set before
/// a map is loaded via the method
///
[Category("MapGuide Viewer")] //NOXLATE
[Description("If true, the map being viewed will have all its tiled groups converted to non-tiled groups. Tiled groups are not supported by this viewer and are not rendered")] //NOXLATE
[DefaultValue(false)]
[MgComponentProperty]
public bool ConvertTiledGroupsToNonTiled
{
get;
set;
}
///
/// Gets whether to use the RenderMap API instead of RenderDynamicOverlay if the map has tiled
/// layers. RenderMap includes tiled layers as part of the output image, but will not take advantage
/// of any tile caching mechanisms. Setting this property to true nullifies any effect of the
/// property
///
[Category("MapGuide Viewer")] //NOXLATE
[Description("If true, the viewer will use the RenderMap API instead of RenderDynamicOverlay allowing tiled layers to be rendered to the final image. Setting this property to true nullifies the ConvertTiledGroupsToNonTiled property")] //NOXLATE
[DefaultValue(true)]
[MgComponentProperty]
public bool UseRenderMapIfTiledLayersExist { get; set; }
///
/// Gets whether to respect the list of finite display scales in a map being viewed if there are any defined.
/// If true, all zooms will "snap" to the nearest finite display scale. Otherwise, the viewer will disregard
/// this list when zooming in or out.
///
[Category("MapGuide Viewer")] //NOXLATE
[Description("If true, all zooms will snap to the nearest finite display scale defined in the map being viewed")] //NOXLATE
[DefaultValue(true)]
[MgComponentProperty]
public bool RespectFiniteDisplayScales { get; set; }
///
/// Raised when the viewer has been initialized
///
[Category("MapGuide Viewer")] //NOXLATE
[Description("Raised when the viewer has been initialized with a runtime map")] //NOXLATE
public event EventHandler MapLoaded;
private System.Timers.Timer _delayedResizeTimer;
void OnDelayResizeTimerElapsed(object sender, System.Timers.ElapsedEventArgs e)
{
var action = new MethodInvoker(() =>
{
ResetMouseWheelPaintTransforms();
if (_map != null)
{
//Trace.TraceInformation("Performing delayed resize to (" + this.Width + ", " + this.Height + ")"); //NOXLATE
_provider.SetDisplaySize(this.Width, this.Height);
UpdateExtents();
RefreshMap(false);
}
_delayedResizeTimer.Stop();
//Trace.TraceInformation("Delayed resize timer stopped"); //NOXLATE
});
if (this.InvokeRequired)
this.Invoke(action);
else
action();
}
void OnControlResized(object sender, EventArgs e)
{
if (_delayedResizeTimer == null)
{
_delayedResizeTimer = new System.Timers.Timer();
_delayedResizeTimer.Elapsed += OnDelayResizeTimerElapsed;
//Trace.TraceInformation("Delay resize timer initialized"); //NOXLATE
}
if (_delayedResizeTimer.Enabled)
{
//Trace.TraceInformation("Stopped delayed resize"); //NOXLATE
_delayedResizeTimer.Stop();
}
_delayedResizeTimer.Interval = 500;
_delayedResizeTimer.Start();
//Trace.TraceInformation("Delayed resize re-scheduled"); //NOXLATE
}
///
/// Clears the current selection
///
public void ClearSelection()
{
_provider.ClearSelection(_selection);
if (_selectionImage != null)
{
_selectionImage.Dispose();
_selectionImage = null;
}
var handler = this.SelectionChanged;
if (handler != null)
handler(this, EventArgs.Empty);
this.Refresh();
}
///
/// Gets the current runtime map
///
///
public MgMapBase GetMap()
{
return _map;
}
///
/// Gets the map viewer provider for this control
///
///
public MgMapViewerProvider GetProvider()
{
return _provider;
}
///
/// Gets the selection set of the runtime map
///
///
public MgSelectionBase GetSelection()
{
return _selection;
}
private bool HasSelection()
{
var layers = _selection.GetLayers();
return layers != null;
}
private static int GetSelectionTotal(MgSelectionBase sel)
{
int total = 0;
var layers = sel.GetLayers();
if (layers != null)
{
for (int i = 0; i < layers.GetCount(); i++)
{
var layer = layers.GetItem(i);
total += sel.GetSelectedFeaturesCount(layer, layer.FeatureClassName);
}
}
return total;
}
private MapDigitizationType _digitizingType = MapDigitizationType.None;
///
/// Gets the type of object being currently digitized. If the digitization type is None, then
/// the viewer is not currently digitizing
///
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public MapDigitizationType DigitizingType
{
get { return _digitizingType; }
private set
{
if (_digitizingType.Equals(value))
return;
if (value != MapDigitizationType.None)
{
this.ActiveTool = MapActiveTool.None;
this.Cursor = Cursors.Cross;
}
else
{
this.Cursor = Cursors.Default;
}
_digitizingType = value;
OnPropertyChanged("DigitizingType"); //NOXLATE
}
}
class RenderWorkArgs
{
public RenderWorkArgs() { this.UseRenderMap = false; }
public bool UseRenderMap { get; set; }
public MgViewerRenderingOptions SelectionRenderingOptions { get; set; }
public MgViewerRenderingOptions MapRenderingOptions { get; set; }
public bool RaiseEvents { get; set; }
public bool InvalidateRegardless { get; set; }
}
class RenderResult
{
public Image Image { get; set; }
public Image SelectionImage { get; set; }
public bool RaiseEvents { get; set; }
public bool InvalidateRegardless { get; set; }
}
///
/// Refreshes the current map view
///
public void RefreshMap()
{
RefreshMap(true);
}
///
/// Updates the rendered selection. Call this method if you have manipulated the selection
/// set outside of the viewer. This does not raise the event
///
///
/// If you have modified the selection as a result of calling , calling
/// this method is not necessary as it will have automatically do this.
///
public void UpdateSelection()
{
UpdateSelection(false);
}
///
/// Updates the rendered selection. Call this method if you have manipulated the selection
/// set outside of the viewer
///
/// Indicates if the event should be raised as well
///
/// If you have modified the selection as a result of calling , calling
/// this method is not necessary as it will have automatically do this.
///
public void UpdateSelection(bool raise)
{
RenderSelection();
if (raise)
{
var handler = this.SelectionChanged;
if (handler != null)
handler(this, EventArgs.Empty);
}
}
internal void RenderSelection()
{
RenderSelection(false);
}
internal void RenderSelection(bool invalidateRegardless)
{
//This is our refresh action
RefreshAction action = new RefreshAction(() =>
{
if (HasSelection())
{
this.IsBusy = true;
UpdateSelectionRenderingOptions();
renderWorker.RunWorkerAsync(new RenderWorkArgs()
{
SelectionRenderingOptions = _selectionRenderOpts,
RaiseEvents = false,
InvalidateRegardless = invalidateRegardless
});
}
else
{
if (invalidateRegardless)
this.Invalidate();
}
});
//If an existing rendering operation is in progress queue it if
//there isn't one queued. Because there is no point in doing the
//same thing more than once
if (this.IsBusy)
{
if (_queuedRefresh == null) //No refresh operations currently queued
_queuedRefresh = action;
}
else //Otherwise execute it immediately
{
action();
}
}
delegate void RefreshAction();
RefreshAction _queuedRefresh = null;
internal void RefreshMap(bool raiseEvents)
{
//This is our refresh action
RefreshAction action = new RefreshAction(() =>
{
var args = new RenderWorkArgs()
{
UseRenderMap = this.UseRenderMapIfTiledLayersExist && this.HasTiledLayers,
MapRenderingOptions = _overlayRenderOpts,
RaiseEvents = raiseEvents
};
if (HasSelection())
{
UpdateSelectionRenderingOptions();
args.SelectionRenderingOptions = _selectionRenderOpts;
}
this.IsBusy = true;
renderWorker.RunWorkerAsync(args);
});
//If an existing rendering operation is in progress queue it if
//there isn't one queued. Because there is no point in doing the
//same thing more than once
if (this.IsBusy)
{
if (_queuedRefresh == null) //No refresh operations currently queued
_queuedRefresh = action;
}
else //Otherwise execute it immediately
{
action();
}
}
private bool? _hasTiledLayers;
internal bool HasTiledLayers
{
get
{
if (!_hasTiledLayers.HasValue)
{
if (_map != null)
{
var groups = _map.GetLayerGroups();
for (int i = 0; i < groups.Count; i++)
{
if (groups[i].LayerGroupType == MgLayerGroupType.BaseMap)
{
_hasTiledLayers = true;
break;
}
}
if (!_hasTiledLayers.HasValue)
_hasTiledLayers = false;
}
else
{
_hasTiledLayers = false;
}
}
return _hasTiledLayers.Value;
}
}
///
/// Raised when the map has been refreshed
///
[Category("MapGuide Viewer")] //NOXLATE
[Description("Raised after the viewer has refreshed")] //NOXLATE
public event EventHandler MapRefreshed;
private bool _busy = false;
#if TRACE
private Stopwatch _renderSw = new Stopwatch();
#endif
///
/// Indicates whether a rendering operation is in progress
///
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public bool IsBusy
{
get { return _busy; }
private set
{
if (_busy.Equals(value))
return;
_busy = value;
#if TRACE
//Trace.TraceInformation("IsBusy = " + _busy);
if (value)
{
_renderSw.Reset();
_renderSw.Start();
}
else
{
_renderSw.Stop();
//Trace.TraceInformation("Rendering operation took {0}ms", _renderSw.ElapsedMilliseconds);
}
#endif
OnPropertyChanged("IsBusy"); //NOXLATE
}
}
///
/// Pans the view left by a pre-defined distance
///
///
public void PanLeft(bool refresh)
{
PanTo(_extX1 + (_extX2 - _extX1) / 3, _extY2 + (_extY1 - _extY2) / 2, refresh);
}
///
/// Pans the view up by a pre-defined distance
///
///
public void PanUp(bool refresh)
{
PanTo(_extX1 + (_extX2 - _extX1) / 2, _extY1 - (_extY1 - _extY2) / 3, refresh);
}
///
/// Pans the view right by a pre-defined distance
///
///
public void PanRight(bool refresh)
{
PanTo(_extX2 - (_extX2 - _extX1) / 3, _extY2 + (_extY1 - _extY2) / 2, refresh);
}
///
/// Pans the view down by a pre-defined distance
///
///
public void PanDown(bool refresh)
{
PanTo(_extX1 + (_extX2 - _extX1) / 2, _extY2 + (_extY1 - _extY2) / 3, refresh);
}
///
/// Zooms the extents.
///
public void ZoomExtents()
{
var scale = CalculateScale((_orgX2 - _orgX1), (_orgY1 - _orgY2), this.Width, this.Height);
ZoomToView(_orgX1 + ((_orgX2 - _orgX1) / 2), _orgY2 + ((_orgY1 - _orgY2) / 2), scale, true);
}
///
/// Zooms to the view defined by the specified extent
///
///
///
///
///
public void ZoomToExtents(double llx, double lly, double urx, double ury)
{
var scale = CalculateScale((urx - llx), (ury - lly), this.Width, this.Height);
ZoomToView(llx + ((urx - llx) / 2), ury + ((lly - ury) / 2), scale, true);
}
///
/// Zooms to scale.
///
/// The scale.
public void ZoomToScale(double scale)
{
ZoomToView(_extX1 + (_extX2 - _extX1) / 2, _extY2 + (_extY1 - _extY2) / 2, scale, true);
}
///
/// Zooms to the specified map view
///
///
///
///
///
public void ZoomToView(double x, double y, double scale, bool refresh)
{
ZoomToView(x, y, scale, refresh, true);
}
internal void PanTo(double x, double y, bool refresh)
{
ZoomToView(x, y, _map.ViewScale, refresh);
}
private void UpdateExtents()
{
//Update current extents
double mpu = this.MetersPerUnit;
double scale = _map.ViewScale;
double mpp = GetMetersPerPixel(_map.DisplayDpi);
var pt = _map.ViewCenter;
var coord = pt.Coordinate;
var mcsWidth = _map.DisplayWidth * mpp * scale / mpu;
var mcsHeight = _map.DisplayHeight * mpp * scale / mpu;
_extX1 = coord.X - mcsWidth / 2;
_extY1 = coord.Y + mcsHeight / 2;
_extX2 = coord.X + mcsWidth / 2;
_extY2 = coord.Y - mcsHeight / 2;
}
private bool PruneHistoryEntriesFromCurrentView()
{
if (_viewHistoryIndex < _viewHistory.Count - 1)
{
int removed = 0;
for (int i = _viewHistory.Count - 1; i > _viewHistoryIndex; i--)
{
_viewHistory.RemoveAt(i);
removed++;
}
return removed > 0;
}
return false;
}
internal void ZoomToView(double x, double y, double scale, bool refresh, bool raiseEvents)
{
ZoomToView(x, y, scale, refresh, raiseEvents, true);
}
///
/// Navigates to the previous view in the history stack
///
public void PreviousView()
{
var newIndex = _viewHistoryIndex - 1;
if (newIndex < 0)
return;
var view = _viewHistory[newIndex];
ZoomToView(view.X, view.Y, view.Scale, true, true, false);
_viewHistoryIndex = newIndex;
OnPropertyChanged("ViewHistoryIndex"); //NOXLATE
}
///
/// Navigates to the next view in the history stack
///
public void NextView()
{
//Cannot advance from current view
if (_viewHistoryIndex == _viewHistory.Count - 1)
return;
var newIndex = _viewHistoryIndex + 1;
if (newIndex > _viewHistory.Count - 1)
return;
var view = _viewHistory[newIndex];
ZoomToView(view.X, view.Y, view.Scale, true, true, false);
_viewHistoryIndex = newIndex;
OnPropertyChanged("ViewHistoryIndex"); //NOXLATE
}
///
/// Gets the current index in the view history stack
///
public int ViewHistoryIndex
{
get { return _viewHistoryIndex; }
}
///
/// Gets the view history stack. The first item being the earliest and the last item being the most recent.
///
public ReadOnlyCollection ViewHistory
{
get { return _viewHistory.AsReadOnly(); }
}
internal void ZoomToView(double x, double y, double scale, bool refresh, bool raiseEvents, bool addToHistoryStack)
{
var newScale = NormalizeScale(scale);
if (_map.FiniteDisplayScaleCount > 0 && this.RespectFiniteDisplayScales)
newScale = GetNearestFiniteScale(scale);
if (addToHistoryStack)
{
//If not current view, then any entries from the current view index are no longer needed
if (ViewHistoryIndex < _viewHistory.Count - 1)
PruneHistoryEntriesFromCurrentView();
_viewHistory.Add(new MgMapViewHistoryEntry(x, y, newScale));
OnPropertyChanged("ViewHistory"); //NOXLATE
_viewHistoryIndex = _viewHistory.Count - 1;
OnPropertyChanged("ViewHistoryIndex"); //NOXLATE
}
_provider.SetViewCenterXY(x, y);
#if VIEWER_DEBUG
UpdateCenterDebugPoint();
//var mapExt = _map.MapExtent;
//var dataExt = _map.DataExtent;
Trace.TraceInformation("Map Extent is ({0},{1} {2},{3})", mapExt.LowerLeftCoordinate.X, mapExt.LowerLeftCoordinate.Y, mapExt.UpperRightCoordinate.X, mapExt.UpperRightCoordinate.Y);
Trace.TraceInformation("Data Extent is ({0},{1} {2},{3})", dataExt.LowerLeftCoordinate.X, dataExt.LowerLeftCoordinate.Y, dataExt.UpperRightCoordinate.X, dataExt.UpperRightCoordinate.Y);
Trace.TraceInformation("Center is (" + x + ", " + y + ")");
#endif
var oldScale = _map.ViewScale;
_provider.SetViewScale(newScale);
if (oldScale != _map.ViewScale)
{
var handler = this.MapScaleChanged;
if (handler != null)
handler(this, EventArgs.Empty);
}
UpdateExtents();
#if VIEWER_DEBUG
Trace.TraceInformation("Current extents is ({0},{1} {2},{3})", _extX1, _extY1, _extX2, _extY2);
#endif
//Then refresh
if (refresh)
RefreshMap(raiseEvents);
}
private double GetNearestFiniteScale(double scale)
{
return _map.GetFiniteDisplayScaleAt(GetFiniteScaleIndex(scale));
}
private int GetFiniteScaleIndex(double reqScale)
{
var index = 0;
var scaleCount = _map.GetFiniteDisplayScaleCount();
if (scaleCount > 0)
{
var bestDiff = Math.Abs(_map.GetFiniteDisplayScaleAt(0) - reqScale);
for (var i = 1; i < scaleCount; i++)
{
var scaleDiff = Math.Abs(_map.GetFiniteDisplayScaleAt(i) - reqScale);
if (scaleDiff < bestDiff)
{
index = i;
bestDiff = scaleDiff;
if (bestDiff == 0)
{
//perfect match
break;
}
}
}
}
return index;
}
///
/// Raised when the scale of the current runtime map has changed
///
[Category("MapGuide Viewer")] //NOXLATE
[Description("Raised when the zoom scale of the map has changed")] //NOXLATE
public event EventHandler MapScaleChanged;
///
/// Raised when the selection has changed. Note that programmatic selection modifications
/// will not raise this event.
///
[Category("MapGuide Viewer")] //NOXLATE
[Description("Raised when active viewer selection has changed")] //NOXLATE
public event EventHandler SelectionChanged;
private void renderWorker_DoWork(object sender, DoWorkEventArgs e)
{
var args = (RenderWorkArgs)e.Argument;
var res = new RenderResult() { RaiseEvents = args.RaiseEvents, InvalidateRegardless = args.InvalidateRegardless };
if (args.MapRenderingOptions != null)
{
MgByteReader br = null;
if (args.UseRenderMap)
br = _provider.RenderMap(null, args.MapRenderingOptions.Format);
else
br = _provider.RenderDynamicOverlay(null, args.MapRenderingOptions);
byte[] b = new byte[br.GetLength()];
br.Read(b, b.Length);
using (var ms = new MemoryStream(b))
{
res.Image = Image.FromStream(ms);
}
}
if (args.SelectionRenderingOptions != null)
{
var br = _provider.RenderDynamicOverlay(_selection, args.SelectionRenderingOptions);
byte[] b = new byte[br.GetLength()];
br.Read(b, b.Length);
using (var ms = new MemoryStream(b))
{
res.SelectionImage = Image.FromStream(ms);
}
}
e.Result = res;
}
private void renderWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
this.IsBusy = AreWorkersBusy();
if (e.Error != null)
{
MessageBox.Show(e.Error.Message, Strings.TitleError); //NOXLATE
}
else
{
var res = (RenderResult)e.Result;
//reset translation
translate = new System.Drawing.Point();
bool bInvalidate = false;
//set the image
if (res.Image != null)
{
if (this.Image != null)
{
this.Image.Dispose();
this.Image = null;
}
//Trace.TraceInformation("Set map image"); //NOXLATE
this.Image = res.Image;
bInvalidate = true;
}
if (res.SelectionImage != null)
{
if (_selectionImage != null)
{
_selectionImage.Dispose();
_selectionImage = null;
}
//Trace.TraceInformation("Set selection image"); //NOXLATE
_selectionImage = res.SelectionImage;
bInvalidate = true;
}
//If there is a queued refresh action, execute it now
if (_queuedRefresh != null)
{
//Trace.TraceInformation("Executing queued rendering operation"); //NOXLATE
_queuedRefresh();
_queuedRefresh = null;
}
else
{
if (bInvalidate || res.InvalidateRegardless)
Invalidate(true);
/*
var center = _map.ViewCenter;
var ext = _map.DataExtent;
System.Diagnostics.Trace.TraceInformation(
"**POST-RENDER**{2}Map Center: {0}, {1}{2}Lower left: {3}, {4}{2}Upper Right: {5}, {6}", //NOXLATE
center.Coordinate.X,
center.Coordinate.Y,
Environment.NewLine,
ext.LowerLeftCoordinate.X,
ext.LowerLeftCoordinate.Y,
ext.UpperRightCoordinate.X,
ext.UpperRightCoordinate.Y);
*/
if (res.RaiseEvents)
{
var handler = this.MapRefreshed;
if (handler != null)
handler(this, EventArgs.Empty);
}
}
}
}
private bool AreWorkersBusy()
{
return renderWorker.IsBusy;
}
///
/// Zooms to the initial map view
///
public void InitialMapView()
{
InitialMapView(true);
}
private void InitialMapView(bool refreshMap)
{
var scale = CalculateScale((_orgX2 - _orgX1), (_orgY1 - _orgY2), this.Width, this.Height);
ZoomToView(_orgX1 + ((_orgX2 - _orgX1) / 2), _orgY2 + ((_orgY1 - _orgY2) / 2), scale, refreshMap);
}
private static Rectangle? GetRectangle(Point dPtStart, Point dPtEnd)
{
int? left = null;
int? right = null;
int? top = null;
int? bottom = null;
if (dPtEnd.X < dPtStart.X)
{
if (dPtEnd.Y < dPtStart.Y)
{
left = dPtEnd.X;
bottom = dPtStart.Y;
top = dPtEnd.Y;
right = dPtStart.X;
}
else if (dPtEnd.Y > dPtStart.Y)
{
left = dPtEnd.X;
bottom = dPtEnd.Y;
top = dPtStart.Y;
right = dPtStart.X;
}
else
{
//Equal
}
}
else
{
if (dPtEnd.X > dPtStart.X)
{
if (dPtEnd.Y < dPtStart.Y)
{
left = dPtStart.X;
bottom = dPtStart.Y;
top = dPtEnd.Y;
right = dPtEnd.X;
}
else if (dPtEnd.Y > dPtStart.Y)
{
left = dPtStart.X;
bottom = dPtEnd.Y;
top = dPtStart.Y;
right = dPtEnd.X;
}
else
{
//Equal
}
}
//else equal
}
if (left.HasValue && right.HasValue && top.HasValue && bottom.HasValue)
{
return new Rectangle(left.Value, top.Value, (right.Value - left.Value), (bottom.Value - top.Value));
}
return null;
}
private double _zoomInFactor;
private double _zoomOutFactor;
///
/// Gets or sets the factor by which to multiply the scale to zoom in
///
[Category("MapGuide Viewer")] //NOXLATE
[Description("The zoom in factor")] //NOXLATE
[MgComponentProperty]
public double ZoomInFactor
{
get { return _zoomInFactor; }
set
{
if (value.Equals(_zoomInFactor))
return;
_zoomInFactor = value;
OnPropertyChanged("ZoomInFactor"); //NOXLATE
}
}
///
/// Gets or sets the factor by which to multiply the scale to zoom out
///
[Category("MapGuide Viewer")] //NOXLATE
[Description("The zoom out factor")] //NOXLATE
[MgComponentProperty]
public double ZoomOutFactor
{
get { return _zoomOutFactor; }
set
{
if (value.Equals(_zoomOutFactor))
return;
_zoomOutFactor = value;
OnPropertyChanged("ZoomOutFactor"); //NOXLATE
}
}
private int? _lastTooltipX;
private int? _lastTooltipY;
private string QueryFirstVisibleTooltip(int x, int y)
{
//No intialized map
if (_map == null)
return string.Empty;
//No change in position
if (_lastTooltipX == x && _lastTooltipY == y && !string.IsNullOrEmpty(_activeTooltipText))
return _activeTooltipText;
if (_lastTooltipX.HasValue && _lastTooltipY.HasValue)
{
//Not considered a significant change
if (Math.Abs(x - _lastTooltipX.Value) < MOUSE_MOVE_TOLERANCE ||
Math.Abs(y - _lastTooltipY.Value) < MOUSE_MOVE_TOLERANCE)
return _activeTooltipText;
}
_lastTooltipX = x;
_lastTooltipY = y;
var mapPt1 = ScreenToMapUnits(x - this.PointPixelBuffer, y - this.PointPixelBuffer);
var mapPt2 = ScreenToMapUnits(x + this.PointPixelBuffer, y + this.PointPixelBuffer);
var ringCoords = new MgCoordinateCollection();
ringCoords.Add(_geomFact.CreateCoordinateXY(mapPt2.X, mapPt2.Y));
ringCoords.Add(_geomFact.CreateCoordinateXY(mapPt1.X, mapPt2.Y));
ringCoords.Add(_geomFact.CreateCoordinateXY(mapPt1.X, mapPt1.Y));
ringCoords.Add(_geomFact.CreateCoordinateXY(mapPt2.X, mapPt1.Y));
ringCoords.Add(_geomFact.CreateCoordinateXY(mapPt2.X, mapPt2.Y)); //Close it
var poly = _geomFact.CreatePolygon(_geomFact.CreateLinearRing(ringCoords), new MgLinearRingCollection());
MgTooltipQueryResult tr = _provider.QueryMapFeatures(MgQueryRequestType.Tooltip, null, poly, MgFeatureSpatialOperations.Intersects, "", 1, 5 /* has tooltips */) as MgTooltipQueryResult;
if (tr != null)
{
_activeTooltipText = tr.Tooltip;
return _activeTooltipText;
}
return string.Empty;
}
private static bool IsRasterClass(MgClassDefinition cls)
{
var props = cls.GetProperties();
for (int i = 0; i < props.GetCount(); i++)
{
var p = props.GetItem(i);
if (p.PropertyType == MgFeaturePropertyType.RasterProperty)
return true;
}
return false;
}
private static bool IsRasterLayer(MgLayerBase layer)
{
var cls = layer.GetClassDefinition();
return IsRasterClass(cls);
}
public System.Drawing.Image GetCurrentImage()
{
var bmp = new Bitmap(this.Width, this.Height);
this.DrawToBitmap(bmp, this.ClientRectangle);
return bmp;
}
///
/// Copies the image of the current map to the clipboard
///
public void CopyMap()
{
Clipboard.SetImage(this.GetCurrentImage());
}
///
/// Selects features from all selectable layers that intersects the given geometry
///
///
public void SelectByGeometry(MgGeometry geom)
{
SelectByGeometry(geom, -1);
}
///
/// Selects features from all selectable layers that intersects the given geometry
///
///
///
public void SelectByGeometry(MgGeometry geom, int maxFeatures)
{
//Don't select if dragging. This is the cause of the failure to render
//multiple selections, which required a manual refresh afterwards
if (isDragging)
return;
#if TRACE
var sw = new Stopwatch();
sw.Start();
#endif
//We will be either wiping or updating the existing selection set
MgSelectionQueryResult sel = _provider.QueryMapFeatures(MgQueryRequestType.Selection, null, geom, MgFeatureSpatialOperations.Intersects, "", maxFeatures, (1 | 2)) as MgSelectionQueryResult; //1=Visible, 2=Selectable, 4=HasTooltips
if (sel != null)
{
MgSelectionBase newSel = sel.Selection;
string newXml = newSel.ToXml();
_selection.FromXml(newXml);
newSel.Dispose();
}
else
{
_selection.FromXml(string.Empty);
}
#if TRACE
sw.Stop();
//Trace.TraceInformation("Selection processing completed in {0}ms", sw.ElapsedMilliseconds);
#endif
//This selection may result in nothing, so we invalidate the selection image beforehand
if (_selectionImage != null)
{
_selectionImage.Dispose();
_selectionImage = null;
}
RenderSelection(true); //This is either async or queued up. Either way do this before firing off selection changed
var handler = this.SelectionChanged;
if (handler != null)
handler(this, EventArgs.Empty);
}
protected override void OnResize(EventArgs e)
{
OnControlResized(this, e);
base.OnResize(e);
}
#region Mouse handlers
private void OnMapMouseLeave(object sender, EventArgs e)
{
//Need to do this otherwise a tooltip query is made at the viewer boundary
if (delayTooltipTimer != null && delayTooltipTimer.Enabled)
delayTooltipTimer.Stop();
}
private void OnMapMouseDown(object sender, MouseEventArgs e)
{
if (IsBusy) return;
HandleMouseDown(e);
}
private void OnMapMouseMove(object sender, MouseEventArgs e)
{
if (IsBusy) return;
HandleMouseMove(e);
}
private void OnMapMouseUp(object sender, MouseEventArgs e)
{
if (IsBusy) return;
HandleMouseUp(e);
}
private void OnMapMouseWheel(object sender, MouseEventArgs e)
{
if (IsBusy) return;
this.Focus();
HandleMouseWheel(e);
}
private void OnMapMouseClick(object sender, MouseEventArgs e)
{
if (IsBusy) return;
this.Focus();
HandleMouseClick(e);
}
private void OnMapMouseDoubleClick(object sender, MouseEventArgs e)
{
if (IsBusy) return;
this.Focus();
HandleMouseDoubleClick(e);
}
private void HandleMouseDoubleClick(MouseEventArgs e)
{
//Not enough points to constitute a line string or polygon
if (dPath.Count < 2)
return;
if (this.DigitizingType == MapDigitizationType.LineString)
{
//Fix the last one, can't edit last one because points are value types
dPath.RemoveAt(dPath.Count - 1);
dPath.Add(new Point(e.X, e.Y));
OnLineStringDigitized(dPath);
}
else if (this.DigitizingType == MapDigitizationType.Polygon)
{
//Fix the last one, can't edit last one because points are value types
dPath.RemoveAt(dPath.Count - 1);
dPath.Add(new Point(e.X, e.Y));
OnPolygonDigitized(dPath);
}
}
private double? delayRenderScale;
private PointF? delayRenderViewCenter;
private float? mouseWheelSx = null;
private float? mouseWheelSy = null;
private float? mouseWheelTx = null;
private float? mouseWheelTy = null;
private int? mouseWheelDelta = null;
private System.Timers.Timer delayRenderTimer = null;
private void HandleMouseWheel(MouseEventArgs e)
{
if (!this.HasLoadedMap)
return;
if (delayRenderTimer == null)
{
delayRenderTimer = new System.Timers.Timer();
delayRenderTimer.Enabled = false;
delayRenderTimer.Elapsed += new System.Timers.ElapsedEventHandler(OnDelayRender);
delayRenderTimer.Interval = this.MouseWheelDelayRenderInterval;
}
delayRenderTimer.Stop();
delayRenderTimer.Start();
//Trace.TraceInformation("Postponed delay render"); //NOXLATE
//Trace.TraceInformation("Mouse delta: " + e.Delta + " (" + (e.Delta > 0 ? "Zoom in" : "Zoom out") + ")"); //NOXLATE
//Negative delta = zoom out, Positive delta = zoom in
//deltas are in units of 120, so treat each multiple of 120 as a "zoom unit"
if (!mouseWheelSx.HasValue && !mouseWheelSy.HasValue)
{
mouseWheelSx = 1.0f;
mouseWheelSy = 1.0f;
}
if (!mouseWheelDelta.HasValue)
mouseWheelDelta = 0;
if (e.Delta > 0) //Zoom In
{
mouseWheelDelta++;
mouseWheelSx -= 0.1f;
mouseWheelSy -= 0.1f;
Invalidate();
}
else if (e.Delta < 0) //Zoom Out
{
mouseWheelDelta--;
mouseWheelSx += 0.1f;
mouseWheelSy += 0.1f;
Invalidate();
}
//Trace.TraceInformation("Delta units is: " + mouseWheelDelta); //NOXLATE
//Completely ripped the number crunching here from the AJAX viewer with no sense of shame whatsoever :)
delayRenderScale = GetNewScale(_map.ViewScale, mouseWheelDelta.Value);
double zoomChange = _map.ViewScale / delayRenderScale.Value;
//Determine the center of the new, zoomed map, in current screen device coords
double screenZoomCenterX = e.X - (e.X - this.Width / 2) / zoomChange;
double screenZoomCenterY = e.Y - (e.Y - this.Height / 2) / zoomChange;
delayRenderViewCenter = ScreenToMapUnits(screenZoomCenterX, screenZoomCenterY);
var mpu = this.MetersPerUnit;
var mpp = GetMetersPerPixel(_map.DisplayDpi);
var w = (_extX2 - _extX1) * this.MetersPerUnit / (delayRenderScale * mpp);
if (w > 20000)
{
w = 20000;
}
var h = w * ((double)this.Height / (double)this.Width);
var xClickOffset = screenZoomCenterX - this.Width / 2;
var yClickOffset = screenZoomCenterY - this.Height / 2;
//Set the paint transforms. Will be reset once the delayed render is fired away
mouseWheelTx = (float)((double)this.Width / 2 - w / 2 - xClickOffset * zoomChange);
mouseWheelTy = (float)((double)this.Height / 2 - h / 2 - yClickOffset * zoomChange);
mouseWheelSx = (float)(w / (double)this.Width);
mouseWheelSy = (float)(h / (double)this.Height);
//Trace.TraceInformation("Paint transform (tx: " + mouseWheelTx + ", ty: " + mouseWheelTy + ", sx: " + mouseWheelSx + ", sy: " + mouseWheelSy + ")"); //NOXLATE
}
static double GetMetersPerPixel(int dpi)
{
return 0.0254 / dpi;
}
double GetNewScale(double currentScale, int wheelZoomDelta)
{
var newScale = currentScale;
/*
//handle finite zoom scales for tiled map
if (finscale)
{
var newScaleIndex = sci - wheelDelta;
if (newScaleIndex < 0)
{
newScaleIndex = 0;
}
if (newScaleIndex > scales.length - 1)
{
newScaleIndex = scales.length - 1;
}
newScale = scales[newScaleIndex];
}
//no finite zoom scales (untiled map)
else */
{
var zoomChange = Math.Pow(1.5, wheelZoomDelta);
newScale = zoomChange > 0 ? currentScale / zoomChange : this.MaxScale;
newScale = NormalizeScale(newScale);
}
return newScale;
}
double NormalizeScale(double scale)
{
if (scale < this.MinScale)
return this.MinScale;
if (scale > this.MaxScale)
return this.MaxScale;
return scale;
}
void OnDelayRender(object sender, System.Timers.ElapsedEventArgs e)
{
//Trace.TraceInformation("Delay rendering"); //NOXLATE
//Trace.TraceInformation("Set new map coordinates to (" + delayRenderViewCenter.Value.X + ", " + delayRenderViewCenter.Value.Y + " at " + delayRenderScale.Value + ")"); //NOXLATE
ResetMouseWheelPaintTransforms();
MethodInvoker action = () => { ZoomToView(delayRenderViewCenter.Value.X, delayRenderViewCenter.Value.Y, delayRenderScale.Value, true); };
if (this.InvokeRequired)
this.Invoke(action);
else
action();
}
private void ResetMouseWheelPaintTransforms()
{
if (delayRenderTimer != null)
delayRenderTimer.Stop();
mouseWheelSx = null;
mouseWheelSy = null;
mouseWheelTx = null;
mouseWheelTy = null;
mouseWheelDelta = 0;
//Trace.TraceInformation("Mouse wheel paint transform reset");
}
private void HandleMouseClick(MouseEventArgs e)
{
if (e.Button == MouseButtons.Right)
return;
if (this.DigitizingType != MapDigitizationType.None)
{
//Points are easy, one click and you're done
if (this.DigitizingType == MapDigitizationType.Point)
{
OnPointDigitizationCompleted(new Point(e.X, e.Y));
}
else
{
//Check first click in digitization
if (_digitizationYetToStart)
{
if (this.DigitizingType == MapDigitizationType.LineString ||
this.DigitizingType == MapDigitizationType.Polygon)
{
dPath.Add(new Point(e.X, e.Y));
dPath.Add(new Point(e.X, e.Y)); //This is a transient one
}
else
{
dPtStart.X = e.X;
dPtStart.Y = e.Y;
}
_digitizationYetToStart = false;
}
else
{
if (this.DigitizingType == MapDigitizationType.LineString ||
this.DigitizingType == MapDigitizationType.Polygon)
{
var pt = dPath[dPath.Count - 1];
pt.X = e.X;
pt.Y = e.Y;
dPath.Add(new Point(e.X, e.Y)); //This is a transient one
OnLineStringSegmentDigitized(dPath[dPath.Count - 3], dPath[dPath.Count - 2]);
}
else
{
//Fortunately, these are all 2-click affairs meaning this is
//the second click
switch (this.DigitizingType)
{
case MapDigitizationType.Circle:
{
dPtEnd.X = e.X;
dPtEnd.Y = e.Y;
OnCircleDigitized(dPtStart, dPtEnd);
}
break;
case MapDigitizationType.Line:
{
dPtEnd.X = e.X;
dPtEnd.Y = e.Y;
OnLineDigitized(dPtStart, dPtEnd);
}
break;
case MapDigitizationType.Rectangle:
{
dPtEnd.X = e.X;
dPtEnd.Y = e.Y;
var rect = GetRectangle(dPtStart, dPtEnd);
if (rect.HasValue)
OnRectangleDigitized(rect.Value);
}
break;
}
}
}
}
}
else
{
if (this.ActiveTool == MapActiveTool.Select)
{
var mapPt1 = ScreenToMapUnits(e.X - this.PointPixelBuffer, e.Y - this.PointPixelBuffer);
var mapPt2 = ScreenToMapUnits(e.X + this.PointPixelBuffer, e.Y + this.PointPixelBuffer);
var coord1 = _geomFact.CreateCoordinateXY(mapPt1.X, mapPt1.Y);
var coord2 = _geomFact.CreateCoordinateXY(mapPt2.X, mapPt2.Y);
var dist = _mapMeasure.GetDistance(coord1, coord2);
MgGeometry geom = _wktRW.Read(Util.MakeWktPolygon(mapPt1.X, mapPt1.Y, mapPt2.X, mapPt2.Y));
SelectByGeometry(geom, 1);
}
else if (this.ActiveTool == MapActiveTool.ZoomIn)
{
if (!isDragging)
{
var mapPt = ScreenToMapUnits(e.X, e.Y);
var scale = _map.ViewScale;
ZoomToView(mapPt.X, mapPt.Y, scale * ZoomInFactor, true);
}
}
else if (this.ActiveTool == MapActiveTool.ZoomOut)
{
if (!isDragging)
{
var mapPt = ScreenToMapUnits(e.X, e.Y);
var scale = _map.ViewScale;
ZoomToView(mapPt.X, mapPt.Y, scale * ZoomOutFactor, true);
}
}
}
}
private void HandleMouseDown(MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
dragStart = e.Location;
//Trace.TraceInformation("Drag started at (" + dragStart.X + ", " + dragStart.Y + ")"); //NOXLATE
switch (this.ActiveTool)
{
case MapActiveTool.Pan:
//Trace.TraceInformation("START PANNING"); //NOXLATE
break;
case MapActiveTool.Select:
//Trace.TraceInformation("START SELECT"); //NOXLATE
break;
case MapActiveTool.ZoomIn:
//Trace.TraceInformation("START ZOOM"); //NOXLATE
break;
}
}
}
private System.Drawing.Point translate;
private System.Drawing.Point dragStart;
bool isDragging = false;
private int _mouseX;
private int _mouseY;
private string _activeTooltipText;
private int _mouseDx;
private int _mouseDy;
///
/// A mouse is considered to have moved if the differerence in either X or Y directions is greater
/// than this number
///
const int MOUSE_MOVE_TOLERANCE = 2;
private void HandleMouseMove(MouseEventArgs e)
{
if (_mouseX == e.X &&
_mouseY == e.Y)
{
return;
}
//Record displacement
_mouseDx = e.X - _mouseX;
_mouseDy = e.Y - _mouseY;
_mouseX = e.X;
_mouseY = e.Y;
var mapPt = ScreenToMapUnits(e.X, e.Y);
OnMouseMapPositionChanged(mapPt.X, mapPt.Y);
if (this.ActiveTool == MapActiveTool.Pan || this.ActiveTool == MapActiveTool.Select || this.ActiveTool == MapActiveTool.ZoomIn)
{
if (e.Location != dragStart && !isDragging && e.Button == MouseButtons.Left)
{
isDragging = true;
}
if (this.ActiveTool == MapActiveTool.Pan)
{
if (isDragging)
{
translate = new System.Drawing.Point(e.X - dragStart.X, e.Y - dragStart.Y);
}
}
this.TooltipsEnabled = !isDragging && this.FeatureTooltipsEnabled;
//Only query for tooltips if not digitizing
if (this.DigitizingType == MapDigitizationType.None &&
(this.ActiveTool == MapActiveTool.Select || this.ActiveTool == MapActiveTool.Pan) &&
this.TooltipsEnabled)
{
if (delayTooltipTimer == null)
{
delayTooltipTimer = new System.Timers.Timer();
delayTooltipTimer.Enabled = false;
delayTooltipTimer.Elapsed += new System.Timers.ElapsedEventHandler(OnDelayTooltipTimerElapsed);
delayTooltipTimer.Interval = this.TooltipDelayInterval;
}
_delayTooltipQueryPoint = new Point(e.X, e.Y);
delayTooltipTimer.Start();
if (Math.Abs(e.X - _lastTooltipQueryX) > 2 || Math.Abs(e.Y - _lastTooltipQueryY) > 2)
{
_activeTooltipText = null;
Invalidate();
}
}
else
{
_activeTooltipText = null;
}
if (e.Button == MouseButtons.Left || !string.IsNullOrEmpty(_activeTooltipText))
Invalidate();
}
else if (this.DigitizingType != MapDigitizationType.None)
{
if (dPath.Count >= 2)
{
//Fix the last one, can't edit last one because points are value types
dPath.RemoveAt(dPath.Count - 1);
dPath.Add(new Point(e.X, e.Y));
//Trace.TraceInformation("Updating last point of a {0} point path", dPath.Count); //NOXLATE
}
Invalidate();
}
}
void OnDelayTooltipTimerElapsed(object sender, System.Timers.ElapsedEventArgs e)
{
delayTooltipTimer.Stop();
if (_delayTooltipQueryPoint.HasValue)
{
_activeTooltipText = QueryFirstVisibleTooltip(_delayTooltipQueryPoint.Value.X, _delayTooltipQueryPoint.Value.Y);
_lastTooltipQueryX = _delayTooltipQueryPoint.Value.X;
_lastTooltipQueryY = _delayTooltipQueryPoint.Value.Y;
_delayTooltipQueryPoint = null;
Invalidate();
}
}
private int _lastTooltipQueryX;
private int _lastTooltipQueryY;
private Point? _delayTooltipQueryPoint = null;
private System.Timers.Timer delayTooltipTimer = null;
private void HandleMouseUp(MouseEventArgs e)
{
if (isDragging)
{
isDragging = false;
if (this.ActiveTool == MapActiveTool.Pan)
{
//FIXME: This is not perfect. The view will be slightly off of where you released the mouse button
//System.Diagnostics.Trace.TraceInformation("Dragged screen distance (" + translate.X + ", " + translate.Y + ")"); //NOXLATE
int dx = e.X - dragStart.X;
int dy = e.Y - dragStart.Y;
var centerScreen = new Point(this.Location.X + (this.Width / 2), this.Location.Y + (this.Height / 2));
centerScreen.X -= translate.X;
centerScreen.Y -= translate.Y;
var pt = _map.ViewCenter.Coordinate;
var coord = ScreenToMapUnits(centerScreen.X, centerScreen.Y);
double mdx = coord.X - pt.X;
double mdy = coord.Y - pt.Y;
ZoomToView(coord.X, coord.Y, _map.ViewScale, true);
//Trace.TraceInformation("END PANNING"); //NOXLATE
}
else if (this.ActiveTool == MapActiveTool.Select)
{
var mapPt = ScreenToMapUnits(e.X, e.Y);
var mapDragPt = ScreenToMapUnits(dragStart.X, dragStart.Y);
var ringCoords = new MgCoordinateCollection();
ringCoords.Add(_geomFact.CreateCoordinateXY(mapDragPt.X, mapDragPt.Y));
ringCoords.Add(_geomFact.CreateCoordinateXY(mapPt.X, mapDragPt.Y));
ringCoords.Add(_geomFact.CreateCoordinateXY(mapPt.X, mapPt.Y));
ringCoords.Add(_geomFact.CreateCoordinateXY(mapDragPt.X, mapPt.Y));
ringCoords.Add(_geomFact.CreateCoordinateXY(mapDragPt.X, mapDragPt.Y)); //Close it
var poly = _geomFact.CreatePolygon(_geomFact.CreateLinearRing(ringCoords), new MgLinearRingCollection());
SelectByGeometry(poly);
}
else if (this.ActiveTool == MapActiveTool.ZoomIn)
{
var mapPt = ScreenToMapUnits(e.X, e.Y);
var mapDragPt = ScreenToMapUnits(dragStart.X, dragStart.Y);
PointF ll;
PointF ur;
if (mapPt.X <= mapDragPt.X && mapPt.Y <= mapDragPt.Y)
{
ll = mapPt;
ur = mapDragPt;
}
else
{
ll = mapDragPt;
ur = mapPt;
}
ZoomToExtents(ll.X, ll.Y, ur.X, ur.Y);
}
}
}
private void OnMouseMapPositionChanged(double x, double y)
{
var handler = this.MouseMapPositionChanged;
if (handler != null)
handler(this, new MapPointEventArgs(x, y));
}
///
/// Raised when the map cursor position has changed
///
[Category("MapGuide Viewer")] //NOXLATE
[Description("Raised when the map position as indicated by the current mouse pointer has changed")] //NOXLATE
public event EventHandler MouseMapPositionChanged;
#endregion
private MapActiveTool _tool;
///
/// Gets or sets the active tool
///
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
[MgComponentProperty]
public MapActiveTool ActiveTool
{
get
{
return _tool;
}
set
{
if (_tool.Equals(value))
return;
_tool = value;
switch (value)
{
case MapActiveTool.Pan:
using (var ms = new MemoryStream(Properties.Resources.grab))
{
this.Cursor = new Cursor(ms);
}
break;
case MapActiveTool.ZoomIn:
using (var ms = new MemoryStream(Properties.Resources.zoomin))
{
this.Cursor = new Cursor(ms);
}
break;
case MapActiveTool.ZoomOut:
using (var ms = new MemoryStream(Properties.Resources.zoomout))
{
this.Cursor = new Cursor(ms);
}
break;
case MapActiveTool.None:
case MapActiveTool.Select:
{
this.Cursor = Cursors.Default;
}
break;
}
//Clear to prevent stray tooltips from being rendered
if (value != MapActiveTool.Select &&
value != MapActiveTool.Pan)
{
_activeTooltipText = null;
}
if (value != MapActiveTool.None)
this.DigitizingType = MapDigitizationType.None;
OnPropertyChanged("ActiveTool"); //NOXLATE
}
}
///
/// Screens to map units.
///
/// The x.
/// The y.
///
public PointF ScreenToMapUnits(double x, double y)
{
return ScreenToMapUnits(x, y, false);
}
private PointF ScreenToMapUnits(double sx, double sy, bool allowOutsideWindow)
{
if (!allowOutsideWindow)
{
if (sx > this.Width - 1) sx = this.Width - 1;
else if (sx < 0) sx = 0;
if (sy > this.Height - 1) sy = this.Height - 1;
else if (sy < 0) sy = 0;
}
var mx = _extX1 + ((_extX2 - _extX1) * (sx / this.Width));
var my = _extY1 - ((_extY1 - _extY2) * (sy / this.Height));
return new PointF((float)mx, (float)my);
}
///
/// Converts the specified map coordinates to screen coordinates
///
///
///
///
public Point MapToScreenUnits(double mx, double my)
{
//Equation derived from high-school mathematical inversion of the
//ScreenToMapUnits formula :) Tests thus far indicate this formula
//is correct.
var sx = this.Width * ((mx - _extX1) / (_extX2 - _extX1));
var sy = this.Height * ((my - _extY1) / (_extY2 - _extY1));
return new Point(Convert.ToInt32(Math.Round(sx)), Convert.ToInt32(Math.Round(sy)));
}
///
/// Occurs when a property value changes.
///
[Category("MapGuide Viewer")] //NOXLATE
[Description("Raised when a public property of this component has changed")] //NOXLATE
public event PropertyChangedEventHandler PropertyChanged;
///
/// Called when a public property has changed
///
/// The name.
protected void OnPropertyChanged(string name)
{
var handler = this.PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(name));
}
public bool HasLoadedMap { get { return _map != null; } }
int IMapViewer.ControlHeight { get { return this.Height; } }
int IMapViewer.ControlWidth { get { return this.Width; } }
public event PaintEventHandler PreMapRender;
public event PaintEventHandler PostMapRender;
private Dictionary _properties;
public IEnumerable ComponentProperties
{
get
{
CheckAndInitProperties();
return _properties.Values;
}
}
private void CheckAndInitProperties()
{
if (_properties == null)
{
_properties = new Dictionary();
var props = this.GetType().GetProperties();
foreach (var p in props)
{
var attributes = p.GetCustomAttributes(true);
foreach (var att in attributes)
{
var compAttr = att as MgComponentPropertyAttribute;
if (compAttr != null)
{
_properties[p.Name] = p;
break;
}
}
}
}
}
public void SetPropertyValue(string propertyName, object value)
{
CheckAndInitProperties();
if (!_properties.ContainsKey(propertyName))
throw new InvalidOperationException(string.Format(Strings.ErrorInvalidComponentProperty, propertyName));
var prop = _properties[propertyName];
prop.SetValue(this, Convert.ChangeType(value, prop.PropertyType), null);
}
public object GetPropertyValue(string propertyName)
{
CheckAndInitProperties();
if (!_properties.ContainsKey(propertyName))
throw new InvalidOperationException(string.Format(Strings.ErrorInvalidComponentProperty, propertyName));
return _properties[propertyName].GetValue(propertyName, null);
}
}
}