File scoped namespaces

This commit is contained in:
ClemensFischer 2026-04-13 17:14:49 +02:00
parent c14377f976
commit 65aba44af6
152 changed files with 11962 additions and 12115 deletions

View file

@ -6,169 +6,168 @@ using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace MapControl
namespace MapControl;
public class BitmapTile(int zoomLevel, int x, int y, int columnCount, int width, int height)
: Tile(zoomLevel, x, y, columnCount)
{
public class BitmapTile(int zoomLevel, int x, int y, int columnCount, int width, int height)
: Tile(zoomLevel, x, y, columnCount)
public event EventHandler Completed;
public byte[] PixelBuffer { get; set; }
public override async Task LoadImageAsync(Func<Task<ImageSource>> loadImageFunc)
{
public event EventHandler Completed;
var image = await loadImageFunc().ConfigureAwait(false);
public byte[] PixelBuffer { get; set; }
public override async Task LoadImageAsync(Func<Task<ImageSource>> loadImageFunc)
if (image is BitmapSource bitmap)
{
var image = await loadImageFunc().ConfigureAwait(false);
if (image is BitmapSource bitmap)
if (bitmap.Format != PixelFormats.Pbgra32)
{
if (bitmap.Format != PixelFormats.Pbgra32)
{
bitmap = new FormatConvertedBitmap(bitmap, PixelFormats.Pbgra32, null, 0d);
}
PixelBuffer = new byte[4 * width * height];
bitmap.CopyPixels(PixelBuffer, 4 * width, 0);
bitmap = new FormatConvertedBitmap(bitmap, PixelFormats.Pbgra32, null, 0d);
}
Completed?.Invoke(this, EventArgs.Empty);
PixelBuffer = new byte[4 * width * height];
bitmap.CopyPixels(PixelBuffer, 4 * width, 0);
}
Completed?.Invoke(this, EventArgs.Empty);
}
}
public class BitmapTileMatrixLayer(WmtsTileMatrix wmtsTileMatrix, int zoomLevel) : UIElement
{
private readonly MatrixTransform transform = new MatrixTransform();
private WriteableBitmap bitmap;
public WmtsTileMatrix WmtsTileMatrix => wmtsTileMatrix;
public TileMatrix TileMatrix { get; private set; } = new TileMatrix(zoomLevel, 1, 1, 0, 0);
public IEnumerable<BitmapTile> Tiles { get; private set; } = [];
protected override void OnRender(DrawingContext drawingContext)
{
drawingContext.PushTransform(transform);
drawingContext.DrawImage(bitmap, new Rect(0d, 0d, bitmap.PixelWidth, bitmap.PixelHeight));
}
public class BitmapTileMatrixLayer(WmtsTileMatrix wmtsTileMatrix, int zoomLevel) : UIElement
public void UpdateRenderTransform(ViewTransform viewTransform)
{
private readonly MatrixTransform transform = new MatrixTransform();
// Tile matrix origin in pixels.
//
var tileMatrixOrigin = new Point(WmtsTileMatrix.TileWidth * TileMatrix.XMin, WmtsTileMatrix.TileHeight * TileMatrix.YMin);
private WriteableBitmap bitmap;
transform.Matrix = viewTransform.GetTileLayerTransform(WmtsTileMatrix.Scale, WmtsTileMatrix.TopLeft, tileMatrixOrigin);
}
public WmtsTileMatrix WmtsTileMatrix => wmtsTileMatrix;
public bool UpdateTiles(ViewTransform viewTransform, double viewWidth, double viewHeight)
{
// Tile matrix bounds in pixels.
//
var bounds = viewTransform.GetTileMatrixBounds(WmtsTileMatrix.Scale, WmtsTileMatrix.TopLeft, viewWidth, viewHeight);
public TileMatrix TileMatrix { get; private set; } = new TileMatrix(zoomLevel, 1, 1, 0, 0);
// Tile X and Y bounds.
//
var xMin = (int)Math.Floor(bounds.X / WmtsTileMatrix.TileWidth);
var yMin = (int)Math.Floor(bounds.Y / WmtsTileMatrix.TileHeight);
var xMax = (int)Math.Floor((bounds.X + bounds.Width) / WmtsTileMatrix.TileWidth);
var yMax = (int)Math.Floor((bounds.Y + bounds.Height) / WmtsTileMatrix.TileHeight);
public IEnumerable<BitmapTile> Tiles { get; private set; } = [];
protected override void OnRender(DrawingContext drawingContext)
if (!WmtsTileMatrix.HasFullHorizontalCoverage)
{
drawingContext.PushTransform(transform);
drawingContext.DrawImage(bitmap, new Rect(0d, 0d, bitmap.PixelWidth, bitmap.PixelHeight));
// Set X range limits.
//
xMin = Math.Max(xMin, 0);
xMax = Math.Min(Math.Max(xMax, 0), WmtsTileMatrix.MatrixWidth - 1);
}
public void UpdateRenderTransform(ViewTransform viewTransform)
{
// Tile matrix origin in pixels.
//
var tileMatrixOrigin = new Point(WmtsTileMatrix.TileWidth * TileMatrix.XMin, WmtsTileMatrix.TileHeight * TileMatrix.YMin);
// Set Y range limits.
//
yMin = Math.Max(yMin, 0);
yMax = Math.Min(Math.Max(yMax, 0), WmtsTileMatrix.MatrixHeight - 1);
transform.Matrix = viewTransform.GetTileLayerTransform(WmtsTileMatrix.Scale, WmtsTileMatrix.TopLeft, tileMatrixOrigin);
if (TileMatrix.XMin == xMin && TileMatrix.YMin == yMin &&
TileMatrix.XMax == xMax && TileMatrix.YMax == yMax)
{
// No change of the TileMatrix and the Tiles collection.
//
return false;
}
public bool UpdateTiles(ViewTransform viewTransform, double viewWidth, double viewHeight)
TileMatrix = new TileMatrix(TileMatrix.ZoomLevel, xMin, yMin, xMax, yMax);
bitmap = new WriteableBitmap(
WmtsTileMatrix.TileWidth * TileMatrix.Width,
WmtsTileMatrix.TileHeight * TileMatrix.Height,
96, 96, PixelFormats.Pbgra32, null);
CreateTiles();
InvalidateVisual();
return true;
}
private void CreateTiles()
{
var tiles = new List<BitmapTile>(TileMatrix.Width * TileMatrix.Height);
for (var y = TileMatrix.YMin; y <= TileMatrix.YMax; y++)
{
// Tile matrix bounds in pixels.
//
var bounds = viewTransform.GetTileMatrixBounds(WmtsTileMatrix.Scale, WmtsTileMatrix.TopLeft, viewWidth, viewHeight);
// Tile X and Y bounds.
//
var xMin = (int)Math.Floor(bounds.X / WmtsTileMatrix.TileWidth);
var yMin = (int)Math.Floor(bounds.Y / WmtsTileMatrix.TileHeight);
var xMax = (int)Math.Floor((bounds.X + bounds.Width) / WmtsTileMatrix.TileWidth);
var yMax = (int)Math.Floor((bounds.Y + bounds.Height) / WmtsTileMatrix.TileHeight);
if (!WmtsTileMatrix.HasFullHorizontalCoverage)
for (var x = TileMatrix.XMin; x <= TileMatrix.XMax; x++)
{
// Set X range limits.
//
xMin = Math.Max(xMin, 0);
xMax = Math.Min(Math.Max(xMax, 0), WmtsTileMatrix.MatrixWidth - 1);
}
var tile = Tiles.FirstOrDefault(t => t.X == x && t.Y == y);
// Set Y range limits.
//
yMin = Math.Max(yMin, 0);
yMax = Math.Min(Math.Max(yMax, 0), WmtsTileMatrix.MatrixHeight - 1);
if (TileMatrix.XMin == xMin && TileMatrix.YMin == yMin &&
TileMatrix.XMax == xMax && TileMatrix.YMax == yMax)
{
// No change of the TileMatrix and the Tiles collection.
//
return false;
}
TileMatrix = new TileMatrix(TileMatrix.ZoomLevel, xMin, yMin, xMax, yMax);
bitmap = new WriteableBitmap(
WmtsTileMatrix.TileWidth * TileMatrix.Width,
WmtsTileMatrix.TileHeight * TileMatrix.Height,
96, 96, PixelFormats.Pbgra32, null);
CreateTiles();
InvalidateVisual();
return true;
}
private void CreateTiles()
{
var tiles = new List<BitmapTile>(TileMatrix.Width * TileMatrix.Height);
for (var y = TileMatrix.YMin; y <= TileMatrix.YMax; y++)
{
for (var x = TileMatrix.XMin; x <= TileMatrix.XMax; x++)
if (tile == null)
{
var tile = Tiles.FirstOrDefault(t => t.X == x && t.Y == y);
tile = new BitmapTile(TileMatrix.ZoomLevel, x, y, WmtsTileMatrix.MatrixWidth, WmtsTileMatrix.TileWidth, WmtsTileMatrix.TileHeight);
if (tile == null)
var equivalentTile = Tiles.FirstOrDefault(t => t.PixelBuffer != null && t.Column == tile.Column && t.Row == tile.Row);
if (equivalentTile != null)
{
tile = new BitmapTile(TileMatrix.ZoomLevel, x, y, WmtsTileMatrix.MatrixWidth, WmtsTileMatrix.TileWidth, WmtsTileMatrix.TileHeight);
var equivalentTile = Tiles.FirstOrDefault(t => t.PixelBuffer != null && t.Column == tile.Column && t.Row == tile.Row);
if (equivalentTile != null)
{
tile.IsPending = false;
tile.PixelBuffer = equivalentTile.PixelBuffer;
}
else
{
tile.Completed += OnTileCompleted;
}
tile.IsPending = false;
tile.PixelBuffer = equivalentTile.PixelBuffer;
}
if (tile.PixelBuffer != null)
else
{
CopyTile(tile);
tile.Completed += OnTileCompleted;
}
tiles.Add(tile);
}
}
Tiles = tiles;
if (tile.PixelBuffer != null)
{
CopyTile(tile);
}
tiles.Add(tile);
}
}
private void CopyTile(BitmapTile tile)
Tiles = tiles;
}
private void CopyTile(BitmapTile tile)
{
var width = WmtsTileMatrix.TileWidth;
var height = WmtsTileMatrix.TileHeight;
var x = width * (tile.X - TileMatrix.XMin);
var y = height * (tile.Y - TileMatrix.YMin);
bitmap.WritePixels(new Int32Rect(x, y, width, height), tile.PixelBuffer, 4 * WmtsTileMatrix.TileWidth, 0);
}
private void OnTileCompleted(object sender, EventArgs e)
{
var tile = (BitmapTile)sender;
tile.Completed -= OnTileCompleted;
if (tile.X >= TileMatrix.XMin && tile.X <= TileMatrix.XMax &&
tile.Y >= TileMatrix.YMin && tile.Y <= TileMatrix.YMax &&
tile.PixelBuffer != null)
{
var width = WmtsTileMatrix.TileWidth;
var height = WmtsTileMatrix.TileHeight;
var x = width * (tile.X - TileMatrix.XMin);
var y = height * (tile.Y - TileMatrix.YMin);
bitmap.WritePixels(new Int32Rect(x, y, width, height), tile.PixelBuffer, 4 * WmtsTileMatrix.TileWidth, 0);
}
private void OnTileCompleted(object sender, EventArgs e)
{
var tile = (BitmapTile)sender;
tile.Completed -= OnTileCompleted;
if (tile.X >= TileMatrix.XMin && tile.X <= TileMatrix.XMax &&
tile.Y >= TileMatrix.YMin && tile.Y <= TileMatrix.YMax &&
tile.PixelBuffer != null)
{
_ = Dispatcher.InvokeAsync(() => CopyTile(tile));
}
_ = Dispatcher.InvokeAsync(() => CopyTile(tile));
}
}
}

View file

