// Copyright (C) Stichting Deltares 2016. All rights reserved. // // This file is part of Ringtoets. // // Ringtoets is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with this program. If not, see . // // All names, logos, and references to "Deltares" are registered trademarks of // Stichting Deltares and remain full property of Stichting Deltares at all times. // All rights reserved. using System; using System.Collections.Generic; using System.Linq; using System.Windows.Forms; using Core.Common.Base; using Core.Components.DotSpatial.Layer; using Core.Components.DotSpatial.MapFunctions; using Core.Components.Gis.Data; using Core.Components.Gis.Forms; using DotSpatial.Controls; using DotSpatial.Data; using DotSpatial.Topology; namespace Core.Components.DotSpatial.Forms { /// /// This class describes a map view with configured projection and function mode. /// public sealed class MapControl : Control, IMapControl { private readonly Cursor defaultCursor = Cursors.Default; private readonly RecursiveObserver mapDataCollectionObserver; private readonly IList drawnMapDataList = new List(); private Map map; private MapFunctionPan mapFunctionPan; private MapFunctionSelectionZoom mapFunctionSelectionZoom; private MouseCoordinatesMapExtension mouseCoordinatesMapExtension; private MapDataCollection data; /// /// Creates a new instance of . /// public MapControl() { InitializeMapView(); TogglePanning(); mapDataCollectionObserver = new RecursiveObserver(HandleMapDataCollectionChange, mdc => mdc.Collection); } public bool IsPanningEnabled { get; private set; } public bool IsRectangleZoomingEnabled { get; private set; } public bool IsMouseCoordinatesVisible { get; private set; } public MapDataCollection Data { get { return data; } set { if (data != null) { ClearMapData(); } data = value; mapDataCollectionObserver.Observable = data; if (data != null) { DrawInitialMapData(); } } } public void ZoomToAllVisibleLayers() { ZoomToAllVisibleLayers(Data); } public void ZoomToAllVisibleLayers(MapData layerData) { IEnvelope envelope = CreateEnvelopeForAllVisibleLayers(layerData); if (!envelope.IsNull) { Extent extent = envelope.ToExtent(); AddPadding(extent); map.ViewExtents = extent; } } public void TogglePanning() { ResetDefaultInteraction(); IsPanningEnabled = true; map.FunctionMode = FunctionMode.Pan; } public void ToggleRectangleZooming() { ResetDefaultInteraction(); IsRectangleZoomingEnabled = true; map.ActivateMapFunction(mapFunctionSelectionZoom); } public void ToggleMouseCoordinatesVisibility() { if (!IsMouseCoordinatesVisible) { mouseCoordinatesMapExtension.Activate(); IsMouseCoordinatesVisible = true; } else { mouseCoordinatesMapExtension.Deactivate(); IsMouseCoordinatesVisible = false; } } protected override void Dispose(bool disposing) { map.Dispose(); mouseCoordinatesMapExtension.Dispose(); mapDataCollectionObserver.Dispose(); base.Dispose(disposing); } private static void AddPadding(Extent extent) { var padding = Math.Min(extent.Height, extent.Width) * 0.05; if (Math.Max(extent.Height, extent.Width) + padding <= double.MaxValue) { extent.ExpandBy(padding); } } /// /// Defines the area taken up by the visible map-data based on the provided map-data. /// /// The data to determine the visible extent for. /// The area definition. /// Thrown when is /// not part of the drawn map features. private IEnvelope CreateEnvelopeForAllVisibleLayers(MapData mapData) { var collection = mapData as MapDataCollection; if (collection != null) { return CreateEnvelopeForAllVisibleLayers(collection); } DrawnMapData drawnMapData = drawnMapDataList.FirstOrDefault(dmd => dmd.FeatureBasedMapData.Equals(mapData)); if (drawnMapData == null) { throw new ArgumentException($@"Can only zoom to {typeof(MapData).Name} that is part of this {typeof(MapControl).Name}s drawn {nameof(mapData)}.", nameof(mapData)); } IEnvelope envelope = new Envelope(); if (LayerHasVisibleExtent(drawnMapData.FeatureBasedMapDataLayer)) { envelope.ExpandToInclude(drawnMapData.FeatureBasedMapDataLayer.Extent.ToEnvelope()); } return envelope; } /// /// Defines the area taken up by the visible map-data based on the provided map-data. /// /// The data to determine the visible extent for. /// The area definition. /// Thrown when or /// any of its children is not part of the drawn map features. private IEnvelope CreateEnvelopeForAllVisibleLayers(MapDataCollection mapData) { IEnvelope envelope = new Envelope(); foreach (MapData childMapData in mapData.Collection) { envelope.ExpandToInclude(CreateEnvelopeForAllVisibleLayers(childMapData)); } return envelope; } private static bool LayerHasVisibleExtent(IMapLayer layer) { return layer.IsVisible && !layer.Extent.IsEmpty(); } private void ResetDefaultInteraction() { IsPanningEnabled = false; IsRectangleZoomingEnabled = false; map.FunctionMode = FunctionMode.None; } private void InitializeMapView() { map = new DotSpatialMap { ProjectionModeDefine = ActionMode.Never, Dock = DockStyle.Fill, ZoomOutFartherThanMaxExtent = true }; // Configure the map pan function mapFunctionPan = map.MapFunctions.OfType().First(); mapFunctionPan.FunctionActivated += MapFunctionActivateFunction; mapFunctionPan.MouseDown += MapFunctionPanOnMouseDown; mapFunctionPan.MouseUp += MapFunctionOnMouseUp; // Add and configure the map selection zoom function mapFunctionSelectionZoom = new MapFunctionSelectionZoom(map); map.MapFunctions.Add(mapFunctionSelectionZoom); mapFunctionSelectionZoom.FunctionActivated += MapFunctionActivateFunction; mapFunctionSelectionZoom.MouseDown += MapFunctionSelectionZoomOnMouseDown; mapFunctionSelectionZoom.MouseUp += MapFunctionOnMouseUp; mouseCoordinatesMapExtension = new MouseCoordinatesMapExtension(map); ToggleMouseCoordinatesVisibility(); Controls.Add(map); } private void DrawInitialMapData() { foreach (FeatureBasedMapData featureBasedMapData in Data.GetFeatureBasedMapDataRecursively()) { DrawMapData(featureBasedMapData); } } private void ClearMapData() { foreach (DrawnMapData drawnMapData in drawnMapDataList) { drawnMapData.Observer.Dispose(); } drawnMapDataList.Clear(); map.ClearLayers(); } private void HandleMapDataCollectionChange() { var mapDataThatShouldBeDrawn = Data.GetFeatureBasedMapDataRecursively().ToList(); var drawnMapDataLookup = drawnMapDataList.ToDictionary(dmd => dmd.FeatureBasedMapData, dmd => dmd); DrawMissingMapDataOnCollectionChange(mapDataThatShouldBeDrawn, drawnMapDataLookup); RemoveRedundantMapDataOnCollectionChange(mapDataThatShouldBeDrawn, drawnMapDataLookup); drawnMapDataLookup = drawnMapDataList.ToDictionary(dmd => dmd.FeatureBasedMapData, dmd => dmd); ReorderMapDataOnCollectionChange(mapDataThatShouldBeDrawn, drawnMapDataLookup); } private void DrawMissingMapDataOnCollectionChange(IEnumerable mapDataThatShouldBeDrawn, IDictionary drawnMapDataLookup) { foreach (var mapDataToDraw in mapDataThatShouldBeDrawn.Where(mapDataToDraw => !drawnMapDataLookup.ContainsKey(mapDataToDraw))) { DrawMapData(mapDataToDraw); } } private void RemoveRedundantMapDataOnCollectionChange(IEnumerable mapDataThatShouldBeDrawn, IDictionary drawnMapDataLookup) { foreach (var featureBasedMapData in drawnMapDataLookup.Keys.Except(mapDataThatShouldBeDrawn)) { RemoveMapData(drawnMapDataLookup[featureBasedMapData]); } } private void ReorderMapDataOnCollectionChange(IList mapDataThatShouldBeDrawn, IDictionary drawnMapDataLookup) { for (var i = 0; i < mapDataThatShouldBeDrawn.Count; i++) { map.Layers.Move(drawnMapDataLookup[mapDataThatShouldBeDrawn[i]].FeatureBasedMapDataLayer, i); } } private void DrawMapData(FeatureBasedMapData featureBasedMapData) { var featureBasedMapDataLayer = FeatureBasedMapDataLayerFactory.Create(featureBasedMapData); var drawnMapData = new DrawnMapData { FeatureBasedMapData = featureBasedMapData, FeatureBasedMapDataLayer = featureBasedMapDataLayer }; drawnMapData.Observer = new Observer(() => { drawnMapData.FeatureBasedMapDataLayer.Update(); }) { Observable = featureBasedMapData }; drawnMapDataList.Add(drawnMapData); map.Layers.Add(featureBasedMapDataLayer); } private void RemoveMapData(DrawnMapData drawnMapDataToRemove) { drawnMapDataToRemove.Observer.Dispose(); drawnMapDataList.Remove(drawnMapDataToRemove); map.Layers.Remove(drawnMapDataToRemove.FeatureBasedMapDataLayer); } private void MapFunctionActivateFunction(object sender, EventArgs e) { map.Cursor = defaultCursor; } private void MapFunctionOnMouseUp(object sender, GeoMouseArgs e) { map.Cursor = defaultCursor; } private void MapFunctionPanOnMouseDown(object sender, GeoMouseArgs geoMouseArgs) { map.Cursor = geoMouseArgs.Button != MouseButtons.Right ? Cursors.Hand : defaultCursor; } private void MapFunctionSelectionZoomOnMouseDown(object sender, GeoMouseArgs geoMouseArgs) { switch (geoMouseArgs.Button) { case MouseButtons.Left: map.Cursor = Cursors.SizeNWSE; break; default: map.Cursor = map.IsBusy ? Cursors.SizeNWSE : defaultCursor; break; } } /// /// Lookup class for administration related to drawn map data layers. /// private class DrawnMapData { /// /// The feature based map data which the drawn is based upon. /// public FeatureBasedMapData FeatureBasedMapData { get; set; } /// /// The drawn map data layer. /// public IFeatureBasedMapDataLayer FeatureBasedMapDataLayer { get; set; } /// /// The observer attached to and responsible for updating . /// public Observer Observer { get; set; } } } }