.Net Maui Google 地图 Polyline 绘制路线功能

huangapple go评论69阅读模式
英文:

.Net Maui Google Maps Polyline drawning route feature

问题

我正在构建一个 .NET Maui 智能手机运动应用程序,该应用程序抓取运动活动的纬度和经度列表(使用新的 Location 类),并在地图上绘制一条线(折线)以显示路线。我可以从数据库中获取运动活动的列表,并在地图上绘制折线,但问题是我不知道如何在我的 ViewModel 类中将地图功能进行数据绑定,因此无法同时执行这两个操作。

以下是 ExercisePage.xaml 中的 XAML 代码:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="DoradSmartphone.Views.ExercisePage"
             xmlns:model="clr-namespace:DoradSmartphone.Models"
             xmlns:viewmodel="clr-namespace:DoradSmartphone.ViewModels"
             xmlns:maps="clr-namespace:Microsoft.Maui.Controls.Maps;assembly=Microsoft.Maui.Controls.Maps"
             xmlns:sensors="clr-namespace:Microsoft.Maui.Devices.Sensors;assembly=Microsoft.Maui.Essentials"
             x:DataType="viewmodel:ExerciseViewModel"
             Title="{Binding Title}">


    <Grid Padding="5" Margin="5" RowSpacing="5" ColumnSpacing="3">
        <Grid.RowDefinitions>
            <RowDefinition Height="2*"/>
            <RowDefinition Height="150"/>
        </Grid.RowDefinitions>

        <maps:Map Grid.Row="0" x:Name="routeMap" VerticalOptions="CenterAndExpand" Grid.ColumnSpan="3" HeightRequest="400" IsZoomEnabled="False" IsEnabled="False">
            <x:Arguments>
                <MapSpan>
                    <x:Arguments>
                        <sensors:Location>
                            <x:Arguments>
                                <x:Double>38.744418137669875</x:Double>
                                <x:Double>-9.128544160596851</x:Double>
                            </x:Arguments>
                        </sensors:Location>
                        <x:Double>0.7</x:Double>
                        <x:Double>0.7</x:Double>
                    </x:Arguments>
                </MapSpan>
            </x:Arguments>
        </maps:Map>

        <CarouselView ItemsSource="{Binding Exercises}" Grid.Row="1" PeekAreaInsets="100">
            <CarouselView.ItemTemplate>
                <DataTemplate x:DataType="model:Exercise">
                    <Frame HeightRequest="90" Margin="5">
                        <Frame.GestureRecognizers>
                            <TapGestureRecognizer Command="{Binding Source={RelativeSource AncestorType={x:Type viewmodel:ExerciseViewModel}}, Path=ExerciseDetailsCommand}" CommandParameter="{Binding .}"></TapGestureRecognizer>
                        </Frame.GestureRecognizers>
                        <HorizontalStackLayout Padding="10" Spacing="5">
                            <Label Text="{Binding Id}"></Label>
                            <Label Text="{Binding Date}"></Label>
                        </HorizontalStackLayout>
                    </Frame>
                </DataTemplate>
            </CarouselView.ItemTemplate>
        </CarouselView>
    </Grid>
</ContentPage>

如您所见,我已经声明了地图的名称为 routeMap,并设置了初始位置。我还声明了我的模型和 ViewModel,以便在 CarouselView 中进行数据绑定。点击功能正常工作,并将我带到一个名为 ExerciseDetailsPage 的新视图。

这是 ExercisePage.xaml.cs 的代码:

using DoradSmartphone.Models;
using DoradSmartphone.ViewModels;
using Microsoft.Maui.Controls.Maps;
using Microsoft.Maui.Maps;

namespace DoradSmartphone.Views
{
    public partial class ExercisePage : ContentPage
    {
        public ExercisePage(ExerciseViewModel exerciseViewModel)
        {
            InitializeComponent();
            BindingContext = exerciseViewModel;
        }

        private void OnTapGestureRouteUpdate(object sender, EventArgs e)
        {
            // 这里是添加绘制折线的代码,但无法访问 routeMap 对象。
        }
    }
}

在 ExercisePage.xaml.cs 中,我无法访问 ViewModel、Services 或 Model 类。

这是 ExerciseViewModel.cs 类:

