private FollowingTitle getFollowingTitle(Graphics g, LinePath part, double length, string label, TitleStyle titleStyle, BoundingRectangle viewBox, double scaleFactor) { StringFormat format = StringFormat.GenericTypographic; SizeF sizeF; PointF zeroPoint = new PointF(0, 0); using (Font f = titleStyle.GetFont()) { sizeF = g.MeasureString(label, f, zeroPoint, format); // label length must be less than the length of the line if (sizeF.Width / scaleFactor < length) { LinePath tempPart = new LinePath(); foreach (ICoordinate p in part.Vertices) tempPart.Vertices.Add(p); int vertexNumber = 0; ICoordinate centerPoint = getDistantPoint(tempPart.Vertices, length / 2, out vertexNumber); // if the point of the proposed mid-label misses the display area of the map, the inscription does not appear if (!viewBox.ContainsPoint(centerPoint)) return null; // simplify the line tempPart.Weed(sizeF.Height / scaleFactor / 2); //get the center point of the simplified line centerPoint = getDistantPoint(tempPart.Vertices, length / 2, out vertexNumber); List<double> leftPointsRotationDeltas = new List<double>(); List<double> rightPointsRotationDeltas = new List<double>(); // coordinates of points on the left of the middle of the inscription IList<ICoordinate> leftPoints = getLeftPoints(tempPart.Vertices, centerPoint, sizeF.Width / 2 / scaleFactor, vertexNumber, sizeF.Height / 2 / scaleFactor, leftPointsRotationDeltas); // coordinates of the points to the right of the middle of the inscription IList<ICoordinate> rightPoints = getRightPoints(tempPart.Vertices, centerPoint, sizeF.Width / 2 / scaleFactor, vertexNumber, sizeF.Height / 2 / scaleFactor, rightPointsRotationDeltas); //coordinates of the vertices of the broken line, which will be located along the inscription List<ICoordinate> points = leftPoints.ToList(); points.AddRange(rightPoints); // shifts of the inscriptions associated with break lines List<double> rotationDeltas = leftPointsRotationDeltas; rotationDeltas.AddRange(rightPointsRotationDeltas); for (int i = 0; i < points.Count; i++) points[i] = PlanimetryEnvironment.NewCoordinate((points[i].X - viewBox.MinX) * scaleFactor, (viewBox.MaxY - points[i].Y) * scaleFactor); for (int i = 0; i < rotationDeltas.Count; i++) rotationDeltas[i] = rotationDeltas[i] * scaleFactor; //determine the direction of following labels (direct or reverse) double forwardWeight = 0; double backwardWeight = 0; for (int i = 1; i < points.Count; i++) { Segment s = new Segment(PlanimetryEnvironment.NewCoordinate(points[i].X, points[i].Y), PlanimetryEnvironment.NewCoordinate(points[i - 1].X, points[i - 1].Y)); int quadNumber = pointQuadrantNumber(PlanimetryEnvironment.NewCoordinate(s.V1.X - s.V2.X, s.V1.Y - s.V2.Y)); if (quadNumber == 1 || quadNumber == 4) forwardWeight += s.Length(); else backwardWeight += s.Length(); } if (backwardWeight > forwardWeight) { points.Reverse(); rotationDeltas.Reverse(); } // inscriptions along the route should not be a large number of points if (label.Length > points.Count - 2) { List<int> subStringLengths = new List<int>(); List<double> deltas = new List<double>(); LinePath p1 = new LinePath(points.ToArray()); double l = p1.Length(); // partition of the inscription on the straight parts, the calculation of displacement int startIndex = 0; for (int i = 1; i < points.Count; i++) { double currentDistance = PlanimetryAlgorithms.Distance(points[i - 1], points[i]); if (deltas.Count > 0) if (deltas[deltas.Count - 1] < currentDistance) currentDistance -= deltas[deltas.Count - 1]; //subtract the offset associated with line breaks currentDistance -= rotationDeltas[i - 1]; if (i < rotationDeltas.Count) currentDistance -= rotationDeltas[i]; int currentLength = (int)(currentDistance / l * label.Length); if (startIndex + currentLength > label.Length) currentLength = label.Length - startIndex; subStringLengths.Add(currentLength > 0 ? currentLength : 0); string subString; if (subStringLengths[i - 1] > 0) subString = label.Substring(startIndex, subStringLengths[i - 1]); else subString = string.Empty; float width1, width2, width3; width1 = width2 = width3 = g.MeasureString(subString, f, zeroPoint, format).Width; if (!string.IsNullOrEmpty(subString)) { if (subStringLengths[i - 1] > 1) { width2 = g.MeasureString(label.Substring(startIndex, subStringLengths[i - 1] - 1), f, zeroPoint, format).Width; if (Math.Abs(width2 - currentDistance) < Math.Abs(width1 - currentDistance)) { subStringLengths[i - 1] = subStringLengths[i - 1] - 1; width1 = width2; } } if (label.Length > subStringLengths[i - 1] + startIndex) { width3 = g.MeasureString(label.Substring(startIndex, subStringLengths[i - 1] + 1), f, zeroPoint, format).Width; if (Math.Abs(width3 - currentDistance) < Math.Abs(width1 - currentDistance) && Math.Abs(width3 - currentDistance) < sizeF.Width / label.Length / 6) { subStringLengths[i - 1] = subStringLengths[i - 1] + 1; width1 = width3; } } } deltas.Add(0.5 * (width1 - currentDistance)); if (currentLength > 0) startIndex += currentLength; } int sum = 0; int maxZeroLengths = 0; int zeroLengthsCount = 0; foreach (int k in subStringLengths) { if (k <= 0) { zeroLengthsCount++; if (maxZeroLengths < zeroLengthsCount) maxZeroLengths = zeroLengthsCount; } else zeroLengthsCount = 0; sum += k; } if (maxZeroLengths > 1) return null; int lastIndex = subStringLengths.Count() - 1; if (lastIndex >= 0) { subStringLengths[lastIndex] += label.Length - sum; if (subStringLengths[lastIndex] < 0) { subStringLengths[lastIndex - 1] -= subStringLengths[lastIndex]; subStringLengths[lastIndex] = 0; } } FollowingTitle followingTitle = new FollowingTitle(); startIndex = 0; double? previousAngle = null; for (int i = 0; i < subStringLengths.Count(); i++) { if (subStringLengths[i] <= 0) continue; if (startIndex + subStringLengths[i] > label.Length) return null; SizeF size; size = g.MeasureString(label.Substring(startIndex, subStringLengths[i]), f, zeroPoint, format); int x0 = (i == 0 ? 0 : (int)Math.Round(deltas[i - 1] + rotationDeltas[i - 1])); PointF[] v = new PointF[4]; v[0].X = x0; v[0].Y = -size.Height / 2; v[1].X = size.Width + x0; v[1].Y = -size.Height / 2; v[2].X = size.Width + x0; v[2].Y = size.Height / 2; v[3].X = x0; v[3].Y = size.Height / 2; float angle = (float)(180 / Math.PI * getAngle(PlanimetryEnvironment.NewCoordinate(Math.Abs(points[i].X * 2), points[i].Y), points[i], points[i + 1], false)); if (previousAngle != null) { double angleDelta = Math.Abs(angle - previousAngle.Value); if (angleDelta > 45 && 360 - angleDelta > 45) return null; } previousAngle = angle; g.TranslateTransform((float)points[i].X, (float)points[i].Y); g.RotateTransform(-(angle % 360)); g.Transform.TransformPoints(v); for (int j = 0; j < 4; j++) { v[j].X = (float)(v[j].X / scaleFactor + viewBox.MinX); v[j].Y = (float)(viewBox.MaxY - v[j].Y / scaleFactor); } FollowingTitleElement element = new FollowingTitleElement(new PointF((float)points[i].X, (float)points[i].Y), -(angle % 360), new PointF(x0, -size.Height / 2), label.Substring(startIndex, subStringLengths[i]), v[0], v[1], v[2], v[3]); startIndex += subStringLengths[i]; followingTitle.AddElement(element); g.ResetTransform(); } return followingTitle.EnvelopePolygon == null ? null : followingTitle; } } } return null; }
private void addTitleBufferElement(Graphics g, Feature feature, TitleStyle titleStyle, BoundingRectangle viewBox, double scaleFactor) { ICoordinate targetCoordinate = PlanimetryEnvironment.NewCoordinate(0, 0); Segment s; SizeF size; using (Font f = titleStyle.GetFont()) size = g.MeasureString(feature.Title, f, new PointF(0, 0), _titleStringFormat); switch (feature.FeatureType) { case FeatureType.Polyline: if (!titleStyle.LeadAlong) { foreach (LinePath path in feature.Polyline.Paths) { if (path.Vertices.Count > 2) targetCoordinate = path.Vertices[path.Vertices.Count / 2 - 1]; else { s = new Segment(path.Vertices[0].X, path.Vertices[0].Y, path.Vertices[1].X, path.Vertices[1].Y); targetCoordinate = s.Center(); } addTitleBufferElement(titleStyle, targetCoordinate, size, scaleFactor, feature); } } else { int i = 0; foreach (LinePath path in feature.Polyline.Paths) { FollowingTitle followingTitle = getFollowingTitle(g, path, feature.PolylinePartLengths[i], feature.Title, titleStyle, viewBox, scaleFactor); if (followingTitle != null) _titleBuffer.Add(new TitleBufferElement(followingTitle, titleStyle, _titleCount++)); i++; } } return; case FeatureType.Polygon: //if (feature.Polygon.Contours.Count > 0) // targetPoint = feature.Polygon.Contours[0].RibsCentroid(); //else // return; //break; if (feature.Polygon.Contours.Count > 0) try { targetCoordinate = feature.Polygon.PointOnSurface(); } catch(InvalidOperationException) { //interior point of the polygon for some reason (usually singular) can not be found return; } else return; break; case FeatureType.Point: targetCoordinate = feature.Point.Coordinate; //targetPoint.Y += size.Height / scaleFactor / 2; break; case FeatureType.MultiPoint: if (titleStyle.LeadAlong) { foreach (ICoordinate p in feature.MultiPoint.Points) { targetCoordinate = p; targetCoordinate.Y += size.Height / scaleFactor / 2; addTitleBufferElement(titleStyle, targetCoordinate, size, scaleFactor, feature); } return; } else targetCoordinate = PlanimetryAlgorithms.GetCentroid(feature.MultiPoint.Points); break; } addTitleBufferElement(titleStyle, targetCoordinate, size, scaleFactor, feature); }