// Spludlow Software // Copyright © Samuel P. Ludlow 2020 All Rights Reserved // Distributed under the terms of the GNU General Public License version 3 // Distributed WITHOUT ANY WARRANTY; without implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE // https://www.spludlow.co.uk/LICENCE.TXT // The Spludlow logo is a registered trademark of Samuel P. Ludlow and may not be used without permission // v1.14 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.IO; using System.Data; using System.Drawing; using System.Drawing.Imaging; namespace Spludlow.MetSat { public class AVHRR { private static string[] ColumnLayouts = new string[] { "InId 0 5", "SensDat 6 8", "SensTim 15 6", "SatId 22 6", "Lev 29 3", }; public static void Run() { Run(Spludlow.Config.Get("MetSat.Ready") + @"\AVHRR"); } public static void Run(string directory) { int smallestGapSizeMinutes = 3; // 6; string dataProductId = Path.GetFileName(directory).ToUpper(); string[] filenames = Directory.GetFiles(directory); DataTable table = Spludlow.MetSat.Process.LoadFilenameInfo(ColumnLayouts, filenames); table.Columns.Add("SatIntDesId", typeof(string)); table.Columns.Add("SensingTime", typeof(DateTime)); //table.Columns.Add("Channel", typeof(string)); //table.Columns.Add("Sequence", typeof(string)); table.Columns.Add("Group", typeof(string)); List sats = new List(); foreach (DataRow row in table.Rows) { string satIntDesId = ""; // Spludlow.MetSat.Satellites.SatIntDesId((string)row["SatId"]); row["SatIntDesId"] = satIntDesId; if (sats.Contains(satIntDesId) == false) sats.Add(satIntDesId); row["SensingTime"] = DateTime.ParseExact((string)row["SensDat"] + (string)row["SensTim"], "yyyyMMddHHmmss", null); } sats.Sort(); List groups = new List(); foreach (string satIntDesId in sats) { int series = 0; DateTime lastTime = DateTime.MinValue; string group = null; foreach (DataRow row in table.Select("SatIntDesId = '" + satIntDesId + "'", "SensingTime")) { DateTime thisTime = (DateTime) row["SensingTime"]; if (lastTime != DateTime.MinValue) { double minutes = (thisTime - lastTime).TotalMinutes; // 1 minute gap is normal if (minutes >= smallestGapSizeMinutes) ++series; if (minutes > 1 && minutes < smallestGapSizeMinutes) Spludlow.Log.Warning("AVHRR; Gap between file sensing times looks large, min:" + minutes.ToString()); // warn on large seris also ?? } group = satIntDesId + "-" + series.ToString("0000"); row["Group"] = group; lastTime = thisTime; if (groups.Contains(group) == false) groups.Add(group); } if (group != null) // Don't process last group as may continue into next set { foreach (DataRow row in table.Select("Group = '" + group + "'", "SensingTime")) row["Group"] = null; groups.RemoveAt(groups.Count - 1); } } groups.Sort(); Spludlow.Log.Report("AVHRR" + " " + directory + "; Source Files in Groups", new object[] { table }); Spludlow.MetSat.DataItems dataItems = new Spludlow.MetSat.DataItems(); int count = 0; foreach (string group in groups) { DataRow[] rows = table.Select("Group = '" + group + "'", "SensingTime"); LoadGroup(rows, dataProductId, dataItems); Spludlow.Log.Report("AVHRR" + " " + directory + "; Group:" + group + ", Files:" + rows.Length, new object[] { rows }); ++count; } } private static void LoadGroup(DataRow[] rows, string dataProductId, Spludlow.MetSat.DataItems dataItems) { string satIntDesId = (string)rows[0]["SatIntDesId"]; //string name = Path.GetFileName((string)rows[0]["Filename"]); DateTime startSensingTime = (DateTime)rows[0]["SensingTime"]; // Spludlow.MetSat.Satellites.SatelliteName(satIntDesId) string dataGroupName = "SatId" + " - " + "AVHRR" + " - " + Spludlow.Text.TimeStamp(startSensingTime); Schema.DataItemFilesDataTable filesTable = dataItems.MakeFilesTable(); using (Spludlow.TempDirectory tempDir = new TempDirectory()) { List sourceFilenames = new List(); foreach (DataRow row in rows) sourceFilenames.Add((string)row["Filename"]); byte[][] data = Load(rows); string name; string[] tempStoreName; Schema.DataItemFilesRow fileRow; // SA Source Archive name = dataGroupName + "-SourceArchive"; string tempCopyDirectory = tempDir + @"\" + name; Directory.CreateDirectory(tempCopyDirectory); foreach (string sourceFilename in sourceFilenames) File.Copy(sourceFilename, tempCopyDirectory + @"\" + Path.GetFileName(sourceFilename)); fileRow = dataItems.MakeFileRow(filesTable, "SA", name + ".7z", ""); tempStoreName = ((string)fileRow["Filename"]).Split(new char[] { '@' }); Archive.Create(tempStoreName[0], tempCopyDirectory, true); // CS Combined-Source name = dataGroupName + "-CombinedSource"; string[] combinedFilenames = Spludlow.MetSat.Xrit2Pic.RahAVHRR(sourceFilenames.ToArray(), tempDir.Path); // can produce more that one file if grouped to close if (combinedFilenames.Length != 1) throw new ApplicationException("AVHRR; Xrit2pic.RahAVHRR did not result in one file."); fileRow = dataItems.MakeFileRow(filesTable, "CS", name + ".7z", ""); tempStoreName = ((string)fileRow["Filename"]).Split(new char[] { '@' }); Archive.Create(tempStoreName[0], combinedFilenames[0], true); // Channel Bitmpas name = dataGroupName + "-Channel"; OuputChannels(data, name, dataItems, filesTable); // False Colour name = dataGroupName + "-FalseColour"; OutputColour(data, name, dataItems, filesTable); dataItems.SaveDataItem(filesTable, satIntDesId, dataProductId, startSensingTime, dataGroupName); foreach (string sourceFile in sourceFilenames) File.Delete(sourceFile); } } private static byte[][] Load(DataRow[] rows) { int linesPerFile = 360; int width = 2048; int height = linesPerFile * rows.Length; int channelCount = 5; byte[][] data = new byte[channelCount][]; for (int channel = 0; channel < channelCount; ++channel) data[channel] = new byte[width * height]; for (int index = 0; index < rows.Length; ++index) { DataRow row = rows[index]; string filename = (string)row["Filename"]; int offset = index * (width * linesPerFile); LoadFile(filename, data, offset); } return data; } private static void LoadFile(string filename, byte[][] data, int offset) { using (FileStream stream = new FileStream(filename, FileMode.Open)) { using (BinaryReader reader = new BinaryReader(stream, Encoding.Default)) { byte[] frame = new byte[11090 * 2]; int count = 0; int line = 0; while ((count = reader.Read(frame, 0, frame.Length)) == frame.Length) { ProcessFrame(frame, line, data, offset); ++line; } if (count != 0) throw new ApplicationException("Bad Frame"); } } } private static void ProcessFrame(byte[] frame, int line, byte[][] data, int offset) { //string[] frameSync = new string[] //{ // "1010000100", // "0101101111", // "1101011100", // "0110011101", // "1000001111", // "0010010101", //}; double brightness = 1.0; double maxValue = 1024; double factor = 255 / (maxValue * brightness); //for (int index = 0; index < frameSync.Length; ++index) //{ // if (Convert.ToInt16(frameSync[index], 2) != ReadShort(frame, index + 1)) // throw new ApplicationException("Bad Frame Sync index=" + index.ToString()); //} int channelCount = 5; int lineLength = 2048; // Bit 10; 0=AVHRR Ch3B, 1=AVHRR Ch3A // flag in the telemetry (Word 7, Bit 10) which will indicate which of AVHRR/3 channel 3 sensors (3A or 3B) is operating. for (int x = 0; x < lineLength; ++x) { for (int channel = 0; channel < channelCount; ++channel) { byte[] bitmapData = data[channel]; int dataIndex = (line * lineLength) + x + offset; short sample = ReadShort(frame, 751 + channel + (x * channelCount)); double value = sample; value *= factor; value = Math.Round(value, 0); if (value > 255) value = 255; bitmapData[dataIndex] = (byte)value; } } } private static short ReadShort(byte[] frame, int position) { int index = (position - 1) * 2; byte[] buffer = new byte[2]; buffer[0] = frame[index + 1]; buffer[1] = frame[index]; return BitConverter.ToInt16(buffer, 0); } private static void OuputChannels(byte[][] data, string name, Spludlow.MetSat.DataItems dataItems, Schema.DataItemFilesDataTable filesTable) { for (int channelIndex = 0; channelIndex < data.Length; ++channelIndex) { int height = data[0].Length / 2048; PixelFormat pixelFormat = PixelFormat.Format8bppIndexed; using (Bitmap bitmap = new Bitmap(2048, height, pixelFormat)) { Spludlow.Drawing.Bitmaps.SetPalette(bitmap); byte[] bitmapData = Spludlow.Drawing.Bitmaps.ReadBitmapData(bitmap, pixelFormat); for (int index = 0; index < 2048 * height; ++index) { bitmapData[index] = data[channelIndex][index]; } Spludlow.Drawing.Bitmaps.WriteBitmapData(bitmap, bitmapData, pixelFormat); string channel = (channelIndex + 1).ToString("00"); Schema.DataItemFilesRow fileRow = dataItems.MakeFileRow(filesTable, "CH", name + "-" + channel + ".png", channel); string[] tempStoreName = ((string)fileRow["Filename"]).Split(new char[] { '@' }); bitmap.Save(tempStoreName[0], ImageFormat.Png); fileRow.Size = (int)Spludlow.Io.Files.FileLength(tempStoreName[0]); fileRow.Width = bitmap.Width; fileRow.Height = bitmap.Height; fileRow.Pixels = bitmap.Width * bitmap.Height; } } } private static void OutputColour(byte[][] data, string name, Spludlow.MetSat.DataItems dataItems, Schema.DataItemFilesDataTable filesTable) { int height = data[0].Length / 2048; PixelFormat pixelFormat = PixelFormat.Format24bppRgb; using (Bitmap bitmap = new Bitmap(2048, height, pixelFormat)) { byte[] bitmapData = Spludlow.Drawing.Bitmaps.ReadBitmapData(bitmap, pixelFormat); for (int index = 0; index < 2048 * height; ++index) { byte red = data[0][index]; byte green = data[1][index]; byte blue = data[3][index]; bitmapData[index * 3] = blue; bitmapData[index * 3 + 1] = green; bitmapData[index * 3 + 2] = red; } Spludlow.Drawing.Bitmaps.WriteBitmapData(bitmap, bitmapData, pixelFormat); Schema.DataItemFilesRow fileRow = dataItems.MakeFileRow(filesTable, "FC", name + ".png", ""); string[] tempStoreName = ((string)fileRow["Filename"]).Split(new char[] { '@' }); bitmap.Save(tempStoreName[0], ImageFormat.Png); fileRow.Size = (int)Spludlow.Io.Files.FileLength(tempStoreName[0]); fileRow.Width = bitmap.Width; fileRow.Height = bitmap.Height; fileRow.Pixels = bitmap.Width * bitmap.Height; } } } }