using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using DoradSmartphone.Models;
using DoradSmartphone.Services;
using Microsoft.Maui.Controls.Maps;
using Microsoft.Maui.Maps;
using System.Collections.ObjectModel;

namespace DoradSmartphone.ViewModels
{
    public partial class ExerciseViewModel : BaseViewModel
    {
        // 这里是您的 ViewModel 代码
    }
}

在 ExerciseViewModel.cs 中,您有一个 RelayCommand,但无法访问 routeMap 对象。您还尝试在 ViewModel 类中声明一个 Map 类,但遇到了无法创建静态类实例的错误。

这是 MauiProgram.cs:

using DoradSmartphone.Data;
using DoradSmartphone.Services;
using DoradSmartphone.ViewModels;
using DoradSmartphone.Views;

namespace DoradSmartphone
{
    public static class MauiProgram
    {
        // 这里是您的 MauiProgram 代码
    }
}

希望这些信息对您有所帮助。如果您需要更多帮助,请随时提出具体问题。

英文:

I have this situation:
I'm building a .net Maui smartphone sports app that grabs a list of latitude and longitude (new Location class) of a running activity and draws a line (polyline) in the map to display the route. I can grab the list of exercises from the database and I can draw a polyline in the map, the problem is that I can't do both together because I don't know how to databind the Map functionalitys in my ViewModel class.

Here is my xaml code for the ExercisePage.xaml:

&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot; ?&gt;
&lt;ContentPage xmlns=&quot;http://schemas.microsoft.com/dotnet/2021/maui&quot;
xmlns:x=&quot;http://schemas.microsoft.com/winfx/2009/xaml&quot;
x:Class=&quot;DoradSmartphone.Views.ExercisePage&quot;
xmlns:model=&quot;clr-namespace:DoradSmartphone.Models&quot;
xmlns:viewmodel=&quot;clr-namespace:DoradSmartphone.ViewModels&quot;
xmlns:maps=&quot;clr-namespace:Microsoft.Maui.Controls.Maps;assembly=Microsoft.Maui.Controls.Maps&quot;
xmlns:sensors=&quot;clr-namespace:Microsoft.Maui.Devices.Sensors;assembly=Microsoft.Maui.Essentials&quot;
x:DataType =&quot;viewmodel:ExerciseViewModel&quot;
Title=&quot;{Binding Title}&quot;&gt;
&lt;Grid Padding=&quot;5&quot; Margin=&quot;5&quot; RowSpacing=&quot;5&quot; ColumnSpacing=&quot;3&quot;&gt;
&lt;Grid.RowDefinitions&gt;
&lt;RowDefinition Height=&quot;2*&quot;/&gt;
&lt;RowDefinition Height=&quot;150&quot;/&gt;
&lt;/Grid.RowDefinitions&gt;
&lt;maps:Map Grid.Row=&quot;0&quot; x:Name=&quot;routeMap&quot; VerticalOptions=&quot;CenterAndExpand&quot; Grid.ColumnSpan=&quot;3&quot; HeightRequest=&quot;400&quot; IsZoomEnabled=&quot;False&quot; IsEnabled=&quot;False&quot;&gt;
&lt;x:Arguments&gt;
&lt;MapSpan&gt;
&lt;x:Arguments&gt;
&lt;sensors:Location&gt;
&lt;x:Arguments&gt;
&lt;x:Double&gt;38.744418137669875&lt;/x:Double&gt;
&lt;x:Double&gt;-9.128544160596851&lt;/x:Double&gt;
&lt;/x:Arguments&gt;
&lt;/sensors:Location&gt;
&lt;x:Double&gt;0.7&lt;/x:Double&gt;
&lt;x:Double&gt;0.7&lt;/x:Double&gt;
&lt;/x:Arguments&gt;
&lt;/MapSpan&gt;
&lt;/x:Arguments&gt;
&lt;/maps:Map&gt;
&lt;CarouselView ItemsSource=&quot;{Binding Exercises}&quot; Grid.Row=&quot;1&quot; PeekAreaInsets=&quot;100&quot;&gt;
&lt;CarouselView.ItemTemplate&gt;
&lt;DataTemplate x:DataType=&quot;model:Exercise&quot;&gt;
&lt;Frame HeightRequest=&quot;90&quot; Margin=&quot;5&quot;&gt;
&lt;Frame.GestureRecognizers&gt;
&lt;TapGestureRecognizer Command=&quot;{Binding Source={RelativeSource AncestorType={x:Type viewmodel:ExerciseViewModel}}, Path=ExerciseDetailsCommand}
&quot; CommandParameter=&quot;{Binding .}&quot;&gt;&lt;/TapGestureRecognizer&gt;
&lt;/Frame.GestureRecognizers&gt;
&lt;HorizontalStackLayout Padding=&quot;10&quot; Spacing=&quot;5&quot; &gt;
&lt;Label Text=&quot;{Binding Id}&quot;&gt;&lt;/Label&gt;
&lt;Label Text=&quot;{Binding Date}&quot;&gt;&lt;/Label&gt;
&lt;/HorizontalStackLayout&gt;
&lt;/Frame&gt;
&lt;/DataTemplate&gt;
&lt;/CarouselView.ItemTemplate&gt;
&lt;/CarouselView&gt;
&lt;/Grid&gt;
&lt;/ContentPage&gt;

