// Copyright 2005, 2006 - Morten Nielsen (www.iter.dk) // // This file is part of SharpMap. // SharpMap is free software; you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // SharpMap is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // You should have received a copy of the GNU Lesser General Public License // along with SharpMap; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA using System; using System.Collections.Generic; using System.Drawing; using System.Linq; using DelftTools.Utils.Aop; using GeoAPI.CoordinateSystems.Transformations; using GeoAPI.Extensions.Feature; using GeoAPI.Geometries; using NetTopologySuite.Extensions.Features; using SharpMap.Api; using SharpMap.Api.Delegates; using SharpMap.Api.Enums; using SharpMap.Api.Layers; using SharpMap.CoordinateSystems.Transformations; using SharpMap.Rendering; using SharpMap.Styles; namespace SharpMap.Layers { /// /// Label layer class /// TODO: Auto-configure LabelColumn etc to some default on DataSource change (?) /// TODO: Change Map Rendering to check LabelLayer RenderRequired (more efficient: currently parent layer must be redrawn) /// TODO: Remove classes that override this class only to get access to GetText: there's a delegate for that. /// /// /// Creates a new label layer and sets the label text to the "Name" column in the FeatureDataTable of the datasource /// /// //Set up a label layer /// SharpMap.Layers.LabelLayer layLabel = new SharpMap.Layers.LabelLayer("Country labels"); /// layLabel.DataSource = layCountries.DataSource; /// layLabel.Enabled = true; /// layLabel.LabelColumn = "Name"; /// layLabel.Style = new SharpMap.Styles.LabelStyle(); /// layLabel.Style.CollisionDetection = true; /// layLabel.Style.CollisionBuffer = new SizeF(20, 20); /// layLabel.Style.ForeColor = Color.White; /// layLabel.Style.Font = new Font(FontFamily.GenericSerif, 8); /// layLabel.MaxVisible = 90; /// layLabel.Style.HorizontalAlignment = SharpMap.Styles.LabelStyle.HorizontalAlignmentEnum.Center; /// /// [Entity(FireOnCollectionChange = false)] public class LabelLayer : Layer, ILabelLayer { public LabelLayer(): this("") { } /// /// Creates a new instance of a LabelLayer /// public LabelLayer(string layername) { style = new SharpMap.Styles.LabelStyle(); this.Name = layername; this.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias; this.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias; multipartGeometryBehaviour = MultipartGeometryBehaviourEnum.All; this.ShowInTreeView = false; this.ShowInLegend = false; this.Visible = false; this.LabelFilter = LabelCollisionDetection.ThoroughCollisionDetection; this.Style = new LabelStyle { Font = new Font("Arial", 12), Halo = new Pen(Brushes.White, 1f), CollisionDetection = true }; } private MultipartGeometryBehaviourEnum multipartGeometryBehaviour; /// /// Gets or sets labelling behavior on multipart geometries /// /// Default value is public virtual MultipartGeometryBehaviourEnum MultipartGeometryBehaviour { get { return multipartGeometryBehaviour; } set { multipartGeometryBehaviour = value; } } private SharpMap.Rendering.LabelCollisionDetection.LabelFilterMethod labelFilter; /// /// Filtermethod delegate for performing filtering /// /// /// Default method is /// public virtual SharpMap.Rendering.LabelCollisionDetection.LabelFilterMethod LabelFilter { get { return labelFilter; } set { labelFilter = value; } } private System.Drawing.Drawing2D.SmoothingMode smoothingMode; /// /// Render whether smoothing (antialiasing) is applied to lines and curves and the edges of filled areas /// public virtual System.Drawing.Drawing2D.SmoothingMode SmoothingMode { get { return smoothingMode; } set { smoothingMode = value; } } private System.Drawing.Text.TextRenderingHint textRenderingHint; /// /// Specifies the quality of text rendering /// public virtual System.Drawing.Text.TextRenderingHint TextRenderingHint { get { return textRenderingHint; } set { textRenderingHint = value; } } private ILabelStyle style; /// /// Gets or sets the rendering style of the label layer. /// public virtual ILabelStyle Style { get { return style; } set { style = value; } } private ITheme theme; /// /// Gets or sets thematic settings for the layer. Set to null to ignore thematics /// public virtual ITheme Theme { get { return theme; } set { theme = value; } } private string labelColumn; /// /// Data column or expression where label text is extracted from. /// /// /// This property is overriden by the . /// public virtual string LabelColumn { get { return labelColumn; } set { labelColumn = value; } } private GetLabelMethod getLabelMethod; /// /// Gets or sets the method for creating a custom label string based on a feature. /// /// /// If this method is not null, it will override the value. /// The label delegate must take a and return a string. /// /// Creating a label-text by combining attributes "ROADNAME" and "STATE" into one string, using /// an anonymous delegate: /// /// myLabelLayer.LabelStringDelegate = delegate(SharpMap.Data.FeatureDataRow fdr) /// { return fdr["ROADNAME"].ToString() + ", " + fdr["STATE"].ToString(); }; /// /// /// public virtual GetLabelMethod LabelStringDelegate { get { return getLabelMethod; } set { getLabelMethod = value; } } private string rotationColumn; /// /// Data column from where the label rotation is derived. /// If this is empty, rotation will be zero, or aligned to a linestring. /// Rotation are in degrees (positive = clockwise). /// public virtual string RotationColumn { get { return rotationColumn; } set { rotationColumn = value; } } private int priority; /// /// A value indication the priority of the label in cases of label-collision detection /// public virtual int Priority { get { return priority; } set { priority = value; } } [NoNotifyPropertyChange] public override IFeatureProvider DataSource { get { return base.DataSource ?? ParentDataSource; } //either custom datasource (different from parent, otherwise parent datasource) set { base.DataSource = value; } } [NoNotifyPropertyChange] public override ICoordinateTransformation CoordinateTransformation { get { return base.DataSource == null ? ParentCoordinateTransformation : base.CoordinateTransformation; } set { base.CoordinateTransformation = value; } } private IFeatureProvider ParentDataSource { get { return Parent != null ? Parent.DataSource : null; } } private ICoordinateTransformation ParentCoordinateTransformation { get { return Parent != null ? Parent.CoordinateTransformation : null; } } /// /// Renders the layer /// /// Graphics object reference /// Map which is rendered public override void OnRender(System.Drawing.Graphics g, IMap map) { if (Style.Enabled && Style.MaxVisible >= map.Zoom && Style.MinVisible < map.Zoom) { if (DataSource == null) throw (new ApplicationException("DataSource property not set on layer '" + Name + "'")); g.TextRenderingHint = TextRenderingHint; g.SmoothingMode = SmoothingMode; var features = Parent.GetFeatures(map.Envelope); if (!features.Any()) { return; } //Initialize label collection var labels = new List(); //List LabelBoxes; //Used for collision detection //Render labels foreach (var feature in features) { var geometry = CoordinateTransformation != null ? GeometryTransform.TransformGeometry(feature.Geometry, CoordinateTransformation.MathTransform) : feature.Geometry; ILabelStyle style; if (Theme != null) //If thematics is enabled, lets override the style style = Theme.GetStyle(feature) as LabelStyle; else style = Style; float rotation = 0; if (!String.IsNullOrEmpty(RotationColumn)) rotation = FeatureAttributeAccessorHelper.GetAttributeValue(feature, RotationColumn, 0f); string text = GetText(feature); if (!string.IsNullOrEmpty(text)) { if (geometry is IGeometryCollection) { if (MultipartGeometryBehaviour == MultipartGeometryBehaviourEnum.All) { foreach (IGeometry geom in (geometry as IGeometryCollection)) { Label lbl = CreateLabel(geom, text, rotation, style, map, g); if (lbl != null) labels.Add(lbl); } } else if (MultipartGeometryBehaviour == MultipartGeometryBehaviourEnum.CommonCenter) { Label lbl = CreateLabel(geometry, text, rotation, style, map, g); if (lbl != null) labels.Add(lbl); } else if (MultipartGeometryBehaviour == MultipartGeometryBehaviourEnum.First) { if ((geometry as IGeometryCollection).Geometries.Length > 0) { Label lbl = CreateLabel((geometry as IGeometryCollection).Geometries[0], text, rotation, style, map, g); if (lbl != null) labels.Add(lbl); } } else if (MultipartGeometryBehaviour == MultipartGeometryBehaviourEnum.Largest) { var coll = (geometry as IGeometryCollection); if (coll.NumGeometries > 0) { double largestVal = 0; int idxOfLargest = 0; for (int j = 0; j < coll.NumGeometries; j++) { IGeometry geom = coll.Geometries[j]; if (geom is ILineString && ((ILineString)geom).Length > largestVal) { largestVal = ((ILineString)geom).Length; idxOfLargest = j; } if (geom is IMultiLineString && ((IMultiLineString)geom).Length > largestVal) { largestVal = ((ILineString)geom).Length; idxOfLargest = j; } if (geom is IPolygon && ((IPolygon)geom).Area > largestVal) { largestVal = ((IPolygon)geom).Area; idxOfLargest = j; } if (geom is IMultiPolygon && ((IMultiPolygon)geom).Area > largestVal) { largestVal = ((IMultiPolygon)geom).Area; idxOfLargest = j; } } Label lbl = CreateLabel(coll.Geometries[idxOfLargest], text, rotation, style, map, g); if (lbl != null) labels.Add(lbl); } } } else { var lbl = CreateLabel(geometry, text, rotation, style, map, g); if (lbl != null) labels.Add(lbl); } } } if (labels.Count > 0) //We have labels to render... { if (this.Style.CollisionDetection && this.labelFilter!=null) this.labelFilter(labels); for (int i = 0; i < labels.Count;i++ ) VectorRenderingHelper.DrawLabel(g, labels[i].LabelPoint, labels[i].Style.Offset, labels[i].Style.Font, labels[i].Style.ForeColor, labels[i].Style.BackColor, Style.Halo, labels[i].Rotation, labels[i].Text, map); } labels = null; } } protected virtual string GetText(IFeature feature) { string text = null; if (getLabelMethod != null) { text = getLabelMethod(feature); } if (text == null && LabelColumn != null) { text = FeatureAttributeAccessorHelper.GetAttributeValue(feature, LabelColumn, "", false); } return text; } private SharpMap.Rendering.Label CreateLabel(IGeometry feature,string text, float rotation, ILabelStyle style, IMap map, System.Drawing.Graphics g) { System.Drawing.SizeF size = g.MeasureString(text, style.Font); System.Drawing.PointF position = map.WorldToImage(feature.EnvelopeInternal.Centre); position.X = position.X - size.Width * (short)style.HorizontalAlignment * 0.5f; position.Y = position.Y - size.Height * (short)style.VerticalAlignment * 0.5f; if (position.X-size.Width > map.Size.Width || position.X+size.Width < 0 || position.Y-size.Height > map.Size.Height || position.Y+size.Height < 0) return null; else { SharpMap.Rendering.Label lbl; if (!style.CollisionDetection) lbl = new SharpMap.Rendering.Label(text, position, rotation, this.Priority, null, style); else { //Collision detection is enabled so we need to measure the size of the string lbl = new SharpMap.Rendering.Label(text, position, rotation, this.Priority, new SharpMap.Rendering.LabelBox(position.X - size.Width * 0.5f - style.CollisionBuffer.Width, position.Y + size.Height * 0.5f + style.CollisionBuffer.Height, size.Width + 2f * style.CollisionBuffer.Width, size.Height + style.CollisionBuffer.Height * 2f), style); } if (feature.GetType() == typeof(ILineString)) { ILineString line = feature as ILineString; if (line.Length / map.PixelSize > size.Width) //Only label feature if it is long enough CalculateLabelOnLinestring(line, ref lbl, map); else return null; } return lbl; } } private void CalculateLabelOnLinestring(ILineString line, ref SharpMap.Rendering.Label label, IMap map) { double dx, dy; double tmpx, tmpy; double angle = 0.0; // first find the middle segment of the line int midPoint = (line.Coordinates.Length - 1) / 2; if (line.Coordinates.Length > 2) { dx = line.Coordinates[midPoint + 1].X - line.Coordinates[midPoint].X; dy = line.Coordinates[midPoint + 1].Y - line.Coordinates[midPoint].Y; } else { midPoint = 0; dx = line.Coordinates[1].X - line.Coordinates[0].X; dy = line.Coordinates[1].Y - line.Coordinates[0].Y; } if (dy == 0) label.Rotation = 0; else if (dx == 0) label.Rotation = 90; else { // calculate angle of line angle = -Math.Atan(dy / dx) + Math.PI * 0.5; angle *= (180d / Math.PI); // convert radians to degrees label.Rotation = (float)angle - 90; // -90 text orientation } tmpx = line.Coordinates[midPoint].X + (dx * 0.5); tmpy = line.Coordinates[midPoint].Y + (dy * 0.5); label.LabelPoint = map.WorldToImage(SharpMap.Converters.Geometries.GeometryFactory.CreateCoordinate(tmpx, tmpy)); } /// /// Gets the boundingbox of the entire layer /// public override IEnvelope Envelope { get { if (DataSource == null) { return null; } if (CoordinateTransformation != null) { throw new NotImplementedException(); } return DataSource.GetExtents(); } } [Aggregation] public virtual ILayer Parent { get; set; } /// /// Clones the object /// /// public override object Clone() { //don't use new LabelLayer since this clone is used in subclass NetworkCoverageLabelLayer var newLabelLayer = (LabelLayer)base.Clone(); // Use the orgLabelLayer properties newLabelLayer.ShowInTreeView = ShowInTreeView; newLabelLayer.LabelFilter = LabelFilter; //is this ok? newLabelLayer.LabelStringDelegate = LabelStringDelegate; newLabelLayer.Style = (LabelStyle) Style.Clone(); newLabelLayer.LabelColumn = LabelColumn; return newLabelLayer; } #region IDisposable Members /// /// Disposes the object /// public virtual void Dispose() { } #endregion } }