internal static void RenderSymbol(IBitmap symbolBitmap, Display display, int priority, float dy, bool alignCenter, bool repeatSymbol, float repeatGap, float repeatStart, bool rotate, Point[][] coordinates, IList <MapElementContainer> currentItems) { int skipPixels = (int)repeatStart; Point[] c; if (dy == 0f) { c = coordinates[0]; } else { c = RendererUtils.ParallelPath(coordinates[0], dy); } // get the first way point coordinates double previousX = c[0].X; double previousY = c[0].Y; // draw the symbolContainer on each way segment float segmentLengthRemaining; float segmentSkipPercentage; float theta = 0; for (int i = 1; i < c.Length; ++i) { // get the current way point coordinates double currentX = c[i].X; double currentY = c[i].Y; // calculate the length of the current segment (Euclidian distance) double diffX = currentX - previousX; double diffY = currentY - previousY; double segmentLengthInPixel = Math.Sqrt(diffX * diffX + diffY * diffY); segmentLengthRemaining = (float)segmentLengthInPixel; while (segmentLengthRemaining - skipPixels > repeatStart) { // calculate the percentage of the current segment to skip segmentSkipPercentage = skipPixels / segmentLengthRemaining; // move the previous point forward towards the current point previousX += diffX * segmentSkipPercentage; previousY += diffY * segmentSkipPercentage; if (rotate) { // if we do not rotate theta will be 0, which is correct theta = (float)Math.Atan2(currentY - previousY, currentX - previousX); } Point point = new Point(previousX, previousY); currentItems.Add(new SymbolContainer(point, display, priority, symbolBitmap, theta, alignCenter)); // check if the symbolContainer should only be rendered once if (!repeatSymbol) { return; } // recalculate the distances diffX = currentX - previousX; diffY = currentY - previousY; // recalculate the remaining length of the current segment segmentLengthRemaining -= skipPixels; // set the amount of pixels to skip before repeating the symbolContainer skipPixels = (int)repeatGap; } skipPixels -= (int)segmentLengthRemaining; if (skipPixels < repeatStart) { skipPixels = (int)repeatStart; } // set the previous way point coordinates for the next loop previousX = currentX; previousY = currentY; } }
/// <summary> /// Finds the segments of a line along which a name can be drawn and then adds WayTextContainers /// to the list of drawable items. /// </summary> /// <param name="tile"> the tile on which the text will be drawn. </param> /// <param name="text"> the text to draw </param> /// <param name="priority"> priority of the text </param> /// <param name="dy"> if 0, then a line parallel to the coordinates will be calculated first </param> /// <param name="fill"> fill paint for text </param> /// <param name="stroke"> stroke paint for text </param> /// <param name="coordinates"> the list of way coordinates </param> /// <param name="currentLabels"> the list of labels to which a new WayTextContainer will be added </param> internal static void RenderText(Tile tile, string text, Display display, int priority, float dy, IPaint fill, IPaint stroke, Point[][] coordinates, IList <MapElementContainer> currentLabels) { // Calculate the way name length plus some margin of safety int wayNameWidth = (stroke == null) ? fill.GetTextWidth(text) + WAYNAME_SAFETY_MARGIN * 2 : stroke.GetTextWidth(text) + WAYNAME_SAFETY_MARGIN * 2; // Compute the tile boundary on which we render the name. // We make the tile smaller because otherwise we sometimes write the text beyond the tile boundary // (e.g. a road that runs parallel just below a tile boundary) double textHeight = (stroke == null) ? fill.GetTextHeight(text) : stroke.GetTextHeight(text); Rectangle tileBoundary = tile.BoundaryAbsolute.Envelope(-textHeight); int skipPixels = 0; Point[] c; if (dy == 0f) { c = coordinates[0]; } else { c = RendererUtils.ParallelPath(coordinates[0], dy); } // iterate through the segments to find those long enough to draw the way name on them for (int i = 1; i < c.Length; ++i) { LineSegment currentSegment = new LineSegment(c[i - 1], c[i]); double currentLength = currentSegment.Length(); skipPixels -= (int)currentLength; if (skipPixels > 0) { // we should still be skipping pixels, so skip this segment. Note that // this does not guarantee that we skip any certain minimum of pixels, // it is more a rule of thumb. continue; } if (currentLength < wayNameWidth) { // no point trying to clip, the segment is too short anyway continue; } // clip the current segment to the tile, so that we never overlap tile boundaries // with the way name LineSegment drawableSegment = currentSegment.ClipToRectangle(tileBoundary); if (drawableSegment == null) { // this happens if the segment does not intersect the tile continue; } double segmentLengthInPixel = drawableSegment.Length(); if (segmentLengthInPixel < wayNameWidth) { // not enough space to draw name on this segment continue; } // now calculate the actually used part of the segment to ensure the bbox of the waytext container // is as small as possible. The offset at the beginning/end is to ensure that we are a bit off the center // of an intersection (otherwise we have more collisions at the intersection) LineSegment actuallyUsedSegment = drawableSegment.SubSegment(WAYNAME_SAFETY_MARGIN, wayNameWidth - WAYNAME_SAFETY_MARGIN); // check to prevent inverted way names if (actuallyUsedSegment.Start.X <= actuallyUsedSegment.End.X) { currentLabels.Add(new WayTextContainer(actuallyUsedSegment.Start, actuallyUsedSegment.End, display, priority, text, fill, stroke, textHeight)); } else { currentLabels.Add(new WayTextContainer(actuallyUsedSegment.End, actuallyUsedSegment.Start, display, priority, text, fill, stroke, textHeight)); } skipPixels = wayNameWidth; } }