This project has moved. For the latest updates, please go here.

Map Ruler

May 6, 2015 at 8:37 AM
Edited May 6, 2015 at 8:49 AM
Hello,

I'm trying to implement a map ruler:

Image
interface IMapRuler : INotifyPropertyChanged
{
    Location AnchorA { get; set; }
    Location AnchorB { get; set; }
    IEnumerable<Location> Locations { get; set; } //holds AnchorA and AnchorB
    double Distance { get; set; }
    double Bearing { get; set; }
}
<map:MapItemsControl ItemsSource="{Binding Rulers}"
                    ItemContainerStyle="{StaticResource MapRulerStyle}"/>
<Style x:Key="MapRulerStyle" TargetType="map:MapItem">
  <Setter Property="Template">
    <Setter.Value>
      <ControlTemplate TargetType="map:MapItem">
        <Canvas>
          <map:MapPolyline Locations="{Binding Locations}" Stroke="Blue" StrokeThickness="4"/>
          <Path Canvas.Top="14" Stroke="White" StrokeThickness="4" Fill="Red" map:MapPanel.Location="{Binding AnchorA}" PreviewMouseLeftButtonDown="MapRulerAnchorA_PreviewMouseLeftButtonDown">
            <Path.Data>
              <EllipseGeometry RadiusX="10" RadiusY="10"/>
            </Path.Data>
          </Path>
          <Path Canvas.Top="14" Stroke="White" StrokeThickness="4" Fill="Red" map:MapPanel.Location="{Binding AnchorB}" PreviewMouseLeftButtonDown="MapRulerAnchorB_PreviewMouseLeftButtonDown">
            <Path.Data>
              <EllipseGeometry RadiusX="10" RadiusY="10"/>
            </Path.Data>
          </Path>
        </Canvas>
      </ControlTemplate>
    </Setter.Value>
  </Setter>
</Style>
But am facing a few issues:
  • Anchors position doesn't update when scrolling or zooming the map.
  • How should I implement the text block?
(I've omitted some of the anchors events such as MouseMove, PreviewMouseLeftButtonUp and LostMouseCapture needed for the drag-drop functionality)

Thank you in advance!
May 6, 2015 at 8:54 PM
Clarification: the two anchors are indeed located correctly at startup, but when the map scrolls or zooms, their position doesn't update.
Coordinator
May 7, 2015 at 10:42 AM
Edited May 7, 2015 at 10:44 AM
I'd suggest to create a UserControl like this:
<UserControl x:Class="RulerTest.RulerControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:map="clr-namespace:MapControl;assembly=MapControl.WPF">
    <map:MapPanel>
        <map:MapPolyline Locations="{Binding Locations, RelativeSource={RelativeSource AncestorType=UserControl}}"
                         Stroke="{Binding Foreground, RelativeSource={RelativeSource AncestorType=UserControl}}"
                         StrokeThickness="3"/>
        <Path x:Name="start"
              map:MapPanel.Location="{Binding StartPoint, RelativeSource={RelativeSource AncestorType=UserControl}}"
              Fill="{Binding Foreground, RelativeSource={RelativeSource AncestorType=UserControl}}"
              MouseLeftButtonDown="PathMouseLeftButtonDown"
              MouseLeftButtonUp="PathMouseLeftButtonUp"
              MouseMove="PathMouseMove">
            <Path.Data>
                <EllipseGeometry RadiusX="10" RadiusY="10"/>
            </Path.Data>
        </Path>
        <Path map:MapPanel.Location="{Binding EndPoint, RelativeSource={RelativeSource AncestorType=UserControl}}"
              Fill="{Binding Foreground, RelativeSource={RelativeSource AncestorType=UserControl}}"
              MouseLeftButtonDown="PathMouseLeftButtonDown"
              MouseLeftButtonUp="PathMouseLeftButtonUp"
              MouseMove="PathMouseMove">
            <Path.Data>
                <EllipseGeometry RadiusX="10" RadiusY="10"/>
            </Path.Data>
        </Path>
    </map:MapPanel>
</UserControl>
with this code-behind:
public partial class RulerControl : UserControl
{
    public static readonly DependencyProperty StartPointProperty =
        DependencyProperty.Register(
            "StartPoint", typeof(Location), typeof(RulerControl),
            new FrameworkPropertyMetadata(
                new Location(), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
                (o, e) => ((RulerControl)o).Locations[0] = (Location)e.NewValue));

    public static readonly DependencyProperty EndPointProperty =
        DependencyProperty.Register(
            "EndPoint", typeof(Location), typeof(RulerControl),
            new FrameworkPropertyMetadata(
                new Location(), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
                (o, e) => ((RulerControl)o).Locations[1] = (Location)e.NewValue));

    public Location StartPoint
    {
        get { return (Location)GetValue(StartPointProperty); }
        set { SetValue(StartPointProperty, value); }
    }

    public Location EndPoint
    {
        get { return (Location)GetValue(EndPointProperty); }
        set { SetValue(EndPointProperty, value); }
    }

    public ObservableCollection<Location> Locations { get; private set; }

    public RulerControl()
    {
        Locations = new ObservableCollection<Location>();
        Locations.Add(StartPoint);
        Locations.Add(EndPoint);

        InitializeComponent();
    }

    private void PathMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        var element = (UIElement)sender;

        if (Mouse.Capture(element))
        {
            e.Handled = true;
        }
    }

    private void PathMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
    {
        var element = (UIElement)sender;

        if (element.IsMouseCaptured)
        {
            element.ReleaseMouseCapture();
            e.Handled = true;
        }
    }

    private void PathMouseMove(object sender, MouseEventArgs e)
    {
        var element = (UIElement)sender;

        if (element.IsMouseCaptured)
        {
            var map = MapPanel.GetParentMap(element);
            var location = map.ViewportPointToLocation(e.GetPosition(map));
            if (element == start)
            {
                StartPoint = location;
            }
            else
            {
                EndPoint = location;
            }
            e.Handled = true;
        }
    }
}
and use it in a MapItemsControl's ItemContainerStyle like this:
<Style TargetType="map:MapItem">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="map:MapItem">
                <local:RulerControl
                    StartPoint="{Binding AnchorA}"
                    EndPoint="{Binding AnchorB}" />
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>
May 7, 2015 at 3:27 PM
Edited May 7, 2015 at 3:27 PM
You ROCK man!

How about incorporating this into the project?

Plus:
<TextBlock map:MapPanel.Location="{Binding CenterPoint, RelativeSource={RelativeSource AncestorType=UserControl}}"
            Text="{Binding Text, RelativeSource={RelativeSource AncestorType=UserControl}}"
            IsHitTestVisible="False"
            Foreground="White"
            FontSize="17">
    <TextBlock.Effect>
        <DropShadowEffect Color="Black" ShadowDepth="2" BlurRadius="3"/>
    </TextBlock.Effect>
    <TextBlock.RenderTransform>
        <TransformGroup>
            <TranslateTransform Y="4" X="{Binding ActualWidth, RelativeSource={RelativeSource AncestorType=TextBlock}, Converter={StaticResource NegativeHalfConverter}}" />
            <RotateTransform Angle="{Binding TextAngle, RelativeSource={RelativeSource AncestorType=UserControl}}"/>
        </TransformGroup>
    </TextBlock.RenderTransform>
</TextBlock>