MapMultiPolygon

This commit is contained in:
ClemensFischer 2024-07-16 21:29:25 +02:00
parent 5b9ad68c57
commit dbd32361b5
11 changed files with 217 additions and 61 deletions

View file

@ -52,32 +52,49 @@ namespace MapControl
protected void UpdateData(IEnumerable<Location> locations, bool closed) protected void UpdateData(IEnumerable<Location> locations, bool closed)
{ {
var pathFigures = new PathFigures(); var figures = new PathFigures();
if (ParentMap != null && locations != null) if (ParentMap != null && locations != null)
{ {
var longitudeOffset = GetLongitudeOffset(Location ?? locations.FirstOrDefault()); var longitudeOffset = GetLongitudeOffset(Location ?? locations.FirstOrDefault());
AddPolylinePoints(pathFigures, locations, longitudeOffset, closed); AddPolylinePoints(figures, locations, longitudeOffset, closed);
} }
SetPathFigures(pathFigures); SetPathFigures(figures);
} }
protected void SetPathFigures(PathFigures pathFigures) protected void UpdateData(IEnumerable<IEnumerable<Location>> polygons)
{ {
if (pathFigures.Count == 0) var figures = new PathFigures();
if (ParentMap != null && polygons != null)
{
var longitudeOffset = GetLongitudeOffset(Location);
foreach (var locations in polygons)
{
AddPolylinePoints(figures, locations, longitudeOffset, true);
}
}
SetPathFigures(figures);
}
protected void SetPathFigures(PathFigures figures)
{
if (figures.Count == 0)
{ {
// Avalonia Shape seems to ignore PathGeometry with empty Figures collection // Avalonia Shape seems to ignore PathGeometry with empty Figures collection
pathFigures.Add(new PathFigure { StartPoint = new Point(-1000, -1000) }); figures.Add(new PathFigure { StartPoint = new Point(-1000, -1000) });
} }
((PathGeometry)Data).Figures = pathFigures; ((PathGeometry)Data).Figures = figures;
InvalidateGeometry(); InvalidateGeometry();
} }
private void AddPolylinePoints(PathFigures pathFigures, IEnumerable<Location> locations, double longitudeOffset, bool closed) private void AddPolylinePoints(PathFigures figures, IEnumerable<Location> locations, double longitudeOffset, bool closed)
{ {
var points = locations var points = locations
.Select(location => LocationToView(location, longitudeOffset)) .Select(location => LocationToView(location, longitudeOffset))
@ -86,14 +103,14 @@ namespace MapControl
if (points.Any()) if (points.Any())
{ {
var startPoint = points.First(); var start = points.First();
var polylineSegment = new PolyLineSegment(points.Skip(1)); var polyline = new PolyLineSegment(points.Skip(1));
var minX = startPoint.X; var minX = start.X;
var maxX = startPoint.X; var maxX = start.X;
var minY = startPoint.Y; var minY = start.Y;
var maxY = startPoint.Y; var maxY = start.Y;
foreach (var point in polylineSegment.Points) foreach (var point in polyline.Points)
{ {
minX = Math.Min(minX, point.X); minX = Math.Min(minX, point.X);
maxX = Math.Max(maxX, point.X); maxX = Math.Max(maxX, point.X);
@ -104,15 +121,15 @@ namespace MapControl
if (maxX >= 0 && minX <= ParentMap.ActualWidth && if (maxX >= 0 && minX <= ParentMap.ActualWidth &&
maxY >= 0 && minY <= ParentMap.ActualHeight) maxY >= 0 && minY <= ParentMap.ActualHeight)
{ {
var pathFigure = new PathFigure var figure = new PathFigure
{ {
StartPoint = startPoint, StartPoint = start,
IsClosed = closed, IsClosed = closed,
IsFilled = true IsFilled = true
}; };
pathFigure.Segments.Add(polylineSegment); figure.Segments.Add(polyline);
pathFigures.Add(pathFigure); figures.Add(figure);
} }
} }
} }

View file

@ -3,7 +3,13 @@
// Licensed under the Microsoft Public License (Ms-PL) // Licensed under the Microsoft Public License (Ms-PL)
using System.Collections.Generic; using System.Collections.Generic;
#if WPF
using System.Windows; using System.Windows;
#elif UWP
using Windows.UI.Xaml;
#elif WINUI
using Microsoft.UI.Xaml;
#endif
namespace MapControl namespace MapControl
{ {

View file

@ -0,0 +1,70 @@
// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control
// Copyright © 2024 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
namespace MapControl
{
/// <summary>
/// An ObservableCollection of IEnumerable of Location. PolygonCollection adds a CollectionChanged
/// listener to each element that implements INotifyCollectionChanged and, when such an element changes,
/// fires its own CollectionChanged event with NotifyCollectionChangedAction.Replace for that element.
/// </summary>
public class PolygonCollection : ObservableCollection<IEnumerable<Location>>
{
private void PolygonChanged(object sender, NotifyCollectionChangedEventArgs e)
{
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, sender, sender));
}
protected override void InsertItem(int index, IEnumerable<Location> polygon)
{
if (polygon is INotifyCollectionChanged addedPolygon)
{
addedPolygon.CollectionChanged += PolygonChanged;
}
base.InsertItem(index, polygon);
}
protected override void SetItem(int index, IEnumerable<Location> polygon)
{
if (this[index] is INotifyCollectionChanged removedPolygon)
{
removedPolygon.CollectionChanged -= PolygonChanged;
}
if (polygon is INotifyCollectionChanged addedPolygon)
{
addedPolygon.CollectionChanged += PolygonChanged;
}
base.SetItem(index, polygon);
}
protected override void RemoveItem(int index)
{
if (this[index] is INotifyCollectionChanged removedPolygon)
{
removedPolygon.CollectionChanged -= PolygonChanged;
}
base.RemoveItem(index);
}
protected override void ClearItems()
{
foreach (var polygon in this.OfType<INotifyCollectionChanged>())
{
polygon.CollectionChanged -= PolygonChanged;
}
base.ClearItems();
}
}
}

View file

@ -113,6 +113,9 @@
<Compile Include="..\Shared\MapItemsControl.cs"> <Compile Include="..\Shared\MapItemsControl.cs">
<Link>MapItemsControl.cs</Link> <Link>MapItemsControl.cs</Link>
</Compile> </Compile>
<Compile Include="..\Shared\MapMultiPolygon.cs">
<Link>MapMultiPolygon.cs</Link>
</Compile>
<Compile Include="..\Shared\MapPanel.cs"> <Compile Include="..\Shared\MapPanel.cs">
<Link>MapPanel.cs</Link> <Link>MapPanel.cs</Link>
</Compile> </Compile>
@ -149,6 +152,9 @@
<Compile Include="..\Shared\PolarStereographicProjection.cs"> <Compile Include="..\Shared\PolarStereographicProjection.cs">
<Link>PolarStereographicProjection.cs</Link> <Link>PolarStereographicProjection.cs</Link>
</Compile> </Compile>
<Compile Include="..\Shared\PolygonCollection.cs">
<Link>PolygonCollection.cs</Link>
</Compile>
<Compile Include="..\Shared\PushpinBorder.cs"> <Compile Include="..\Shared\PushpinBorder.cs">
<Link>PushpinBorder.cs</Link> <Link>PushpinBorder.cs</Link>
</Compile> </Compile>

View file

@ -20,6 +20,10 @@
<Reference Include="System.Net.Http" /> <Reference Include="System.Net.Http" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Compile Remove="..\Shared\PolygonCollection.cs" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" /> <PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="8.0.0" /> <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="8.0.0" />

View file

@ -77,9 +77,9 @@ namespace MapControl
{ {
var longitudeOffset = GetLongitudeOffset(Location); var longitudeOffset = GetLongitudeOffset(Location);
foreach (var polygon in polygons) foreach (var locations in polygons)
{ {
AddPolylinePoints(context, polygon, longitudeOffset, true); AddPolylinePoints(context, locations, longitudeOffset, true);
} }
} }
} }
@ -94,8 +94,27 @@ namespace MapControl
if (points.Any()) if (points.Any())
{ {
context.BeginFigure(points.First(), true, closed); var start = points.First();
context.PolyLineTo(points.Skip(1).ToList(), true, true); 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 >= 0 && minX <= ParentMap.ActualWidth &&
maxY >= 0 && minY <= ParentMap.ActualHeight)
{
context.BeginFigure(start, true, closed);
context.PolyLineTo(polyline, true, true);
}
} }
} }
} }