@ -1,128 +1,127 @@
using System;
using System.Windows;
namespace MapControl
namespace MapControl;
public static class DependencyPropertyHelper
{
public static class DependencyPropertyHelper
public static DependencyProperty RegisterAttached<TValue>(
string name,
Type ownerType,
TValue defaultValue = default,
Action<FrameworkElement, TValue, TValue> changed = null,
bool inherits = false)
{
public static DependencyProperty RegisterAttached<TValue>(
string name,
Type ownerType,
TValue defaultValue = default,
Action<FrameworkElement, TValue, TValue> changed = null,
bool inherits = false)
var metadata = new FrameworkPropertyMetadata
{
var metadata = new FrameworkPropertyMetadata
{
DefaultValue = defaultValue,
Inherits = inherits
};
DefaultValue = defaultValue,
Inherits = inherits
};
if (changed != null)
if (changed != null)
{
metadata.PropertyChangedCallback = (o, e) =>
{
metadata.PropertyChangedCallback = (o, e) =>
if (o is FrameworkElement element)
{
if (o is FrameworkElement element)
{
changed(element, (TValue)e.OldValue, (TValue)e.NewValue);
}
};
}
return DependencyProperty.RegisterAttached(name, typeof(TValue), ownerType, metadata);
}
public static DependencyProperty RegisterAttached<TValue>(
string name,
Type ownerType,
TValue defaultValue,
FrameworkPropertyMetadataOptions options)
{
var metadata = new FrameworkPropertyMetadata(defaultValue, options);
return DependencyProperty.RegisterAttached(name, typeof(TValue), ownerType, metadata);
}
public static DependencyProperty Register<TOwner, TValue>(
string name,
TValue defaultValue,
FrameworkPropertyMetadataOptions options)
where TOwner : DependencyObject
{
var metadata = new FrameworkPropertyMetadata(defaultValue, options);
return DependencyProperty.Register(name, typeof(TValue), typeof(TOwner), metadata);
}
public static DependencyProperty Register<TOwner, TValue>(
string name,
TValue defaultValue = default,
Action<TOwner, TValue, TValue> changed = null,
Func<TOwner, TValue, TValue> coerce = null,
bool bindTwoWayByDefault = false)
where TOwner : DependencyObject
{
var metadata = new FrameworkPropertyMetadata
{
DefaultValue = defaultValue,
BindsTwoWayByDefault = bindTwoWayByDefault
changed(element, (TValue)e.OldValue, (TValue)e.NewValue);
}
};
if (changed != null)
{
metadata.PropertyChangedCallback = (o, e) => changed((TOwner)o, (TValue)e.OldValue, (TValue)e.NewValue);
}
if (coerce != null)
{
metadata.CoerceValueCallback = (o, v) => coerce((TOwner)o, (TValue)v);
}
return DependencyProperty.Register(name, typeof(TValue), typeof(TOwner), metadata);
}
public static DependencyPropertyKey RegisterReadOnly<TOwner, TValue>(
string name,
TValue defaultValue = default)
where TOwner : DependencyObject
return DependencyProperty.RegisterAttached(name, typeof(TValue), ownerType, metadata);
}
public static DependencyProperty RegisterAttached<TValue>(
string name,
Type ownerType,
TValue defaultValue,
FrameworkPropertyMetadataOptions options)
{
var metadata = new FrameworkPropertyMetadata(defaultValue, options);
return DependencyProperty.RegisterAttached(name, typeof(TValue), ownerType, metadata);
}
public static DependencyProperty Register<TOwner, TValue>(
string name,
TValue defaultValue,
FrameworkPropertyMetadataOptions options)
where TOwner : DependencyObject
{
var metadata = new FrameworkPropertyMetadata(defaultValue, options);
return DependencyProperty.Register(name, typeof(TValue), typeof(TOwner), metadata);
}
public static DependencyProperty Register<TOwner, TValue>(
string name,
TValue defaultValue = default,
Action<TOwner, TValue, TValue> changed = null,
Func<TOwner, TValue, TValue> coerce = null,
bool bindTwoWayByDefault = false)
where TOwner : DependencyObject
{
var metadata = new FrameworkPropertyMetadata
{
return DependencyProperty.RegisterReadOnly(name, typeof(TValue), typeof(TOwner), new PropertyMetadata(defaultValue));
DefaultValue = defaultValue,
BindsTwoWayByDefault = bindTwoWayByDefault
};
if (changed != null)
{
metadata.PropertyChangedCallback = (o, e) => changed((TOwner)o, (TValue)e.OldValue, (TValue)e.NewValue);
}
public static DependencyProperty AddOwner<TOwner, TValue>(
DependencyProperty source) where TOwner : DependencyObject
if (coerce != null)
{
return source.AddOwner(typeof(TOwner));
metadata.CoerceValueCallback = (o, v) => coerce((TOwner)o, (TValue)v);
}
public static DependencyProperty AddOwner<TOwner, TValue>(
DependencyProperty source,
TValue defaultValue) where TOwner : DependencyObject
{
return source.AddOwner(typeof(TOwner), new FrameworkPropertyMetadata(defaultValue));
}
return DependencyProperty.Register(name, typeof(TValue), typeof(TOwner), metadata);
}
public static DependencyProperty AddOwner<TOwner, TValue>(
DependencyProperty source,
Action<TOwner, TValue, TValue> changed) where TOwner : DependencyObject
{
return source.AddOwner(typeof(TOwner), new FrameworkPropertyMetadata(
(o, e) => changed((TOwner)o, (TValue)e.OldValue, (TValue)e.NewValue)));
}
public static DependencyPropertyKey RegisterReadOnly<TOwner, TValue>(
string name,
TValue defaultValue = default)
where TOwner : DependencyObject
{
return DependencyProperty.RegisterReadOnly(name, typeof(TValue), typeof(TOwner), new PropertyMetadata(defaultValue));
}
public static DependencyProperty AddOwner<TOwner, TValue>(
string _, // for compatibility with WinUI/UWP DependencyPropertyHelper
DependencyProperty source) where TOwner : DependencyObject
{
return AddOwner<TOwner, TValue>(source);
}
public static DependencyProperty AddOwner<TOwner, TValue>(
DependencyProperty source) where TOwner : DependencyObject
{
return source.AddOwner(typeof(TOwner));
}
public static DependencyProperty AddOwner<TOwner, TValue>(
string _, // for compatibility with WinUI/UWP DependencyPropertyHelper
DependencyProperty source,
Action<TOwner, TValue, TValue> changed) where TOwner : DependencyObject
{
return AddOwner(source, changed);
}
public static DependencyProperty AddOwner<TOwner, TValue>(
DependencyProperty source,
TValue defaultValue) where TOwner : DependencyObject
{
return source.AddOwner(typeof(TOwner), new FrameworkPropertyMetadata(defaultValue));
}
public static DependencyProperty AddOwner<TOwner, TValue>(
DependencyProperty source,
Action<TOwner, TValue, TValue> changed) where TOwner : DependencyObject
{
return source.AddOwner(typeof(TOwner), new FrameworkPropertyMetadata(
(o, e) => changed((TOwner)o, (TValue)e.OldValue, (TValue)e.NewValue)));
}
public static DependencyProperty AddOwner<TOwner, TValue>(
string _, // for compatibility with WinUI/UWP DependencyPropertyHelper
DependencyProperty source) where TOwner : DependencyObject
{
return AddOwner<TOwner, TValue>(source);
}
public static DependencyProperty AddOwner<TOwner, TValue>(
string _, // for compatibility with WinUI/UWP DependencyPropertyHelper
DependencyProperty source,
Action<TOwner, TValue, TValue> changed) where TOwner : DependencyObject
{
return AddOwner(source, changed);
}
}

View file

@ -4,75 +4,74 @@ using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;
namespace MapControl
namespace MapControl;
public class DrawingTile : Tile
{
public class DrawingTile : Tile
public DrawingTile(int zoomLevel, int x, int y, int columnCount)
: base(zoomLevel, x, y, columnCount)
{
public DrawingTile(int zoomLevel, int x, int y, int columnCount)
: base(zoomLevel, x, y, columnCount)
Drawing.Children.Add(ImageDrawing);
}
public DrawingGroup Drawing { get; } = new DrawingGroup();
public ImageDrawing ImageDrawing { get; } = new ImageDrawing();
public override async Task LoadImageAsync(Func<Task<ImageSource>> loadImageFunc)
{
var image = await loadImageFunc().ConfigureAwait(false);
void SetImageSource()
{
Drawing.Children.Add(ImageDrawing);
}
ImageDrawing.ImageSource = image;
public DrawingGroup Drawing { get; } = new DrawingGroup();
public ImageDrawing ImageDrawing { get; } = new ImageDrawing();
public override async Task LoadImageAsync(Func<Task<ImageSource>> loadImageFunc)
{
var image = await loadImageFunc().ConfigureAwait(false);
void SetImageSource()
if (image != null && MapBase.ImageFadeDuration > TimeSpan.Zero)
{
ImageDrawing.ImageSource = image;
if (image != null && MapBase.ImageFadeDuration > TimeSpan.Zero)
if (image is BitmapSource bitmap && !bitmap.IsFrozen && bitmap.IsDownloading)
{
if (image is BitmapSource bitmap && !bitmap.IsFrozen && bitmap.IsDownloading)
{
bitmap.DownloadCompleted += BitmapDownloadCompleted;
bitmap.DownloadFailed += BitmapDownloadFailed;
}
else
{
BeginFadeInAnimation();
}
bitmap.DownloadCompleted += BitmapDownloadCompleted;
bitmap.DownloadFailed += BitmapDownloadFailed;
}
else
{
BeginFadeInAnimation();
}
}
await Drawing.Dispatcher.InvokeAsync(SetImageSource);
}
private void BeginFadeInAnimation()
await Drawing.Dispatcher.InvokeAsync(SetImageSource);
}
private void BeginFadeInAnimation()
{
var fadeInAnimation = new DoubleAnimation
{
var fadeInAnimation = new DoubleAnimation
{
From = 0d,
Duration = MapBase.ImageFadeDuration,
FillBehavior = FillBehavior.Stop
};
From = 0d,
Duration = MapBase.ImageFadeDuration,
FillBehavior = FillBehavior.Stop
};
Drawing.BeginAnimation(DrawingGroup.OpacityProperty, fadeInAnimation);
}
Drawing.BeginAnimation(DrawingGroup.OpacityProperty, fadeInAnimation);
}
private void BitmapDownloadCompleted(object sender, EventArgs e)
{
var bitmap = (BitmapSource)sender;
private void BitmapDownloadCompleted(object sender, EventArgs e)
{
var bitmap = (BitmapSource)sender;
bitmap.DownloadCompleted -= BitmapDownloadCompleted;
bitmap.DownloadFailed -= BitmapDownloadFailed;
bitmap.DownloadCompleted -= BitmapDownloadCompleted;
bitmap.DownloadFailed -= BitmapDownloadFailed;
BeginFadeInAnimation();
}
BeginFadeInAnimation();
}
private void BitmapDownloadFailed(object sender, ExceptionEventArgs e)
{
var bitmap = (BitmapSource)sender;
private void BitmapDownloadFailed(object sender, ExceptionEventArgs e)
{
var bitmap = (BitmapSource)sender;
bitmap.DownloadCompleted -= BitmapDownloadCompleted;
bitmap.DownloadFailed -= BitmapDownloadFailed;
bitmap.DownloadCompleted -= BitmapDownloadCompleted;
bitmap.DownloadFailed -= BitmapDownloadFailed;
ImageDrawing.ImageSource = null;
}
ImageDrawing.ImageSource = null;
}
}

View file

@ -4,113 +4,112 @@ using System.Linq;
using System.Windows;
using System.Windows.Media;
namespace MapControl
namespace MapControl;
public class DrawingTileMatrixLayer(WmtsTileMatrix wmtsTileMatrix, int zoomLevel) : UIElement
{
public class DrawingTileMatrixLayer(WmtsTileMatrix wmtsTileMatrix, int zoomLevel) : UIElement
private readonly MatrixTransform transform = new MatrixTransform();
public WmtsTileMatrix WmtsTileMatrix => wmtsTileMatrix;
public TileMatrix TileMatrix { get; private set; } = new TileMatrix(zoomLevel, 1, 1, 0, 0);
public IEnumerable<DrawingTile> Tiles { get; private set; } = [];
protected override void OnRender(DrawingContext drawingContext)
{
private readonly MatrixTransform transform = new MatrixTransform();
drawingContext.PushTransform(transform);
public WmtsTileMatrix WmtsTileMatrix => wmtsTileMatrix;
public TileMatrix TileMatrix { get; private set; } = new TileMatrix(zoomLevel, 1, 1, 0, 0);
public IEnumerable<DrawingTile> Tiles { get; private set; } = [];
protected override void OnRender(DrawingContext drawingContext)
foreach (var tile in Tiles)
{
drawingContext.PushTransform(transform);
foreach (var tile in Tiles)
{
drawingContext.DrawDrawing(tile.Drawing);
}
}
public void UpdateRenderTransform(ViewTransform viewTransform)
{
// Tile matrix origin in pixels.
//
var tileMatrixOrigin = new Point(WmtsTileMatrix.TileWidth * TileMatrix.XMin, WmtsTileMatrix.TileHeight * TileMatrix.YMin);
transform.Matrix = viewTransform.GetTileLayerTransform(WmtsTileMatrix.Scale, WmtsTileMatrix.TopLeft, tileMatrixOrigin);
}
public bool UpdateTiles(ViewTransform viewTransform, double viewWidth, double viewHeight)
{
// Tile matrix bounds in pixels.
//
var bounds = viewTransform.GetTileMatrixBounds(WmtsTileMatrix.Scale, WmtsTileMatrix.TopLeft, viewWidth, viewHeight);
// Tile X and Y bounds.
//
var xMin = (int)Math.Floor(bounds.X / WmtsTileMatrix.TileWidth);
var yMin = (int)Math.Floor(bounds.Y / WmtsTileMatrix.TileHeight);
var xMax = (int)Math.Floor((bounds.X + bounds.Width) / WmtsTileMatrix.TileWidth);
var yMax = (int)Math.Floor((bounds.Y + bounds.Height) / WmtsTileMatrix.TileHeight);
if (!WmtsTileMatrix.HasFullHorizontalCoverage)
{
// Set X range limits.
//
xMin = Math.Max(xMin, 0);
xMax = Math.Min(Math.Max(xMax, 0), WmtsTileMatrix.MatrixWidth - 1);
}
// Set Y range limits.
//
yMin = Math.Max(yMin, 0);
yMax = Math.Min(Math.Max(yMax, 0), WmtsTileMatrix.MatrixHeight - 1);
if (TileMatrix.XMin == xMin && TileMatrix.YMin == yMin &&
TileMatrix.XMax == xMax && TileMatrix.YMax == yMax)
{
// No change of the TileMatrix and the Tiles collection.
//
return false;
}
TileMatrix = new TileMatrix(TileMatrix.ZoomLevel, xMin, yMin, xMax, yMax);
CreateTiles();
InvalidateVisual();
return true;
}
private void CreateTiles()
{
var tiles = new List<DrawingTile>(TileMatrix.Width * TileMatrix.Height);
for (var y = TileMatrix.YMin; y <= TileMatrix.YMax; y++)
{
for (var x = TileMatrix.XMin; x <= TileMatrix.XMax; x++)
{
var tile = Tiles.FirstOrDefault(t => t.X == x && t.Y == y);
if (tile == null)
{
tile = new DrawingTile(TileMatrix.ZoomLevel, x, y, WmtsTileMatrix.MatrixWidth);
var equivalentTile = Tiles.FirstOrDefault(t => t.ImageDrawing.ImageSource != null && t.Column == tile.Column && t.Row == tile.Row);
if (equivalentTile != null)
{
tile.IsPending = false;
tile.ImageDrawing.ImageSource = equivalentTile.ImageDrawing.ImageSource; // no Opacity animation
}
}
tile.ImageDrawing.Rect = new Rect(
WmtsTileMatrix.TileWidth * (x - TileMatrix.XMin),
WmtsTileMatrix.TileHeight * (y - TileMatrix.YMin),
WmtsTileMatrix.TileWidth,
WmtsTileMatrix.TileHeight);
tiles.Add(tile);
}
}
Tiles = tiles;
drawingContext.DrawDrawing(tile.Drawing);
}
}
public void UpdateRenderTransform(ViewTransform viewTransform)
{
// Tile matrix origin in pixels.
//
var tileMatrixOrigin = new Point(WmtsTileMatrix.TileWidth * TileMatrix.XMin, WmtsTileMatrix.TileHeight * TileMatrix.YMin);
transform.Matrix = viewTransform.GetTileLayerTransform(WmtsTileMatrix.Scale, WmtsTileMatrix.TopLeft, tileMatrixOrigin);
}
public bool UpdateTiles(ViewTransform viewTransform, double viewWidth, double viewHeight)
{
// Tile matrix bounds in pixels.
//
var bounds = viewTransform.GetTileMatrixBounds(WmtsTileMatrix.Scale, WmtsTileMatrix.TopLeft, viewWidth, viewHeight);
// Tile X and Y bounds.
//
var xMin = (int)Math.Floor(bounds.X / WmtsTileMatrix.TileWidth);
var yMin = (int)Math.Floor(bounds.Y / WmtsTileMatrix.TileHeight);
var xMax = (int)Math.Floor((bounds.X + bounds.Width) / WmtsTileMatrix.TileWidth);
var yMax = (int)Math.Floor((bounds.Y + bounds.Height) / WmtsTileMatrix.TileHeight);
if (!WmtsTileMatrix.HasFullHorizontalCoverage)
{
// Set X range limits.
//
xMin = Math.Max(xMin, 0);
xMax = Math.Min(Math.Max(xMax, 0), WmtsTileMatrix.MatrixWidth - 1);
}
// Set Y range limits.
//
yMin = Math.Max(yMin, 0);
yMax = Math.Min(Math.Max(yMax, 0), WmtsTileMatrix.MatrixHeight - 1);
if (TileMatrix.XMin == xMin && TileMatrix.YMin == yMin &&
TileMatrix.XMax == xMax && TileMatrix.YMax == yMax)
{
// No change of the TileMatrix and the Tiles collection.
//
return false;
}
TileMatrix = new TileMatrix(TileMatrix.ZoomLevel, xMin, yMin, xMax, yMax);
CreateTiles();
InvalidateVisual();
return true;
}
private void CreateTiles()
{
var tiles = new List<DrawingTile>(TileMatrix.Width * TileMatrix.Height);
for (var y = TileMatrix.YMin; y <= TileMatrix.YMax; y++)
{
for (var x = TileMatrix.XMin; x <= TileMatrix.XMax; x++)
{
var tile = Tiles.FirstOrDefault(t => t.X == x && t.Y == y);
if (tile == null)
{
tile = new DrawingTile(TileMatrix.ZoomLevel, x, y, WmtsTileMatrix.MatrixWidth);
var equivalentTile = Tiles.FirstOrDefault(t => t.ImageDrawing.ImageSource != null && t.Column == tile.Column && t.Row == tile.Row);
if (equivalentTile != null)
{
tile.IsPending = false;
tile.ImageDrawing.ImageSource = equivalentTile.ImageDrawing.ImageSource; // no Opacity animation
}
}
tile.ImageDrawing.Rect = new Rect(
WmtsTileMatrix.TileWidth * (x - TileMatrix.XMin),
WmtsTileMatrix.TileHeight * (y - TileMatrix.YMin),
WmtsTileMatrix.TileWidth,
WmtsTileMatrix.TileHeight);
tiles.Add(tile);
}
}
Tiles = tiles;
}
}

View file

@ -5,113 +5,112 @@ using System.Threading.Tasks;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace MapControl
namespace MapControl;
public static partial class GeoImage
{
public static partial class GeoImage
private static Task<GeoBitmap> LoadGeoTiff(string sourcePath)
{
private static Task<GeoBitmap> LoadGeoTiff(string sourcePath)
return Task.Run(() =>
{
return Task.Run(() =>
BitmapSource bitmap;
Matrix transform;
MapProjection projection = null;
using (var stream = File.OpenRead(sourcePath))
{
BitmapSource bitmap;
Matrix transform;
MapProjection projection = null;
bitmap = BitmapFrame.Create(stream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
}
using (var stream = File.OpenRead(sourcePath))
{
bitmap = BitmapFrame.Create(stream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
}
var metadata = (BitmapMetadata)bitmap.Metadata;
var metadata = (BitmapMetadata)bitmap.Metadata;
if (metadata.GetQuery(QueryString(ModelPixelScaleTag)) is double[] pixelScale &&
pixelScale.Length == 3 &&
metadata.GetQuery(QueryString(ModelTiePointTag)) is double[] tiePoint &&
tiePoint.Length >= 6)
{
transform = new Matrix(pixelScale[0], 0d, 0d, -pixelScale[1], tiePoint[3], tiePoint[4]);
}
else if (metadata.GetQuery(QueryString(ModelTransformationTag)) is double[] transformValues &&
transformValues.Length == 16)
{
transform = new Matrix(transformValues[0], transformValues[1],
transformValues[4], transformValues[5],
transformValues[3], transformValues[7]);
}
else
{
throw new ArgumentException("No coordinate transformation found.");
}
if (metadata.GetQuery(QueryString(ModelPixelScaleTag)) is double[] pixelScale &&
pixelScale.Length == 3 &&
metadata.GetQuery(QueryString(ModelTiePointTag)) is double[] tiePoint &&
tiePoint.Length >= 6)
{
transform = new Matrix(pixelScale[0], 0d, 0d, -pixelScale[1], tiePoint[3], tiePoint[4]);
}
else if (metadata.GetQuery(QueryString(ModelTransformationTag)) is double[] transformValues &&
transformValues.Length == 16)
{
transform = new Matrix(transformValues[0], transformValues[1],
transformValues[4], transformValues[5],
transformValues[3], transformValues[7]);
}
else
{
throw new ArgumentException("No coordinate transformation found.");
}
if (metadata.GetQuery(QueryString(GeoKeyDirectoryTag)) is short[] geoKeyDirectory)
{
projection = GetProjection(geoKeyDirectory);
}
if (metadata.GetQuery(QueryString(GeoKeyDirectoryTag)) is short[] geoKeyDirectory)
{
projection = GetProjection(geoKeyDirectory);
}
if (metadata.GetQuery(QueryString(NoDataTag)) is string noData &&
int.TryParse(noData, out int noDataValue))
{
bitmap = ConvertTransparentPixel(bitmap, noDataValue);
}
if (metadata.GetQuery(QueryString(NoDataTag)) is string noData &&
int.TryParse(noData, out int noDataValue))
{
bitmap = ConvertTransparentPixel(bitmap, noDataValue);
}
return new GeoBitmap(bitmap, transform, projection);
});
}
return new GeoBitmap(bitmap, transform, projection);
});
private static BitmapSource ConvertTransparentPixel(BitmapSource source, int transparentPixel)
{
BitmapPalette sourcePalette = null;
var targetFormat = source.Format;
if (source.Format == PixelFormats.Indexed8 ||
source.Format == PixelFormats.Indexed4 ||
source.Format == PixelFormats.Indexed2 ||
source.Format == PixelFormats.Indexed1)
{
sourcePalette = source.Palette;
}
else if (source.Format == PixelFormats.Gray8)
{
sourcePalette = BitmapPalettes.Gray256;
targetFormat = PixelFormats.Indexed8;
}
else if (source.Format == PixelFormats.Gray4)
{
sourcePalette = BitmapPalettes.Gray16;
targetFormat = PixelFormats.Indexed4;
}
else if (source.Format == PixelFormats.Gray2)
{
sourcePalette = BitmapPalettes.Gray4;
targetFormat = PixelFormats.Indexed2;
}
else if (source.Format == PixelFormats.BlackWhite)
{
sourcePalette = BitmapPalettes.BlackAndWhite;
targetFormat = PixelFormats.Indexed1;
}
private static BitmapSource ConvertTransparentPixel(BitmapSource source, int transparentPixel)
if (sourcePalette == null || transparentPixel >= sourcePalette.Colors.Count)
{
BitmapPalette sourcePalette = null;
var targetFormat = source.Format;
if (source.Format == PixelFormats.Indexed8 ||
source.Format == PixelFormats.Indexed4 ||
source.Format == PixelFormats.Indexed2 ||
source.Format == PixelFormats.Indexed1)
{
sourcePalette = source.Palette;
}
else if (source.Format == PixelFormats.Gray8)
{
sourcePalette = BitmapPalettes.Gray256;
targetFormat = PixelFormats.Indexed8;
}
else if (source.Format == PixelFormats.Gray4)
{
sourcePalette = BitmapPalettes.Gray16;
targetFormat = PixelFormats.Indexed4;
}
else if (source.Format == PixelFormats.Gray2)
{
sourcePalette = BitmapPalettes.Gray4;
targetFormat = PixelFormats.Indexed2;
}
else if (source.Format == PixelFormats.BlackWhite)
{
sourcePalette = BitmapPalettes.BlackAndWhite;
targetFormat = PixelFormats.Indexed1;
}
if (sourcePalette == null || transparentPixel >= sourcePalette.Colors.Count)
{
return source;
}
var colors = sourcePalette.Colors.ToList();
colors[transparentPixel] = Colors.Transparent;
var stride = (source.PixelWidth * source.Format.BitsPerPixel + 7) / 8;
var buffer = new byte[stride * source.PixelHeight];
source.CopyPixels(buffer, stride, 0);
var target = BitmapSource.Create(
source.PixelWidth, source.PixelHeight, source.DpiX, source.DpiY,
targetFormat, new BitmapPalette(colors), buffer, stride);
target.Freeze();
return target;
return source;
}
var colors = sourcePalette.Colors.ToList();
colors[transparentPixel] = Colors.Transparent;
var stride = (source.PixelWidth * source.Format.BitsPerPixel + 7) / 8;
var buffer = new byte[stride * source.PixelHeight];
source.CopyPixels(buffer, stride, 0);
var target = BitmapSource.Create(
source.PixelWidth, source.PixelHeight, source.DpiX, source.DpiY,
targetFormat, new BitmapPalette(colors), buffer, stride);
target.Freeze();
return target;
}
}

View file

@ -6,92 +6,91 @@ using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace MapControl
namespace MapControl;
public static partial class ImageLoader
{
public static partial class ImageLoader
public static ImageSource LoadResourceImage(Uri uri)
{
public static ImageSource LoadResourceImage(Uri uri)
return new BitmapImage(uri);
}
public static ImageSource LoadImage(Stream stream)
{
var image = new BitmapImage();
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnLoad;
image.StreamSource = stream;
image.EndInit();
image.Freeze();
return image;
}
public static ImageSource LoadImage(string path)
{
ImageSource image = null;
if (File.Exists(path))
{
return new BitmapImage(uri);
using var stream = File.OpenRead(path);
image = LoadImage(stream);
}
public static ImageSource LoadImage(Stream stream)
return image;
}
public static Task<ImageSource> LoadImageAsync(Stream stream)
{
return Thread.CurrentThread.IsThreadPoolThread ?
Task.FromResult(LoadImage(stream)) :
Task.Run(() => LoadImage(stream));
}
public static Task<ImageSource> LoadImageAsync(string path)
{
return Thread.CurrentThread.IsThreadPoolThread ?
Task.FromResult(LoadImage(path)) :
Task.Run(() => LoadImage(path));
}
internal static async Task<ImageSource> LoadMergedImageAsync(Uri uri1, Uri uri2, IProgress<double> progress)
{
WriteableBitmap mergedBitmap = null;
var p1 = 0d;
var p2 = 0d;
var images = await Task.WhenAll(
LoadImageAsync(uri1, new Progress<double>(p => { p1 = p; progress.Report((p1 + p2) / 2d); })),
LoadImageAsync(uri2, new Progress<double>(p => { p2 = p; progress.Report((p1 + p2) / 2d); })));
if (images.Length == 2 &&
images[0] is BitmapSource bitmap1 &&
images[1] is BitmapSource bitmap2 &&
bitmap1.PixelHeight == bitmap2.PixelHeight &&
bitmap1.Format == bitmap2.Format &&
bitmap1.Format.BitsPerPixel % 8 == 0)
{
var image = new BitmapImage();
var format = bitmap1.Format;
var height = bitmap1.PixelHeight;
var width1 = bitmap1.PixelWidth;
var width2 = bitmap2.PixelWidth;
var stride1 = width1 * format.BitsPerPixel / 8;
var stride2 = width2 * format.BitsPerPixel / 8;
var buffer1 = new byte[stride1 * height];
var buffer2 = new byte[stride2 * height];
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnLoad;
image.StreamSource = stream;
image.EndInit();
image.Freeze();
bitmap1.CopyPixels(buffer1, stride1, 0);
bitmap2.CopyPixels(buffer2, stride2, 0);
return image;
mergedBitmap = new WriteableBitmap(width1 + width2, height, 96, 96, format, null);
mergedBitmap.WritePixels(new Int32Rect(0, 0, width1, height), buffer1, stride1, 0);
mergedBitmap.WritePixels(new Int32Rect(width1, 0, width2, height), buffer2, stride2, 0);
mergedBitmap.Freeze();
}
public static ImageSource LoadImage(string path)
{
ImageSource image = null;
if (File.Exists(path))
{
using var stream = File.OpenRead(path);
image = LoadImage(stream);
}
return image;
}
public static Task<ImageSource> LoadImageAsync(Stream stream)
{
return Thread.CurrentThread.IsThreadPoolThread ?
Task.FromResult(LoadImage(stream)) :
Task.Run(() => LoadImage(stream));
}
public static Task<ImageSource> LoadImageAsync(string path)
{
return Thread.CurrentThread.IsThreadPoolThread ?
Task.FromResult(LoadImage(path)) :
Task.Run(() => LoadImage(path));
}
internal static async Task<ImageSource> LoadMergedImageAsync(Uri uri1, Uri uri2, IProgress<double> progress)
{
WriteableBitmap mergedBitmap = null;
var p1 = 0d;
var p2 = 0d;
var images = await Task.WhenAll(
LoadImageAsync(uri1, new Progress<double>(p => { p1 = p; progress.Report((p1 + p2) / 2d); })),
LoadImageAsync(uri2, new Progress<double>(p => { p2 = p; progress.Report((p1 + p2) / 2d); })));
if (images.Length == 2 &&
images[0] is BitmapSource bitmap1 &&
images[1] is BitmapSource bitmap2 &&
bitmap1.PixelHeight == bitmap2.PixelHeight &&
bitmap1.Format == bitmap2.Format &&
bitmap1.Format.BitsPerPixel % 8 == 0)
{
var format = bitmap1.Format;
var height = bitmap1.PixelHeight;
var width1 = bitmap1.PixelWidth;
var width2 = bitmap2.PixelWidth;
var stride1 = width1 * format.BitsPerPixel / 8;
var stride2 = width2 * format.BitsPerPixel / 8;
var buffer1 = new byte[stride1 * height];
var buffer2 = new byte[stride2 * height];
bitmap1.CopyPixels(buffer1, stride1, 0);
bitmap2.CopyPixels(buffer2, stride2, 0);
mergedBitmap = new WriteableBitmap(width1 + width2, height, 96, 96, format, null);
mergedBitmap.WritePixels(new Int32Rect(0, 0, width1, height), buffer1, stride1, 0);
mergedBitmap.WritePixels(new Int32Rect(width1, 0, width2, height), buffer2, stride2, 0);
mergedBitmap.Freeze();
}
return mergedBitmap;
}
return mergedBitmap;
}
}

View file

@ -6,68 +6,67 @@ using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;
namespace MapControl
namespace MapControl;
public class ImageTile(int zoomLevel, int x, int y, int columnCount)
: Tile(zoomLevel, x, y, columnCount)
{
public class ImageTile(int zoomLevel, int x, int y, int columnCount)
: Tile(zoomLevel, x, y, columnCount)
public Image Image { get; } = new Image { Stretch = Stretch.Fill };
public override async Task LoadImageAsync(Func<Task<ImageSource>> loadImageFunc)
{
public Image Image { get; } = new Image { Stretch = Stretch.Fill };
var image = await loadImageFunc().ConfigureAwait(false);
public override async Task LoadImageAsync(Func<Task<ImageSource>> loadImageFunc)
void SetImageSource()
{
var image = await loadImageFunc().ConfigureAwait(false);
Image.Source = image;
void SetImageSource()
if (image != null && MapBase.ImageFadeDuration > TimeSpan.Zero)
{
Image.Source = image;
if (image != null && MapBase.ImageFadeDuration > TimeSpan.Zero)
if (image is BitmapSource bitmap && !bitmap.IsFrozen && bitmap.IsDownloading)
{
if (image is BitmapSource bitmap && !bitmap.IsFrozen && bitmap.IsDownloading)
{
bitmap.DownloadCompleted += BitmapDownloadCompleted;
bitmap.DownloadFailed += BitmapDownloadFailed;
}
else
{
BeginFadeInAnimation();
}
bitmap.DownloadCompleted += BitmapDownloadCompleted;
bitmap.DownloadFailed += BitmapDownloadFailed;
}
else
{
BeginFadeInAnimation();
}
}
await Image.Dispatcher.InvokeAsync(SetImageSource);
}
private void BeginFadeInAnimation()
await Image.Dispatcher.InvokeAsync(SetImageSource);
}
private void BeginFadeInAnimation()
{
var fadeInAnimation = new DoubleAnimation
{
var fadeInAnimation = new DoubleAnimation
{
From = 0d,
Duration = MapBase.ImageFadeDuration,
FillBehavior = FillBehavior.Stop
};
From = 0d,
Duration = MapBase.ImageFadeDuration,
FillBehavior = FillBehavior.Stop
};
Image.BeginAnimation(UIElement.OpacityProperty, fadeInAnimation);
}
Image.BeginAnimation(UIElement.OpacityProperty, fadeInAnimation);
}
private void BitmapDownloadCompleted(object sender, EventArgs e)
{
var bitmap = (BitmapSource)sender;
private void BitmapDownloadCompleted(object sender, EventArgs e)
{
var bitmap = (BitmapSource)sender;
bitmap.DownloadCompleted -= BitmapDownloadCompleted;
bitmap.DownloadFailed -= BitmapDownloadFailed;
bitmap.DownloadCompleted -= BitmapDownloadCompleted;
bitmap.DownloadFailed -= BitmapDownloadFailed;
BeginFadeInAnimation();
}
BeginFadeInAnimation();
}
private void BitmapDownloadFailed(object sender, ExceptionEventArgs e)
{
var bitmap = (BitmapSource)sender;
private void BitmapDownloadFailed(object sender, ExceptionEventArgs e)
{
var bitmap = (BitmapSource)sender;
bitmap.DownloadCompleted -= BitmapDownloadCompleted;
bitmap.DownloadFailed -= BitmapDownloadFailed;
bitmap.DownloadCompleted -= BitmapDownloadCompleted;
bitmap.DownloadFailed -= BitmapDownloadFailed;
Image.Source = null;
}
Image.Source = null;
}
}

View file

@ -2,38 +2,37 @@
using System.Windows;
using System.Windows.Media.Animation;
namespace MapControl
namespace MapControl;
public class LocationAnimation : AnimationTimeline
{
public class LocationAnimation : AnimationTimeline
public override Type TargetPropertyType => typeof(Location);
public Location To { get; set; }
public IEasingFunction EasingFunction { get; set; }
protected override Freezable CreateInstanceCore()
{
public override Type TargetPropertyType => typeof(Location);
public Location To { get; set; }
public IEasingFunction EasingFunction { get; set; }
protected override Freezable CreateInstanceCore()
return new LocationAnimation
{
return new LocationAnimation
{
To = To,
EasingFunction = EasingFunction
};
To = To,
EasingFunction = EasingFunction
};
}
public override object GetCurrentValue(object defaultOriginValue, object defaultDestinationValue, AnimationClock animationClock)
{
var from = (Location)defaultOriginValue;
var progress = animationClock.CurrentProgress ?? 1d;
if (EasingFunction != null)
{
progress = EasingFunction.Ease(progress);
}
public override object GetCurrentValue(object defaultOriginValue, object defaultDestinationValue, AnimationClock animationClock)
{
var from = (Location)defaultOriginValue;
var progress = animationClock.CurrentProgress ?? 1d;
if (EasingFunction != null)
{
progress = EasingFunction.Ease(progress);
}
return new Location(
(1d - progress) * from.Latitude + progress * To.Latitude,
(1d - progress) * from.Longitude + progress * To.Longitude);
}
return new Location(
(1d - progress) * from.Latitude + progress * To.Latitude,
(1d - progress) * from.Longitude + progress * To.Longitude);
}
}

View file

@ -1,93 +1,92 @@
using System.Windows;
using System.Windows.Input;
namespace MapControl
namespace MapControl;
public partial class Map
{
public partial class Map
static Map()
{
static Map()
IsManipulationEnabledProperty.OverrideMetadata(typeof(Map), new FrameworkPropertyMetadata(true));
}
public static readonly DependencyProperty ManipulationModeProperty =
DependencyPropertyHelper.Register<Map, ManipulationModes>(nameof(ManipulationMode), ManipulationModes.Translate | ManipulationModes.Scale);
/// <summary>
/// Gets or sets a value that specifies how the map control handles manipulations.
/// </summary>
public ManipulationModes ManipulationMode
{
get => (ManipulationModes)GetValue(ManipulationModeProperty);
set => SetValue(ManipulationModeProperty, value);
}
protected override void OnMouseWheel(MouseWheelEventArgs e)
{
base.OnMouseWheel(e);
// Standard mouse wheel delta value is 120.
//
OnMouseWheel(e.GetPosition(this), e.Delta / 120d);
}
private Point? mousePosition;
protected override void OnPreviewMouseLeftButtonDown(MouseButtonEventArgs e)
{
base.OnPreviewMouseLeftButtonDown(e);
if (Keyboard.Modifiers == ModifierKeys.None)
{
IsManipulationEnabledProperty.OverrideMetadata(typeof(Map), new FrameworkPropertyMetadata(true));
}
public static readonly DependencyProperty ManipulationModeProperty =
DependencyPropertyHelper.Register<Map, ManipulationModes>(nameof(ManipulationMode), ManipulationModes.Translate | ManipulationModes.Scale);
/// <summary>
/// Gets or sets a value that specifies how the map control handles manipulations.
/// </summary>
public ManipulationModes ManipulationMode
{
get => (ManipulationModes)GetValue(ManipulationModeProperty);
set => SetValue(ManipulationModeProperty, value);
}
protected override void OnMouseWheel(MouseWheelEventArgs e)
{
base.OnMouseWheel(e);
// Standard mouse wheel delta value is 120.
// Do not call CaptureMouse here because it avoids MapItem selection.
//
OnMouseWheel(e.GetPosition(this), e.Delta / 120d);
}
private Point? mousePosition;
protected override void OnPreviewMouseLeftButtonDown(MouseButtonEventArgs e)
{
base.OnPreviewMouseLeftButtonDown(e);
if (Keyboard.Modifiers == ModifierKeys.None)
{
// Do not call CaptureMouse here because it avoids MapItem selection.
//
mousePosition = e.GetPosition(this);
}
}
protected override void OnPreviewMouseLeftButtonUp(MouseButtonEventArgs e)
{
base.OnPreviewMouseLeftButtonUp(e);
if (mousePosition.HasValue)
{
mousePosition = null;
ReleaseMouseCapture();
}
}
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
if (mousePosition.HasValue)
{
if (!IsMouseCaptured)
{
CaptureMouse();
}
var p = e.GetPosition(this);
TranslateMap(new Point(p.X - mousePosition.Value.X, p.Y - mousePosition.Value.Y));
mousePosition = p;
}
}
protected override void OnManipulationStarted(ManipulationStartedEventArgs e)
{
Manipulation.SetManipulationMode(this, ManipulationMode);
base.OnManipulationStarted(e);
}
protected override void OnManipulationDelta(ManipulationDeltaEventArgs e)
{
base.OnManipulationDelta(e);
TransformMap(e.ManipulationOrigin,
(Point)e.DeltaManipulation.Translation,
e.DeltaManipulation.Rotation,
e.DeltaManipulation.Scale.LengthSquared / 2d);
mousePosition = e.GetPosition(this);
}
}
protected override void OnPreviewMouseLeftButtonUp(MouseButtonEventArgs e)
{
base.OnPreviewMouseLeftButtonUp(e);
if (mousePosition.HasValue)
{
mousePosition = null;
ReleaseMouseCapture();
}
}
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
if (mousePosition.HasValue)
{
if (!IsMouseCaptured)
{
CaptureMouse();
}
var p = e.GetPosition(this);
TranslateMap(new Point(p.X - mousePosition.Value.X, p.Y - mousePosition.Value.Y));
mousePosition = p;
}
}
protected override void OnManipulationStarted(ManipulationStartedEventArgs e)
{
Manipulation.SetManipulationMode(this, ManipulationMode);
base.OnManipulationStarted(e);
}
protected override void OnManipulationDelta(ManipulationDeltaEventArgs e)
{
base.OnManipulationDelta(e);
TransformMap(e.ManipulationOrigin,
(Point)e.DeltaManipulation.Translation,
e.DeltaManipulation.Rotation,
e.DeltaManipulation.Scale.LengthSquared / 2d);
}
}

View file

@ -3,278 +3,277 @@ using System.Windows.Documents;
using System.Windows.Media;
using System.Windows.Media.Animation;
namespace MapControl
namespace MapControl;
public partial class MapBase
{
public partial class MapBase
public static readonly DependencyProperty ForegroundProperty =
DependencyPropertyHelper.AddOwner<MapBase, Brush>(TextElement.ForegroundProperty, Brushes.Black);
public static readonly DependencyProperty AnimationEasingFunctionProperty =
DependencyPropertyHelper.Register<MapBase, IEasingFunction>(nameof(AnimationEasingFunction),
new QuadraticEase { EasingMode = EasingMode.EaseOut });
public static readonly DependencyProperty CenterProperty =
DependencyPropertyHelper.Register<MapBase, Location>(nameof(Center), new Location(0d, 0d),
(map, oldValue, newValue) => map.CenterPropertyChanged(newValue),
(map, value) => map.CoerceCenterProperty(value),
true);
public static readonly DependencyProperty TargetCenterProperty =
DependencyPropertyHelper.Register<MapBase, Location>(nameof(TargetCenter), new Location(0d, 0d),
(map, oldValue, newValue) => map.TargetCenterPropertyChanged(newValue),
(map, value) => map.CoerceCenterProperty(value),
true);
public static readonly DependencyProperty MinZoomLevelProperty =
DependencyPropertyHelper.Register<MapBase, double>(nameof(MinZoomLevel), 1d,
(map, oldValue, newValue) => map.MinZoomLevelPropertyChanged(newValue),
(map, value) => map.CoerceMinZoomLevelProperty(value));
public static readonly DependencyProperty MaxZoomLevelProperty =
DependencyPropertyHelper.Register<MapBase, double>(nameof(MaxZoomLevel), 20d,
(map, oldValue, newValue) => map.MaxZoomLevelPropertyChanged(newValue),
(map, value) => map.CoerceMaxZoomLevelProperty(value));
public static readonly DependencyProperty ZoomLevelProperty =
DependencyPropertyHelper.Register<MapBase, double>(nameof(ZoomLevel), 1d,
(map, oldValue, newValue) => map.ZoomLevelPropertyChanged(newValue),
(map, value) => map.CoerceZoomLevelProperty(value),
true);
public static readonly DependencyProperty TargetZoomLevelProperty =
DependencyPropertyHelper.Register<MapBase, double>(nameof(TargetZoomLevel), 1d,
(map, oldValue, newValue) => map.TargetZoomLevelPropertyChanged(newValue),
(map, value) => map.CoerceZoomLevelProperty(value),
true);
public static readonly DependencyProperty HeadingProperty =
DependencyPropertyHelper.Register<MapBase, double>(nameof(Heading), 0d,
(map, oldValue, newValue) => map.HeadingPropertyChanged(newValue),
(map, value) => map.CoerceHeadingProperty(value),
true);
public static readonly DependencyProperty TargetHeadingProperty =
DependencyPropertyHelper.Register<MapBase, double>(nameof(TargetHeading), 0d,
(map, oldValue, newValue) => map.TargetHeadingPropertyChanged(newValue),
(map, value) => map.CoerceHeadingProperty(value),
true);
private static readonly DependencyPropertyKey ViewScalePropertyKey =
DependencyPropertyHelper.RegisterReadOnly<MapBase, double>(nameof(ViewScale));
public static readonly DependencyProperty ViewScaleProperty = ViewScalePropertyKey.DependencyProperty;
private LocationAnimation centerAnimation;
private DoubleAnimation zoomLevelAnimation;
private DoubleAnimation headingAnimation;
static MapBase()
{
public static readonly DependencyProperty ForegroundProperty =
DependencyPropertyHelper.AddOwner<MapBase, Brush>(TextElement.ForegroundProperty, Brushes.Black);
BackgroundProperty.OverrideMetadata(typeof(MapBase), new FrameworkPropertyMetadata(Brushes.White));
ClipToBoundsProperty.OverrideMetadata(typeof(MapBase), new FrameworkPropertyMetadata(true));
DefaultStyleKeyProperty.OverrideMetadata(typeof(MapBase), new FrameworkPropertyMetadata(typeof(MapBase)));
}
public static readonly DependencyProperty AnimationEasingFunctionProperty =
DependencyPropertyHelper.Register<MapBase, IEasingFunction>(nameof(AnimationEasingFunction),
new QuadraticEase { EasingMode = EasingMode.EaseOut });
/// <summary>
/// Gets or sets the EasingFunction of the Center, ZoomLevel and Heading animations.
/// The default value is a QuadraticEase with EasingMode.EaseOut.
/// </summary>
public IEasingFunction AnimationEasingFunction
{
get => (IEasingFunction)GetValue(AnimationEasingFunctionProperty);
set => SetValue(AnimationEasingFunctionProperty, value);
}
public static readonly DependencyProperty CenterProperty =
DependencyPropertyHelper.Register<MapBase, Location>(nameof(Center), new Location(0d, 0d),
(map, oldValue, newValue) => map.CenterPropertyChanged(newValue),
(map, value) => map.CoerceCenterProperty(value),
true);
/// <summary>
/// Gets the scaling factor from projected map coordinates to view coordinates,
/// as pixels per meter.
/// </summary>
public double ViewScale
{
get => (double)GetValue(ViewScaleProperty);
private set => SetValue(ViewScalePropertyKey, value);
}
public static readonly DependencyProperty TargetCenterProperty =
DependencyPropertyHelper.Register<MapBase, Location>(nameof(TargetCenter), new Location(0d, 0d),
(map, oldValue, newValue) => map.TargetCenterPropertyChanged(newValue),
(map, value) => map.CoerceCenterProperty(value),
true);
protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
{
base.OnRenderSizeChanged(sizeInfo);
public static readonly DependencyProperty MinZoomLevelProperty =
DependencyPropertyHelper.Register<MapBase, double>(nameof(MinZoomLevel), 1d,
(map, oldValue, newValue) => map.MinZoomLevelPropertyChanged(newValue),
(map, value) => map.CoerceMinZoomLevelProperty(value));
ResetTransformCenter();
UpdateTransform();
}
public static readonly DependencyProperty MaxZoomLevelProperty =
DependencyPropertyHelper.Register<MapBase, double>(nameof(MaxZoomLevel), 20d,
(map, oldValue, newValue) => map.MaxZoomLevelPropertyChanged(newValue),
(map, value) => map.CoerceMaxZoomLevelProperty(value));
public static readonly DependencyProperty ZoomLevelProperty =
DependencyPropertyHelper.Register<MapBase, double>(nameof(ZoomLevel), 1d,
(map, oldValue, newValue) => map.ZoomLevelPropertyChanged(newValue),
(map, value) => map.CoerceZoomLevelProperty(value),
true);
public static readonly DependencyProperty TargetZoomLevelProperty =
DependencyPropertyHelper.Register<MapBase, double>(nameof(TargetZoomLevel), 1d,
(map, oldValue, newValue) => map.TargetZoomLevelPropertyChanged(newValue),
(map, value) => map.CoerceZoomLevelProperty(value),
true);
public static readonly DependencyProperty HeadingProperty =
DependencyPropertyHelper.Register<MapBase, double>(nameof(Heading), 0d,
(map, oldValue, newValue) => map.HeadingPropertyChanged(newValue),
(map, value) => map.CoerceHeadingProperty(value),
true);
public static readonly DependencyProperty TargetHeadingProperty =
DependencyPropertyHelper.Register<MapBase, double>(nameof(TargetHeading), 0d,
(map, oldValue, newValue) => map.TargetHeadingPropertyChanged(newValue),
(map, value) => map.CoerceHeadingProperty(value),
true);
private static readonly DependencyPropertyKey ViewScalePropertyKey =
DependencyPropertyHelper.RegisterReadOnly<MapBase, double>(nameof(ViewScale));
public static readonly DependencyProperty ViewScaleProperty = ViewScalePropertyKey.DependencyProperty;
private LocationAnimation centerAnimation;
private DoubleAnimation zoomLevelAnimation;
private DoubleAnimation headingAnimation;
static MapBase()
private void CenterPropertyChanged(Location center)
{
if (!internalPropertyChange)
{
BackgroundProperty.OverrideMetadata(typeof(MapBase), new FrameworkPropertyMetadata(Brushes.White));
ClipToBoundsProperty.OverrideMetadata(typeof(MapBase), new FrameworkPropertyMetadata(true));
DefaultStyleKeyProperty.OverrideMetadata(typeof(MapBase), new FrameworkPropertyMetadata(typeof(MapBase)));
}
/// <summary>
/// Gets or sets the EasingFunction of the Center, ZoomLevel and Heading animations.
/// The default value is a QuadraticEase with EasingMode.EaseOut.
/// </summary>
public IEasingFunction AnimationEasingFunction
{
get => (IEasingFunction)GetValue(AnimationEasingFunctionProperty);
set => SetValue(AnimationEasingFunctionProperty, value);
}
/// <summary>
/// Gets the scaling factor from projected map coordinates to view coordinates,
/// as pixels per meter.
/// </summary>
public double ViewScale
{
get => (double)GetValue(ViewScaleProperty);
private set => SetValue(ViewScalePropertyKey, value);
}
protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
{
base.OnRenderSizeChanged(sizeInfo);
ResetTransformCenter();
UpdateTransform();
}
private void CenterPropertyChanged(Location center)
{
if (!internalPropertyChange)
if (centerAnimation == null)
{
UpdateTransform();
if (centerAnimation == null)
{
SetValueInternal(TargetCenterProperty, center);
}
SetValueInternal(TargetCenterProperty, center);
}
}
}
private void TargetCenterPropertyChanged(Location targetCenter)
private void TargetCenterPropertyChanged(Location targetCenter)
{
if (!internalPropertyChange && !targetCenter.Equals(Center))
{
if (!internalPropertyChange && !targetCenter.Equals(Center))
{
ResetTransformCenter();
ResetTransformCenter();
if (centerAnimation != null)
{
centerAnimation.Completed -= CenterAnimationCompleted;
}
centerAnimation = new LocationAnimation
{
To = new Location(targetCenter.Latitude, NearestLongitude(targetCenter.Longitude)),
Duration = AnimationDuration,
EasingFunction = AnimationEasingFunction
};
centerAnimation.Completed += CenterAnimationCompleted;
BeginAnimation(CenterProperty, centerAnimation, HandoffBehavior.Compose);
}
}
private void CenterAnimationCompleted(object sender, object e)
{
if (centerAnimation != null)
{
centerAnimation.Completed -= CenterAnimationCompleted;
centerAnimation = null;
SetValueInternal(CenterProperty, TargetCenter);
UpdateTransform();
BeginAnimation(CenterProperty, null);
}
}
private void MinZoomLevelPropertyChanged(double minZoomLevel)
{
if (ZoomLevel < minZoomLevel)
centerAnimation = new LocationAnimation
{
ZoomLevel = minZoomLevel;
}
}
To = new Location(targetCenter.Latitude, NearestLongitude(targetCenter.Longitude)),
Duration = AnimationDuration,
EasingFunction = AnimationEasingFunction
};
private void MaxZoomLevelPropertyChanged(double maxZoomLevel)
centerAnimation.Completed += CenterAnimationCompleted;
BeginAnimation(CenterProperty, centerAnimation, HandoffBehavior.Compose);
}
}
private void CenterAnimationCompleted(object sender, object e)
{
if (centerAnimation != null)
{
if (ZoomLevel > maxZoomLevel)
{
ZoomLevel = maxZoomLevel;
}
}
centerAnimation.Completed -= CenterAnimationCompleted;
centerAnimation = null;
private void ZoomLevelPropertyChanged(double zoomLevel)
SetValueInternal(CenterProperty, TargetCenter);
UpdateTransform();
BeginAnimation(CenterProperty, null);
}
}
private void MinZoomLevelPropertyChanged(double minZoomLevel)
{
if (ZoomLevel < minZoomLevel)
{
if (!internalPropertyChange)
{
UpdateTransform();
if (zoomLevelAnimation == null)
{
SetValueInternal(TargetZoomLevelProperty, zoomLevel);
}
}
ZoomLevel = minZoomLevel;
}
}
private void TargetZoomLevelPropertyChanged(double targetZoomLevel)
private void MaxZoomLevelPropertyChanged(double maxZoomLevel)
{
if (ZoomLevel > maxZoomLevel)
{
if (!internalPropertyChange && targetZoomLevel != ZoomLevel)
ZoomLevel = maxZoomLevel;
}
}
private void ZoomLevelPropertyChanged(double zoomLevel)
{
if (!internalPropertyChange)
{
UpdateTransform();
if (zoomLevelAnimation == null)
{
if (zoomLevelAnimation != null)
{
zoomLevelAnimation.Completed -= ZoomLevelAnimationCompleted;
}
zoomLevelAnimation = new DoubleAnimation
{
To = targetZoomLevel,
Duration = AnimationDuration,
EasingFunction = AnimationEasingFunction
};
zoomLevelAnimation.Completed += ZoomLevelAnimationCompleted;
BeginAnimation(ZoomLevelProperty, zoomLevelAnimation, HandoffBehavior.Compose);
SetValueInternal(TargetZoomLevelProperty, zoomLevel);
}
}
}
private void ZoomLevelAnimationCompleted(object sender, object e)
private void TargetZoomLevelPropertyChanged(double targetZoomLevel)
{
if (!internalPropertyChange && targetZoomLevel != ZoomLevel)
{
if (zoomLevelAnimation != null)
{
zoomLevelAnimation.Completed -= ZoomLevelAnimationCompleted;
zoomLevelAnimation = null;
SetValueInternal(ZoomLevelProperty, TargetZoomLevel);
UpdateTransform(true);
BeginAnimation(ZoomLevelProperty, null);
}
}
private void HeadingPropertyChanged(double heading)
{
if (!internalPropertyChange)
zoomLevelAnimation = new DoubleAnimation
{
UpdateTransform();
To = targetZoomLevel,
Duration = AnimationDuration,
EasingFunction = AnimationEasingFunction
};
if (headingAnimation == null)
{
SetValueInternal(TargetHeadingProperty, heading);
}
}
zoomLevelAnimation.Completed += ZoomLevelAnimationCompleted;
BeginAnimation(ZoomLevelProperty, zoomLevelAnimation, HandoffBehavior.Compose);
}
}
private void TargetHeadingPropertyChanged(double targetHeading)
private void ZoomLevelAnimationCompleted(object sender, object e)
{
if (zoomLevelAnimation != null)
{
if (!internalPropertyChange && targetHeading != Heading)
{
var delta = targetHeading - Heading;
zoomLevelAnimation.Completed -= ZoomLevelAnimationCompleted;
zoomLevelAnimation = null;
if (delta > 180d)
{
delta -= 360d;
}
else if (delta < -180d)
{
delta += 360d;
}
if (headingAnimation != null)
{
headingAnimation.Completed -= HeadingAnimationCompleted;
}
headingAnimation = new DoubleAnimation
{
By = delta,
Duration = AnimationDuration,
EasingFunction = AnimationEasingFunction
};
headingAnimation.Completed += HeadingAnimationCompleted;
BeginAnimation(HeadingProperty, headingAnimation, HandoffBehavior.SnapshotAndReplace); // don't compose
}
SetValueInternal(ZoomLevelProperty, TargetZoomLevel);
UpdateTransform(true);
BeginAnimation(ZoomLevelProperty, null);
}
}
private void HeadingAnimationCompleted(object sender, object e)
private void HeadingPropertyChanged(double heading)
{
if (!internalPropertyChange)
{
if (headingAnimation != null)
{
headingAnimation.Completed -= HeadingAnimationCompleted;
headingAnimation = null;
UpdateTransform();
SetValueInternal(HeadingProperty, TargetHeading);
UpdateTransform();
BeginAnimation(HeadingProperty, null);
if (headingAnimation == null)
{
SetValueInternal(TargetHeadingProperty, heading);
}
}
}
private void TargetHeadingPropertyChanged(double targetHeading)
{
if (!internalPropertyChange && targetHeading != Heading)
{
var delta = targetHeading - Heading;
if (delta > 180d)
{
delta -= 360d;
}
else if (delta < -180d)
{
delta += 360d;
}
if (headingAnimation != null)
{
headingAnimation.Completed -= HeadingAnimationCompleted;
}
headingAnimation = new DoubleAnimation
{
By = delta,
Duration = AnimationDuration,
EasingFunction = AnimationEasingFunction
};
headingAnimation.Completed += HeadingAnimationCompleted;
BeginAnimation(HeadingProperty, headingAnimation, HandoffBehavior.SnapshotAndReplace); // don't compose
}
}
private void HeadingAnimationCompleted(object sender, object e)
{
if (headingAnimation != null)
{
headingAnimation.Completed -= HeadingAnimationCompleted;
headingAnimation = null;
SetValueInternal(HeadingProperty, TargetHeading);
UpdateTransform();
BeginAnimation(HeadingProperty, null);
}
}
}

View file

@ -1,29 +1,28 @@
using System.Windows;
namespace MapControl
namespace MapControl;
public partial class MapContentControl
{
public partial class MapContentControl
static MapContentControl()
{
static MapContentControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MapContentControl), new FrameworkPropertyMetadata(typeof(MapContentControl)));
}
}
public partial class Pushpin
{
static Pushpin()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(Pushpin), new FrameworkPropertyMetadata(typeof(Pushpin)));
}
public static readonly DependencyProperty CornerRadiusProperty =
DependencyPropertyHelper.Register<Pushpin, CornerRadius>(nameof(CornerRadius));
public CornerRadius CornerRadius
{
get => (CornerRadius)GetValue(CornerRadiusProperty);
set => SetValue(CornerRadiusProperty, value);
}
DefaultStyleKeyProperty.OverrideMetadata(typeof(MapContentControl), new FrameworkPropertyMetadata(typeof(MapContentControl)));
}
}
public partial class Pushpin
{
static Pushpin()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(Pushpin), new FrameworkPropertyMetadata(typeof(Pushpin)));
}
public static readonly DependencyProperty CornerRadiusProperty =
DependencyPropertyHelper.Register<Pushpin, CornerRadius>(nameof(CornerRadius));
public CornerRadius CornerRadius
{
get => (CornerRadius)GetValue(CornerRadiusProperty);
set => SetValue(CornerRadiusProperty, value);
}
}

View file

