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

问题描述

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

在运行时创建多段线

按照文档创建多段线: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);

如何解决这个问题?

解决方法

虽然 Polyline.Geopath 是只获取属性,但返回的 IList<Position> 不是只读集合,因此您可以在构造后向其中添加 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<MapRoute> 直接:

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,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" 而不是 { {1}} 和 "lat",因为您的 "lng" 是 pascal-cased 而不是 camel-cased。

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

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

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

  • JsonDocument 是一次性的:

    这个类利用池内存中的资源来最小化垃圾收集器 (GC) 在高使用率场景中的影响。未能正确处置此对象将导致内存无法返回到池中,这将增加框架各个部分的 GC 影响。

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

Demo fiddle #1 here 使用 doc,#2 here 使用 JsonDocument

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...