PageRenderTime 63ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 1ms

/GeoRouting/branches/SharpMap/Layers/LabelLayer.cs

http://georouting-hyperpath.googlecode.com/
C# | 608 lines | 373 code | 55 blank | 180 comment | 82 complexity | 38f9051cf58aa2a76928f35a80953c33 MD5 | raw file
Possible License(s): BSD-3-Clause, LGPL-2.1
  1. // Copyright 2005, 2006 - Morten Nielsen (www.iter.dk)
  2. //
  3. // This file is part of SharpMap.
  4. // SharpMap is free software; you can redistribute it and/or modify
  5. // it under the terms of the GNU Lesser General Public License as published by
  6. // the Free Software Foundation; either version 2 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // SharpMap is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU Lesser General Public License for more details.
  13. // You should have received a copy of the GNU Lesser General Public License
  14. // along with SharpMap; if not, write to the Free Software
  15. // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  16. using System;
  17. using System.Collections.Generic;
  18. using System.Drawing;
  19. using System.Drawing.Drawing2D;
  20. using System.Drawing.Text;
  21. using System.Globalization;
  22. #if !DotSpatialProjections
  23. using ProjNet.CoordinateSystems.Transformations;
  24. #else
  25. using DotSpatial.Projections;
  26. #endif
  27. using SharpMap.Data;
  28. using SharpMap.Data.Providers;
  29. using SharpMap.Geometries;
  30. using SharpMap.Rendering;
  31. using SharpMap.Rendering.Thematics;
  32. using SharpMap.Styles;
  33. using Point=SharpMap.Geometries.Point;
  34. using Transform = SharpMap.Utilities.Transform;
  35. namespace SharpMap.Layers
  36. {
  37. /// <summary>
  38. /// Label layer class
  39. /// </summary>
  40. /// <example>
  41. /// Creates a new label layer and sets the label text to the "Name" column in the FeatureDataTable of the datasource
  42. /// <code lang="C#">
  43. /// //Set up a label layer
  44. /// SharpMap.Layers.LabelLayer layLabel = new SharpMap.Layers.LabelLayer("Country labels");
  45. /// layLabel.DataSource = layCountries.DataSource;
  46. /// layLabel.Enabled = true;
  47. /// layLabel.LabelColumn = "Name";
  48. /// layLabel.Style = new SharpMap.Styles.LabelStyle();
  49. /// layLabel.Style.CollisionDetection = true;
  50. /// layLabel.Style.CollisionBuffer = new SizeF(20, 20);
  51. /// layLabel.Style.ForeColor = Color.White;
  52. /// layLabel.Style.Font = new Font(FontFamily.GenericSerif, 8);
  53. /// layLabel.MaxVisible = 90;
  54. /// layLabel.Style.HorizontalAlignment = SharpMap.Styles.LabelStyle.HorizontalAlignmentEnum.Center;
  55. /// </code>
  56. /// </example>
  57. public class LabelLayer : Layer, IDisposable
  58. {
  59. #region Delegates
  60. /// <summary>
  61. /// Delegate method for creating advanced label texts
  62. /// </summary>
  63. /// <param name="fdr">the <see cref="FeatureDataRow"/> to build the label for</param>
  64. /// <returns>the label</returns>
  65. public delegate string GetLabelMethod(FeatureDataRow fdr);
  66. /// <summary>
  67. /// Delegate method for calculating the priority of label rendering
  68. /// </summary>
  69. /// <param name="fdr">the <see cref="FeatureDataRow"/> to compute the priority value from</param>
  70. /// <returns>the priority value</returns>
  71. public delegate int GetPriorityMethod(FeatureDataRow fdr);
  72. #endregion
  73. #region MultipartGeometryBehaviourEnum enum
  74. /// <summary>
  75. /// Labelling behaviour for Multipart geometry collections
  76. /// </summary>
  77. public enum MultipartGeometryBehaviourEnum
  78. {
  79. /// <summary>
  80. /// Place label on all parts (default)
  81. /// </summary>
  82. All,
  83. /// <summary>
  84. /// Place label on object which the greatest length or area.
  85. /// </summary>
  86. /// <remarks>
  87. /// Multipoint geometries will default to <see cref="First"/>
  88. /// </remarks>
  89. Largest,
  90. /// <summary>
  91. /// The center of the combined geometries
  92. /// </summary>
  93. CommonCenter,
  94. /// <summary>
  95. /// Center of the first geometry in the collection (fastest method)
  96. /// </summary>
  97. First
  98. }
  99. #endregion
  100. private IProvider _dataSource;
  101. private GetLabelMethod _getLabelMethod;
  102. private GetPriorityMethod _getPriorityMethod;
  103. /// <summary>
  104. /// Name of the column that holds the value for the label.
  105. /// </summary>
  106. private string _labelColumn;
  107. /// <summary>
  108. /// Delegate for custom Label Collision Detection
  109. /// </summary>
  110. private LabelCollisionDetection.LabelFilterMethod _labelFilter;
  111. /// <summary>
  112. /// A value indicating the labeling technique use in case of MultiPart geometries
  113. /// </summary>
  114. private MultipartGeometryBehaviourEnum _multipartGeometryBehaviour;
  115. /// <summary>
  116. /// A value indication the priority of the label in cases of label-collision detection
  117. /// </summary>
  118. private int _priority;
  119. /// <summary>
  120. /// Name of the column that contains the value indicating the priority of the label in case of label-collision detection
  121. /// </summary>
  122. private string _priorityColumn = "";
  123. private string _rotationColumn;
  124. private SmoothingMode _smoothingMode;
  125. //private LabelStyle _Style;
  126. private TextRenderingHint _textRenderingHint;
  127. private ITheme _theme;
  128. /// <summary>
  129. /// Creates a new instance of a LabelLayer
  130. /// </summary>
  131. public LabelLayer(string layername)
  132. :base(new LabelStyle())
  133. {
  134. //_Style = new LabelStyle();
  135. LayerName = layername;
  136. SmoothingMode = SmoothingMode.AntiAlias;
  137. TextRenderingHint = TextRenderingHint.AntiAlias;
  138. _multipartGeometryBehaviour = MultipartGeometryBehaviourEnum.All;
  139. _labelFilter = LabelCollisionDetection.SimpleCollisionDetection;
  140. }
  141. /// <summary>
  142. /// Gets or sets labelling behavior on multipart geometries
  143. /// </summary>
  144. /// <remarks>Default value is <see cref="MultipartGeometryBehaviourEnum.All"/></remarks>
  145. public MultipartGeometryBehaviourEnum MultipartGeometryBehaviour
  146. {
  147. get { return _multipartGeometryBehaviour; }
  148. set { _multipartGeometryBehaviour = value; }
  149. }
  150. /// <summary>
  151. /// Filtermethod delegate for performing filtering
  152. /// </summary>
  153. /// <remarks>
  154. /// Default method is <see cref="SharpMap.Rendering.LabelCollisionDetection.SimpleCollisionDetection"/>
  155. /// </remarks>
  156. public LabelCollisionDetection.LabelFilterMethod LabelFilter
  157. {
  158. get { return _labelFilter; }
  159. set { _labelFilter = value; }
  160. }
  161. /// <summary>
  162. /// Render whether smoothing (antialiasing) is applied to lines and curves and the edges of filled areas
  163. /// </summary>
  164. public SmoothingMode SmoothingMode
  165. {
  166. get { return _smoothingMode; }
  167. set { _smoothingMode = value; }
  168. }
  169. /// <summary>
  170. /// Specifies the quality of text rendering
  171. /// </summary>
  172. public TextRenderingHint TextRenderingHint
  173. {
  174. get { return _textRenderingHint; }
  175. set { _textRenderingHint = value; }
  176. }
  177. /// <summary>
  178. /// Gets or sets the datasource
  179. /// </summary>
  180. public IProvider DataSource
  181. {
  182. get { return _dataSource; }
  183. set { _dataSource = value; }
  184. }
  185. /// <summary>
  186. /// Gets or sets the rendering style of the label layer.
  187. /// </summary>
  188. public new LabelStyle Style
  189. {
  190. get { return base.Style as LabelStyle; }
  191. set { base.Style = value; }
  192. }
  193. /// <summary>
  194. /// Gets or sets thematic settings for the layer. Set to null to ignore thematics
  195. /// </summary>
  196. public ITheme Theme
  197. {
  198. get { return _theme; }
  199. set { _theme = value; }
  200. }
  201. /// <summary>
  202. /// Data column or expression where label text is extracted from.
  203. /// </summary>
  204. /// <remarks>
  205. /// This property is overriden by the <see cref="LabelStringDelegate"/>.
  206. /// </remarks>
  207. public string LabelColumn
  208. {
  209. get { return _labelColumn; }
  210. set { _labelColumn = value; }
  211. }
  212. /// <summary>
  213. /// Gets or sets the method for creating a custom label string based on a feature.
  214. /// </summary>
  215. /// <remarks>
  216. /// <para>If this method is not null, it will override the <see cref="LabelColumn"/> value.</para>
  217. /// <para>The label delegate must take a <see cref="SharpMap.Data.FeatureDataRow"/> and return a string.</para>
  218. /// <example>
  219. /// Creating a label-text by combining attributes "ROADNAME" and "STATE" into one string, using
  220. /// an anonymous delegate:
  221. /// <code lang="C#">
  222. /// myLabelLayer.LabelStringDelegate = delegate(SharpMap.Data.FeatureDataRow fdr)
  223. /// { return fdr["ROADNAME"].ToString() + ", " + fdr["STATE"].ToString(); };
  224. /// </code>
  225. /// </example>
  226. /// </remarks>
  227. public GetLabelMethod LabelStringDelegate
  228. {
  229. get { return _getLabelMethod; }
  230. set { _getLabelMethod = value; }
  231. }
  232. /// <summary>
  233. /// Gets or sets the method for calculating the render priority of a label based on a feature.
  234. /// </summary>
  235. /// <remarks>
  236. /// <para>If this method is not null, it will override the <see cref="PriorityColumn"/> value.</para>
  237. /// <para>The label delegate must take a <see cref="SharpMap.Data.FeatureDataRow"/> and return an Int32.</para>
  238. /// <example>
  239. /// Creating a priority by combining attributes "capital" and "population" into one value, using
  240. /// an anonymous delegate:
  241. /// <code lang="C#">
  242. /// myLabelLayer.PriorityDelegate = delegate(SharpMap.Data.FeatureDataRow fdr)
  243. /// {
  244. /// Int32 retVal = 100000000 * (Int32)( (String)fdr["capital"] == "Y" ? 1 : 0 );
  245. /// return retVal + Convert.ToInt32(fdr["population"]);
  246. /// };
  247. /// </code>
  248. /// </example>
  249. /// </remarks>
  250. public GetPriorityMethod PriorityDelegate
  251. {
  252. get { return _getPriorityMethod; }
  253. set { _getPriorityMethod = value; }
  254. }
  255. /// <summary>
  256. /// Data column from where the label rotation is derived.
  257. /// If this is empty, rotation will be zero, or aligned to a linestring.
  258. /// Rotation are in degrees (positive = clockwise).
  259. /// </summary>
  260. public string RotationColumn
  261. {
  262. get { return _rotationColumn; }
  263. set { _rotationColumn = value; }
  264. }
  265. /// <summary>
  266. /// A value indication the priority of the label in cases of label-collision detection
  267. /// </summary>
  268. public int Priority
  269. {
  270. get { return _priority; }
  271. set { _priority = value; }
  272. }
  273. /// <summary>
  274. /// Name of the column that holds the value indicating the priority of the label in cases of label-collision detection
  275. /// </summary>
  276. public string PriorityColumn
  277. {
  278. get { return _priorityColumn; }
  279. set { _priorityColumn = value; }
  280. }
  281. /// <summary>
  282. /// Gets the boundingbox of the entire layer
  283. /// </summary>
  284. public override BoundingBox Envelope
  285. {
  286. get
  287. {
  288. if (DataSource == null)
  289. throw (new ApplicationException("DataSource property not set on layer '" + LayerName + "'"));
  290. bool wasOpen = DataSource.IsOpen;
  291. if (!wasOpen)
  292. DataSource.Open();
  293. BoundingBox box = DataSource.GetExtents();
  294. if (!wasOpen) //Restore state
  295. DataSource.Close();
  296. return box;
  297. }
  298. }
  299. /// <summary>
  300. /// Gets or sets the SRID of this VectorLayer's data source
  301. /// </summary>
  302. public override int SRID
  303. {
  304. get
  305. {
  306. if (DataSource == null)
  307. throw (new ApplicationException("DataSource property not set on layer '" + LayerName + "'"));
  308. return DataSource.SRID;
  309. }
  310. set { DataSource.SRID = value; }
  311. }
  312. #region IDisposable Members
  313. /// <summary>
  314. /// Disposes the object
  315. /// </summary>
  316. public void Dispose()
  317. {
  318. if (DataSource != null) DataSource.Dispose();
  319. }
  320. #endregion
  321. /// <summary>
  322. /// Renders the layer
  323. /// </summary>
  324. /// <param name="g">Graphics object reference</param>
  325. /// <param name="map">Map which is rendered</param>
  326. public override void Render(Graphics g, Map map)
  327. {
  328. if (Style.Enabled && Style.MaxVisible >= map.Zoom && Style.MinVisible < map.Zoom)
  329. {
  330. if (DataSource == null)
  331. throw (new ApplicationException("DataSource property not set on layer '" + LayerName + "'"));
  332. g.TextRenderingHint = TextRenderingHint;
  333. g.SmoothingMode = SmoothingMode;
  334. BoundingBox envelope = map.Envelope; //View to render
  335. if (CoordinateTransformation != null)
  336. {
  337. #if !DotSpatialProjections
  338. CoordinateTransformation.MathTransform.Invert();
  339. envelope = GeometryTransform.TransformBox(envelope, CoordinateTransformation.MathTransform);
  340. CoordinateTransformation.MathTransform.Invert();
  341. #else
  342. envelope = GeometryTransform.TransformBox(envelope, CoordinateTransformation.Target, CoordinateTransformation.Source);
  343. #endif
  344. }
  345. FeatureDataSet ds = new FeatureDataSet();
  346. DataSource.Open();
  347. DataSource.ExecuteIntersectionQuery(envelope, ds);
  348. DataSource.Close();
  349. if (ds.Tables.Count == 0)
  350. {
  351. base.Render(g, map);
  352. return;
  353. }
  354. FeatureDataTable features = ds.Tables[0];
  355. //Initialize label collection
  356. List<Label> labels = new List<Label>();
  357. //List<System.Drawing.Rectangle> LabelBoxes; //Used for collision detection
  358. //Render labels
  359. for (int i = 0; i < features.Count; i++)
  360. {
  361. FeatureDataRow feature = features[i];
  362. if (CoordinateTransformation != null)
  363. #if !DotSpatialProjections
  364. features[i].Geometry = GeometryTransform.TransformGeometry(features[i].Geometry,
  365. CoordinateTransformation.
  366. MathTransform);
  367. #else
  368. features[i].Geometry = GeometryTransform.TransformGeometry(features[i].Geometry,
  369. CoordinateTransformation.Source,
  370. CoordinateTransformation.Target);
  371. #endif
  372. LabelStyle style;
  373. if (Theme != null) //If thematics is enabled, lets override the style
  374. style = Theme.GetStyle(feature) as LabelStyle;
  375. else
  376. style = Style;
  377. float rotationStyle = style != null ? style.Rotation : 0f;
  378. float rotationColumn = 0f;
  379. if (!String.IsNullOrEmpty(RotationColumn))
  380. float.TryParse(feature[RotationColumn].ToString(), NumberStyles.Any, Map.NumberFormatEnUs,
  381. out rotationColumn);
  382. float rotation = rotationStyle + rotationColumn;
  383. int priority = Priority;
  384. if (_getPriorityMethod != null)
  385. priority = _getPriorityMethod(feature);
  386. else if (!String.IsNullOrEmpty(PriorityColumn))
  387. int.TryParse(feature[PriorityColumn].ToString(), NumberStyles.Any, Map.NumberFormatEnUs,
  388. out priority);
  389. string text;
  390. if (_getLabelMethod != null)
  391. text = _getLabelMethod(feature);
  392. else
  393. text = feature[LabelColumn].ToString();
  394. if (!String.IsNullOrEmpty(text))
  395. {
  396. if (feature.Geometry is GeometryCollection)
  397. {
  398. if (MultipartGeometryBehaviour == MultipartGeometryBehaviourEnum.All)
  399. {
  400. foreach (Geometry geom in (feature.Geometry as GeometryCollection))
  401. {
  402. Label lbl = CreateLabel(geom, text, rotation, priority, style, map, g);
  403. if (lbl != null)
  404. labels.Add(lbl);
  405. }
  406. }
  407. else if (MultipartGeometryBehaviour == MultipartGeometryBehaviourEnum.CommonCenter)
  408. {
  409. Label lbl = CreateLabel(feature.Geometry, text, rotation, priority, style, map, g);
  410. if (lbl != null)
  411. labels.Add(lbl);
  412. }
  413. else if (MultipartGeometryBehaviour == MultipartGeometryBehaviourEnum.First)
  414. {
  415. if ((feature.Geometry as GeometryCollection).Collection.Count > 0)
  416. {
  417. Label lbl = CreateLabel((feature.Geometry as GeometryCollection).Collection[0], text,
  418. rotation, style, map, g);
  419. if (lbl != null)
  420. labels.Add(lbl);
  421. }
  422. }
  423. else if (MultipartGeometryBehaviour == MultipartGeometryBehaviourEnum.Largest)
  424. {
  425. GeometryCollection coll = (feature.Geometry as GeometryCollection);
  426. if (coll.NumGeometries > 0)
  427. {
  428. double largestVal = 0;
  429. int idxOfLargest = 0;
  430. for (int j = 0; j < coll.NumGeometries; j++)
  431. {
  432. Geometry geom = coll.Geometry(j);
  433. if (geom is LineString && ((LineString) geom).Length > largestVal)
  434. {
  435. largestVal = ((LineString) geom).Length;
  436. idxOfLargest = j;
  437. }
  438. if (geom is MultiLineString && ((MultiLineString) geom).Length > largestVal)
  439. {
  440. largestVal = ((MultiLineString)geom).Length;
  441. idxOfLargest = j;
  442. }
  443. if (geom is Polygon && ((Polygon) geom).Area > largestVal)
  444. {
  445. largestVal = ((Polygon) geom).Area;
  446. idxOfLargest = j;
  447. }
  448. if (geom is MultiPolygon && ((MultiPolygon) geom).Area > largestVal)
  449. {
  450. largestVal = ((MultiPolygon) geom).Area;
  451. idxOfLargest = j;
  452. }
  453. }
  454. Label lbl = CreateLabel(coll.Geometry(idxOfLargest), text, rotation, priority, style,
  455. map, g);
  456. if (lbl != null)
  457. labels.Add(lbl);
  458. }
  459. }
  460. }
  461. else
  462. {
  463. Label lbl = CreateLabel(feature.Geometry, text, rotation, priority, style, map, g);
  464. if (lbl != null)
  465. labels.Add(lbl);
  466. }
  467. }
  468. }
  469. if (labels.Count > 0) //We have labels to render...
  470. {
  471. if (Style.CollisionDetection && _labelFilter != null)
  472. _labelFilter(labels);
  473. for (int i = 0; i < labels.Count; i++)
  474. if (labels[i].Show)
  475. VectorRenderer.DrawLabel(g, labels[i].LabelPoint, labels[i].Style.Offset,
  476. labels[i].Style.Font, labels[i].Style.ForeColor,
  477. labels[i].Style.BackColor, Style.Halo, labels[i].Rotation,
  478. labels[i].Text, map);
  479. }
  480. }
  481. base.Render(g, map);
  482. }
  483. private Label CreateLabel(Geometry feature, string text, float rotation, LabelStyle style, Map map, Graphics g)
  484. {
  485. return CreateLabel(feature, text, rotation, Priority, style, map, g);
  486. }
  487. private static Label CreateLabel(Geometry feature, string text, float rotation, int priority, LabelStyle style, Map map,
  488. Graphics g)
  489. {
  490. //SizeF size = g.MeasureString(text, style.Font);
  491. SizeF size = VectorRenderer.SizeOfString(g, text, style.Font);
  492. //PointF position = map.WorldToImage(feature.GetBoundingBox().GetCentroid());
  493. PointF position = Transform.WorldtoMap(feature.GetBoundingBox().GetCentroid(), map);
  494. position.X = position.X - size.Width*(short) style.HorizontalAlignment*0.5f;
  495. position.Y = position.Y - size.Height*(short) (2-(int)style.VerticalAlignment)*0.5f;
  496. if (position.X - size.Width > map.Size.Width || position.X + size.Width < 0 ||
  497. position.Y - size.Height > map.Size.Height || position.Y + size.Height < 0)
  498. return null;
  499. Label lbl;
  500. if (!style.CollisionDetection)
  501. lbl = new Label(text, position, rotation, priority, null, style);
  502. else
  503. {
  504. //Collision detection is enabled so we need to measure the size of the string
  505. lbl = new Label(text, position, rotation, priority,
  506. new LabelBox(position.X - size.Width*0.5f - style.CollisionBuffer.Width,
  507. position.Y + size.Height*0.5f + style.CollisionBuffer.Height,
  508. size.Width + 2f*style.CollisionBuffer.Width,
  509. size.Height + style.CollisionBuffer.Height*2f), style);
  510. }
  511. if (feature is LineString)
  512. {
  513. LineString line = feature as LineString;
  514. if (line.Length/map.PixelSize > size.Width) //Only label feature if it is long enough
  515. CalculateLabelOnLinestring(line, ref lbl, map);
  516. else
  517. return null;
  518. }
  519. return lbl;
  520. }
  521. private static void CalculateLabelOnLinestring(LineString line, ref Label label, Map map)
  522. {
  523. double dx, dy;
  524. // first find the middle segment of the line
  525. int midPoint = (line.Vertices.Count - 1)/2;
  526. if (line.Vertices.Count > 2)
  527. {
  528. dx = line.Vertices[midPoint + 1].X - line.Vertices[midPoint].X;
  529. dy = line.Vertices[midPoint + 1].Y - line.Vertices[midPoint].Y;
  530. }
  531. else
  532. {
  533. midPoint = 0;
  534. dx = line.Vertices[1].X - line.Vertices[0].X;
  535. dy = line.Vertices[1].Y - line.Vertices[0].Y;
  536. }
  537. if (dy == 0)
  538. label.Rotation = 0;
  539. else if (dx == 0)
  540. label.Rotation = 90;
  541. else
  542. {
  543. // calculate angle of line
  544. double angle = -Math.Atan(dy/dx) + Math.PI*0.5;
  545. angle *= (180d/Math.PI); // convert radians to degrees
  546. label.Rotation = (float) angle - 90; // -90 text orientation
  547. }
  548. double tmpx = line.Vertices[midPoint].X + (dx*0.5);
  549. double tmpy = line.Vertices[midPoint].Y + (dy*0.5);
  550. label.LabelPoint = map.WorldToImage(new Point(tmpx, tmpy));
  551. }
  552. }
  553. }