// 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.ObjectModel; using System.Drawing; using System.Drawing.Drawing2D; using SharpMap.Data; using SharpMap.Data.Providers; using GeoAPI.Geometries; using SharpMap.Rendering; using SharpMap.Rendering.Thematics; using SharpMap.Styles; using System.Collections.Generic; using Common.Logging; namespace SharpMap.Layers { /// /// Class for vector layer properties /// [Serializable] public class VectorLayer : Layer, ICanQueryLayer, ICloneable { static readonly ILog _logger = LogManager.GetLogger(typeof(VectorLayer)); private bool _clippingEnabled; private bool _isQueryEnabled = true; private IBaseProvider _dataSource; private SmoothingMode _smoothingMode; private ITheme _theme; private Envelope _envelope; /// /// Initializes a new layer /// /// Name of layer public VectorLayer(string layername) : base(new VectorStyle()) { LayerName = layername; SmoothingMode = SmoothingMode.AntiAlias; } /// /// Initializes a new layer with a specified datasource /// /// Name of layer /// Data source public VectorLayer(string layername, IBaseProvider dataSource) : this(layername) { _dataSource = dataSource; } /// /// Gets or sets a Dictionary with themes suitable for this layer. A theme in the dictionary can be used for rendering be setting the Theme Property using a delegate function /// public Dictionary Themes { get; set; } /// /// Gets or sets thematic settings for the layer. Set to null to ignore thematics /// public ITheme Theme { get { return _theme; } set { _theme = value; } } /// /// Specifies whether polygons should be clipped prior to rendering /// /// /// Clipping will clip and /// to the current view prior /// to rendering the object. /// Enabling clipping might improve rendering speed if you are rendering /// only small portions of very large objects. /// public bool ClippingEnabled { get { return _clippingEnabled; } set { _clippingEnabled = value; } } /// /// Render whether smoothing (antialiasing) is applied to lines and curves and the edges of filled areas /// public SmoothingMode SmoothingMode { get { return _smoothingMode; } set { _smoothingMode = value; } } /// /// Gets or sets the datasource /// public IBaseProvider DataSource { get { return _dataSource; } set { _dataSource = value; _envelope = null; } } /// /// Gets or sets the rendering style of the vector layer. /// public new VectorStyle Style { get { return base.Style as VectorStyle; } set { base.Style = value; } } /// /// Returns the extent of the layer /// /// Bounding box corresponding to the extent of the features in the layer public override Envelope Envelope { get { if (DataSource == null) throw (new ApplicationException("DataSource property not set on layer '" + LayerName + "'")); if (_envelope != null && CacheExtent) return ToTarget(_envelope.Copy()); Envelope box; lock (_dataSource) { // Is datasource already open? bool wasOpen = DataSource.IsOpen; if (!wasOpen) { DataSource.Open(); } box = DataSource.GetExtents(); if (!wasOpen) { DataSource.Close(); } } if (CacheExtent) _envelope = box; return ToTarget(box); } } /// /// Gets or sets a value indicating whether the layer envelope should be treated as static or not. /// /// /// When CacheExtent is enabled the layer Envelope will be calculated only once from DataSource, this /// helps to speed up the Envelope calculation with some DataProviders. Default is false for backward /// compatibility. /// public virtual bool CacheExtent { get; set; } /// /// Gets or sets the SRID of this VectorLayer's data source /// public override int SRID { get { if (DataSource == null) throw (new ApplicationException("DataSource property not set on layer '" + LayerName + "'")); return DataSource.SRID; } set { DataSource.SRID = value; base.SRID = value; } } #region IDisposable Members /// /// Disposes the object /// protected override void ReleaseManagedResources() { if (DataSource != null) DataSource.Dispose(); base.ReleaseManagedResources(); } #endregion /// /// Renders the layer to a graphics object, using the given map viewport /// /// Graphics object reference /// Map view port public override void Render(Graphics g, MapViewport mvp) { if (mvp.Center == null) throw (new ApplicationException("Cannot render map. View center not specified")); g.SmoothingMode = SmoothingMode; var envelope = ToSource(mvp.Envelope); //View to render if (DataSource == null) throw (new ApplicationException("DataSource property not set on layer '" + LayerName + "'")); //If thematics is enabled, we use a slighty different rendering approach if (Theme != null) RenderInternal(g, mvp, envelope, Theme); else RenderInternal(g, mvp, envelope); //OnLayerRendered(g); // Obsolete (and will cause infinite loop) base.Render(g, mvp); } /// /// Method to render this layer to the map, applying . /// /// The graphics object /// The map object /// The envelope to render /// The theme to apply protected void RenderInternal(Graphics g, MapViewport map, Envelope envelope, ITheme theme) { var canvasArea = RectangleF.Empty; var combinedArea = RectangleF.Empty; var ds = new FeatureDataSet(); lock (_dataSource) { // Is datasource already open? bool wasOpen = DataSource.IsOpen; if (!wasOpen) { DataSource.Open(); } DataSource.ExecuteIntersectionQuery(envelope, ds); if (!wasOpen) { DataSource.Close(); } } double scale = map.GetMapScale((int)g.DpiX); double zoom = map.Zoom; Func evalStyle; if (theme is IThemeEx) evalStyle = new ThemeExEvaluator((IThemeEx)theme).GetStyle; else evalStyle = new ThemeEvaluator(theme).GetStyle; foreach (FeatureDataTable features in ds.Tables) { // Transform geometries if necessary if (CoordinateTransformation != null) { for (int i = 0; i < features.Count; i++) { features[i].Geometry = ToTarget(features[i].Geometry); } } //Linestring outlines is drawn by drawing the layer once with a thicker line //before drawing the "inline" on top. if (Style.EnableOutline) { for (int i = 0; i < features.Count; i++) { var feature = features[i]; var outlineStyle = evalStyle(map, feature) as VectorStyle; if (outlineStyle == null) continue; if (!(outlineStyle.Enabled && outlineStyle.EnableOutline)) continue; var compare = outlineStyle.VisibilityUnits == VisibilityUnits.ZoomLevel ? zoom : scale; if (!(outlineStyle.MinVisible <= compare && compare <= outlineStyle.MaxVisible)) continue; using (outlineStyle = outlineStyle.Clone()) { if (outlineStyle != null) { //Draw background of all line-outlines first if (feature.Geometry is ILineString) { canvasArea = VectorRenderer.DrawLineStringEx(g, feature.Geometry as ILineString, outlineStyle.Outline, map, outlineStyle.LineOffset); } else if (feature.Geometry is IMultiLineString) { canvasArea = VectorRenderer.DrawMultiLineStringEx(g, feature.Geometry as IMultiLineString, outlineStyle.Outline, map, outlineStyle.LineOffset); } combinedArea = canvasArea.ExpandToInclude(combinedArea); } } } } for (int i = 0; i < features.Count; i++) { var feature = features[i]; var style = evalStyle(map, feature); if (style == null) continue; if (!style.Enabled) continue; double compare = style.VisibilityUnits == VisibilityUnits.ZoomLevel ? zoom : scale; if (!(style.MinVisible <= compare && compare <= style.MaxVisible)) continue; IEnumerable stylesToRender = GetStylesToRender(style); if (stylesToRender == null) return; foreach (var vstyle in stylesToRender) { if (!(vstyle is VectorStyle) || !vstyle.Enabled) continue; using (var clone = (vstyle as VectorStyle).Clone()) { if (clone != null) { canvasArea = RenderGeometryEx(g, map, feature.Geometry, clone); combinedArea = canvasArea.ExpandToInclude(combinedArea); } } } } } CanvasArea = combinedArea; } /// /// Method to render this layer to the map, applying . /// /// The graphics object /// The map object /// The envelope to render protected void RenderInternal(Graphics g, MapViewport map, Envelope envelope) { //if style is not enabled, we don't need to render anything if (!Style.Enabled) return; IEnumerable stylesToRender = GetStylesToRender(Style); if (stylesToRender == null) return; var canvasArea = RectangleF.Empty; var combinedArea = RectangleF.Empty; Collection geoms = null; foreach (var style in stylesToRender) { if (!(style is VectorStyle) || !style.Enabled) continue; using (var vStyle = (style as VectorStyle).Clone()) { if (vStyle != null) { if (geoms == null) { lock (_dataSource) { // Is datasource already open? bool wasOpen = DataSource.IsOpen; if (!wasOpen) { DataSource.Open(); } // Read data geoms = DataSource.GetGeometriesInView(envelope); if (!wasOpen) { DataSource.Close(); } } if (_logger.IsDebugEnabled) { _logger.DebugFormat("Layer {0}, NumGeometries {1}", LayerName, geoms.Count); } // Transform geometries if necessary if (CoordinateTransformation != null) { for (int i = 0; i < geoms.Count; i++) { geoms[i] = ToTarget(geoms[i]); } } } if (vStyle.LineSymbolizer != null) { vStyle.LineSymbolizer.Begin(g, map, geoms.Count); } else { //Linestring outlines is drawn by drawing the layer once with a thicker line //before drawing the "inline" on top. if (vStyle.EnableOutline) { foreach (var geom in geoms) { if (geom != null) { //Draw background of all line-outlines first if (geom is ILineString) canvasArea = VectorRenderer.DrawLineStringEx(g, geom as ILineString, vStyle.Outline, map, vStyle.LineOffset); else if (geom is IMultiLineString) canvasArea = VectorRenderer.DrawMultiLineStringEx(g, geom as IMultiLineString, vStyle.Outline, map, vStyle.LineOffset); combinedArea = canvasArea.ExpandToInclude(combinedArea); } } } } foreach (IGeometry geom in geoms) { if (geom != null) { canvasArea = RenderGeometryEx(g, map, geom, vStyle); combinedArea = canvasArea.ExpandToInclude(combinedArea); } } if (vStyle.LineSymbolizer != null) { vStyle.LineSymbolizer.Symbolize(g, map); vStyle.LineSymbolizer.End(g, map); } } } } CanvasArea = combinedArea; } /// /// Unpacks styles to render (can be nested group-styles) /// /// /// public static IEnumerable GetStylesToRender(IStyle style) { IStyle[] stylesToRender = null; if (style is GroupStyle) { var gs = style as GroupStyle; var styles = new List(); for (var i = 0; i < gs.Count; i++) { styles.AddRange(GetStylesToRender(gs[i])); } stylesToRender = styles.ToArray(); } else if (style is VectorStyle) { stylesToRender = new[] { style }; } return stylesToRender; } /// /// Method to render using /// /// The graphics object /// The map /// The feature's geometry /// The style to apply protected void RenderGeometry(Graphics g, MapViewport map, IGeometry feature, VectorStyle style) { RenderGeometryEx(g, map, feature, style); } /// /// Function to render using and returning the area covered. /// /// The graphics object /// The map /// The feature's geometry /// The style to apply protected RectangleF RenderGeometryEx(Graphics g, MapViewport map, IGeometry feature, VectorStyle style) { if (feature == null) return RectangleF.Empty; var geometryType = feature.OgcGeometryType; switch (geometryType) { case OgcGeometryType.Polygon: if (style.EnableOutline) return VectorRenderer.DrawPolygonEx(g, (IPolygon)feature, style.Fill, style.Outline, _clippingEnabled, map); else return VectorRenderer.DrawPolygonEx(g, (IPolygon)feature, style.Fill, null, _clippingEnabled, map); case OgcGeometryType.MultiPolygon: if (style.EnableOutline) return VectorRenderer.DrawMultiPolygonEx(g, (IMultiPolygon)feature, style.Fill, style.Outline, _clippingEnabled, map); return VectorRenderer.DrawMultiPolygonEx(g, (IMultiPolygon)feature, style.Fill, null, _clippingEnabled, map); case OgcGeometryType.LineString: if (style.LineSymbolizer != null) { style.LineSymbolizer.Render(map, (ILineString)feature, g); return RectangleF.Empty; } return VectorRenderer.DrawLineStringEx(g, (ILineString)feature, style.Line, map, style.LineOffset); case OgcGeometryType.MultiLineString: if (style.LineSymbolizer != null) { style.LineSymbolizer.Render(map, (IMultiLineString)feature, g); return RectangleF.Empty; } return VectorRenderer.DrawMultiLineStringEx(g, (IMultiLineString)feature, style.Line, map, style.LineOffset); case OgcGeometryType.Point: if (style.PointSymbolizer != null) return VectorRenderer.DrawPointEx(style.PointSymbolizer, g, (IPoint)feature, map); if (style.Symbol != null || style.PointColor == null) return VectorRenderer.DrawPointEx(g, (IPoint)feature, style.Symbol, style.SymbolScale, style.SymbolOffset, style.SymbolRotation, map); return VectorRenderer.DrawPointEx(g, (IPoint)feature, style.PointColor, style.PointSize, style.SymbolOffset, map); case OgcGeometryType.MultiPoint: if (style.PointSymbolizer != null) return VectorRenderer.DrawMultiPointEx(style.PointSymbolizer, g, (IMultiPoint)feature, map); if (style.Symbol != null || style.PointColor == null) return VectorRenderer.DrawMultiPointEx(g, (IMultiPoint)feature, style.Symbol, style.SymbolScale, style.SymbolOffset, style.SymbolRotation, map); return VectorRenderer.DrawMultiPointEx(g, (IMultiPoint)feature, style.PointColor, style.PointSize, style.SymbolOffset, map); case OgcGeometryType.GeometryCollection: var coll = (IGeometryCollection)feature; var combinedArea = RectangleF.Empty; for (var i = 0; i < coll.NumGeometries; i++) { IGeometry geom = coll[i]; var canvasArea = RenderGeometryEx(g, map, geom, style); combinedArea = canvasArea.ExpandToInclude(combinedArea); } return combinedArea; } throw new NotSupportedException(); } #region Implementation of ICanQueryLayer /// /// Returns the data associated with all the geometries that are intersected by 'geom' /// /// Geometry to intersect with /// FeatureDataSet to fill data into public void ExecuteIntersectionQuery(Envelope box, FeatureDataSet ds) { box = ToSource(box); int tableCount = ds.Tables.Count; lock (_dataSource) { // Is datasource already open? bool wasOpen = _dataSource.IsOpen; if (!wasOpen) { _dataSource.Open(); } _dataSource.ExecuteIntersectionQuery(box, ds); if (!wasOpen) { DataSource.Close(); } } if (ds.Tables.Count > tableCount) { //We added a table, name it according to layer ds.Tables[ds.Tables.Count - 1].TableName = LayerName; } } /// /// Returns the data associated with all the geometries that are intersected by 'geom' /// /// Geometry to intersect with /// FeatureDataSet to fill data into public void ExecuteIntersectionQuery(IGeometry geometry, FeatureDataSet ds) { geometry = ToSource(geometry); int tableCount = ds.Tables.Count; lock (_dataSource) { // Is datasource already open? bool wasOpen = DataSource.IsOpen; if (!wasOpen) { DataSource.Open(); } _dataSource.ExecuteIntersectionQuery(geometry, ds); if (!wasOpen) { DataSource.Close(); } } if (ds.Tables.Count > tableCount) { //We added a table, name it according to layer ds.Tables[ds.Tables.Count - 1].TableName = LayerName; } } /// /// Whether the layer is queryable when used in a SharpMap.Web.Wms.WmsServer, ExecuteIntersectionQuery() will be possible in all other situations when set to FALSE /// public bool IsQueryEnabled { get { return _isQueryEnabled; } set { _isQueryEnabled = value; } } #endregion /// > public object Clone() { var res = (VectorLayer)MemberwiseClone(); res.Style = Style.Clone(); if (Theme is ICloneable) res.Theme = (ITheme)((ICloneable)Theme).Clone(); return res; } #region Theme evaluators private abstract class ThemeEvaluatorBase { public abstract IStyle GetStyle(MapViewport mvp, FeatureDataRow feature); } private class ThemeEvaluator : ThemeEvaluatorBase { private readonly ITheme _theme; public ThemeEvaluator(ITheme theme) { _theme = theme; } public sealed override IStyle GetStyle(MapViewport mvp, FeatureDataRow feature) { return _theme.GetStyle(feature); } } private class ThemeExEvaluator : ThemeEvaluatorBase { private readonly IThemeEx _theme; public ThemeExEvaluator(IThemeEx theme) { _theme = theme; } public sealed override IStyle GetStyle(MapViewport mvp, FeatureDataRow feature) { return _theme.GetStyle(mvp, feature); } } #endregion } }