Avalonia Pushpin

This commit is contained in:
ClemensFischer 2024-05-25 15:55:31 +02:00
parent 1788da27bd
commit 8e4110b600
10 changed files with 287 additions and 133 deletions

View file

@ -3,7 +3,6 @@
// Licensed under the Microsoft Public License (Ms-PL)
using Avalonia.Controls;
using System;
namespace MapControl
{
@ -18,8 +17,6 @@ namespace MapControl
public static readonly StyledProperty<Location> LocationProperty =
DependencyPropertyHelper.AddOwner<MapContentControl, Location>(MapPanel.LocationProperty);
protected override Type StyleKeyOverride => typeof(MapContentControl);
/// <summary>
/// Gets/sets MapPanel.AutoCollapse.
/// </summary>
@ -44,6 +41,5 @@ namespace MapControl
/// </summary>
public class Pushpin : MapContentControl
{
protected override Type StyleKeyOverride => typeof(Pushpin);
}
}

View file

@ -31,7 +31,6 @@
<Compile Remove="..\Shared\MapPath.cs" />
<Compile Remove="..\Shared\MapPolyline.cs" />
<Compile Remove="..\Shared\MapPolygon.cs" />
<Compile Remove="..\Shared\PushpinBorder.cs" />
<Compile Remove="..\Shared\ViewTransform.cs" />
</ItemGroup>

View file

@ -0,0 +1,107 @@
// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control
// Copyright © 2024 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
using Avalonia.Controls;
using Avalonia.Media;
using System;
namespace MapControl
{
public partial class PushpinBorder : Decorator
{
public static readonly StyledProperty<CornerRadius> CornerRadiusProperty =
DependencyPropertyHelper.Register<PushpinBorder, CornerRadius>(nameof(CornerRadius), new CornerRadius());
public static readonly StyledProperty<Size> ArrowSizeProperty =
DependencyPropertyHelper.Register<PushpinBorder, Size>(nameof(ArrowSize), new Size(10d, 20d));
public static readonly StyledProperty<double> BorderWidthProperty =
DependencyPropertyHelper.Register<PushpinBorder, double>(nameof(BorderWidth));
public static readonly StyledProperty<IBrush> BackgroundProperty =
DependencyPropertyHelper.Register<PushpinBorder, IBrush>(nameof(Background));
public static readonly StyledProperty<IBrush> BorderBrushProperty =
DependencyPropertyHelper.Register<PushpinBorder, IBrush>(nameof(BorderBrush));
static PushpinBorder()
{
AffectsMeasure<PushpinBorder>(ArrowSizeProperty, BorderWidthProperty, CornerRadiusProperty);
AffectsRender<PushpinBorder>(ArrowSizeProperty, BorderWidthProperty, CornerRadiusProperty, BackgroundProperty, BorderBrushProperty);
}
private Size RenderSize => Bounds.Size;
public CornerRadius CornerRadius
{
get => GetValue(CornerRadiusProperty);
set => SetValue(CornerRadiusProperty, value);
}
public IBrush Background
{
get => GetValue(BackgroundProperty);
set => SetValue(BackgroundProperty, value);
}
public IBrush BorderBrush
{
get => GetValue(BorderBrushProperty);
set => SetValue(BorderBrushProperty, 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)
{
if (Child != null)
{
Child.Arrange(new Rect(
BorderWidth + Padding.Left,
BorderWidth + Padding.Top,
size.Width - BorderWidth - Padding.Right,
size.Height - BorderWidth - Padding.Bottom));
}
return size;
}
public override void Render(DrawingContext drawingContext)
{
var pen = new Pen
{
Brush = BorderBrush,
Thickness = BorderWidth,
LineJoin = PenLineJoin.Round
};
drawingContext.DrawGeometry(Background, pen, BuildGeometry());
base.Render(drawingContext);
}
}
}

View file

@ -0,0 +1,49 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:map="clr-namespace:MapControl">
<Style Selector="map|MapContentControl">
<Setter Property="Background" Value="{Binding Background, RelativeSource={RelativeSource AncestorType=map:MapBase}}"/>
<Setter Property="BorderBrush" Value="{Binding Foreground, RelativeSource={RelativeSource AncestorType=map:MapBase}}"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
<Setter Property="Template">
<ControlTemplate>
<ContentPresenter Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Margin="{TemplateBinding Padding}"/>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="map|Pushpin">
<Setter Property="Background" Value="{Binding Background, RelativeSource={RelativeSource AncestorType=map:MapBase}}"/>
<Setter Property="BorderBrush" Value="{Binding Foreground, RelativeSource={RelativeSource AncestorType=map:MapBase}}"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="VerticalAlignment" Value="Bottom"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="CornerRadius" Value="5"/>
<Setter Property="Padding" Value="7,5"/>
<Setter Property="Template">
<ControlTemplate>
<map:PushpinBorder
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderWidth="{Binding BorderThickness.Left, RelativeSource={RelativeSource TemplatedParent}}"
CornerRadius="{TemplateBinding CornerRadius}"
Padding="{TemplateBinding Padding}">
<ContentPresenter
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</map:PushpinBorder>
</ControlTemplate>
</Setter>
</Style>
</Styles>

View file

@ -59,15 +59,14 @@ namespace MapControl
private static readonly DependencyProperty ParentMapProperty =
DependencyPropertyHelper.RegisterAttached<MapPanel, MapBase>("ParentMap", null,
(element, oldValue, newValue) => SetParentMap(element, newValue), true);
private static void SetParentMap(FrameworkElement element, MapBase parentMap)
{
if (element is IMapElement mapElement)
{
mapElement.ParentMap = parentMap;
}
}
(element, oldValue, newValue) =>
{
if (element is IMapElement mapElement)
{
mapElement.ParentMap = newValue;
}
},
true); // inherits
private MapBase parentMap;

