#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;
}
}
}
}
}