Jump to content

Simplifying Polyline With Visvalingham-Whyatt Method + Zhou - Jones Effective Weight Area.


Recommended Posts

Posted

Here's another Net app to facilitate the simplification of polyline with lots of segments. I am including the code and a bat file to compile it for Autocad 2017.

 

Once you've compiled  VWSimplifyInteractive.cs into  VWSimplifyInteractive.dll, Netload it into autocad.  New comand  VWSimplifyInteractive will now be available to trhe command line or Autolisp.  

If you run the command you will be asked to select a polyline. The selected poly will be copied in Magenta,then a dialog box with 3 sliders will appear to let you vary the parameter of the simplification.  The polyline is simplified in real-time and you can adjust the parameters until you Accept or Cancel.

 

Note that your original polyline remains untouched and that the simplified one is place on layer "Simplified Poly" with color Green. once you press Accept.

 

Here's the C# code:

// VWSimplifyInteractive.cs
// Interactive Visvalingam + Zhou-Jones for AutoCAD 2017

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.Runtime;
using AcApp = Autodesk.AutoCAD.ApplicationServices.Application;

[assembly: CommandClass(typeof(VWSimplify.InteractiveCommands))]

namespace VWSimplify
{
    public class InteractiveCommands
    {
        [CommandMethod("VWSimplifyInteractive", CommandFlags.Modal | CommandFlags.Session)]
        public void Run()
        {
            Document doc = AcApp.DocumentManager.MdiActiveDocument;
            if (doc == null) return;
            Editor ed = doc.Editor;

            PromptEntityOptions peo = new PromptEntityOptions("\nSelect polyline to simplify: ");
            peo.SetRejectMessage("\nOnly LWPOLYLINE allowed.");
            peo.AddAllowedClass(typeof(Polyline), true);
            PromptEntityResult per = ed.GetEntity(peo);
            if (per.Status != PromptStatus.OK) return;

            ObjectId srcId = per.ObjectId;

            // Create and show the modeless form
            var form = new SimplifyForm(srcId);
            AcApp.ShowModelessDialog(form);
        }
    }

    public class SimplifyForm : Form
    {
        private ObjectId _srcId;
        private ObjectId _previewId = ObjectId.Null;

        private TrackBar tbPercent;
        private CheckBox chkFlat, chkSkew, chkConv;
        private TrackBar tbFlat, tbSkew, tbConv;
        private Label lblPercent, lblVertices, lblFlat, lblSkew, lblConv;
        private Button btnAccept, btnCancel;

        // Current parameters
        private double _percent = 15.0;
        private bool _useFlat = true;
        private bool _useSkew = false;
        private bool _useConv = true;
        private double _flatStrength = 1.5;   // reasonable start
        private double _skewStrength = 1.0;
        private double _convStrength = 2.5;   // reasonable start

        public SimplifyForm(ObjectId srcId)
        {
            _srcId = srcId;
            InitializeComponent();

            this.Shown += SimplifyForm_Shown;
        }

        private void SimplifyForm_Shown(object sender, EventArgs e)
        {
            UpdatePreview();   // First preview once form is displayed
        }

