ClemensFischer / XAML-Map-Control

XAML Map Control Library
Microsoft Public License
205 stars 61 forks source link

Ellipses do not scale with zoom in WPF sample #66

Closed ghost closed 4 years ago

ghost commented 4 years ago

Hi and thank you for this repo,

When zooming, ellipses defined in a MapItemsControl do not appear to scale with the zoom level. As you zoom out they get bigger instead of smaller. This is the opposite behavior of a statically defined ellipse, which correctly scales (gets smaller) as you zoom out:

For instance, this works as described above (from your WPF sample app):

<map:MapPath Location="53.5,8.2" Stroke="Blue" StrokeThickness="3" Fill="#1F007F00">
                <map:MapPath.Data>
                    <EllipseGeometry RadiusX="1852" RadiusY="1852"/>
                </map:MapPath.Data>
            </map:MapPath>

But this doesn't work; Extending your WPF Sample, I display an ellipse for each point location you've defined. They display perfectly fine, but as you zoom out they get bigger (whereas static ellipse stays a fixed size)

<Style x:Key="EllipseItemStyle" TargetType="map:MapItem">
            <Setter Property="map:MapPanel.Location" Value="{Binding Location}"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="map:MapItem">
                        <Canvas>
                            <map:MapPath Fill="Red" Opacity="0.7">
                                <map:MapPath.Data>
                                    <EllipseGeometry RadiusX="100" RadiusY="100"/>
                                </map:MapPath.Data>
                            </map:MapPath>
                        </Canvas>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

And then beside the other mapcontrols:

 <map:MapItemsControl ItemsSource="{Binding Points}"
                                 ItemContainerStyle="{StaticResource EllipseItemStyle}"
                                 SelectionMode="Extended"/>
ClemensFischer commented 4 years ago

That is perhaps a misconception. Such an Ellipse either scales with the Map's ViewScale, or it has a fixed size in view coordinates, i.e. pixels.

ghost commented 4 years ago

thanks for the quick response. If you run the example WPF app with my changes, you should notice an inconsistency when zoomed out the ellipses created in the map:MapItemsControl do not scale correctly (notice Red ellipses are much bigger than blue even though they were defined with much smaller Radius properties): sizeDiff

ClemensFischer commented 4 years ago

The MapPath in your ControlTemplate doesn't have a Location property set. The Location is set on the templated parent element, i.e. the MapItem.

It doesn't work that way - without a Location, a MapPath can't scale the Geometry in its Data property.

ClemensFischer commented 4 years ago

You should be able to do something like

<Canvas>
    <Canvas.Resources>
        <EllipseGeometry RadiusX="100" RadiusY="100" x:Shared="False" x:Key="Data"/>
    </Canvas.Resources>
    <map:MapPath Fill="Red" Opacity="0.7" Data="{StaticResource Data}" Location="{Binding Location}"/>
</Canvas>
ghost commented 4 years ago

That works, and drawing a theoretical ellipse is much more efficient than my work around of drawing a MapPolygon of hundreds of NTS points, so thank you for clarifying this.

For anyone else stuck on how to implement drawing enumerable ellipses that scale with zoom level, here's my fully working Style to use in the WPF Sample app:

<Style x:Key="EllipseItemStyle" TargetType="map:MapItem">
            <Setter Property="map:MapPanel.Location" Value="{Binding Location}"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="map:MapItem">
                        <Canvas>
                            <Canvas.Resources>
                                <EllipseGeometry
                                    RadiusX="100"
                                    RadiusY="100"
                                    x:Shared="False"
                                    x:Key="Data" />
                            </Canvas.Resources>
                            <map:MapPath 
                                Fill="Red" 
                                Opacity="0.7" 
                                Data="{StaticResource Data}"
                                Location="{Binding Location}" />
                        </Canvas>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

(Notice location must be bound twice, as without the Setter Property="map:MapPanel.Location" nothing would display)

YiehraiS commented 2 years ago

Hello, @ClemensFischer! Thank you for your great work!

I have the similar issue but difference is items disappear from map after zoom changed. I need to draw point on some location and add filled circle around it (I call it heat map). There are ~2000 points and I need to draw heat map circles to find clusters fast. The problems are:

What I need:

Thanks in advance!

ClemensFischer commented 2 years ago

You have not used an EllipseGeometry as shared resource. Take another look.

YiehraiS commented 2 years ago

@ClemensFischer, If I use x:Shared="False" my circles disappear after zoom changed. And I catch the error if I don't.

