using System;
using System.Drawing;
using GeoAPI.Geometries;
using NetTopologySuite.Geometries.Utilities;
using SharpMap.Utilities;
namespace SharpMap
{
///
/// A utility class, that encapsulates all data required for rendering.
///
/// This is a value class
public class MapViewport
{
private readonly Envelope _envelope;
private readonly Coordinate _center;
private Coordinate _centerOfInterest;
private readonly float[] _mapTransformElements;
private readonly float[] _mapTransformInvertedElements;
private readonly double[] _worldToMapElements;
private readonly double[] _worldToMapElementsCareAboutTransform;
private double _mapScale;
private int _lastDpi;
private readonly object _lockMapScale = new object();
///
/// Creates an instance of this class
///
/// The id of the map
/// The spatial reference
/// current map zoom
/// map height
/// The envelope containing the viewport
/// The size of the viewport
/// A ratio between width and height
/// An affine map transform matrix
/// The affine map transformation that inverts
/// The rotation in degrees applied by
public MapViewport(Guid mapId, int srid, double zoom, double mapHeight, Envelope env, Size size, double pixelAspectRatio,
System.Drawing.Drawing2D.Matrix mapTransform, System.Drawing.Drawing2D.Matrix mapTransformInverted,
float mapTransformRotation)
{
ID = mapId;
SRID = srid;
Zoom = zoom;
MapHeight = mapHeight;
_envelope = env.Copy();
Size = size;
_center = env.Centre;
PixelAspectRatio = pixelAspectRatio;
PixelWidth = Zoom / size.Width;
PixelHeight = PixelWidth * pixelAspectRatio;
_mapTransformElements = mapTransform.Elements;
_mapTransformInvertedElements = mapTransformInverted.Elements;
MapTransformRotation = mapTransformRotation;
// pre-calculated for use when MapTransformRotation == 0
Left = Center.X - Zoom * 0.5;
Top = Center.Y + mapHeight * 0.5;
// pre-defined for use when MapTransformation != 0
if (!mapTransformRotation.Equals(0f))
{
_worldToMapElements = Transform.WorldToMapMatrix(
Center, PixelWidth, PixelHeight, MapTransformRotation, Size, false).MatrixEntries;
_worldToMapElementsCareAboutTransform = Transform.WorldToMapMatrix(
Center, PixelWidth, PixelHeight, MapTransformRotation, Size, true).MatrixEntries;
}
}
///
/// Creates an instance of this class based on the provided map
///
/// The Map
public MapViewport(Map map)
: this(map.ID, map.SRID, map.Zoom, map.MapHeight, map.Envelope, map.Size, map.PixelAspectRatio,
map.MapTransform, map.MapTransformInverted, map.MapTransformRotation)
{
CenterOfInterest = map.CenterOfInterest;
}
///
/// Gets a value indicating the which this viewport belongs to.
///
public Guid ID { get; }
///
/// Gets a value indicating the spatial reference id of the map
///
public int SRID { get; /*set;*/ }
///
/// Gets a value indicating the size of the map
///
public Size Size { get; }
///
/// Gets the rectilinear extents of the map based on the current ,
/// , map , and (optionally) the
/// If a is applied, the envelope CONTAINING the rotated view
/// will be returned (used by layers to spatially select data) and the aspect ratio will NOT be the
/// same as map . If aspect ratio is important then refer to
/// and
///
public Envelope Envelope => _envelope.Copy();
///
/// Gets a value indicating the transformation that has to be applied when
/// rendering the map
///
public System.Drawing.Drawing2D.Matrix MapTransform
{
get
{
return new System.Drawing.Drawing2D.Matrix(
_mapTransformElements[0],
_mapTransformElements[1],
_mapTransformElements[2],
_mapTransformElements[3],
_mapTransformElements[4],
_mapTransformElements[5]
);
}
}
///
/// Gets a value indicating the inverse transformation that is applied when
/// rendering the map
///
public System.Drawing.Drawing2D.Matrix MapTransformInverted
{
get
{
return new System.Drawing.Drawing2D.Matrix(
_mapTransformInvertedElements[0],
_mapTransformInvertedElements[1],
_mapTransformInvertedElements[2],
_mapTransformInvertedElements[3],
_mapTransformInvertedElements[4],
_mapTransformInvertedElements[5]
);
}
}
///
/// Map rotation in degrees (defined by )
///
public float MapTransformRotation { get; }
///
/// Cached affine transformation used to transform world coordinates from apparent rotated coordinate frame (ie MapTransformRotation != 0)
/// to image space. Unlike MapTransform, this matrix defines a complete transformation from World to Image taking into account MapRotation.
/// 2 variants are available, depending on whether or not map rotation has already been applied.
///
internal AffineTransformation WorldToMapTransform(bool careAboutTransform)
{
if (careAboutTransform)
return new AffineTransformation(
_worldToMapElementsCareAboutTransform[0],
_worldToMapElementsCareAboutTransform[1],
_worldToMapElementsCareAboutTransform[2],
_worldToMapElementsCareAboutTransform[3],
_worldToMapElementsCareAboutTransform[4],
_worldToMapElementsCareAboutTransform[5]
);
else
return new AffineTransformation(
_worldToMapElements[0],
_worldToMapElements[1],
_worldToMapElements[2],
_worldToMapElements[3],
_worldToMapElements[4],
_worldToMapElements[5]
);
}
///
/// Gets a value indicating the center of the map viewport
///
public Coordinate Center => _center.Copy();
///
/// Gets a value indicating the center of the map viewport
///
public Coordinate CenterOfInterest
{
get => _centerOfInterest?.Copy() ?? Center;
set => _centerOfInterest = value;
}
///
/// Gets a value indicating the zoom of the map viewport
///
/// This value is identical to
public double Zoom { get; /*set;*/ }
///
/// Gets a value indicating the height of the map viewport in world units
///
public double MapHeight { get; }
///
/// Gets a value indicating the width of the map viewport in world units
///
/// This value is equal to
private double MapWidth => Zoom;
///
/// Applicable to non-rotated views only, returning the minimum X value of the map viewport in world units
///
public double Left { get; }
///
/// Applicable to non-rotated views only, returning the maximum Y value of the map viewport in world units
///
public double Top { get; }
///
/// 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 double PixelAspectRatio { get; }
///
/// Returns the width of a pixel in world coordinate units.
///
public double PixelWidth { get; }
///
/// Returns the height of a pixel in world coordinate units.
///
public double PixelHeight { get; }
///
/// Function to compute the denominator of the viewport's scale when using a given resolution.
///
/// The resolution
/// The scale's denominator
public double GetMapScale(int dpi)
{
if (_lastDpi != dpi)
{
lock (_lockMapScale)
if (_lastDpi != dpi)
{
_mapScale = ScaleCalculations.CalculateScaleNonLatLong(Zoom, Size.Width, 1, dpi);
_lastDpi = dpi;
}
}
return _mapScale;
}
///
/// Converts an array of world coordinates to image coordinates based on the current , ,
/// map , and (optionally) the .
///
/// Coordinate array in world coordinates
/// Indicates whether should be applied. True for typical coordinate calcs,
/// False when rendering to image as the Graphics object has already applied the MapTransform
/// PointF array in image coordinates
public PointF[] WorldToImage(Coordinate[] coordinates, bool careAboutMapTransform = false)
{
// see WorldToImage discussion in Map.cs. This is a slightly shortened form using cached values.
if (MapTransformRotation.Equals(0f))
return Transform.WorldToMap(coordinates, Left, Top, PixelWidth, PixelHeight);
var matrix = WorldToMapTransform(careAboutMapTransform);
return Transform.WorldToMap(coordinates, matrix);
}
///
/// Converts a point in world coordinates to image coordinates based on the current , ,
/// map , and (optionally) the .
///
/// Point in world coordinates
/// Indicates whether should be applied. When rendering to image,
/// the Graphics object has usually applied MapTransform
/// PointF in image coordinates
public PointF WorldToImage(Coordinate p, bool careAboutMapTransform = false)
{
var points = WorldToImage(new Coordinate[] {p}, careAboutMapTransform);
return points[0];
}
///
/// Converts a point array from image coordinates to world coordinates based on the current , ,
/// map , and (optionally) the .
///
/// Point array in image coordinates. Note: if you wish to preserve the input values then
/// you must clone the point array as it will be modified if a MapTransform is applied
/// Indicates whether should be applied.
/// Point array in world coordinates
public Coordinate[] ImageToWorld(PointF[] points, bool careAboutMapTransform = false)
{
if (careAboutMapTransform && !MapTransformRotation.Equals(0f))
using (var transformInv = MapTransformInverted)
transformInv.TransformPoints(points);
return Transform.MapToWorld(points, Center, Zoom, MapHeight, PixelWidth, PixelHeight);
}
///
/// Converts a point from image coordinates to world coordinates based on the current , ,
/// map , and (optionally) the .
///
/// Point in image coordinates. Note: if you wish to preserve the input value then
/// you must clone the point as it will be modified if a MapTransform is applied
/// Indicates whether should be applied.
/// Point in world coordinates
public Coordinate ImageToWorld(PointF p, bool careAboutMapTransform = false)
{
var pts = ImageToWorld(new PointF[] {p}, careAboutMapTransform);
return pts[0];
}
///
/// Creates a map viewport from a given map
///
/// The map
///
public static implicit operator MapViewport(Map map)
{
return new MapViewport(map);
}
}
}