View file

@ -27,9 +27,9 @@ namespace MapControl
protected override void InsertItem(int index, IEnumerable<Location> polygon) protected override void InsertItem(int index, IEnumerable<Location> polygon)
{ {
if (polygon is INotifyCollectionChanged observablePolygon) if (polygon is INotifyCollectionChanged addedPolygon)
{ {
CollectionChangedEventManager.AddListener(observablePolygon, this); CollectionChangedEventManager.AddListener(addedPolygon, this);
} }
base.InsertItem(index, polygon); base.InsertItem(index, polygon);
@ -37,9 +37,14 @@ namespace MapControl
protected override void SetItem(int index, IEnumerable<Location> polygon) protected override void SetItem(int index, IEnumerable<Location> polygon)
{ {
if (this[index] is INotifyCollectionChanged observablePolygon) if (this[index] is INotifyCollectionChanged removedPolygon)
{ {
CollectionChangedEventManager.RemoveListener(observablePolygon, this); CollectionChangedEventManager.RemoveListener(removedPolygon, this);
}
if (polygon is INotifyCollectionChanged addedPolygon)
{
CollectionChangedEventManager.AddListener(addedPolygon, this);
} }
base.SetItem(index, polygon); base.SetItem(index, polygon);
@ -47,9 +52,9 @@ namespace MapControl
protected override void RemoveItem(int index) protected override void RemoveItem(int index)
{ {
if (this[index] is INotifyCollectionChanged observablePolygon) if (this[index] is INotifyCollectionChanged removedPolygon)
{ {
CollectionChangedEventManager.RemoveListener(observablePolygon, this); CollectionChangedEventManager.RemoveListener(removedPolygon, this);
} }
base.RemoveItem(index); base.RemoveItem(index);
@ -57,9 +62,9 @@ namespace MapControl
protected override void ClearItems() protected override void ClearItems()
{ {
foreach (var observablePolygon in this.OfType<INotifyCollectionChanged>()) foreach (var polygon in this.OfType<INotifyCollectionChanged>())
{ {
CollectionChangedEventManager.RemoveListener(observablePolygon, this); CollectionChangedEventManager.RemoveListener(polygon, this);
} }
base.ClearItems(); base.ClearItems();

View file

@ -59,18 +59,34 @@ namespace MapControl
protected void UpdateData(IEnumerable<Location> locations, bool closed) protected void UpdateData(IEnumerable<Location> locations, bool closed)
{ {
var pathFigures = ((PathGeometry)Data).Figures; var figures = ((PathGeometry)Data).Figures;
pathFigures.Clear(); figures.Clear();
if (ParentMap != null && locations != null) if (ParentMap != null && locations != null)
{ {
var longitudeOffset = GetLongitudeOffset(Location ?? locations.FirstOrDefault()); var longitudeOffset = GetLongitudeOffset(Location ?? locations.FirstOrDefault());
AddPolylinePoints(pathFigures, locations, longitudeOffset, closed); AddPolylinePoints(figures, locations, longitudeOffset, closed);
} }
} }
private void AddPolylinePoints(PathFigureCollection pathFigures, IEnumerable<Location> locations, double longitudeOffset, bool closed) protected void UpdateData(IEnumerable<IEnumerable<Location>> polygons)
{
var figures = ((PathGeometry)Data).Figures;
figures.Clear();
if (ParentMap != null && polygons != null)
{
var longitudeOffset = GetLongitudeOffset(Location);
foreach (var locations in polygons)
{
AddPolylinePoints(figures, locations, longitudeOffset, true);
}
}
}
private void AddPolylinePoints(PathFigureCollection figures, IEnumerable<Location> locations, double longitudeOffset, bool closed)
{ {
var points = locations var points = locations
.Select(location => LocationToView(location, longitudeOffset)) .Select(location => LocationToView(location, longitudeOffset))
@ -79,16 +95,16 @@ namespace MapControl
if (points.Any()) if (points.Any())
{ {
var startPoint = points.First(); var start = points.First();
var polylineSegment = new PolyLineSegment(); var polyline = new PolyLineSegment();
var minX = startPoint.X; var minX = start.X;
var maxX = startPoint.X; var maxX = start.X;
var minY = startPoint.Y; var minY = start.Y;
var maxY = startPoint.Y; var maxY = start.Y;
foreach (var point in points.Skip(1)) foreach (var point in points.Skip(1))
{ {
polylineSegment.Points.Add(point); polyline.Points.Add(point);
minX = Math.Min(minX, point.X); minX = Math.Min(minX, point.X);
maxX = Math.Max(maxX, point.X); maxX = Math.Max(maxX, point.X);
minY = Math.Min(minY, point.Y); minY = Math.Min(minY, point.Y);
@ -98,15 +114,15 @@ namespace MapControl
if (maxX >= 0 && minX <= ParentMap.ActualWidth && if (maxX >= 0 && minX <= ParentMap.ActualWidth &&
maxY >= 0 && minY <= ParentMap.ActualHeight) maxY >= 0 && minY <= ParentMap.ActualHeight)
{ {
var pathFigure = new PathFigure var figure = new PathFigure
{ {
StartPoint = startPoint, StartPoint = start,
IsClosed = closed, IsClosed = closed,
IsFilled = true IsFilled = true
}; };
pathFigure.Segments.Add(polylineSegment); figure.Segments.Add(polyline);
pathFigures.Add(pathFigure); figures.Add(figure);
} }
} }
} }

View file

@ -85,17 +85,20 @@
</map:MapItemsControl.Styles> </map:MapItemsControl.Styles>
</map:MapItemsControl> </map:MapItemsControl>
<map:MapPath Location="53.5,8.2" Stroke="Blue" StrokeThickness="3" Fill="#1F007F00"> <map:MapPath Location="53.5,8.2" Stroke="Blue" StrokeThickness="3" Fill="#1F007F00" IsHitTestVisible="False">
<map:MapPath.Data> <map:MapPath.Data>
<EllipseGeometry RadiusX="1852" RadiusY="1852"/> <EllipseGeometry RadiusX="1852" RadiusY="1852"/>
</map:MapPath.Data> </map:MapPath.Data>
</map:MapPath> </map:MapPath>
<map:MapPolygon Stroke="Yellow" StrokeThickness="2"> <map:MapMultiPolygon Stroke="Yellow" StrokeThickness="2" Fill="#1FFF0000" IsHitTestVisible="False">
<map:MapPolygon.Locations> <map:MapMultiPolygon.Polygons>
<map:PolygonCollection>
<map:LocationCollection>53.45,8.1 53.45,8.3 53.55,8.3 53.55,8.1</map:LocationCollection> <map:LocationCollection>53.45,8.1 53.45,8.3 53.55,8.3 53.55,8.1</map:LocationCollection>
</map:MapPolygon.Locations> <map:LocationCollection>53.47,8.14 53.47,8.26 53.53,8.26 53.53,8.14</map:LocationCollection>
</map:MapPolygon> </map:PolygonCollection>
</map:MapMultiPolygon.Polygons>
</map:MapMultiPolygon>
<map:Pushpin AutoCollapse="True" Location="53.5,8.2" Content="N 53°30' E 8°12'"/> <map:Pushpin AutoCollapse="True" Location="53.5,8.2" Content="N 53°30' E 8°12'"/>
</map:Map> </map:Map>

View file

@ -174,17 +174,20 @@
SelectionChanged="MapItemsControlSelectionChanged" SelectionChanged="MapItemsControlSelectionChanged"
DoubleTapped="MapItemsControlDoubleTapped"/> DoubleTapped="MapItemsControlDoubleTapped"/>
<map:MapPath Location="53.5,8.2" Stroke="Blue" StrokeThickness="3" Fill="#1F007F00"> <map:MapPath Location="53.5,8.2" Stroke="Blue" StrokeThickness="3" Fill="#1F007F00" IsHitTestVisible="False">
<map:MapPath.Data> <map:MapPath.Data>
<EllipseGeometry RadiusX="1852" RadiusY="1852"/> <EllipseGeometry RadiusX="1852" RadiusY="1852"/>
</map:MapPath.Data> </map:MapPath.Data>
</map:MapPath> </map:MapPath>
<map:MapPolygon Stroke="Yellow" StrokeThickness="2"> <map:MapMultiPolygon Stroke="Yellow" StrokeThickness="2" Fill="#1FFF0000" IsHitTestVisible="False">
<map:MapPolygon.Locations> <map:MapMultiPolygon.Polygons>
<map:PolygonCollection>
<map:LocationCollection>53.45,8.1 53.45,8.3 53.55,8.3 53.55,8.1</map:LocationCollection> <map:LocationCollection>53.45,8.1 53.45,8.3 53.55,8.3 53.55,8.1</map:LocationCollection>
</map:MapPolygon.Locations> <map:LocationCollection>53.47,8.14 53.47,8.26 53.53,8.26 53.53,8.14</map:LocationCollection>
</map:MapPolygon> </map:PolygonCollection>
</map:MapMultiPolygon.Polygons>
</map:MapMultiPolygon>
<map:Pushpin Location="53.5,8.2" AutoCollapse="True" Content="N 53°30' E 8°12'"/> <map:Pushpin Location="53.5,8.2" AutoCollapse="True" Content="N 53°30' E 8°12'"/>
</map:Map> </map:Map>

View file

@ -145,14 +145,21 @@
<map:Pushpin AutoCollapse="True" Location="35,33" Content="Cyprus"/> <map:Pushpin AutoCollapse="True" Location="35,33" Content="Cyprus"/>
<map:Pushpin AutoCollapse="True" Location="28.25,-16.5" Content="Tenerife"/> <map:Pushpin AutoCollapse="True" Location="28.25,-16.5" Content="Tenerife"/>
<map:MapPolygon Stroke="Yellow" StrokeThickness="2" Locations="53.45,8.1 53.45,8.3 53.55,8.3 53.55,8.1"/> <map:MapPath Location="53.5,8.2" Stroke="Blue" StrokeThickness="3" Fill="#1F007F00" IsHitTestVisible="False">
<map:MapPath Location="53.5,8.2" Stroke="Blue" StrokeThickness="3" Fill="#1F007F00">
<map:MapPath.Data> <map:MapPath.Data>
<EllipseGeometry RadiusX="1852" RadiusY="1852"/> <EllipseGeometry RadiusX="1852" RadiusY="1852"/>
</map:MapPath.Data> </map:MapPath.Data>
</map:MapPath> </map:MapPath>
<map:MapMultiPolygon Stroke="Yellow" StrokeThickness="2" Fill="#1FFF0000" IsHitTestVisible="False">
<map:MapMultiPolygon.Polygons>
<map:PolygonCollection>
<map:LocationCollection>53.45,8.1 53.45,8.3 53.55,8.3 53.55,8.1</map:LocationCollection>
<map:LocationCollection>53.47,8.14 53.47,8.26 53.53,8.26 53.53,8.14</map:LocationCollection>
</map:PolygonCollection>
</map:MapMultiPolygon.Polygons>
</map:MapMultiPolygon>
<map:Pushpin AutoCollapse="True" Location="53.5,8.2" Content="N 53°30' E 8°12'"/> <map:Pushpin AutoCollapse="True" Location="53.5,8.2" Content="N 53°30' E 8°12'"/>
</map:Map> </map:Map>