ClemensFischer commented 2 years ago

@YiehraiS You may perhaps do something like shown below. It uses the read-only ViewScale property of the MapBase class, which I have just added - check out the latest sources.

Be aware that this is a scaling factor from projected map coordinates - typically meters in a projected cartesian coordinate system - to view coordinates, i.e. device-independent pixels.

<map:MapItemsControl ItemsSource="{Binding HeatMap}">
    <map:MapItemsControl.ItemContainerStyle>
        <Style TargetType="map:MapItem">
            <Setter Property="Location" Value="{Binding Location}"/>
        </Style>
    </map:MapItemsControl.ItemContainerStyle>
    <map:MapItemsControl.ItemTemplate>
        <DataTemplate>
            <Path Fill="Green" Opacity="0.2">
                <Path.Data>
                    <EllipseGeometry RadiusX="100" RadiusY="100"/>
                </Path.Data>
                <Path.RenderTransform>
                    <ScaleTransform
                        ScaleX="{Binding ViewScale, ElementName=map}"
                        ScaleY="{Binding ViewScale, ElementName=map}"/>
                </Path.RenderTransform>
            </Path>
        </DataTemplate>
    </map:MapItemsControl.ItemTemplate>
</map:MapItemsControl>
ClemensFischer commented 2 years ago

@YiehraiS For the original approach, I would suggest to change the Location Binding of the MapPath to this:

<map:MapPath Fill="Green" Opacity="0.2"
             Data="{StaticResource Data}"
             Location="{TemplateBinding Location}"/>

You can also use a Setter for a regular dependency property (instead of an attached property) in the ItemContainerStyle:

<Setter Property="Location" Value="{Binding Location}"/>

You may also drop the Canvas and move the EllipseGeometry to Style.Resources:

<Style TargetType="map:MapItem">
    <Style.Resources>
        <EllipseGeometry RadiusX="100" RadiusY="100" x:Shared="False" x:Key="Data" />
    </Style.Resources>
    <Setter Property="Location" Value="{Binding Location}"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="map:MapItem">
                <map:MapPath
                    Fill="Green" Opacity="0.2"
                    Data="{StaticResource Data}"
                    Location="{TemplateBinding Location}"/>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>
ClemensFischer commented 2 years ago

@YiehraiS With another recent source update, you can now also directly set the MapPath.Data property

<Style TargetType="map:MapItem">
    <Setter Property="Location" Value="{Binding Location}"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="map:MapItem">
                <map:MapPath
                    Fill="Green" Opacity="0.2"
                    Location="{TemplateBinding Location}">
                    <map:MapPath.Data>
                        <EllipseGeometry RadiusX="100" RadiusY="100"/>
                    </map:MapPath.Data>
                </map:MapPath>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>
YiehraiS commented 2 years ago

https://github.com/ClemensFischer/XAML-Map-Control/issues/66#issuecomment-1301085272

@ClemensFischer In this case circles are not disappeared after zoom changed, but size of each is const despite zoom changing. And by the way it takes a lot of time to load when I use ScaleTransform but it doesn't when I don't (just removed it). image image

Thank you!

YiehraiS commented 2 years ago

https://github.com/ClemensFischer/XAML-Map-Control/issues/66#issuecomment-1301175669

This is not working. Circles get disappeared with zoom changes.

YiehraiS commented 2 years ago

This option throws the exception image

I will be based on this option when searching for the solution of scaling: https://github.com/ClemensFischer/XAML-Map-Control/issues/66#issuecomment-1301085272

Thanks for your help, @ClemensFischer! One more thing I need is to show scale bar (how much meters per 1 visible centimeter) for user. How could it be implemented?

ClemensFischer commented 2 years ago

@YiehraiS Forgot a commit or push last night. Will do later. For the scale bar, see the MapScale class.

ClemensFischer commented 2 years ago

@YiehraiS I've just pushed the recent code changes.

ClemensFischer commented 2 years ago

There is now a read-only MapTransform property in the MapItem class, which scales and rotates from map to view coordinates.

It may be used like this:

<ControlTemplate TargetType="map:MapItem">
    <Path Fill="Green" Opacity="0.2"
          RenderTransform="{Binding MapTransform, RelativeSource={RelativeSource TemplatedParent}}">
        <Path.Data>
            <EllipseGeometry RadiusX="100" RadiusY="100"/>
        </Path.Data>
    </Path>
</ControlTemplate>