public List <List <Point2d <T> > > Geometry <T>(
            uint?clipBuffer = null
            , float?scale   = null
            )
        {
            // parameters passed to this method override parameters passed to the constructor
            if (_clipBuffer.HasValue && !clipBuffer.HasValue)
            {
                clipBuffer = _clipBuffer;
            }
            if (_scale.HasValue && !scale.HasValue)
            {
                scale = _scale;
            }

            // TODO: how to cache 'finalGeom' without making whole class generic???
            // and without using an object (boxing) ???
            List <List <Point2d <T> > > finalGeom = _cachedGeometry as List <List <Point2d <T> > >;

            if (null != finalGeom && scale == _previousScale)
            {
                return(finalGeom);
            }

            //decode commands and coordinates
            List <List <Point2d <long> > > geom = DecodeGeometry.GetGeometry(
                _layer.Extent
                , GeometryType
                , GeometryCommands
                , scale.Value
                );

            if (clipBuffer.HasValue)
            {
                geom = UtilGeom.ClipGeometries(geom, GeometryType, (long)_layer.Extent, clipBuffer.Value, scale.Value);
            }

            //HACK: use 'Scale' to convert to <T> too
            finalGeom = DecodeGeometry.Scale <T>(geom, scale.Value);

            //set field needed for next iteration
            _previousScale  = scale;
            _cachedGeometry = finalGeom;

            return(finalGeom);
        }
        /// <summary>
        /// Get a feature of the <see cref="VectorTileLayer"/>
        /// </summary>
        /// <param name="layer"><see cref="VectorTileLayer"/> containing the feature</param>
        /// <param name="data">Raw byte data of the feature</param>
        /// <param name="validate">If true, run checks if the tile contains valid data. Decreases decoding speed.</param>
        /// <param name="clippBuffer">
        /// <para>'null': returns the geometries unaltered as they are in the vector tile. </para>
        /// <para>Any value >=0 clips a border with the size around the tile. </para>
        /// <para>These are not pixels but the same units as the 'extent' of the layer. </para>
        /// </param>
        /// <returns></returns>
        public static VectorTileFeature GetFeature(
            VectorTileLayer layer
            , byte[] data
            , bool validate    = true
            , uint?clippBuffer = null
            )
        {
            PbfReader         featureReader = new PbfReader(data);
            VectorTileFeature feat          = new VectorTileFeature(layer);
            bool geomTypeSet = false;

            while (featureReader.NextByte())
            {
                int featureType = featureReader.Tag;
                if (validate)
                {
                    if (!ConstantsAsDictionary.FeatureType.ContainsKey(featureType))
                    {
                        throw new System.Exception(string.Format("Layer [{0}] has unknown feature type: {1}", layer.Name, featureType));
                    }
                }
                switch ((FeatureType)featureType)
                {
                case FeatureType.Id:
                    feat.Id = (ulong)featureReader.Varint();
                    break;

                case FeatureType.Tags:
#if NET20
                    List <int> tags = featureReader.GetPackedUnit32().ConvertAll <int>(ui => (int)ui);
#else
                    List <int> tags = featureReader.GetPackedUnit32().Select(t => (int)t).ToList();
#endif
                    feat.Tags = tags;
                    break;

                case FeatureType.Type:
                    int geomType = (int)featureReader.Varint();
                    if (validate)
                    {
                        if (!ConstantsAsDictionary.GeomType.ContainsKey(geomType))
                        {
                            throw new System.Exception(string.Format("Layer [{0}] has unknown geometry type tag: {1}", layer.Name, geomType));
                        }
                    }
                    feat.GeometryType = (GeomType)geomType;
                    geomTypeSet       = true;
                    break;

                case FeatureType.Geometry:
                    if (null != feat.Geometry)
                    {
                        throw new System.Exception(string.Format("Layer [{0}], feature already has a geometry", layer.Name));
                    }
                    //get raw array of commands and coordinates
                    List <uint> geometryCommands = featureReader.GetPackedUnit32();
                    //decode commands and coordinates
                    List <List <Point2d> > geom = DecodeGeometry.GetGeometry(
                        layer.Extent
                        , feat.GeometryType
                        , geometryCommands
                        );
                    if (clippBuffer.HasValue)
                    {
                        geom = clipGeometries(geom, feat.GeometryType, (long)layer.Extent, clippBuffer.Value);
                    }
                    feat.Geometry = geom;
                    break;

                default:
                    featureReader.Skip();
                    break;
                }
            }

            if (validate)
            {
                if (!geomTypeSet)
                {
                    throw new System.Exception(string.Format("Layer [{0}]: feature missing geometry type", layer.Name));
                }
                if (null == feat.Geometry)
                {
                    throw new System.Exception(string.Format("Layer [{0}]: feature has no geometry", layer.Name));
                }
                if (0 != feat.Tags.Count % 2)
                {
                    throw new System.Exception(string.Format("Layer [{0}]: uneven number of feature tag ids", layer.Name));
                }
                if (feat.Tags.Count > 0)
                {
#if NET20
                    int maxKeyIndex = -9999;
                    for (int i = 0; i < feat.Tags.Count; i += 2)
                    {
                        if (feat.Tags[i] > maxKeyIndex)
                        {
                            maxKeyIndex = feat.Tags[i];
                        }
                    }
                    int maxValueIndex = -9999;
                    for (int i = 1; i < feat.Tags.Count; i += 2)
                    {
                        if (feat.Tags[i] > maxValueIndex)
                        {
                            maxValueIndex = feat.Tags[i];
                        }
                    }
#else
                    int maxKeyIndex   = feat.Tags.Where((key, idx) => idx % 2 == 0).Max();
                    int maxValueIndex = feat.Tags.Where((key, idx) => (idx + 1) % 2 == 0).Max();
#endif
                    if (maxKeyIndex >= layer.Keys.Count)
                    {
                        throw new System.Exception(string.Format("Layer [{0}]: maximum key index equal or greater number of key elements", layer.Name));
                    }
                    if (maxValueIndex >= layer.Values.Count)
                    {
                        throw new System.Exception(string.Format("Layer [{0}]: maximum value index equal or greater number of value elements", layer.Name));
                    }
                }
            }

            return(feat);
        }
        public List <List <Vector2 <T> > > Geometry <T>(
            uint?clipBuffer = null
            , float?scale   = null
            )
        {
            // parameters passed to this method override parameters passed to the constructor
            if (_clipBuffer.HasValue && !clipBuffer.HasValue)
            {
                clipBuffer = _clipBuffer;
            }

            if (_scale.HasValue && !scale.HasValue)
            {
                scale = _scale;
            }

            // TODO: how to cache 'finalGeom' without making whole class generic???
            // and without using an object (boxing) ???
            var finalGeom = _cachedGeometry as List <List <Vector2 <T> > >;

            if (null != finalGeom && scale == _previousScale)
            {
                return(finalGeom);
            }

            //decode commands and coordinates
            List <List <Vector2 <long> > > geom = DecodeGeometry.GetGeometry(
                Layer.Extent
                , GeometryType
                , GeometryCommands
                , scale.Value
                );

            if (clipBuffer.HasValue)
            {
                // HACK !!!
                // work around a 'feature' of clipper where the ring order gets mixed up
                // with multipolygons containing holes
                if (geom.Count < 2 || GeometryType != GeomType.POLYGON)
                {
                    // work on points, lines and single part polygons as before
                    geom = UtilGeom.ClipGeometries(geom, GeometryType, (long)Layer.Extent, clipBuffer.Value,
                                                   scale.Value);
                }
                else
                {
                    // process every ring of a polygon in a separate loop
                    var newGeom   = new List <List <Vector2 <long> > >();
                    int geomCount = geom.Count;
                    for (int i = 0; i < geomCount; i++)
                    {
                        List <Vector2 <long> > part = geom[i];
                        var tmp = new List <List <Vector2 <long> > >();
                        // flip order of inner rings to look like outer rings
                        bool isInner = signedPolygonArea(part) >= 0;
                        if (isInner)
                        {
                            part.Reverse();
                        }

                        tmp.Add(part);
                        tmp = UtilGeom.ClipGeometries(tmp, GeometryType, (long)Layer.Extent, clipBuffer.Value,
                                                      scale.Value);
                        // ring was completely outside of clip border
                        if (0 == tmp.Count)
                        {
                            continue;
                        }

                        // one part might result in several geoms after clipping, eg 'u'-shape where the
                        // lower part is completely beyond the actual tile border but still within the buffer
                        foreach (List <Vector2 <long> > item in tmp)
                        {
                            // flip winding order of inner rings back
                            if (isInner)
                            {
                                item.Reverse();
                            }

                            newGeom.Add(item);
                        }
                    }

                    geom = newGeom;
                }
            }

            //HACK: use 'Scale' to convert to <T> too
            finalGeom = DecodeGeometry.Scale <T>(geom, scale.Value);

            //set field needed for next iteration
            _previousScale  = scale;
            _cachedGeometry = finalGeom;

            return(finalGeom);
        }