Question

So I have an array of points (x, y) that form a path. I need to calculate the points of a surrounding polygon. Essentially CGPathCreateCopyByStrokingPath if you're familiar with iOS. Unfortunately, ObjC is not an option. I need to implement it either in Javascript, PHP, etc.

    -------
    |  |  |
    |  |  |
    \  \  \
     \  \  \
      \  \  \
       \  \  \
        \  \  \-----------
         \  \----------- |
          \---------------

Apologize for the bad ascii art.

I have a semi-working version in Javascript, but I have issues with corners.

        function pathToPoly(points) {
            var numOfPoints = points.length;

            var fullPath = [];
            var leftPaths = [];
            var rightPaths = [];

            var pad = 20;

            for(var i=0; i<numOfPoints-1; i++) {
                var pointA = points[i];
                var pointB = points[i+1];

                var slope = (pointB.Y - pointA.Y) / (pointB.X - pointA.X);
                var inverseSlope = -1 / slope;
                var inverseAngle = Math.atan(inverseSlope);

                if(inverseAngle < 0) {
                    leftPaths.push({
                        X1: pointA.X - pad * Math.cos(inverseAngle),
                        Y1: pointA.Y - pad * Math.sin(inverseAngle),
                        X2: pointB.X - pad * Math.cos(inverseAngle),
                        Y2: pointB.Y - pad * Math.sin(inverseAngle)
                    });

                    rightPaths.push({
                        X1: pointA.X + pad * Math.cos(inverseAngle),
                        Y1: pointA.Y + pad * Math.sin(inverseAngle),
                        X2: pointB.X + pad * Math.cos(inverseAngle),
                        Y2: pointB.Y + pad * Math.sin(inverseAngle)
                    });
                } else {
                    rightPaths.push({
                        X1: pointA.X - pad * Math.cos(inverseAngle),
                        Y1: pointA.Y - pad * Math.sin(inverseAngle),
                        X2: pointB.X - pad * Math.cos(inverseAngle),
                        Y2: pointB.Y - pad * Math.sin(inverseAngle)
                    });

                    leftPaths.push({
                        X1: pointA.X + pad * Math.cos(inverseAngle),
                        Y1: pointA.Y + pad * Math.sin(inverseAngle),
                        X2: pointB.X + pad * Math.cos(inverseAngle),
                        Y2: pointB.Y + pad * Math.sin(inverseAngle)
                    });
                }

                if(drawSides) {
                    var leftSide = leftPaths[i];
                    var rightSide = rightPaths[i];

                    context.beginPath();

                    context.moveTo(leftSide.X1, leftSide.Y1);
                    context.lineTo(leftSide.X2, leftSide.Y2);

                    context.lineWidth = 1;
                    context.strokeStyle = 'yellow';
                    context.stroke();

                    context.beginPath();

                    context.moveTo(rightSide.X1, rightSide.Y1);
                    context.lineTo(rightSide.X2, rightSide.Y2);

                    context.lineWidth = 1;
                    context.strokeStyle = 'cyan';
                    context.stroke();
                }
            }

            for(var i=0; i<numOfPoints-1; i++) {
                var line1 = leftPaths[i];
                var line2 = leftPaths[i+1];

                fullPath.push({
                    X: line1.X1,
                    Y: line1.Y1
                });
                fullPath.push({
                    X: line1.X2,
                    Y: line1.Y2,
                });

                if(line2) {
                    fullPath.push({
                        X: line2.X1,
                        Y: line2.Y1,
                    });
                }
            }

            fullPath.push({
                X: leftPaths[numOfPoints-2].X2,
                Y: leftPaths[numOfPoints-2].Y2
            });

            for(var i=numOfPoints-2; i>=0; i--) {
                var line1 = rightPaths[i];
                var line2 = rightPaths[i-1];

                fullPath.push({
                    X: line1.X2,
                    Y: line1.Y2
                });

                fullPath.push({
                    X: line1.X1,
                    Y: line1.Y1,
                });
                if(line2) {
                    fullPath.push({
                        X: line2.X2,
                        Y: line2.Y2,
                    });
                }
            }

            fullPath.push({
                X: rightPaths[0].X1,
                Y: rightPaths[0].Y1
            });

            return fullPath;
        }

This code draws parallel lines on each side of every segment, and connects them. But for turns, my methods creates an INVALID polygon. The sides "swap" and generate an "inverse" area. And being able to calculate the area is crucial for my application.

Example (my reputation is too low to post images): The blue works fine, but the red fails (notice how the bottom right turn, the sides swap).

![example](http://www.originalfunction.com/stackoverflow_16590082-1.png)

Any suggestions?

Was it helpful?

Solution

Looks like it fails when and only when the line bends at an acute angle (based on a small sample), correct?

I haven't gone thru the algorithm thoroughly, but just an educated guess: I wonder if you've taken into account the fact that for atan(slope), there are two right answers, and the javascript atan() function may not be returning the answer you're expecting (180 degrees off)?

atan2(y, x) eliminates this ambiguity, if you can use it.

OTHER TIPS

You can use Javascript Clipper for offsetting polygons (closed path). To offset a polyline (open path), you can use this code. There is a demo, where you can test offsetting polygons.

enter image description here enter image description here

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top