// 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.ComponentModel; using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Imaging; using System.Globalization; using System.Linq; using Core.Common.Base; using Core.Common.Utils; using Core.Common.Utils.Collections; using Core.Common.Utils.Collections.Generic; using Core.Common.Utils.Drawing; using Core.GIS.GeoAPI.CoordinateSystems; using Core.GIS.GeoAPI.Extensions.Feature; using Core.GIS.GeoAPI.Geometries; using Core.GIS.NetTopologySuite.Extensions.Features; using Core.GIS.NetTopologySuite.Geometries; using Core.GIS.SharpMap.Api; using Core.GIS.SharpMap.Api.Delegates; using Core.GIS.SharpMap.Api.Layers; using Core.GIS.SharpMap.CoordinateSystems; using Core.GIS.SharpMap.CoordinateSystems.Transformations; using Core.GIS.SharpMap.Data.Providers; using Core.GIS.SharpMap.Layers; using Core.GIS.SharpMap.Utilities; using log4net; namespace Core.GIS.SharpMap.Map { /// /// Map class /// /// /// Creating a new map instance, adding layers and rendering the map: /// /// SharpMap.Map myMap = new SharpMap.Map(picMap.Size); /// myMap.MinimumZoom = 100; /// myMap.BackgroundColor = Color.White; /// /// SharpMap.Layers.VectorLayer myLayer = new SharpMap.Layers.VectorLayer("My layer"); /// string ConnStr = "Server=127.0.0.1;Port=5432;User Id=postgres;Password=password;Database=myGisDb;"; /// myLayer.DataSource = new SharpMap.Data.Providers.PostGIS(ConnStr, "myTable", "the_geom", 32632); /// myLayer.FillStyle = new SolidBrush(Color.FromArgb(240,240,240)); //Applies to polygon types only /// myLayer.OutlineStyle = new Pen(Color.Blue, 1); //Applies to polygon and linetypes only /// //Setup linestyle (applies to line types only) /// myLayer.Style.Line.Width = 2; /// myLayer.Style.Line.Color = Color.Black; /// myLayer.Style.Line.EndCap = System.Drawing.Drawing2D.LineCap.Round; //Round end /// myLayer.Style.Line.StartCap = layRailroad.LineStyle.EndCap; //Round start /// myLayer.Style.Line.DashPattern = new float[] { 4.0f, 2.0f }; //Dashed linestyle /// myLayer.Style.EnableOutline = true; /// myLayer.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias; //Render smooth lines /// myLayer.MaxVisible = 40000; /// /// myMap.Layers.Add(myLayer); /// // [add more layers...] /// /// myMap.Center = new SharpMap.Geometries.Point(725000, 6180000); //Set center of map /// myMap.Zoom = 1200; //Set zoom level /// myMap.Size = new System.Drawing.Size(300,200); //Set output size /// /// System.Drawing.Image imgMap = myMap.GetMap(); //Renders the map /// /// public class Map : Observable, IDisposable, INotifyCollectionChange, INotifyPropertyChange, IMap { /// /// When layer render time is less than this - image will not be cached, increase this parameter when you get memory leaks. /// public const int ThresholdToClearLayerImageInMillis = 100; /// /// Used for converting numbers to/from strings /// public static readonly NumberFormatInfo numberFormat_EnUS = new CultureInfo("en-US", false).NumberFormat; public static bool UseParallelRendering = true; //used in zoomtoextends to have default 10 percent margin private const int defaultExtendsMarginPercentage = 10; private static readonly ILog log = LogManager.GetLogger(typeof(Map)); private EventedList layers; private double worldHeight; private double worldLeft; private double worldTop; private bool showGrid; private VectorLayer gridLayer; private bool rendering; /// /// Initializes a new map /// public Map() : this(new Size(100, 100)) {} /// /// Initializes a new map /// /// Size of map in pixels public Map(Size size) { name = "Map"; maximumZoom = 1e9; minimumZoom = 1e-4; center = GeometryFactory.CreateCoordinate(0, 0); zoom = 1000; pixelAspectRatio = 1.0; Size = size; Layers = new EventedList(); BackColor = Color.Transparent; mapTransform = new Matrix(); mapTransformInverted = new Matrix(); UpdateDimensions(); } /// /// True if map needs to be rendered. Map will check this flag while it will render itself. /// If flag is set to true - Render() will be called before Image is drawn on Map. /// /// Calling Render() resets this flag automatically. /// public virtual bool RenderRequired { get; protected set; } public virtual Image Image { get { return image; } } public virtual bool IsDisposing { get; protected set; } /// /// Gets or sets a flag indicating if we should draw grid (usually latitude / longitude projected to the current map coordinate system). /// /// TODO: extract this into IMapDecoration, together with tools like NorthArrow, ScaleBar ... /// public virtual bool ShowGrid { get { return showGrid; } set { OnPropertyChanging("ShowGrid"); showGrid = value; if (value) { BuildGrid(); } else { RemoveGrid(); } RenderRequired = true; OnPropertyChanged("ShowGrid"); } } /// /// Returns the (first) layer on which is present. /// /// The feature to search for. /// search only visible layers /// The layer that contains the . Null if not layer can be found. public virtual ILayer GetLayerByFeature(IFeature feature, bool visibleOnly) { var allLayers = visibleOnly ? GetAllVisibleLayers(true) : GetAllLayers(true); return allLayers.Where(l => l.DataSource != null).FirstOrDefault(layer => layer.DataSource.Contains(feature)); } public static IEnumerable GetLayers(IEnumerable layers, bool includeGroupLayers, bool includeInvisibleLayers) { foreach (var layer in layers) { if (layer.Visible || includeInvisibleLayers) { var groupLayer = layer as GroupLayer; if (groupLayer != null) { if (includeGroupLayers) { yield return layer; } var childLayers = GetLayers(groupLayer.Layers, includeGroupLayers, includeInvisibleLayers); foreach (var childLayer in childLayers) { if (childLayer.Visible || includeInvisibleLayers) { yield return childLayer; } } } else { yield return layer; } } } } public virtual bool GetDataMinMaxForThemeGroup(string themeGroup, string attributeName, out double min, out double max) { if (String.IsNullOrEmpty(themeGroup)) { throw new ArgumentException("expected non-empty themegroup", "themeGroup"); } min = Double.MaxValue; max = Double.MinValue; var layersForThemeGroup = GetLayersForThemeGroup(themeGroup, attributeName).ToList(); if (!layersForThemeGroup.Any()) { return false; //no layers, or no visible layers: no update possible } foreach (var sameRangeLayer in layersForThemeGroup) { min = Math.Min(sameRangeLayer.MinDataValue, min); max = Math.Max(sameRangeLayer.MaxDataValue, max); } return true; } public virtual void OnThemeGroupDataChanged(string themeGroup, string attributeName) { foreach (var layer in GetLayersForThemeGroup(themeGroup, attributeName)) { layer.ThemeIsDirty = true; } } public override string ToString() { return (!String.IsNullOrEmpty(Name)) ? Name : base.ToString(); } /// /// Disposes the map object /// public virtual void Dispose() { foreach (Layer layer in layers) { var disposable = layer as IDisposable; if (disposable != null) { disposable.Dispose(); } layer.ClearImage(); } } public virtual void ClearImage() { if (image != null) { image.Dispose(); image = null; } foreach (var layer in Layers) { layer.ClearImage(); } } /// /// Renders the map to an image /// /// public virtual Image Render() { // DateTime startTime = DateTime.Now; // Used when logging Rendering time of Map if (Size.IsEmpty) { return null; // nothing to render } if (MapRendering != null) { MapRendering(null); } if (image != null && (Size.Width != image.Width || Size.Height != image.Height)) // re-create only when it is required { image.Dispose(); image = null; } if (image == null) { image = new Bitmap(Size.Width, Size.Height, PixelFormat.Format32bppPArgb); } if (rendering) { return null; } rendering = true; // TODO: draw using multiple threads /* Action renderLayer = delegate(int i) { if (Layers[i].RenderRequired) { Layers[i].Render(); } }; Parallel.For(0, Layers.Count, renderLayer); */ var allLayers = GetLayers(layers, true, false).OrderByDescending(l => l.RenderOrder).ToArray(); // draw decoration layers on top var gridVectorLayer = GetGridLayer(); if (gridVectorLayer != null) { allLayers = allLayers.Concat(new[] { gridVectorLayer }).ToArray(); } // merge all layer bitmaps var g = Graphics.FromImage(image); g.Clear(BackColor); foreach (var layer in allLayers) { if (!(layer.MaxVisible >= Zoom) || !(layer.MinVisible < Zoom)) { continue; } if (layer.RenderRequired || layer.Image == null) { layer.Render(); } if (layer.Image == null) { continue; } if (Math.Abs(layer.Opacity - 1.0) > 0.0000001) { g.DrawImageTransparent(layer.Image, layer.Opacity); } else { g.DrawImage(layer.Image, 0, 0); } if (MapLayerRendered != null) { MapLayerRendered(g, layer); } if (layer.LastRenderDuration < 100) // do not keep Bitmap if it is very fast to render { layer.ClearImage(); } } g.Transform = MapTransform; g.PageUnit = GraphicsUnit.Pixel; if (MapRendered != null) { MapRendered(g); } g.Dispose(); foreach (var layer in Layers) { ClearLayerImages(layer); } // don't delete, enable when optimizing performance //double dt = (DateTime.Now - startTime).TotalMilliseconds; //log.DebugFormat("Map rendered in {0:F0} ms, size {1} x {2} px", dt, Size.Width, Size.Height); RenderRequired = false; rendering = false; return Image; } /// /// Returns an enumerable for all layers containing the search parameter in the LayerName property /// /// Search parameter /// IEnumerable public virtual IEnumerable FindLayer(string layername) { return Layers.Where(l => l.Name.Contains(layername)); } /// /// Returns a layer by its name /// /// Name of layer /// Layer public virtual ILayer GetLayerByName(string layerName) { //return Layers.Find(delegate(SharpMap.Layers.ILayer layer) { return layer.LayerName.Equals(name); }); return Layers.FirstOrDefault(t => String.Equals(t.Name, layerName, StringComparison.InvariantCultureIgnoreCase)); } /// /// Returns the (first) layer on which is present. /// /// The feature to search for. /// The layer that contains the . Null if not layer can be found. public virtual ILayer GetLayerByFeature(IFeature feature) { return GetLayerByFeature(feature, false); } /// /// Find the grouplayer for a given layer. Returns null if the layer is not contained in a group. /// /// Child layer to be found /// Grouplayer containing the childlayer or null if no grouplayer is found public virtual IGroupLayer GetGroupLayerContainingLayer(ILayer childLayer) { IEnumerable layers1 = Layers; return GetLayers(layers1, true, true).OfType().FirstOrDefault(l => l.Layers.Contains(childLayer)); } public virtual void DoWithLayerRecursive(ILayer layer, Action action) { if (layer == null || action == null) { return; } action(layer); var groupLayer = layer as IGroupLayer; if (groupLayer != null) { foreach (var subLayer in groupLayer.Layers) { DoWithLayerRecursive(subLayer, action); } } } public virtual IEnumerable GetAllLayers(bool includeGroupLayers) { return GetLayers(Layers, includeGroupLayers, true); } public virtual IEnumerable GetAllVisibleLayers(bool includeGroupLayers) { return GetLayers(Layers, includeGroupLayers, false); } /// /// Zooms to the extents of all layers /// Adds an extra 10 % margin to each border /// public virtual void ZoomToExtents() { IEnvelope boundingBox = GetExtents(); if (boundingBox == null || boundingBox.IsNull) { return; } boundingBox = (IEnvelope) boundingBox.Clone(); // beware of true 1d networks if ((boundingBox.Width < 1.0e-6) && (boundingBox.Height < 1.0e-6)) { return; } AddMargin(boundingBox, defaultExtendsMarginPercentage); ZoomToFit(boundingBox); } /// /// Sets the layer in front of all other layers (by changing the rendering order number of the layer) /// /// public virtual void BringToFront(ILayer layer) { if (layer == null) { return; } var groupLayer = layer as IGroupLayer; if (groupLayer != null) { var orderedLayers = GetLayers(groupLayer.Layers, false, true).OrderBy(l => l.RenderOrder).ToList(); ResetRenderOrder(orderedLayers.Count); var count = 0; foreach (var orderedlayer in orderedLayers) { orderedlayer.RenderOrder = count++; } } else { ResetRenderOrder(1); layer.RenderOrder = 0; } Render(); } /// /// Sets the layer behind all other layers (by changing the rendering order number of the layer) /// /// public virtual void SendToBack(ILayer layer) { if (layer == null) { return; } var groupLayer = layer as IGroupLayer; if (groupLayer != null) { var orderedLayers = GetLayers(groupLayer.Layers, false, true).OrderBy(l => l.RenderOrder).ToList(); var count = GetNewRenderNumber(); foreach (var orderedlayer in orderedLayers) { orderedlayer.RenderOrder = count++; } ResetRenderOrder(0); } else { layer.RenderOrder = GetNewRenderNumber(); ResetRenderOrder(0); } Render(); } public virtual void SendBackward(ILayer layer) { if (layer == null) { return; } var nextLayer = GetLayers(layers, false, true) .Where(l => l.RenderOrder >= layer.RenderOrder) .OrderBy(l => l.RenderOrder) .FirstOrDefault(l => l != layer); if (nextLayer == null) { return; } if (nextLayer.RenderOrder != layer.RenderOrder) { nextLayer.RenderOrder--; } layer.RenderOrder++; ResetRenderOrder(0); Render(); } public virtual void BringForward(ILayer layer) { if (layer == null) { return; } var previousLayer = GetLayers(layers, false, true) .Where(l => l.RenderOrder <= layer.RenderOrder) .OrderBy(l => l.RenderOrder) .LastOrDefault(l => l != layer); if (previousLayer == null) { return; } previousLayer.RenderOrder++; layer.RenderOrder--; ResetRenderOrder(0); Render(); } /// /// Zooms the map to fit a bounding box /// /// /// NOTE: If the aspect ratio of the box and the aspect ratio of the mapsize /// isn't the same, the resulting map-envelope will be adjusted so that it contains /// the bounding box, thus making the resulting envelope larger! /// /// public virtual void ZoomToFit(IEnvelope bbox) { ZoomToFit(bbox, false); } /// /// Zooms the map to fit a bounding box. /// /// /// NOTE: If the aspect ratio of the box and the aspect ratio of the mapsize /// isn't the same, the resulting map-envelope will be adjusted so that it contains /// the bounding box, thus making the resulting envelope larger! /// /// /// Add a default margin? public virtual void ZoomToFit(IEnvelope bbox, bool addMargin) { if (bbox == null || bbox.Width == 0 || bbox.Height == 0 || Double.IsInfinity(bbox.Width) || Double.IsInfinity(bbox.Height) || Double.IsNaN(bbox.Width) || Double.IsNaN(bbox.Height)) { return; } //create a copy so we don't mess up any given envelope... bbox = (IEnvelope) bbox.Clone(); if (addMargin) { AddMargin(bbox, defaultExtendsMarginPercentage); } desiredEnvelope = bbox; zoom = bbox.Width; //Set the private center value so we only fire one MapOnViewChange event //if the map height is smaller than the given bbox height scale to the height if (Envelope.Height < bbox.Height) { zoom *= bbox.Height/MapHeight; //zoom *= bbox.Height / Envelope.Height; --> Significance decrease for large center coordinates (TOOLS-7678) } center = bbox.Centre; UpdateDimensions(); if (GetExtents() == null || GetExtents().IsNull) { desiredEnvelope = Envelope; } if (MapViewOnChange != null) { MapViewOnChange(); } SetRenderRequiredForAllLayers(); } /// /// Converts a point from world coordinates to image coordinates based on the current /// zoom, center and mapsize. /// /// Point in world coordinates /// Point in image coordinates public virtual PointF WorldToImage(ICoordinate p) { return Transform.WorldtoMap(p, this); } /// /// Converts a point from image coordinates to world coordinates based on the current /// zoom, center and mapsize. /// /// Point in image coordinates /// Point in world coordinates public virtual ICoordinate ImageToWorld(PointF p) { return Transform.MapToWorld(p, this); } public virtual object Clone() { var clone = new Map(Size) { name = name, Center = new Coordinate(Center), minimumZoom = minimumZoom, maximumZoom = maximumZoom, Zoom = Zoom, SrsWkt = SrsWkt, showGrid = ShowGrid, desiredEnvelope = desiredEnvelope }; foreach (ILayer layer in Layers) { clone.Layers.Add((ILayer) layer.Clone()); clone.NotifyObservers(); } return clone; } public void ReplaceLayer(ILayer sourceLayer, ILayer targetLayer) { sourceLayer.ThemeGroup = sourceLayer.Name; sourceLayer.ShowInTreeView = false; targetLayer.ThemeGroup = sourceLayer.ThemeGroup; targetLayer.Theme = sourceLayer.Theme != null ? (ITheme) sourceLayer.Theme.Clone() : null; var allLayers = GetAllLayers(true).ToList(); if (!allLayers.Contains(sourceLayer)) { return; } var layerIndex = Layers.IndexOf(sourceLayer); if (layerIndex >= 0) { Layers.Remove(sourceLayer); Layers.Insert(layerIndex, targetLayer); targetLayer.Map = this; } else { var groupLayer = allLayers.OfType().FirstOrDefault(gl => gl.Layers.Contains(sourceLayer)); if (groupLayer != null) { var subIndex = groupLayer.Layers.IndexOf(sourceLayer); var groupLayersReadonly = groupLayer.LayersReadOnly; groupLayer.LayersReadOnly = false; groupLayer.Layers.Remove(sourceLayer); groupLayer.Layers.Insert(subIndex, targetLayer); targetLayer.Map = this; groupLayer.LayersReadOnly = groupLayersReadonly; } } NotifyObservers(); } private void UpdateDimensions() { pixelSize = zoom/size.Width; pixelHeight = pixelSize*pixelAspectRatio; worldHeight = pixelSize*size.Height; worldLeft = center.X - zoom*0.5; worldTop = center.Y + worldHeight*0.5*pixelAspectRatio; } private void SetRenderRequiredForAllLayers() { if (Layers == null) { return; } foreach (var layer in Layers) { layer.RenderRequired = true; } if (ShowGrid) { GetGridLayer().RenderRequired = true; } } private void RemoveGrid() { if (gridLayer == null) { return; } gridLayer = null; } private void BuildGrid() { var gridLines = new List(); if (CoordinateSystemFactory == null) { log.DebugFormat("Showing map grid is only supported when map has coordinate system defined"); return; // can only draw if coordinate system factory is available } for (var i = -180; i <= 180; i += 10) { var coordinates = new ICoordinate[179]; for (var j = -89; j <= 89; j++) { coordinates[j + 89] = new Coordinate(i, j); } gridLines.Add(new Feature { Geometry = new LineString(coordinates) }); } for (var i = -90; i <= 90; i += 10) { var coordinates = new ICoordinate[361]; for (var j = -180; j <= 180; j++) { coordinates[j + 180] = new Coordinate(j, i); } gridLines.Add(new Feature { Geometry = new LineString(coordinates) }); } var src = CoordinateSystemFactory.CreateFromEPSG(4326 /* WGS84 */); var dst = CoordinateSystem; var transformation = dst == null ? null : CoordinateSystemFactory.CreateTransformation(src, dst); gridLayer = new VectorLayer { DataSource = new FeatureCollection { Features = gridLines, CoordinateSystem = src }, CoordinateTransformation = transformation, ShowInTreeView = false, ShowInLegend = false, Selectable = false, Map = this }; gridLayer.Style.Line.Color = Color.FromArgb(50, 100, 100, 100); } /// /// Clears layer image it if takes very little time to render it. /// /// private void ClearLayerImages(ILayer layer) { if (layer.LastRenderDuration < ThresholdToClearLayerImageInMillis) { layer.ClearImage(); } // will make sure that only those child layers where render duration is very fast will dispose their images var groupLayer = layer as IGroupLayer; if (layer.Image != null && groupLayer != null) { foreach (var childLayer in groupLayer.Layers) { ClearLayerImages(childLayer); } } } /// /// Expands the given boundingBox by percentage. /// /// Boundingbox to expand /// Percentage by which boundingBox is expanded private static void AddMargin(IEnvelope boundingBox, double percentage) { double minX = 0.0; double minY = 0.0; if (boundingBox.Width < 1.0e-6) { minX = 1.0; } if (boundingBox.Height < 1.0e-6) { minY = 1.0; } var factor = percentage/200; //factor is used left and right so divide by 200 (iso 100) boundingBox.ExpandBy(minX + boundingBox.Width*factor, minY + boundingBox.Height*factor); } private int GetNewRenderNumber() { var allMapLayers = GetLayers(layers, false, true).ToList(); return allMapLayers.Any() ? allMapLayers.Max(l => l.RenderOrder) + 1 : 0; } private void ResetRenderOrder(int offset) { var allMapLayers = GetLayers(layers, false, true).OrderBy(l => l.RenderOrder).ToList(); var count = offset; foreach (var layer in allMapLayers) { layer.RenderOrder = count++; } } private IEnumerable GetLayersForThemeGroup(string themeGroup, string attributeName) { var layersWithSameRange = GetAllVisibleLayers(true) .Where(l => l.ThemeGroup == themeGroup && l.ThemeAttributeName == attributeName); return layersWithSameRange; } #region Events /// /// Event fired when the zoomlevel or the center point has been changed /// public virtual event MapViewChangedHandler MapViewOnChange; public virtual event MapLayerRenderedEventHandler MapLayerRendered; /// /// Event fired when all layers have been rendered /// public virtual event MapRenderedEventHandler MapRendered; public virtual event MapRenderedEventHandler MapRendering; #endregion #region Properties /// /// Gets the extents of the current map based on the current zoom, center and mapsize /// public virtual IEnvelope Envelope { get { return new Envelope( Center.X - Zoom*.5, Center.X + Zoom*.5, Center.Y - MapHeight*.5, Center.Y + MapHeight*.5); } } [NonSerialized] private Matrix mapTransform; [NonSerialized] private Matrix mapTransformInverted; /// /// Using the you can alter the coordinate system of the map rendering. /// This makes it possible to rotate or rescale the image, for instance to have another direction than north upwards. /// /// /// Rotate the map output 45 degrees around its center: /// /// System.Drawing.Drawing2D.Matrix maptransform = new System.Drawing.Drawing2D.Matrix(); //Create transformation matrix /// maptransform.RotateAt(45,new PointF(myMap.Size.Width/2,myMap.Size.Height/2)); //Apply 45 degrees rotation around the center of the map /// myMap.MapTransform = maptransform; //Apply transformation to map /// /// public virtual Matrix MapTransform { get { return mapTransform; } set { OnPropertyChanging("MapTransform"); mapTransform = value; if (mapTransform.IsInvertible) { mapTransformInverted = mapTransform.Clone(); mapTransformInverted.Invert(); } else { mapTransformInverted.Reset(); } SetRenderRequiredForAllLayers(); OnPropertyChanged("MapTransform"); } } public static ICoordinateSystemFactory CoordinateSystemFactory { get; set; } private string srsWkt; /// /// Gets or sets the spatial reference system in WKT format. /// public virtual string SrsWkt { get { return srsWkt; } set { OnPropertyChanging("SrsWkt"); srsWkt = value; CreateCoordinateSystemFromWkt(value); OnPropertyChanged("SrsWkt"); } } private void CreateCoordinateSystemFromWkt(string value) { if (createCoordinateSystemFromWkt) { return; } createCoordinateSystemFromWkt = true; if (CoordinateSystemFactory == null || String.IsNullOrEmpty(srsWkt)) { CoordinateSystem = null; } else { CoordinateSystem = CoordinateSystemFactory.CreateFromWkt(value); } createCoordinateSystemFromWkt = false; } private ICoordinateSystem coordinateSystem; public virtual ICoordinateSystem CoordinateSystem { get { if (srsWkt != null && (coordinateSystem == null || coordinateSystem.WKT != srsWkt)) { CreateCoordinateSystemFromWkt(srsWkt); } return coordinateSystem; } set { OnPropertyChanging("CoordinateSystem"); srsWkt = null; if (value != null) { CheckCoordinateSystem(value); } coordinateSystem = value; if (value != null) { srsWkt = coordinateSystem.WKT; } foreach (var layer in GetAllLayers(true).Where(l => l.CoordinateSystem != null).ToArray()) { UpdateLayerCoordinateTransformation(layer); if (layer.ShowLabels) { UpdateLayerCoordinateTransformation(layer.LabelLayer); } } if (ShowGrid) { if (coordinateSystem == null) { ShowGrid = false; } else { UpdateLayerCoordinateTransformation(GetGridLayer()); } } ZoomToExtents(); OnPropertyChanged("CoordinateSystem"); } } private void CheckCoordinateSystem(ICoordinateSystem targetCS) { foreach (var layer in GetAllVisibleLayers(true).Where(l => l.DataSource != null).ToArray()) { if (layer.CoordinateSystem == null || layer.Envelope == null) { continue; } var downLeftCorner = new Coordinate(layer.Envelope.MinX, layer.Envelope.MinY, 0); var upperRightCorner = new Coordinate(layer.Envelope.MaxX, layer.Envelope.MaxY, 0); var transform = CoordinateSystemFactory.CreateTransformation(layer.CoordinateSystem, targetCS); if (!CoordinateSystemValidator.CanConvertByTransformation( new[] { downLeftCorner, upperRightCorner }, transform)) { throw new CoordinateTransformException(layer.Name, layer.CoordinateSystem, targetCS); } } } private void UpdateLayerCoordinateTransformation(ILayer layer) { if (CoordinateSystem == null) { layer.CoordinateTransformation = null; } else { layer.CoordinateTransformation = (layer.CoordinateSystem == null) ? null : CoordinateSystemFactory.CreateTransformation(layer.CoordinateSystem, CoordinateSystem); } } private int srid = -1; /// /// Coordinate system used by the current map. /// public virtual int SRID { get { return srid; } set { OnPropertyChanging("SRID"); srid = value; if (CoordinateSystemFactory != null) { CoordinateSystem = CoordinateSystemFactory.CreateFromEPSG(srid); } OnPropertyChanged("SRID"); } } private bool layersInitialized; /// /// A collection of layers. The first layer in the list is drawn first, the last one on top. /// public virtual EventedList Layers { get { if (!layersInitialized && layers != null) { layersInitialized = true; foreach (var layer in layers) { layer.Map = this; } } return layers; } set { if (layers != null) { layers.PropertyChanging -= OnPropertyChanging; layers.PropertyChanged -= OnPropertyChanged; layers.CollectionChanging -= LayersCollectionChanging; layers.CollectionChanged -= LayersCollectionChanged; } layers = value; if (layers != null) { layers.PropertyChanging += OnPropertyChanging; layers.PropertyChanged += OnPropertyChanged; layers.CollectionChanging += LayersCollectionChanging; layers.CollectionChanged += LayersCollectionChanged; } layersInitialized = false; } } private void LayersCollectionChanging(object sender, NotifyCollectionChangingEventArgs e) { if (CollectionChanging != null) { CollectionChanging(sender, e); } } private void LayersCollectionChanged(object sender, NotifyCollectionChangingEventArgs e) { OnLayersCollectionChanged(e); if (CollectionChanged != null) { CollectionChanged(sender, e); } } private void OnLayersCollectionChanged(NotifyCollectionChangingEventArgs e) { var layer1 = e.Item as ILayer; if (layer1 != null) { switch (e.Action) { case NotifyCollectionChangeAction.Replace: throw new NotImplementedException(); case NotifyCollectionChangeAction.Add: layer1.Map = this; CheckMapExtends(layer1); UpdateLayerCoordinateTransformation(layer1); layer1.RenderRequired = true; if (!String.IsNullOrEmpty(layer1.ThemeGroup)) { OnThemeGroupDataChanged(layer1.ThemeGroup, layer1.ThemeAttributeName); } SetRenderOrderAfterInsert(layer1); break; case NotifyCollectionChangeAction.Remove: RenderRequired = true; if (!String.IsNullOrEmpty(layer1.ThemeGroup)) { OnThemeGroupDataChanged(layer1.ThemeGroup, layer1.ThemeAttributeName); } SetRenderOrderAfterRemove(layer1); break; } } } private void SetRenderOrderAfterInsert(ILayer layer) { // Group layers are ignored in this code (by design) var allLayers = GetAllLayers(false).ToList(); var newLayers = layer is GroupLayer ? GetLayers(((GroupLayer) layer).Layers, false, true).ToList() : new List { layer }; var firstNewLayer = newLayers.FirstOrDefault(); if (firstNewLayer == null) { return; } // Calculate the first render order based on actual map layers structure (1 based) var startRenderOrder = allLayers.IndexOf(firstNewLayer) + 1; // Shift render orders for layers we inserting in front allLayers.Where(l => l.RenderOrder >= startRenderOrder) .ToList() .ForEach(l => l.RenderOrder += newLayers.Count); // Assign to new layers, again based on map layer structure (1 based) newLayers.ForEach(l => l.RenderOrder = allLayers.IndexOf(l) + 1); } private void SetRenderOrderAfterRemove(ILayer layer) { // Group layers are ignored in this code (by design) var allLayers = GetAllLayers(false).ToList(); var oldLayers = layer is GroupLayer ? GetLayers(((GroupLayer) layer).Layers, false, true).ToList() : new List { layer }; if (!oldLayers.Any()) { return; } // Shift render orders for succeeding layers allLayers.Where(l => l.RenderOrder >= oldLayers.Max(l1 => l1.RenderOrder)) .ToList() .ForEach(l => l.RenderOrder -= oldLayers.Count); } /// /// Zooms map to extends if the added layer is the only layer with valid envelope. /// /// private void CheckMapExtends(ILayer layer) { if (!layer.Visible || layers == null) { return; // don't bother } var allVisibleLayersWereEmpty = GetAllVisibleLayers(false).Except(new[] { layer }).All(l => l.Envelope != null && l.Envelope.IsNull || !l.Visible); if (!allVisibleLayersWereEmpty) { return; } var layerEnvelope = layer.Envelope; if (layerEnvelope != null && !layerEnvelope.IsNull) { ZoomToExtents(); } } private Color backColor; /// /// Map background color (defaults to transparent) /// public virtual Color BackColor { get { return backColor; } set { OnPropertyChanging("BackColor"); backColor = value; if (MapViewOnChange != null) { MapViewOnChange(); } OnPropertyChanged("BackColor"); } } private ICoordinate center; /// /// Center of map in WCS /// public virtual ICoordinate Center { get { return center; } set { OnPropertyChanging("Center"); center = value; desiredEnvelope.SetCentre(center); ZoomToFit(desiredEnvelope, false); OnPropertyChanged("Center"); } } /// /// The envelope as last set by ZoomToFit(). Used to re-ZoomToFit on resize. Adjusted whenever Zoom is manually set. /// private IEnvelope desiredEnvelope; private double zoom; /// /// Gets or sets the zoom level of map. /// /// /// The zoom level corresponds to the width of the map in WCS units. /// A zoomlevel of 0 will result in an empty map being rendered, but will not throw an exception /// public virtual double Zoom { get { return zoom; } set { OnPropertyChanging("Zoom"); double oldZoom = zoom; double clippedZoom; if (value < minimumZoom) { clippedZoom = minimumZoom; } else if (value > maximumZoom) { clippedZoom = maximumZoom; } else { clippedZoom = value; } desiredEnvelope.Zoom(100*(clippedZoom/oldZoom)); //adjust desiredEnvelope ZoomToFit(desiredEnvelope, false); zoom = clippedZoom; //using intermediate value because desired.Zoom(100*) causes minor rounding issues in ZoomToFit OnPropertyChanged("Zoom"); } } /// /// Gets the extents of the map based on the extents of all the layers in the layers collection /// /// Full map extents public virtual IEnvelope GetExtents() { if (Layers == null || Layers.Count == 0) { return null; } var onlyBaseLayers = layers.All(l => !l.ExcludeFromMapExtent); IEnvelope envelope = new Envelope(); foreach (ILayer layer in Layers) { if (layer.Visible && (!layer.ExcludeFromMapExtent || onlyBaseLayers)) { var layerEnvelope = layer.Envelope; if (layerEnvelope != null && !layerEnvelope.IsNull) { envelope.ExpandToInclude(layerEnvelope); } } } if (ShowGrid) { envelope.ExpandToInclude(GetGridLayer().Envelope); } return envelope; } private VectorLayer GetGridLayer() { if (!ShowGrid) { return null; } if (gridLayer == null) { BuildGrid(); } return gridLayer; } public virtual double WorldHeight { get { return worldHeight; } } public virtual double WorldLeft { get { return worldLeft; } } public virtual double WorldTop { get { return worldTop; } } /// /// Returns the size of a pixel in world coordinate units /// public virtual double PixelSize { get { return pixelSize; } } /// /// Returns the width of a pixel in world coordinate units. /// /// The value returned is the same as . public virtual double PixelWidth { get { return pixelSize; } } /// /// Returns the height of a pixel in world coordinate units. /// /// The value returned is the same as unless is different from 1. public virtual double PixelHeight { get { return pixelHeight; } } private double pixelAspectRatio = 1.0; /// /// Gets or sets the aspect-ratio of the pixel scales. A value less than /// 1 will make the map stretch upwards, and larger than 1 will make it smaller. /// /// Throws an argument exception when value is 0 or less. public virtual double PixelAspectRatio { get { return pixelAspectRatio; } set { if (pixelAspectRatio <= 0) { throw new ArgumentException("Invalid Pixel Aspect Ratio"); } OnPropertyChanging("PixelAspectRatio"); pixelAspectRatio = value; UpdateDimensions(); SetRenderRequiredForAllLayers(); OnPropertyChanged("PixelAspectRatio"); } } /// /// Height of map in world units /// /// public virtual double MapHeight { get { return (Zoom*Size.Height)/Size.Width*PixelAspectRatio; } } private Size size; /// /// Size of output map /// public virtual Size Size { get { return size; } set { OnPropertyChanging("Size"); size = value; ZoomToFit(desiredEnvelope ?? Envelope, false); OnPropertyChanged("Size"); } } private double minimumZoom; /// /// Minimum zoom amount allowed /// public virtual double MinimumZoom { get { return minimumZoom; } set { if (value < 0) { throw (new ArgumentException("Minimum zoom must be 0 or more")); } OnPropertyChanging("MinimumZoom"); minimumZoom = value; SetRenderRequiredForAllLayers(); OnPropertyChanged("MinimumZoom"); } } private double maximumZoom; private string name; private Image image; private double pixelSize; private double pixelHeight; private bool createCoordinateSystemFromWkt; /// /// Maximum zoom amount allowed /// public virtual double MaximumZoom { get { return maximumZoom; } set { if (value <= 0) { throw (new ArgumentException("Maximum zoom must larger than 0")); } OnPropertyChanging("MaximumZoom"); maximumZoom = value; SetRenderRequiredForAllLayers(); OnPropertyChanged("MaximumZoom"); } } public virtual string Name { get { return name; } set { OnPropertyChanging("Name"); name = value; OnPropertyChanged("Name"); } } #endregion #region INotifyCollectionChange Members public virtual event NotifyCollectionChangedEventHandler CollectionChanged; public virtual event NotifyCollectionChangingEventHandler CollectionChanging; public virtual bool HasDefaultEnvelopeSet { get { return desiredEnvelope.Equals(new Envelope(-500, 500, -500, 500)); } } #endregion #region INotifyPropertyChange public event PropertyChangingEventHandler PropertyChanging; protected void OnPropertyChanging(string propertyName) { if (PropertyChanging != null) { PropertyChanging(this, new PropertyChangingEventArgs(propertyName)); } } protected void OnPropertyChanging(object sender, PropertyChangingEventArgs e) { if (PropertyChanging != null) { PropertyChanging(sender, e); } } public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged(string propertyName) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } protected void OnPropertyChanged(object sender, PropertyChangedEventArgs e) { if (PropertyChanged != null) { PropertyChanged(sender, e); } } #endregion } }