/// <summary>
        /// Writes the tile to the given stream.
        /// </summary>
        /// <param name="vectorTile">The vector tile.</param>
        /// <param name="stream">The stream to write to.</param>
        /// <param name="extent">The extent.</param>
        public static void Write(this VectorTile vectorTile, Stream stream, uint extent = 4096)
        {
            var tile = new Tiles.Tile(vectorTile.TileId);

            double latitudeStep  = (tile.Top - tile.Bottom) / extent;
            double longitudeStep = (tile.Right - tile.Left) / extent;
            double top           = tile.Top;
            double left          = tile.Left;

            var mapboxTile = new Mapbox.Tile();

            foreach (var localLayer in vectorTile.Layers)
            {
                var layer = new Mapbox.Tile.Layer {
                    Version = 2, Name = localLayer.Name, Extent = extent
                };

                var keys   = new Dictionary <string, uint>();
                var values = new Dictionary <string, uint>();


                foreach (var localLayerFeature in localLayer.Features)
                {
                    if (localLayerFeature.Geometry is Point p)
                    {
                        var feature = new Mapbox.Tile.Feature();

                        var posX = (int)((p.X - left) / longitudeStep);
                        var posY = (int)((top - p.Y) / latitudeStep);
                        GenerateMoveTo(feature.Geometry, posX, posY);
                        feature.Type = Tile.GeomType.Point;

                        AddAttributes(feature.Tags, keys, values, localLayerFeature.Attributes);

                        layer.Features.Add(feature);
                    }
                    else if (localLayerFeature.Geometry is LineString ls)
                    {
                        var feature = new Mapbox.Tile.Feature();

                        var posX = (int)((ls.Coordinates[0].X - left) / longitudeStep);
                        var posY = (int)((top - ls.Coordinates[0].Y) / latitudeStep);
                        GenerateMoveTo(feature.Geometry, posX, posY);

                        // generate line to.
                        feature.Geometry.Add(GenerateCommandInteger(2, ls.Coordinates.Length - 1));
                        for (var j = 1; j < ls.Coordinates.Length; j++)
                        {
                            var localPosX = (int)((ls.Coordinates[j].X - left) / longitudeStep);
                            var localPosY = (int)((top - ls.Coordinates[j].Y) / latitudeStep);
                            var dx        = localPosX - posX;
                            var dy        = localPosY - posY;
                            posX = localPosX;
                            posY = localPosY;

                            feature.Geometry.Add(GenerateParameterInteger(dx));
                            feature.Geometry.Add(GenerateParameterInteger(dy));
                        }

                        feature.Type = Tile.GeomType.LineString;

                        AddAttributes(feature.Tags, keys, values, localLayerFeature.Attributes);

                        layer.Features.Add(feature);
                    }
                }

                layer.Keys.AddRange(keys.Keys);
                foreach (var value in values.Keys)
                {
                    if (int.TryParse(value, out var intValue))
                    {
                        layer.Values.Add(new Tile.Value()
                        {
                            IntValue = intValue
                        });
                    }
                    else if (float.TryParse(value, out var floatValue))
                    {
                        layer.Values.Add(new Tile.Value()
                        {
                            FloatValue = floatValue
                        });
                    }
                    else
                    {
                        layer.Values.Add(new Tile.Value()
                        {
                            StringValue = value
                        });
                    }
                }
                mapboxTile.Layers.Add(layer);
            }

            ProtoBuf.Serializer.Serialize <Tile>(stream, mapboxTile);
        }
        /// <summary>
        /// Writes the tile to the given stream.
        /// </summary>
        public static void Write(this VectorTile vectorTile, Stream stream,
                                 Func <IAttributeCollection, Itinero.VectorTiles.Layers.Layer, IAttributeCollection> mapAttributes = null, uint extent = 4096)
        {
            var tile = new Tiles.Tile(vectorTile.TileId);

            double latitudeStep  = (tile.Top - tile.Bottom) / extent;
            double longitudeStep = (tile.Right - tile.Left) / extent;
            double top           = tile.Top;
            double left          = tile.Left;

            var mapboxTile = new Mapbox.Tile();

            foreach (var localLayer in vectorTile.Layers)
            {
                var layer = new Mapbox.Tile.Layer();
                layer.Version = 2;
                layer.Name    = localLayer.Name;
                layer.Extent  = extent;

                var keys   = new Dictionary <string, uint>();
                var values = new Dictionary <string, uint>();

                if (localLayer is SegmentLayer)
                {
                    var segmentLayer = localLayer as SegmentLayer;
                    var segments     = segmentLayer.Segments;
                    var edgeProfile  = segmentLayer.Profiles;
                    var edgeMeta     = segmentLayer.Meta;

                    for (var i = 0; i < segments.Length; i++)
                    {
                        var feature = new Mapbox.Tile.Feature();

                        var shape = segments[i].Shape;
                        var posX  = (int)((shape[0].Longitude - left) / longitudeStep);
                        var posY  = (int)((top - shape[0].Latitude) / latitudeStep);
                        GenerateMoveTo(feature.Geometry, posX, posY);

                        // generate line to.
                        feature.Geometry.Add(GenerateCommandInteger(2, shape.Length - 1));
                        for (var j = 1; j < shape.Length; j++)
                        {
                            var localPosX = (int)((shape[j].Longitude - left) / longitudeStep);
                            var localPosY = (int)((top - shape[j].Latitude) / latitudeStep);
                            var dx        = localPosX - posX;
                            var dy        = localPosY - posY;
                            posX = localPosX;
                            posY = localPosY;

                            feature.Geometry.Add(GenerateParameterInteger(dx));
                            feature.Geometry.Add(GenerateParameterInteger(dy));
                        }

                        feature.Type = Tile.GeomType.LineString;

                        if (mapAttributes != null)
                        {
                            IAttributeCollection attributes = new AttributeCollection(edgeProfile.Get(segments[i].Profile));
                            var meta = edgeMeta.Get(segments[i].Meta);
                            foreach (var a in meta)
                            {
                                attributes.AddOrReplace(a);
                            }

                            attributes = mapAttributes(attributes, localLayer);

                            foreach (var attribute in attributes)
                            {
                                uint keyId;
                                if (!keys.TryGetValue(attribute.Key, out keyId))
                                {
                                    keyId = (uint)keys.Count;
                                    keys.Add(attribute.Key, keyId);
                                }
                                uint valueId;
                                if (!values.TryGetValue(attribute.Value, out valueId))
                                {
                                    valueId = (uint)values.Count;
                                    values.Add(attribute.Value, valueId);
                                }
                                feature.Tags.Add(keyId);
                                feature.Tags.Add(valueId);
                            }
                        }
                        else
                        {
                            var profile = edgeProfile.Get(segments[i].Profile);
                            if (profile != null)
                            {
                                foreach (var attribute in profile)
                                {
                                    uint keyId;
                                    if (!keys.TryGetValue(attribute.Key, out keyId))
                                    {
                                        keyId = (uint)keys.Count;
                                        keys.Add(attribute.Key, keyId);
                                    }
                                    uint valueId;
                                    if (!values.TryGetValue(attribute.Value, out valueId))
                                    {
                                        valueId = (uint)values.Count;
                                        values.Add(attribute.Value, valueId);
                                    }
                                    feature.Tags.Add(keyId);
                                    feature.Tags.Add(valueId);
                                }
                            }
                            var meta = edgeMeta.Get(segments[i].Meta);
                            if (meta != null)
                            {
                                foreach (var attribute in meta)
                                {
                                    uint keyId;
                                    if (!keys.TryGetValue(attribute.Key, out keyId))
                                    {
                                        keyId = (uint)keys.Count;
                                        keys.Add(attribute.Key, keyId);
                                    }
                                    uint valueId;
                                    if (!values.TryGetValue(attribute.Value, out valueId))
                                    {
                                        valueId = (uint)values.Count;
                                        values.Add(attribute.Value, valueId);
                                    }
                                    feature.Tags.Add(keyId);
                                    feature.Tags.Add(valueId);
                                }
                            }
                        }

                        layer.Features.Add(feature);
                    }
                }
                else if (localLayer is StopLayer)
                {
                    var pointLayer = localLayer as StopLayer;
                    var points     = pointLayer.Points;
                    var metaIndex  = pointLayer.Meta;

                    for (var i = 0; i < points.Length; i++)
                    {
                        var point = points[i];

                        var feature = new Mapbox.Tile.Feature();

                        var posX = (int)((point.Longitude - left) / longitudeStep);
                        var posY = (int)((top - point.Latitude) / latitudeStep);
                        GenerateMoveTo(feature.Geometry, posX, posY);
                        feature.Type = Tile.GeomType.Point;

                        var attributes = metaIndex.Get(point.MetaId);
                        if (mapAttributes != null)
                        {
                            attributes = mapAttributes(attributes, localLayer);
                        }

                        if (attributes != null)
                        {
                            foreach (var attribute in attributes)
                            {
                                uint keyId;
                                if (!keys.TryGetValue(attribute.Key, out keyId))
                                {
                                    keyId = (uint)keys.Count;
                                    keys.Add(attribute.Key, keyId);
                                }
                                uint valueId;
                                if (!values.TryGetValue(attribute.Value, out valueId))
                                {
                                    valueId = (uint)values.Count;
                                    values.Add(attribute.Value, valueId);
                                }
                                feature.Tags.Add(keyId);
                                feature.Tags.Add(valueId);
                            }
                        }

                        layer.Features.Add(feature);
                    }
                }
                else
                { // unknown type of layer.
                    continue;
                }

                layer.Keys.AddRange(keys.Keys);
                foreach (var value in values.Keys)
                {
                    layer.Values.Add(new Tile.Value()
                    {
                        StringValue = value
                    });
                }
                mapboxTile.Layers.Add(layer);
            }

            ProtoBuf.Serializer.Serialize <Tile>(stream, mapboxTile);
        }
        /// <summary>
        /// Writes the tile to the given stream.
        /// </summary>
        /// <param name="vectorTile">The vector tile.</param>
        /// <param name="stream">The stream to write to.</param>
        /// <param name="extent">The extent.</param>
        /// <param name="idAttributeName">The name of an attribute property to use as the ID for the Feature. Vector tile feature ID's should be integer or ulong numbers.</param>
        public static void Write(this VectorTile vectorTile, Stream stream, uint extent = 4096, string idAttributeName = "id")
        {
            var tile = new Tiles.Tile(vectorTile.TileId);
            var tgt  = new TileGeometryTransform(tile, extent);

            var mapboxTile = new Mapbox.Tile();

            foreach (var localLayer in vectorTile.Layers)
            {
                var layer = new Mapbox.Tile.Layer {
                    Version = 2, Name = localLayer.Name, Extent = extent
                };

                var keys   = new Dictionary <string, uint>();
                var values = new Dictionary <Tile.Value, uint>();

                foreach (var localLayerFeature in localLayer.Features)
                {
                    var feature = new Mapbox.Tile.Feature();

                    // Encode geometry
                    switch (localLayerFeature.Geometry)
                    {
                    case IPuntal puntal:
                        feature.Type = Tile.GeomType.Point;
                        feature.Geometry.AddRange(Encode(puntal, tgt));
                        break;

                    case ILineal lineal:
                        feature.Type = Tile.GeomType.LineString;
                        feature.Geometry.AddRange(Encode(lineal, tgt));
                        break;

                    case IPolygonal polygonal:
                        feature.Type = Tile.GeomType.Polygon;
                        feature.Geometry.AddRange(Encode(polygonal, tgt, tile.Zoom));
                        break;

                    default:
                        feature.Type = Tile.GeomType.Unknown;
                        break;
                    }

                    // If geometry collapsed during encoding, we don't add the feature at all
                    if (feature.Geometry.Count == 0)
                    {
                        continue;
                    }

                    // Translate attributes for feature
                    AddAttributes(feature.Tags, keys, values, localLayerFeature.Attributes);

                    //Try and retrieve an ID from the attributes.
                    var id = localLayerFeature.Attributes.GetOptionalValue(idAttributeName);

                    //Converting ID to string, then trying to parse. This will handle situations will ignore situations where the ID value is not actually an integer or ulong number.
                    if (id != null && ulong.TryParse(id.ToString(), out ulong idVal))
                    {
                        feature.Id = idVal;
                    }

                    // Add feature to layer
                    layer.Features.Add(feature);
                }

                layer.Keys.AddRange(keys.Keys);
                layer.Values.AddRange(values.Keys);

                mapboxTile.Layers.Add(layer);
            }

            ProtoBuf.Serializer.Serialize <Tile>(stream, mapboxTile);
        }