Exemplo n.º 1
0
 /// <summary>
 /// Intiaizlize a new KV level
 /// </summary>
 public MM_KVLevel()
 {
     this.Energized             = new MM_DisplayParameter();
     this.DeEnergized           = new MM_DisplayParameter();
     this.PartiallyEnergized    = new MM_DisplayParameter();
     this.DeEnergized.ForeColor = this.PartiallyEnergized.ForeColor = Color.Gray;
     this.Energized.Width       = this.DeEnergized.Width = this.PartiallyEnergized.Width = 1;
 }
Exemplo n.º 2
0
 /// <summary>
 /// Initialize a new KV level based on its name and foreground color
 /// </summary>
 /// <param name="Name">The name of the KV level</param>
 /// <param name="ForeColor">The foreground color </param>
 public MM_KVLevel(String Name, String ForeColor)
 {
     this.Name               = Name;
     this.Energized          = new MM_DisplayParameter(ColorTranslator.FromHtml(ForeColor), 1, false);
     this.DeEnergized        = new MM_DisplayParameter(Color.DarkGray, 1, false);
     this.PartiallyEnergized = new MM_DisplayParameter(Color.DarkGray, 1, false);
     this.PartiallyEnergized = new MM_DisplayParameter(Color.DarkGray, 1, false);
     this.Index              = MM_Repository.KVLevels.Count;
 }
Exemplo n.º 3
0
 /// <summary>
 /// Initialize a new KV Level
 /// </summary>
 /// <param name="ElementSource">Our element source</param>
 /// <param name="AddIfNew"></param>
 public MM_KVLevel(XmlElement ElementSource, bool AddIfNew)
     : base(ElementSource, AddIfNew)
 {
     this.Energized          = new MM_DisplayParameter(ElementSource["Energized"], AddIfNew);
     this.PartiallyEnergized = new MM_DisplayParameter(ElementSource["PartiallyEnergized"], AddIfNew);
     if (ElementSource["Unknown"] != null)
     {
         this.Unknown = new MM_DisplayParameter(ElementSource["Unknown"], AddIfNew);
     }
     else
     {
         this.Unknown = new MM_DisplayParameter(ElementSource["PartiallyEnergized"], AddIfNew);
     }
     this.DeEnergized = new MM_DisplayParameter(ElementSource["DeEnergized"], AddIfNew);
 }
Exemplo n.º 4
0
        private void DrawLegendLine(MM_DisplayParameter state, Vector2 start, Vector2 end, bool drawKVText = false, bool drawEnergizationState = false)
        {
            if (_legendFont == null || _legendFont.IsDisposed)
            {
                _legendFont = null;
                _legendFont = Surface.Fonts.GetTextFormat(MM_Repository.OverallDisplay.NetworkMapFont);
            }

            var color = state.ForeColor;

            var width  = state.Width;
            var stroke = state.DashStyle;

            var style = _solidStyle;

            if (stroke == System.Drawing.Drawing2D.DashStyle.Dash)
            {
                style = _dashStyle;
            }
            else if (stroke == System.Drawing.Drawing2D.DashStyle.DashDot || stroke == System.Drawing.Drawing2D.DashStyle.DashDotDot)
            {
                style = _dotDashStyle;
            }

            var brush = Surface.Brushes.GetBrush(color, 1f);

            if (drawKVText)
            {
                TextLayout textLayout = null;
                string     text       = state.Name.Split('.')[0];
                if (!_strings.TryGetValue(text, out textLayout) || textLayout == null || textLayout.IsDisposed)
                {
                    textLayout = new TextLayout(Surface.FactoryDirectWrite, text ?? string.Empty, _legendFont, end.X - start.X - 2, 16)
                    {
                        WordWrapping = WordWrapping.NoWrap
                    };
                    _strings[text] = textLayout;
                }
                Surface.RenderTarget2D.DrawTextLayout(start + Vector2.One, textLayout, brush, DrawTextOptions.Clip);
            }

            if (drawEnergizationState)
            {
                TextLayout textLayout = null;
                string     text       = state.Name.Split('.')[1];
                if (!_strings.TryGetValue(text, out textLayout) || textLayout == null || textLayout.IsDisposed)
                {
                    textLayout = new TextLayout(Surface.FactoryDirectWrite, text ?? string.Empty, _legendFont, end.X - start.X - 2, 12)
                    {
                        WordWrapping = WordWrapping.NoWrap
                    };
                    _strings[text] = textLayout;
                }
                Surface.RenderTarget2D.DrawTextLayout(start + new Vector2(1, -textLayout.Metrics.Height - 3), textLayout, Surface.Brushes.GetBrush(SharpDX.Color.White), DrawTextOptions.Clip);
            }

            if (_isBlinking || !state.Blink)
            {
                // offset by half a pixel to get hard lines
                bool offsetHalf = width - Math.Truncate(width) > 0 || ((int)width % 2 == 1);
                var  aa         = Surface.RenderTarget2D.AntialiasMode;
                Surface.RenderTarget2D.AntialiasMode = AntialiasMode.Aliased;
                Surface.RenderTarget2D.DrawLine(start + new Vector2(0, offsetHalf ? 0.5f : 0), end + new Vector2(0, offsetHalf ? 0.5f : 0), brush, width, style);
                Surface.RenderTarget2D.AntialiasMode = aa;
            }
        }