        private void InitializeComponent()
        {
            this.Text = "VW Simplify – Interactive";
            this.Size = new Size(380, 445);
            this.FormBorderStyle = FormBorderStyle.FixedDialog;
            this.MaximizeBox = false;
            this.StartPosition = FormStartPosition.CenterScreen;

            int y = 15;

            // Percentage
            lblPercent = new Label() { Left = 15, Top = y, Width = 330, Text = "Target Ratio: 15%" };
            tbPercent = new TrackBar()
            {
                Left = 15, Top = y + 20, Width = 330,
                Minimum = 1, Maximum = 100, Value = 15,
                TickFrequency = 5
            };
            tbPercent.ValueChanged += (s, e) =>
            {
                _percent = tbPercent.Value;
                lblPercent.Text = "Target Ratio: " + _percent + "%";
                UpdatePreview();
            };
            y += 65;

            // Vertex Counter Label
            lblVertices = new Label() { Left = 15, Top = y, Width = 330, Text = "Vertices: Original: - | Current: - (-%)", ForeColor = Color.DarkBlue };
            y += 25;

            // Flatness
            chkFlat = new CheckBox() { Left = 15, Top = y, Text = "Use Flatness weight", Checked = true, Width = 200 };
            chkFlat.CheckedChanged += (s, e) => { _useFlat = chkFlat.Checked; tbFlat.Enabled = _useFlat; UpdatePreview(); };
            y += 25;
            lblFlat = new Label() { Left = 30, Top = y, Width = 300, Text = "Flatness strength: 1.5" };
            tbFlat = new TrackBar()
            {
                Left = 30, Top = y + 20, Width = 300,
                Minimum = 10, Maximum = 40, Value = 15,   // 1.0 – 4.0
                TickFrequency = 5
            };
            tbFlat.ValueChanged += (s, e) =>
            {
                _flatStrength = tbFlat.Value / 10.0;
                lblFlat.Text = "Flatness strength: " + _flatStrength.ToString("0.0");
                UpdatePreview();
            };
            y += 65;

            // Skewness
            chkSkew = new CheckBox() { Left = 15, Top = y, Text = "Use Skewness weight", Checked = false, Width = 200 };
            chkSkew.CheckedChanged += (s, e) => { _useSkew = chkSkew.Checked; tbSkew.Enabled = _useSkew; UpdatePreview(); };
            y += 25;
            lblSkew = new Label() { Left = 30, Top = y, Width = 300, Text = "Skewness strength: 1.0" };
            tbSkew = new TrackBar()
            {
                Left = 30, Top = y + 20, Width = 300,
                Minimum = 10, Maximum = 30, Value = 10,
                Enabled = false
            };
            tbSkew.ValueChanged += (s, e) =>
            {
                _skewStrength = tbSkew.Value / 10.0;
                lblSkew.Text = "Skewness strength: " + _skewStrength.ToString("0.0");
                UpdatePreview();
            };
            y += 65;

            // Convexity
            chkConv = new CheckBox() { Left = 15, Top = y, Text = "Use Convexity weight", Checked = true, Width = 200 };
            chkConv.CheckedChanged += (s, e) => { _useConv = chkConv.Checked; tbConv.Enabled = _useConv; UpdatePreview(); };
            y += 25;
            lblConv = new Label() { Left = 30, Top = y, Width = 300, Text = "Convexity strength: 2.5" };
            tbConv = new TrackBar()
            {
                Left = 30, Top = y + 20, Width = 300,
                Minimum = 10, Maximum = 50, Value = 25,   // 1.0 – 5.0
                TickFrequency = 5
            };
            tbConv.ValueChanged += (s, e) =>
            {
                _convStrength = tbConv.Value / 10.0;
                lblConv.Text = "Convexity strength: " + _convStrength.ToString("0.0");
                UpdatePreview();
            };
            y += 70;

            // Buttons
            btnAccept = new Button() { Text = "Accept", Left = 80, Top = y, Width = 90 };
            btnCancel = new Button() { Text = "Cancel", Left = 190, Top = y, Width = 90 };

            btnAccept.Click += BtnAccept_Click;
            btnCancel.Click += BtnCancel_Click;

            this.Controls.AddRange(new Control[]
            {
                lblPercent, tbPercent,
                lblVertices,
                chkFlat, lblFlat, tbFlat,
                chkSkew, lblSkew, tbSkew,
                chkConv, lblConv, tbConv,
                btnAccept, btnCancel
            });

            this.FormClosing += (s, e) => CleanupPreview(false);
        }

