### Author Topic: centroid of closed polyline  (Read 22556 times)

0 Members and 1 Guest are viewing this topic.

#### Chumplybum

• Newt
• Posts: 97
##### centroid of closed polyline
« on: November 05, 2008, 10:10:45 PM »
hi all, i'm trying to find the centroid of a closed polyline.

i've read a few lisp examples that can do this (including one on the swamp http://www.theswamp.org/index.php?topic=18725.0) but haven't been able to transform this code to .net. From the lisp examples it seems that the polyline is converted to a region and then the centriod is found from the region, however from what i can see there is no centroid property (although there is a centroid property under solid3dmassproperties).
So i couldn't convert lisp to .net and went off and found the formula for the centroid of a polyline (http://en.wikipedia.org/wiki/Centroid) as well as a vb.net sample (http://www.vb-helper.com/howto_net_polygon_centroid.html) after slightly modifying the code i've come up with the following...

Code: [Select]
`        ' For polylines         Dim pl As AADS.Polyline = TryCast(context.PickedObject, AADS.Polyline)        If pl Is Nothing OrElse pl.Closed = False Then Return        Dim RunningX As Long        Dim RunningY As Long        Dim second_factor As Long        For Counter As Integer = 0 To pl.NumberOfVertices - 2            second_factor = pl.GetPoint2dAt(Counter).X * pl.GetPoint2dAt(Counter + 1).Y - pl.GetPoint2dAt(Counter + 1).X * pl.GetPoint2dAt(Counter).Y            RunningX += (pl.GetPoint2dAt(Counter).X + pl.GetPoint2dAt(Counter + 1).X) * second_factor            RunningY += (pl.GetPoint2dAt(Counter).Y + pl.GetPoint2dAt(Counter + 1).Y) * second_factor        Next        Dim X, Y As Long        ' Divide by 6 times the polygon's area.        Dim polygon_area As Single = pl.Area        X = RunningX / (6 * pl.Area)        Y = RunningY / (6 * pl.Area)        ' If the values are negative, the polygon is        ' oriented counterclockwise. Reverse the signs.        If X < 0 Then            X = -X            Y = -Y        End If        Dim CentroidPoint As AAG.Point3d = New AAG.Point3d(X, Y, pl.Elevation)`
the code works for a rectangle that is drawn at 0,0... however, when the polyline moves away from 0,0 the code produces some crazy results and if the polyline is in a different quadrant about the usc it produces some more crazy results

does anyone know of a better / easier way to find the centroid, or a problem with my code???

any help would be great

cheers, Mark

#### pkohut

