using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using Deltares.DSoilModel.Data;
using Deltares.DSoilModel.Forms.Properties;
using Deltares.Geographic;
using Deltares.Geotechnics;
using Deltares.Geotechnics.ConePenetrationTest;
using Deltares.Standard;
using Deltares.Standard.EventPublisher;
using Deltares.Standard.Extensions;
using Deltares.Standard.Forms.DExpress;
using Deltares.Standard.Forms.Maps;
using Deltares.Standard.Language;
using Deltares.Standard.Maps;
using Deltares.Standard.Reflection;
using DevExpress.XtraBars;
using DotSpatial.Controls;
using DotSpatial.Symbology;
using DotSpatial.Topology;
namespace Deltares.DSoilModel.Forms
{
///
/// The D-Soil Model MapEditor
///
public class DSoilModelMapEditor : IVisibleEnabled, IDisposable
{
private const int labelFontSize = 9;
private readonly MapEditor mapEditor;
private readonly List mappedSoilProfiles1D = new List();
private readonly List mappedSoilProfiles2D = new List();
private readonly Panel panel = new Panel();
// only one segment can be split at a time, but these need to be lists so we can add them to the map as layers
private readonly List segmentSplitters = new List();
private readonly List splittingSegments = new List();
private DSoilModelProject project;
private readonly BarButtonItem splitSegmentButton;
///
/// Initializes a new instance of the class, registers MapLayerDefinitions
/// for CPT, Boring, SoilProfile1D, SoilProfile2D and Segments.
///
/// The map editor.
public DSoilModelMapEditor(MapEditor mapEditor)
{
this.mapEditor = mapEditor;
mapEditor.ShowSelectWebLayer = true;
mapEditor.WebLayerType = MapEditor.WebLayerTypes.OpenStreetMapnik;
mapEditor.RegisterLayer(typeof(ConePenetrationTestData), new MapLayerDefinition(FeatureType.Point, "CPTs", Resources.CPT));
mapEditor.RegisterLayer(typeof(Boring), new MapLayerDefinition(FeatureType.Point, "Borings", Resources.Boring));
mapEditor.RegisterLayer(typeof(SoilProfile1D), new MapLayerDefinition(FeatureType.Point, "1D Geometry", Resources.SoilProfile));
mapEditor.RegisterLayer(typeof(SoilProfile2D), new MapLayerDefinition(FeatureType.Line, "2D Geometry", Color.Lime));
mapEditor.RegisterLayer(typeof(SoilSegment), new MapLayerDefinition(FeatureType.Line, "Soil Segment", Color.DarkBlue));
mapEditor.RegisterLayer(typeof(HighlightSoilSegment), new MapLayerDefinition(FeatureType.Line, "Split soil Segment", Color.DarkOrange, false));
mapEditor.RegisterLayer(typeof(SoilSegmentSplitLocation), SegmentSplitLocationLayerDefinition());
mapEditor.MapControl.LayerAdded += MapControlOnLayerAdded;
// split segment button & popup menu
splitSegmentButton = CreateButton(Resources.SplitSegment);
BindSupport.Bind(panel, splitSegmentButton, me => me.SplitSegmentLocation());
ConfigureContextMenu();
BindSupport.Assign(panel, this);
DataEventPublisher.OnSelectionChanged += DataEventPublisher_OnSelectionChanged;
DataEventPublisher.OnDataListModified += DataEventPublisher_OnDataLisModified;
DataEventPublisher.OnAfterChange += DataEventPublisher_OnAfterChange;
}
///
/// Gets or sets the project (setting: Adding Layers from Project to MapEditor).
///
///
/// The project.
///
public DSoilModelProject Project
{
get
{
return project;
}
set
{
project = value;
mapEditor.Clear();
mapEditor.AddLayer(project.CPTs);
mapEditor.AddLayer(project.Borings);
// Do not show profiles without a location
UpdateSoilProfiles1D();
mapEditor.AddLayer(mappedSoilProfiles1D);
UpdateSoilProfiles2D();
mapEditor.AddLayer(mappedSoilProfiles2D);
mapEditor.AddLayer(project.CurrentSoilSegments);
mapEditor.ZoomData();
}
}
///
/// Returns the MainForm MapEditor
///
public MapEditor MapEditor
{
get
{
return mapEditor;
}
}
///
/// creates a tool button with the provided info and add them to the tool bar
///
public BarButtonItem CreateButton(Image image)
{
var button = new BarButtonItem
{
PaintStyle = BarItemPaintStyle.Standard,
Glyph = image
};
mapEditor.Toolbar.AddItem(button);
return button;
}
///
/// Start (or complete) splitting selected SoilSegment by adding a divisionpoint on the map
///
public void SplitSegmentLocation()
{
var segment = (mapEditor.SelectedObjects != null) ? mapEditor.SelectedObjects.FirstOrDefault() as SoilSegment : null;
var splitter = segmentSplitters.FirstOrDefault();
if (segment != null || splitter != null)
{
if (splitter == null)
{
// initiate split operation
if (segment.Points != null && segment.Points.Count > 1)
{
UndoRedoManager.Instance.BeginAction();
splitter = AddSegmentSplitter(segment);
// event to trigger SegmentGeometryEditor
DataEventPublisher.SelectionChanged(splitter);
}
}
else
{
// complete split operation
segment = splitter.SoilSegment;
var x = splitter.XCoordinate;
ClearSegmentSplitter();
project.SplitSoilSegment(segment, x);
UndoRedoManager.Instance.EndAction();
}
}
else
{
ClearSegmentSplitter();
var msg = LocalizationManager.GetTranslatedText(this, "SplitSegmentSelection");
MessageBox.Show(msg);
}
}
///
/// Zoom out the map to display all data.
///
public void ZoomData()
{
mapEditor.ZoomData();
}
///
/// SnapRule method for splitting soil segments, snaps the splitter location to the segment geographically
///
/// Point to be snapped
/// Snapped point
public IGeographic GetSnapPointSplitSegment(IGeographicPoint point)
{
var returnPoint = new GeographicPoint(point.X, point.Y);
if (project != null && splittingSegments.Any())
{
var nearest = splittingSegments[0].NearestPointOnGeographicString(point);
returnPoint.X = nearest.X;
returnPoint.Y = nearest.Y;
}
return returnPoint;
}
public void ConfigureMainMenu(MainForm mainForm)
{
var linkButtonItem = new BarButtonItem();
mainForm.EditMenu.ItemLinks.Add(linkButtonItem);
BindSupport.Bind(panel, linkButtonItem, ge => ge.LinkToNearestSegment());
var splitButtonItem = new BarButtonItem();
mainForm.EditMenu.ItemLinks.Add(splitButtonItem);
BindSupport.Bind(panel, splitButtonItem, ge => ge.SplitSegment());
}
///
/// Dispose of unmanaged resources and delegates
///
public void Dispose()
{
DataEventPublisher.OnSelectionChanged -= DataEventPublisher_OnSelectionChanged;
DataEventPublisher.OnDataListModified -= DataEventPublisher_OnDataLisModified;
DataEventPublisher.OnAfterChange -= DataEventPublisher_OnAfterChange;
mapEditor.MapControl.LayerAdded -= MapControlOnLayerAdded;
}
///
/// Determine visibility of properties
///
///
///
public bool IsVisible(string property)
{
if (property == this.GetMemberName(x => x.SplitSegment()))
{
// splitting location has been defined ?
if (splittingSegments.Any() && segmentSplitters.Any())
{
return true;
}
return false;
}
if (property == this.GetMemberName(x => x.LinkToNearestSegment()))
{
// segments present and at least one CPT or boring must be selected
if (project != null && project.CurrentSoilSegments.Count > 0 && mapEditor.SelectedObjects != null && mapEditor.SelectedObjects.Count > 0)
{
if (mapEditor.SelectedObjects.Any(o => o is ConePenetrationTestData || o is Boring))
{
return true;
}
}
return false;
}
else
{
return true;
}
}
///
/// Determine if property is enabled
///
///
///
public bool IsEnabled(string property)
{
if (property == this.GetMemberName(x => x.SplitSegmentLocation()))
{
// a single segment must be selected to start splitting it
if (mapEditor.SelectedObjects != null && mapEditor.SelectedObjects.Count == 1)
{
var selection = mapEditor.SelectedObjects.FirstOrDefault();
return (selection is SoilSegment && segmentSplitters.Count == 0);
}
else
{
return false;
}
}
else
{
return true;
}
}
private void ConfigureContextMenu()
{
var splitSegmentMenuItem = new ToolStripMenuItem("Split segment");
mapEditor.ContextMenuStrip.Items.Add(splitSegmentMenuItem);
BindSupport.Bind(panel, splitSegmentMenuItem, ge => ge.SplitSegment());
var assignToSegmentMenuItem = new ToolStripMenuItem("Link to nearest segment");
mapEditor.ContextMenuStrip.Items.Add(assignToSegmentMenuItem);
BindSupport.Bind(panel, assignToSegmentMenuItem, ge => ge.LinkToNearestSegment());
}
///
/// Complete split segment operation (after setting a split location)
///
private void SplitSegment()
{
if (splittingSegments.Any() && segmentSplitters.Any())
{
var splitter = segmentSplitters[0];
double x = GeographicHelper.Instance.GetOffsetOnGeographicLineString(splitter, splitter.SoilSegment);
ClearSegmentSplitter();
project.SplitSoilSegment(splitter.SoilSegment, x);
UndoRedoManager.Instance.EndAction();
}
}
///
/// Link all selected CPT's and or Borings to the nearest (selected) segment(s)
///
private void LinkToNearestSegment()
{
var selectedObjects = mapEditor.SelectedObjects.Where(o => o is ConePenetrationTestData || o is Boring || o is SoilSegment).ToArray();
project.LinkCptsAndBoringsToNearestSegment(selectedObjects);
}
///
/// Selection change handler for specific interactions
///
/// Selected object
/// Event arguments
private void DataEventPublisher_OnSelectionChanged(object sender, PublishEventArgs e)
{
// trigger enabled state of split button
BindSupport.Update(panel);
// split going on in the length profile window ?
var splitLocation = sender as SoilSegmentSplitLocation;
if (splitLocation != null)
{
if (splitLocation != segmentSplitters.FirstOrDefault())
{
// initiate the same splitting operation in the map
AddSegmentSplitter(splitLocation.SoilSegment, splitLocation);
}
}
if (sender is IGeographic)
{
// selecting another segment cancels segment split operation
var segment = sender as SoilSegment;
if (segment != null)
{
if (segmentSplitters.Any() && segmentSplitters.First().SoilSegment != segment)
{
ClearSegmentSplitter();
}
}
}
if (sender == null)
{
if (segmentSplitters.Any())
{
ClearSegmentSplitter();
}
}
}
private void DataEventPublisher_OnDataLisModified(object sender, PublishEventArgs e)
{
if (project == null)
{
return;
}
// update soil profiles ?
if (sender == project.SoilProfiles1D)
{
UpdateSoilProfiles1D();
}
if (sender == project.SoilProfiles2D)
{
UpdateSoilProfiles2D();
}
if (sender == project.SoilSegments)
{
// ended split operation in segment geometry editor ?
if (splittingSegments.Any() || segmentSplitters.Any())
{
ClearSegmentSplitter();
}
}
// in case user deleted split point via context menu or segment geometry editor
if (sender == segmentSplitters)
{
var dataListModifiedArgs = e as DataListModifiedArgs;
if (dataListModifiedArgs != null && dataListModifiedArgs.Action == ListModifyAction.Delete)
{
ClearSegmentSplitter();
}
}
}
private void DataEventPublisher_OnAfterChange(object sender, PublishEventArgs e)
{
var soilProfile1D = sender as SoilProfile1D;
if (soilProfile1D != null)
{
if (e.Property != null && (e.Property.Equals("X") || e.Property.Equals("Y")))
{
UpdateSoilProfiles1D();
}
}
var soilProfile2D = sender as SoilProfile2D;
if (soilProfile2D != null)
{
if (e.Property != null && (e.Property.Equals("XBegin") || e.Property.Equals("YBegin") || e.Property.Equals("XEnd") || e.Property.Equals("XEnd")))
{
UpdateSoilProfiles2D();
}
}
var splitLocation = sender as SoilSegmentSplitLocation;
if (splitLocation != null && (e.Property == "X" || e.Property == "Y"))
{
DataEventPublisher.DataListModified(segmentSplitters);
}
}
private void MapControlOnLayerAdded(object sender, LayerEventArgs layerEventArgs)
{
// Labels can only be defined once a layer was actually added to the map
var featureLayer = layerEventArgs.Layer as FeatureLayer;
if (featureLayer != null)
{
if (featureLayer.LabelLayer == null)
{
if (featureLayer.FeatureSet.TypeName == typeof(ConePenetrationTestData).FullName ||
featureLayer.FeatureSet.TypeName == typeof(Boring).FullName)
{
featureLayer.AddLabels("[Name]", new Font(FontFamily.GenericSansSerif, labelFontSize), Color.Black);
featureLayer.LabelLayer.Symbolizer.Orientation = ContentAlignment.TopCenter;
featureLayer.LabelLayer.Symbolizer.OffsetY = 10;
featureLayer.LabelLayer.DynamicVisibilityMode = DynamicVisibilityMode.ZoomedIn;
featureLayer.LabelLayer.DynamicVisibilityWidth = 50000;
featureLayer.LabelLayer.UseDynamicVisibility = true;
featureLayer.ShowLabels = true;
}
else if (featureLayer.FeatureSet.TypeName == typeof(SoilSegment).FullName)
{
featureLayer.AddLabels("[Name]", new Font(FontFamily.GenericSansSerif, labelFontSize), Color.Black);
featureLayer.LabelLayer.Symbolizer.Orientation = ContentAlignment.TopCenter;
featureLayer.LabelLayer.Symbolizer.UseLineOrientation = true;
featureLayer.LabelLayer.Symbolizer.LineOrientation = LineOrientation.Parallel;
featureLayer.LabelLayer.Symbolizer.LineLabelPlacementMethod = LineLabelPlacementMethod.MiddleSegment;
featureLayer.LabelLayer.DynamicVisibilityMode = DynamicVisibilityMode.ZoomedIn;
featureLayer.LabelLayer.DynamicVisibilityWidth = 100000;
featureLayer.LabelLayer.UseDynamicVisibility = true;
featureLayer.ShowLabels = true;
}
}
}
}
private void UpdateSoilProfiles1D()
{
mappedSoilProfiles1D.Clear();
mappedSoilProfiles1D.AddRange(project.SoilProfiles1D.Where(sp => !double.IsNaN(sp.Location.X) && !double.IsNaN(sp.Location.Y)));
DataEventPublisher.DataListModified(mappedSoilProfiles1D);
}
private void UpdateSoilProfiles2D()
{
mappedSoilProfiles2D.Clear();
mappedSoilProfiles2D.AddRange(project.SoilProfiles2D.Where(sp => !double.IsNaN(sp.Location.X) && !double.IsNaN(sp.Location.Y)));
DataEventPublisher.DataListModified(mappedSoilProfiles2D);
}
private SoilSegmentSplitLocation AddSegmentSplitter(SoilSegment segment, SoilSegmentSplitLocation splitLocation = null)
{
if (segment.Points != null && segment.Points.Count > 1)
{
DataEventPublisher.InvokeWithoutPublishingEvents(() =>
{
HighlightSoilSegment splitSegment = new HighlightSoilSegment
{
SoilSegment = segment
};
splitSegment.Points.AddRange(segment.Points);
splittingSegments.Clear();
splittingSegments.Add(splitSegment);
mapEditor.AddLayer(splittingSegments, false);
if (splitLocation == null)
{
splitLocation = new SoilSegmentSplitLocation
{
SoilSegment = segment,
XCoordinate = segment.GetSegmentLength()/2
};
}
segmentSplitters.Clear();
segmentSplitters.Add(splitLocation);
mapEditor.AddLayer(segmentSplitters);
});
// event for length profile geometry editor
DataEventPublisher.DataListModified(segmentSplitters, ListModifyAction.Add);
return segmentSplitters.FirstOrDefault();
}
else
{
return null;
}
}
private void ClearSegmentSplitter()
{
if (splittingSegments.Any())
{
splittingSegments.Clear();
segmentSplitters.Clear();
mapEditor.RemoveLayer(segmentSplitters);
mapEditor.RemoveLayer(splittingSegments);
DataEventPublisher.DataListModified(segmentSplitters, ListModifyAction.Delete);
BindSupport.Update(panel);
}
}
private MapLayerDefinition SegmentSplitLocationLayerDefinition()
{
var scheme = new PointScheme();
scheme.Categories.Clear();
var pointCategory = new PointCategory
{
LegendText = "Segment Split",
Symbolizer = new PointSymbolizer(Resources.SplitPoint, 15),
SelectionSymbolizer = new PointSymbolizer(Resources.SplitPoint, 10.0)
};
pointCategory.SelectionSymbolizer.SetOutline(Color.Black, 2);
scheme.AddCategory(pointCategory);
return new MapLayerDefinition(FeatureType.Point, "Split location", scheme)
{
Selectable = true,
AllowMoveFeature = true,
AllowDeleteFeature = true,
AllowAddNewFeature = false,
SnapRule = GetSnapPointSplitSegment,
};
}
}
}