MAUI Blazor学习11-百度地图定位
MAUI Blazor学习11-百度地图定位
MAUI Blazor系列目录
- MAUI Blazor学习1-移动客户端Shell布局 - SunnyTrudeau - 博客园 (cnblogs.com)
- MAUI Blazor学习2-创建移动客户端Razor页面 - SunnyTrudeau - 博客园 (cnblogs.com)
- MAUI Blazor学习3-绘制ECharts图表 - SunnyTrudeau - 博客园 (cnblogs.com)
- MAUI Blazor学习4-绘制BootstrapBlazor.Chart图表 - SunnyTrudeau - 博客园 (cnblogs.com)
- MAUI Blazor学习5-BLE低功耗蓝牙 - SunnyTrudeau - 博客园 (cnblogs.com)
- MAUI Blazor学习6-扫描二维码 - SunnyTrudeau - 博客园 (cnblogs.com)
- MAUI Blazor学习7-实现登录跳转页面 - SunnyTrudeau - 博客园 (cnblogs.com)
- MAUI Blazor学习8-支持多语言 - SunnyTrudeau - 博客园 (cnblogs.com)
- MAUI Blazor学习9-VS Code开发调试MAUI入门 - SunnyTrudeau - 博客园 (cnblogs.com)
- MAUI Blazor学习10-BarcodeScanner扫描二维码 - SunnyTrudeau - 博客园 (cnblogs.com)
地图和定位功能在APP里很常见,原生APP开发可以直接使用配套的第三方地图SDK,Xamarin.Forms项目要把原生地图SDK转换为C#语言的绑定库,技术要求高,开发效率低下。MAUI Blazor可以采用网页版第三方地图组件,不需要SDK绑定库了,但是要编写一部分JavaScript代码,有得有失,总体上利大于弊。
NetCore官网建议采用隔离的方案操作JavaScript,参见:JavaScript 模块中的 JavaScript 隔离
https://learn.microsoft.com/zh-cn/aspnet/core/blazor/javascript-interoperability/call-javascript-from-dotnet?view=aspnetcore-7.0#javascript-isolation-in-javascript-modules
网上有大佬写了隔离方案操作JavaScript的Blazor组件,我把大佬的代码拿过来简化一下,用于MAUI Blazor项目。感谢大佬无私奉献。
Blazor组件自做六 : 使用JS隔离封装Baidu地图 - AlexChow - 博客园 (cnblogs.com)
继续沿用之前的MAUI Blazor项目MaBlaApp,增加百度地图定位功能。
编写JavaScript调用地图函数baidumap.js
在AlexChow大佬代码基础上小改一下,模块主要功能是初始化百度地图控件容器,呈现地图内容,定位等。
D:\Software\gitee\mauiblazorapp\MaBlaApp\wwwroot\js\baidumap.js
//百度地图实例 var map = null; //地图容器控件 var containerId = null; //初始化百度地图api的js export function addScript(key, elementId, dotnetRef, backgroundColor, controlSize) { if (!key || !elementId) { return; } //设置百度地图容器控件id containerId = elementId; let url = "https://api.map.baidu.com/api?v=3.0&ak="; let scriptsIncluded = false; //获取所有js let scriptTags = document.querySelectorAll('body > script'); scriptTags.forEach(scriptTag => { if (scriptTag) { let srcAttribute = scriptTag.getAttribute('src'); //查找百度地图api的js是否已经添加到页面 if (srcAttribute && srcAttribute.startsWith(url)) { scriptsIncluded = true; return true; } } }); //如果百度地图的js已经添加到页面,就呈现初始地图到容器控件 if (scriptsIncluded) { initMapsG(dotnetRef); return true; } //把百度地图的js添加到页面 url = url + key + "&callback=initMapsG"; let script = document.createElement('script'); script.src = url; document.body.appendChild(script); return false; } //复位地图 export function resetMaps(elementId) { initMaps(elementId); } //呈现初始地图到容器控件 function initMapsG(dotnetRef) { initMaps(containerId); //定位 geolocation(dotnetRef); } //呈现初始地图到容器控件 function initMaps(elementId) { //创建地图实例 map = new BMap.Map(elementId, { //coordsType指定输入输出的坐标类型,3为gcj02坐标,5为bd0ll坐标,默认为5。指定完成后API将以指定的坐标类型处理您传入的坐标 coordsType: 5 }); //创建默认地点坐标,北京 var point = new BMap.Point(116.47496, 39.77856); //设置中心点坐标和地图级别 map.centerAndZoom(point, 15); //开启鼠标滚轮缩放 map.enableScrollWheelZoom(true); map.addControl(new BMap.NavigationControl()); map.addControl(new BMap.ScaleControl()); map.addControl(new BMap.OverviewMapControl()); //map.addControl(new BMap.MapTypeControl()); // 仅当设置城市信息时,MapTypeControl的切换功能才能可用 //map.setCurrentCity("北京"); } //定位,返回位置信息 export function geolocation(wrapper) { var geolocation = new BMap.Geolocation(); //开启SDK辅助定位 geolocation.enableSDKLocation(); //执行定位,回调函数参数r包含位置信息 geolocation.getCurrentPosition(function (r) { let location; if (this.getStatus() == BMAP_STATUS_SUCCESS) { var mk = new BMap.Marker(r.point); map.addOverlay(mk); map.panTo(r.point); location = r; location.Status = BMAP_STATUS_SUCCESS;//0 console.log(r); // 设置中心点坐标和地图级别 map.centerAndZoom(location.point, 15); //map.setCurrentCity(location.address.city); } else { location = { "Status": this.getStatus() }; } if (wrapper) { //回调页面的dotnet函数,传递定位结果 wrapper.invokeMethodAsync('GetResult', location); } return location; }); }
定义百度地图定位结果类BaiduLocation
在浏览器调试运行的时候,打印geolocation(wrapper)的定位结果,复制出来是一个Json,用VS2022的粘贴为类的功能,创建class,然后做一下修正。有的json值为null,自动转换字段类型是object,我去百度地图官网也没有找到定义,只好保留这些object字段。
网上也能够找到类似定位结果类定义,比如https://developer.mozilla.org/en-US/docs/Web/API/GeolocationCoordinates,百度地图应该是在这个上边扩展的。
D:\Software\gitee\mauiblazorapp\MaBlaApp\Data\BaiduLocation.cs
namespace MaBlaApp.Data; /// <summary> /// 百度地图定位返回结果 /// </summary> public class BaiduLocation { public int Accuracy { get; set; } public object Altitude { get; set; } public object AltitudeAccuracy { get; set; } public object Heading { get; set; } public double Latitude { get; set; } public double Longitude { get; set; } public object Speed { get; set; } public object Timestamp { get; set; } public Point? Point { get; set; } public Address? Address { get; set; } public int Status { get; set; } = 0; public string Error { get; set; } = string.Empty; public override string ToString() { string msg = (Status != 0) ? $"错误码{Status}" : $"{Address}"; return msg; } } public class Point { public double Lng { get; set; } public double Lat { get; set; } } public class Address { public string Country { get; set; } = string.Empty; public string City { get; set; } = string.Empty; public int City_code { get; set; } public string District { get; set; } = string.Empty; public string Province { get; set; } = string.Empty; public string Street { get; set; } = string.Empty; public string Street_number { get; set; } = string.Empty; public override string ToString() { return $"{Province}{City}{District}{Street}{Street_number}"; } } /* 选择性粘贴,将json粘贴为类 { "accuracy": 1999, "altitude": null, "altitudeAccuracy": null, "heading": null, "latitude": 22.527458177915, "longitude": 113.92798387312, "speed": null, "timestamp": null, "point": { "lng": 113.92798387312, "lat": 22.527458177915 }, "address": { "country": "", "city": "深圳市", "city_code": 0, "district": "南山区", "province": "广东省", "street": "海德二道", "street_number": "479" } } */
编写地图页面组件BaiduMap
编写一个地图页面组件BaiduMap。
D:\Software\gitee\mauiblazorapp\MaBlaApp\Shared\BaiduMap.razor
@using Microsoft.Extensions.Configuration; @implements IAsyncDisposable @inject IJSRuntime JS @inject IConfiguration config <div id="@ContainerId" style="@Style"></div> @code { /// <summary> /// BaiduKey<para></para> /// 为空则在 IConfiguration 服务获取 "BaiduKey" , 默认在 appsettings.json 文件配置 /// </summary> [Parameter] public string? Key { get; set; } /// <summary> /// 定位结果回调方法 /// </summary> [Parameter] public Func<BaiduLocation, Task>? OnResult { get; set; } /// <summary> /// 容器控件style /// </summary> public string Style { get; set; } = "height:400px;width:100%;"; /// <summary> /// 容器控件ID /// </summary> public string ContainerId { get; set; } = "container"; private IJSObjectReference? module; private DotNetObjectReference<BaiduMap>? InstanceGeo { get; set; } //百度地图秘钥 private string key = String.Empty; protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { //在 IConfiguration 服务获取 "BaiduKey" , 默认在 appsettings.json 文件配置 key = Key ?? config["BaiduKey"]; module = await JS.InvokeAsync<IJSObjectReference>("import", "./js/baidumap.js"); InstanceGeo = DotNetObjectReference.Create(this); while (!(await InitMapScript())) { await Task.Delay(500); } } } //初始化百度地图api的js public async Task<bool> InitMapScript() => await module!.InvokeAsync<bool>("addScript", new object?[] { key, ContainerId, InstanceGeo, null, null }); /// <summary> /// 定位完成回调方法 /// </summary> /// <param name="geolocations"></param> /// <returns></returns> [JSInvokable] public async Task GetResult(BaiduLocation geolocations) { await OnResult?.Invoke(geolocations); string json = JsonSerializer.Serialize(geolocations); System.Diagnostics.Debug.WriteLine(json); } async ValueTask IAsyncDisposable.DisposeAsync() { if (module is not null) { await module.DisposeAsync(); } } }
在TestBaiduMap.razor页面引入BaiduMap组件
百度地图KEY要去申请的。我调试的时候遇到过一些错误,后来发现是权限不全导致,比如定位返回结果是要地理编码权限的,不仅仅是定位权限。我申请的KEY权限很多,宁滥勿缺。
新建百度地图测试页面TestBaiduMap.razor,引入BaiduMap组件
D:\Software\gitee\mauiblazorapp\MaBlaApp\Pages\TestBaiduMap.razor
@page "/testbaidumap" <h3>百度地图 Baidu Map</h3> <p>@message</p> <BaiduMap Key="百度地图KEY" OnResult="@OnResult" /> @code { private string message; private BaiduLocation location; private Task OnResult(BaiduLocation geolocations) { location = geolocations; message = $"{location}"; StateHasChanged(); return Task.CompletedTask; } }
在安卓平台申请定位权限
如果是在windows平台运行,会提示要求定位,允许定位即可。在安卓平台则需要把浏览器权限转换为安卓APP权限,在MaBlaApp项目中已经做好了框架,参见:MAUI Blazor学习5-BLE低功耗蓝牙。在这个基础上只需要增加定位权限即可。
D:\Software\gitee\mauiblazorapp\MaBlaApp\Platforms\Android\AndroidManifest.xml
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
测试
使用VS2022调试运行,在windows上面定位不太准确。
在华为手机鸿蒙4(安卓12)上定位准确。
其他方案
MAUI自身也集成了一些跳转第三方地图,定位功能,也可以结合着用,比如调用MAUI的定位函数,得到经纬度,再去调用百度地图api,但是可能要进行坐标系转换,感觉也没啥优势。如果是重度的地图功能,还是要跳到专门的地图APP去的,用人所长,不要在MAUI APP里边解决所有需求,手机APP的生态环境就是这样的,没有大而全的APP,有一些超级APP比如微信,支付宝,也是通过小程序平台去实现各类具体业务需求的。
DEMO代码地址:https://gitee.com/woodsun/mauiblazorapp