#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; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; using GeoAPI.Geometries; using HeuristicLab.BioBoost.ProblemDescription; using HeuristicLab.BioBoost.Representation; using HeuristicLab.BioBoost.Utils; using HeuristicLab.BioBoost.Views.MapViews; using HeuristicLab.Core.Views; using HeuristicLab.MainForm; using HeuristicLab.PluginInfrastructure; using NetTopologySuite.Geometries; using SharpMap.Data; using SharpMap.Forms; using SharpMap.Layers; using SharpMap.Rendering.Decoration; using SharpMap.Rendering.Decoration.ScaleBar; using Point = System.Drawing.Point; namespace HeuristicLab.BioBoost.Views { [Content(typeof(BioBoostCompoundSolution))] public partial class BioBoostCompoundSolutionEditor : ItemView { private readonly GradientLegend utilizationLegend; 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 }); public const string AllRegionsName = "All"; public new BioBoostCompoundSolution Content { get { return (BioBoostCompoundSolution)base.Content; } set { base.Content = value; } } public BioBoostCompoundSolutionEditor() { InitializeComponent(); 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, }); utilizationLegend = new GradientLegend(); mapBox.Map.Decorations.Add(utilizationLegend); mapBox.Map.Decorations.Add(new NorthArrow()); mapBox.ActiveTool = MapBox.Tools.None; } protected override void SetEnabledStateOfControls() { base.SetEnabledStateOfControls(); ReadOnly = Locked || Content == null; mapBox.Enabled = !Locked && Content != null; transportLayersListBox.Enabled = !Locked && Content != null; utilizationLayersListBox.Enabled = !Locked && Content != null; } #region content events protected override void OnContentChanged() { if (InvokeRequired) { Invoke((Action)OnContentChanged); } else { base.OnContentChanged(); if (Content == null) { Clear(); } else { Update(); } } } protected override void DeregisterContentEvents() { Content.SolutionChanged -= SolutionChanged; base.DeregisterContentEvents(); } protected override void RegisterContentEvents() { base.RegisterContentEvents(); Content.SolutionChanged += SolutionChanged; } private void SolutionChanged(object sender, EventArgs e) { if (InvokeRequired) { Invoke(new EventHandler(SolutionChanged), sender, e); } else { Update(); } } #endregion #region Main Logic private void Clear() { qualityLabel.Text = string.Empty; feedstockCostsLabel.Text = string.Empty; transportCostsLabel.Text = string.Empty; utilizationLayersListBox.Items.Clear(); transportLayersListBox.Items.Clear(); mapBox.Map.Layers.Clear(); // only two layers are active at any time (representing the layers selected in the list boxes on the left) } private void Update() { // store currently selected items var selectedValueLayerName = GetSelectedLayer(utilizationLayersListBox); var selectedVectorLayerName = GetSelectedLayer(transportLayersListBox); // clear everything Clear(); FillQualityInformation(); FillValueLayersListBox(); FillVectorLayersListBox(); // restore selection if possible // when we couldn't restore the value or vector layer then there has been a larger change (unexpected) and we need to reset the zoom level // this also makes sure that the zoom level is set correctly when the solution is initially displayed if (!TrySetSelectedLayer(utilizationLayersListBox, selectedValueLayerName) | !TrySetSelectedLayer(transportLayersListBox, selectedVectorLayerName)) { ResetZoom(); } mapBox.Refresh(); } private void FillQualityInformation() { qualityLabel.Text = string.Format("Solution quality: {0:F4}", Content.Quality); feedstockCostsLabel.Text = string.Format("Feedstock [Mio€/a]: {0:N1}", Content.TotalFeedstockCosts / 1E6); transportCostsLabel.Text = string.Format("Transport [Mio€/a]: {0:N1}", Content.TotalTransportCosts / 1E6); costPerFuelLabel.Text = string.Format("Transport Fuel Costs [€/t]: {0:N1}", Content.TotalCosts / Content.TotalTransportFuelAmount); } private void FillValueLayersListBox() { // we only add items for layers that we are allowed to change (layers that correspond to a vector in the representation) // add utilizations for all feedstock utilizationLayersListBox.BeginUpdate(); foreach (var pair in Content.UtilizationVectors) { var newItem = new ListBoxItem { Color = Color.White, Text = pair.Item1, Value = pair.Item2 }; utilizationLayersListBox.Items.Add(newItem); } utilizationLayersListBox.EndUpdate(); } private void FillVectorLayersListBox() { // we only add items for vectors that we are allowed to change // add transport vector layers for all products transportLayersListBox.BeginUpdate(); int i = 0; int n = Content.TransportVectors.Count(); foreach (var pair in Content.TransportVectors) { var newItem = new ListBoxItem { Color = vectorColorBlend.GetColor(1f * i++ / (float)n), Text = pair.Item1, Value = pair.Item2 }; transportLayersListBox.Items.Add(newItem); } transportLayersListBox.EndUpdate(); } private void UpdateMapLayers() { mapBox.Map.Layers.Clear(); AddSelectedValueLayer(); // values first ... AddSelectedVectorLayer(); // ... then vectors (because they need to be drawn last) AddWorstTransportsVectorLayer(); mapBox.Refresh(); } private void AddWorstTransportsVectorLayer() { var geom = Content.Geometry; var locs = Content.LocationNames; var selectedProduct = GetSelectedLayer(transportLayersListBox); if (selectedProduct == string.Empty) return; int[] worstTransports = new int[locs.Length]; for (int i = 0; i < worstTransports.Length; i++) { worstTransports[i] = -1; } foreach (var transport in CalcNonDominatedTransports()) { if (transport.Product != selectedProduct) continue; worstTransports[transport.SrcRegionIdx] = transport.DestRegionIdx; } var allTransports = Content.Transports.Where(t => t.Product == selectedProduct).ToArray(); // for all regions find the matching transport (if there is one) var transportAmounts = locs .Select(loc => allTransports.FirstOrDefault(t => t.SrcRegion == loc)) .Select(trans => trans == null ? 0.0 : trans.Amount) .ToArray(); var generator = new LazyVectorLayerGenerator(geom, locs, worstTransports, transportAmounts, Color.Red, "Worst Transports"); mapBox.Map.Layers.Add(generator.Layer); } private void AddSelectedVectorLayer() { var selectedVectorLayerItem = transportLayersListBox.SelectedItem as ListBoxItem; if (selectedVectorLayerItem == null) return; var selectedProduct = GetSelectedLayer(transportLayersListBox); var geom = Content.Geometry; var locs = Content.LocationNames; var allTranports = Content.Transports.Where(t => t.Product == selectedProduct).ToArray(); // foreach location find the transport amounts (if there is any) var amounts = locs .Select(loc => allTranports.FirstOrDefault(trans => trans.SrcRegion == loc)) .Select(trans => trans == null ? 0.0 : trans.Amount) .ToArray(); var generator = new LazyVectorLayerGenerator(geom, locs, selectedVectorLayerItem.Value, amounts, selectedVectorLayerItem.Color, "Transports"); mapBox.Map.Layers.Add(generator.Layer); } private void AddSelectedValueLayer() { var selectedValueLayerItem = utilizationLayersListBox.SelectedItem as ListBoxItem; if (selectedValueLayerItem == null) return; var geom = Content.Geometry; var locs = Content.LocationNames; var generator = new LazyValueLayerGenerator(geom, locs, selectedValueLayerItem.Value, "Utilizations"); mapBox.Map.Layers.Add(generator.Layer); } // clicking on the map changes the underlying solution and causes a reevaluation // this is called by the mouse-up event handler private async void DispatchMouseUp(string startRegion, string endRegion, Tuple arrow, Point mousePos) { // some operations on the content might fail ... show an error dialog in this case try { if (arrow != null && startRegion == endRegion) { // an vector was clicked => remove it Content.SetTransport(GetSelectedLayer(transportLayersListBox), arrow.Item1, arrow.Item1); // remove the vector } else if (startRegion != string.Empty && endRegion == startRegion && Util.Distance(startPos, mousePos) < 4) { // no vector but // region was clicked (no dragging) => update utilization var currentUtil = Content.GetUtilization(GetSelectedLayer(utilizationLayersListBox), endRegion); var newUtil = await ShowTrackBar(new Point(mousePos.X - 10, mousePos.Y - 10), currentUtil); Content.SetUtilization(GetSelectedLayer(utilizationLayersListBox), endRegion, newUtil); } else if (startRegion != string.Empty && endRegion != string.Empty) { // we dragged from one region to another (possibly the same region Content.SetTransport(GetSelectedLayer(transportLayersListBox), startRegion, endRegion); } } catch (SystemException e) { var dialog = new ErrorDialog(e); dialog.ShowDialog(this); } } public void OptimizeTransports() { // FullLocalSearchForTransports(); // NearestPlantLocalSearchForTransports(); PruneLongAndSmallTransports(); } private IEnumerable CalcNonDominatedTransports() { var transports = Content.Transports.ToArray(); var transportDistances = transports.Select(t => t.Distance).ToArray(); var transportAmounts = transports.Select(t => t.Amount).ToArray(); for (int i = 0; i < transports.Length; i++) { if (!IsDominated(i, transportDistances, transportAmounts)) { yield return transports[i]; } } } private void PruneLongAndSmallTransports() { var nonDominatedTransports = CalcNonDominatedTransports().ToArray(); var oldQ = Content.Quality; var curQ = Content.Quality; var curContent = (BioBoostCompoundSolution)Content.Clone(); foreach (var transport in nonDominatedTransports) { curContent.SetTransport(transport.Product, transport.SrcRegionIdx, transport.SrcRegionIdx); } curQ = curContent.Quality; SetStatusInfo(string.Format("Deleted {0} transports. Total quality improvement: {1:F4}", nonDominatedTransports.Length, curQ - Content.Quality)); Content.UpdateTo(curContent); } private bool IsDominated(int idx, double[] transportDistances, double[] transportAmounts) { // check if there is any other transport that has a larger distance and a smaller amount var dominatingIdx = Enumerable.Range(0, transportDistances.Length) .Where((otherIdx) => idx != otherIdx) .Where((otherIdx) => transportDistances[otherIdx] > transportDistances[idx]) .Where((otherIdx) => transportAmounts[otherIdx] < transportAmounts[idx]); return (dominatingIdx.Any()); } /* private void NearestPlantLocalSearchForTransports() { // for each transport check if we can reroute it to a nearer plant (with minor loss in quality 1%) const double maxRelativeQualityChange = -0.001; var oldQ = Content.Quality; var curQ = Content.Quality; var curContent = (BioBoostCompoundSolution)Content.Clone(); // TODO: assumes that there is only one distance matrix and all products are transported using this matrix!! Debug.Assert(Content.ProblemDataReference.Parameters.Count(p => p.Name.EndsWith("DistanceMatrix")) == 1); var distanceMatrix = (DistanceMatrix)Content.ProblemDataReference.Parameters.First(p => p.Name.EndsWith("DistanceMatrix")).ActualValue; // for each product a list of possible targets (possible targets stay fixed in optimization) var curTransportTargets = curContent.TransportVectors .Select(kvp => new KeyValuePair(kvp.Item1, kvp.Item2.Distinct().Where(i => i >= 0).OrderBy(t => t).ToArray())) .ToDictionary(kvp => kvp.Key, kvp => kvp.Value); // for each product and for each region a list of the max 5 nearest plants var nearestPlantIdx = new Dictionary(); foreach (var product in curTransportTargets.Keys) { nearestPlantIdx.Add(product, new int[curContent.LocationNames.Length][]); var nIdx = nearestPlantIdx[product]; for (int src = 0; src < nIdx.Length; src++) { var plantsByDistance = curTransportTargets[product].OrderBy(possibleTarget => distanceMatrix[src, possibleTarget]); var nPlants = Math.Min(plantsByDistance.Count(), 3); nIdx[src] = plantsByDistance.Take(nPlants).ToArray(); } } // generate possible changes // either change the transport to the nearest existing plant ... // ... or if the transport is within the same region then to the second nearest plant (reduces plants) var changes = new List>(); // product x source x dest foreach (var kvp in curContent.TransportVectors) { var prod = kvp.Item1; var curDest = kvp.Item2; for (int src = 0; src < curDest.Length; src++) { var dest = curDest[src]; if (dest == -1) continue; // don't allow transports if the current transport target is -1 foreach (var alternativeDest in nearestPlantIdx[prod][src]) { if (alternativeDest != dest) changes.Add(Tuple.Create(prod, src, alternativeDest)); } } } var improvements = new List(); // evalute changes (full evaluation) for (int i = 0; i < changes.Count; i++) { // apply each change in turn to a clone of the solution and check the change in quality var change = changes[i]; var tmpContent = (BioBoostCompoundSolution)curContent.Clone(); tmpContent.SetTransport(change.Item1, change.Item2, change.Item3); // product, src, dest improvements.Add(tmpContent.Quality - curQ); // if quality after change is worse than current quality improvement is negative } var sortedChanges = changes.Zip(improvements, (change, improvement) => new { change, improvement }) .OrderByDescending(p => p.improvement) // positive improvements first .ToArray(); int changeCount = 0; int changeIdx = 0; bool changed = true; // has there been a change in this iteration // while there is a total deterioation not worse than -1% var totalChange = 0.0; while (changeIdx < sortedChanges.Length && changed && totalChange > (oldQ * maxRelativeQualityChange)) { changed = false; var change = sortedChanges[changeIdx].change; var improvement = sortedChanges[changeIdx].improvement; // and each step is not worse than -1% if (improvement > curQ * maxRelativeQualityChange) { changeCount++; totalChange += improvement; curContent.SetTransport(change.Item1, change.Item2, change.Item3); // product, src, dest curQ = curContent.Quality; // update current quality changed = true; } changeIdx++; } Content.UpdateTo(curContent); SetStatusInfo(string.Format("Changed {0} transports. Total quality improvement: {1:F4}", changeCount, Content.Quality - oldQ)); } */ /* private void FullLocalSearchForTransports() { // for each transport check if we can reroute the transport to any other location (which already is a transport target for this product type) // local search using best improvement double oldQ; var curQ = Content.Quality; var curContent = (BioBoostCompoundSolution)Content.Clone(); // for each product a list of possible targets (possible targets stay fixed in optimization) var curTransportTargets = curContent.TransportVectors .Select(kvp => new KeyValuePair(kvp.Item1, kvp.Item2.Distinct().OrderBy(t => t).ToArray())) .ToDictionary(kvp => kvp.Key, kvp => kvp.Value); int changeCount = 0; do { // store current quality (to check if we found an improvement) oldQ = curQ; // generate possible changes var changes = new List>(); // product x source x dest foreach (var kvp in curContent.TransportVectors) { var prod = kvp.Item1; var curDest = kvp.Item2; foreach (var alternativeDest in curTransportTargets[prod]) { if (alternativeDest == -1) continue; // don't allow transport to new locations (without plants) for (int src = 0; src < curDest.Length; src++) { var dest = curDest[src]; if (dest == -1) continue; // don't allow transports if the current transport target is -1 (TODO: check with erik) if (dest != alternativeDest) { changes.Add(Tuple.Create(prod, src, alternativeDest)); } } } } // evalute changes (full evaluation) var bestImprovement = double.NegativeInfinity; var bestChangeIdx = -1; for (int i = 0; i < changes.Count; i++) { // apply each change in turn to a clone of the solution and check the change in quality var change = changes[i]; var tmpContent = (BioBoostCompoundSolution)curContent.Clone(); tmpContent.SetTransport(change.Item1, change.Item2, change.Item3); // product, src, dest var improvement = tmpContent.Quality - curQ; if (improvement > bestImprovement) { bestImprovement = improvement; bestChangeIdx = i; } } if (bestImprovement > 0) { changeCount++; var bestChange = changes[bestChangeIdx]; curContent.SetTransport(bestChange.Item1, bestChange.Item2, bestChange.Item3); // product, src, dest curQ = curContent.Quality; // update current quality } // apply best change } while (oldQ < curQ); SetStatusInfo(string.Format("Changed {0} transports. Total quality improvement: {1:F4}", changeCount, curQ - Content.Quality)); Content.UpdateTo(curContent); }*/ public void OptimizeUtilizations() { double oldQ; var curQ = Content.Quality; var curContent = (BioBoostCompoundSolution)Content.Clone(); int changeCount = 0; do { oldQ = curQ; // for each product and region check if the quality increases if we set the utilization to zero // local search using first improvement var changes = new List>(); // product x region x utilization foreach (var pair in curContent.UtilizationVectors) { var prod = pair.Item1; var util = pair.Item2; for (int regIdx = 0; regIdx < util.Length; regIdx++) { if (util[regIdx] > 0) { changes.Add(Tuple.Create(prod, regIdx, 0.0)); } } } var bestImprovement = double.NegativeInfinity; var bestChange = changes.First(); foreach (var change in changes) { var tmpContent = (BioBoostCompoundSolution)curContent.Clone(); tmpContent.SetUtilization(change.Item1, change.Item2, change.Item3); var newQ = tmpContent.Quality; var improvement = newQ - curQ; // positive improvements (new quality is higher than current quality if (improvement > bestImprovement) { bestImprovement = improvement; bestChange = change; } } // apply if we found a possible improvement if (bestImprovement > 0) { curContent.SetUtilization(bestChange.Item1, bestChange.Item2, bestChange.Item3); curQ = curContent.Quality; changeCount++; } } while (oldQ < curQ); SetStatusInfo(string.Format("Changed {0} utilizations. Total quality improvement: {1:F4}", changeCount, curQ - Content.Quality)); Content.UpdateTo(curContent); } private void SetStatusInfo(string message) { if (InvokeRequired) { Invoke((Action)SetStatusInfo, message); } else { toolStripStatusLabel.Text = message; } } private void UpdateTransportInfo(Tuple vector) { transportInfoLabel.Text = string.Empty; // clear if (vector != null) { var transportProduct = GetSelectedLayer(transportLayersListBox); var transportInfo = Content.Transports.FirstOrDefault( info => info.Product == transportProduct && info.SrcRegion == vector.Item1 && info.DestRegion == vector.Item2); if (transportInfo != null) { var sb = new StringBuilder(); sb.AppendFormat("{0} -> {1}", transportInfo.SrcRegion, transportInfo.DestRegion).AppendLine(); sb.AppendFormat("{0} (Mode: {1})", transportInfo.Product, transportInfo.Mode).AppendLine(); sb.AppendFormat("{0:N0} kt km", transportInfo.Distance * transportInfo.Amount / 1000.0).AppendLine(); sb.AppendFormat("{0:N1} kt {1:N0} km", transportInfo.Amount / 1000.0, transportInfo.Distance); transportInfoLabel.Text = sb.ToString(); } } } #endregion #region Event Handlers (child controls) private void vectorLayerNamesListBox_SelectedIndexChanged(object sender, EventArgs e) { UpdateMapLayers(); } private void valueLayerNamesListBox_SelectedIndexChanged(object sender, EventArgs e) { UpdateMapLayers(); } private void optimizeTransportsButton_Click(object sender, EventArgs e) { using (BackgroundWorker bg = new BackgroundWorker()) { MainFormManager.GetMainForm().AddOperationProgressToView(this, "Optimizing Transports..."); bg.DoWork += (o, a) => OptimizeTransports(); bg.RunWorkerCompleted += (o, a) => MainFormManager.GetMainForm().RemoveOperationProgressFromView(this); bg.RunWorkerAsync(); } } private void optimizeUtilizationsButton_Click(object sender, EventArgs e) { using (BackgroundWorker bg = new BackgroundWorker()) { MainFormManager.GetMainForm().AddOperationProgressToView(this, "Optimizing Utilizations..."); bg.DoWork += (o, a) => OptimizeUtilizations(); bg.RunWorkerCompleted += (o, a) => MainFormManager.GetMainForm().RemoveOperationProgressFromView(this); bg.RunWorkerAsync(); } } #endregion #region mouse events // TODO: it should be possible to allow panning and drawing arrows at the same time by implementing a CustomTool 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; mapBox.Cursor = String.IsNullOrEmpty(nuts_id) ? Cursors.Default : Cursors.Hand; var arrow = GetArrowAtWorldPos(worldPos); UpdateTransportInfo(arrow); } private Point startPos = new Point(0, 0); private Coordinate startWorldPos; private void mapBox_MouseDown(Coordinate worldPos, MouseEventArgs mouseEventArgs) { if (mouseEventArgs.Button == MouseButtons.Left) { startPos = mouseEventArgs.Location; startWorldPos = worldPos; } } private void mapBox_MouseUp(Coordinate endWorldPos, MouseEventArgs mousePos) { if (mousePos.Button == MouseButtons.Left) { var arrow = GetArrowAtWorldPos(startWorldPos); var startRegion = GetRegionAtWorldPos(startWorldPos); var endRegion = GetRegionAtWorldPos(endWorldPos); DispatchMouseUp(startRegion, endRegion, arrow, mousePos.Location); } } // custom drawing method for vector list box (for coloring of layers) private void vectorLayerNamesListBox_DrawItem(object sender, DrawItemEventArgs e) { var item = transportLayersListBox.Items[e.Index]; var listBoxItem = item as ListBoxItem; var backColor = listBoxItem.Color; var 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.Text, transportLayersListBox.Font, foreBrush, e.Bounds); } } #endregion #region drag-and-drop of problem data private bool dropAllowed = false; protected override void OnDragDrop(DragEventArgs e) { if (e.Effect != DragDropEffects.None) { var problemData = (BioBoostProblemData)e.Data.GetData(HeuristicLab.Common.Constants.DragDropDataFormat); Content.ProblemDataReference = (BioBoostProblemData)problemData.Clone(); } } protected override void OnDragEnter(DragEventArgs e) { var problemData = (BioBoostProblemData)e.Data.GetData(HeuristicLab.Common.Constants.DragDropDataFormat); dropAllowed = !ReadOnly && !Locked && problemData != null; } protected override void OnDragOver(DragEventArgs e) { e.Effect = dropAllowed ? DragDropEffects.Copy : DragDropEffects.None; } #endregion #region Auxiliar Methods private Task ShowTrackBar(Point location, double value) { var wh = new AutoResetEvent(false); var tb = new TrackBar { TickStyle = TickStyle.None, Minimum = 0, Maximum = 100, Value = (int)Math.Round(value * 100), Location = location }; var label = new Label(); label.Location = new Point(location.X, location.Y - label.Height); label.Text = string.Format("Utilization: {0:F2}", value); label.Width = tb.Width; mapBox.Controls.Add(tb); mapBox.Controls.Add(label); tb.Focus(); tb.ValueChanged += (sender, args) => { value = tb.Value / 100.0; label.Text = string.Format("Utilization: {0:F2}", value); }; tb.LostFocus += (sender, args) => { mapBox.Controls.Remove(tb); mapBox.Controls.Remove(label); wh.Set(); }; return Task.Run(() => { wh.WaitOne(); wh.Dispose(); return value; }); } // returns the NUTS_ID of the region if it has been hit private string GetRegionAtWorldPos(Coordinate loc) { var ds = new FeatureDataSet(); var layer = mapBox.Map.Layers.OfType().SingleOrDefault(l => l.LayerName == "Utilizations"); if (layer == null) return string.Empty; layer.ExecuteIntersectionQuery(new NetTopologySuite.Geometries.Point(loc), ds); return (from FeatureDataRow row in ds.Tables[0] select row["NUTS_ID"] as string).FirstOrDefault() ?? string.Empty; // default is string.Empty } // returns the source and target NUTS_ID (Tuple(src, trgt)) for an vector if it has been hit private Tuple GetArrowAtWorldPos(Coordinate loc) { var ds = new FeatureDataSet(); var layer = mapBox.Map.Layers.OfType().SingleOrDefault(l => l.LayerName == "Transports"); if (layer == null) return null; var rect = new Coordinate[5]; var delta = mapBox.Map.PixelWidth * 2; // for bounding box arount the click point rect[0] = new Coordinate(loc.X - delta, loc.Y - delta); rect[1] = new Coordinate(loc.X + delta, loc.Y - delta); rect[3] = new Coordinate(loc.X + delta, loc.Y + delta); rect[2] = new Coordinate(loc.X - delta, loc.Y + delta); rect[4] = new Coordinate(loc.X - delta, loc.Y - delta); layer.ExecuteIntersectionQuery(new NetTopologySuite.Geometries.Polygon(new LinearRing(rect)), ds); var arrow = (from FeatureDataRow row in ds.Tables[0] select Tuple.Create((string)row["NUTS_ID_SRC"], (string)row["NUTS_ID_DEST"]) ).FirstOrDefault(); return arrow; } 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 string.Empty; } private void ResetZoom() { if (mapBox.Map.Layers.Count > 0) mapBox.Map.ZoomToExtents(); // mapBox.ActiveTool = MapBox.Tools.DrawLine; mapBox.Refresh(); } private string GetSelectedLayer(ListBox listBox) { return listBox.SelectedItem == null ? string.Empty : listBox.SelectedItem.ToString(); } private bool TrySetSelectedLayer(ListBox listBox, string text) { bool success = false; if (string.IsNullOrEmpty(text)) { listBox.SelectedIndex = 0; } else { int selectedIndex = 0; for (int i = 0; i < listBox.Items.Count; i++) { if (listBox.Items[i].ToString() == text) { selectedIndex = i; success = true; break; } } listBox.SelectedIndex = selectedIndex; } return success; } #endregion // only necessary for drawing of vector layer elements with color private class ListBoxItem { public Color Color { get; set; } public string Text { get; set; } public T Value { get; set; } public override string ToString() { return Text; } } } }