[MAUI]集成高德地图组件至.NET MAUI Blazor项目

文章目录

    • 前期准备:注册高德开发者并创建 key
      • 登录控制台
      • 创建 key
      • 获取 key 和密钥
    • 创建项目
      • 创建JS API Loader
      • 配置权限
      • 创建定义
      • 创建模型
      • 创建地图组件
      • 创建交互逻辑
    • 项目地址

地图组件在手机App中常用地理相关业务,如查看线下门店,设置导航,或选取地址等。是一个较为常见的组件。

在.NET MAUI 中,有两种方案可以集成高德地图,一种是使用原生库绑定。网上也有人实现过:https://blog.csdn.net/sD7O95O/article/details/125827031

但这种方案需要大量平台原生开发的知识,而且需要对每一个平台进行适配。

在这里我介绍第二种方案:.NET MAUI Blazor + 高德地图JS API 2.0 库的实现。

JS API 2.0 是高德开放平台基于WebGL的地图组件,可以将高德地图模块集成到.NET MAUI Blazor中的BlazorWebView控件,由于BlazorWebView的跨平台特性,可以达到一次开发全平台通用,无需为每个平台做适配。

今天用此方法实现一个地图选择器,使用手机的GPS定位初始化当前位置,使用高德地图JS API库实现地点选择功能。混合开发方案涉及本机代码与JS runtime的交互,如果你对这一部分还不太了解,可以先阅读这篇文章:[MAUI]深入了解.NET MAUI Blazor与Vue的混合开发

.NET MAUI Blazor

使用.NET MAU实现跨平台支持,本项目可运行于Android、iOS平台。

前期准备:注册高德开发者并创建 key

登录控制台

登录 高德开放平台控制台,如果没有开发者账号,请 注册开发者。

在这里插入图片描述

创建 key

进入应用管理,创建新应用,新应用中添加 key,服务平台选择 Web端(JS API)。再创建一个Web服务类型的Key,用于解析初始位置地址。

在这里插入图片描述

获取 key 和密钥

创建成功后,可获取 key 和安全密钥。

在这里插入图片描述

创建项目

新建.NET MAUI Blazor项目,命名AMap

创建JS API Loader

前往https://webapi.amap.com/loader.js另存js文件至项目wwwroot文件夹

在这里插入图片描述

在wwwroot创建amap_index.html文件,将loader.js引用到页面中。创建_AMapSecurityConfig对象并设置安全密钥。

<!DOCTYPE html>
<html lang="en">
<head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" /><title>AmapApp</title><base href="/" /><link href="css/app2.css" rel="stylesheet" />
</head><body><div class="status-bar-safe-area"></div><div id="app">Loading...</div><div id="blazor-error-ui">An unhandled error has occurred.<a href="" class="reload">Reload</a><a class="dismiss">🗙</a></div><script src="_framework/blazor.webview.js" autostart="false"></script><script src="lib/amap/loader.js"></script><script type="text/javascript">window._AMapSecurityConfig = {securityJsCode: "764832459a38e824a0d555b62d8ec1f0",};</script></body></html>

配置权限

打开Android端AndroidManifest.xml文件

在这里插入图片描述

添加权限:

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />

打开Info.plist文件,添加权限描述信心

在这里插入图片描述

<key>NSLocationWhenInUseUsageDescription</key>
<string>允许使用设备的GPS更新您的位置信息。</string>

创建定义

创建Position,Poi,Location等类型,用于描述位置信息。由于篇幅这里不展开介绍。

创建模型

创建一个MainPageViewModel类,用于处理页面逻辑。代码如下:

public class MainPageViewModel : ObservableObject
{public event EventHandler<FinishedChooiseEvenArgs> OnFinishedChooise;private static AsyncLock asyncLock = new AsyncLock();public static RateLimitedAction throttledAction = Debouncer.Debounce(null, TimeSpan.FromMilliseconds(1500), leading: false, trailing: true);public MainPageViewModel(){Search = new Command(SearchAction);Done = new Command(DoneAction);Remove = new Command(RemoveAction);}private void RemoveAction(object obj){this.Address=null;this.CurrentLocation=null;OnFinishedChooise?.Invoke(this, new FinishedChooiseEvenArgs(Address, CurrentLocation));}private void DoneAction(object obj){OnFinishedChooise?.Invoke(this, new FinishedChooiseEvenArgs(Address, CurrentLocation));}private void SearchAction(object obj){Init();}public async void Init(){var location = await GeoLocationHelper.GetNativePosition();if (location==null){return;}var amapLocation = new Location.Location(){Latitude=location.Latitude,Longitude=location.Longitude};CurrentLocation=amapLocation;}private Location.Location _currentLocation;public Location.Location CurrentLocation{get { return _currentLocation; }set{if (_currentLocation != value){if (value!=null &&_currentLocation!=null&&Location.Location.CalcDistance(value, _currentLocation)<100){return;}_currentLocation = value;OnPropertyChanged();}}}private string _address;public string Address{get { return _address; }set{_address = value;OnPropertyChanged();}}private ObservableCollection<Poi> _pois;public ObservableCollection<Poi> Pois{get { return _pois; }set{_pois = value;OnPropertyChanged();}}private Poi _selectedPoi;public Poi SelectedPoi{get { return _selectedPoi; }set{_selectedPoi = value;OnPropertyChanged();}}public Command Search { get; set; }public Command Done { get; set; }public Command Remove { get; set; }}

注意这里的Init方法,用于初始化位置。

GeoLocationHelper.GetNativePosition()方法用于从你设备的GPS模块,获取当前位置。它调用的是Microsoft.Maui.Devices.Sensors提供的设备传感器访问功能
,详情可参考官方文档地理位置 - .NET MAUI

创建地图组件

创建Blazor页面AMapPage.razor以及AMapPage.razor.js

AMapPage.razor中引入

protected override async Task OnAfterRenderAsync(bool firstRender)
{if (!firstRender)return;await JSRuntime.InvokeAsync<IJSObjectReference>("import", "./AMapPage.razor.js");await Refresh();await JSRuntime.InvokeVoidAsync("window.initObjRef", this.objRef);
}

razor页面的 @Code 代码段中,放置MainPageViewModel属性,以及一个DotNetObjectReference对象,用于在JS中调用C#方法。

