mirror of
https://github.com/ClemensFischer/XAML-Map-Control.git
synced 2025-12-06 07:12:04 +01:00
Version 4.10.0: Updated target framework versions. Cleanup of TypeConverters, ImageLoader, MBTileSource.
This commit is contained in:
parent
63a4c1f0a7
commit
6a1653056f
|
|
@ -46,7 +46,7 @@
|
|||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform">
|
||||
<Version>6.1.5</Version>
|
||||
<Version>6.1.7</Version>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ using System.Runtime.InteropServices;
|
|||
[assembly: AssemblyCompany("Clemens Fischer")]
|
||||
[assembly: AssemblyCopyright("© 2018 Clemens Fischer")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyVersion("4.9.0")]
|
||||
[assembly: AssemblyFileVersion("4.9.0")]
|
||||
[assembly: AssemblyVersion("4.10.0")]
|
||||
[assembly: AssemblyFileVersion("4.10.0")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
[assembly: ComVisible(false)]
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>MapControl.Caching</RootNamespace>
|
||||
<AssemblyName>FileDbCache.WPF</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<TargetFrameworkProfile />
|
||||
</PropertyGroup>
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ using System.Runtime.InteropServices;
|
|||
[assembly: AssemblyCompany("Clemens Fischer")]
|
||||
[assembly: AssemblyCopyright("© 2018 Clemens Fischer")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyVersion("4.9.0")]
|
||||
[assembly: AssemblyFileVersion("4.9.0")]
|
||||
[assembly: AssemblyVersion("4.10.0")]
|
||||
[assembly: AssemblyFileVersion("4.10.0")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
[assembly: ComVisible(false)]
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control
|
||||
// © 2018 Clemens Fischer
|
||||
// Licensed under the Microsoft Public License (Ms-PL)
|
||||
|
||||
#if WINDOWS_UWP
|
||||
using Windows.UI.Xaml;
|
||||
#else
|
||||
|
|
|
|||
|
|
@ -1,4 +1,8 @@
|
|||
using System;
|
||||
// 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.Runtime.InteropServices.WindowsRuntime;
|
||||
|
|
@ -6,7 +10,6 @@ using System.Threading.Tasks;
|
|||
using Microsoft.Data.Sqlite;
|
||||
using Windows.Storage.Streams;
|
||||
using Windows.UI.Xaml.Media;
|
||||
using Windows.UI.Xaml.Media.Imaging;
|
||||
|
||||
namespace MapControl
|
||||
{
|
||||
|
|
@ -53,10 +56,7 @@ namespace MapControl
|
|||
await stream.WriteAsync(buffer.AsBuffer());
|
||||
stream.Seek(0);
|
||||
|
||||
var bitmapImage = new BitmapImage();
|
||||
await bitmapImage.SetSourceAsync(stream);
|
||||
|
||||
imageSource = bitmapImage;
|
||||
imageSource = await ImageLoader.CreateImageSourceAsync(stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@
|
|||
<Version>2.1.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform">
|
||||
<Version>6.1.5</Version>
|
||||
<Version>6.1.7</Version>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ using System.Runtime.InteropServices;
|
|||
[assembly: AssemblyCompany("Clemens Fischer")]
|
||||
[assembly: AssemblyCopyright("© 2018 Clemens Fischer")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyVersion("4.9.0")]
|
||||
[assembly: AssemblyFileVersion("4.9.0")]
|
||||
[assembly: AssemblyVersion("4.10.0")]
|
||||
[assembly: AssemblyFileVersion("4.10.0")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
[assembly: ComVisible(false)]
|
||||
|
|
|
|||
|
|
@ -1,11 +1,14 @@
|
|||
using System;
|
||||
// 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.Data.SQLite;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using System.Data.SQLite;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
|
||||
namespace MapControl
|
||||
{
|
||||
|
|
@ -46,14 +49,11 @@ namespace MapControl
|
|||
var buffer = await command.ExecuteScalarAsync() as byte[];
|
||||
|
||||
if (buffer != null)
|
||||
{
|
||||
imageSource = await Task.Run(() =>
|
||||
{
|
||||
using (var stream = new MemoryStream(buffer))
|
||||
{
|
||||
return BitmapFrame.Create(stream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
|
||||
imageSource = await ImageLoader.CreateImageSourceAsync(stream);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>MapControl</RootNamespace>
|
||||
<AssemblyName>MBTiles.WPF</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<TargetFrameworkProfile />
|
||||
<NuGetPackageImportStamp>
|
||||
|
|
@ -38,8 +38,8 @@
|
|||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Data.SQLite, Version=1.0.107.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\System.Data.SQLite.Core.1.0.107.0\lib\net45\System.Data.SQLite.dll</HintPath>
|
||||
<Reference Include="System.Data.SQLite, Version=1.0.108.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\System.Data.SQLite.Core.1.0.108.0\lib\net46\System.Data.SQLite.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Xaml" />
|
||||
<Reference Include="WindowsBase" />
|
||||
|
|
@ -61,11 +61,11 @@
|
|||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<Import Project="..\..\packages\System.Data.SQLite.Core.1.0.107.0\build\net45\System.Data.SQLite.Core.targets" Condition="Exists('..\..\packages\System.Data.SQLite.Core.1.0.107.0\build\net45\System.Data.SQLite.Core.targets')" />
|
||||
<Import Project="..\..\packages\System.Data.SQLite.Core.1.0.108.0\build\net46\System.Data.SQLite.Core.targets" Condition="Exists('..\..\packages\System.Data.SQLite.Core.1.0.108.0\build\net46\System.Data.SQLite.Core.targets')" />
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('..\..\packages\System.Data.SQLite.Core.1.0.107.0\build\net45\System.Data.SQLite.Core.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\System.Data.SQLite.Core.1.0.107.0\build\net45\System.Data.SQLite.Core.targets'))" />
|
||||
<Error Condition="!Exists('..\..\packages\System.Data.SQLite.Core.1.0.108.0\build\net46\System.Data.SQLite.Core.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\System.Data.SQLite.Core.1.0.108.0\build\net46\System.Data.SQLite.Core.targets'))" />
|
||||
</Target>
|
||||
</Project>
|
||||
|
|
@ -7,8 +7,8 @@ using System.Runtime.InteropServices;
|
|||
[assembly: AssemblyCompany("Clemens Fischer")]
|
||||
[assembly: AssemblyCopyright("© 2018 Clemens Fischer")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyVersion("4.9.0")]
|
||||
[assembly: AssemblyFileVersion("4.9.0")]
|
||||
[assembly: AssemblyVersion("4.10.0")]
|
||||
[assembly: AssemblyFileVersion("4.10.0")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
[assembly: ComVisible(false)]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="System.Data.SQLite.Core" version="1.0.107.0" targetFramework="net45" />
|
||||
<package id="System.Data.SQLite.Core" version="1.0.108.0" targetFramework="net47" />
|
||||
</packages>
|
||||
|
|
@ -10,7 +10,10 @@ namespace MapControl
|
|||
/// <summary>
|
||||
/// A geographic bounding box with south and north latitude and west and east longitude values in degrees.
|
||||
/// </summary>
|
||||
public partial class BoundingBox
|
||||
#if !WINDOWS_UWP
|
||||
[System.ComponentModel.TypeConverter(typeof(BoundingBoxConverter))]
|
||||
#endif
|
||||
public class BoundingBox
|
||||
{
|
||||
private double south;
|
||||
private double west;
|
||||
|
|
|
|||
69
MapControl/Shared/ImageLoader.cs
Normal file
69
MapControl/Shared/ImageLoader.cs
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
// 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.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
|
||||
|
||||
namespace MapControl
|
||||
{
|
||||
public static partial class ImageLoader
|
||||
{
|
||||
/// <summary>
|
||||
/// The HttpClient instance used when image data is downloaded from a web resource.
|
||||
/// </summary>
|
||||
public static HttpClient HttpClient { get; set; } = new HttpClient();
|
||||
|
||||
public static async Task<ImageSource> LoadImageAsync(Uri uri, bool isTileImage)
|
||||
{
|
||||
ImageSource imageSource = null;
|
||||
|
||||
if (!uri.IsAbsoluteUri || uri.Scheme == "file")
|
||||
{
|
||||
imageSource = await LoadLocalImageAsync(uri);
|
||||
}
|
||||
else if (uri.Scheme == "http")
|
||||
{
|
||||
imageSource = await LoadHttpImageAsync(uri, isTileImage);
|
||||
}
|
||||
else
|
||||
{
|
||||
imageSource = new BitmapImage(uri);
|
||||
}
|
||||
|
||||
return imageSource;
|
||||
}
|
||||
|
||||
public static async Task<ImageSource> LoadHttpImageAsync(Uri uri, bool isTileImage)
|
||||
{
|
||||
ImageSource imageSource = null;
|
||||
|
||||
using (var response = await HttpClient.GetAsync(uri))
|
||||
{
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
Debug.WriteLine("ImageLoader: {0}: {1} {2}", uri, (int)response.StatusCode, response.ReasonPhrase);
|
||||
}
|
||||
else if (!isTileImage || IsTileAvailable(response.Headers))
|
||||
{
|
||||
using (var stream = await GetResponseStreamAsync(response.Content))
|
||||
{
|
||||
imageSource = await CreateImageSourceAsync(stream);
|
||||
}
|
||||
}
|
||||
|
||||
return imageSource;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -10,7 +10,10 @@ namespace MapControl
|
|||
/// <summary>
|
||||
/// A geographic location with latitude and longitude values in degrees.
|
||||
/// </summary>
|
||||
public partial class Location : IEquatable<Location>
|
||||
#if !WINDOWS_UWP
|
||||
[System.ComponentModel.TypeConverter(typeof(LocationConverter))]
|
||||
#endif
|
||||
public class Location : IEquatable<Location>
|
||||
{
|
||||
private double latitude;
|
||||
private double longitude;
|
||||
|
|
|
|||
|
|
@ -11,7 +11,10 @@ namespace MapControl
|
|||
/// <summary>
|
||||
/// A collection of Locations with support for parsing.
|
||||
/// </summary>
|
||||
public partial class LocationCollection : List<Location>
|
||||
#if !WINDOWS_UWP
|
||||
[System.ComponentModel.TypeConverter(typeof(LocationCollectionConverter))]
|
||||
#endif
|
||||
public class LocationCollection : List<Location>
|
||||
{
|
||||
public LocationCollection()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,6 +7,9 @@ using System.Linq;
|
|||
|
||||
namespace MapControl
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides helper methods for geodetic calculations on a sphere.
|
||||
/// </summary>
|
||||
public static class LocationEx
|
||||
{
|
||||
/// <summary>
|
||||
|
|
@ -18,9 +21,48 @@ namespace MapControl
|
|||
var lon1 = location1.Longitude * Math.PI / 180d;
|
||||
var lat2 = location2.Latitude * Math.PI / 180d;
|
||||
var lon2 = location2.Longitude * Math.PI / 180d;
|
||||
var cosS12 = Math.Sin(lat1) * Math.Sin(lat2) + Math.Cos(lat1) * Math.Cos(lat2) * Math.Cos(lon2 - lon1);
|
||||
var sinLat1 = Math.Sin(lat1);
|
||||
var cosLat1 = Math.Cos(lat1);
|
||||
var sinLat2 = Math.Sin(lat2);
|
||||
var cosLat2 = Math.Cos(lat2);
|
||||
var cosLon12 = Math.Cos(lon2 - lon1);
|
||||
var cosS12 = sinLat1 * sinLat2 + cosLat1 * cosLat2 * cosLon12;
|
||||
var s12 = 0d;
|
||||
|
||||
return earthRadius * Math.Acos(Math.Min(Math.Max(cosS12, -1d), 1d));
|
||||
if (Math.Abs(cosS12) < 0.99999999)
|
||||
{
|
||||
s12 = Math.Acos(Math.Min(Math.Max(cosS12, -1d), 1d));
|
||||
}
|
||||
else
|
||||
{
|
||||
var sinLon12 = Math.Sin(lon2 - lon1);
|
||||
var a = cosLat1 * sinLat2 - sinLat1 * cosLat2 * cosLon12;
|
||||
var b = cosLat2 * sinLon12;
|
||||
s12 = Math.Atan2(Math.Sqrt(a * a + b * b), cosS12);
|
||||
}
|
||||
|
||||
return earthRadius * s12;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// see https://en.wikipedia.org/wiki/Great-circle_navigation
|
||||
/// </summary>
|
||||
public static Location GreatCircleLocation(this Location location, double azimuth, double distance, double earthRadius = MapProjection.Wgs84EquatorialRadius)
|
||||
{
|
||||
var s12 = distance / earthRadius;
|
||||
var az1 = azimuth * Math.PI / 180d;
|
||||
var lat1 = location.Latitude * Math.PI / 180d;
|
||||
var lon1 = location.Longitude * Math.PI / 180d;
|
||||
var sinS12 = Math.Sin(s12);
|
||||
var cosS12 = Math.Cos(s12);
|
||||
var sinAz1 = Math.Sin(az1);
|
||||
var cosAz1 = Math.Cos(az1);
|
||||
var sinLat1 = Math.Sin(lat1);
|
||||
var cosLat1 = Math.Cos(lat1);
|
||||
var lat2 = Math.Asin(sinLat1 * cosS12 + cosLat1 * sinS12 * cosAz1);
|
||||
var lon2 = lon1 + Math.Atan2(sinS12 * sinAz1, (cosLat1 * cosS12 - sinLat1 * sinS12 * cosAz1));
|
||||
|
||||
return new Location(lat2 / Math.PI * 180d, lon2 / Math.PI * 180d);
|
||||
}
|
||||
|
||||
public static LocationCollection CalculateMeridianLocations(this Location location, double latitude2, double resolution = 1d)
|
||||
|
|
|
|||
|
|
@ -344,9 +344,11 @@ namespace MapControl
|
|||
var maxZoomLevel = Math.Min(TileGrid.ZoomLevel, MaxZoomLevel);
|
||||
var minZoomLevel = MinZoomLevel;
|
||||
|
||||
if (minZoomLevel < maxZoomLevel && parentMap.MapLayer != this) // load lower tiles only in a base layer
|
||||
if (minZoomLevel < maxZoomLevel &&
|
||||
parentMap.MapLayer != this &&
|
||||
parentMap.Children.Cast<UIElement>().FirstOrDefault() != this)
|
||||
{
|
||||
minZoomLevel = maxZoomLevel;
|
||||
minZoomLevel = maxZoomLevel; // do not load lower level tiles if this is note a "base" layer
|
||||
}
|
||||
|
||||
for (var z = minZoomLevel; z <= maxZoomLevel; z++)
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ using System.Threading.Tasks;
|
|||
#if WINDOWS_UWP
|
||||
using Windows.UI.Xaml.Media;
|
||||
#else
|
||||
using System.ComponentModel;
|
||||
using System.Windows.Media;
|
||||
#endif
|
||||
|
||||
|
|
@ -17,7 +18,10 @@ namespace MapControl
|
|||
/// <summary>
|
||||
/// Provides the download Uri or ImageSource of map tiles.
|
||||
/// </summary>
|
||||
public partial class TileSource
|
||||
#if !WINDOWS_UWP
|
||||
[TypeConverter(typeof(TileSourceConverter))]
|
||||
#endif
|
||||
public class TileSource
|
||||
{
|
||||
private Func<int, int, int, string> getUri;
|
||||
private string uriFormat;
|
||||
|
|
|
|||
|
|
@ -15,72 +15,24 @@ using Windows.Web.Http.Headers;
|
|||
|
||||
namespace MapControl
|
||||
{
|
||||
public static class ImageLoader
|
||||
public static partial class ImageLoader
|
||||
{
|
||||
/// <summary>
|
||||
/// The HttpClient instance used when image data is downloaded from a web resource.
|
||||
/// </summary>
|
||||
public static HttpClient HttpClient { get; set; } = new HttpClient();
|
||||
|
||||
public static async Task<ImageSource> LoadImageAsync(Uri uri, bool isTileImage)
|
||||
{
|
||||
if (!uri.IsAbsoluteUri || uri.Scheme == "file")
|
||||
{
|
||||
return await LoadLocalImageAsync(uri);
|
||||
}
|
||||
|
||||
if (uri.Scheme == "http")
|
||||
{
|
||||
return await LoadHttpImageAsync(uri, isTileImage);
|
||||
}
|
||||
|
||||
return new BitmapImage(uri);
|
||||
}
|
||||
|
||||
public static async Task<ImageSource> LoadLocalImageAsync(Uri uri)
|
||||
{
|
||||
ImageSource imageSource = null;
|
||||
var path = uri.IsAbsoluteUri ? uri.LocalPath : uri.OriginalString;
|
||||
|
||||
if (!File.Exists(path))
|
||||
if (File.Exists(path))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var file = await StorageFile.GetFileFromPathAsync(path);
|
||||
|
||||
using (var stream = await file.OpenReadAsync())
|
||||
{
|
||||
var bitmapImage = new BitmapImage();
|
||||
await bitmapImage.SetSourceAsync(stream);
|
||||
|
||||
return bitmapImage;
|
||||
imageSource = await CreateImageSourceAsync(stream);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<ImageSource> LoadHttpImageAsync(Uri uri, bool isTileImage)
|
||||
{
|
||||
using (var response = await HttpClient.GetAsync(uri))
|
||||
{
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
Debug.WriteLine("ImageLoader: {0}: {1} {2}", uri, (int)response.StatusCode, response.ReasonPhrase);
|
||||
}
|
||||
else if (!isTileImage || IsTileAvailable(response.Headers))
|
||||
{
|
||||
using (var stream = new InMemoryRandomAccessStream())
|
||||
{
|
||||
await response.Content.WriteToStreamAsync(stream);
|
||||
stream.Seek(0);
|
||||
|
||||
var bitmapImage = new BitmapImage();
|
||||
await bitmapImage.SetSourceAsync(stream);
|
||||
|
||||
return bitmapImage;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
return imageSource;
|
||||
}
|
||||
|
||||
public static async Task<bool> LoadHttpTileImageAsync(Uri uri, Func<IBuffer, TimeSpan?, Task> tileCallback)
|
||||
|
|
@ -102,6 +54,21 @@ namespace MapControl
|
|||
}
|
||||
}
|
||||
|
||||
public static async Task<ImageSource> CreateImageSourceAsync(IRandomAccessStream stream)
|
||||
{
|
||||
var bitmapImage = new BitmapImage();
|
||||
await bitmapImage.SetSourceAsync(stream);
|
||||
return bitmapImage;
|
||||
}
|
||||
|
||||
private static async Task<InMemoryRandomAccessStream> GetResponseStreamAsync(IHttpContent content)
|
||||
{
|
||||
var stream = new InMemoryRandomAccessStream();
|
||||
await content.WriteToStreamAsync(stream);
|
||||
stream.Seek(0);
|
||||
return stream;
|
||||
}
|
||||
|
||||
private static bool IsTileAvailable(HttpResponseHeaderCollection responseHeaders)
|
||||
{
|
||||
return !responseHeaders.TryGetValue("X-VE-Tile-Info", out string tileInfo) || tileInfo != "no-tile";
|
||||
|
|
|
|||
|
|
@ -67,6 +67,9 @@
|
|||
<Compile Include="..\Shared\HyperlinkText.cs">
|
||||
<Link>HyperlinkText.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Shared\ImageLoader.cs">
|
||||
<Link>ImageLoader.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Shared\Intersections.cs">
|
||||
<Link>Intersections.cs</Link>
|
||||
</Compile>
|
||||
|
|
@ -168,7 +171,7 @@
|
|||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform">
|
||||
<Version>6.1.5</Version>
|
||||
<Version>6.1.7</Version>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ using System.Runtime.InteropServices;
|
|||
[assembly: AssemblyCompany("Clemens Fischer")]
|
||||
[assembly: AssemblyCopyright("© 2018 Clemens Fischer")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyVersion("4.9.0")]
|
||||
[assembly: AssemblyFileVersion("4.9.0")]
|
||||
[assembly: AssemblyVersion("4.10.0")]
|
||||
[assembly: AssemblyFileVersion("4.10.0")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
[assembly: ComVisible(false)]
|
||||
|
|
|
|||
|
|
@ -8,11 +8,10 @@ using System.Threading.Tasks;
|
|||
using Windows.Storage;
|
||||
using Windows.Storage.Streams;
|
||||
using Windows.UI.Core;
|
||||
using Windows.UI.Xaml.Media.Imaging;
|
||||
|
||||
namespace MapControl
|
||||
{
|
||||
public partial class TileImageLoader : ITileImageLoader
|
||||
public partial class TileImageLoader
|
||||
{
|
||||
/// <summary>
|
||||
/// Default StorageFolder where an IImageCache instance may save cached data,
|
||||
|
|
@ -43,10 +42,10 @@ namespace MapControl
|
|||
{
|
||||
try
|
||||
{
|
||||
loaded = await ImageLoader.LoadHttpTileImageAsync(uri, async (buffer, maxAge) =>
|
||||
loaded = await ImageLoader.LoadHttpTileImageAsync(uri,
|
||||
async (buffer, maxAge) =>
|
||||
{
|
||||
await SetTileImageAsync(tile, buffer); // create BitmapImage before caching
|
||||
|
||||
await Cache.SetAsync(cacheKey, buffer, GetExpiration(maxAge));
|
||||
});
|
||||
}
|
||||
|
|
@ -76,10 +75,7 @@ namespace MapControl
|
|||
{
|
||||
try
|
||||
{
|
||||
var bitmapImage = new BitmapImage();
|
||||
await bitmapImage.SetSourceAsync(stream);
|
||||
|
||||
tile.SetImage(bitmapImage);
|
||||
tile.SetImage(await ImageLoader.CreateImageSourceAsync(stream));
|
||||
tcs.SetResult(null);
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
|
|||
|
|
@ -1,81 +0,0 @@
|
|||
// 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.Diagnostics;
|
||||
using System.IO;
|
||||
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;
|
||||
|
||||
namespace MapControl
|
||||
{
|
||||
public partial class TileSource
|
||||
{
|
||||
/// <summary>
|
||||
/// The HttpClient instance used when image data is downloaded from a web resource.
|
||||
/// </summary>
|
||||
public static HttpClient HttpClient { get; set; } = new HttpClient();
|
||||
|
||||
/// <summary>
|
||||
/// Check HTTP response headers for tile availability, e.g. X-VE-Tile-Info=no-tile
|
||||
/// </summary>
|
||||
public static bool IsTileAvailable(HttpResponseHeaderCollection responseHeaders)
|
||||
{
|
||||
string tileInfo;
|
||||
|
||||
return !responseHeaders.TryGetValue("X-VE-Tile-Info", out tileInfo) || tileInfo != "no-tile";
|
||||
}
|
||||
|
||||
protected static async Task<ImageSource> LoadLocalImageAsync(Uri uri)
|
||||
{
|
||||
var path = uri.IsAbsoluteUri ? uri.LocalPath : uri.OriginalString;
|
||||
|
||||
if (!await Task.Run(() => File.Exists(path)))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var file = await StorageFile.GetFileFromPathAsync(path);
|
||||
|
||||
using (var stream = await file.OpenReadAsync())
|
||||
{
|
||||
var bitmapImage = new BitmapImage();
|
||||
await bitmapImage.SetSourceAsync(stream);
|
||||
|
||||
return bitmapImage;
|
||||
}
|
||||
}
|
||||
|
||||
protected static async Task<ImageSource> LoadHttpImageAsync(Uri uri)
|
||||
{
|
||||
using (var response = await HttpClient.GetAsync(uri))
|
||||
{
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
Debug.WriteLine("TileSource: {0}: {1} {2}", uri, (int)response.StatusCode, response.ReasonPhrase);
|
||||
}
|
||||
else if (IsTileAvailable(response.Headers))
|
||||
{
|
||||
using (var stream = new InMemoryRandomAccessStream())
|
||||
{
|
||||
await response.Content.WriteToStreamAsync(stream);
|
||||
stream.Seek(0);
|
||||
|
||||
var bitmapImage = new BitmapImage();
|
||||
await bitmapImage.SetSourceAsync(stream);
|
||||
|
||||
return bitmapImage;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -15,69 +15,27 @@ using System.Windows.Media.Imaging;
|
|||
|
||||
namespace MapControl
|
||||
{
|
||||
public static class ImageLoader
|
||||
public static partial class ImageLoader
|
||||
{
|
||||
/// <summary>
|
||||
/// The HttpClient instance used when image data is downloaded from a web resource.
|
||||
/// </summary>
|
||||
public static HttpClient HttpClient { get; set; } = new HttpClient();
|
||||
|
||||
public static async Task<ImageSource> LoadImageAsync(Uri uri, bool isTileImage)
|
||||
{
|
||||
if (!uri.IsAbsoluteUri || uri.Scheme == "file")
|
||||
{
|
||||
return await LoadLocalImageAsync(uri);
|
||||
}
|
||||
|
||||
if (uri.Scheme == "http")
|
||||
{
|
||||
return await LoadHttpImageAsync(uri, isTileImage);
|
||||
}
|
||||
|
||||
return new BitmapImage(uri);
|
||||
}
|
||||
|
||||
public static Task<ImageSource> LoadLocalImageAsync(Uri uri)
|
||||
{
|
||||
return Task.Run(() =>
|
||||
{
|
||||
ImageSource imageSource = null;
|
||||
var path = uri.IsAbsoluteUri ? uri.LocalPath : uri.OriginalString;
|
||||
|
||||
if (!File.Exists(path))
|
||||
if (File.Exists(path))
|
||||
{
|
||||
return null;
|
||||
using (var stream = File.OpenRead(path))
|
||||
{
|
||||
imageSource = CreateImageSource(stream);
|
||||
}
|
||||
}
|
||||
|
||||
using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read))
|
||||
{
|
||||
return (ImageSource)BitmapFrame.Create(stream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
|
||||
}
|
||||
return imageSource;
|
||||
});
|
||||
}
|
||||
|
||||
public static async Task<ImageSource> LoadHttpImageAsync(Uri uri, bool isTileImage)
|
||||
{
|
||||
using (var response = await HttpClient.GetAsync(uri))
|
||||
{
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
Debug.WriteLine("ImageLoader: {0}: {1} {2}", uri, (int)response.StatusCode, response.ReasonPhrase);
|
||||
}
|
||||
else if (!isTileImage || IsTileAvailable(response.Headers))
|
||||
{
|
||||
using (var stream = new MemoryStream())
|
||||
{
|
||||
await response.Content.CopyToAsync(stream);
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
return BitmapFrame.Create(stream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<bool> LoadHttpTileImageAsync(Uri uri, Func<MemoryStream, TimeSpan?, Task> tileCallback)
|
||||
{
|
||||
using (var response = await HttpClient.GetAsync(uri))
|
||||
|
|
@ -88,22 +46,46 @@ namespace MapControl
|
|||
}
|
||||
else if (IsTileAvailable(response.Headers))
|
||||
{
|
||||
var stream = new MemoryStream();
|
||||
|
||||
using (var stream = new MemoryStream())
|
||||
{
|
||||
await response.Content.CopyToAsync(stream);
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
await tileCallback(stream, response.Headers.CacheControl?.MaxAge);
|
||||
}
|
||||
}
|
||||
|
||||
return response.IsSuccessStatusCode;
|
||||
}
|
||||
}
|
||||
|
||||
public static ImageSource CreateImageSource(Stream stream)
|
||||
{
|
||||
var bitmapImage = new BitmapImage();
|
||||
bitmapImage.BeginInit();
|
||||
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
|
||||
bitmapImage.StreamSource = stream;
|
||||
bitmapImage.EndInit();
|
||||
bitmapImage.Freeze();
|
||||
return bitmapImage;
|
||||
}
|
||||
|
||||
public static Task<ImageSource> CreateImageSourceAsync(Stream stream)
|
||||
{
|
||||
return Task.Run(() => CreateImageSource(stream));
|
||||
}
|
||||
|
||||
private static async Task<Stream> GetResponseStreamAsync(HttpContent content)
|
||||
{
|
||||
var stream = new MemoryStream();
|
||||
await content.CopyToAsync(stream);
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
return stream;
|
||||
}
|
||||
|
||||
private static bool IsTileAvailable(HttpResponseHeaders responseHeaders)
|
||||
{
|
||||
IEnumerable<string> tileInfo;
|
||||
|
||||
return !responseHeaders.TryGetValues("X-VE-Tile-Info", out tileInfo) || !tileInfo.Contains("no-tile");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,11 +9,12 @@
|
|||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>MapControl</RootNamespace>
|
||||
<AssemblyName>MapControl.WPF</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<TargetFrameworkProfile />
|
||||
<TargetFrameworkProfile>
|
||||
</TargetFrameworkProfile>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
|
|
@ -25,6 +26,7 @@
|
|||
<WarningLevel>4</WarningLevel>
|
||||
<NoWarn>
|
||||
</NoWarn>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>none</DebugType>
|
||||
|
|
@ -33,6 +35,7 @@
|
|||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
|
|
@ -86,6 +89,9 @@
|
|||
<Compile Include="..\Shared\HyperlinkText.cs">
|
||||
<Link>HyperlinkText.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Shared\ImageLoader.cs">
|
||||
<Link>ImageLoader.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Shared\Intersections.cs">
|
||||
<Link>Intersections.cs</Link>
|
||||
</Compile>
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ namespace MapControl
|
|||
{
|
||||
latLabels.Add(new Label(lat, new FormattedText(
|
||||
GetLabelText(lat, labelFormat, "NS"),
|
||||
CultureInfo.InvariantCulture, FlowDirection.LeftToRight, typeface, FontSize, Foreground)));
|
||||
CultureInfo.InvariantCulture, FlowDirection.LeftToRight, typeface, FontSize, Foreground, 1d)));
|
||||
|
||||
drawingContext.DrawLine(pen,
|
||||
projection.LocationToViewportPoint(new Location(lat, boundingBox.West)),
|
||||
|
|
@ -83,7 +83,7 @@ namespace MapControl
|
|||
{
|
||||
lonLabels.Add(new Label(lon, new FormattedText(
|
||||
GetLabelText(Location.NormalizeLongitude(lon), labelFormat, "EW"),
|
||||
CultureInfo.InvariantCulture, FlowDirection.LeftToRight, typeface, FontSize, Foreground)));
|
||||
CultureInfo.InvariantCulture, FlowDirection.LeftToRight, typeface, FontSize, Foreground, 1d)));
|
||||
|
||||
drawingContext.DrawLine(pen,
|
||||
projection.LocationToViewportPoint(new Location(boundingBox.South, lon)),
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ using System.Windows;
|
|||
[assembly: AssemblyCompany("Clemens Fischer")]
|
||||
[assembly: AssemblyCopyright("© 2018 Clemens Fischer")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyVersion("4.9.0")]
|
||||
[assembly: AssemblyFileVersion("4.9.0")]
|
||||
[assembly: AssemblyVersion("4.10.0")]
|
||||
[assembly: AssemblyFileVersion("4.10.0")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
[assembly: ComVisible(false)]
|
||||
|
|
|
|||
|
|
@ -9,11 +9,10 @@ using System.Net;
|
|||
using System.Runtime.Caching;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Media.Imaging;
|
||||
|
||||
namespace MapControl
|
||||
{
|
||||
public partial class TileImageLoader : ITileImageLoader
|
||||
public partial class TileImageLoader
|
||||
{
|
||||
/// <summary>
|
||||
/// Default folder path where an ObjectCache instance may save cached data,
|
||||
|
|
@ -44,10 +43,10 @@ namespace MapControl
|
|||
{
|
||||
try
|
||||
{
|
||||
loaded = await ImageLoader.LoadHttpTileImageAsync(uri, async (stream, maxAge) =>
|
||||
loaded = await ImageLoader.LoadHttpTileImageAsync(uri,
|
||||
async (stream, maxAge) =>
|
||||
{
|
||||
await SetTileImageAsync(tile, stream); // create BitmapFrame before caching
|
||||
|
||||
await SetTileImageAsync(tile, stream); // create BitmapImage before caching
|
||||
SetCachedImage(cacheKey, stream, GetExpiration(maxAge));
|
||||
});
|
||||
}
|
||||
|
|
@ -68,7 +67,7 @@ namespace MapControl
|
|||
|
||||
private async Task SetTileImageAsync(Tile tile, MemoryStream stream)
|
||||
{
|
||||
var imageSource = BitmapFrame.Create(stream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
|
||||
var imageSource = ImageLoader.CreateImageSource(stream);
|
||||
|
||||
await tile.Image.Dispatcher.InvokeAsync(() => tile.SetImage(imageSource));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,12 +21,6 @@ namespace MapControl
|
|||
}
|
||||
}
|
||||
|
||||
[TypeConverter(typeof(LocationConverter))]
|
||||
[Serializable]
|
||||
public partial class Location
|
||||
{
|
||||
}
|
||||
|
||||
public class LocationCollectionConverter : TypeConverter
|
||||
{
|
||||
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
|
||||
|
|
@ -40,11 +34,6 @@ namespace MapControl
|
|||
}
|
||||
}
|
||||
|
||||
[TypeConverter(typeof(LocationCollectionConverter))]
|
||||
public partial class LocationCollection
|
||||
{
|
||||
}
|
||||
|
||||
public class BoundingBoxConverter : TypeConverter
|
||||
{
|
||||
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
|
||||
|
|
@ -58,12 +47,6 @@ namespace MapControl
|
|||
}
|
||||
}
|
||||
|
||||
[TypeConverter(typeof(BoundingBoxConverter))]
|
||||
[Serializable]
|
||||
public partial class BoundingBox
|
||||
{
|
||||
}
|
||||
|
||||
public class TileSourceConverter : TypeConverter
|
||||
{
|
||||
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
|
||||
|
|
@ -76,9 +59,4 @@ namespace MapControl
|
|||
return new TileSource { UriFormat = value as string };
|
||||
}
|
||||
}
|
||||
|
||||
[TypeConverter(typeof(TileSourceConverter))]
|
||||
public partial class TileSource
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ using System.Runtime.InteropServices;
|
|||
[assembly: AssemblyCopyright("© 2018 Clemens Fischer")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
[assembly: AssemblyVersion("4.9.0")]
|
||||
[assembly: AssemblyFileVersion("4.9.0")]
|
||||
[assembly: AssemblyVersion("4.10.0")]
|
||||
[assembly: AssemblyFileVersion("4.10.0")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: ComVisible(false)]
|
||||
|
|
|
|||
|
|
@ -156,7 +156,7 @@
|
|||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform">
|
||||
<Version>6.1.5</Version>
|
||||
<Version>6.1.7</Version>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Condition=" '$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' < '14.0' ">
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0"?>
|
||||
<configuration>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2"/>
|
||||
</startup>
|
||||
</configuration>
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@ namespace WpfApplication
|
|||
advanceWidths[i] = glyphTypeface.AdvanceWidths[glyphIndex] * FontSize;
|
||||
}
|
||||
|
||||
glyphRun = new GlyphRun(glyphTypeface, 0, false, FontSize, glyphIndices, new Point(), advanceWidths, null, null, null, null, null, null);
|
||||
glyphRun = new GlyphRun(glyphTypeface, 0, false, FontSize, 1f, glyphIndices, new Point(), advanceWidths, null, null, null, null, null, null);
|
||||
|
||||
outline = glyphRun.BuildGeometry().GetWidenedPathGeometry(new Pen(null, OutlineThickness * 2d));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ using System.Runtime.InteropServices;
|
|||
[assembly: AssemblyCompany("Clemens Fischer")]
|
||||
[assembly: AssemblyCopyright("© 2018 Clemens Fischer")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyVersion("4.9.0")]
|
||||
[assembly: AssemblyFileVersion("4.9.0")]
|
||||
[assembly: AssemblyVersion("4.10.0")]
|
||||
[assembly: AssemblyFileVersion("4.10.0")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
[assembly: ComVisible(false)]
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>WpfApplication</RootNamespace>
|
||||
<AssemblyName>WpfApplication</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
|
|
|
|||
Loading…
Reference in a new issue