@ -4,109 +4,108 @@ using System.Windows;
using System.Windows.Documents;
using System.Windows.Media;
namespace MapControl
namespace MapControl;
public partial class MapGrid : FrameworkElement, IMapElement
{
public partial class MapGrid : FrameworkElement, IMapElement
public static readonly DependencyProperty ForegroundProperty =
DependencyPropertyHelper.AddOwner<MapGrid, Brush>(TextElement.ForegroundProperty);
public static readonly DependencyProperty FontFamilyProperty =
DependencyPropertyHelper.AddOwner<MapGrid, FontFamily>(TextElement.FontFamilyProperty);
public static readonly DependencyProperty FontSizeProperty =
DependencyPropertyHelper.AddOwner<MapGrid, double>(TextElement.FontSizeProperty, 12d);
/// <summary>
/// Implements IMapElement.ParentMap.
/// </summary>
public MapBase ParentMap
{
public static readonly DependencyProperty ForegroundProperty =
DependencyPropertyHelper.AddOwner<MapGrid, Brush>(TextElement.ForegroundProperty);
public static readonly DependencyProperty FontFamilyProperty =
DependencyPropertyHelper.AddOwner<MapGrid, FontFamily>(TextElement.FontFamilyProperty);
public static readonly DependencyProperty FontSizeProperty =
DependencyPropertyHelper.AddOwner<MapGrid, double>(TextElement.FontSizeProperty, 12d);
/// <summary>
/// Implements IMapElement.ParentMap.
/// </summary>
public MapBase ParentMap
get;
set
{
get;
set
if (field != null)
{
if (field != null)
{
field.ViewportChanged -= OnViewportChanged;
}
field.ViewportChanged -= OnViewportChanged;
}
field = value;
field = value;
if (field != null)
{
field.ViewportChanged += OnViewportChanged;
}
if (field != null)
{
field.ViewportChanged += OnViewportChanged;
}
}
}
private void OnViewportChanged(object sender, ViewportChangedEventArgs e)
{
OnViewportChanged(e);
}
private void OnViewportChanged(object sender, ViewportChangedEventArgs e)
{
OnViewportChanged(e);
}
protected virtual void OnViewportChanged(ViewportChangedEventArgs e)
{
InvalidateVisual();
}
protected virtual void OnViewportChanged(ViewportChangedEventArgs e)
{
InvalidateVisual();
}
protected override void OnRender(DrawingContext drawingContext)
protected override void OnRender(DrawingContext drawingContext)
{
if (ParentMap != null)
{
if (ParentMap != null)
var pathGeometry = new PathGeometry();
var labels = new List<Label>();
var pen = new Pen
{
var pathGeometry = new PathGeometry();
var labels = new List<Label>();
var pen = new Pen
Brush = Foreground,
Thickness = StrokeThickness,
};
DrawGrid(pathGeometry.Figures, labels);
drawingContext.DrawGeometry(null, pen, pathGeometry);
if (labels.Count > 0)
{
var typeface = new Typeface(FontFamily, FontStyles.Normal, FontWeights.Normal, FontStretches.Normal);
var pixelsPerDip = VisualTreeHelper.GetDpi(this).PixelsPerDip;
foreach (var label in labels)
{
Brush = Foreground,
Thickness = StrokeThickness,
};
var text = new FormattedText(label.Text,
CultureInfo.InvariantCulture, FlowDirection.LeftToRight, typeface, FontSize, Foreground, pixelsPerDip);
var x = label.X +
label.HorizontalAlignment switch
{
HorizontalAlignment.Left => 2d,
HorizontalAlignment.Right => -text.Width - 2d,
_ => -text.Width / 2d
};
var y = label.Y +
label.VerticalAlignment switch
{
VerticalAlignment.Top => 0,
VerticalAlignment.Bottom => -text.Height,
_ => -text.Height / 2d
};
DrawGrid(pathGeometry.Figures, labels);
drawingContext.DrawGeometry(null, pen, pathGeometry);
if (labels.Count > 0)
{
var typeface = new Typeface(FontFamily, FontStyles.Normal, FontWeights.Normal, FontStretches.Normal);
var pixelsPerDip = VisualTreeHelper.GetDpi(this).PixelsPerDip;
foreach (var label in labels)
if (label.Rotation != 0d)
{
var text = new FormattedText(label.Text,
CultureInfo.InvariantCulture, FlowDirection.LeftToRight, typeface, FontSize, Foreground, pixelsPerDip);
var x = label.X +
label.HorizontalAlignment switch
{
HorizontalAlignment.Left => 2d,
HorizontalAlignment.Right => -text.Width - 2d,
_ => -text.Width / 2d
};
var y = label.Y +
label.VerticalAlignment switch
{
VerticalAlignment.Top => 0,
VerticalAlignment.Bottom => -text.Height,
_ => -text.Height / 2d
};
if (label.Rotation != 0d)
{
drawingContext.PushTransform(new RotateTransform(label.Rotation, label.X, label.Y));
drawingContext.DrawText(text, new Point(x, y));
drawingContext.Pop();
}
else
{
drawingContext.DrawText(text, new Point(x, y));
}
drawingContext.PushTransform(new RotateTransform(label.Rotation, label.X, label.Y));
drawingContext.DrawText(text, new Point(x, y));
drawingContext.Pop();
}
else
{
drawingContext.DrawText(text, new Point(x, y));
}
}
}
}
}
private static PolyLineSegment CreatePolyLineSegment(IEnumerable<Point> points)
{
return new PolyLineSegment(points, true);
}
private static PolyLineSegment CreatePolyLineSegment(IEnumerable<Point> points)
{
return new PolyLineSegment(points, true);
}
}

View file

@ -2,35 +2,34 @@
using System.Windows;
using System.Windows.Media.Animation;
namespace MapControl
namespace MapControl;
public partial class MapImageLayer
{
public partial class MapImageLayer
private void FadeOver()
{
private void FadeOver()
var fadeInAnimation = new DoubleAnimation
{
var fadeInAnimation = new DoubleAnimation
{
To = 1d,
Duration = MapBase.ImageFadeDuration
};
To = 1d,
Duration = MapBase.ImageFadeDuration
};
var fadeOutAnimation = new DoubleAnimation
{
To = 0d,
BeginTime = MapBase.ImageFadeDuration,
Duration = TimeSpan.Zero
};
var fadeOutAnimation = new DoubleAnimation
{
To = 0d,
BeginTime = MapBase.ImageFadeDuration,
Duration = TimeSpan.Zero
};
Storyboard.SetTarget(fadeInAnimation, Children[1]);
Storyboard.SetTargetProperty(fadeInAnimation, new PropertyPath(OpacityProperty));
Storyboard.SetTarget(fadeInAnimation, Children[1]);
Storyboard.SetTargetProperty(fadeInAnimation, new PropertyPath(OpacityProperty));
Storyboard.SetTarget(fadeOutAnimation, Children[0]);
Storyboard.SetTargetProperty(fadeOutAnimation, new PropertyPath(OpacityProperty));
Storyboard.SetTarget(fadeOutAnimation, Children[0]);
Storyboard.SetTargetProperty(fadeOutAnimation, new PropertyPath(OpacityProperty));
var storyboard = new Storyboard();
storyboard.Children.Add(fadeInAnimation);
storyboard.Children.Add(fadeOutAnimation);
storyboard.Begin();
}
var storyboard = new Storyboard();
storyboard.Children.Add(fadeInAnimation);
storyboard.Children.Add(fadeOutAnimation);
storyboard.Begin();
}
}

View file

@ -2,38 +2,37 @@
using System.Windows.Controls;
using System.Windows.Input;
namespace MapControl
namespace MapControl;
public partial class MapItem
{
public partial class MapItem
static MapItem()
{
static MapItem()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MapItem), new FrameworkPropertyMetadata(typeof(MapItem)));
}
DefaultStyleKeyProperty.OverrideMetadata(typeof(MapItem), new FrameworkPropertyMetadata(typeof(MapItem)));
}
protected override void OnTouchDown(TouchEventArgs e)
protected override void OnTouchDown(TouchEventArgs e)
{
e.Handled = true;
}
protected override void OnTouchUp(TouchEventArgs e)
{
e.Handled = true;
}
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
if (Keyboard.Modifiers.HasFlag(ModifierKeys.Shift) &&
ItemsControl.ItemsControlFromItemContainer(this) is MapItemsControl mapItemsControl &&
mapItemsControl.SelectionMode == SelectionMode.Extended)
{
mapItemsControl.SelectItemsInRange(this);
e.Handled = true;
}
protected override void OnTouchUp(TouchEventArgs e)
else
{
e.Handled = true;
}
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
if (Keyboard.Modifiers.HasFlag(ModifierKeys.Shift) &&
ItemsControl.ItemsControlFromItemContainer(this) is MapItemsControl mapItemsControl &&
mapItemsControl.SelectionMode == SelectionMode.Extended)
{
mapItemsControl.SelectItemsInRange(this);
e.Handled = true;
}
else
{
base.OnMouseLeftButtonDown(e);
}
base.OnMouseLeftButtonDown(e);
}
}
}

View file

@ -2,50 +2,49 @@
using System.Windows.Controls;
using System.Windows.Media;
namespace MapControl
namespace MapControl;
public partial class MapItemsControl
{
public partial class MapItemsControl
static MapItemsControl()
{
static MapItemsControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MapItemsControl), new FrameworkPropertyMetadata(typeof(MapItemsControl)));
}
DefaultStyleKeyProperty.OverrideMetadata(typeof(MapItemsControl), new FrameworkPropertyMetadata(typeof(MapItemsControl)));
}
public void SelectItemsInGeometry(Geometry geometry)
{
SelectItemsByPosition(geometry.FillContains);
}
public void SelectItemsInGeometry(Geometry geometry)
{
SelectItemsByPosition(geometry.FillContains);
}
public MapItem ContainerFromItem(object item)
{
return (MapItem)ItemContainerGenerator.ContainerFromItem(item);
}
public MapItem ContainerFromItem(object item)
{
return (MapItem)ItemContainerGenerator.ContainerFromItem(item);
}
public object ItemFromContainer(MapItem container)
{
return ItemContainerGenerator.ItemFromContainer(container);
}
public object ItemFromContainer(MapItem container)
{
return ItemContainerGenerator.ItemFromContainer(container);
}
protected override bool IsItemItsOwnContainerOverride(object item)
{
return item is MapItem;
}
protected override bool IsItemItsOwnContainerOverride(object item)
{
return item is MapItem;
}
protected override DependencyObject GetContainerForItemOverride()
{
return new MapItem();
}
protected override DependencyObject GetContainerForItemOverride()
{
return new MapItem();
}
protected override void PrepareContainerForItemOverride(DependencyObject container, object item)
{
base.PrepareContainerForItemOverride(container, item);
PrepareContainer((MapItem)container, item);
}
protected override void PrepareContainerForItemOverride(DependencyObject container, object item)
{
base.PrepareContainerForItemOverride(container, item);
PrepareContainer((MapItem)container, item);
}
protected override void ClearContainerForItemOverride(DependencyObject container, object item)
{
base.ClearContainerForItemOverride(container, item);
ClearContainer((MapItem)container);
}
protected override void ClearContainerForItemOverride(DependencyObject container, object item)
{
base.ClearContainerForItemOverride(container, item);
ClearContainer((MapItem)container);
}
}

View file

@ -1,27 +1,26 @@
using System.Windows;
namespace MapControl
namespace MapControl;
public partial class MapPanel
{
public partial class MapPanel
public static readonly DependencyProperty AutoCollapseProperty =
DependencyPropertyHelper.RegisterAttached< bool>("AutoCollapse", typeof(MapPanel));
public static readonly DependencyProperty LocationProperty =
DependencyPropertyHelper.RegisterAttached<Location>("Location", typeof(MapPanel), null,
FrameworkPropertyMetadataOptions.AffectsParentArrange);
public static readonly DependencyProperty BoundingBoxProperty =
DependencyPropertyHelper.RegisterAttached<BoundingBox>("BoundingBox", typeof(MapPanel), null,
FrameworkPropertyMetadataOptions.AffectsParentArrange);
public static readonly DependencyProperty MapRectProperty =
DependencyPropertyHelper.RegisterAttached<Rect?>("MapRect", typeof(MapPanel), null,
FrameworkPropertyMetadataOptions.AffectsParentArrange);
public static MapBase GetParentMap(FrameworkElement element)
{
public static readonly DependencyProperty AutoCollapseProperty =
DependencyPropertyHelper.RegisterAttached< bool>("AutoCollapse", typeof(MapPanel));
public static readonly DependencyProperty LocationProperty =
DependencyPropertyHelper.RegisterAttached<Location>("Location", typeof(MapPanel), null,
FrameworkPropertyMetadataOptions.AffectsParentArrange);
public static readonly DependencyProperty BoundingBoxProperty =
DependencyPropertyHelper.RegisterAttached<BoundingBox>("BoundingBox", typeof(MapPanel), null,
FrameworkPropertyMetadataOptions.AffectsParentArrange);
public static readonly DependencyProperty MapRectProperty =
DependencyPropertyHelper.RegisterAttached<Rect?>("MapRect", typeof(MapPanel), null,
FrameworkPropertyMetadataOptions.AffectsParentArrange);
public static MapBase GetParentMap(FrameworkElement element)
{
return (MapBase)element.GetValue(ParentMapProperty);
}
return (MapBase)element.GetValue(ParentMapProperty);
}
}