Exemplo n.º 5
0
        private void UpdateAndDrawFlowArrow(RenderTime time, LineProxy proxy, int zoomLevel, MM_DisplayParameter displayOptions, SharpDX.Direct2D1.Brush brush)
        {
            //Now, draw the line as appropriate
            if (proxy.Coordinates == null || proxy.Coordinates.Count == 0)
            {
                return;
            }

            // reset if bad
            if (proxy.FlowPositionPercentage > 1)
            {
                proxy.FlowPositionPercentage = 1;
            }
            if (proxy.FlowPositionPercentage < float.Epsilon)
            {
                proxy.FlowPositionPercentage = 0;
            }

            // reset if bad
            if (proxy.FlowVarPositionPercentage > 1)
            {
                proxy.FlowVarPositionPercentage = 1;
            }
            if (proxy.FlowVarPositionPercentage < float.Epsilon)
            {
                proxy.FlowVarPositionPercentage = 0;
            }

            var Line = proxy.BaseLine;

            // don't render dead lines flow arrows
            if (Line == null || Math.Abs(Line.MVAFlow) < 0.1 || (Line.NearBus != null && Line.NearBus.Dead) || (Line.FarBus != null && Line.FarBus.Dead))
            {
                return;
            }

            //Determine how large our arrow should be

            float baseSize         = proxy.BaseLine.KVLevel.MVASize;
            float threshold        = proxy.BaseLine.KVLevel.MVAThreshold / 100.0f;
            bool  flowKvVisible    = proxy.BaseLine.KVLevel.ShowMVA;
            float mvaSize          = Line.KVLevel.MVASize;
            float lineWidth        = displayOptions.Width;
            float flowIncrement    = 0;
            float flowVarIncrement = 0;
            float flowSize;

            bool isTie = Line is MM_Tie;


            if ((!MM_Repository.OverallDisplay.ShowLineFlows || !flowKvVisible) && !(isTie && MM_Repository.OverallDisplay.TieFlowDirectionVisible))
            {
                return;
            }
            //MM_Repository.OverallDisplay.
            // absolute vs % limit
            bool absoluteValueFlowSize = zoomLevel < MM_Repository.OverallDisplay.LineFlows;

            if (isTie && MM_Repository.OverallDisplay.TieFlowDirectionVisible)
            {
                // show enhanced size/color for AC Tie lines if TieFlowDirectionVisible == true
                if (MM_Repository.KVLevels.ContainsKey("Tie"))
                {
                    mvaSize = MM_Repository.KVLevels["Tie"].MVASize;
                }
                else if (MM_Repository.KVLevels.ContainsKey("DCTie"))
                {
                    mvaSize = MM_Repository.KVLevels["DCTie"].MVASize;
                }
                else
                {
                    mvaSize = 5;
                }
            }
            else if (Line.KVLevel.Name.Contains("Tie"))
            {
                isTie = true;
            }
            bool  moveForward = true;
            float zoomScale   = (float)zoomLevel / MM_Repository.OverallDisplay.LineFlows;

            bool varForward  = true;
            var  varFlowSize = Math.Min(50, lineWidth * baseSize);

            if (absoluteValueFlowSize)
            {
                if (Math.Abs(Line.MVAFlow) < float.Epsilon)
                {
                    flowSize = flowIncrement = 0;
                }
                else
                {
                    moveForward = Line.MVAFlowDirection == Line.ConnectedStations[1];
                    varForward  = Line.MVARFlowDirection == Line.ConnectedStations[1];
                    flowSize    = (zoomLevel / 20f) * mvaSize * Line.MVAFlow * 0.02f; // + 1.5f * lineWidth;
                    varFlowSize = (zoomLevel / 20f) * mvaSize * Line.MVAFlow * 0.04f; // + 1.5f * lineWidth;
                }
            }
            else
            {
                // relative size based on limit
                if (Line.NormalLimit == 0)
                {
                    flowSize = flowIncrement = 0;
                }
                else
                {
                    var percentLimit = Line.MVAFlow / Line.NormalLimit;
                    var percentVar   = Line.MVARFlow / Line.NormalLimit;

                    if (percentLimit < threshold)
                    {
                        return;
                    }

                    moveForward = Line.MVAFlowDirection == Line.ConnectedStations[1];
                    varForward  = Line.MVARFlowDirection == Line.ConnectedStations[1];
                    flowSize    = 10f * mvaSize * percentLimit * ((float)(MathUtil.Clamp(lineWidth, 1, 3) / 2f));
                    varFlowSize = 30f * mvaSize * percentVar * ((float)(MathUtil.Clamp(lineWidth, 1, 3) / 2f));
                }
            }
            flowSize    = MathUtil.Clamp(flowSize, 0, 100);
            varFlowSize = MathUtil.Clamp(varFlowSize, 0, 100);

            if (float.IsNaN(flowSize) || flowSize < 0)
            {
                flowSize    = flowIncrement = 0;
                varFlowSize = flowVarIncrement = 0;
            }
            else
            {
                flowIncrement    = MathUtil.Clamp(0.5f * zoomScale * flowSize, -20f, 20f) * (float)time.ElapsedTime.TotalSeconds;
                flowVarIncrement = MathUtil.Clamp(0.5f * zoomScale * varFlowSize, -20f, 20f) * (float)time.ElapsedTime.TotalSeconds;
            }

            // scale up arrow (but not speed) based on zoom


            if (flowSize < lineWidth + 0.25f || proxy.Length <= 0 ||
                (MM_Repository.OverallDisplay.BlackstartMode == MM_Display.enumBlackstartMode.HideExternalElements && !Line.IsInternal))
            {
                return;
            }

            // Move MVA flow arrow
            if (moveForward)
            {
                proxy.FlowPositionPercentage += flowIncrement / proxy.Length;
            }
            else
            {
                proxy.FlowPositionPercentage -= flowIncrement / proxy.Length;
            }

            if (proxy.FlowPositionPercentage < float.Epsilon)
            {
                proxy.FlowPositionPercentage = 1 - proxy.FlowPositionPercentage;
            }
            else
            {
                proxy.FlowPositionPercentage -= (float)Math.Floor(proxy.FlowPositionPercentage); // just keep the decimal
            }
            // Move Var Arrow
            if (varForward)
            {
                proxy.FlowVarPositionPercentage += flowVarIncrement / proxy.Length;
            }
            else
            {
                proxy.FlowVarPositionPercentage -= flowVarIncrement / proxy.Length;
            }

            if (proxy.FlowVarPositionPercentage < float.Epsilon)
            {
                proxy.FlowVarPositionPercentage = 1 - proxy.FlowVarPositionPercentage;
            }
            else
            {
                proxy.FlowVarPositionPercentage -= (float)Math.Floor(proxy.FlowVarPositionPercentage); // just keep the decimal
            }
            if (zoomLevel < MM_Repository.OverallDisplay.LineFlows)
            {
                proxy.FlowPositionPercentage    = 0.5f;
                proxy.FlowVarPositionPercentage = 0.5f;
            }

            SolidColorBrush borderBrush = null;

            if (isTie && MM_Repository.OverallDisplay.TieFlowDirectionVisible)
            {
                bool importing = (Line.Substation1.IsInternal && (Line.MVAFlowDirection == Line.Substation1)) ||
                                 (Line.Substation2.IsInternal && (Line.MVAFlowDirection == Line.Substation2));

                var color = importing ? MM_Repository.OverallDisplay.TieFlowImportColor : MM_Repository.OverallDisplay.TieFlowExportColor;
                if (proxy.BlackStartDim)
                {
                    color = MM_Repository.OverallDisplay.BlackstartDim(color);
                }

                borderBrush = Surface.Brushes.GetBrush(color);
            }

            // add border around flow arrow for tie

            //proxy.FlowPositionPercentage = 0;
            float      rotAngle = 0;
            RawVector2 ArrowTip = proxy.GetPosition(proxy.FlowPositionPercentage, out rotAngle);

            float      varrotAngle = 0;
            RawVector2 varArrowTip = proxy.GetPosition(proxy.FlowVarPositionPercentage, out varrotAngle);

            //var
            if (MM_Repository.OverallDisplay.VarFlows)
            {
                DrawArrow(null, brush, varArrowTip, varrotAngle, varFlowSize, varForward, 0.1f);
            }

            // mva
            DrawArrow(brush, borderBrush, ArrowTip, rotAngle, flowSize, moveForward, 0.25f);
        }
