// 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.Converter; using Core.Components.DotSpatial.Layer; using Core.Components.DotSpatial.MapFunctions; using Core.Components.Gis.Data; using Core.Components.Gis.Features; 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() { IEnvelope envelope = CreateEnvelopeForAllVisibleLayers(); if (!envelope.IsNull) { var 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); } } private IEnvelope CreateEnvelopeForAllVisibleLayers() { IEnvelope envelope = new Envelope(); foreach (IMapLayer layer in map.Layers.Where(layer => layer.IsVisible && !layer.Extent.IsEmpty())) { envelope.ExpandToInclude(layer.Extent.ToEnvelope()); } return envelope; } 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 (var featureBasedMapData in GetFeatureBasedMapDataRecursively(Data)) { DrawMapData(featureBasedMapData); } } private void ClearMapData() { foreach (DrawnMapData drawnMapData in drawnMapDataList) { drawnMapData.Observer.Dispose(); } drawnMapDataList.Clear(); map.ClearLayers(); } private void HandleMapDataCollectionChange() { var mapDataThatShouldBeDrawn = GetFeatureBasedMapDataRecursively(Data).ToList(); var drawnMapDataLookup = drawnMapDataList.ToDictionary(dmd => dmd.FeatureBasedMapData, dmd => dmd); DrawMissingMapDataOnCollectionChange(mapDataThatShouldBeDrawn, drawnMapDataLookup); RemoveRedundantMapDataOnCollectionChange(drawnMapDataLookup, mapDataThatShouldBeDrawn); drawnMapDataLookup = drawnMapDataList.ToDictionary(le => le.FeatureBasedMapData, le => le); MoveMapDataOnCollectionChange(mapDataThatShouldBeDrawn, drawnMapDataLookup); } private void DrawMissingMapDataOnCollectionChange(List mapDataThatShouldBeDrawn, Dictionary drawnMapDataLookup) { foreach (var mapDataToDraw in mapDataThatShouldBeDrawn) { if (!drawnMapDataLookup.ContainsKey(mapDataToDraw)) { DrawMapData(mapDataToDraw); } } } private void RemoveRedundantMapDataOnCollectionChange(Dictionary drawnMapDataLookup, List mapDataThatShouldBeDrawn) { foreach (var featureBasedMapData in drawnMapDataLookup.Keys.Except(mapDataThatShouldBeDrawn)) { RemoveMapData(drawnMapDataLookup[featureBasedMapData]); } } private void MoveMapDataOnCollectionChange(List mapDataThatShouldBeDrawn, Dictionary drawnMapDataLookup) { for (var i = 0; i < mapDataThatShouldBeDrawn.Count; i++) { map.Layers.Move(drawnMapDataLookup[mapDataThatShouldBeDrawn[i]].MapFeatureLayer, i); } } private void DrawMapData(FeatureBasedMapData featureBasedMapData) { var mapFeatureLayer = MapFeatureLayerFactory.CreateLayer(featureBasedMapData); var drawnMapData = new DrawnMapData { FeatureBasedMapData = featureBasedMapData, Features = featureBasedMapData.Features, MapFeatureLayer = mapFeatureLayer }; drawnMapData.Observer = new Observer(() => { drawnMapData.MapFeatureLayer.Update(); }) { Observable = featureBasedMapData }; drawnMapDataList.Add(drawnMapData); map.Layers.Add(mapFeatureLayer); } private void RemoveMapData(DrawnMapData drawnMapDataToRemove) { drawnMapDataToRemove.Observer.Dispose(); drawnMapDataList.Remove(drawnMapDataToRemove); map.Layers.Remove(drawnMapDataToRemove.MapFeatureLayer); } private static IEnumerable GetFeatureBasedMapDataRecursively(MapDataCollection mapDataCollection) { var featureBaseMapDataList = new List(); foreach (MapData mapData in mapDataCollection.Collection) { var nestedMapDataCollection = mapData as MapDataCollection; if (nestedMapDataCollection != null) { featureBaseMapDataList.AddRange(GetFeatureBasedMapDataRecursively(nestedMapDataCollection)); continue; } featureBaseMapDataList.Add((FeatureBasedMapData) mapData); } return featureBaseMapDataList; } 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; } } private class DrawnMapData { public FeatureBasedMapData FeatureBasedMapData { get; set; } public MapFeature[] Features { get; set; } public IFeatureBasedMapDataLayer MapFeatureLayer { get; set; } public Observer Observer { get; set; } } } }