        private void UpdatePreview()
        {
            Document doc = AcApp.DocumentManager.MdiActiveDocument;
            if (doc == null) return;

            int originalCount = 0;
            int simplifiedCount = 0;

            using (DocumentLock dl = doc.LockDocument())
            using (Transaction tr = doc.TransactionManager.StartTransaction())
            {
                Polyline src = tr.GetObject(_srcId, OpenMode.ForRead) as Polyline;
                if (src == null) { tr.Commit(); return; }

                originalCount = src.NumberOfVertices;

                List<Point2d> simplified = Simplify(src,
                    _percent, _useFlat, _useSkew, _useConv,
                    _flatStrength, _skewStrength, _convStrength);

                if (simplified == null || simplified.Count < 2)
                {
                    tr.Commit();
                    return;
                }

                simplifiedCount = simplified.Count;

                BlockTableRecord btr = (BlockTableRecord)tr.GetObject(doc.Database.CurrentSpaceId, OpenMode.ForWrite);

                // Erase old preview polyline if it exists
                if (!_previewId.IsNull && !_previewId.IsErased)
                {
                    DBObject oldObj = tr.GetObject(_previewId, OpenMode.ForWrite, false);
                    if (oldObj != null)
                    {
                        oldObj.Erase();
                    }
                    _previewId = ObjectId.Null;
                }

                // Construct new polyline
                Polyline preview = new Polyline();
                for (int i = 0; i < simplified.Count; i++)
                {
                    preview.AddVertexAt(i, simplified[i], 0, 0, 0);
                }
                preview.Closed = src.Closed && simplified.Count >= 3;
                preview.ColorIndex = 6;          // Magenta
                preview.ConstantWidth = 0;

                btr.AppendEntity(preview);
                tr.AddNewlyCreatedDBObject(preview, true);
                _previewId = preview.ObjectId;

                tr.Commit();
            }

            // Update UI vertex stats
            if (originalCount > 0)
            {
                double reduction = 100.0 * (1.0 - ((double)simplifiedCount / originalCount));
                lblVertices.Text = string.Format("Vertices: Original: {0} | Current: {1} ({2:0.0}% reduced)",
                    originalCount, simplifiedCount, reduction);
            }

            AcApp.DocumentManager.MdiActiveDocument.Editor.Regen();
        }

        private void BtnAccept_Click(object sender, EventArgs e)
        {
            Document doc = AcApp.DocumentManager.MdiActiveDocument;
            if (doc == null) return;

            using (DocumentLock dl = doc.LockDocument())
            using (Transaction tr = doc.TransactionManager.StartTransaction())
            {
                if (!_previewId.IsNull && !_previewId.IsErased)
                {
                    Polyline preview = tr.GetObject(_previewId, OpenMode.ForWrite) as Polyline;
                    if (preview != null)
                    {
                        // Ensure layer exists
                        LayerTable lt = (LayerTable)tr.GetObject(doc.Database.LayerTableId, OpenMode.ForRead);
                        ObjectId layerId;
                        if (!lt.Has("Simplified Poly"))
                        {
                            lt.UpgradeOpen();
                            LayerTableRecord ltr = new LayerTableRecord();
                            ltr.Name = "Simplified Poly";
                            ltr.Color = Autodesk.AutoCAD.Colors.Color.FromColorIndex(
                                Autodesk.AutoCAD.Colors.ColorMethod.ByAci, 3); // green
                            layerId = lt.Add(ltr);
                            tr.AddNewlyCreatedDBObject(ltr, true);
                        }
                        else
                            layerId = lt["Simplified Poly"];

                        preview.LayerId = layerId;
                        preview.Color = Autodesk.AutoCAD.Colors.Color.FromColorIndex(
                            Autodesk.AutoCAD.Colors.ColorMethod.ByLayer, 256);

                        // Add XData with parameters
                        AddXData(preview, tr, doc.Database);

                        _previewId = ObjectId.Null;   // prevent cleanup from erasing it
                    }
                }
                tr.Commit();
            }
            this.Close();
        }

        private void BtnCancel_Click(object sender, EventArgs e)
        {
            CleanupPreview(true);
            this.Close();
        }

        private void CleanupPreview(bool erase)
        {
            if (_previewId.IsNull || _previewId.IsErased) return;

            Document doc = AcApp.DocumentManager.MdiActiveDocument;
            if (doc == null) return;

            using (DocumentLock dl = doc.LockDocument())
            using (Transaction tr = doc.TransactionManager.StartTransaction())
            {
                if (erase)
                {
                    DBObject obj = tr.GetObject(_previewId, OpenMode.ForWrite, false);
                    if (obj != null) obj.Erase();
                }
                tr.Commit();
            }
            _previewId = ObjectId.Null;
        }

