/// <summary> /// Offset a Linestring the given amount perpendicular to the line /// For example if a line should be drawn 10px to the right of its original position /// /// Positive offset offsets right /// Negative offset offsets left /// </summary> /// <param name="lineCoordinates">LineString</param> /// <param name="offset">offset amount</param> /// <returns>Array of coordinates for the offseted line</returns> public static PointF[] OffsetPolyline(PointF[] lineCoordinates, float offset) { List<PointF> retPoints = new List<PointF>(); PointF old_pt, old_diffdir = PointF.Empty, old_offdir = PointF.Empty; int idx = 0; bool first = true; /* saved metrics of the last processed point */ if (lineCoordinates.Length > 0) { old_pt = lineCoordinates[0]; for (int j = 1; j < lineCoordinates.Length; j++) { PointF pt = lineCoordinates[j]; /* place of the point */ PointF diffdir = point_norm(point_diff(pt, old_pt)); /* direction of the line */ PointF offdir = point_rotz90(diffdir);/* direction where the distance between the line and the offset is measured */ PointF offPt; if (first) { first = false; offPt = point_sum(old_pt, point_mul(offdir, offset)); } else /* middle points */ { /* curve is the angle of the last and the current line's direction (supplementary angle of the shape's inner angle) */ double sin_curve = point_cross(diffdir, old_diffdir); double cos_curve = point_cross(old_offdir, diffdir); if ((-1.0) * CURVE_SIN_LIMIT < sin_curve && sin_curve < CURVE_SIN_LIMIT) { offPt = point_sum(old_pt, point_mul(point_sum(offdir, old_offdir), 0.5 * offset)); } else { double base_shift = -1.0 * (1.0 + cos_curve) / sin_curve; offPt = point_sum(old_pt, point_mul(point_sum(point_mul(diffdir, base_shift), offdir), offset)); } } retPoints.Add(offPt); old_pt = pt; old_diffdir = diffdir; old_offdir = offdir; } /* last point */ if (!first) { PointF offpt = point_sum(old_pt, point_mul(old_offdir, offset)); retPoints.Add(offpt); idx++; } } return retPoints.ToArray(); }
static PointF point_norm(PointF a) { double lenmul; PointF retv = new PointF(); if (a.X == 0 && a.Y == 0) return a; lenmul = 1.0 / Math.Sqrt(point_abs2(a)); /* this seems to be the costly operation */ retv.X = (float)(a.X * lenmul); retv.Y = (float)(a.Y * lenmul); return retv; }
static double point_cross(PointF a, PointF b) { return a.X * b.Y - a.Y * b.X; }
static PointF point_diff(PointF a, PointF b) { PointF retv = new PointF(a.X - b.X, a.Y - b.Y); return retv; }
static PointF point_sum(PointF a, PointF b) { PointF retv = new PointF(a.X + b.X, a.Y + b.Y); return retv; }
/* vector multiply */ static PointF point_mul(PointF a, double b) { PointF retv = new PointF((float)(a.X * b), (float)(a.Y * b)); return retv; }
/* rotate a vector 90 degrees */ static PointF point_rotz90(PointF a) { double nx = -1.0 * a.Y, ny = a.X; PointF retv = new PointF(); retv.X = (float)nx; retv.Y = (float)ny; return retv; }
static double point_abs2(PointF a) { return a.X * a.X + a.Y * a.Y; }