@code {[Parameter]public MainPageViewModel MainPageViewModel { get; set; }private DotNetObjectReference<AMapPage> objRef;protected override void OnInitialized(){objRef = DotNetObjectReference.Create(this);}private async Task Refresh(){...}

AMapPage.razor.js我们加载地图,并设置地图的中心点。和一些地图挂件。此外,我们还需要监听地图的中心点变化,更新中心点。 这些代码可以从官方示例中复制。(https://lbs.amap.com/demo/javascript-api-v2/example/map/map-moving)。

console.info("start load")
window.viewService = {map: null,zoom: 13,amaplocation: [116.397428, 39.90923],SetAmapContainerSize: function (width, height) {console.info("setting container size")var div = document.getElementById("container");div.style.height = height + "px";},SetLocation: function (longitude, latitude) {console.info("setting loc", longitude, latitude)window.viewService.amaplocation = [longitude, latitude];if (window.viewService.map) {window.viewService.map.setZoomAndCenter(window.viewService.zoom, window.viewService.amaplocation);console.info("set loc", window.viewService.zoom, window.viewService.map)}},isHotspot: true}
AMapLoader.load({ //首次调用 loadkey: '0896cedc056413f83ca0aee5b029c65d',//首次load key为必填version: '2.0',plugins: ['AMap.Scale', 'AMap.ToolBar', 'AMap.InfoWindow', 'AMap.PlaceSearch']
}).then((AMap) => {console.info("loading..")var opt = {resizeEnable: true,center: window.viewService.amaplocation,zoom: window.viewService.zoom,isHotspot: true}var map = new AMap.Map('container', opt);console.info(AMap, map, opt)map.addControl(new AMap.Scale())map.addControl(new AMap.ToolBar())window.viewService.marker = new AMap.Marker({position: map.getCenter()})map.add(window.viewService.marker);var placeSearch = new AMap.PlaceSearch();  //构造地点查询类var infoWindow = new AMap.InfoWindow({});map.on('hotspotover', function (result) {placeSearch.getDetails(result.id, function (status, result) {if (status === 'complete' && result.info === 'OK') {onPlaceSearch(result);}});});map.on('moveend', onMapMoveend);// map.on('zoomend', onMapMoveend);//回调函数window.viewService.map = map;function onMapMoveend() {var zoom = window.viewService.map.getZoom(); //获取当前地图级别var center = window.viewService.map.getCenter(); //获取当前地图中心位置if (window.viewService.marker) {window.viewService.marker.setPosition(center);}window.objRef.invokeMethodAsync('OnMapMoveend', center);}function onPlaceSearch(data) { //infoWindow.open(map, result.lnglat);var poiArr = data.poiList.pois;if (poiArr[0]) {var location = poiArr[0].location;infoWindow.setContent(createContent(poiArr[0]));infoWindow.open(window.viewService.map, location);}}function createContent(poi) {  //信息窗体内容var s = [];s.push('<div class="info-title">' + poi.name + '</div><div class="info-content">' + "地址:" + poi.address);s.push("电话:" + poi.tel);s.push("类型:" + poi.type);s.push('<div>');return s.join("<br>");}console.info("loaded")}).catch((e) => {console.error(e);
});
window.initObjRef = function (objRef) {window.objRef = objRef;
}

地图中心点改变时,我们需要使用window.objRef.invokeMethodAsync('OnMapMoveend', center);从JS runtime中通知到C#代码。

同时,在AMapPage.razor中配置一个方法,用于接收从JS runtime发来的回调通知。
在此赋值CurrentLocation属性。


[JSInvokable]
public async Task OnMapMoveend(dynamic location)
{await Task.Run(() =>{var locationArray = JsonConvert.DeserializeObject<double[]>(location.ToString());MainPageViewModel.CurrentLocation=new Location.Location(){Longitude=locationArray[0],Latitude =locationArray[1]};});
}

同时监听CurrentLocation属性的值,一旦发生变化,则调用JS runtime中的viewService.SetLocation方法,更新地图中心点。

protected override async Task OnInitializedAsync()
{MainPageViewModel.PropertyChanged +=  async (o, e) =>{if (e.PropertyName==nameof(MainPageViewModel.CurrentLocation)){if (MainPageViewModel.CurrentLocation!=null){var longitude = MainPageViewModel.CurrentLocation.Longitude;var latitude = MainPageViewModel.CurrentLocation.Latitude;await JSRuntime.InvokeVoidAsync("viewService.SetLocation", longitude, latitude);}}};}

MainPageViewModel类中,我们添加一个PropertyChanged事件,用于监听CurrentLocation属性的改变。

当手指滑动地图触发位置变化,导致CurrentLocation属性改变时,将当前的中心点转换为具体的地址。这里使用了高德逆地理编码API服务(https://restapi.amap.com/v3/geocode/regeo)解析CurrentLocation的值, 还需使用了防抖策略,避免接口的频繁调用。


public MainPageViewModel()
{PropertyChanged+=MainPageViewModel_PropertyChanged;...
}private async void MainPageViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{if (e.PropertyName == nameof(CurrentLocation)){if (CurrentLocation!=null){// 使用防抖using (await asyncLock.LockAsync()){var amapLocation = new Location.Location(){Latitude=CurrentLocation.Latitude,Longitude=CurrentLocation.Longitude};var amapInverseHttpRequestParamter = new AmapInverseHttpRequestParamter(){Locations= new Location.Location[] { amapLocation }};ReGeocodeLocation reGeocodeLocation = null;try{reGeocodeLocation = await amapHttpRequestClient.InverseAsync(amapInverseHttpRequestParamter);}catch (Exception ex){Console.WriteLine(ex.ToString());}throttledAction.Update(() =>{MainThread.BeginInvokeOnMainThread(() =>{CurrentLocation=amapLocation;if (reGeocodeLocation!=null){Address = reGeocodeLocation.Address;Pois=new ObservableCollection<Poi>(reGeocodeLocation.Pois);}});});throttledAction.Invoke();}}}
}

至此我们完成了地图组件的基本功能。

创建交互逻辑

在MainPage.xaml中,创建一个选择器按钮,以及一个卡片模拟选择器按钮点击后的弹窗。


<Button Clicked="Button_Clicked"Grid.Row="1"x:Name="SelectorButton"HorizontalOptions="Center"VerticalOptions="Center"Text="{Binding Address, TargetNullValue=请选择地点}"></Button><Border StrokeShape="RoundRectangle 10"Grid.RowSpan="2"x:Name="SelectorPopup"IsVisible="False"Margin="5,50"MinimumHeightRequest="500"><Grid Padding="0"><Grid.RowDefinitions><RowDefinition Height="Auto"></RowDefinition><RowDefinition Height="Auto"></RowDefinition><RowDefinition></RowDefinition></Grid.RowDefinitions><Grid Grid.Row="0"><Grid.ColumnDefinitions><ColumnDefinition Width="*"></ColumnDefinition><ColumnDefinition></ColumnDefinition></Grid.ColumnDefinitions><Label FontSize="Large"Margin="10, 10, 10, 0"FontAttributes="Bold"Text="选择地点"></Label><HorizontalStackLayout Grid.Column="1"HorizontalOptions="End"><Button Text="删除"Margin="5,0"Command="{Binding Remove}"></Button><Button Text="完成"Margin="5,0"Command="{Binding Done}"></Button></HorizontalStackLayout></Grid><Grid Grid.Row="1"Margin="10, 10, 10, 0"><Grid.RowDefinitions><RowDefinition></RowDefinition><RowDefinition Height="Auto"></RowDefinition></Grid.RowDefinitions><Label HorizontalTextAlignment="Center"VerticalOptions="Center"x:Name="ContentLabel"Text="{Binding Address}"></Label><Border IsVisible="False"Grid.RowSpan="2"x:Name="ContentFrame"><Entry Text="{Binding Address, Mode=TwoWay}"Placeholder="请输入地址, 按Enter键完成"Completed="Entry_Completed"Unfocused="Entry_Unfocused"ClearButtonVisibility="WhileEditing"></Entry></Border><Border x:Name="ContentButton"Grid.Row="1"HorizontalOptions="Center"VerticalOptions="Center"><Label><Label.FormattedText><FormattedString><Span FontFamily="FontAwesome"Text="&#xf044;"></Span><Span Text=" 修改"></Span></FormattedString></Label.FormattedText></Label><Border.GestureRecognizers><TapGestureRecognizer Tapped="TapGestureRecognizer_Tapped"></TapGestureRecognizer></Border.GestureRecognizers></Border></Grid><BlazorWebView Grid.Row="2"Margin="-10, 0"x:Name="mainMapBlazorWebView"HostPage="wwwroot/amap_index.html"><BlazorWebView.RootComponents><RootComponent Selector="#app"x:Name="rootComponent"ComponentType="{x:Type views:AMapPage}" /></BlazorWebView.RootComponents></BlazorWebView></Grid>
</Border>

在这里插入图片描述

最终效果如下:

在这里插入图片描述

项目地址

Github:maui-samples

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/765871.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

LeetCode 热题 100 | 堆(二)

目录 1 什么是优先队列 1.1 优先队列与堆的关系 1.2 如何定义优先队列 1.3 如何使用优先队列 1.4 如何设置排序规则 2 347. 前 K 个高频元素 2.1 第 2 步的具体实现 2.2 举例说明 2.3 完整代码 3 215. 数组中的第 K 个最大元素 - v2 菜鸟做题&#xff0c;语…

Shell学习

一、 变量 shell是弱类型语言&#xff0c;不用定义数据类型&#xff0c;默认都是字符串。 变量与值之间不得有空格 只能包含数字、字母、下划线 不能以数字开头 区分大小写 根据变量的作用域可以将shell变量分为&#xff1a;全局变量、局部变量、环境变量。全局变量通常和…

app自动化测试怎么学?

app测试的主要内容有那些 1、功能测试 : 查看功能是否正常&#xff0c;主要针对每一个功能点进行一一测试&#xff0c;主要核心就是把验证的每个测试点都满足需求的对应功能&#xff0c;验证标准就是让预期结果和实际结果保持一致。 2、安装卸载测试&#xff1a;首先要测试的…

【Linux】从零认识进程 — 中下篇

送给大家一句话&#xff1a; 人一切的痛苦&#xff0c;本质上都是对自己无能的愤怒。而自律&#xff0c;恰恰是解决人生痛苦的根本途径。—— 王小波 从零认识进程 1 进程优先级1.1 什么是优先级1.2 为什么要有优先级1.3 Linux优先级的特点 && 查看方式1.4 其他概念 2…

深度解析深度学习中的长短期记忆网络(LSTM)(含代码实现)

在深度学习中&#xff0c;长短期记忆网络&#xff08;LSTM&#xff09;是一种强大的循环神经网络结构&#xff0c;能够更好地处理长序列数据并减轻梯度消失的问题。本文将介绍LSTM的工作原理&#xff0c;并使用PyTorch实现一个简单的LSTM模型来展示其在自然语言处理中的应用。 …

MongoDB完全开发手册(一篇学会MongoDB所有知识点)

目录 一、MongoDB 基础 1.1 、MongoDB 是什么&#xff1f; 1.2、 MongoDB 的存储结构是什么&#xff1f; 1.3 、文档 1.4 、集合 1.5 、数据库 1.6、 MongoDB 有什么特点&#xff1f; 1.7、 MongoDB 适合什么应用场景&#xff1f; 二、MongoDB 存储引擎 2.1 、MongoDB…

Autosar Crypto Interface学习笔记

文章目录 前言Functional specificationError classificationError detection API specificationType DefinitionsFunction definitionsGeneral APICryIf_InitCryIf_GetVersionInfo Job Processing InterfaceCryIf_ProcessJobDispatch Key IDs匹配KeyId Job Cancellation Inter…

【嵌入式——QT】Charts常见的图表的绘制

【嵌入式——QT】Charts常见的图表的绘制 柱状图QBarSetQBarSeriesQBarCategoryAxis图示 饼图堆叠柱状图百分比柱状图散点图和光滑曲线图代码示例 柱状图 QBarSet 用于创建柱状图的数据集。 主要函数 setLabel()&#xff1a;设置数据集标签 &#xff1b;setLabelBrush()&am…

租用阿里云2核2G服务器配置报价,61元和99元

阿里云2核2G服务器配置优惠价格61元和99元&#xff0c;61元是轻量应用服务器2核2G3M带宽、50G高效云盘&#xff0c;99元服务器是ECS云服务器经济型e实例2核2G、3M固定带宽、40G ESSD entry 系统盘。活动 aliyunfuwuqi.com/go/aliyun 阿里云服务器网aliyunfuwuqi.com根据上面的官…

​ YOLOv9改进策略:SPPELAN优化 | 新一代高效可形变卷积DCNv4如何做二次创新?高效结合SPPELAN| CVPR2024

&#x1f4a1;&#x1f4a1;&#x1f4a1;本文改进内容&#xff1a; DCNv4来自CVPR2024 的论文&#xff0c;它不仅收敛速度明显快于DCNv3&#xff0c;而且正向速度提高了3倍以上。这一改进使DCNv4能够充分利用其稀疏特性&#xff0c;成为最快的通用核心视觉算子之一。 |新一代…

如何减少pdf的文件大小?pdf压缩工具介绍

文件发不出去&#xff0c;有时就会耽误工作进度&#xff0c;文件太大无法发送&#xff0c;这应该是大家在发送PDF时&#xff0c;常常会碰到的问题吧&#xff0c;那么PDF文档压缩大小怎么做呢&#xff1f;因此我们需要对pdf压缩后再发送&#xff0c;那么有没有好用的pdf压缩工具…

牛客题霸-SQL进阶篇(刷题记录二)

本文基于前段时间学习总结的 MySQL 相关的查询语法&#xff0c;在牛客网找了相应的 MySQL 题目进行练习&#xff0c;以便加强对于 MySQL 查询语法的理解和应用。 由于涉及到的数据库表较多&#xff0c;因此本文不再展示&#xff0c;只提供 MySQL 代码与示例输出。 部分题目因…

烯冷新能源邀您参观2024长三角快递物流展

参加企业介绍 宁波戈雷贝拓科技有限公司&#xff08;宁波烯冷新能源科技有限公司&#xff09;宁波烯冷新能源科技有限公司于2022年初成立&#xff0c;依托中国科学院宁波材料技术与工程研究所和国家石墨烯创新中心&#xff0c;公司开发产品包括&#xff1a;新能源制冷系统和集…

深入理解Netty以及为什么项目中要使用?(六)Netty核心组件实例

调度器详解 前面我们讲过NIO多路复用的设计模式之Reactor模型&#xff0c;Reactor模型的主要思想就是把网络连接、事件分发、任务处理的职责进行分离&#xff0c;并且通过引入多线程来提高Reactor模型中的吞吐量。其中包括三种Reactor模型 单线程单Reactor模型 多线程单React…

【Python + Django】表结构创建

以员工管理系统为例。 事前呢&#xff0c;我们先把项目和app创建出来&#xff0c;详细步骤可以看我同栏目的第一篇、第二篇文章。 我知道你们是不会下来找的&#xff0c;就把链接贴在下面吧&#xff1a; 【Python Django】启动简单的文本页面-CSDN博客 【Python Django】…

Microsoft Windows 10 22H2官方简体中文正式版2023年12月更新版(最新微软原版ISO镜像)

Microsoft Windows 10 22H2官方简体中文正式版2023年12月更新版(最新微软原版ISO镜像) 将标红的地址放大迅雷里面下载就好&#xff01; MVS Microsoft Windows 10 22H2 官方正式版2023年12月版ISO镜像微软订阅中心发布信息 简体中文商业版2023年12月版&#xff08;教育版、…

【C语言进阶篇】动态内存管理

【C语言进阶篇】动态内存管理 &#x1f308;个人主页&#xff1a;开敲 &#x1f525;所属专栏&#xff1a;C语言 &#x1f33c;文章目录&#x1f33c; 1. 为什么要有动态内存分配 2.动态内存开辟和释放函数 2.1 动态内存释放函数 2.1.1 free函数 2.2 动态内存开辟函数 2.2.1 …

【鸿蒙HarmonyOS开发笔记】应用数据持久化之通过用户首选项实现数据持久化

概述 应用数据持久化&#xff0c;是指应用将内存中的数据通过文件或数据库的形式保存到设备上。内存中的数据形态通常是任意的数据结构或数据对象&#xff0c;存储介质上的数据形态可能是文本、数据库、二进制文件等。 HarmonyOS标准系统支持典型的存储数据形态&#xff0c;包…

OceanPen Art AI绘画系统 运营教程(三)2.10绘画全新界面升级

在一个崇高的目标支持下&#xff0c;不停地工作&#xff0c;即使慢&#xff0c;也一定会获得成功。 —— 爱因斯坦 演示站点&#xff1a; ai.oceanpen.art 官方论坛&#xff1a; www.jingyuai.com 一、前端用户界面全新体验 二、 MJ绘画分享 提示词自取&#xff1a;htt…

如何使用 ArcGIS Pro 制作好看的高程渲染图

虽然 ArcGIS Pro 已经提供了很多好看的配色方案&#xff0c;但是如果直接对高程DEM进行渲染效果不是很理想&#xff0c;我们可以结合山体阴影让高程渲染图看起来更加立体&#xff0c;这里为大家介绍一下制作方法&#xff0c;希望能对你有所帮助。 数据来源 教程所使用的数据是…