在运行时使用 Xamarin.Forms.Maps 创建折线时如何附加位置?

Posted

技术标签:

【中文标题】在运行时使用 Xamarin.Forms.Maps 创建折线时如何附加位置?【英文标题】:How to append positions when creating a Polyline with Xamarin.Forms.Maps in runtime? 【发布时间】:2021-06-04 03:19:15 【问题描述】:

我正在运行时创建Xamarin.Forms.Maps.Polyline。鉴于Polyline.Geopath 属性是只读的,如何动态附加positions?

在运行时创建折线

按照文档创建折线:Xamarin.Forms Map Polygons and Polylines。此链接是代码中固定位置的教程。如何在运行时动态分配位置。

using Xamarin.Forms.Maps;
// ...
Map map = new Map

// ...
;

创建一个对象来存储路由数据(从json中提取的数据)

public class MapRoute

    //[JsonPropertyName("timestamp")]
    public long timestamp  get; set; 
    //[JsonPropertyName("lat")]
    public double lat  get; set; 
    //[JsonPropertyName("lng")]
    public double lng  get; set; 

    public MapRoute(long v1, double v2, double v3)
    
        timestamp = v1;
        lat = v2;
        lng = v3;
    

将路由对象序列化为 JsonString。

public void RouteAppend(MapRoute route)

    JsonString.Append(JsonSerializer.Serialize(route));
    JsonString.Append(",");

在现实生活中,jsonString中有2个以上的元素(jsonString中存储的元素有1000多个)

readonly string jsonString = " [ \"timestamp\": 1514172600000, \"Lat\": 37.33417925, \"Lng\": -122.04153133, " + "\"timestamp\": 1514172610000, \"Lat\": 37.33419725, \"Lng\": -122.04151333 ]";

JsonDocument doc;
JsonElement root;

private IList<Position> pos;

doc = Parse(testString);
root = doc.RootElement;
     
var routes = root.EnumerateArray();
while (routes.MoveNext())

    var user = routes.Current;
  
    pos.Add(new Position(Convert.ToDouble(user.GetProperty("lat")), Convert.ToDouble(user.GetProperty("lng"))));
           

最后,有一个列表pos 有很多Position,我会将pos 分配给Geopath。不幸的是,这是不允许的。它是一个只读属性:

// instantiate a polyline
Polyline polyline = new Polyline

    StrokeColor = Color.Blue,
    StrokeWidth = 12,
    Geopath = pos // It is a readonly property, cannot assign pos to Geopath


// add the polyline to the map's MapElements collection
map.MapElements.Add(polyline);

如何解决这个问题?

【问题讨论】:

【参考方案1】:

虽然Polyline.Geopath 是一个get-only 属性,但返回的IList&lt;Position&gt; 不是只读集合,因此您可以在构造后将Position 对象添加到其中。

因此您可以创建以下工厂方法:

public static class PolylineFactory

    const string latitudeJsonName = "Lat";
    const string longitudeJsonName = "Lng";
    
    public static Polyline FromLatLngJson(string jsonString, float? strokeWidth = default, Color strokeColor = default)
    
        using var doc = JsonDocument.Parse(jsonString); 
        var query = doc.RootElement.EnumerateArray()
            // GetProperty performs property name matching as an ordinal, case-sensitive comparison.
            .Select(e => new Position(e.GetProperty(latitudeJsonName).GetDouble(), e.GetProperty(longitudeJsonName).GetDouble()));

        var polyline = new Polyline();
        if (strokeColor != default)
            polyline.StrokeColor = strokeColor;
        if (strokeWidth != default)
            polyline.StrokeWidth = strokeWidth.Value;
        foreach (var position in query)
            polyline.Geopath.Add(position);
        
        return polyline;
    

另外,在 .NET 5 中,JsonSerializer 支持使用单个参数化构造函数反序列化对象,因此如果您稍微修改您的 MapRoute 类,如下所示,您可以将您的 jsonString 反序列化为 List&lt;MapRoute&gt; 直接:

public class MapRoute

    public long timestamp  get; set; 
    public double lat  get; set; 
    public double lng  get; set; 

    public MapRoute(long timestamp, double lat, double lng)
    
        // The constructor argument names and property names must match for JsonSerializer to bind to the constructor successfully
        this.timestamp = timestamp;
        this.lat = lat;
        this.lng = lng;
    


public static class PolylineFactory

    public static Polyline FromLatLngJson(string jsonString, float? strokeWidth = default, Color strokeColor = default)
    
        var routes = JsonSerializer.Deserialize<List<MapRoute>>(jsonString, new JsonSerializerOptions  PropertyNameCaseInsensitive = true );
        
        var polyline = new Polyline();
        if (strokeColor != default)
            polyline.StrokeColor = strokeColor;
        if (strokeWidth != default)
            polyline.StrokeWidth = strokeWidth.Value;
        foreach (var route in routes)
            polyline.Geopath.Add(new Position(route.lat, route.lng));
        
        return polyline;
    

(在 .NET Core 3.x 中,您需要向 MapRoute 添加无参数构造函数,并确保所有属性都是可变的才能成功反序列化。)

无论哪种方式,都可以如下调用工厂方法:

var polyline = PolylineFactory.FromLatLngJson(jsonString, 12, Color.Blue);

注意事项:

JsonElement.GetProperty() 执行属性名称匹配作为一个序数、区分大小写的比较,因此您需要传递"Lat""Lng" 而不是"lat""lng",因为您的jsonString 是帕斯卡大小写而不是骆驼大小写。

如果这是您问题中的错误,并且您的 JSON 字符串确实是驼峰式大小写,请适当修改 latitudeJsonNamelongitudeJsonName

如果您在获取属性时需要忽略大小写,请参阅Ignore case when using TryGetProperty

Convert.ToDouble() 通过使用当前线程文化的格式约定来解释传入的值。在许多欧洲语言环境中,, 逗号在格式化浮点值时用作小数分隔符。由于这与 JSON 标准不一致,因此最好使用内置方法 JsonElement.GetDouble(),它将始终使用正确的小数分隔符。

JsonDocument 是一次性的:

此类利用池化内存中的资源来最大程度地减少垃圾收集器 (GC) 在高使用情况下的影响。未能正确处置此对象将导致内存未返回到池中,这将增加对框架各个部分的 GC 影响。

在您的代码中,您不会处理您的doc,但您应该处理。

演示小提琴#1 here 使用JsonDocument,#2 here 使用MapRoute

【讨论】:

感谢您的帮助,我会尽快采纳您的建议 采纳了您的建议,问题已解决。谢谢。

以上是关于在运行时使用 Xamarin.Forms.Maps 创建折线时如何附加位置?的主要内容,如果未能解决你的问题,请参考以下文章

如何在单击 xamarin.forms.maps iOS c# 时更改 pin 图标

在 iOS 上的 Xamarin.Forms.Maps 中使用 Xamarin.Essentials Geolocation 获取设备位置的奇怪问题

Xamarin.Forms.Maps 如何像谷歌地图一样在图钉旁边/上方有一个标签?

从 ViewModel 绑定到 Xamarin.Forms.Maps.Map

有没有办法动画Xamarin.Forms.Maps针运动

有没有办法在 iOS 项目上禁用 xamarin.forms.maps 上的信息窗口?