        private void AddXData(Polyline pl, Transaction tr, Database db)
        {
            RegAppTable rat = (RegAppTable)tr.GetObject(db.RegAppTableId, OpenMode.ForRead);
            if (!rat.Has("VW_SIMPLIFY"))
            {
                rat.UpgradeOpen();
                RegAppTableRecord reg = new RegAppTableRecord();
                reg.Name = "VW_SIMPLIFY";
                rat.Add(reg);
                tr.AddNewlyCreatedDBObject(reg, true);
            }

            ResultBuffer rb = new ResultBuffer(
                new TypedValue(1001, "VW_SIMPLIFY"),
                new TypedValue(1000, "Visvalingam-ZhouJones"),
                new TypedValue(1040, _percent),
                new TypedValue(1071, _useFlat ? 1 : 0),
                new TypedValue(1040, _flatStrength),
                new TypedValue(1071, _useSkew ? 1 : 0),
                new TypedValue(1040, _skewStrength),
                new TypedValue(1071, _useConv ? 1 : 0),
                new TypedValue(1040, _convStrength)
            );
            pl.XData = rb;
        }

        //--------------------------------------------------------------------
        // Simplification core with Zhou-Jones weights
        //--------------------------------------------------------------------
        private List<Point2d> Simplify(Polyline src, double percent,
            bool useFlat, bool useSkew, bool useConv,
            double flatStr, double skewStr, double convStr)
        {
            int n = src.NumberOfVertices;
            if (n < 3) return null;

            List<Point2d> pts = new List<Point2d>(n);
            for (int i = 0; i < n; i++)
                pts.Add(src.GetPoint2dAt(i));

            bool closed = src.Closed;
            if (closed && !pts[0].IsEqualTo(pts[pts.Count - 1], new Tolerance(1e-10, 1e-10)))
                pts.Add(pts[0]);

            int target = (int)Math.Max(2, Math.Round(n * percent / 100.0));
            if (closed) target = Math.Max(target, 3);

            return VisvalingamZhouJones(pts, target, useFlat, useSkew, useConv, flatStr, skewStr, convStr);
        }

        private List<Point2d> VisvalingamZhouJones(List<Point2d> pts, int target,
            bool useFlat, bool useSkew, bool useConv,
            double flatStr, double skewStr, double convStr)
        {
            int n = pts.Count;
            if (n <= target) return new List<Point2d>(pts);

            Point2d[] points = pts.ToArray();
            double[] areas = new double[n];
            bool[] alive = new bool[n];
            int[] prev = new int[n];
            int[] next = new int[n];

            for (int i = 0; i < n; i++)
            {
                alive[i] = true;
                prev[i] = i - 1;
                next[i] = i + 1;
                areas[i] = double.MaxValue;
            }
            prev[0] = -1;
            next[n - 1] = -1;

            var heap = new SortedSet<HeapItem>();

            for (int i = 1; i < n - 1; i++)
            {
                double a = WeightedArea(points, i, prev, next, useFlat, useSkew, useConv, flatStr, skewStr, convStr);
                areas[i] = a;
                heap.Add(new HeapItem(a, i));
            }

            double lastArea = 0.0;
            int live = n;

            while (heap.Count > 0 && live > target)
            {
                HeapItem item = heap.Min;
                heap.Remove(item);
                int idx = item.Index;

                if (!alive[idx] || Math.Abs(item.Area - areas[idx]) > 1e-12) continue;

                double a = Math.Max(item.Area, lastArea);
                lastArea = a;
                areas[idx] = a;
                alive[idx] = false;
                live--;

                int p0 = prev[idx];
                int p2 = next[idx];
                if (p0 >= 0) next[p0] = p2;
                if (p2 >= 0) prev[p2] = p0;

                if (p0 >= 0 && alive[p0] && prev[p0] >= 0)
                {
                    double na = WeightedArea(points, p0, prev, next, useFlat, useSkew, useConv, flatStr, skewStr, convStr);
                    if (na < lastArea) na = lastArea;
                    areas[p0] = na;
                    heap.Add(new HeapItem(na, p0));
                }
                if (p2 >= 0 && alive[p2] && next[p2] >= 0)
                {
                    double na = WeightedArea(points, p2, prev, next, useFlat, useSkew, useConv, flatStr, skewStr, convStr);
                    if (na < lastArea) na = lastArea;
                    areas[p2] = na;
                    heap.Add(new HeapItem(na, p2));
                }
            }

            List<Point2d> res = new List<Point2d>();
            for (int i = 0; i < n; i++)
                if (alive[i]) res.Add(points[i]);
            return res;
        }

        private double WeightedArea(Point2d[] pts, int i, int[] prev, int[] next,
            bool useFlat, bool useSkew, bool useConv,
            double flatStr, double skewStr, double convStr)
        {
            Point2d a = pts[prev[i]];
            Point2d b = pts[i];
            Point2d c = pts[next[i]];

            double ea = TriangleArea(a, b, c);
            if (ea < 1e-20) return 0.0;

            double wFlat = 1.0, wSkew = 1.0, wConv = 1.0;

            // Base length and height
            double baseLen = a.GetDistanceTo(c);
            if (baseLen < 1e-12) return ea;

            // Height
            double area2 = Math.Abs((b.X - a.X) * (c.Y - a.Y) - (b.Y - a.Y) * (c.X - a.X));
            double height = area2 / baseLen;

            // ---- Flatness ----
            if (useFlat)
            {
                double ratio = height / baseLen;
                wFlat = Math.Pow(Math.Max(ratio, 1e-6), flatStr);
            }

            // ---- Skewness (prefer isosceles) ----
            if (useSkew)
            {
                double d1 = a.GetDistanceTo(b);
                double d2 = b.GetDistanceTo(c);
                double maxd = Math.Max(d1, d2);
                double mind = Math.Min(d1, d2);
                double skewRatio = (maxd > 1e-12) ? mind / maxd : 1.0;
                wSkew = Math.Pow(skewRatio, skewStr);
            }

            // ---- Convexity ----
            if (useConv)
            {
                // Cross product sign to determine turn direction
                double cross = (b.X - a.X) * (c.Y - a.Y) - (b.Y - a.Y) * (c.X - a.X);
                if (cross > 0)
                    wConv = convStr;          // boost convex
                else
                    wConv = 1.0;              // concave stays normal
            }

            return ea * wFlat * wSkew * wConv;
        }

        private double TriangleArea(Point2d a, Point2d b, Point2d c)
        {
            return 0.5 * Math.Abs(a.X * (b.Y - c.Y) + b.X * (c.Y - a.Y) + c.X * (a.Y - b.Y));
        }

        private class HeapItem : IComparable<HeapItem>
        {
            public double Area;
            public int Index;
            public HeapItem(double area, int index) { Area = area; Index = index; }
            public int CompareTo(HeapItem other)
            {
                int cmp = Area.CompareTo(other.Area);
                return cmp != 0 ? cmp : Index.CompareTo(other.Index);
            }
        }
    }
}

 

Bat file to compile for Acad 2017, From a powershell prompt issue:  .\Build VWSimplifyInteractive.cs

@echo off
if "%~1"=="" (
    echo Error: No source file specified.
    echo Usage: build.bat SourceFileName
    exit /b 1
)

set CSC="C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe"
set ACAD=C:\Program Files\Autodesk\AutoCAD 2017

%CSC% /target:library /platform:x64 /out:"%~1.dll" "/r:%ACAD%\AcCoreMgd.dll" "/r:%ACAD%\AcDbMgd.dll" "/r:%ACAD%\AcMgd.dll" "%~1.cs"

if %ERRORLEVEL% equ 0 (
    echo.
    echo Successfully built %~1.dll
) else (
    echo.
    echo Compilation failed.
)

image.thumb.png.26288d05b4421ca9c30320a786a81d4d.png

VWSimplifyInteractive.cs Build.bat

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Unfortunately, your content contains terms that we do not allow. Please edit your content to remove the highlighted words below.
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...