context: // Copyright 2005, 2006 - Morten Nielsen (www.iter.dk)
  //
  // This file is part of SharpMap.
  // Mapsui 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.Concurrent;
  using System.Collections.Generic;
  using System.ComponentModel;
  using System.Linq;
  using Mapsui.Fetcher;
  using Mapsui.Layers;
  using Mapsui.Styles;
  using Mapsui.UI;
  using Mapsui.Widgets;
  
  namespace Mapsui
  {
      /// <summary>
      /// Map class
      /// </summary>
      /// <remarks>
      /// Map holds all map related infos like the target CRS, layers, widgets and so on.
      /// </remarks>
      public class Map : INotifyPropertyChanged, IMap, IDisposable
      {
          private LayerCollection _layers = new();
          private Color _backColor = Color.White;
          private IViewportLimiter _limiter = new ViewportLimiter();
  
          /// <summary>
          /// Initializes a new map
          /// </summary>
          public Map()
          {
              BackColor = Color.White;
              Layers = new LayerCollection();
          }
  
          /// <summary>
          /// To register if the initial Home call has been done.
          /// </summary>
          public bool Initialized { get; set; }
  
          /// <summary>
          /// When true the user can not pan (move) the map.
          /// </summary>
          public bool PanLock { get; set; }
  
          /// <summary>
          /// When true the user an not rotate the map
          /// </summary>
          public bool ZoomLock { get; set; }
  
          /// <summary>
          /// When true the user can not zoom into the map
          /// </summary>
          public bool RotationLock { get; set; }
  
          /// <summary>
          /// List of Widgets belonging to map
          /// </summary>
          public ConcurrentQueue<IWidget> Widgets { get; } = new();
  
          /// <summary>
          /// Limit the extent to which the user can navigate
          /// </summary>
          public IViewportLimiter Limiter
          {
              get => _limiter;
              set
              {
                  if (!_limiter.Equals(value))
                  {
                      _limiter = value;
                      OnPropertyChanged(nameof(Limiter));
                  }
              }
          }
  
          /// <summary>
          /// Projection type of Map. Normally in format like "EPSG:3857"
          /// </summary>
          public string? CRS { get; set; }
  
          /// <summary>
          /// A collection of layers. The first layer in the list is drawn first, the last one on top.
          /// </summary>
          public LayerCollection Layers
          {
              get => _layers;
              private set
              {
                  var tempLayers = _layers;
                  if (tempLayers != null)
                      _layers.Changed -= LayersCollectionChanged;
  
                  _layers = value;
                  _layers.Changed += LayersCollectionChanged;
              }
          }
  
          [Obsolete("Use ILayer.IsMapInfoLayer instead", true)]
          public IList<ILayer> InfoLayers { get; } = new List<ILayer>();
  
          [Obsolete("Use your own hover event and call MapControl.GetMapInfo", true)]
          public IList<ILayer> HoverLayers { get; } = new List<ILayer>();
  
          /// <summary>
          /// Map background color (defaults to transparent)
          ///  </summary>
          public Color BackColor
          {
              get => _backColor;
              set
              {
                  if (_backColor == value) return;
                  _backColor = value;
                  OnPropertyChanged(nameof(BackColor));
              }
          }
  
          /// <summary>
          /// Gets the extent of the map based on the extent of all the layers in the layers collection
          /// </summary>
          /// <returns>Full map extent</returns>
          public MRect? Extent
          {
              get
              {
                  if (_layers.Count == 0) return null;
  
                  MRect? extent = null;
                  foreach (var layer in _layers)
                  {
                      extent = extent == null ? layer.Extent : extent.Join(layer.Extent);
                  }
                  return extent;
              }
          }
  
          /// <summary>
          /// List of all native resolutions of this map
          /// </summary>
          public IReadOnlyList<double> Resolutions { get; private set; } = new List<double>();
  
          /// <summary>
          /// Called whenever a property changed
          /// </summary>
          public event PropertyChangedEventHandler? PropertyChanged;
  
          /// <summary>
          /// DataChanged should be triggered by any data changes of any of the child layers
          /// </summary>
          public event DataChangedEventHandler? DataChanged;
  
  #pragma warning disable 67
          [Obsolete("Use PropertyChanged instead", true)]
          public event EventHandler? RefreshGraphics;
  #pragma warning restore 67
  
          /// <summary>
          /// Called whenever the map is clicked. The MapInfoEventArgs contain the features that were hit in
          /// the layers that have IsMapInfoLayer set to true. 
          /// </summary>
          public event EventHandler<MapInfoEventArgs>? Info;
  
          [Obsolete("Use your own hover event instead and call MapControl.GetMapInfo", true)]
  #pragma warning disable 67
          public event EventHandler<MapInfoEventArgs>? Hover;
  #pragma warning restore 67
  
          /// <summary>
          /// Abort fetching of all layers
          /// </summary>
          public void AbortFetch()
          {
              foreach (var layer in _layers.ToList())
              {
                  if (layer is IAsyncDataFetcher asyncLayer) asyncLayer.AbortFetch();
              }
          }
  
          /// <summary>
          /// Clear cache of all layers
          /// </summary>
          public void ClearCache()
          {
              foreach (var layer in _layers)
              {
                  if (layer is IAsyncDataFetcher asyncLayer) asyncLayer.ClearCache();
              }
          }
  
          public void RefreshData(FetchInfo fetchInfo)
          {
              foreach (var layer in _layers.ToList())
              {
                  layer.RefreshData(fetchInfo);
              }
          }
  
          private void LayersCollectionChanged(object sender, LayerCollectionChangedEventArgs args)
          {
              foreach (var layer in args.RemovedLayers ?? Enumerable.Empty<ILayer>())
                  LayerRemoved(layer);
  
              foreach (var layer in args.AddedLayers ?? Enumerable.Empty<ILayer>())
                  LayerAdded(layer);
  
              LayersChanged();
          }
  
          private void LayerAdded(ILayer layer)
          {
  
gt:             layer.DataChanged += LayerDataChanged;