#region License Information /* HeuristicLab * Copyright (C) 2002-2015 Heuristic and Evolutionary Algorithms Laboratory (HEAL) * * This file is part of HeuristicLab. * * HeuristicLab is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * HeuristicLab 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with HeuristicLab. If not, see . */ #endregion using System.Globalization; using GeoAPI.Geometries; using HeuristicLab.BioBoost.Representation; using HeuristicLab.BioBoost.Utils; using HeuristicLab.BioBoost.Views.MapViews; using HeuristicLab.Common; using HeuristicLab.Core.Views; using HeuristicLab.MainForm; using HeuristicLab.MainForm.WindowsForms; using SharpMap.Data; using SharpMap.Forms; using SharpMap.Layers; using SharpMap.Rendering.Decoration; using SharpMap.Rendering.Decoration.ScaleBar; using System; using System.Collections.Generic; using System.Drawing; using System.Drawing.Drawing2D; using System.Linq; using System.Threading; using System.Windows.Forms; using HeuristicLab.BioBoost.Evaluators; using HeuristicLab.PluginInfrastructure; using Point = System.Drawing.Point; namespace HeuristicLab.BioBoost.Views { [Content(typeof(BioBoostCompoundSolution), IsDefaultView = true)] public partial class BioBoostCompoundSolutionView : ItemView { private readonly Dictionary layerCache; private readonly GradientLegend legend; private readonly Disclaimer disclaimer; private Thread mapUpdateThread; private Thread costsUpdateThread; private ManualResetEventSlim updateMap; private ManualResetEventSlim updateCosts; private SharpMap.Rendering.Thematics.ColorBlend vectorColorBlend = new SharpMap.Rendering.Thematics.ColorBlend(new[] { Color.FromArgb(0, 0, 255), Color.FromArgb(128, 0, 128), Color.FromArgb(255, 0, 0), }, new[] { 0f, .5f, 1f }); private string currentRegion; public const string AllRegionsName = "All"; public new BioBoostCompoundSolution Content { get { return (BioBoostCompoundSolution)base.Content; } set { base.Content = value; } } public BioBoostCompoundSolutionView() { InitializeComponent(); layerCache = new Dictionary(); mapBox.Map.Decorations.Add(new ScaleBar { Anchor = MapDecorationAnchor.RightBottom, BarColor1 = Color.White, BarColor2 = Color.Black, BarStyle = ScaleBarStyle.Meridian, MapUnit = (int)Unit.Degree, BarUnit = (int)Unit.Kilometer, }); disclaimer = new Disclaimer { Anchor = MapDecorationAnchor.RightTop, Text = "ForestryResidues Utilization", Enabled = true, BackgroundColor = Color.FromArgb(128, 255, 255, 255), ForeColor = Color.Black, BorderColor = Color.Transparent, Font = new Font(FontFamily.GenericSansSerif, 10, FontStyle.Regular) }; legend = new GradientLegend {Size = new Size(300, 20)}; mapBox.Map.Decorations.Add(legend); mapBox.Map.Decorations.Add(new NorthArrow()); mapBox.Map.Decorations.Add(disclaimer); updateMap = new ManualResetEventSlim(true); mapUpdateThread = new Thread(UpdateMap); mapUpdateThread.Start(); updateCosts = new ManualResetEventSlim(true); costsUpdateThread = new Thread(UpdateCosts); costsUpdateThread.Start(); currentRegion = AllRegionsName; } protected override void DeregisterContentEvents() { Content.SolutionChanged -= SolutionChanged; base.DeregisterContentEvents(); } protected override void RegisterContentEvents() { base.RegisterContentEvents(); Content.SolutionChanged += SolutionChanged; } #region Event Handlers (Content) private void SolutionChanged(object sender, EventArgs e) { if (InvokeRequired) { Invoke(new EventHandler(SolutionChanged), sender, e); } else { UpgradeSolution(); InitializeLayers(); DiscoverLayerNames(); summaryView.Content = new RegionDetailData {RegionName = null, Solution = Content}; ShowSelectedLayers(false); ShowCosts(); } } #endregion protected override void OnContentChanged() { base.OnContentChanged(); if (Content == null) { ClearAllLayerNames(); ClearCostsSummary(); } else { UpgradeSolution(); InitializeLayers(); DiscoverLayerNames(); summaryView.Content = new RegionDetailData {RegionName = null, Solution = Content}; ShowSelectedLayers(true); ShowCosts(); } } private bool upgradeInProgress = false; private void UpgradeSolution() { if (Content != null && !upgradeInProgress) { upgradeInProgress = true; Content.UpdateTo(new BioBoostCompoundSolution( AggregateEvaluator.Evaluate(Content.ProblemDataReference, Content.DoubleValues, Content.IntValues), Content.ProblemDataReference)); upgradeInProgress = false; } } #region Main Logic private void ResetZoom() { if (mapBox.Map.Layers.Count > 0) mapBox.Map.ZoomToExtents(); mapBox.ActiveTool = MapBox.Tools.Pan; mapBox.Refresh(); } private void DiscoverLayerNames() { DiscoverValueLayers(); DiscoverVectorLayers(); } private class ListBoxItem { public Color Color; public String Text; } private void DiscoverVectorLayers() { var layerNames = vectorLayerNamesListBox.Items.Cast().Select(l => l.Text).ToList(); if (layerNames.SequenceEqual(Content.IntValues.Keys)) return; vectorLayerNamesListBox.BeginUpdate(); vectorLayerNamesListBox.Items.Clear(); int i = 0, n = Math.Max(1, Content.IntValues.Count - 1); foreach (var values in Content.IntValues) { var newItem = new ListBoxItem { Color = vectorColorBlend.GetColor(1f * i++ / n), Text = values.Key }; vectorLayerNamesListBox.Items.Add(newItem); if (layerNames.Any(name => name == values.Key) || layerNames.Count == 0) vectorLayerNamesListBox.SelectedItems.Add(newItem); } vectorLayerNamesListBox.EndUpdate(); } private void DiscoverValueLayers() { var layerNames = AddRelativePotentials(Content.DoubleValues.Keys).ToList(); layerNames.Sort(); if (valueLayerNamesListBox.Items.Cast().SequenceEqual(layerNames)) return; valueLayerNamesListBox.BeginUpdate(); var selectedValueLayerName = (string)valueLayerNamesListBox.SelectedItem; var firstFinalProduct = Content.ProblemDataReference.FinalProducts.FirstOrDefault(); if (selectedValueLayerName == null && firstFinalProduct != null) selectedValueLayerName = LayerDescriptor.RelativeCostAtSource.NameWithPrefix(firstFinalProduct.Value); valueLayerNamesListBox.Items.Clear(); foreach (var name in layerNames) { var i = valueLayerNamesListBox.Items.Add(name); if (name == selectedValueLayerName) valueLayerNamesListBox.SelectedIndex = i; } if (valueLayerNamesListBox.SelectedIndex == -1) valueLayerNamesListBox.SelectedIndex = 0; valueLayerNamesListBox.EndUpdate(); } private IEnumerable AddRelativePotentials(IEnumerable names) { foreach (var name in names) { if (LayerDescriptor.PotentialsFromProblemData.IsSuffixOf(name)) { var formattedName = name.Replace(LayerDescriptor.PotentialsFromProblemData.FullName, " Potentials"); yield return formattedName; yield return formattedName + " Relative"; } else { yield return name; } } } private void ClearAllLayerNames() { valueLayerNamesListBox.Items.Clear(); vectorLayerNamesListBox.Items.Clear(); } private void ShowSelectedLayers(bool resetZoom) { lock (updateMap) { if (pendingUpdate != null) { pendingUpdate.ResetZoom |= resetZoom; } else { pendingUpdate = new BackgroundArguments { ResetZoom = resetZoom }; } updateMap.Set(); } } private void ShowCosts() { updateCosts.Set(); } private sealed class BackgroundArguments { public bool ResetZoom; public LazyValueLayerGenerator ValueLayerGenerator; public List VectorLayerGenerators; public ILayer ValueLayer; public List TransportLayers; } private BackgroundArguments pendingUpdate; private bool runningUpdateMap = true; private bool runningUpdateCosts = true; private void UpdateMap() { while (runningUpdateMap) { BackgroundArguments currentUpdate = null; // wait for a signal to update the map while (!updateMap.Wait(2000)) { if (!runningUpdateMap) return; // stop thread if requested } lock (updateMap) { updateMap.Reset(); currentUpdate = pendingUpdate; pendingUpdate = null; } // only do something if the map-tab is active if (currentUpdate != null) { DoShowSelectedLayers(currentUpdate); } Thread.Sleep(500); } } private void UpdateCosts() { while (runningUpdateCosts) { // wait for signal to update the costs while (!updateCosts.Wait(2000)) { if (!runningUpdateCosts) return; // stop the thread if requested } updateCosts.Reset(); ClearCostsSummary(); PopulateRegionTree(); PopulateCostsSummary(); Thread.Sleep(500); } } private void DoShowSelectedLayers(BackgroundArguments args) { if (InvokeRequired) { try { var result = BeginInvoke(new Action(DoShowSelectedLayers), args); result.AsyncWaitHandle.WaitOne(); EndInvoke(result); } catch (ObjectDisposedException) { } } else { // only do something if the map-tab is active if (tabControl1.SelectedTab != tabPage1) return; GetConfigFromUI(args); var generator = args.ValueLayerGenerator; if (generator == null) return; args.ValueLayer = generator.Layer; // this might take a while args.TransportLayers = new List(); foreach (var g in args.VectorLayerGenerators) { args.TransportLayers.Add(g.Layer); // TODO: cycle colors of vectors } UpdateUI(args); } } private void GetConfigFromUI(BackgroundArguments args) { var valueLayerName = (string)valueLayerNamesListBox.SelectedItem; var vectorLayerNames = vectorLayerNamesListBox.SelectedItems.Cast().Select(i => i.Text); if (String.IsNullOrEmpty(valueLayerName)) return; var generator = layerCache[valueLayerName] as LazyValueLayerGenerator; if (generator == null) return; args.ValueLayerGenerator = generator; args.VectorLayerGenerators = vectorLayerNames.Select(n => layerCache[n] as LazyVectorLayerGenerator).Where(g => g != null).ToList(); } private void UpdateUI(BackgroundArguments args) { if (IsDisposed || Disposing) return; try { var currentZoom = mapBox.Map.Zoom; var currentCenter = mapBox.Map.Center; mapBox.Map.Layers.Clear(); mapBox.Map.Layers.Add(args.ValueLayer); foreach (var tl in args.TransportLayers) mapBox.Map.Layers.Add(tl); legend.MinValue = args.ValueLayerGenerator.ActualMinValue; legend.MaxValue = args.ValueLayerGenerator.ActualMaxValue; legend.Unit = args.ValueLayerGenerator.Unit; legend.ColorBlend = new ColorBlend { Colors = args.ValueLayerGenerator.ColorBlend.Colors, Positions = args.ValueLayerGenerator.ColorBlend.Positions, }; disclaimer.Text = args.ValueLayer.LayerName; if (args.ResetZoom) { ResetZoom(); } else { mapBox.ActiveTool = MapBox.Tools.Pan; mapBox.Map.Zoom = currentZoom; mapBox.Map.Center = currentCenter; mapBox.Refresh(); } } catch (Exception x) { MessageBox.Show(this, "Could not update UI, please try again:\n" + x); } } private void InitializeLayers() { lock (updateMap) { layerCache.Clear(); var geom = Content.Geometry; var locs = Content.LocationNames; foreach (var kvp in Content.DoubleValues) { var generator = new LazyValueLayerGenerator(geom, locs, kvp.Value, kvp.Key); var name = kvp.Key; if (kvp.Value.Min() > 0) generator.MinValue = 0; if (kvp.Value.Max() < 0) generator.MaxValue = 0; var layer = LayerDescriptor.GetLayer(name); if (layer == LayerDescriptor.Utilizations || layer == LayerDescriptor.UtilizationsEffective) { generator.MaxValue = 1; } else if (layer == Representation.LayerDescriptor.PotentialsFromProblemData) { generator.Unit = "t/a"; name = name.Replace(LayerDescriptor.PotentialsFromProblemData.FullName, " Potentials"); layerCache.Add(name + " Relative", new LazyValueLayerGenerator(geom, locs, kvp.Value, name + " Relative") { MinValue = 0, RealtiveToSize = true, Unit = "t/ha/a" }); } else { if (layer != null) generator.Unit = layer.Unit; } layerCache.Add(name, generator); } int i = 0, n = Math.Max(1, Content.IntValues.Count - 1); foreach (var kvp in Content.IntValues) { var name = kvp.Key; var targets = kvp.Value; var amounts = BioBoostCompoundSolution.FindAmountsTransportedFromSource(Content.DoubleValues, name); if (amounts != null) { for (int j = 0; j < amounts.Length; j++) { if (amounts[j] <= 0) { targets[j] = -1; } } } layerCache.Add( name, new LazyVectorLayerGenerator(geom, locs, targets, amounts, vectorColorBlend.GetColor(1.0f * i / n), name) { LineOffset = i * 2 }); i++; } } } private void ClearCostsSummary() { if (InvokeRequired) { try { var result = BeginInvoke(new Action(ClearCostsSummary)); result.AsyncWaitHandle.WaitOne(); EndInvoke(result); } catch (ObjectDisposedException) { } } else { if (IsDisposed || Disposing) return; bioBoostSolutionCostsView1.Content = null; } } private void PopulateCostsSummary() { if (InvokeRequired) { try { var result = BeginInvoke(new Action(PopulateCostsSummary)); result.AsyncWaitHandle.WaitOne(); EndInvoke(result); } catch (ObjectDisposedException) { } } else { if (IsDisposed || Disposing) return; // only do something if the costs-tab is active if (tabControl1.SelectedTab != tabPage2) return; regionsTreeView.SelectedNode = regionsTreeView.Nodes.Find(currentRegion, true).FirstOrDefault(); regionsTreeView.Focus(); if (IsDisposed) return; bioBoostSolutionCostsView1.Content = new RegionDetailData { RegionName = currentRegion, Solution = Content, }; } } private void PopulateRegionTree() { if (InvokeRequired) { try { var result = BeginInvoke(new Action(PopulateRegionTree)); result.AsyncWaitHandle.WaitOne(); EndInvoke(result); } catch (ObjectDisposedException) { } } else { if (IsDisposed || Disposing) return; regionsTreeView.Nodes.Clear(); if (Content == null) return; regionsTreeView.Nodes.Add(AllRegionsName, AllRegionsName); var allRegionsNodes = regionsTreeView.Nodes[AllRegionsName].Nodes; foreach (var location in Content.LocationNames) { var country = location.Substring(0, 2); if (!allRegionsNodes.ContainsKey(country)) { allRegionsNodes.Add(country, country); } allRegionsNodes[country].Nodes.Add(location, location); } regionsTreeView.HideSelection = false; regionsTreeView.Nodes[AllRegionsName].Expand(); } } #endregion public void SetFocus(string regionName) { var row = Content.Geometry.Features.Rows.Cast().FirstOrDefault(r => (string)r["NUTS_ID"] == regionName); if (row == null) return; var coords = row.Geometry.Envelope.Buffer(1).Coordinates; var env = new Envelope(coords.Min(c => c.X), coords.Max(c => c.X), coords.Min(c => c.Y), coords.Max(c => c.Y)); mapBox.Map.ZoomToBox(env); mapBox.Refresh(); } protected override void SetEnabledStateOfControls() { base.SetEnabledStateOfControls(); vectorLayerNamesListBox.Enabled = Content != null; valueLayerNamesListBox.Enabled = Content != null; openSolutionEditorButton.Enabled = Content != null && !Locked; } #region Auxiliary Methods private string GetRegionAtWorldPos(Coordinate loc) { var ds = new FeatureDataSet(); Content.Geometry.ExecuteIntersectionQuery(new NetTopologySuite.Geometries.Point(loc), ds); return (from FeatureDataRow row in ds.Tables[0] select row["NUTS_ID"] as string).FirstOrDefault(); } private string GetRegionDescription(string regionName) { if (regionName != null && Content.Geometry.Features.Columns.Contains("name")) { var row = Content.Geometry.Features.Rows.Cast().FirstOrDefault(r => (string)r["NUTS_ID"] == regionName); if (row != null) return row["name"].ToString(); } return ""; } private string GetCurrentValueLayerValue(string regionName) { if (regionName == null) return ""; var layerName = valueLayerNamesListBox.SelectedItem as string; if (layerName == null) return ""; var generator = layerCache[layerName] as LazyValueLayerGenerator; if (generator == null) return ""; var mapping = generator.Mapping; if (mapping == null) return ""; double value; if (!mapping.TryGetValue(regionName, out value)) return ""; var v = Math.Abs(value); //var format = v > 1000 ? "F0" : v > 100 ? "F1" : v > 10 ? "F2" : value < 0.01 ? "G" : "F3"; var format = v > 1000 ? "F0" : v > 100 ? "F1" : v > 10 ? "F2" : value < 0.01 ? "G" : "F3"; var info = (NumberFormatInfo)NumberFormatInfo.InvariantInfo.Clone(); info.NumberGroupSeparator = " "; return string.Format("{0} {1}", value.ToString(format, info), generator.Unit); } #endregion #region Event Handlers (child controls) private void vectorLayerNamesListBox_SelectedIndexChanged(object sender, EventArgs e) { ShowSelectedLayers(false); vectorLayerNamesListBox.Invalidate(); } private void valueLayerNamesListBox_SelectedIndexChanged(object sender, EventArgs e) { ShowSelectedLayers(false); } private void mapBox_MouseMove(Coordinate worldPos, MouseEventArgs imagePos) { var nuts_id = GetRegionAtWorldPos(worldPos); var nuts_name = GetRegionDescription(nuts_id); regionNameLabel.Text = nuts_id; regionDescriptionLabel.Text = nuts_name; valueLayerValueLabel.Text = GetCurrentValueLayerValue(nuts_id); mapBox.Cursor = String.IsNullOrEmpty(nuts_id) ? Cursors.Default : Cursors.Hand; } private Point startPos = new Point(0, 0); private void mapBox_MouseDown(Coordinate worldPos, MouseEventArgs imagePos) { if (imagePos.Button == MouseButtons.Left) { startPos = imagePos.Location; } } private void mapBox_MouseUp(Coordinate worldPos, MouseEventArgs imagePos) { if (imagePos.Button == MouseButtons.Left && Util.Distance(imagePos.Location, startPos) < 4) { currentRegion = GetRegionAtWorldPos(worldPos); if (!String.IsNullOrEmpty(currentRegion)) { regionDetailView.Content = new RegionDetailData { RegionName = currentRegion, Solution = Content, }; } } } private void solutionDetailView_RegionLabelClicked(object sender, EventArgs e) { if (regionDetailView.Content != null) SetFocus(regionDetailView.Content.RegionName); } private void vectorLayerNamesListBox_DrawItem(object sender, DrawItemEventArgs e) { var item = vectorLayerNamesListBox.Items[e.Index]; var listBoxItem = item as ListBoxItem; var foreColor = Color.Black; if (listBoxItem != null) { foreColor = listBoxItem.Color; } var backColor = vectorLayerNamesListBox.BackColor; if (vectorLayerNamesListBox.SelectedItems.Contains(item)) { backColor = foreColor; foreColor = Color.White; } using (var backBrush = new SolidBrush(backColor)) { e.Graphics.FillRectangle(backBrush, e.Bounds); } using (var foreBrush = new SolidBrush(foreColor)) { e.Graphics.DrawString( listBoxItem != null ? listBoxItem.Text : item.ToString(), vectorLayerNamesListBox.Font, foreBrush, e.Bounds); } } #endregion private void tabControl1_Selected(object sender, TabControlEventArgs e) { switch (e.TabPageIndex) { case 0: ShowSelectedLayers(false); // prepare for the map update updateMap.Set(); // signal that the map should be updated once break; case 1: updateCosts.Set(); // signal that the costs should be updated once break; } } private void regionsTreeView_AfterSelect(object sender, TreeViewEventArgs e) { var labeltext = e.Node.Text.Split(' '); if (labeltext.Length > 0) currentRegion = labeltext[0]; if (e.Node.GetNodeCount(true) == 0) { regionDetailView.Content = new RegionDetailData { RegionName = currentRegion, Solution = Content, }; } ClearCostsSummary(); PopulateCostsSummary(); } private void openSolutionEditorButton_Click(object sender, EventArgs e) { // clone solution and also clone problem-data separately (solution does not clone problem data itself) var cloner = new Cloner(); var clonedSolution = cloner.Clone(Content); var clonedProblemData = cloner.Clone(Content.ProblemDataReference); clonedSolution.ProblemDataReference = clonedProblemData; // create and open editor view for content var vh = new ViewHost(); vh.Content = clonedSolution; vh.ViewType = typeof (BioBoostCompoundSolutionEditor); vh.Show(); } public void SaveMapViewToPNG(string filename) { var bitmap = new Bitmap(mapBox.Width, mapBox.Height); mapBox.DrawToBitmap(bitmap, new Rectangle(0, 0, mapBox.Width, mapBox.Height)); bitmap.Save(filename); } private void saveMapImageButton_Click(object sender, EventArgs e) { if (saveFileDialog.ShowDialog() == DialogResult.OK) { try { Enabled = false; SaveMapViewToPNG(saveFileDialog.FileName); } catch (Exception x) { ErrorHandling.ShowErrorDialog(this, x); } finally { Enabled = true; } } } } }