As you can see I have my map name declared as routeMap and the first location just to start in somewhere. I also has my model and viewmodel declared for DataBinding of the exercise list in the CarouselView. The tap feature works fine and take me to a new view called ExerciseDetailsPage.

This is the code behind ExercisePage.xaml.cs

using DoradSmartphone.Models;
using DoradSmartphone.ViewModels;
using Microsoft.Maui.Controls.Maps;
using Microsoft.Maui.Maps;
namespace DoradSmartphone.Views;
public partial class ExercisePage : ContentPage
{
public ExercisePage(ExerciseViewModel exerciseViewModel)
{
InitializeComponent();
BindingContext = exerciseViewModel;
}
private void OnTapGestureRouteUpdate(object sender, EventArgs e)
{
var route = new Polyline
{
StrokeColor = Colors.Red,
StrokeWidth = 12,
Geopath =
{
new Location(38.70061856336034 , -8.957381918676203 ),
new Location(38.70671683905933 , -8.945225024701308 ),
new Location(38.701985630081595, -8.944503277546072 ),
new Location(38.701872978433386, -8.940750192338834 ),
new Location(38.71054663609023 , -8.939162348597312 ),
new Location(38.717755109243214, -8.942193686649311 ),
new Location(38.7435419727561  , -8.928480490699792 ),
new Location(38.78327379379296 , -8.880556478454272 ),
new Location(38.925473761602376, -8.881999972299806 ),
new Location(38.93692729913667 , -8.869585920414709 ),
new Location(38.93493556584553 , -8.86536198145887  )
}
};
routeMap.MoveToRegion(
MapSpan.FromCenterAndRadius(
new Location(38.93479161472441, -8.865352563545757), Distance.FromMiles(1)));
// Add the polyline to the map
routeMap.MapElements.Add(route);
}
}

If I change the actual tap functionality to this tap event, I can drawn any line and other stuffs with in the Map because I can read the map name defined in the xaml code. But in this codebehind class I can't reach my ViewModel, Services or Model class.

This is my ExerciseViewModel.cs class:

using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using DoradSmartphone;
using DoradSmartphone.Models;
using DoradSmartphone.Services;
using DoradSmartphone.Views;
using Microsoft.Maui.Controls.Maps;
using Microsoft.Maui.Maps;
using System.Collections.ObjectModel;
namespace DoradSmartphone.ViewModels
{
public partial class ExerciseViewModel : BaseViewModel
{
private readonly ExerciseService exerciseService;
public ObservableCollection&lt;Exercise&gt; Exercises { get; private set; } = new();
public ExerciseViewModel(ExerciseService exerciseService)
{
Title = &quot;Training Routes&quot;;
this.exerciseService = exerciseService;
_ = GetExerciseList();                                   
}
[ObservableProperty]
bool isRefreshing;
async Task GetExerciseList()
{
if (IsLoading) return;
try
{
IsLoading = true;
if (Exercises.Any()) Exercises.Clear();
var exercices = exerciseService.GetExercises();
foreach (var exercise in exercices) Exercises.Add(exercise);
} catch(Exception ex) { 
Console.WriteLine(ex.ToString());
await Shell.Current.DisplayAlert(&quot;Error&quot;, &quot;Failed to retrieve the exercice list&quot;, &quot;Ok&quot;);
}
finally { 
IsLoading = false; 
isRefreshing= false;
}
}
[RelayCommand]
async Task ExerciseDetails(Exercise exercise)
{
if(exercise == null) return;
var routes = GetLocations(exercise.Id);
DrawRoutes(routes);
}
public List&lt;Location&gt; GetLocations(int exerciseId)
{
if (exerciseId == 1)
{
return new List&lt;Location&gt;
{
new Location(35.6823324582143, 139.7620853729577),
new Location(35.679263477092704, 139.75773939496295),
new Location(35.68748054650018, 139.761486207315),
new Location(35.690745005825136, 139.7560362984393),
new Location(35.68966608916097, 139.75147199952355),
new Location(35.68427128680411, 139.7442168083328)
};
}
else if (exerciseId == 2)
{
return new List&lt;Location&gt;
{
new Location(35.6823324582143, 139.7620853729577),
new Location(35.679263477092704, 139.75773939496295),
new Location(35.68748054650018, 139.761486207315),
new Location(35.690745005825136, 139.7560362984393),
new Location(35.68966608916097, 139.75147199952355),
new Location(35.68427128680411, 139.7442168083328)
};
}
else
{
return new List&lt;Location&gt;
{
new Location(35.6823324582143, 139.7620853729577),
new Location(35.679263477092704, 139.75773939496295),
new Location(35.68748054650018, 139.761486207315),
new Location(35.690745005825136, 139.7560362984393),
new Location(35.68966608916097, 139.75147199952355),
new Location(35.68427128680411, 139.7442168083328)
};
}
}
private void DrawRoutes(List&lt;Location&gt; routes)
{
var polylines = new Polyline
{
StrokeColor = Colors.Red,
StrokeWidth = 12,
};
foreach(var route in routes)
{
polylines.Geopath.Add(route);
}                        
routeMap.MoveToRegion(
MapSpan.FromCenterAndRadius(
routes.FirstOrDefault(), Distance.FromMiles(1)));
// Add the polyline to the map
routeMap.MapElements.Add(polylines);
}
}
}

This class inherits the BaseViewModel that inherits ObservableObject and has some common properties for all others classes. In the ExerciseViewModel I have my RelayCommand related to the tap feature that grabs the exercise object and add the route, but I cant access the routeMap object. I've tried also to declare a Map class in my viewmodel class, but I get the error all the time that I can't create a instance of a static class.

This is my MauiProgram.cs just in case there's something wrong:

using DoradSmartphone.Data;
using DoradSmartphone.Services;
using DoradSmartphone.ViewModels;
using DoradSmartphone.Views;
namespace DoradSmartphone;
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp&lt;App&gt;()       
.UseMauiMaps()
.ConfigureFonts(fonts =&gt;
{
fonts.AddFont(&quot;OpenSans-Regular.ttf&quot;, &quot;OpenSansRegular&quot;);
fonts.AddFont(&quot;OpenSans-Semibold.ttf&quot;, &quot;OpenSansSemibold&quot;);
});
builder.Services.AddSingleton&lt;DatabaseConn&gt;();
builder.Services.AddScoped&lt;IRepository, DatabaseConn&gt;();
builder.Services.AddSingleton&lt;MainPage&gt;();
builder.Services.AddSingleton&lt;UserPage&gt;();
builder.Services.AddSingleton&lt;LoginPage&gt;();        
builder.Services.AddSingleton&lt;LoadingPage&gt;();
builder.Services.AddSingleton&lt;ExercisePage&gt;();
builder.Services.AddSingleton&lt;DashboardPage&gt;();
builder.Services.AddSingleton&lt;ExerciseDetailsPage&gt;();
builder.Services.AddSingleton&lt;UserService&gt;();
builder.Services.AddSingleton&lt;LoginService&gt;();        
builder.Services.AddSingleton&lt;ExerciseService&gt;();
builder.Services.AddSingleton&lt;DashboardService&gt;();
builder.Services.AddSingleton&lt;UserViewModel&gt;();
builder.Services.AddSingleton&lt;LoginViewModel&gt;();        
builder.Services.AddSingleton&lt;LoadingViewModel&gt;();
builder.Services.AddSingleton&lt;ExerciseViewModel&gt;();
builder.Services.AddSingleton&lt;DashboardViewModel&gt;();
builder.Services.AddTransient&lt;ExerciseDetailsViewModel&gt;();
return builder.Build();
}
}