Exemplo n.º 6
0
        private void DrawLines(RenderTime renderTime, RawRectangleF coordinateBounds)
        {
            if (_lineFont == null || _lineFont.IsDisposed)
            {
                _lineFont = Surface.Fonts.GetTextFormat(MM_Repository.OverallDisplay.NetworkMapFont);
            }
            int zoomLevel = Surface.Coordinates.ZoomLevel;

            foreach (var lineKvp in _lineRenderProxies)
            {
                var proxy = lineKvp.Value;
                proxy.Visible = false;

                var line = proxy == null?null:proxy.BaseLine;


                if (line == null || proxy.BlackStartHidden || !IsLineVisible(coordinateBounds, proxy, zoomLevel))
                {
                    continue;
                }

                bool showViolation = ((MM_Network_Map_DX)Surface).IsBlinking;
                MM_DisplayParameter displayOptions = line.LineDisplay(_violationViewer.ShownViolations, Surface, showViolation);
                var stroke = displayOptions.ForePen.DashStyle;
                var color  = displayOptions.ForePen.Color;
                var width  = displayOptions.ForePen.Width;
                //var energizationState = line.LineEnergizationState;

                // set dash style
                var style = _solidStyle;

                if (stroke == System.Drawing.Drawing2D.DashStyle.Dash)
                {
                    style = _dashStyle;
                }
                else if (stroke == System.Drawing.Drawing2D.DashStyle.DashDot || stroke == System.Drawing.Drawing2D.DashStyle.DashDotDot)
                {
                    style = _dotDashStyle;
                }

                if (proxy.BlackStartDim)
                {
                    color = MM_Repository.OverallDisplay.BlackstartDim(color);
                }

                var colorBrush = Surface.Brushes.GetBrush(color);
                proxy.Visible = true;

                for (int i = 0; i < proxy.Coordinates.Count - 1; i++)
                {
                    Surface.RenderTarget2D.DrawLine(proxy.Coordinates[i], proxy.Coordinates[i + 1], colorBrush, width, style);
                }

                if (zoomLevel >= MM_Repository.OverallDisplay.LineFlows || (proxy.BaseLine is MM_Tie && MM_Repository.OverallDisplay.TieFlowDirectionVisible))
                {
                    UpdateAndDrawFlowArrow(renderTime, proxy, zoomLevel, displayOptions, colorBrush);
                }

                if (zoomLevel > (line.KVLevel.StationNames + 1) && (line.KVLevel.ShowMVARText || line.KVLevel.ShowMVAText || line.KVLevel.ShowMWText || line.KVLevel.ShowPercentageText || line.KVLevel.ShowLineName))
                {
                    // Draw Line Name
                    string nameText = line.GetLineText();
                    if (proxy.NameText == null || proxy.NameText != nameText || (proxy.NameTextLayout != null && proxy.NameTextLayout.IsDisposed))
                    {
                        if (proxy.NameTextLayout != null)
                        {
                            proxy.NameTextLayout.Dispose();
                        }
                        proxy.NameText       = nameText;
                        proxy.NameTextLayout = new TextLayout(Surface.FactoryDirectWrite, nameText, _lineFont, 300, 300);
                    }

                    var tx = Surface.RenderTarget2D.Transform;
                    Surface.RenderTarget2D.Transform = Matrix3x2.Rotation(proxy.Angle, proxy.Center) * Matrix3x2.Translation(-Surface.Coordinates.TopLeftXY.X, -Surface.Coordinates.TopLeftXY.Y);
                    Surface.RenderTarget2D.DrawTextAtPoint(proxy.NameTextLayout, colorBrush, proxy.Center.X, proxy.Center.Y, centerX: true);
                    Surface.RenderTarget2D.Transform = tx;
                }
                else
                {
                    if (proxy.NameTextLayout != null)
                    {
                        proxy.NameTextLayout.Dispose();
                    }
                    proxy.NameTextLayout = null;
                }
            }

            // render flowgate text on top of lines
            if (MM_Repository.OverallDisplay.ShowFlowgates)
            {
                foreach (var proxy in _lineRenderProxies.Values)
                {
                    if (!proxy.Visible)
                    {
                        continue;
                    }

                    var line     = proxy.BaseLine;
                    var flowgate = line.Contingencies.Where(f => f.Type == "Flowgate").Cast <MM_Flowgate>().OrderByDescending(f => Math.Abs(f.PercentLoaded)).FirstOrDefault(f => Math.Abs(f.PercentLoaded) > 0.90);
                    if (flowgate != null && Math.Abs(flowgate.PCTGFlow) > float.Epsilon)
                    {
                        var  fgName     = flowgate.Name;
                        bool monitored  = flowgate.MonitoredElements.Contains(line.LineTEID);
                        bool contingent = flowgate.ContingentElements.Contains(line.LineTEID);
                        fgName += monitored ? " (M)" : contingent ? " (C)" : "()";

                        if (proxy.FlowgateText != fgName || proxy.FlowgateText == null || (proxy.FlowgateTextLayout != null && proxy.FlowgateTextLayout.IsDisposed))
                        {
                            // rebuild text layout
                            if (proxy.FlowgateTextLayout != null)
                            {
                                proxy.FlowgateTextLayout.Dispose();
                            }
                            proxy.FlowgateText       = fgName;
                            proxy.FlowgateTextLayout = new TextLayout(Surface.FactoryDirectWrite, fgName, _lineFont, 300, 300);
                        }


                        var fgcolor = SharpDX.Color.Gray;

                        if (flowgate.PercentLoaded > 1)
                        {
                            fgcolor = SharpDX.Color.Red;
                        }
                        else if (Math.Abs(flowgate.PercentLoaded) > .95)
                        {
                            fgcolor = SharpDX.Color.Orange;
                        }
                        else if (Math.Abs(flowgate.PercentLoaded) > .90)
                        {
                            fgcolor = SharpDX.Color.Yellow;
                        }

                        var tx = Surface.RenderTarget2D.Transform;
                        if (zoomLevel > 8)
                        {
                            Surface.RenderTarget2D.Transform = Matrix3x2.Rotation(proxy.Angle, proxy.Center) * Matrix3x2.Translation(-Surface.Coordinates.TopLeftXY.X, -Surface.Coordinates.TopLeftXY.Y);
                        }

                        Surface.RenderTarget2D.DrawTextAtPoint(proxy.FlowgateTextLayout, Surface.Brushes.GetBrush(fgcolor), proxy.Center.X, proxy.Center.Y - proxy.FlowgateTextLayout.Metrics.Height, centerX: true);
                        Surface.RenderTarget2D.Transform = tx;
                    }
                }
            }
        }
