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

How to append positions when creating a Polyline with Xamarin.Forms.Maps in runtime?

我正在创建 Xamarin.Forms.Maps.Polyline in runtime. How can I append positions dynamically, given that the Polyline.Geopath 属性 是只读的?

正在运行时创建折线

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

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"))));
           
}

最后,有一个包含很多 Position 的列表 pos,我会将 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 class,则可以反序列化您的jsonStringList<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, 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 是 pascal-cased 而不是 camel-cased.

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

    如果在获取 属性 时需要忽略大小写,请参阅

  • Convert.ToDouble() interprets the incoming value by using the formatting conventions of the current thread culture. E.g. in many European locales a , comma is used as the decimal separator when formatting floating values. As this is inconsistent with the JSON standard it is better to use the built-in method JsonElement.GetDouble() 将始终使用正确的小数点分隔符。

  • JsonDocument一次性:

    This class utilizes resources from pooled memory to minimize the impact of the garbage collector (GC) in high-usage scenarios. Failure to properly dispose this object will result in the memory not being returned to the pool, which will increase GC impact across various parts of the framework.

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

演示 fiddle #1 here using JsonDocument, and #2 here 使用 MapRoute.