Thank you in advance!

答案1

得分: 1

抱歉,MapElements 不是一个可绑定的属性。不过,您可以通过几种方式来解决这个问题。

例如,您可以在您的 VM 中创建一个公共方法来返回路线数据。

public Polyline GetRouteData()
{
    var polylines = new Polyline
    {
        StrokeColor = Colors.Red,
        StrokeWidth = 12,
    };

    foreach(var route in routes)
    {
        polylines.Geopath.Add(route);
    } 

    return polylines; 
}

然后,在您的代码后台,首先创建一个对 VM 的类引用。

ExerciseViewModel ViewModel;

public ExercisePage(ExerciseViewModel exerciseViewModel)
{
    InitializeComponent();
    BindingContext = ViewModel = exerciseViewModel;
}

接着,您的代码后台可以从 VM 获取更新地图所需的数据。

routeMap.MapElements.Add(ViewModel.GetRouteData());
英文:

unfortunately, MapElements is not a bindable property. However, you can work around that in a couple of ways

for example, create a public method in your VM that returns the route data

public Polyline GetRouteData()
{
var polylines = new Polyline
{
StrokeColor = Colors.Red,
StrokeWidth = 12,
};
foreach(var route in routes)
{
polylines.Geopath.Add(route);
} 
return polylines; 
}

then in your code behind, first create a class reference to the VM

ExerciseViewModel ViewModel;
public ExercisePage(ExerciseViewModel exerciseViewModel)
{
InitializeComponent();
BindingContext = ViewModel = exerciseViewModel;
}

then your code behind can get the data from the VM that it needs to update the map

routeMap.MapElements.Add(ViewModel.GetRouteData());

答案2

得分: 0

是的,Jason的解决方案有效!!!我不得不改一些东西,但最后它结束得很好。

我必须注入VM类

private ExerciseViewModel viewModel;

public ExercisePage(ExerciseViewModel exerciseViewModel)
{
    InitializeComponent();
    BindingContext = exerciseViewModel;
    viewModel = exerciseViewModel;
}

然后我使用一个轻击手势从数据库中获取并绘制路线。

private void OnTapGestureRouteUpdate(object sender, EventArgs e)
{
    routeMap.MapElements.Clear();

    var frame = (Frame)sender;
    var exercise = (Exercise)frame.BindingContext;
    var exerciseId = exercise.Id;
    var routes = viewModel.GetLocations(exerciseId);

    var polyline = new Polyline
    {
        StrokeColor = Colors.Red,
        StrokeWidth = 12,
    };

    foreach (var route in routes)
    {
        polyline.Geopath.Add(route);
    }

    routeMap.MoveToRegion(
        MapSpan.FromCenterAndRadius(
            routes.FirstOrDefault(), Distance.FromMiles(0.5)));
    routeMap.MapElements.Add(polyline);
}

稍后我会应用一些清理代码,并将折线逻辑放在VM类中,但是,是的,它正在工作!

英文:

Yes, Jason's solution works!!! I had to change a few stuffs, but in the end it end well.

I had to inject the VM class

private ExerciseViewModel viewModel;
public ExercisePage(ExerciseViewModel exerciseViewModel)
{
InitializeComponent();
BindingContext = exerciseViewModel;
viewModel = exerciseViewModel;
}

Then I use a tap gesture to get the routes from database and draw.

private void OnTapGestureRouteUpdate(object sender, EventArgs e)
{
routeMap.MapElements.Clear();
var frame = (Frame)sender;
var exercise = (Exercise)frame.BindingContext;
var exerciseId = exercise.Id;
var routes = viewModel.GetLocations(exerciseId);
var polyline = new Polyline
{
StrokeColor = Colors.Red,
StrokeWidth = 12,
};
foreach (var route in routes)
{
polyline.Geopath.Add(route);
}
routeMap.MoveToRegion(
MapSpan.FromCenterAndRadius(
routes.FirstOrDefault(), Distance.FromMiles(0.5)));
routeMap.MapElements.Add(polyline);
}

I'll aply some clean code and put the polyline logic in the VM class later, but yeah, it's working!

huangapple
  • 本文由 发表于 2023年6月1日 17:44:46
  • 转载请务必保留本文链接:https://go.coder-hub.com/76380607.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定