diff --git a/MBTiles/Shared/MBTileData.cs b/MBTiles/Shared/MBTileData.cs
index a2fcacc6..63764f77 100644
--- a/MBTiles/Shared/MBTileData.cs
+++ b/MBTiles/Shared/MBTileData.cs
@@ -13,7 +13,7 @@ using SQLiteConnection = Microsoft.Data.Sqlite.SqliteConnection;
using System.Data.SQLite;
#endif
-namespace MapControl
+namespace MapControl.MBTiles
{
public class MBTileData : IDisposable
{
diff --git a/MBTiles/Shared/MBTileLayer.cs b/MBTiles/Shared/MBTileLayer.cs
index 7d775a0c..206d7007 100644
--- a/MBTiles/Shared/MBTileLayer.cs
+++ b/MBTiles/Shared/MBTileLayer.cs
@@ -9,7 +9,7 @@ using Windows.UI.Xaml;
using System.Windows;
#endif
-namespace MapControl
+namespace MapControl.MBTiles
{
public class MBTileLayer : MapTileLayer
{
diff --git a/MBTiles/Shared/MBTileSource.cs b/MBTiles/Shared/MBTileSource.cs
index d0446710..de2bae6a 100644
--- a/MBTiles/Shared/MBTileSource.cs
+++ b/MBTiles/Shared/MBTileSource.cs
@@ -10,7 +10,7 @@ using Windows.UI.Xaml.Media;
using System.Windows.Media;
#endif
-namespace MapControl
+namespace MapControl.MBTiles
{
public class MBTileSource : TileSource, IDisposable
{
diff --git a/MBTiles/UWP/MBTiles.UWP.csproj b/MBTiles/UWP/MBTiles.UWP.csproj
index 46ec7d8f..d8d3ec26 100644
--- a/MBTiles/UWP/MBTiles.UWP.csproj
+++ b/MBTiles/UWP/MBTiles.UWP.csproj
@@ -7,7 +7,7 @@
{DCC111E9-EC8B-492A-A09D-DF390D83AE8D}
Library
Properties
- MapControl
+ MapControl.MBTiles
MBTiles.UWP
en-US
UAP
@@ -54,7 +54,7 @@
- 2.1.0
+ 2.2.0
6.2.2
diff --git a/MBTiles/WPF/MBTiles.WPF.csproj b/MBTiles/WPF/MBTiles.WPF.csproj
index 1e5e58b3..ebb3fc56 100644
--- a/MBTiles/WPF/MBTiles.WPF.csproj
+++ b/MBTiles/WPF/MBTiles.WPF.csproj
@@ -7,7 +7,7 @@
{38B18AB6-6E70-4696-8FB4-E8C8E12BF50C}
Library
Properties
- MapControl
+ MapControl.MBTiles
MBTiles.WPF
v4.7.2
512
@@ -32,6 +32,12 @@
prompt
4
+
+ true
+
+
+ ..\..\MapControl.snk
+
@@ -63,6 +69,9 @@
+
+ MapControl.snk
+
diff --git a/MapControl/Shared/ImageLoader.cs b/MapControl/Shared/ImageLoader.cs
index 40e5dd6c..34b22411 100644
--- a/MapControl/Shared/ImageLoader.cs
+++ b/MapControl/Shared/ImageLoader.cs
@@ -7,11 +7,9 @@ using System.Diagnostics;
using System.Threading.Tasks;
#if WINDOWS_UWP
using Windows.Web.Http;
-using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Media.Imaging;
#else
using System.Net.Http;
-using System.Windows.Media;
using System.Windows.Media.Imaging;
#endif
@@ -24,23 +22,23 @@ namespace MapControl
///
public static HttpClient HttpClient { get; set; } = new HttpClient();
- public static async Task LoadImageAsync(Uri uri)
+ public static async Task LoadImageAsync(Uri uri)
{
- ImageSource imageSource = null;
+ BitmapSource image = null;
try
{
if (!uri.IsAbsoluteUri || uri.Scheme == "file")
{
- imageSource = await LoadLocalImageAsync(uri);
+ image = await LoadLocalImageAsync(uri);
}
else if (uri.Scheme == "http" || uri.Scheme == "https")
{
- imageSource = await LoadHttpImageAsync(uri);
+ image = await LoadHttpImageAsync(uri);
}
else
{
- imageSource = new BitmapImage(uri);
+ image = new BitmapImage(uri);
}
}
catch (Exception ex)
@@ -48,12 +46,12 @@ namespace MapControl
Debug.WriteLine("ImageLoader: {0}: {1}", uri, ex.Message);
}
- return imageSource;
+ return image;
}
- private static async Task LoadHttpImageAsync(Uri uri)
+ private static async Task LoadHttpImageAsync(Uri uri)
{
- ImageSource imageSource = null;
+ BitmapSource image = null;
using (var response = await HttpClient.GetAsync(uri))
{
@@ -63,11 +61,11 @@ namespace MapControl
}
else if (IsTileAvailable(response.Headers))
{
- imageSource = await LoadImageAsync(response.Content);
+ image = await LoadImageAsync(response.Content);
}
}
- return imageSource;
+ return image;
}
}
}
\ No newline at end of file
diff --git a/MapControl/UWP/ImageLoader.UWP.cs b/MapControl/UWP/ImageLoader.UWP.cs
index f39651ef..8becd5ac 100644
--- a/MapControl/UWP/ImageLoader.UWP.cs
+++ b/MapControl/UWP/ImageLoader.UWP.cs
@@ -9,7 +9,6 @@ using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading.Tasks;
using Windows.Storage;
using Windows.Storage.Streams;
-using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Media.Imaging;
using Windows.Web.Http;
using Windows.Web.Http.Headers;
@@ -18,14 +17,14 @@ namespace MapControl
{
public static partial class ImageLoader
{
- public static async Task LoadImageAsync(IRandomAccessStream stream)
+ public static async Task LoadImageAsync(IRandomAccessStream stream)
{
- var bitmapImage = new BitmapImage();
- await bitmapImage.SetSourceAsync(stream);
- return bitmapImage;
+ var image = new BitmapImage();
+ await image.SetSourceAsync(stream);
+ return image;
}
- public static async Task LoadImageAsync(byte[] buffer)
+ public static async Task LoadImageAsync(byte[] buffer)
{
using (var stream = new InMemoryRandomAccessStream())
{
@@ -35,7 +34,7 @@ namespace MapControl
}
}
- private static async Task LoadImageAsync(IHttpContent content)
+ private static async Task LoadImageAsync(IHttpContent content)
{
using (var stream = new InMemoryRandomAccessStream())
{
@@ -45,9 +44,9 @@ namespace MapControl
}
}
- private static async Task LoadLocalImageAsync(Uri uri)
+ private static async Task LoadLocalImageAsync(Uri uri)
{
- ImageSource imageSource = null;
+ BitmapSource image = null;
var path = uri.IsAbsoluteUri ? uri.LocalPath : uri.OriginalString;
if (File.Exists(path))
@@ -56,11 +55,11 @@ namespace MapControl
using (var stream = await file.OpenReadAsync())
{
- imageSource = await LoadImageAsync(stream);
+ image = await LoadImageAsync(stream);
}
}
- return imageSource;
+ return image;
}
internal static async Task> LoadHttpBufferAsync(Uri uri)
diff --git a/MapControl/WPF/ImageLoader.WPF.cs b/MapControl/WPF/ImageLoader.WPF.cs
index 42d3a3d5..a89869bc 100644
--- a/MapControl/WPF/ImageLoader.WPF.cs
+++ b/MapControl/WPF/ImageLoader.WPF.cs
@@ -10,14 +10,13 @@ using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
-using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace MapControl
{
public static partial class ImageLoader
{
- public static ImageSource LoadImage(Stream stream)
+ public static BitmapSource LoadImage(Stream stream)
{
var bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
@@ -28,12 +27,12 @@ namespace MapControl
return bitmapImage;
}
- public static Task LoadImageAsync(Stream stream)
+ public static Task LoadImageAsync(Stream stream)
{
return Task.Run(() => LoadImage(stream));
}
- public static ImageSource LoadImage(byte[] buffer)
+ public static BitmapSource LoadImage(byte[] buffer)
{
using (var stream = new MemoryStream(buffer))
{
@@ -41,12 +40,12 @@ namespace MapControl
}
}
- public static Task LoadImageAsync(byte[] buffer)
+ public static Task LoadImageAsync(byte[] buffer)
{
return Task.Run(() => LoadImage(buffer));
}
- private static async Task LoadImageAsync(HttpContent content)
+ private static async Task LoadImageAsync(HttpContent content)
{
using (var stream = new MemoryStream())
{
@@ -56,23 +55,23 @@ namespace MapControl
}
}
- private static ImageSource LoadLocalImage(Uri uri)
+ private static BitmapSource LoadLocalImage(Uri uri)
{
- ImageSource imageSource = null;
+ BitmapSource image = null;
var path = uri.IsAbsoluteUri ? uri.LocalPath : uri.OriginalString;
if (File.Exists(path))
{
using (var stream = File.OpenRead(path))
{
- imageSource = LoadImage(stream);
+ image = LoadImage(stream);
}
}
- return imageSource;
+ return image;
}
- private static Task LoadLocalImageAsync(Uri uri)
+ private static Task LoadLocalImageAsync(Uri uri)
{
return Task.Run(() => LoadLocalImage(uri));
}
diff --git a/MapControlExtended.sln b/MapControlExtended.sln
index 15315186..2caf67e3 100644
--- a/MapControlExtended.sln
+++ b/MapControlExtended.sln
@@ -33,6 +33,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WpfApplication", "SampleApp
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MapProjections.UWP", "MapProjections\UWP\MapProjections.UWP.csproj", "{9EE69591-5EDC-45E3-893E-2F9A4B82D538}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MapImages", "MapImages", "{2FDC8B91-FB95-4C57-8183-63587FBFE180}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MapImages.WPF", "MapImages\WPF\MapImages.WPF.csproj", "{52042F63-563A-45BB-9A08-A8635AAAB84C}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MapImages.UWP", "MapImages\UWP\MapImages.UWP.csproj", "{BE08B7BC-8C89-4837-BCE7-EDDDABEAB372}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -213,6 +219,38 @@ Global
{9EE69591-5EDC-45E3-893E-2F9A4B82D538}.Release|x64.Build.0 = Release|Any CPU
{9EE69591-5EDC-45E3-893E-2F9A4B82D538}.Release|x86.ActiveCfg = Release|Any CPU
{9EE69591-5EDC-45E3-893E-2F9A4B82D538}.Release|x86.Build.0 = Release|Any CPU
+ {52042F63-563A-45BB-9A08-A8635AAAB84C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {52042F63-563A-45BB-9A08-A8635AAAB84C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {52042F63-563A-45BB-9A08-A8635AAAB84C}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {52042F63-563A-45BB-9A08-A8635AAAB84C}.Debug|ARM.Build.0 = Debug|Any CPU
+ {52042F63-563A-45BB-9A08-A8635AAAB84C}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {52042F63-563A-45BB-9A08-A8635AAAB84C}.Debug|x64.Build.0 = Debug|Any CPU
+ {52042F63-563A-45BB-9A08-A8635AAAB84C}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {52042F63-563A-45BB-9A08-A8635AAAB84C}.Debug|x86.Build.0 = Debug|Any CPU
+ {52042F63-563A-45BB-9A08-A8635AAAB84C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {52042F63-563A-45BB-9A08-A8635AAAB84C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {52042F63-563A-45BB-9A08-A8635AAAB84C}.Release|ARM.ActiveCfg = Release|Any CPU
+ {52042F63-563A-45BB-9A08-A8635AAAB84C}.Release|ARM.Build.0 = Release|Any CPU
+ {52042F63-563A-45BB-9A08-A8635AAAB84C}.Release|x64.ActiveCfg = Release|Any CPU
+ {52042F63-563A-45BB-9A08-A8635AAAB84C}.Release|x64.Build.0 = Release|Any CPU
+ {52042F63-563A-45BB-9A08-A8635AAAB84C}.Release|x86.ActiveCfg = Release|Any CPU
+ {52042F63-563A-45BB-9A08-A8635AAAB84C}.Release|x86.Build.0 = Release|Any CPU
+ {BE08B7BC-8C89-4837-BCE7-EDDDABEAB372}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {BE08B7BC-8C89-4837-BCE7-EDDDABEAB372}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {BE08B7BC-8C89-4837-BCE7-EDDDABEAB372}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {BE08B7BC-8C89-4837-BCE7-EDDDABEAB372}.Debug|ARM.Build.0 = Debug|Any CPU
+ {BE08B7BC-8C89-4837-BCE7-EDDDABEAB372}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {BE08B7BC-8C89-4837-BCE7-EDDDABEAB372}.Debug|x64.Build.0 = Debug|Any CPU
+ {BE08B7BC-8C89-4837-BCE7-EDDDABEAB372}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {BE08B7BC-8C89-4837-BCE7-EDDDABEAB372}.Debug|x86.Build.0 = Debug|Any CPU
+ {BE08B7BC-8C89-4837-BCE7-EDDDABEAB372}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {BE08B7BC-8C89-4837-BCE7-EDDDABEAB372}.Release|Any CPU.Build.0 = Release|Any CPU
+ {BE08B7BC-8C89-4837-BCE7-EDDDABEAB372}.Release|ARM.ActiveCfg = Release|Any CPU
+ {BE08B7BC-8C89-4837-BCE7-EDDDABEAB372}.Release|ARM.Build.0 = Release|Any CPU
+ {BE08B7BC-8C89-4837-BCE7-EDDDABEAB372}.Release|x64.ActiveCfg = Release|Any CPU
+ {BE08B7BC-8C89-4837-BCE7-EDDDABEAB372}.Release|x64.Build.0 = Release|Any CPU
+ {BE08B7BC-8C89-4837-BCE7-EDDDABEAB372}.Release|x86.ActiveCfg = Release|Any CPU
+ {BE08B7BC-8C89-4837-BCE7-EDDDABEAB372}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -228,6 +266,8 @@ Global
{426C21C0-5F14-491F-BCD1-6D2993510420} = {7BC11E28-8D3B-4C5B-AC08-AB249CC95F6D}
{F92DA93D-75DB-4308-A5F9-6B4C3908A675} = {8F2103C2-78AF-4810-8FB9-67572F50C8FC}
{9EE69591-5EDC-45E3-893E-2F9A4B82D538} = {7BC11E28-8D3B-4C5B-AC08-AB249CC95F6D}
+ {52042F63-563A-45BB-9A08-A8635AAAB84C} = {2FDC8B91-FB95-4C57-8183-63587FBFE180}
+ {BE08B7BC-8C89-4837-BCE7-EDDDABEAB372} = {2FDC8B91-FB95-4C57-8183-63587FBFE180}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {458346DD-B23F-4FDC-8F9D-A10F1882A4DB}
diff --git a/MapImages/Shared/GroundOverlayPanel.cs b/MapImages/Shared/GroundOverlayPanel.cs
new file mode 100644
index 00000000..7a1b3f13
--- /dev/null
+++ b/MapImages/Shared/GroundOverlayPanel.cs
@@ -0,0 +1,205 @@
+// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control
+// © 2018 Clemens Fischer
+// Licensed under the Microsoft Public License (Ms-PL)
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Xml;
+#if WINDOWS_UWP
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Media;
+#else
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Media;
+#endif
+
+namespace MapControl.Images
+{
+ public partial class GroundOverlayPanel : MapPanel
+ {
+ class LatLonBox : BoundingBox
+ {
+ public LatLonBox(double south, double west, double north, double east, double rotation)
+ : base(south, west, north, east)
+ {
+ Rotation = rotation;
+ }
+
+ public double Rotation { get; }
+ }
+
+ class ImageOverlay
+ {
+ public ImageOverlay(LatLonBox latLonBox, string imagePath, int zIndex)
+ {
+ LatLonBox = latLonBox;
+ ImagePath = imagePath;
+ ZIndex = zIndex;
+ }
+
+ public LatLonBox LatLonBox { get; }
+ public string ImagePath { get; }
+ public int ZIndex { get; }
+ public ImageSource ImageSource { get; set; }
+ }
+
+ public static readonly DependencyProperty KmlFileProperty = DependencyProperty.Register(
+ nameof(KmlFile), typeof(string), typeof(GroundOverlayPanel),
+ new PropertyMetadata(null, async (o, e) => await ((GroundOverlayPanel)o).KmlFilePropertyChanged((string)e.NewValue)));
+
+ public string KmlFile
+ {
+ get { return (string)GetValue(KmlFileProperty); }
+ set { SetValue(KmlFileProperty, value); }
+ }
+
+ private async Task KmlFilePropertyChanged(string path)
+ {
+ IEnumerable imageOverlays = null;
+
+ if (!string.IsNullOrEmpty(path))
+ {
+ try
+ {
+ var ext = Path.GetExtension(path).ToLower();
+ if (ext == ".kmz")
+ {
+ imageOverlays = await ReadGroundOverlaysFromArchive(path);
+ }
+ else if (ext == ".kml")
+ {
+ imageOverlays = await ReadGroundOverlaysFromFile(path);
+ }
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine("GroundOverlayPanel: {0}: {1}", path, ex.Message);
+ }
+ }
+
+ Children.Clear();
+
+ if (imageOverlays != null)
+ {
+ AddImageOverlays(imageOverlays);
+ }
+ }
+
+ private void AddImageOverlays(IEnumerable imageOverlays)
+ {
+ foreach (var imageOverlay in imageOverlays.Where(i => i.ImageSource != null))
+ {
+ FrameworkElement overlay = new Image
+ {
+ Source = imageOverlay.ImageSource,
+ Stretch = Stretch.Fill
+ };
+
+ if (imageOverlay.LatLonBox.Rotation != 0d)
+ {
+ overlay.RenderTransform = new RotateTransform { Angle = -imageOverlay.LatLonBox.Rotation };
+ overlay.RenderTransformOrigin = new Point(0.5, 0.5);
+
+ // additional Panel for map rotation, see MapPanel.ArrangeElementWithBoundingBox
+ var panel = new Grid { Background = null };
+ panel.Children.Add(overlay);
+ overlay = panel;
+ }
+
+ SetBoundingBox(overlay, imageOverlay.LatLonBox);
+ Canvas.SetZIndex(overlay, imageOverlay.ZIndex);
+ Children.Add(overlay);
+ }
+ }
+
+ private IEnumerable ReadGroundOverlays(XmlDocument kmlDocument)
+ {
+ foreach (XmlElement groundOverlayElement in kmlDocument.GetElementsByTagName("GroundOverlay"))
+ {
+ LatLonBox latLonBox = null;
+ string imagePath = null;
+ int zIndex = 0;
+
+ foreach (var childElement in groundOverlayElement.ChildNodes.OfType())
+ {
+ switch (childElement.LocalName)
+ {
+ case "LatLonBox":
+ latLonBox = ReadLatLonBox(childElement);
+ break;
+ case "Icon":
+ imagePath = ReadImagePath(childElement);
+ break;
+ case "drawOrder":
+ int.TryParse(childElement.InnerText.Trim(), out zIndex);
+ break;
+ }
+ }
+
+ if (latLonBox != null && imagePath != null)
+ {
+ yield return new ImageOverlay(latLonBox, imagePath, zIndex);
+ }
+ }
+ }
+
+ private string ReadImagePath(XmlElement element)
+ {
+ string href = null;
+
+ foreach (var childElement in element.ChildNodes.OfType())
+ {
+ switch (childElement.LocalName)
+ {
+ case "href":
+ href = childElement.InnerText.Trim();
+ break;
+ }
+ }
+
+ return href;
+ }
+
+ private LatLonBox ReadLatLonBox(XmlElement element)
+ {
+ double north = double.NaN;
+ double south = double.NaN;
+ double east = double.NaN;
+ double west = double.NaN;
+ double rotation = 0d;
+
+ foreach (var childElement in element.ChildNodes.OfType())
+ {
+ switch (childElement.LocalName)
+ {
+ case "north":
+ double.TryParse(childElement.InnerText.Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out north);
+ break;
+ case "south":
+ double.TryParse(childElement.InnerText.Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out south);
+ break;
+ case "east":
+ double.TryParse(childElement.InnerText.Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out east);
+ break;
+ case "west":
+ double.TryParse(childElement.InnerText.Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out west);
+ break;
+ case "rotation":
+ double.TryParse(childElement.InnerText.Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out rotation);
+ break;
+ }
+ }
+
+ return !double.IsNaN(north) && !double.IsNaN(south) && !double.IsNaN(east) && !double.IsNaN(west)
+ ? new LatLonBox(south, west, north, east, rotation)
+ : null;
+ }
+ }
+}
diff --git a/MapImages/Shared/WorldFile.cs b/MapImages/Shared/WorldFile.cs
new file mode 100644
index 00000000..59492305
--- /dev/null
+++ b/MapImages/Shared/WorldFile.cs
@@ -0,0 +1,133 @@
+// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control
+// © 2018 Clemens Fischer
+// Licensed under the Microsoft Public License (Ms-PL)
+
+using System;
+using System.IO;
+using System.Threading.Tasks;
+using MapControl.Projections;
+#if WINDOWS_UWP
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Media;
+using Windows.UI.Xaml.Media.Imaging;
+#else
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+#endif
+
+namespace MapControl.Images
+{
+ public static class WorldFile
+ {
+ public static readonly DependencyProperty ImagePathProperty = DependencyProperty.RegisterAttached(
+ "ImagePath", typeof(string), typeof(WorldFile),
+ new PropertyMetadata(null, async (o, e) => await SetWorldImageAsync((Image)o, (string)e.NewValue)));
+
+ public static string GetImagePath(this Image image)
+ {
+ return (string)image.GetValue(ImagePathProperty);
+ }
+
+ public static void SetImagePath(this Image image, string imagePath)
+ {
+ image.SetValue(ImagePathProperty, imagePath);
+ }
+
+ public static void SetWorldImage(this Image image, BitmapSource bitmapSource, WorldFileParameters parameters, MapProjection projection = null)
+ {
+ image.Source = bitmapSource;
+ image.Stretch = Stretch.Fill;
+
+ MapPanel.SetBoundingBox(image, parameters.GetBoundingBox(bitmapSource.PixelWidth, bitmapSource.PixelHeight, projection));
+ }
+
+ private static async Task SetWorldImageAsync(Image image, string imagePath)
+ {
+ var ext = Path.GetExtension(imagePath);
+ if (ext.Length < 4)
+ {
+ throw new ArgumentException("Invalid image file path extension, must have at least three characters.");
+ }
+
+ BitmapSource bitmap;
+ using (var stream = File.OpenRead(imagePath))
+ {
+#if WINDOWS_UWP
+ bitmap = await ImageLoader.LoadImageAsync(stream.AsRandomAccessStream());
+#else
+ bitmap = await ImageLoader.LoadImageAsync(stream);
+#endif
+ }
+
+ var dir = Path.GetDirectoryName(imagePath);
+ var file = Path.GetFileNameWithoutExtension(imagePath);
+ var worldFilePath = Path.Combine(dir, file + ext.Remove(2, 1) + "w");
+ var projFilePath = Path.Combine(dir, file + ".prj");
+
+ var parameters = new WorldFileParameters(worldFilePath);
+ MapProjection projection = null;
+
+ if (File.Exists(projFilePath))
+ {
+ projection = new GeoApiProjection { WKT = File.ReadAllText(projFilePath) };
+ }
+
+ SetWorldImage(image, bitmap, parameters, projection);
+ }
+
+ public static FrameworkElement CreateWorldImage(BitmapSource bitmap, WorldFileParameters parameters)
+ {
+ if (parameters.XScale == 0d || parameters.YScale == 0d)
+ {
+ throw new ArgumentException("Invalid WorldFileParameters, XScale and YScale must be non-zero.");
+ }
+
+ var pixelWidth = parameters.XScale;
+ var pixelHeight = parameters.YScale;
+ var rotation = 0d;
+
+ if (parameters.YSkew != 0 || parameters.XSkew != 0)
+ {
+ pixelWidth = Math.Sqrt(parameters.XScale * parameters.XScale + parameters.YSkew * parameters.YSkew);
+ pixelHeight = Math.Sqrt(parameters.YScale * parameters.YScale + parameters.XSkew * parameters.XSkew);
+
+ var xAxisRotation = Math.Atan2(parameters.YSkew, parameters.XScale) / Math.PI * 180d;
+ var yAxisRotation = Math.Atan2(parameters.XSkew, -parameters.YScale) / Math.PI * 180d;
+ rotation = 0.5 * (xAxisRotation + yAxisRotation);
+ }
+
+ var x1 = parameters.XOrigin;
+ var x2 = parameters.XOrigin + pixelWidth * bitmap.PixelWidth;
+ var y1 = parameters.YOrigin;
+ var y2 = parameters.YOrigin + pixelHeight * bitmap.PixelHeight;
+
+ var bbox = new BoundingBox
+ {
+ West = Math.Min(x1, x2),
+ East = Math.Max(x1, x2),
+ South = Math.Min(y1, y2),
+ North = Math.Max(y1, y2)
+ };
+
+ FrameworkElement image = new Image
+ {
+ Source = bitmap,
+ Stretch = Stretch.Fill
+ };
+
+ if (rotation != 0d)
+ {
+ image.RenderTransform = new RotateTransform { Angle = rotation };
+ var panel = new Grid();
+ panel.Children.Add(image);
+ image = panel;
+ }
+
+ MapPanel.SetBoundingBox(image, bbox);
+ return image;
+ }
+ }
+}
diff --git a/MapImages/Shared/WorldFileParameters.cs b/MapImages/Shared/WorldFileParameters.cs
new file mode 100644
index 00000000..267136d4
--- /dev/null
+++ b/MapImages/Shared/WorldFileParameters.cs
@@ -0,0 +1,94 @@
+// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control
+// © 2018 Clemens Fischer
+// Licensed under the Microsoft Public License (Ms-PL)
+
+using System;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+#if WINDOWS_UWP
+using Windows.Foundation;
+#else
+using System.Windows;
+#endif
+
+namespace MapControl.Images
+{
+ public class WorldFileParameters
+ {
+ public WorldFileParameters()
+ {
+ }
+
+ public WorldFileParameters(string path)
+ {
+ if (!File.Exists(path))
+ {
+ throw new ArgumentException("World file \"" + path + "\"not found.");
+ }
+
+ var lines = File.ReadLines(path).Take(6).ToList();
+
+ if (lines.Count != 6)
+ {
+ throw new ArgumentException("Invalid number of parameters in world file \"" + path + "\".");
+ }
+
+ double xscale, yskew, xskew, yscale, xorigin, yorigin;
+
+ if (!double.TryParse(lines[0], NumberStyles.Float, CultureInfo.InvariantCulture, out xscale) ||
+ !double.TryParse(lines[1], NumberStyles.Float, CultureInfo.InvariantCulture, out yskew) ||
+ !double.TryParse(lines[2], NumberStyles.Float, CultureInfo.InvariantCulture, out xskew) ||
+ !double.TryParse(lines[3], NumberStyles.Float, CultureInfo.InvariantCulture, out yscale) ||
+ !double.TryParse(lines[4], NumberStyles.Float, CultureInfo.InvariantCulture, out xorigin) ||
+ !double.TryParse(lines[5], NumberStyles.Float, CultureInfo.InvariantCulture, out yorigin))
+ {
+ throw new ArgumentException("Failed parsing parameters in world file \"" + path + "\".");
+ }
+
+ XScale = xscale;
+ YSkew = yskew;
+ XSkew = xskew;
+ YScale = yscale;
+ XOrigin = xorigin;
+ YOrigin = yorigin;
+ }
+
+ public double XScale { get; set; }
+ public double YSkew { get; set; }
+ public double XSkew { get; set; }
+ public double YScale { get; set; }
+ public double XOrigin { get; set; }
+ public double YOrigin { get; set; }
+
+ public BoundingBox GetBoundingBox(double imageWidth, double imageHeight, MapProjection projection = null)
+ {
+ if (XScale == 0d || YScale == 0d)
+ {
+ throw new ArgumentException("Invalid WorldFileParameters, XScale and YScale must be non-zero.");
+ }
+
+ if (YSkew != 0d || XSkew != 0d)
+ {
+ throw new ArgumentException("Invalid WorldFileParameters, YSkew and XSkew must be zero.");
+ }
+
+ var p1 = new Point(XOrigin, YOrigin);
+ var p2 = new Point(XOrigin + XScale * imageWidth, YOrigin + YScale * imageHeight);
+ var rect = new Rect(p1, p2);
+
+ if (projection != null)
+ {
+ return projection.RectToBoundingBox(rect);
+ }
+
+ return new BoundingBox
+ {
+ West = rect.X,
+ East = rect.X + rect.Width,
+ South = rect.Y,
+ North = rect.Y + rect.Height
+ };
+ }
+ }
+}
diff --git a/MapImages/Shared/ZoomLevelToOpacityConverter.cs b/MapImages/Shared/ZoomLevelToOpacityConverter.cs
new file mode 100644
index 00000000..2fb38717
--- /dev/null
+++ b/MapImages/Shared/ZoomLevelToOpacityConverter.cs
@@ -0,0 +1,62 @@
+// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control
+// © 2018 Clemens Fischer
+// Licensed under the Microsoft Public License (Ms-PL)
+
+using System;
+using System.Globalization;
+#if WINDOWS_UWP
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Data;
+#else
+using System.Windows;
+using System.Windows.Data;
+#endif
+
+namespace MapControl.Images
+{
+ public class ZoomLevelToOpacityConverter : IValueConverter
+ {
+ public double MinZoomLevel { get; set; } = 0d;
+ public double MaxZoomLevel { get; set; } = 22d;
+ public double FadeZoomRange { get; set; } = 1d;
+
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ if (!(value is double))
+ {
+ return DependencyProperty.UnsetValue;
+ }
+
+ var zoomLevel = (double)value;
+ var opacity = 0d;
+
+ if (zoomLevel > MinZoomLevel && zoomLevel < MaxZoomLevel)
+ {
+ opacity = 1d;
+
+ if (FadeZoomRange > 0d)
+ {
+ opacity = Math.Min(opacity, (zoomLevel - MinZoomLevel) / FadeZoomRange);
+ opacity = Math.Min(opacity, (MaxZoomLevel - zoomLevel) / FadeZoomRange);
+ }
+ }
+
+ return opacity;
+ }
+
+ public object Convert(object value, Type targetType, object parameter, string language)
+ {
+ return Convert(value, targetType, parameter, CultureInfo.InvariantCulture);
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotSupportedException();
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, string language)
+ {
+ throw new NotSupportedException();
+ }
+ }
+}
diff --git a/MapImages/UWP/GroundOverlayPanel.UWP.cs b/MapImages/UWP/GroundOverlayPanel.UWP.cs
new file mode 100644
index 00000000..bce993ae
--- /dev/null
+++ b/MapImages/UWP/GroundOverlayPanel.UWP.cs
@@ -0,0 +1,92 @@
+// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control
+// © 2018 Clemens Fischer
+// Licensed under the Microsoft Public License (Ms-PL)
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.IO.Compression;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Xml;
+using Windows.Storage;
+
+namespace MapControl.Images
+{
+ public partial class GroundOverlayPanel
+ {
+ private async Task> ReadGroundOverlaysFromFile(string docFile)
+ {
+ if (!Path.IsPathRooted(docFile))
+ {
+ docFile = Path.Combine(Directory.GetCurrentDirectory(), docFile);
+ }
+
+ var docUri = new Uri(docFile);
+
+ var imageOverlays = await Task.Run(async () =>
+ {
+ var file = await StorageFile.GetFileFromPathAsync(docFile);
+ var kmlDocument = new XmlDocument();
+
+ using (var stream = await file.OpenReadAsync())
+ {
+ kmlDocument.Load(stream.AsStreamForRead());
+ }
+
+ return ReadGroundOverlays(kmlDocument).ToList();
+ });
+
+ foreach (var imageOverlay in imageOverlays)
+ {
+ imageOverlay.ImageSource = await ImageLoader.LoadImageAsync(new Uri(docUri, imageOverlay.ImagePath));
+ }
+
+ return imageOverlays;
+ }
+
+ private async Task> ReadGroundOverlaysFromArchive(string archiveFile)
+ {
+ using (var archive = ZipFile.OpenRead(archiveFile))
+ {
+ var docEntry = archive.GetEntry("doc.kml")
+ ?? archive.Entries.FirstOrDefault(e => e.Name.EndsWith(".kml"));
+
+ if (docEntry == null)
+ {
+ throw new ArgumentException("No KML entry found in " + archiveFile);
+ }
+
+ var imageOverlays = await Task.Run(() =>
+ {
+ var kmlDocument = new XmlDocument();
+
+ using (var docStream = docEntry.Open())
+ {
+ kmlDocument.Load(docStream);
+ }
+
+ return ReadGroundOverlays(kmlDocument).ToList();
+ });
+
+ foreach (var imageOverlay in imageOverlays)
+ {
+ var imageEntry = archive.GetEntry(imageOverlay.ImagePath);
+
+ if (imageEntry != null)
+ {
+ using (var zipStream = imageEntry.Open())
+ using (var memoryStream = new MemoryStream())
+ {
+ await zipStream.CopyToAsync(memoryStream);
+ memoryStream.Position = 0;
+ imageOverlay.ImageSource = await ImageLoader.LoadImageAsync(memoryStream.AsRandomAccessStream());
+ }
+ }
+ }
+
+ return imageOverlays;
+ }
+ }
+ }
+}
diff --git a/MapImages/UWP/MapImages.UWP.csproj b/MapImages/UWP/MapImages.UWP.csproj
new file mode 100644
index 00000000..56ffffd9
--- /dev/null
+++ b/MapImages/UWP/MapImages.UWP.csproj
@@ -0,0 +1,96 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {BE08B7BC-8C89-4837-BCE7-EDDDABEAB372}
+ Library
+ Properties
+ MapControl.Images
+ MapImages.UWP
+ en-US
+ UAP
+ 10.0.10586.0
+ 10.0.10586.0
+ 14
+ 512
+ {A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
+
+
+ AnyCPU
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
+ prompt
+ 4
+
+
+ AnyCPU
+ pdbonly
+ true
+ bin\Release\
+ TRACE;NETFX_CORE;WINDOWS_UWP
+ prompt
+ 4
+
+
+ PackageReference
+
+
+
+ GroundOverlayPanel.cs
+
+
+ WorldFile.cs
+
+
+ WorldFileParameters.cs
+
+
+ ZoomLevelToOpacityConverter.cs
+
+
+
+
+
+
+
+ 6.2.2
+
+
+
+
+ {9545f73c-9c35-4cf6-baae-19a0baebd344}
+ MapControl.UWP
+
+
+ {9ee69591-5edc-45e3-893e-2f9a4b82d538}
+ MapProjections.UWP
+
+
+
+
+ MapControl.snk
+
+
+
+ 14.0
+
+
+ true
+
+
+ ..\..\MapControl.snk
+
+
+
+
\ No newline at end of file
diff --git a/MapImages/UWP/Properties/AssemblyInfo.cs b/MapImages/UWP/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000..51a08645
--- /dev/null
+++ b/MapImages/UWP/Properties/AssemblyInfo.cs
@@ -0,0 +1,14 @@
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+[assembly: AssemblyTitle("XAML Map Control Image Support (UWP)")]
+[assembly: AssemblyDescription("Image Support Library for XAML Map Control")]
+[assembly: AssemblyProduct("XAML Map Control")]
+[assembly: AssemblyCompany("Clemens Fischer")]
+[assembly: AssemblyCopyright("© 2018 Clemens Fischer")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyVersion("4.12.0")]
+[assembly: AssemblyFileVersion("4.12.0")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCulture("")]
+[assembly: ComVisible(false)]
diff --git a/MapImages/UWP/Properties/MapImages.UWP.rd.xml b/MapImages/UWP/Properties/MapImages.UWP.rd.xml
new file mode 100644
index 00000000..bebda206
--- /dev/null
+++ b/MapImages/UWP/Properties/MapImages.UWP.rd.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
diff --git a/MapImages/WPF/GroundOverlayPanel.WPF.cs b/MapImages/WPF/GroundOverlayPanel.WPF.cs
new file mode 100644
index 00000000..b4dd9af5
--- /dev/null
+++ b/MapImages/WPF/GroundOverlayPanel.WPF.cs
@@ -0,0 +1,86 @@
+// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control
+// © 2018 Clemens Fischer
+// Licensed under the Microsoft Public License (Ms-PL)
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.IO.Compression;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Xml;
+
+namespace MapControl.Images
+{
+ public partial class GroundOverlayPanel
+ {
+ private async Task> ReadGroundOverlaysFromFile(string docFile)
+ {
+ if (!Path.IsPathRooted(docFile))
+ {
+ docFile = Path.Combine(Directory.GetCurrentDirectory(), docFile);
+ }
+
+ var docUri = new Uri(docFile);
+
+ var imageOverlays = await Task.Run(() =>
+ {
+ var kmlDocument = new XmlDocument();
+ kmlDocument.Load(docUri.ToString());
+
+ return ReadGroundOverlays(kmlDocument).ToList();
+ });
+
+ foreach (var imageOverlay in imageOverlays)
+ {
+ imageOverlay.ImageSource = await ImageLoader.LoadImageAsync(new Uri(docUri, imageOverlay.ImagePath));
+ }
+
+ return imageOverlays;
+ }
+
+ private Task> ReadGroundOverlaysFromArchive(string archiveFile)
+ {
+ return Task.Run(() =>
+ {
+ using (var archive = ZipFile.OpenRead(archiveFile))
+ {
+ var docEntry = archive.GetEntry("doc.kml")
+ ?? archive.Entries.FirstOrDefault(e => e.Name.EndsWith(".kml"));
+
+ if (docEntry == null)
+ {
+ throw new ArgumentException("No KML entry found in " + archiveFile);
+ }
+
+ var kmlDocument = new XmlDocument();
+
+ using (var docStream = docEntry.Open())
+ {
+ kmlDocument.Load(docStream);
+ }
+
+ var imageOverlays = ReadGroundOverlays(kmlDocument).ToList();
+
+ foreach (var imageOverlay in imageOverlays)
+ {
+ var imageEntry = archive.GetEntry(imageOverlay.ImagePath);
+
+ if (imageEntry != null)
+ {
+ using (var zipStream = imageEntry.Open())
+ using (var memoryStream = new MemoryStream())
+ {
+ zipStream.CopyTo(memoryStream);
+ memoryStream.Position = 0;
+ imageOverlay.ImageSource = ImageLoader.LoadImage(memoryStream);
+ }
+ }
+ }
+
+ return imageOverlays;
+ }
+ });
+ }
+ }
+}
diff --git a/MapImages/WPF/MapImages.WPF.csproj b/MapImages/WPF/MapImages.WPF.csproj
new file mode 100644
index 00000000..7b34f18e
--- /dev/null
+++ b/MapImages/WPF/MapImages.WPF.csproj
@@ -0,0 +1,81 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {52042F63-563A-45BB-9A08-A8635AAAB84C}
+ Library
+ Properties
+ MapControl.Images
+ MapImages.WPF
+ v4.7.2
+ 512
+ true
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ none
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+ true
+
+
+ ..\..\MapControl.snk
+
+
+
+
+
+
+
+
+
+
+
+
+
+ GroundOverlayPanel.cs
+
+
+ WorldFile.cs
+
+
+ WorldFileParameters.cs
+
+
+ ZoomLevelToOpacityConverter.cs
+
+
+
+
+
+
+ {a204a102-c745-4d65-aec8-7b96faedef2d}
+ MapControl.WPF
+
+
+ {426c21c0-5f14-491f-bcd1-6d2993510420}
+ MapProjections.WPF
+
+
+
+
+ MapControl.snk
+
+
+
+
\ No newline at end of file
diff --git a/MapImages/WPF/Properties/AssemblyInfo.cs b/MapImages/WPF/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000..eea202e6
--- /dev/null
+++ b/MapImages/WPF/Properties/AssemblyInfo.cs
@@ -0,0 +1,14 @@
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+[assembly: AssemblyTitle("XAML Map Control Image Support (WPF)")]
+[assembly: AssemblyDescription("Image Support Library for XAML Map Control")]
+[assembly: AssemblyProduct("XAML Map Control")]
+[assembly: AssemblyCompany("Clemens Fischer")]
+[assembly: AssemblyCopyright("© 2018 Clemens Fischer")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyVersion("4.12.0")]
+[assembly: AssemblyFileVersion("4.12.0")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCulture("")]
+[assembly: ComVisible(false)]
diff --git a/MapProjections/UWP/MapProjections.UWP.csproj b/MapProjections/UWP/MapProjections.UWP.csproj
index 31b1daa8..6424f1c2 100644
--- a/MapProjections/UWP/MapProjections.UWP.csproj
+++ b/MapProjections/UWP/MapProjections.UWP.csproj
@@ -63,9 +63,20 @@
MapControl.UWP
+
+
+ MapControl.snk
+
+
14.0
+
+ true
+
+
+ ..\..\MapControl.snk
+