• Guest
##### Re: centroid of closed polyline
« Reply #1 on: November 06, 2008, 01:55:02 AM »
You have to do the calculations relative to the object you're trying to measure.  The code would
look something like this (sorry I don't have VB skills)
Code: [Select]
`dim ptRef as Point2ddim pt1 as Point2ddim pt2 as Point2ddim second_factor as doubledim drX as doubledim drY as doubleFor Counter as Integer = 0 To pl.NumberOfVertices - 2  pt1 = pl.GetPoint2dAt(Counter)  Pt2 = pl.GetPoint2dAt(counter + 1)  if i == 0 then      ptRef = pt1  endif  second_factor = (pt1.x - ptRef.x) * (pt2.y - ptRef.y) - (pt2.x - ptRef.x) * (pt1.y - ptRef.y);  drX += ((pt1.x - ptRef.x) + (pt2.x - ptRef.x)) * b0;  drY += ((pt1.y - ptRef.y) + (pt2.y - ptRef.y)) * b0;Nextx = drX / (6 * pl.area) + ptRef.xy = drY / (6 * pl.area) + ptRef.y`
With this there is no need to reverse sign at the end.  Your example code is declaring some variables
as Long when they should be doubles (or whatever VB calls them).  Was your code returning
a float or double for those computations?  If they are then I would turn on the Option strict to get a
little tighter control over the VB.

Paul

#### Cathy

• Guest
##### Re: centroid of closed polyline
« Reply #2 on: November 06, 2008, 03:01:21 PM »
It looks to me like you are missing one side of the polygon -- the one from the last point to the first point.  I note that the sample code at http://www.vb-helper.com/howto_net_polygon_centroid.html makes a copy of the first point at the end, but I don't see where you do that.

#### SomeCallMeDave

• Guest
##### Re: centroid of closed polyline
« Reply #3 on: November 06, 2008, 03:27:18 PM »
You could also used TF.net  http://code.google.com/p/tf-net/ and let it do the work for you.

C# code, but I'm sure you can convert it
Code: [Select]
`using System;using System.Linq;using System.Text;using Autodesk.AutoCAD.ApplicationServices;using Autodesk.AutoCAD.Runtime;using Autodesk.AutoCAD.DatabaseServices;using Autodesk.AutoCAD.EditorInput;using Autodesk.AutoCAD.Geometry;using Topology.Geometries;using Topology.IO.Dwg;namespace TF_Test{    public class TF_Test    {        [CommandMethod("TFCentroid")]        public void FindCentroid()        {            {                Document doc = Application.DocumentManager.MdiActiveDocument;                Database db = doc.Database;                Editor ed = doc.Editor;                PromptEntityOptions opts = new PromptEntityOptions("\nSelect Entity to Query: ");                opts.AllowNone = true;                opts.AllowObjectOnLockedLayer = true;                opts.SetRejectMessage("\nDon't pick that kind ");                opts.AddAllowedClass(typeof(Polyline), false);                //Select the object on-screen                PromptEntityResult res = ed.GetEntity(opts);                if (res.Status == PromptStatus.OK)                {                    ObjectId ObjID = res.ObjectId;                    try                    {                        using (Transaction trans = db.TransactionManager.StartTransaction())                        {                            Entity ent = (Entity)trans.GetObject(ObjID, OpenMode.ForRead, false);                            DwgReader reader = new DwgReader();                            DwgWriter writer = new DwgWriter();                            Geometry geometry = (Geometry)reader.ReadGeometry(ent);                            IPoint centroid = geometry.Centroid;                            ed.WriteMessage("\nCentroid is " + centroid.ToString());                        }//using                    }//end try                    catch (System.Exception ex)                    {                        ed.WriteMessage("Error Reading Centroid: " + ex.Message);                    }                }// if            }        }`

#### Chumplybum

• Newt
• Posts: 97
##### Re: centroid of closed polyline
« Reply #4 on: November 06, 2008, 07:01:47 PM »
thanks pkohut for your help / code...

i've amended my code to suit your changes (see below), however, the code only seems to produce an accurate result if the polyline is drawn counterclockwise (see attached images, the polyline has been mirrod to get counterclockwise vs clockwise)

Code: [Select]
`        imports AADS = AutoDesk.AutoCAD.DatabaseServicesimports AAG = Autodesk.AutoCAD.GeometryPublic Shared Function GetCentroid(ByVal polyline As AADS.Polyline) As AAG.Point3d            Dim ReferencePoint As AAG.Point2d            Dim RunningX As Double            Dim RunningY As Double            Dim second_factor As Double            Dim Point1 As AAG.Point2d            Dim Point2 As AAG.Point2d            For Counter As Integer = 0 To polyline.NumberOfVertices - 2                Point1 = polyline.GetPoint2dAt(Counter)                Point2 = polyline.GetPoint2dAt(Counter + 1)                If Counter = 0 Then ReferencePoint = Point1                second_factor = (Point1.X - ReferencePoint.X) * (Point2.Y - ReferencePoint.Y) - (Point2.X - ReferencePoint.X) * (Point1.Y - ReferencePoint.Y)                RunningX += ((Point1.X - ReferencePoint.X) + (Point2.X - ReferencePoint.X)) * second_factor                RunningY += ((Point1.Y - ReferencePoint.Y) + (Point2.Y - ReferencePoint.Y)) * second_factor            Next            Dim X, Y As Double            X = RunningX / (6 * polyline.Area) + ReferencePoint.X            Y = RunningY / (6 * polyline.Area) + ReferencePoint.Y            Return New AAG.Point3d(X, Y, polyline.Elevation)        End Function`
« Last Edit: November 06, 2008, 07:04:52 PM by Chumplybum »

#### pkohut

• Guest
##### Re: centroid of closed polyline
« Reply #5 on: November 06, 2008, 07:56:41 PM »
The easiest way to fix this is to keep a running total of second_factor during the loop.  Then
once out of the loop if the summation is negative then you'll need to multiply your X and Y
calculations with -1.0.  FYI, the area of the polyline is the abs(summation) * 0.5   (straight
line segments only).

Cathy mentions possibly missing the final segment.  You'll need to check if the first and last
vertex are the same or not.  You already know the polyline is closed, so the final segment
may be interpolated across the final vertex to the first vertex, and you would miss that.

Paul

#### pkohut

• Guest
##### Re: centroid of closed polyline
« Reply #6 on: November 06, 2008, 08:26:21 PM »
Need to correct a sentence in my last post from:
Quote
Then
once out of the loop if the summation is negative then you'll need to multiply your X and Y
calculations with -1.0.

to:

Quote
Then once out of the loop if the summation is negative then you'll need to multiply
RunningX and RunningY with -1.0.  Here's an example:

dim dVal as double
dVal = 1.0
if summation < 0.0 then dVal = -1.0
X = (RunningX * dVal) / (6 * polyline.Area) + ReferencePoint.X
Y = (RunningY * dVal) / (6 * polyline.Area) + ReferencePoint.Y

Paul

#### Chumplybum

• Newt
• Posts: 97
##### Re: centroid of closed polyline
« Reply #7 on: November 06, 2008, 08:48:31 PM »
awesome... thanks pkohut, that fixed it up

cheers, Mark

#### gile

• Gator
• Posts: 2507
• Marseille, France
##### Re: centroid of closed polyline
« Reply #8 on: November 09, 2008, 01:34:53 PM »
Hi,

It was quite difficult for me (I'm a complete beginer with .NET), but it was a fine exercice.
Here's a translation (C#) of the LISP routines I gave here.

It works with arc polylines.

EDIT: added a testing command and a LISP function.
The "pline_centroid" command draws a point on the selected polyline centroid.
(pline_centroid (car (entsel))) returns the WCS coordinates of the centroid.

Code: [Select]
`using System;using Autodesk.AutoCAD.ApplicationServices;using Autodesk.AutoCAD.DatabaseServices;using Autodesk.AutoCAD.EditorInput;using Autodesk.AutoCAD.Geometry;using Autodesk.AutoCAD.Runtime;namespace PolylineCentroid{    // Main class    public class Centroid    {        public Point3d GetCentroid(Polyline pl)        {            Point2d p0 = pl.GetPoint2dAt(0);            Point2d cen = new Point2d(0.0, 0.0);            double area = 0.0;            double bulge = pl.GetBulgeAt(0);            int last = pl.NumberOfVertices - 1;            double tmpArea;            Point2d tmpPoint;            if (bulge != 0.0)            {                double[] datas = getArcGeom(pl, bulge, 0, 1);                area = datas[0];                cen = new Point2d(datas[1], datas[2]) * datas[0];            }            for (int i = 1; i < last; i++)            {                tmpArea = triangleAlgebricArea(p0, pl.GetPoint2dAt(i), pl.GetPoint2dAt(i + 1));                tmpPoint = triangleCentroid(p0, pl.GetPoint2dAt(i), pl.GetPoint2dAt(i + 1));                cen += (tmpPoint * tmpArea).GetAsVector();                area += tmpArea;                bulge = pl.GetBulgeAt(i);                if (bulge != 0.0)                {                    double[] datas = getArcGeom(pl, bulge, i, i + 1);                    area += datas[0];                    cen += new Vector2d(datas[1], datas[2]) * datas[0];                }            }            bulge = pl.GetBulgeAt(last);            if (bulge != 0.0)            {                double[] datas = getArcGeom(pl, bulge, last, 0);                area += datas[0];                cen += new Vector2d(datas[1], datas[2]) * datas[0];            }            cen = cen.DivideBy(area);            Point3d result = new Point3d(cen.X, cen.Y, pl.Elevation);            return result.TransformBy(Matrix3d.PlaneToWorld(pl.Normal));        }        public double[] GetArcGeom(Polyline pl, double bulge, int index1, int index2)        {            CircularArc2d arc = (pl.GetArcSegment2dAt(index1));            double arcRadius = arc.Radius;            Point2d arcCenter = arc.Center;            double arcAngle = 4.0 * Math.Atan(bulge);            double tmpArea = arcAlgebricArea(arcRadius, arcAngle);            Point2d tmpPoint = arcCentroid(pl.GetPoint2dAt(index1), pl.GetPoint2dAt(index2), arcCenter, tmpArea);            return new double[3] { tmpArea, tmpPoint.X, tmpPoint.Y };        }        public Point2d TriangleCentroid(Point2d p0, Point2d p1, Point2d p2)        {            return (p0 + p1.GetAsVector() + p2.GetAsVector()) / 3.0;        }        public double TriangleAlgebricArea(Point2d p0, Point2d p1, Point2d p2)        {            return (((p1.X - p0.X) * (p2.Y - p0.Y)) - ((p2.X - p0.X) * (p1.Y - p0.Y))) / 2.0;        }        public Point2d ArcCentroid(Point2d start, Point2d end, Point2d cen, double tmpArea)        {            double chord = start.GetDistanceTo(end);            double angle = angleFromTo(start, end);            return polar2d(cen, angle - (Math.PI / 2.0), (chord * chord * chord) / (12.0 * tmpArea));        }        public double ArcAlgebricArea(double rad, double ang)        {            return rad * rad * (ang - Math.Sin(ang)) / 2.0;        }        public double AngleFromTo(Point2d p1, Point2d p2)        {            return (p2 - p1).Angle;        }        public Point2d Polar2d(Point2d org, double angle, double distance)        {            return new Point2d(org.X + distance, org.Y).RotateBy(angle, org);        }    }    // Testing command    public class MyClass    {        [CommandMethod("pline_centroid")]        public void centroid()        {            Document doc = Application.DocumentManager.MdiActiveDocument;            Database db = doc.Database;            Editor ed = doc.Editor;            PromptEntityOptions opts = new PromptEntityOptions("\nSelect a polyline: ");            opts.AllowNone = true;            opts.AllowObjectOnLockedLayer = true;            opts.SetRejectMessage("\nEntité non valide.");            opts.AddAllowedClass(typeof(Polyline), false);            PromptEntityResult pline = ed.GetEntity(opts);            if (pline.Status == PromptStatus.OK)            {                ObjectId ObjID = pline.ObjectId;                try                {                    using (Transaction trans = db.TransactionManager.StartTransaction())                    {                        Polyline ent = (Polyline)trans.GetObject(ObjID, OpenMode.ForRead, false);                        Centroid c = new Centroid();                        Point3d cen = c.GetCentroid(ent);                        DBPoint pt = new DBPoint(cen);                        BlockTable bTable = (BlockTable)trans.GetObject(db.BlockTableId, OpenMode.ForRead);                        BlockTableRecord mSpace = (BlockTableRecord)trans.GetObject(bTable[BlockTableRecord.ModelSpace], OpenMode.ForWrite);                        mSpace.AppendEntity(pt);                        trans.AddNewlyCreatedDBObject(pt, true);                        trans.Commit();                    }                }                catch (System.Exception ex)                {                    ed.WriteMessage("Error: " + ex.Message);                }            }        }    }    // LISP function    public class LispCentoid    {        [LispFunction("pline_centroid")]        public static object centroid(ResultBuffer buff)        {            Document doc = Application.DocumentManager.MdiActiveDocument;            Database db = doc.Database;            Editor ed = doc.Editor;            if (buff == null)            {                ed.WriteMessage("Error: none argument.\n");                return false;            }            TypedValue[] args = buff.AsArray();            if (args[0].TypeCode != (int)LispDataType.ObjectId)            {                ed.WriteMessage("Error: incorrect argument type: lentityp {0}.\n", args[0].Value);                return false;            }            ObjectId objID = (ObjectId)args[0].Value;            try            {                using (Transaction trans = db.TransactionManager.StartTransaction())                {                    Entity ent = (Entity)trans.GetObject(objID, OpenMode.ForRead, false);                    string entType = ent.GetType().ToString().Substring(34);                    if (entType == "Polyline")                    {                        Polyline pl = (Polyline)trans.GetObject(objID, OpenMode.ForRead, false);                        Centroid c = new Centroid();                        return c.GetCentroid(pl);                    }                    else                     {                        if (entType.Contains("DB")) entType = entType.Substring(2);                        ed.WriteMessage("Error: incorrect entity type: " + entType + "\n");                        return null;                    }                }            }            catch (System.Exception ex)            {                ed.WriteMessage("\nError: " + ex.Message + "\n");                return false;            }        }    }}`
« Last Edit: November 15, 2008, 03:48:32 AM by gile »
Speaking English as a French Frog

#### gile

• Gator
• Posts: 2507
• Marseille, France
##### Re: centroid of closed polyline
« Reply #9 on: November 11, 2008, 04:23:36 AM »
I edited the Centroid class code so that some methods, as angleFromTo, polar2d, triangleAlgebricArea, triangleCentroid and so on, which may be usefull are more easily readable.
Speaking English as a French Frog

#### It's Alive!

• Retired
• Needs a day job
• Posts: 8690
• AKA Daniel
##### Re: centroid of closed polyline
« Reply #10 on: November 11, 2008, 08:26:38 AM »
Excellent gile!

#### gile

• Gator
• Posts: 2507
• Marseille, France
##### Re: centroid of closed polyline
« Reply #11 on: November 11, 2008, 09:03:39 AM »
Thanks Daniel..
Speaking English as a French Frog

#### Chumplybum

• Newt
• Posts: 97
##### Re: centroid of closed polyline
« Reply #12 on: November 11, 2008, 05:37:46 PM »
nice one, thanks gile

#### pkohut

• Guest
##### Re: centroid of closed polyline
« Reply #13 on: November 11, 2008, 06:24:45 PM »
Ya Gile looks good.  Since your a self admitted beginner at .Net
may I suggest making all those "int" doubles into doubles
instead of letting the compiler upcast them for you.
return (p0 + p1.GetAsVector() + p2.GetAsVector()) / 3
to
return (p0 + p1.GetAsVector() + p2.GetAsVector()) / 3.0

if(bulge != 0)
to
if(bulge != 0.0)

The compiler will happily up cast for you, but that isn't something
you should let it do.  Make your intent known to the compiler
and it will help you when you make a mistake.  It also helps

Later,
Paul

#### gile

• Gator
• Posts: 2507
• Marseille, France
##### Re: centroid of closed polyline
« Reply #14 on: November 12, 2008, 02:19:52 AM »
Thanks Paul,

It makes sense, I edit the code according to your suggestion.
Speaking English as a French Frog