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; namespace OSGeo.MapGuide.Viewer { /// /// A map viewer component /// 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 MgMeasure _mapMeasure; private Color _mapBgColor; private bool firstRun = true; 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(); } } const double MINIMUM_ZOOM_SCALE = 5.0; #if VIEWER_DEBUG private MgdLayer _debugLayer; private void CreateDebugFeatureSource() { var id = new MgDataPropertyDefinition("ID"); id.DataType = MgPropertyType.Int32; id.Nullable = false; id.SetAutoGeneration(true); var geom = new MgGeometricPropertyDefinition("Geometry"); geom.GeometryTypes = MgFeatureGeometricType.Point; geom.SpatialContextAssociation = "MapCs"; var cls = new MgClassDefinition(); cls.Name = "Debug"; var props = cls.GetProperties(); props.Add(id); props.Add(geom); var idProps = cls.GetIdentityProperties(); idProps.Add(id); cls.DefaultGeometryPropertyName = "Geometry"; var schema = new MgFeatureSchema("Default", "Default schema"); 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"); var createSdf = new MgCreateSdfParams("MapCs", _map.GetMapSRS(), schema); 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")); var source = new MgByteSource(bytes, bytes.Length); var debugLayerId = new MgResourceIdentifier("Session:" + sessionId + "//" + debugFsId.Name + ".LayerDefinition"); var breader = source.GetReader(); resSvc.SetResource(debugLayerId, breader, null); _debugLayer = new MgdLayer(debugLayerId, resSvc); _debugLayer.SetLegendLabel("Debug Layer"); _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 + " " + _map.ViewCenter.Coordinate.Y + ")"); var agf = _agfRW.Write(center); if (!_debugCenter.Contains("Geometry")) { MgGeometryProperty geom = new MgGeometryProperty("Geometry", agf); _debugCenter.Add(geom); } else { MgGeometryProperty geom = (MgGeometryProperty)_debugCenter.GetItem("Geometry"); geom.SetValue(agf); } int deleted = _debugLayer.DeleteFeatures(""); Trace.TraceInformation("Deleted {0} debug points", deleted); var reader = _debugLayer.InsertFeatures(_debugCenter); int inserted = 0; while (reader.ReadNext()) { inserted++; } reader.Close(); Trace.TraceInformation("Added {0} debug points", inserted); _debugLayer.ForceRefresh(); } #endif private MgCoordinateSystem _mapCs; /// /// Initializes a new instance of the class. /// public MgMapViewer() { this.ShowVertexCoordinatesWhenDigitizing = false; this.FeatureTooltipsEnabled = false; this.TooltipsEnabled = false; this.ZoomInFactor = 0.5; this.ZoomOutFactor = 2.0; this.SelectionColor = Color.Blue; this.DigitizingFillTransparency = 100; this.DigitizingOutline = Brushes.Red; this.DigitzingFillColor = Color.White; this.TooltipFillColor = Color.LightYellow; this.TooltipFillTransparency = 200; 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; 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; } /// /// Raises the event. /// /// A that contains the event data. protected override void OnKeyUp(KeyEventArgs e) { if (e.KeyCode == Keys.Escape) { CancelDigitization(); } } private 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; if (renderWorker != null) { renderWorker.DoWork -= renderWorker_DoWork; renderWorker.RunWorkerCompleted -= renderWorker_RunWorkerCompleted; } 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); } private Color _selColor; /// /// Gets or sets the color used to render selected features /// [Category("MapGuide Viewer")] [Description("The color to use for active selections")] public Color SelectionColor { get { return _selColor; } set { _selColor = value; OnPropertyChanged("SelectionColor"); } } private Color _tooltipFillColor; /// /// Gets or sets the color of the tooltip fill. /// /// /// The color of the tooltip fill. /// [Category("MapGuide Viewer")] [Description("The color background for feature tooltips")] internal Color TooltipFillColor { get { return _tooltipFillColor; } set { if (!value.Equals(_tooltipFillColor)) { _tooltipFillColor = value; OnPropertyChanged("TooltipFillColor"); } } } private int _tooltipFillTransparency; [Category("MapGuide Viewer")] [Description("The color background transparency for feature tooltips")] [DefaultValue(200)] internal int TooltipFillTransparency { get { return _tooltipFillTransparency; } set { if (!value.Equals(_tooltipFillTransparency)) { _tooltipFillTransparency = value; OnPropertyChanged("TooltipFillTransparency"); } } } 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")] [Description("Indicates whether coordinate values are shown when digitizing geometry")] [DefaultValue(false)] public bool ShowVertexCoordinatesWhenDigitizing { get { return _showVertexCoords; } set { if (!value.Equals(_showVertexCoords)) { _showVertexCoords = value; OnPropertyChanged("ShowVertexCoordinatesWhenDigitizing"); } } } /// /// Raises the event. /// /// A that contains the event data. protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); Trace.TraceInformation("OnPaint(e)"); if (!translate.IsEmpty) e.Graphics.TranslateTransform(translate.X, translate.Y); if (_mapImage != null) { Trace.TraceInformation("Render Map"); e.Graphics.DrawImage(_mapImage, new PointF(0, 0)); } //Thread.Sleep(100); if (_selectionImage != null) { Trace.TraceInformation("Render Selection"); e.Graphics.DrawImage(_selectionImage, new PointF(0, 0)); } if (isDragging && (this.ActiveTool == MapActiveTool.Select || this.ActiveTool == MapActiveTool.ZoomIn)) { DrawDragRectangle(e); } else { if (this.DigitizingType != MapDigitizationType.None) { if (this.DigitizingType == MapDigitizationType.Point) { DrawTrackingTooltip(e, "Click to finish. Press ESC to cancel"); } 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); } } } } private Brush _digitizingOutline; [Category("MapGuide Viewer")] [Description("The outline color for geometries being digitized")] internal Brush DigitizingOutline { get { return _digitizingOutline; } set { _digitizingOutline = value; OnPropertyChanged("DigitizingOutline"); } } private int _digitizingFillTransparency; [Category("MapGuide Viewer")] [Description("The fill color transparency for geometries being digitized")] [DefaultValue(100)] internal int DigitizingFillTransparency { get { return _digitizingFillTransparency; } set { if (!value.Equals(_digitizingFillTransparency)) { _digitizingFillTransparency = value; OnPropertyChanged("DigitizingFillTransparency"); } } } private Color _digitizingFillColor; [Category("MapGuide Viewer")] [Description("The fill color for geometries being digitized")] internal Color DigitzingFillColor { get { return _digitizingFillColor; } set { _digitizingFillColor = value; OnPropertyChanged("DigitzingFillColor"); } } 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 = ""; if (mapSpace) { var mapPt = ScreenToMapUnits(devX, devY); text = string.Format("X: {0}, Y: {1}", mapPt.X, mapPt.Y); } else { text = string.Format("X: {0}, Y: {1}", devX, devY); } 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); 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); 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); DrawTrackingTooltip(e, "Click to finish. Press ESC to cancel"); } 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); DrawTrackingTooltip(e, "Click to finish. Press ESC to cancel"); } 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); } DrawTrackingTooltip(e, "Click again to add a new vertex.\nDouble-click to finish. Press ESC to cancel"); } 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); } DrawTrackingTooltip(e, "Click again to add a new vertex.\nDouble-click to finish. Press ESC to cancel"); } 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); e.Graphics.DrawRectangle(CreateOutlinePen(), r); Trace.TraceInformation("Fill rangle ({0} {1}, {2} {3})", r.Left, r.Top, r.Right, r.Bottom); 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); DrawTrackingTooltip(e, "Click to finish. Press ESC to cancel"); } } 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); e.Graphics.DrawRectangle(CreateOutlinePen(), r); Trace.TraceInformation("Fill rangle ({0} {1}, {2} {3})", r.Left, r.Top, r.Right, r.Bottom); 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)] public bool FeatureTooltipsEnabled { get { return _featTooltipsEnabled; } set { if (value.Equals(_featTooltipsEnabled)) return; _featTooltipsEnabled = value; if (!value) { _activeTooltipText = null; Invalidate(); } OnPropertyChanged("FeatureTooltipsEnabled"); } } /// /// 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 public void DigitizeCircle(CircleDigitizationCallback callback) { this.DigitizingType = MapDigitizationType.Circle; _digitzationCallback = callback; _digitizationYetToStart = true; } /// /// Starts the digitization process for a line /// /// The callback to be invoked when the digitization process completes public void DigitizeLine(LineDigitizationCallback callback) { this.DigitizingType = MapDigitizationType.Line; _digitzationCallback = callback; _digitizationYetToStart = true; } /// /// Starts the digitization process for a point /// /// The callback to be invoked when the digitization process completes public void DigitizePoint(PointDigitizationCallback callback) { this.DigitizingType = MapDigitizationType.Point; _digitzationCallback = callback; _digitizationYetToStart = true; } /// /// Starts the digitization process for a polygon /// /// The callback to be invoked when the digitization process completes public void DigitizePolygon(PolygonDigitizationCallback callback) { this.DigitizingType = MapDigitizationType.Polygon; _digitzationCallback = callback; _digitizationYetToStart = true; } /// /// Starts the digitization process for a line string (polyline) /// /// The callback to be invoked when the digitization process completes public void DigitizeLineString(LineStringDigitizationCallback callback) { this.DigitizingType = MapDigitizationType.LineString; _digitzationCallback = callback; _digitizationYetToStart = true; } /// /// Starts the digitization process for a rectangle /// /// The callback to be invoked when the digitization process completes public void DigitizeRectangle(RectangleDigitizationCallback callback) { this.DigitizingType = MapDigitizationType.Rectangle; _digitzationCallback = callback; _digitizationYetToStart = true; } private void ResetDigitzationState() { _digitzationCallback = null; dPath.Clear(); dPtEnd.X = dPtStart.Y = 0; dPtStart.X = dPtStart.Y = 0; this.DigitizingType = MapDigitizationType.None; 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 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)); } static MgViewerRenderingOptions CreateSelectionRenderingOptions(short red, short green, short blue) { return new MgViewerRenderingOptions("PNG", (1 | 4), new MgColor(red, green, blue)); } /// /// Initializes this viewer with the specified runtime map /// /// 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; _mapCs = _provider.GetMapCoordinateSystem(); _mapMeasure = _mapCs.GetMeasure(); if (_resSvc == null) { if (_resSvc == null) _resSvc = (MgResourceService)_provider.CreateService(MgServiceType.ResourceService); } _overlayRenderOpts = CreateMapRenderingOptions(0, 0, 255); _selectionRenderOpts = CreateSelectionRenderingOptions(0, 0, 255); _provider = provider; _map = provider.GetMap(); var bgColor = _map.GetBackgroundColor(); if (bgColor.Length == 8 || bgColor.Length == 6) { _mapBgColor = ColorTranslator.FromHtml("#" + bgColor); 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); } } _provider.RebuildLayerInfoCache(); _provider.CacheGeometryProperties(_map.GetLayers()); #if VIEWER_DEBUG CreateDebugFeatureSource(); #endif this.Focus(); var handler = this.ViewerInitialized; 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 = 0.0254 / _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")] [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")] [DefaultValue(false)] public bool ConvertTiledGroupsToNonTiled { get; set; } /// /// Raised when the viewer has been initialized /// [Category("MapGuide Viewer")] [Description("Raised when the viewer has been initialized with a runtime map")] public event EventHandler ViewerInitialized; private System.Timers.Timer _delayedResizeTimer; void OnDelayResizeTimerElapsed(object sender, System.Timers.ElapsedEventArgs e) { var action = new MethodInvoker(() => { if (_map != null) { Trace.TraceInformation("Performing delayed resize to (" + this.Width + ", " + this.Height + ")"); _provider.SetDisplaySize(this.Width, this.Height); UpdateExtents(); RefreshMap(false); } _delayedResizeTimer.Stop(); Trace.TraceInformation("Delayed resize timer stopped"); }); 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"); } if (_delayedResizeTimer.Enabled) { Trace.TraceInformation("Stopped delayed resize"); _delayedResizeTimer.Stop(); } _delayedResizeTimer.Interval = 500; _delayedResizeTimer.Start(); Trace.TraceInformation("Delayed resize re-scheduled"); } /// /// 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"); } } class RenderWorkArgs { public MgViewerRenderingOptions SelectionRenderingOptions { get; set; } public MgViewerRenderingOptions MapRenderingOptions { get; set; } public bool RaiseEvents { get; set; } } class RenderResult { public Image Image { get; set; } public Image SelectionImage { get; set; } public bool RaiseEvents { 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 /// public void UpdateSelection() { RenderSelection(); } internal void RenderSelection() { //This is our refresh action RefreshAction action = new RefreshAction(() => { if (HasSelection()) { this.IsBusy = true; UpdateSelectionRenderingOptions(); renderWorker.RunWorkerAsync(new RenderWorkArgs() { SelectionRenderingOptions = _selectionRenderOpts, RaiseEvents = false, }); } }); //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() { 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(); } } /// /// Raised when the map has been refreshed /// [Category("MapGuide Viewer")] [Description("Raised after the viewer has refreshed")] 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"); } } /// /// 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 = 0.0254 / _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; } internal void ZoomToView(double x, double y, double scale, bool refresh, bool raiseEvents) { _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(Math.Max(scale, MINIMUM_ZOOM_SCALE)); 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); } /// /// Raised when the scale of the current runtime map has changed /// [Category("MapGuide Viewer")] [Description("Raised when the zoom scale of the map has changed")] public event EventHandler MapScaleChanged; /// /// Raised when the selection has changed. Note that programmatic selection modifications /// will not raise this event. /// [Category("MapGuide Viewer")] [Description("Raised when active viewer selection has changed")] public event EventHandler SelectionChanged; private void renderWorker_DoWork(object sender, DoWorkEventArgs e) { var args = (RenderWorkArgs)e.Argument; var res = new RenderResult() { RaiseEvents = args.RaiseEvents }; if (args.MapRenderingOptions != null) { var 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, "Error"); } 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"); this.Image = res.Image; bInvalidate = true; } if (res.SelectionImage != null) { if (_selectionImage != null) { _selectionImage.Dispose(); _selectionImage = null; } Trace.TraceInformation("Set selection image"); _selectionImage = res.SelectionImage; bInvalidate = true; } //If there is a queued refresh action, execute it now if (_queuedRefresh != null) { Trace.TraceInformation("Executing queued rendering operation"); _queuedRefresh(); _queuedRefresh = null; } else { if (bInvalidate) 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}", 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")] [Description("The zoom in factor")] public double ZoomInFactor { get { return _zoomInFactor; } set { if (value.Equals(_zoomInFactor)) return; _zoomInFactor = value; OnPropertyChanged("ZoomInFactor"); } } /// /// Gets or sets the factor by which to multiply the scale to zoom out /// [Category("MapGuide Viewer")] [Description("The zoom out factor")] public double ZoomOutFactor { get { return _zoomOutFactor; } set { if (value.Equals(_zoomOutFactor)) return; _zoomOutFactor = value; OnPropertyChanged("ZoomOutFactor"); } } private static string MakeWktPolygon(double x1, double y1, double x2, double y2) { return "POLYGON((" + x1 + " " + y1 + ", " + x2 + " " + y1 + ", " + x2 + " " + y2 + ", " + x1 + " " + y2 + ", " + x1 + " " + y1 + "))"; } private int? _lastTooltipX; private int? _lastTooltipY; private string QueryFirstVisibleTooltip(int x, int y) { //No intialized map if (_map == null) return ""; //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 layers = _map.GetLayers(); var mapPt1 = ScreenToMapUnits(x - 2, y - 2); var mapPt2 = ScreenToMapUnits(x + 2, y + 2); 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()); for (int i = 0; i < layers.GetCount(); i++) { var layer = layers.GetItem(i); var layerGroup = layer.GetGroup(); //Layer not visible or its parent is not visible if (!layer.IsVisible() || (layerGroup != null && !layerGroup.IsVisible())) continue; //No defined tooltips if (!_provider.LayerHasTooltips(layer)) continue; //Drawing layers have no intelligence string fsId = layer.FeatureSourceId; if (fsId.EndsWith(MgResourceType.DrawingSource)) continue; //Nor do rasters if (IsRasterLayer(layer)) continue; var objId = layer.GetObjectId(); var ldfId = layer.GetLayerDefinition(); var ldfIdStr = ldfId.ToString(); //No tooltip detected //if (!_tooltipExpressions.ContainsKey(ldfIdStr)) if (!_provider.HasTooltips(ldfId)) continue; string propName = "QUERYTOOLTIP"; MgFeatureQueryOptions query = new MgFeatureQueryOptions(); query.AddComputedProperty(propName, _provider.GetTooltipExpression(ldfId)); query.SetSpatialFilter(_provider.GetGeometryProperty(objId), poly, MgFeatureSpatialOperations.Intersects); MgFeatureReader reader = null; reader = layer.SelectFeatures(query); try { if (reader.ReadNext()) { object value = null; var pt = reader.GetPropertyType(propName); switch (pt) { case MgPropertyType.String: value = reader.GetString(propName); break; case MgPropertyType.Boolean: value = reader.GetBoolean(propName); break; case MgPropertyType.Byte: value = reader.GetByte(propName); break; case MgPropertyType.DateTime: value = reader.GetByte(propName); break; case MgPropertyType.Double: case MgPropertyType.Decimal: value = reader.GetDouble(propName); break; case MgPropertyType.Int16: value = reader.GetInt16(propName); break; case MgPropertyType.Int32: value = reader.GetInt32(propName); break; case MgPropertyType.Int64: value = reader.GetInt64(propName); break; case MgPropertyType.Single: value = reader.GetSingle(propName); break; } if (value != null) return value.ToString(); //.Replace("\n", Environment.NewLine); } } finally { reader.Close(); } } 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); } /// /// Copies the image of the current map to the clipboard /// public void CopyMap() { var bmp = new Bitmap(this.Width, this.Height); this.DrawToBitmap(bmp, this.ClientRectangle); Clipboard.SetImage(bmp); } /// /// Selects features from all selectable layers that intersects the given geometry /// /// public void SelectByGeometry(MgGeometry geom) { //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 var layers = _map.GetLayers(); if (ModifierKeys != Keys.Control) _provider.ClearSelection(_selection); for (int i = 0; i < layers.GetCount(); i++) { var layer = layers.GetItem(i); if (!layer.Selectable || !layer.IsVisible()) continue; string fsId = layer.FeatureSourceId; if (fsId.EndsWith(MgResourceType.DrawingSource)) continue; //Nor do rasters if (IsRasterLayer(layer)) continue; //This could be a newly added layer _provider.CheckAndCacheGeometryProperty(layer); var objId = layer.GetObjectId(); MgFeatureQueryOptions query = new MgFeatureQueryOptions(); string filter = layer.GetFilter(); if (!string.IsNullOrEmpty(filter)) query.SetFilter(filter); query.SetSpatialFilter(_provider.GetGeometryProperty(objId), geom, MgFeatureSpatialOperations.Intersects); MgFeatureReader reader = layer.SelectFeatures(query); try { _selection.AddFeatures(layer, reader, 0); } finally { reader.Close(); } } #if TRACE sw.Stop(); Trace.TraceInformation("Selection processing completed in {0}ms", sw.ElapsedMilliseconds); #endif RenderSelection(); //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 OnMapMouseDown(object sender, MouseEventArgs e) { HandleMouseDown(e); } private void OnMapMouseMove(object sender, MouseEventArgs e) { HandleMouseMove(e); } private void OnMapMouseUp(object sender, MouseEventArgs e) { HandleMouseUp(e); } private void OnMapMouseClick(object sender, MouseEventArgs e) { this.Focus(); HandleMouseClick(e); } private void OnMapMouseDoubleClick(object sender, MouseEventArgs e) { 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 void HandleMouseClick(MouseEventArgs e) { 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 } 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 - 2, e.Y - 2); var mapPt2 = ScreenToMapUnits(e.X + 2, e.Y + 2); 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(MakeWktPolygon(mapPt1.X, mapPt1.Y, mapPt2.X, mapPt2.Y)); SelectByGeometry(geom); } 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 + ")"); switch (this.ActiveTool) { case MapActiveTool.Pan: Trace.TraceInformation("START PANNING"); break; case MapActiveTool.Select: Trace.TraceInformation("START SELECT"); break; case MapActiveTool.ZoomIn: Trace.TraceInformation("START ZOOM"); break; } } } private System.Drawing.Point translate; private System.Drawing.Point dragStart; bool isDragging = false; private int _mouseX; private int _mouseY; /* class ToolTipWaitArgs { public int Interval { get; set; } public int MouseX { get; set; } public int MouseY { get; set; } } void TooltipWaitProc(ToolTipWaitArgs e) { Thread.Sleep(e.Interval); this.BeginInvoke(new MethodInvoker(() => { //Compare old position against current if ((Math.Abs(e.MouseX - _mouseX) < 2) && (Math.Abs(e.MouseY - _mouseY) < 2)) { FireTooltipQuery(); } })); } private void FireTooltipQuery() { string tooltip = QueryFirstMatchingTooltip(); if (tooltip != null) { _tooltip.Show(tooltip, this); } else { _tooltip.Hide(this); } } private string QueryFirstMatchingTooltip() { throw new NotImplementedException(); } */ 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); } } // FIXME: // // We really need a JS setTimeout() equivalent for C# because that's what we want // to do here, set a delayed call to QueryFirstVisibleTooltip() that is aborted if // the mouse pointer has moved significantly since the last time. // // A timer based approach could probably work, but I haven't figured out the best // way yet. 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 TRACE var sw = new Stopwatch(); sw.Start(); #endif _activeTooltipText = QueryFirstVisibleTooltip(e.X, e.Y); #if TRACE sw.Stop(); Trace.TraceInformation("QueryFirstVisibleTooltip() executed in {0}ms", sw.ElapsedMilliseconds); #endif } 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); } Invalidate(); } } 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 + ")"); 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"); } 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")] [Description("Raised when the map position as indicated by the current mouse pointer has changed")] public event EventHandler MouseMapPositionChanged; #endregion private MapActiveTool _tool; /// /// Gets or sets the active tool /// [Browsable(false)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] 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"); } } /// /// Screens to map units. /// /// The x. /// The y. /// public PointF ScreenToMapUnits(double x, double y) { return ScreenToMapUnits(x, y, false); } private PointF ScreenToMapUnits(double x, double y, bool allowOutsideWindow) { if (!allowOutsideWindow) { if (x > this.Width - 1) x = this.Width - 1; else if (x < 0) x = 0; if (y > this.Height - 1) y = this.Height - 1; else if (y < 0) y = 0; } x = _extX1 + (_extX2 - _extX1) * (x / this.Width); y = _extY1 - (_extY1 - _extY2) * (y / this.Height); return new PointF((float)x, (float)y); } /// /// Occurs when a property value changes. /// [Category("MapGuide Viewer")] [Description("Raised when a public property of this component has changed")] 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)); } } }