diff --git a/application/MilitaryPlanner.application b/application/MilitaryPlanner.application index 508113b..fac1e0e 100644 --- a/application/MilitaryPlanner.application +++ b/application/MilitaryPlanner.application @@ -7,14 +7,14 @@ - + - wfO19QTf/faxHsMlkhXLFJbx9h+eWETz1IlR0ayr1E0= + EMBPjPlzoXVeFky1SyVEP4NsgoCr3m+Xv+PAJDb8xcU= diff --git a/application/MilitaryPlanner.exe b/application/MilitaryPlanner.exe index a607ca8..bc15846 100644 Binary files a/application/MilitaryPlanner.exe and b/application/MilitaryPlanner.exe differ diff --git a/application/MilitaryPlanner.exe.config b/application/MilitaryPlanner.exe.config index c09b163..f3e7a96 100644 --- a/application/MilitaryPlanner.exe.config +++ b/application/MilitaryPlanner.exe.config @@ -1,5 +1,10 @@ + + +
+ + @@ -12,4 +17,23 @@ + + + + 200 + + + 100 + + + 900 + + + 480 + + + 0 + + + diff --git a/application/MilitaryPlanner.exe.manifest b/application/MilitaryPlanner.exe.manifest index 47f44c6..fc129f0 100644 --- a/application/MilitaryPlanner.exe.manifest +++ b/application/MilitaryPlanner.exe.manifest @@ -486,14 +486,14 @@ - + - a5SKu54QOu2khusYWxVDmu0G8BieM5iulCWWsH8Cup0= + RFmclQc6UiyJNvUGBVCnzozRHyEDN7u+/7LWwd8hIxg= @@ -839,13 +839,13 @@ 7p8UwQtRbihveyTowoZ6eDC/QsqKyRt14M9oWkwQocU= - + - JgF8u+SmlekVyttocpovsnMnjGwSmU+c9zec8rkLsgE= + rbyHaVCrS1SIk4LeaaXZ4OQJXQWS499dkBdRJJMprms= \ No newline at end of file diff --git a/source/MilitaryPlanner/App.xaml b/source/MilitaryPlanner/App.xaml index e7d2a54..0434cdf 100644 --- a/source/MilitaryPlanner/App.xaml +++ b/source/MilitaryPlanner/App.xaml @@ -1,7 +1,8 @@  + StartupUri="Views\MainWindow.xaml" + Exit="Application_Exit"> diff --git a/source/MilitaryPlanner/App.xaml.cs b/source/MilitaryPlanner/App.xaml.cs index 67b7974..f039368 100644 --- a/source/MilitaryPlanner/App.xaml.cs +++ b/source/MilitaryPlanner/App.xaml.cs @@ -30,7 +30,10 @@ private void Application_Startup(object sender, StartupEventArgs e) } } - } - + private void Application_Exit(object sender, ExitEventArgs e) + { + MilitaryPlanner.Properties.Settings.Default.Save(); + } + } } diff --git a/source/MilitaryPlanner/Controllers/BasemapGalleryController.cs b/source/MilitaryPlanner/Controllers/BasemapGalleryController.cs new file mode 100644 index 0000000..b55b023 --- /dev/null +++ b/source/MilitaryPlanner/Controllers/BasemapGalleryController.cs @@ -0,0 +1,109 @@ +// Copyright 2015 Esri +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Collections.ObjectModel; +using System.Linq; +using System.Windows; +using Esri.ArcGISRuntime.Layers; +using Esri.ArcGISRuntime.Portal; +using Esri.ArcGISRuntime.WebMap; +using MilitaryPlanner.Helpers; +using MilitaryPlanner.Views; +using MapView = Esri.ArcGISRuntime.Controls.MapView; + +namespace MilitaryPlanner.Controllers +{ + class BasemapGalleryController + { + private readonly MapView _mapView; + private readonly BasemapGalleryView _basemapGalleryView; + private ArcGISPortal _arcGisPortal; + + public BasemapGalleryController(MapView mapView) + { + _mapView = mapView; + + _basemapGalleryView = new BasemapGalleryView { PlacementTarget = mapView, ViewModel = { mapView = mapView } }; + + var owner = Window.GetWindow(mapView); + + if (owner != null) + { + owner.LocationChanged += (sender, e) => + { + _basemapGalleryView.HorizontalOffset += 1; + _basemapGalleryView.HorizontalOffset -= 1; + }; + } + + InitializeArcGISPortal(); + + Mediator.Register(Constants.ACTION_UPDATE_BASEMAP, DoUpdateBasemap); + } + + public void Toggle() + { + _basemapGalleryView.ViewModel.Toggle(); + } + + private async void InitializeArcGISPortal() + { + _arcGisPortal = await ArcGISPortal.CreateAsync(); + + // Load portal basemaps + var result = await _arcGisPortal.ArcGISPortalInfo.SearchBasemapGalleryAsync(); + _basemapGalleryView.ViewModel.Basemaps = new ObservableCollection(result.Results); + } + + private async void DoUpdateBasemap(object obj) + { + try + { + var item = obj as ArcGISPortalItem; + + if (item != null) + { + var webmap = await WebMap.FromPortalItemAsync(item); + + while (_mapView.Map.Layers.Any(l => l.ID == "basemap")) + { + _mapView.Map.Layers.Remove("basemap"); + } + + foreach (var s in webmap.Basemap.Layers.Reverse()) + { + switch (s.LayerType) + { + case WebMapLayerType.ArcGISTiledMapServiceLayer: + case WebMapLayerType.Unknown: + _mapView.Map.Layers.Insert(0,new ArcGISTiledMapServiceLayer(new Uri(s.Url)){ID="basemap"}); + break; + case WebMapLayerType.OpenStreetMap: + var layer = new OpenStreetMapLayer(){ID="basemap"}; + _mapView.Map.Layers.Insert(0,layer); + break; + default: + break; + } + } + } + } + catch (Exception) + { + + } + } + } +} diff --git a/source/MilitaryPlanner/Helpers/Constants.cs b/source/MilitaryPlanner/Helpers/Constants.cs index d19201b..35e12de 100644 --- a/source/MilitaryPlanner/Helpers/Constants.cs +++ b/source/MilitaryPlanner/Helpers/Constants.cs @@ -36,6 +36,7 @@ static public class Constants public const string ACTION_EDIT_MISSION_PHASES = "EditMissionPhases"; public const string ACTION_MISSION_CLONED = "EditMissionCloned"; + public const string ACTION_CLONE_MISSION = "CloneMission"; public const string MSG_ACTION_UPDATE = "update"; public const string MSG_ACTION_REMOVE = "remove"; @@ -45,6 +46,12 @@ static public class Constants public const string ACTION_COORDINATE_READOUT_FORMAT_CHANGED = "ActionCoordinateReadoutFormatChanged"; + public const string ACTION_EDIT_GEOMETRY = "ActionEditGeometry"; + public const string ACTION_EDIT_UNDO = "ActionEditUndo"; + public const string ACTION_EDIT_REDO = "ActionEditRedo"; + + public const string ACTION_UPDATE_BASEMAP = "ActionUpdateBasemap"; + public const int SAVE_AS_MISSION = 1; public const int SAVE_AS_GEOMESSAGES = 2; public const string SAVE_AS_DELIMITER = "::"; diff --git a/source/MilitaryPlanner/Helpers/SettingBindingExtension.cs b/source/MilitaryPlanner/Helpers/SettingBindingExtension.cs new file mode 100644 index 0000000..857f096 --- /dev/null +++ b/source/MilitaryPlanner/Helpers/SettingBindingExtension.cs @@ -0,0 +1,25 @@ +using System.Windows.Data; +using MilitaryPlanner.Properties; + +namespace MilitaryPlanner.Helpers +{ + public class SettingBindingExtension : Binding + { + public SettingBindingExtension() + { + Initialize(); + } + + public SettingBindingExtension(string path) + : base(path) + { + Initialize(); + } + + private void Initialize() + { + this.Source = Settings.Default; + this.Mode = BindingMode.TwoWay; + } + } +} diff --git a/source/MilitaryPlanner/Helpers/TimeAwareMessageLayer.cs b/source/MilitaryPlanner/Helpers/TimeAwareMessageLayer.cs index 7012d43..eb8096a 100644 --- a/source/MilitaryPlanner/Helpers/TimeAwareMessageLayer.cs +++ b/source/MilitaryPlanner/Helpers/TimeAwareMessageLayer.cs @@ -17,6 +17,7 @@ using System.Xml.Schema; using System.Xml.Serialization; using Esri.ArcGISRuntime.Data; +using Esri.ArcGISRuntime.Geometry; using Esri.ArcGISRuntime.Symbology.Specialized; namespace MilitaryPlanner.Helpers @@ -39,6 +40,8 @@ public TimeExtent VisibleTimeExtent set; } + public Geometry SymbolGeometry { get; set; } + public Dictionary PhaseControlPointsDictionary = new Dictionary(); public void StoreControlPoints(string phaseID, MilitaryMessage msg) diff --git a/source/MilitaryPlanner/MilitaryPlanner.csproj b/source/MilitaryPlanner/MilitaryPlanner.csproj index 0181f23..56d4d4d 100644 --- a/source/MilitaryPlanner/MilitaryPlanner.csproj +++ b/source/MilitaryPlanner/MilitaryPlanner.csproj @@ -136,6 +136,7 @@ + @@ -144,8 +145,10 @@ + + @@ -156,6 +159,9 @@ + + BasemapGalleryView.xaml + EditMissionPhasesView.xaml @@ -189,6 +195,10 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + Designer MSBuild:Compile diff --git a/source/MilitaryPlanner/Properties/Settings.Designer.cs b/source/MilitaryPlanner/Properties/Settings.Designer.cs index d7a8ab7..5cfa07a 100644 --- a/source/MilitaryPlanner/Properties/Settings.Designer.cs +++ b/source/MilitaryPlanner/Properties/Settings.Designer.cs @@ -8,23 +8,79 @@ // //------------------------------------------------------------------------------ -using System.CodeDom.Compiler; -using System.Configuration; -using System.Runtime.CompilerServices; - namespace MilitaryPlanner.Properties { - [CompilerGenerated()] - [GeneratedCode("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] - internal sealed partial class Settings : ApplicationSettingsBase { + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { - private static Settings defaultInstance = ((Settings)(Synchronized(new Settings()))); + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); public static Settings Default { get { return defaultInstance; } } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("200")] + public double Left { + get { + return ((double)(this["Left"])); + } + set { + this["Left"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("100")] + public double Top { + get { + return ((double)(this["Top"])); + } + set { + this["Top"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("900")] + public double Width { + get { + return ((double)(this["Width"])); + } + set { + this["Width"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("480")] + public double Height { + get { + return ((double)(this["Height"])); + } + set { + this["Height"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("0")] + public string WindowState { + get { + return ((string)(this["WindowState"])); + } + set { + this["WindowState"] = value; + } + } } } diff --git a/source/MilitaryPlanner/Properties/Settings.settings b/source/MilitaryPlanner/Properties/Settings.settings index 033d7a5..ef324b9 100644 --- a/source/MilitaryPlanner/Properties/Settings.settings +++ b/source/MilitaryPlanner/Properties/Settings.settings @@ -1,7 +1,21 @@  - - - - - + + + + + 200 + + + 100 + + + 900 + + + 480 + + + 0 + + \ No newline at end of file diff --git a/source/MilitaryPlanner/ViewModels/BasemapGalleryViewModel.cs b/source/MilitaryPlanner/ViewModels/BasemapGalleryViewModel.cs new file mode 100644 index 0000000..ad89bcb --- /dev/null +++ b/source/MilitaryPlanner/ViewModels/BasemapGalleryViewModel.cs @@ -0,0 +1,48 @@ +// Copyright 2015 Esri +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System.Collections.ObjectModel; +using MilitaryPlanner.Helpers; +using Esri.ArcGISRuntime.Portal; + +namespace MilitaryPlanner.ViewModels +{ + public class BasemapGalleryViewModel : BaseToolViewModel + { + public RelayCommand ChangeBasemapCommand { get; set; } + + public BasemapGalleryViewModel() + { + ChangeBasemapCommand = new RelayCommand(OnChangeBasemapCommand); + } + + private ObservableCollection _basemaps; + + public ObservableCollection Basemaps + { + get { return _basemaps;} + + set + { + _basemaps = value; + RaisePropertyChanged(() => Basemaps); + } + } + + private void OnChangeBasemapCommand(object obj) + { + Mediator.NotifyColleagues(Constants.ACTION_UPDATE_BASEMAP, obj); + } + } +} diff --git a/source/MilitaryPlanner/ViewModels/MainWindowViewModel.cs b/source/MilitaryPlanner/ViewModels/MainWindowViewModel.cs index 709e0fc..29ffb54 100644 --- a/source/MilitaryPlanner/ViewModels/MainWindowViewModel.cs +++ b/source/MilitaryPlanner/ViewModels/MainWindowViewModel.cs @@ -11,7 +11,9 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. + using System; +using System.Windows; using Esri.ArcGISRuntime; using Microsoft.Win32; using MilitaryPlanner.Helpers; @@ -133,8 +135,52 @@ public OrderOfBattleView OOBView } } - #endregion + private MissionTimeLineView _MTLView; + public MissionTimeLineView MTLView + { + get { return _MTLView; } + set + { + if (_MTLView != value) + { + _MTLView = value; + RaisePropertyChanged(() => MTLView); + } + } + } + + private Visibility _mapViewVisibility = Visibility.Visible; + public Visibility MapViewVisibility + { + get + { + return _mapViewVisibility; + } + + set + { + _mapViewVisibility = value; + RaisePropertyChanged(() => MapViewVisibility); + } + } + + private Visibility _timeLineViewVisibility = Visibility.Collapsed; + public Visibility TimeLineViewVisibility + { + get + { + return _timeLineViewVisibility; + } + set + { + _timeLineViewVisibility = value; + RaisePropertyChanged(() => TimeLineViewVisibility); + } + } + + #endregion + #region Commands public RelayCommand CancelCommand { get; set; } @@ -142,6 +188,10 @@ public OrderOfBattleView OOBView public RelayCommand SaveCommand { get; set; } public RelayCommand OpenCommand { get; set; } public RelayCommand EditMissionPhasesCommand { get; set; } + public RelayCommand EditGeometryCommand { get; set; } + public RelayCommand EditGeometryUndoCommand { get; set; } + public RelayCommand EditGeometryRedoCommand { get; set; } + public RelayCommand SwitchViewCommand { get; set; } #endregion @@ -167,9 +217,44 @@ public MainWindowViewModel() SaveCommand = new RelayCommand(OnSaveCommand); OpenCommand = new RelayCommand(OnOpenCommand); EditMissionPhasesCommand = new RelayCommand(OnEditMissionPhases); + EditGeometryCommand = new RelayCommand(OnEditGeometryCommand); + EditGeometryRedoCommand = new RelayCommand(OnEditGeometryRedoCommand); + EditGeometryUndoCommand = new RelayCommand(OnEditGeometryUndoCommand); + SwitchViewCommand = new RelayCommand(OnSwitchViewCommand); MapView = new MapView(); OOBView = new OrderOfBattleView(); + MTLView = new MissionTimeLineView(); + } + + private void OnSwitchViewCommand(object obj) + { + if (MapViewVisibility == Visibility.Visible) + { + Mediator.NotifyColleagues(Constants.ACTION_CLONE_MISSION, null); + TimeLineViewVisibility = Visibility.Visible; + MapViewVisibility = Visibility.Collapsed; + } + else + { + TimeLineViewVisibility = Visibility.Collapsed; + MapViewVisibility = Visibility.Visible; + } + } + + private void OnEditGeometryUndoCommand(object obj) + { + Mediator.NotifyColleagues(Constants.ACTION_EDIT_UNDO, null); + } + + private void OnEditGeometryRedoCommand(object obj) + { + Mediator.NotifyColleagues(Constants.ACTION_EDIT_REDO, null); + } + + private void OnEditGeometryCommand(object obj) + { + Mediator.NotifyColleagues(Constants.ACTION_EDIT_GEOMETRY, null); } private void OnEditMissionPhases(object obj) @@ -210,11 +295,15 @@ private void OnDeleteCommand(object obj) private void OnSaveCommand(object obj) { // file dialog - var sfd = new SaveFileDialog {Filter = "xml files (*.xml)|*.xml", RestoreDirectory = true}; + var sfd = new SaveFileDialog + { + Filter = "Mission xml files (*.xml)|*.xml|Geomessage xml files (*.xml)|*.xml", + RestoreDirectory = true + }; if (sfd.ShowDialog() == true) { - Mediator.NotifyColleagues(Constants.ACTION_SAVE_MISSION, sfd.FileName); + Mediator.NotifyColleagues(Constants.ACTION_SAVE_MISSION, String.Format("{0}{1}{2}", sfd.FilterIndex, Constants.SAVE_AS_DELIMITER, sfd.FileName)); } } diff --git a/source/MilitaryPlanner/ViewModels/MapViewModel.cs b/source/MilitaryPlanner/ViewModels/MapViewModel.cs index 649ca8a..bf4d769 100644 --- a/source/MilitaryPlanner/ViewModels/MapViewModel.cs +++ b/source/MilitaryPlanner/ViewModels/MapViewModel.cs @@ -47,6 +47,7 @@ private enum EditState DragDrop, Move, Tool, + Edit, None }; @@ -57,7 +58,7 @@ private enum EditState private Message _currentMessage; private EditState _editState = EditState.None; private MessageLayer _militaryMessageLayer; - private TimeExtent _currentTimeExtent = null; //new TimeExtent(DateTime.Now, DateTime.Now.AddSeconds(3599)); + private TimeExtent _currentTimeExtent = null; private readonly Mission _mission = new Mission("Default Mission"); private int _currentPhaseIndex = 0; @@ -80,12 +81,14 @@ private enum EditState public RelayCommand ToggleViewShedToolCommand { get; set; } public RelayCommand ToggleGotoXYToolCommand { get; set; } public RelayCommand ToggleNetworkingToolCommand { get; set; } + public RelayCommand ToggleBasemapGalleryCommand { get; set; } // controllers private GotoXYToolController _gotoXYToolController; private NetworkingToolController _networkingToolController; private ViewShedToolController _viewShedToolController; private CoordinateReadoutController _coordinateReadoutController; + private BasemapGalleryController _basemapGalleryController; public MapViewModel() { @@ -98,6 +101,10 @@ public MapViewModel() Mediator.Register(Constants.ACTION_SAVE_MISSION, DoSaveMission); Mediator.Register(Constants.ACTION_OPEN_MISSION, DoOpenMission); Mediator.Register(Constants.ACTION_EDIT_MISSION_PHASES, DoEditMissionPhases); + Mediator.Register(Constants.ACTION_EDIT_GEOMETRY, DoEditGeometry); + Mediator.Register(Constants.ACTION_EDIT_REDO, DoEditRedo); + Mediator.Register(Constants.ACTION_EDIT_UNDO, DoEditUndo); + Mediator.Register(Constants.ACTION_CLONE_MISSION, DoCloneMission); SetMapCommand = new RelayCommand(OnSetMap); PhaseAddCommand = new RelayCommand(OnPhaseAdd); @@ -113,6 +120,86 @@ public MapViewModel() ToggleViewShedToolCommand = new RelayCommand(OnToggleViewShedToolCommand); ToggleGotoXYToolCommand = new RelayCommand(OnToggleGotoXYToolCommand); ToggleNetworkingToolCommand = new RelayCommand(OnToggleNetworkingToolCommand); + ToggleBasemapGalleryCommand = new RelayCommand(OnToggleBasemapGalleryCommand); + } + + private void OnToggleBasemapGalleryCommand(object obj) + { + _basemapGalleryController.Toggle(); + } + + private void DoEditUndo(object obj) + { + if (_mapView.Editor.IsActive && _mapView.Editor.Undo.CanExecute(null)) + { + _mapView.Editor.Undo.Execute(null); + } + } + + private void DoEditRedo(object obj) + { + if (_mapView.Editor.IsActive && _mapView.Editor.Redo.CanExecute(null)) + { + _mapView.Editor.Redo.Execute(null); + } + } + + /// + /// On this command the currently selected geometry gets edited if it is a polyline or polygon + /// Updates the military message with the new geometry during and after editing + /// + /// + private async void DoEditGeometry(object obj) + { + if (_mapView.Editor.IsActive) + { + if (_mapView.Editor.Complete.CanExecute(null)) + { + _mapView.Editor.Complete.Execute(null); + _editState = EditState.None; + return; + } + } + + if (_currentMessage != null) + { + var tam = _mission.MilitaryMessages.FirstOrDefault(msg => msg.Id == _currentMessage.Id); + + if (tam != null) + { + _editState = EditState.Edit; + + try + { + var progress = new Progress(); + + progress.ProgressChanged += (a, ges) => + { + UpdateCurrentMessage(tam, ges.NewGeometry); + }; + + var resultGeometry = await _mapView.Editor.EditGeometryAsync(tam.SymbolGeometry, null, progress); + + if (resultGeometry != null) + { + tam.SymbolGeometry = resultGeometry; + UpdateCurrentMessage(tam, resultGeometry); + } + } + catch + { + // ignored + } + } + } + } + + private void DoCloneMission(object obj) + { + Mission cloneMission = _mission.DeepCopy(); + + // update mission cloned + Mediator.NotifyColleagues(Constants.ACTION_MISSION_CLONED, cloneMission); } private void OnToggleNetworkingToolCommand(object obj) @@ -130,7 +217,7 @@ private void OnSaveCommand(object obj) // file dialog var sfd = new SaveFileDialog { - Filter = "xml files (*.xml)|*.xml|Geomessage xml files (*.xml)|*.xml", + Filter = "Mission xml files (*.xml)|*.xml|Geomessage xml files (*.xml)|*.xml", RestoreDirectory = true }; @@ -255,6 +342,7 @@ private void DoOpenMission(object obj) if (_mission.Load(fileName)) { InitializeMapWithMission(); + RaisePropertyChanged(() => PhaseDescription); } } } @@ -464,7 +552,7 @@ private async void OnMeasureCommand(object param) // World Topo Map doesn't support mensuration //var temp = _mapView.Map.Layers["World Topo Map"]; - var temp = _mapView.Map.Layers["TestMapServiceLayer"]; + var temp = _mapView.Map.Layers["MensurationMapServiceLayer"]; _mensurationTask = new MensurationTask(new Uri((temp as ArcGISTiledMapServiceLayer).ServiceUri)); @@ -500,6 +588,10 @@ private async void OnMeasureCommand(object param) { MessageBox.Show(ex.Message, "Mensuration Error"); } + finally + { + _graphicsOverlay.Graphics.Clear(); + } } } @@ -638,6 +730,7 @@ private void OnSetMap(object param) _networkingToolController = new NetworkingToolController(mapView, this); _viewShedToolController = new ViewShedToolController(mapView, this); _coordinateReadoutController = new CoordinateReadoutController(mapView, this); + _basemapGalleryController = new BasemapGalleryController(mapView); // add default message layer AddNewMilitaryMessagelayer(); @@ -654,7 +747,7 @@ private void OnSetMap(object param) void mapView_MouseMove(object sender, MouseEventArgs e) { - if (_map == null || _editState == EditState.Tool) + if (_map == null || _editState == EditState.Tool || _editState == EditState.Edit) { return; } @@ -685,7 +778,7 @@ void mapView_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) async void mapView_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { - if (_editState == EditState.Create || _editState == EditState.Tool) + if (_editState == EditState.Create || _editState == EditState.Tool || _editState == EditState.Edit) { return; } @@ -820,13 +913,62 @@ private void UpdateCurrentMessage(MapPoint mapPoint) } } + private void UpdateCurrentMessage(TimeAwareMilitaryMessage tam, Geometry geometry) + { + var cpts = string.Empty; + + // TODO find a way to determine if polyline map points need adjustment based on symbol being drawn + + List mpList = null; + + var polyline = geometry as Polyline; + + if (polyline != null) + { + if (tam[MilitaryMessage.SicCodePropertyName].Contains("POLA") || + tam[MilitaryMessage.SicCodePropertyName].Contains("PPA")) + { + mpList = AdjustMapPoints(polyline, DrawShape.Arrow); + } + else + { + mpList = AdjustMapPoints(polyline, DrawShape.Polyline); + } + } + else + { + var polygon = geometry as Polygon; + + if (polygon != null) + { + mpList = new List(); + foreach (var part in polygon.Parts) + { + mpList.AddRange(part.GetPoints()); + } + } + } + + if (mpList != null) + { + var msg = new MilitaryMessage(tam.Id, MilitaryMessageType.PositionReport, MilitaryMessageAction.Update, + mpList); + + tam[MilitaryMessage.ControlPointsPropertyName] = msg[MilitaryMessage.ControlPointsPropertyName]; + + if (_militaryMessageLayer.ProcessMessage(msg)) + { + UpdateMilitaryMessageControlPoints(msg); + } + } + } + private void UpdateMilitaryMessageControlPoints(MilitaryMessage msg) { var tam = _mission.MilitaryMessages.First(m => m.Id == msg.Id); if (tam != null) { - //tam[MilitaryMessage.ControlPointsPropertyName] = msg[MilitaryMessage.ControlPointsPropertyName]; tam.StoreControlPoints(_mission.PhaseList[CurrentPhaseIndex].ID, msg); } } @@ -882,12 +1024,6 @@ private bool ProcessMessage(MessageLayer messageLayer, Message msg) { var result = messageLayer.ProcessMessage(msg); - // add id to messageIDList - //if (!_messageDictionary.ContainsKey(msg.Id) && result) - //{ - // _messageDictionary.Add(msg.Id, messageLayer.ID); - //} - if (!_phaseMessageDictionary.ContainsKey(messageLayer.ID)) { _phaseMessageDictionary.Add(messageLayer.ID, new List()); @@ -908,7 +1044,7 @@ private bool ProcessMessage(MessageLayer messageLayer, Message msg) private void DoActionDelete(object obj) { // remove any selected messages - if (_currentMessage != null) + if (_currentMessage != null && _editState != EditState.Edit) { // remove message RemoveMessage(_currentMessage); @@ -925,7 +1061,7 @@ private void DoActionCancel(object obj) } } - if (_editState == EditState.Create) + if (_editState == EditState.Create || _editState == EditState.Edit) { _editState = EditState.None; } @@ -995,7 +1131,8 @@ private void ProcessSymbol(SymbolViewModel symbol, Geometry geometry) { VisibleTimeExtent = new TimeExtent(_mission.PhaseList[CurrentPhaseIndex].VisibleTimeExtent.Start, _mission.PhaseList[CurrentPhaseIndex].VisibleTimeExtent.End), - Id = Guid.NewGuid().ToString("D") + Id = Guid.NewGuid().ToString("D"), + SymbolGeometry = geometry }; // set default time extent @@ -1156,6 +1293,7 @@ private void AddNewMessage(SymbolViewModel symbolViewModel, Point p, string guid // Construct the Control Points based on the geometry type of the drawn geometry. var point = _mapView.ScreenToLocation(p); + tam.SymbolGeometry = point; tam.Add(MilitaryMessage.ControlPointsPropertyName, point.X.ToString() + "," + point.Y.ToString()); //Process the message diff --git a/source/MilitaryPlanner/ViewModels/MissionViewModel.cs b/source/MilitaryPlanner/ViewModels/MissionViewModel.cs index 5d691d1..dc7053d 100644 --- a/source/MilitaryPlanner/ViewModels/MissionViewModel.cs +++ b/source/MilitaryPlanner/ViewModels/MissionViewModel.cs @@ -11,11 +11,16 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. + using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Globalization; using System.Linq; +using System.Windows; using System.Windows.Data; +using System.Windows.Media; +using Esri.ArcGISRuntime.Data; using Esri.ArcGISRuntime.Symbology.Specialized; using MilitaryPlanner.Helpers; using MilitaryPlanner.Models; @@ -24,7 +29,8 @@ namespace MilitaryPlanner.ViewModels { public class MissionViewModel : BaseViewModel { - readonly List _symbols = new List(); + //List _phaseSymbols = new List(); + readonly ObservableCollection _phaseSymbols = new ObservableCollection(); private Mission _mission = new Mission(); @@ -38,15 +44,19 @@ public Mission CurrentMission set { _mission = value; + _phaseSymbols.Clear(); ProcessMission(); RaisePropertyChanged(() => CurrentMission); RaisePropertyChanged(() => PhaseCount); RaisePropertyChanged(() => CurrentPhase); + RaisePropertyChanged(() => MissionTimeExtent); + RaisePropertyChanged(() => PhaseSymbols); } } public RelayCommand PhaseBackCommand { get; set; } public RelayCommand PhaseNextCommand { get; set; } + public RelayCommand DeletePhaseCommand { get; set; } public MissionViewModel() { @@ -61,6 +71,30 @@ public MissionViewModel() PhaseBackCommand = new RelayCommand(OnPhaseBack); PhaseNextCommand = new RelayCommand(OnPhaseNext); + DeletePhaseCommand = new RelayCommand(OnDeletePhase); + } + + private void OnDeletePhase(object obj) + { + var question = String.Format("Are you sure you want to delete phase?\n\rName : {0}",CurrentPhase.Name); + var result = MessageBox.Show(question, "Delete Phase?", MessageBoxButton.YesNo, MessageBoxImage.Warning); + + if (result == MessageBoxResult.Yes) + { + if (CurrentPhaseIndex < PhaseCount) + { + // remove phase + _mission.PhaseList.RemoveAt(CurrentPhaseIndex); + if (CurrentPhaseIndex >= PhaseCount && CurrentPhaseIndex != 0) + { + CurrentPhaseIndex--; + } + else + { + CurrentPhaseIndex = CurrentPhaseIndex; + } + } + } } private void OnPhaseIndexChanged(object obj) @@ -95,11 +129,11 @@ private void OnMissionCloned(object obj) } } - public IReadOnlyCollection Symbols + public ObservableCollection PhaseSymbols { get { - return _symbols; + return _phaseSymbols; } } @@ -111,6 +145,14 @@ public int PhaseCount } } + public TimeExtent MissionTimeExtent + { + get + { + return new TimeExtent(_mission.PhaseList.Min(t => t.VisibleTimeExtent.Start), _mission.PhaseList.Max(t => t.VisibleTimeExtent.End)); + } + } + private int _currentPhaseIndex = 0; public int CurrentPhaseIndex { @@ -121,10 +163,13 @@ public int CurrentPhaseIndex set { - _currentPhaseIndex = value; - CurrentPhase = _mission.PhaseList[_currentPhaseIndex]; - RaisePropertyChanged(() => CurrentPhaseIndex); - RaisePropertyChanged(() => PhaseMessage); + if (value < _mission.PhaseList.Count) + { + _currentPhaseIndex = value; + CurrentPhase = _mission.PhaseList[_currentPhaseIndex]; + RaisePropertyChanged(() => CurrentPhaseIndex); + RaisePropertyChanged(() => PhaseMessage); + } } } @@ -164,50 +209,80 @@ private void ProcessMission() int currentStartPhase = 0; int currentEndPhase = 0; - foreach (var phase in _mission.PhaseList) + foreach (var mm in _mission.MilitaryMessages) { - // for each message in the phase - //TODO revisit - //foreach (var pm in phase.PersistentMessages) - //{ - // // for each message, create/update a phase symbol in the symbol list - // CreateUpdateSymbolWithPM(pm, currentStartPhase, currentEndPhase); - //} - - currentStartPhase++; - currentEndPhase++; + currentStartPhase = 0; + currentEndPhase = -1; + + foreach (var phase in _mission.PhaseList) + { + if (mm.VisibleTimeExtent.Intersects(phase.VisibleTimeExtent)) + { + currentEndPhase = _mission.PhaseList.IndexOf(phase); + } + else + { + //if (_mission.PhaseList.IndexOf(phase) <= currentEndPhase) + if (currentEndPhase < 0) + { + //currentStartPhase = _mission.PhaseList.IndexOf(phase); + currentStartPhase++; + } + } + } + + var pm = new PersistentMessage() { ID = mm.Id, VisibleTimeExtent = mm.VisibleTimeExtent}; + + var piList = new List(); + + foreach (var item in mm) + { + piList.Add(new PropertyItem() { Key = item.Key, Value = item.Value }); + } + + pm.PropertyItems = piList; + + CreateUpdateSymbolWithPM(pm, currentStartPhase, currentEndPhase); } + } private void CreateUpdateSymbolWithPM(PersistentMessage pm, int currentStartPhase, int currentEndPhase) { // is this an update or a new symbol - var foundSymbol = _symbols.Where(sl => sl.ItemSVM.Model.Values.ContainsKey(Message.IdPropertyName) && sl.ItemSVM.Model.Values[Message.IdPropertyName] == pm.ID); + var foundSymbol = _phaseSymbols.FirstOrDefault(sl => sl.ItemSVM.Model.Values.ContainsKey(Message.IdPropertyName) && sl.ItemSVM.Model.Values[Message.IdPropertyName] == pm.ID); - if (foundSymbol != null && foundSymbol.Any()) + //if (foundSymbol != null && foundSymbol.Any()) + if(foundSymbol != null) { // symbol is in list, do an update - var ps = foundSymbol.ElementAt(0); + var ps = foundSymbol;//.ElementAt(0); ps.EndPhase = currentEndPhase; } else { // symbol is missing, ADD a new one - var psvm = new PhaseSymbolViewModel - { - StartPhase = currentStartPhase, - EndPhase = currentEndPhase, - ItemSVM = SymbolLoader.Search(pm.PropertyItems.Where(pi => pi.Key == "sic").ElementAt(0).Value) - }; + PropertyItem first = pm.PropertyItems.FirstOrDefault(pi => pi.Key == "sic"); - // create SVM - if (!psvm.ItemSVM.Model.Values.ContainsKey(Message.IdPropertyName)) + if (first != null) { - psvm.ItemSVM.Model.Values.Add(Message.IdPropertyName, pm.ID); + var psvm = new PhaseSymbolViewModel + { + StartPhase = currentStartPhase, + EndPhase = currentEndPhase, + ItemSVM = SymbolLoader.Search(first.Value), + VisibleTimeExtent = pm.VisibleTimeExtent + }; + + // create SVM + if (!psvm.ItemSVM.Model.Values.ContainsKey(Message.IdPropertyName)) + { + psvm.ItemSVM.Model.Values.Add(Message.IdPropertyName, pm.ID); + } + + _phaseSymbols.Add(psvm); } - - _symbols.Add(psvm); } } @@ -262,6 +337,45 @@ public int ViewWidth } } + private TimeExtent _visibleTimeExtent = new TimeExtent(); + + public TimeExtent VisibleTimeExtent + { + get + { + return _visibleTimeExtent; + } + set + { + _visibleTimeExtent = value; + RaisePropertyChanged(() => VisibleTimeExtent); + } + } + + } + + public class PadLeftVariableWidthConverter : IMultiValueConverter + { + + public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) + { + double listWidth = (double)values[0]; + TimeExtent messageTimeExtent = (TimeExtent)values[1]; + TimeExtent missionTimeExtent = (TimeExtent)values[2]; + + TimeSpan ts = messageTimeExtent.Start.Subtract(missionTimeExtent.Start); + TimeSpan mts = missionTimeExtent.End.Subtract(missionTimeExtent.Start); + + var widthFactor = ts.TotalSeconds / mts.TotalSeconds; + var width = listWidth * widthFactor; + + return Math.Max(width,0.0); + } + + public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } } public class VariableWidthConverter : IMultiValueConverter @@ -269,18 +383,43 @@ public class VariableWidthConverter : IMultiValueConverter public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) { - int totalPhaseLength = (int)values[0]; - int phaseLength = (int)values[1]; - double listWidth = (double)values[2]; - - var width = ((listWidth / totalPhaseLength) * phaseLength); + double listWidth = (double)values[0]; + TimeExtent messageTimeExtent = (TimeExtent)values[1]; + TimeExtent missionTimeExtent = (TimeExtent)values[2]; + + TimeSpan ts = messageTimeExtent.End.Subtract(messageTimeExtent.Start); + TimeSpan mts = missionTimeExtent.End.Subtract(missionTimeExtent.Start); + + var widthFactor = ts.TotalSeconds / mts.TotalSeconds; + var width = listWidth * widthFactor; + + return Math.Max(0.0, width - 12); + } + + public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } + + public class PhaseWidthConverter : IMultiValueConverter + { + + public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) + { + int totalPhaseCount = (int)values[0]; + double listWidth = (double)values[1]; + + var width = (listWidth - (listWidth % totalPhaseCount)) / totalPhaseCount; - if (phaseLength-1 > 0) + var offset = 0; + + while (totalPhaseCount*(width - offset) > listWidth - 3) { - width -= 12; + offset++; } - return width; + return Math.Max(0.0,width - offset); } public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) @@ -288,5 +427,50 @@ public object[] ConvertBack(object value, Type[] targetTypes, object parameter, throw new NotImplementedException(); } } + public class PhaseHeightConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return (double)value; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } + + public class SIC2BrushConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + var sic = (string) value; + + Brush brush; + + switch (sic[1].ToString().ToLower()) + { + case "f": + brush = Brushes.DeepSkyBlue; + break; + case "n": + brush = Brushes.LightGreen; + break; + case "h": + brush = Brushes.Salmon; + break; + default: + brush = Brushes.Yellow; + break; + } + + return brush; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } } diff --git a/source/MilitaryPlanner/ViewModels/SymbolViewModel.cs b/source/MilitaryPlanner/ViewModels/SymbolViewModel.cs index 64e30ff..aa3cdd8 100644 --- a/source/MilitaryPlanner/ViewModels/SymbolViewModel.cs +++ b/source/MilitaryPlanner/ViewModels/SymbolViewModel.cs @@ -30,11 +30,6 @@ public string SymbolID get { return _model.Values["SymbolID"]; } } - //public string StyleFile - //{ - // get { return _model.Values["StyleFile"].ToString(); } - //} - public SymbolProperties Model { get { return _model; } diff --git a/source/MilitaryPlanner/Views/BasemapGalleryView.xaml b/source/MilitaryPlanner/Views/BasemapGalleryView.xaml new file mode 100644 index 0000000..e0e00a0 --- /dev/null +++ b/source/MilitaryPlanner/Views/BasemapGalleryView.xaml @@ -0,0 +1,28 @@ + + + + + + + + + + + diff --git a/source/MilitaryPlanner/Views/BasemapGalleryView.xaml.cs b/source/MilitaryPlanner/Views/BasemapGalleryView.xaml.cs new file mode 100644 index 0000000..98c117e --- /dev/null +++ b/source/MilitaryPlanner/Views/BasemapGalleryView.xaml.cs @@ -0,0 +1,21 @@ +using System.Windows; +using MilitaryPlanner.ViewModels; +using System.Windows.Controls.Primitives; + +namespace MilitaryPlanner.Views +{ + /// + /// Interaction logic for BasemapGalleryView.xaml + /// + public partial class BasemapGalleryView : Popup + { + public BasemapGalleryView() + { + InitializeComponent(); + ViewModel = new BasemapGalleryViewModel(); + DataContext = ViewModel; + } + + public BasemapGalleryViewModel ViewModel { get; set; } + } +} diff --git a/source/MilitaryPlanner/Views/EditMissionPhasesView.xaml b/source/MilitaryPlanner/Views/EditMissionPhasesView.xaml index 2bcc1e6..0e89832 100644 --- a/source/MilitaryPlanner/Views/EditMissionPhasesView.xaml +++ b/source/MilitaryPlanner/Views/EditMissionPhasesView.xaml @@ -24,6 +24,7 @@ + diff --git a/source/MilitaryPlanner/Views/GotoXYToolView.xaml b/source/MilitaryPlanner/Views/GotoXYToolView.xaml index cbebac2..be48bbe 100644 --- a/source/MilitaryPlanner/Views/GotoXYToolView.xaml +++ b/source/MilitaryPlanner/Views/GotoXYToolView.xaml @@ -11,7 +11,7 @@ HorizontalOffset="10" d:DesignHeight="320" d:DesignWidth="300"> - diff --git a/source/MilitaryPlanner/Views/MainWindow.xaml b/source/MilitaryPlanner/Views/MainWindow.xaml index b3e020b..7c4304d 100644 --- a/source/MilitaryPlanner/Views/MainWindow.xaml +++ b/source/MilitaryPlanner/Views/MainWindow.xaml @@ -2,8 +2,13 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:vm="clr-namespace:MilitaryPlanner.ViewModels" + xmlns:helper="clr-namespace:MilitaryPlanner.Helpers" Title="Military Planner" - WindowState="Maximized" + Height="{helper:SettingBinding Height}" + Width="{helper:SettingBinding Width}" + Left="{helper:SettingBinding Left}" + Top="{helper:SettingBinding Top}" + WindowState="{helper:SettingBinding WindowState}" DataContext="{DynamicResource ViewModelMain}"> @@ -13,6 +18,9 @@ + + + @@ -30,9 +38,11 @@