public float[][] ReadRegionAcrossFiles(int xOffset, int yOffset, int xSize, int ySize)
        {
            int nPixPerTile = xSize * ySize;

            float[][] tileData = new float[m_FileDates.Count][];
            int       fileNum  = 0;

            foreach (var t in m_FileDates)
            {
                var newshape = GDAL_Operations.GetRasterShape(t.Value);
                if (newshape.Item1 != Shape.Item1 || newshape.Item2 != Shape.Item2)
                {
                    throw new ArgumentException("Raster shapes don't match");
                }
                var newGT = GDAL_Operations.GetGeoTransform(t.Value);
                if (!GeoTransform.SequenceEqual(newGT))
                {
                    throw new ArgumentException("Raster geotransforms don't match");
                }
                var newNDV = GDAL_Operations.GetNoDataValue(t.Value);
                if (newNDV != NoDataValue)
                {
                    throw new ArgumentException("Raster nodata values don't match");
                }
                var newProj = GDAL_Operations.GetProjection(t.Value);
                if (newProj != Projection)
                {
                    throw new ArgumentException("Raster projections don't match");
                }
                tileData[fileNum] = GDAL_Operations.ReadGDALRasterBandsToFlatArray(
                    t.Value, xSize, ySize, xOffset, yOffset, 1);
                fileNum += 1;
            }
            return(tileData);
        }
        /// <summary>
        /// Parse key information about the first raster: the geotransform, shape (n pixels X and Y), and
        /// nodata value (of the first band). Also calculate the latitude of each row and the longitude of
        /// each column.
        /// </summary>
        private void PopulateRasterProperties()
        {
            string firstFilename = m_FileDates.First().Value;

            GeoTransform = GDAL_Operations.GetGeoTransform(firstFilename);
            Shape        = GDAL_Operations.GetRasterShape(firstFilename);
            NoDataValue  = GDAL_Operations.GetNoDataValue(firstFilename);
            Projection   = GDAL_Operations.GetProjection(firstFilename);
            double[] latcoords = new double[Shape.Item1];
            double[] loncoords = new double[Shape.Item2];
            double   originX   = GeoTransform[0];
            double   cellsizeX = GeoTransform[1];
            double   originY   = GeoTransform[3];
            double   cellsizeY = GeoTransform[5];

            for (int i = 0; i < Shape.Item1; i++)
            {
                latcoords[i] = originY + i * cellsizeY;
            }
            for (int i = 0; i < Shape.Item2; i++)
            {
                loncoords[i] = originX + i * cellsizeX;
            }
            GlobalLatCoords = latcoords;
            GlobalLonCoords = loncoords;
        }
        static float[] TestReadTileAcrossTime(int column, int row, int xSize = 512, int ySize = 512)
        {
            int xOff = column * xSize;
            int yOff = row * ySize;

            string fileWildCard = "F:\\MOD11A2_Gapfilled_Output\\LST_Day\\Output_Final_30k_2030pc\\*Data.tif";
            IFilenameDateParser modisFileParse = new FilenameDateParser_MODIS8DayRaw();
            var    details       = GetFilenamesAndDates(fileWildCard, modisFileParse);
            string firstFileName = details[0].Item1;

            double[] overallGT = GDAL_Operations.GetGeoTransform(firstFileName);
            var      shape     = GDAL_Operations.GetRasterShape(firstFileName);

            if (yOff > shape.Item1 || xOff > shape.Item2)
            {
                throw new ArgumentException("you specified a column or row greater than the number of tiles available");
            }
            if (yOff + ySize > shape.Item1)
            {
                ySize = shape.Item1 - yOff;
            }
            if (xOff + xSize > shape.Item2)
            {
                xSize = shape.Item2 - xOff;
            }
            int nPix = xSize * ySize;

            float[] tileData = new float[nPix * details.Count];

            for (int t = 0; t < details.Count; t++)
            {
                int      pxStart  = nPix * t;
                string   filename = details[t].Item1;
                DateTime filedate = details[t].Item2;
                var      newshape = GDAL_Operations.GetRasterShape(filename);
                if (newshape.Item1 != shape.Item1 || newshape.Item2 != shape.Item2)
                {
                    throw new ArgumentException("Raster shapes don't match");
                }
                var bandarr = GDAL_Operations.ReadGDALRasterBandsToFlatArray(filename, xSize, ySize, xOff, yOff, 1);
                Array.Copy(bandarr, 0, tileData, pxStart, bandarr.Length);
                // tileData[pxStart : pxStart+nPix] = arr;
            }
            return(tileData);
        }
        // see also https://www.codeproject.com/Articles/14465/Specify-a-Configuration-File-at-Runtime-for-a-C-Co


        private TSModelRunner(IFilenameDateParser _parser, TSIModelConfig cfg)
        {
            _fnParser = _parser;
            //var set = Properties.Settings.Default;
            //System.Console.WriteLine("Looking for files in " + m_FileWildCard);
            //var d = new System.Diagnostics.DefaultTraceListener();

            _maxReader = new TiffCubeReader(cfg.dataPathConfig.MaxTempFiles, _fnParser,
                                            cfg.modelRunConfig.ReadFromDate, cfg.modelRunConfig.ReadToDate);
            var nMax = _maxReader.Filenames.Count;

            Console.WriteLine("Looking for max temp files in " + cfg.dataPathConfig.MaxTempFiles +
                              " - found " + nMax.ToString());
            _minReader = new TiffCubeReader(cfg.dataPathConfig.MinTempFiles, _fnParser,
                                            cfg.modelRunConfig.ReadFromDate, cfg.modelRunConfig.ReadToDate);
            var nMin = _minReader.Filenames.Count;

            Console.WriteLine("Looking for min temp files in " + cfg.dataPathConfig.MinTempFiles +
                              " - found " + nMin.ToString());

            if (nMax == 0 || nMin == 0)
            {
                throw new ArgumentException("Can't continue without data");
                // I don't see any reason not to continue if the numbers for day and night aren't actually equal
            }
            if (!_maxReader.GeoTransform.SequenceEqual(_minReader.GeoTransform))
            {
                throw new ArgumentException("max and min temp image transforms do not match!");
            }
            if (!GDAL_Operations.GetGeoTransform(cfg.dataPathConfig.MaskFile).SequenceEqual(_maxReader.GeoTransform))
            {
                throw new ArgumentException("Land-sea mask doesn't match images!");
            }
            if (!System.IO.Directory.Exists(cfg.dataPathConfig.OutputFolder))
            {
                System.IO.Directory.CreateDirectory(cfg.dataPathConfig.OutputFolder);
            }
            _cfg = cfg;
        }
        public float[][] ReadRegionAcrossFiles_MP(int xOffset, int yOffset, int xSize, int ySize)
        {
            int nPixPerTile = xSize * ySize;

            float[][]       tileData = new float[m_FileDates.Count][];
            ParallelOptions b        = new ParallelOptions();

            b.MaxDegreeOfParallelism = 6;
            var keys = m_FileDates.Keys;

            Parallel.For(0, keys.Count, b, c =>
            {
                var fDate    = keys[c];
                var fName    = m_FileDates[fDate];
                var newshape = GDAL_Operations.GetRasterShape(fName);
                if (newshape.Item1 != Shape.Item1 || newshape.Item2 != Shape.Item2)
                {
                    throw new ArgumentException("Raster shapes don't match");
                }
                var newGT = GDAL_Operations.GetGeoTransform(fName);
                if (!GeoTransform.SequenceEqual(newGT))
                {
                    throw new ArgumentException("Raster geotransforms don't match");
                }
                var newNDV = GDAL_Operations.GetNoDataValue(fName);
                if (newNDV != NoDataValue)
                {
                    throw new ArgumentException("Raster nodata values don't match");
                }
                var newProj = GDAL_Operations.GetProjection(fName);
                if (newProj != Projection)
                {
                    throw new ArgumentException("Raster projections don't match");
                }
                tileData[c] = GDAL_Operations.ReadGDALRasterBandsToFlatArray(
                    fName, xSize, ySize, xOffset, yOffset, 1);
            });
            return(tileData);
        }