mirror of
https://github.com/ClemensFischer/XAML-Map-Control.git
synced 2026-05-07 13:37:47 +00:00
File scoped namespaces
This commit is contained in:
parent
c14377f976
commit
65aba44af6
152 changed files with 11962 additions and 12115 deletions
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue