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.Geometry; 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(); button.PaintStyle = BarItemPaintStyle.Standard; button.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.SoilSegments.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 }; splitLocation.XCoordinate = segment.GetSegmentLength()/2; } segmentSplitters.Clear(); segmentSplitters.Add(splitLocation); mapEditor.AddLayer(segmentSplitters, true); }); // 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(); pointCategory.LegendText = "Segment Split"; pointCategory.Symbolizer = new PointSymbolizer(Resources.SplitPoint, 15); pointCategory.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, }; } } }