View file

@ -2,36 +2,35 @@
using System.Windows.Media;
using System.Windows.Shapes;
namespace MapControl
namespace MapControl;
public partial class MapPath : Shape
{
public partial class MapPath : Shape
public static readonly DependencyProperty DataProperty =
DependencyPropertyHelper.AddOwner<MapPath, Geometry>(Path.DataProperty,
(path, oldValue, newValue) => path.DataPropertyChanged(oldValue, newValue));
public Geometry Data
{
public static readonly DependencyProperty DataProperty =
DependencyPropertyHelper.AddOwner<MapPath, Geometry>(Path.DataProperty,
(path, oldValue, newValue) => path.DataPropertyChanged(oldValue, newValue));
get => (Geometry)GetValue(DataProperty);
set => SetValue(DataProperty, value);
}
public Geometry Data
protected override Geometry DefiningGeometry => Data;
private void DataPropertyChanged(Geometry oldValue, Geometry newValue)
{
// Check if Data is actually a new Geometry.
//
if (newValue != null && !ReferenceEquals(newValue, oldValue))
{
get => (Geometry)GetValue(DataProperty);
set => SetValue(DataProperty, value);
}
protected override Geometry DefiningGeometry => Data;
private void DataPropertyChanged(Geometry oldValue, Geometry newValue)
{
// Check if Data is actually a new Geometry.
//
if (newValue != null && !ReferenceEquals(newValue, oldValue))
if (newValue.IsFrozen)
{
if (newValue.IsFrozen)
{
Data = newValue.Clone(); // DataPropertyChanged called again
}
else
{
UpdateData();
}
Data = newValue.Clone(); // DataPropertyChanged called again
}
else
{
UpdateData();
}
}
}

View file

@ -3,64 +3,63 @@ using System.Collections.Generic;
using System.Linq;
using System.Windows.Media;
namespace MapControl
namespace MapControl;
public partial class MapPolypoint : MapPath
{
public partial class MapPolypoint : MapPath
protected void UpdateData(IEnumerable<Location> locations, bool closed)
{
protected void UpdateData(IEnumerable<Location> locations, bool closed)
using var context = ((StreamGeometry)Data).Open();
if (ParentMap != null && locations != null)
{
using var context = ((StreamGeometry)Data).Open();
var longitudeOffset = GetLongitudeOffset(locations);
if (ParentMap != null && locations != null)
AddPolylinePoints(context, locations, longitudeOffset, closed);
}
}
protected void UpdateData(IEnumerable<IEnumerable<Location>> polygons)
{
using var context = ((StreamGeometry)Data).Open();
if (ParentMap != null && polygons != null)
{
var longitudeOffset = GetLongitudeOffset(polygons.FirstOrDefault());
foreach (var locations in polygons)
{
var longitudeOffset = GetLongitudeOffset(locations);
AddPolylinePoints(context, locations, longitudeOffset, closed);
AddPolylinePoints(context, locations, longitudeOffset, true);
}
}
}
protected void UpdateData(IEnumerable<IEnumerable<Location>> polygons)
private void AddPolylinePoints(StreamGeometryContext context, IEnumerable<Location> locations, double longitudeOffset, bool closed)
{
var points = locations.Select(location => LocationToView(location, longitudeOffset));
if (points.Any())
{
using var context = ((StreamGeometry)Data).Open();
var start = points.First();
var polyline = points.Skip(1).ToList();
var minX = start.X;
var maxX = start.X;
var minY = start.Y;
var maxY = start.Y;
if (ParentMap != null && polygons != null)
foreach (var point in polyline)
{
var longitudeOffset = GetLongitudeOffset(polygons.FirstOrDefault());
foreach (var locations in polygons)
{
AddPolylinePoints(context, locations, longitudeOffset, true);
}
minX = Math.Min(minX, point.X);
maxX = Math.Max(maxX, point.X);
minY = Math.Min(minY, point.Y);
maxY = Math.Max(maxY, point.Y);
}
}
private void AddPolylinePoints(StreamGeometryContext context, IEnumerable<Location> locations, double longitudeOffset, bool closed)
{
var points = locations.Select(location => LocationToView(location, longitudeOffset));
if (points.Any())
if (maxX >= 0d && minX <= ParentMap.ActualWidth &&
maxY >= 0d && minY <= ParentMap.ActualHeight)
{
var start = points.First();
var polyline = points.Skip(1).ToList();
var minX = start.X;
var maxX = start.X;
var minY = start.Y;
var maxY = start.Y;
foreach (var point in polyline)
{
minX = Math.Min(minX, point.X);
maxX = Math.Max(maxX, point.X);
minY = Math.Min(minY, point.Y);
maxY = Math.Max(maxY, point.Y);
}
if (maxX >= 0d && minX <= ParentMap.ActualWidth &&
maxY >= 0d && minY <= ParentMap.ActualHeight)
{
context.BeginFigure(start, true, closed);
context.PolyLineTo(polyline, true, true);
}
context.BeginFigure(start, true, closed);
context.PolyLineTo(polyline, true, true);
}
}
}

View file

@ -3,104 +3,103 @@ using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace MapControl
namespace MapControl;
public partial class PushpinBorder : Decorator
{
public partial class PushpinBorder : Decorator
public static readonly DependencyProperty ArrowSizeProperty =
DependencyPropertyHelper.Register<PushpinBorder, Size>(nameof(ArrowSize), new Size(10d, 20d),
FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender);
public static readonly DependencyProperty BorderWidthProperty =
DependencyPropertyHelper.Register<PushpinBorder, double>(nameof(BorderWidth), 0d,
FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender);
public static readonly DependencyProperty BackgroundProperty =
DependencyPropertyHelper.Register<PushpinBorder, Brush>(nameof(Background), null,
FrameworkPropertyMetadataOptions.AffectsRender);
public static readonly DependencyProperty BorderBrushProperty =
DependencyPropertyHelper.Register<PushpinBorder, Brush>(nameof(BorderBrush), null,
FrameworkPropertyMetadataOptions.AffectsRender);
public static readonly DependencyProperty CornerRadiusProperty =
DependencyPropertyHelper.Register<PushpinBorder, CornerRadius>(nameof(CornerRadius), new CornerRadius(),
FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender);
public static readonly DependencyProperty PaddingProperty =
DependencyPropertyHelper.Register<PushpinBorder, Thickness>(nameof(Padding), new Thickness(2),
FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender);
public Brush Background
{
public static readonly DependencyProperty ArrowSizeProperty =
DependencyPropertyHelper.Register<PushpinBorder, Size>(nameof(ArrowSize), new Size(10d, 20d),
FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender);
get => (Brush)GetValue(BackgroundProperty);
set => SetValue(BackgroundProperty, value);
}
public static readonly DependencyProperty BorderWidthProperty =
DependencyPropertyHelper.Register<PushpinBorder, double>(nameof(BorderWidth), 0d,
FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender);
public Brush BorderBrush
{
get => (Brush)GetValue(BorderBrushProperty);
set => SetValue(BorderBrushProperty, value);
}
public static readonly DependencyProperty BackgroundProperty =
DependencyPropertyHelper.Register<PushpinBorder, Brush>(nameof(Background), null,
FrameworkPropertyMetadataOptions.AffectsRender);
public CornerRadius CornerRadius
{
get => (CornerRadius)GetValue(CornerRadiusProperty);
set => SetValue(CornerRadiusProperty, value);
}
public static readonly DependencyProperty BorderBrushProperty =
DependencyPropertyHelper.Register<PushpinBorder, Brush>(nameof(BorderBrush), null,
FrameworkPropertyMetadataOptions.AffectsRender);
public Thickness Padding
{
get => (Thickness)GetValue(PaddingProperty);
set => SetValue(PaddingProperty, value);
}
public static readonly DependencyProperty CornerRadiusProperty =
DependencyPropertyHelper.Register<PushpinBorder, CornerRadius>(nameof(CornerRadius), new CornerRadius(),
FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender);
protected override Size MeasureOverride(Size constraint)
{
var width = 2d * BorderWidth + Padding.Left + Padding.Right;
var height = 2d * BorderWidth + Padding.Top + Padding.Bottom;
public static readonly DependencyProperty PaddingProperty =
DependencyPropertyHelper.Register<PushpinBorder, Thickness>(nameof(Padding), new Thickness(2),
FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender);
public Brush Background
if (Child != null)
{
get => (Brush)GetValue(BackgroundProperty);
set => SetValue(BackgroundProperty, value);
Child.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
width += Child.DesiredSize.Width;
height += Child.DesiredSize.Height;
}
public Brush BorderBrush
var minWidth = BorderWidth + Math.Max(
CornerRadius.TopLeft + CornerRadius.TopRight,
CornerRadius.BottomLeft + CornerRadius.BottomRight + ArrowSize.Width);
var minHeight = BorderWidth + Math.Max(
CornerRadius.TopLeft + CornerRadius.BottomLeft,
CornerRadius.TopRight + CornerRadius.BottomRight);
return new Size(
Math.Max(width, minWidth),
Math.Max(height, minHeight) + ArrowSize.Height);
}
protected override Size ArrangeOverride(Size size)
{
Child?.Arrange(new Rect(
BorderWidth + Padding.Left,
BorderWidth + Padding.Top,
Child.DesiredSize.Width,
Child.DesiredSize.Height));
return DesiredSize;
}
protected override void OnRender(DrawingContext drawingContext)
{
var pen = new Pen
{
get => (Brush)GetValue(BorderBrushProperty);
set => SetValue(BorderBrushProperty, value);
}
Brush = BorderBrush,
Thickness = BorderWidth,
LineJoin = PenLineJoin.Round
};
public CornerRadius CornerRadius
{
get => (CornerRadius)GetValue(CornerRadiusProperty);
set => SetValue(CornerRadiusProperty, value);
}
public Thickness Padding
{
get => (Thickness)GetValue(PaddingProperty);
set => SetValue(PaddingProperty, value);
}
protected override Size MeasureOverride(Size constraint)
{
var width = 2d * BorderWidth + Padding.Left + Padding.Right;
var height = 2d * BorderWidth + Padding.Top + Padding.Bottom;
if (Child != null)
{
Child.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
width += Child.DesiredSize.Width;
height += Child.DesiredSize.Height;
}
var minWidth = BorderWidth + Math.Max(
CornerRadius.TopLeft + CornerRadius.TopRight,
CornerRadius.BottomLeft + CornerRadius.BottomRight + ArrowSize.Width);
var minHeight = BorderWidth + Math.Max(
CornerRadius.TopLeft + CornerRadius.BottomLeft,
CornerRadius.TopRight + CornerRadius.BottomRight);
return new Size(
Math.Max(width, minWidth),
Math.Max(height, minHeight) + ArrowSize.Height);
}
protected override Size ArrangeOverride(Size size)
{
Child?.Arrange(new Rect(
BorderWidth + Padding.Left,
BorderWidth + Padding.Top,
Child.DesiredSize.Width,
Child.DesiredSize.Height));
return DesiredSize;
}
protected override void OnRender(DrawingContext drawingContext)
{
var pen = new Pen
{
Brush = BorderBrush,
Thickness = BorderWidth,
LineJoin = PenLineJoin.Round
};
drawingContext.DrawGeometry(Background, pen, BuildGeometry());
}
drawingContext.DrawGeometry(Background, pen, BuildGeometry());
}
}