Exemplo n.º 7
0
        private void DrawSubstations(RawRectangleF bounds, int zoomLevel, bool showViolation)
        {
            foreach (var proxy in _substationProxies.Values)
            {
                proxy.Visible = false;

                if (proxy.BlackStartHidden)
                {
                    continue;
                }

                // check visibility and bounds
                if (!bounds.Contains(proxy.Coordinates))
                {
                    continue;
                }

                if (!IsVisible(proxy, zoomLevel))
                {
                    continue;
                }



                MM_DisplayParameter Disp = proxy.Substation.SubstationDisplay(_violationViewer.ShownViolations, Surface);
                if (Disp.Blink && !showViolation)
                {
                    Disp = MM_Repository.SubstationDisplay;
                }

                bool showingViolation = Disp != MM_Repository.SubstationDisplay;

                bool  isInternal = proxy.Substation.IsInternal;
                var   DrawColor  = Disp.ForeColor;
                float width      = Disp.Width;

                //If drawing the substation with voltage profile, update the color accordingly
                if (MM_Repository.SubstationDisplay.ShowSubstationVoltages && (!Disp.Blink || (Disp.Blink && showViolation)))
                {
                    try
                    {
                        float WorstPu = proxy.Substation.Average_pU;

                        if (float.IsNaN(WorstPu))
                        {
                            DrawColor = Color.FromArgb(50, 50, 50);
                        }
                        else if ((WorstPu >= 1f + (MM_Repository.OverallDisplay.ContourThreshold / 100f) || WorstPu <= 1f - (MM_Repository.OverallDisplay.ContourThreshold / 100f)))
                        {
                            DrawColor = RelativeColor(WorstPu - 1f);
                        }
                    }
                    catch
                    {
                        /* suppress errors */
                    }
                }

                if (proxy.BlackStartDim)
                {
                    DrawColor = MM_Repository.OverallDisplay.BlackstartDim(DrawColor);
                }

                proxy.Color = DrawColor.ToDxColor4();
                var  solidColorBrush = Surface.Brushes.GetBrush(proxy.Color);
                bool drawBubbles     = false;
                proxy.Visible = true;

                drawBubbles = MM_Repository.SubstationDisplay.GeneratorBubblesMode != MM_Substation_Display.MM_Generator_Bubble_Mode.None;

                bool fuelBubbleVisible = false;
                bool loadBubbleVisible = false;
                // Draw load squares if requested


                if (proxy.Substation.HasLoads && MM_Repository.SubstationDisplay.ShowLoadBubbles)
                {
                    DrawLoadBubbles(proxy, zoomLevel);
                    loadBubbleVisible = true;
                }

                if (drawBubbles && proxy.Substation.HasUnits)
                {
                    DrawFuelBubbles(proxy, zoomLevel);
                    fuelBubbleVisible = true;
                }

                if (MM_Repository.SubstationDisplay.ShowReserveZones && proxy.Substation.HasUnits)
                {
                    ShowReserveZonesBubbles(proxy, zoomLevel);
                }

                // Draw unit substations
                if (proxy.Substation.HasUnits &&
                    (MM_Repository.SubstationDisplay.ShowSubstations == MM_Substation_Display.SubstationViewEnum.Units ||
                     MM_Repository.SubstationDisplay.ShowSubstations == MM_Substation_Display.SubstationViewEnum.All))
                {
                    if ((showingViolation || !fuelBubbleVisible))
                    {
                        // scale down diamonds for substations if we are drawing bubbles
                        float scale = 1.75f;
                        using (var geom = SharpDxExtensions.CreatePathGeometry(Surface.Factory2D, true, true,
                                                                               new Vector2(proxy.Coordinates.X - (width * scale), proxy.Coordinates.Y),
                                                                               new Vector2(proxy.Coordinates.X, proxy.Coordinates.Y - (width * scale)),
                                                                               new Vector2(proxy.Coordinates.X + (width * scale), proxy.Coordinates.Y),
                                                                               new Vector2(proxy.Coordinates.X, proxy.Coordinates.Y + (width * scale))))
                        {
                            Surface.RenderTarget2D.FillGeometry(geom, solidColorBrush);
                        }
                    }
                }
                else if ((showingViolation || !loadBubbleVisible))
                {
                    // we don't need to AA squares. this saves performance
                    var aa = Surface.RenderTarget2D.AntialiasMode;
                    Surface.RenderTarget2D.AntialiasMode = AntialiasMode.Aliased;
                    Surface.RenderTarget2D.FillRectangle(new RectangleF(proxy.Coordinates.X - width, proxy.Coordinates.Y - width, width * 2f, width * 2f), solidColorBrush);
                    Surface.RenderTarget2D.AntialiasMode = aa;
                }

                bool hasIcon = DrawIcons(proxy, showViolation);
                if (proxy.Substation.KVLevels != null)
                {
                    foreach (MM_KVLevel Voltage in proxy.Substation.KVLevels)
                    {
                        if (zoomLevel >= Voltage.StationMW || zoomLevel >= Voltage.StationNames)
                        {
                            DrawText(proxy, zoomLevel, solidColorBrush, hasIcon);
                        }
                    }
                }
            }
        }