private static RectangleShape GetTileSphericalMercatorBoundingBox(long tileX, long tileY, int zoomLevel, int tileSize = 256)
        {
            SphericalMercatorZoomLevelSet zoomLevelSet = new SphericalMercatorZoomLevelSet();
            double         currentScale = GetZoomLevelSetScale(zoomLevelSet, zoomLevel);
            var            tileMatrix   = TileMatrix.GetDefaultMatrix(currentScale, 512, 512, GeographyUnit.Meter);
            RectangleShape bbox         = tileMatrix.GetCell(tileX, tileY).BoundingBox;

            return(new RectangleShape(bbox.UpperLeftPoint.X, bbox.UpperLeftPoint.Y, bbox.LowerRightPoint.X, bbox.LowerRightPoint.Y));
        }
        private async static Task Process(ShapeFileFeatureLayer shapeFile, string targetMbtiles, CancellationToken cancellationToken, int minZoom, int maxZoom, int tileSize, List <string> includedAttributes = null)
        {
            Console.Out.WriteLine("Processing tiles. StartZoom:{0}, EndZoom:{1}", minZoom, maxZoom);

            RectangleShape shapeFileBounds = shapeFile.GetBoundingBox();

            shapeFile.Close();

            ThinkGeoMBTilesLayer.CreateDatabase(targetMbtiles);
            var targetDBConnection = new SqliteConnection($"Data Source={targetMbtiles}");

            targetDBConnection.Open();

            // Meta Table
            var targetMetadata = new MetadataTable(targetDBConnection);

            PointShape centerPoint = shapeFileBounds.GetCenterPoint();
            string     center      = $"{centerPoint.X},{centerPoint.Y},{maxZoom}";
            string     bounds      = $"{shapeFileBounds.UpperLeftPoint.X},{shapeFileBounds.UpperLeftPoint.Y},{shapeFileBounds.LowerRightPoint.X},{shapeFileBounds.LowerRightPoint.Y}";

            List <MetadataEntry> Entries = new List <MetadataEntry>();

            Entries.Add(new MetadataEntry()
            {
                Name = "name", Value = "ThinkGeo World Streets"
            });
            Entries.Add(new MetadataEntry()
            {
                Name = "format", Value = "pbf"
            });
            Entries.Add(new MetadataEntry()
            {
                Name = "bounds", Value = bounds
            });                                                                   //"-96.85310250357627,33.10809235525063,-96.85260897712004,33.107616047247156"
            Entries.Add(new MetadataEntry()
            {
                Name = "center", Value = center
            });                                                                   // "-96.85285574034816,33.1078542012489,14"
            Entries.Add(new MetadataEntry()
            {
                Name = "minzoom", Value = $"{minZoom}"
            });
            Entries.Add(new MetadataEntry()
            {
                Name = "maxzoom", Value = $"{maxZoom}"
            });
            Entries.Add(new MetadataEntry()
            {
                Name = "attribution", Value = "Copyright @2020 ThinkGeo LLC.All rights reserved."
            });
            Entries.Add(new MetadataEntry()
            {
                Name = "description", Value = "ThinkGeo World Street Vector Tile Data in EPSG:3857"
            });
            Entries.Add(new MetadataEntry()
            {
                Name = "version", Value = "2.0"
            });
            Entries.Add(new MetadataEntry()
            {
                Name = "json", Value = ""
            });
            targetMetadata.Insert(Entries);

            // Tile Table
            var targetMap             = new TilesTable(targetDBConnection);
            List <TilesEntry> entries = new List <TilesEntry>();

            string targetFolder = Path.Combine(Path.GetDirectoryName(targetMbtiles), Path.GetFileNameWithoutExtension(targetMbtiles), "-tmp");

            if (!Directory.Exists(targetFolder))
            {
                Directory.CreateDirectory(targetFolder);
            }

            SphericalMercatorZoomLevelSet zoomLevelSet = new SphericalMercatorZoomLevelSet();
            double      currentScale = GetZoomLevelIndex(zoomLevelSet, minZoom);
            var         tileMatrix   = TileMatrix.GetDefaultMatrix(currentScale, tileSize, tileSize, GeographyUnit.Meter);
            var         tileRange    = tileMatrix.GetIntersectingRowColumnRange(shapeFileBounds);
            List <Task> tasks        = new List <Task>();

            for (long tileY = tileRange.MinRowIndex; tileY <= tileRange.MaxRowIndex && !cancellationToken.IsCancellationRequested; ++tileY)
            {
                for (long tileX = tileRange.MinColumnIndex; tileX <= tileRange.MaxColumnIndex && !cancellationToken.IsCancellationRequested; ++tileX)
                {
                    Task task = ProcessTileRecursive(shapeFile, (int)tileY, (int)tileX, minZoom, maxZoom, cancellationToken, targetFolder, includedAttributes);
                    tasks.Add(task);
                }
            }

            foreach (var task in tasks)
            {
                await task;
            }

            await Task.Run(() =>
            {
                long index = 0;

                string[] files = Directory.GetFiles(targetFolder, "*.mvt");
                foreach (string file in files)
                {
                    string fileName     = Path.GetFileNameWithoutExtension(file);
                    string[] NameValues = fileName.Split('_');

                    byte[] bytes = File.ReadAllBytes(file);

                    TilesEntry newEntry = new TilesEntry();
                    int zoomLevel       = int.Parse(NameValues[0]);
                    newEntry.ZoomLevel  = zoomLevel;
                    long row            = long.Parse(NameValues[1]);
                    row = (long)Math.Pow(2, zoomLevel) - row - 1;
                    newEntry.TileRow    = row;
                    newEntry.TileColumn = long.Parse(NameValues[2]);
                    newEntry.TileId     = index++;
                    newEntry.TileData   = bytes;
                    File.Delete(file);

                    entries.Add(newEntry);

                    if (index % 1000 == 0)
                    {
                        targetMap.Insert(entries);
                        entries.Clear();
                        continue;
                    }
                }
                targetMap.Insert(entries);
            });

            targetDBConnection.Close();
        }