View file

@ -14,6 +14,9 @@ using Windows.UI.Xaml.Media;
using Windows.Foundation;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Media;
#elif AVALONIA
using Avalonia.Media;
using HorizontalAlignment = Avalonia.Layout.HorizontalAlignment;
#endif
namespace MapControl

View file

@ -57,16 +57,16 @@ namespace MapControl
if (parentMap != null)
{
// Workaround for missing RelativeSource AncestorType=MapBase Bindings in default Style.
//
if (Background == null)
{
SetBinding(BackgroundProperty, parentMap.CreateBinding(nameof(Background)));
}
if (Foreground == null)
{
SetBinding(ForegroundProperty, parentMap.CreateBinding(nameof(Foreground)));
}
if (BorderBrush == null)
{
SetBinding(BorderBrushProperty, parentMap.CreateBinding(nameof(Foreground)));

View file

@ -23,13 +23,11 @@ namespace MapControl
public static readonly DependencyProperty LocationProperty =
DependencyPropertyHelper.Register<MapItem, Location>(nameof(Location), null,
(item, oldValue, newValue) => item.LocationPropertyChanged(newValue));
private void LocationPropertyChanged(Location location)
{
MapPanel.SetLocation(this, location);
UpdateMapTransform(location);
}
(item, oldValue, newValue) =>
{
MapPanel.SetLocation(item, newValue);
item.UpdateMapTransform(newValue);
});
public MapItem()
{
@ -51,16 +49,16 @@ namespace MapControl
if (parentMap != null)
{
// Workaround for missing RelativeSource AncestorType=MapBase Bindings in default Style.
//
if (Background == null)
{
SetBinding(BackgroundProperty, parentMap.CreateBinding(nameof(Background)));
}
if (Foreground == null)
{
SetBinding(ForegroundProperty, parentMap.CreateBinding(nameof(Foreground)));
}
if (BorderBrush == null)
{
SetBinding(BorderBrushProperty, parentMap.CreateBinding(nameof(Foreground)));

View file

@ -4,5 +4,6 @@
RequestedThemeVariant="Default">
<Application.Styles>
<FluentTheme />
<StyleInclude Source="avares://MapControl.Avalonia/Themes/Generic.axaml"/>
</Application.Styles>
</Application>

View file

@ -12,114 +12,116 @@
Center="53.5,8.2"
DoubleTapped="OnMapDoubleTapped">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Bottom" Background="#7FFFFFFF"
DataContext="{Binding MapLayer, ElementName=map}">
<ProgressBar Width="100" Height="8" Margin="4,2" VerticalAlignment="Center"
Maximum="1" Value="{Binding LoadingProgress}">
<ProgressBar.Styles>
<Style Selector="ProgressBar[Value=0]">
<Setter Property="IsIndeterminate" Value="True"/>
</Style>
<Style Selector="ProgressBar[Value=1]">
<Setter Property="IsVisible" Value="False"/>
</Style>
</ProgressBar.Styles>
</ProgressBar>
<LayoutTransformControl Margin="4,2">
<LayoutTransformControl.LayoutTransform>
<ScaleTransform ScaleX="0.7" ScaleY="0.7"/>
</LayoutTransformControl.LayoutTransform>
<md:MarkdownScrollViewer Markdown="{Binding Description}"/>
</LayoutTransformControl>
</StackPanel>
<StackPanel HorizontalAlignment="Left" VerticalAlignment="Top" Margin="6" Background="#7FFFFFFF">
<tools:MapLayersMenuButton x:Name="mapLayersMenuButton"
Margin="2" ToolTip.Tip="Map Layers and Overlays" Map="{Binding ElementName=map}">
<tools:MapLayerItem Text="OpenStreetMap">
<map:MapTileLayer
TileSource="https://tile.openstreetmap.org/{z}/{x}/{y}.png"
SourceName="OpenStreetMap"
Description="© [OpenStreetMap contributors](http://www.openstreetmap.org/copyright)"/>
</tools:MapLayerItem>
<tools:MapLayerItem Text="OpenStreetMap German">
<map:MapTileLayer
TileSource="https://{s}.tile.openstreetmap.de/{z}/{x}/{y}.png"
SourceName="OpenStreetMap German"
Description="© [OpenStreetMap contributors](http://www.openstreetmap.org/copyright)"/>
</tools:MapLayerItem>
<tools:MapLayerItem Text="OpenStreetMap French">
<map:MapTileLayer
TileSource="http://{s}.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png"
SourceName="OpenStreetMap French"
Description="© [OpenStreetMap France](https://www.openstreetmap.fr/mentions-legales/) © [OpenStreetMap contributors](http://www.openstreetmap.org/copyright)"/>
</tools:MapLayerItem>
<tools:MapLayerItem Text="OpenTopoMap">
<map:MapTileLayer
TileSource="https://tile.opentopomap.org/{z}/{x}/{y}.png"
SourceName="OpenTopoMap"
Description="© [OpenTopoMap](https://opentopomap.org/) © [OpenStreetMap contributors](http://www.openstreetmap.org/copyright)"/>
</tools:MapLayerItem>
<tools:MapLayerItem Text="TopPlusOpen WMTS">
<map:WmtsTileLayer
CapabilitiesUri="https://sgx.geodatenzentrum.de/wmts_topplus_open/1.0.0/WMTSCapabilities.xml"
SourceName="TopPlusOpen"
Description="© [BKG](https://gdz.bkg.bund.de/index.php/default/webdienste/topplus-produkte/wmts-topplusopen-wmts-topplus-open.html)"/>
</tools:MapLayerItem>
<tools:MapLayerItem Text="TopPlusOpen WMS">
<map:WmsImageLayer
ServiceUri="https://sgx.geodatenzentrum.de/wms_topplus_open"
Description="© [BKG](https://gdz.bkg.bund.de/index.php/default/webdienste/topplus-produkte/wms-topplusopen-mit-layer-fur-normalausgabe-und-druck-wms-topplus-open.html)"/>
</tools:MapLayerItem>
<tools:MapLayerItem Text="TopPlusOpen WMS Tiles">
<map:MapTileLayer
SourceName="TopPlusOpen"
Description="© [BKG](https://gdz.bkg.bund.de/index.php/default/webdienste/topplus-produkte/wms-topplusopen-mit-layer-fur-normalausgabe-und-druck-wms-topplus-open.html)">
<map:MapTileLayer.TileSource>
<map:BoundingBoxTileSource UriTemplate="https://sgx.geodatenzentrum.de/wms_topplus_open?SERVICE=WMS&amp;VERSION=1.3.0&amp;REQUEST=GetMap&amp;CRS=EPSG:3857&amp;LAYERS=web&amp;STYLES=&amp;FORMAT=image/png&amp;WIDTH=256&amp;HEIGHT=256&amp;BBOX={west},{south},{east},{north}"/>
</map:MapTileLayer.TileSource>
</map:MapTileLayer>
</tools:MapLayerItem>
<tools:MapLayerItem Text="OpenStreetMap WMS">
<map:WmsImageLayer
ServiceUri="http://ows.terrestris.de/osm/service"
Description="© [terrestris GmbH &amp; Co. KG](http://ows.terrestris.de/) © [OpenStreetMap contributors](http://www.openstreetmap.org/copyright)"/>
</tools:MapLayerItem>
<tools:MapLayersMenuButton.MapOverlays>
<tools:MapLayerItem Text="Scale">
<map:MapScale HorizontalAlignment="Center" VerticalAlignment="Bottom"/>
</tools:MapLayerItem>
<tools:MapLayerItem Text="Seamarks">
<map:MapTileLayer
TileSource="http://tiles.openseamap.org/seamark/{z}/{x}/{y}.png"
SourceName="Seamarks"
MinZoomLevel="9" MaxZoomLevel="18"/>
</tools:MapLayerItem>
<tools:MapLayerItem Text="Sample Image">
<Image Source="avares://AvaloniaApp/10_535_330.jpg" Stretch="Fill"
map:MapPanel.BoundingBox="53.54031,8.08594,53.74871,8.43750"/>
</tools:MapLayerItem>
<tools:MapLayerItem Text="Mount Etna KML">
<map:GroundOverlay SourcePath="etna.kml"/>
</tools:MapLayerItem>
</tools:MapLayersMenuButton.MapOverlays>
</tools:MapLayersMenuButton>
<tools:MapProjectionsMenuButton x:Name="mapProjectionsMenuButton"
Margin="2" ToolTip.Tip="Map Projections" Map="{Binding ElementName=map}">
<tools:MapProjectionItem Text="Web Mercator" Projection="EPSG:3857"/>
<tools:MapProjectionItem Text="Equirectangular" Projection="EPSG:4326"/>
<tools:MapProjectionItem Text="ETRS89 / UTM zone 32N" Projection="EPSG:25832"/>
<tools:MapProjectionItem Text="WGS84 / Auto UTM" Projection="AUTO2:42001"/>
</tools:MapProjectionsMenuButton>
<Slider Orientation="Vertical" Margin="-4,8" Height="100"
Minimum="{Binding MinZoomLevel, ElementName=map}"
Maximum="{Binding MaxZoomLevel, ElementName=map}"
Value="{Binding TargetZoomLevel, ElementName=map}"
SmallChange="0.1"/>
</StackPanel>
<map:Pushpin AutoCollapse="True" Location="53.5,8.2" Content="N 53°30' E 8°12'"/>
</map:Map>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Bottom" Background="#7FFFFFFF"
DataContext="{Binding MapLayer, ElementName=map}">
<ProgressBar Width="100" Height="8" Margin="4,2" VerticalAlignment="Center"
Maximum="1" Value="{Binding LoadingProgress}">
<ProgressBar.Styles>
<Style Selector="ProgressBar[Value=0]">
<Setter Property="IsIndeterminate" Value="True"/>
</Style>
<Style Selector="ProgressBar[Value=1]">
<Setter Property="IsVisible" Value="False"/>
</Style>
</ProgressBar.Styles>
</ProgressBar>
<LayoutTransformControl Margin="4,2">
<LayoutTransformControl.LayoutTransform>
<ScaleTransform ScaleX="0.7" ScaleY="0.7"/>
</LayoutTransformControl.LayoutTransform>
<md:MarkdownScrollViewer Markdown="{Binding Description}"/>
</LayoutTransformControl>
</StackPanel>
<StackPanel HorizontalAlignment="Left" VerticalAlignment="Top" Margin="6" Background="#7FFFFFFF">
<tools:MapLayersMenuButton x:Name="mapLayersMenuButton"
Margin="2" ToolTip.Tip="Map Layers and Overlays" Map="{Binding ElementName=map}">
<tools:MapLayerItem Text="OpenStreetMap">
<map:MapTileLayer
TileSource="https://tile.openstreetmap.org/{z}/{x}/{y}.png"
SourceName="OpenStreetMap"
Description="© [OpenStreetMap contributors](http://www.openstreetmap.org/copyright)"/>
</tools:MapLayerItem>
<tools:MapLayerItem Text="OpenStreetMap German">
<map:MapTileLayer
TileSource="https://{s}.tile.openstreetmap.de/{z}/{x}/{y}.png"
SourceName="OpenStreetMap German"
Description="© [OpenStreetMap contributors](http://www.openstreetmap.org/copyright)"/>
</tools:MapLayerItem>
<tools:MapLayerItem Text="OpenStreetMap French">
<map:MapTileLayer
TileSource="http://{s}.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png"
SourceName="OpenStreetMap French"
Description="© [OpenStreetMap France](https://www.openstreetmap.fr/mentions-legales/) © [OpenStreetMap contributors](http://www.openstreetmap.org/copyright)"/>
</tools:MapLayerItem>
<tools:MapLayerItem Text="OpenTopoMap">
<map:MapTileLayer
TileSource="https://tile.opentopomap.org/{z}/{x}/{y}.png"
SourceName="OpenTopoMap"
Description="© [OpenTopoMap](https://opentopomap.org/) © [OpenStreetMap contributors](http://www.openstreetmap.org/copyright)"/>
</tools:MapLayerItem>
<tools:MapLayerItem Text="TopPlusOpen WMTS">
<map:WmtsTileLayer
CapabilitiesUri="https://sgx.geodatenzentrum.de/wmts_topplus_open/1.0.0/WMTSCapabilities.xml"
SourceName="TopPlusOpen"
Description="© [BKG](https://gdz.bkg.bund.de/index.php/default/webdienste/topplus-produkte/wmts-topplusopen-wmts-topplus-open.html)"/>
</tools:MapLayerItem>
<tools:MapLayerItem Text="TopPlusOpen WMS">
<map:WmsImageLayer
ServiceUri="https://sgx.geodatenzentrum.de/wms_topplus_open"
Description="© [BKG](https://gdz.bkg.bund.de/index.php/default/webdienste/topplus-produkte/wms-topplusopen-mit-layer-fur-normalausgabe-und-druck-wms-topplus-open.html)"/>
</tools:MapLayerItem>
<tools:MapLayerItem Text="TopPlusOpen WMS Tiles">
<map:MapTileLayer
SourceName="TopPlusOpen"
Description="© [BKG](https://gdz.bkg.bund.de/index.php/default/webdienste/topplus-produkte/wms-topplusopen-mit-layer-fur-normalausgabe-und-druck-wms-topplus-open.html)">
<map:MapTileLayer.TileSource>
<map:BoundingBoxTileSource UriTemplate="https://sgx.geodatenzentrum.de/wms_topplus_open?SERVICE=WMS&amp;VERSION=1.3.0&amp;REQUEST=GetMap&amp;CRS=EPSG:3857&amp;LAYERS=web&amp;STYLES=&amp;FORMAT=image/png&amp;WIDTH=256&amp;HEIGHT=256&amp;BBOX={west},{south},{east},{north}"/>
</map:MapTileLayer.TileSource>
</map:MapTileLayer>
</tools:MapLayerItem>
<tools:MapLayerItem Text="OpenStreetMap WMS">
<map:WmsImageLayer
ServiceUri="http://ows.terrestris.de/osm/service"
Description="© [terrestris GmbH &amp; Co. KG](http://ows.terrestris.de/) © [OpenStreetMap contributors](http://www.openstreetmap.org/copyright)"/>
</tools:MapLayerItem>
<tools:MapLayersMenuButton.MapOverlays>
<tools:MapLayerItem Text="Scale">
<map:MapScale HorizontalAlignment="Center" VerticalAlignment="Bottom"/>
</tools:MapLayerItem>
<tools:MapLayerItem Text="Seamarks">
<map:MapTileLayer
TileSource="http://tiles.openseamap.org/seamark/{z}/{x}/{y}.png"
SourceName="Seamarks"
MinZoomLevel="9" MaxZoomLevel="18"/>
</tools:MapLayerItem>
<tools:MapLayerItem Text="Sample Image">
<Image Source="avares://AvaloniaApp/10_535_330.jpg" Stretch="Fill"
map:MapPanel.BoundingBox="53.54031,8.08594,53.74871,8.43750"/>
</tools:MapLayerItem>
<tools:MapLayerItem Text="Mount Etna KML">
<map:GroundOverlay SourcePath="etna.kml"/>
</tools:MapLayerItem>
</tools:MapLayersMenuButton.MapOverlays>
</tools:MapLayersMenuButton>
<tools:MapProjectionsMenuButton x:Name="mapProjectionsMenuButton"
Margin="2" ToolTip.Tip="Map Projections" Map="{Binding ElementName=map}">
<tools:MapProjectionItem Text="Web Mercator" Projection="EPSG:3857"/>
<tools:MapProjectionItem Text="Equirectangular" Projection="EPSG:4326"/>
<tools:MapProjectionItem Text="ETRS89 / UTM zone 32N" Projection="EPSG:25832"/>
<tools:MapProjectionItem Text="WGS84 / Auto UTM" Projection="AUTO2:42001"/>
</tools:MapProjectionsMenuButton>
<Slider Orientation="Vertical" Margin="-4,8" Height="100"
Minimum="{Binding MinZoomLevel, ElementName=map}"
Maximum="{Binding MaxZoomLevel, ElementName=map}"
Value="{Binding TargetZoomLevel, ElementName=map}"
SmallChange="0.1"/>
</StackPanel>
</Grid>
</Window>