public List <TimeSeriesPoint> GeneratePoints() { var points = new List <TimeSeriesPoint>(); var isXValueSelected = !string.IsNullOrWhiteSpace(Context.WaveFormTextX); var text = isXValueSelected ? Context.WaveFormTextX : Context.WaveFormTextY; text = text.Trim(); if (string.IsNullOrEmpty(text)) { throw new ExpectedException($"You must set the /{nameof(Context.WaveFormTextX)}= or /{nameof(Context.WaveFormTextY)}= to a non-empty value."); } var invalidChars = text .Where(ch => !VectorFont.Symbols.ContainsKey(ch)) .ToList(); if (invalidChars.Any()) { throw new ExpectedException($"Only printable ASCII characters are supported. {"invalid character".ToQuantity(invalidChars.Count)}' detected."); } var x = 0.0; var y = 0.0 + Context.WaveformOffset; foreach (var symbol in text.Select(ch => VectorFont.Symbols[ch])) { var startX = x; var startY = y; foreach (var point in symbol.Lines.SelectMany(RenderLine)) { points.Add(new TimeSeriesPoint { Time = Context.StartTime.PlusTicks(points.Count * Duration.FromTimeSpan(Context.PointInterval).BclCompatibleTicks), Value = isXValueSelected ? startX + Context.WaveformScalar * point.X : startY + Context.WaveformScalar * point.Y, GradeCode = Context.GradeCode, Qualifiers = Context.Qualifiers }); } x += symbol.Width * Context.WaveformScalar; } Log.Info($"Generated {PointSummarizer.Summarize(points, "vector-text point")}. Scatter-plot X vs Y to read the message."); return(points); }
public void AppendPoints() { Log.Info(Context.ExecutingFileVersion); (Points, Notes) = GetPoints(); AdjustNotes(); if (Points.All(p => p.Type != PointType.Gap)) { Points = Points .OrderBy(p => p.Time) .ToList(); } ThrowIfInvalidGapInterval(); AdjustGradesAndQualifiers(Points); if (!string.IsNullOrEmpty(Context.SaveCsvPath)) { new CsvWriter(Context) .WritePoints(Points, Notes); if (Context.StopAfterSavingCsv) { return; } } Log.Info($"Connecting to {Context.Server} ..."); using (var client = CreateConnectedClient()) { Log.Info($"Connected to {Context.Server} ({client.ServerVersion})"); ThrowIfGapsNotSupported(client); if (Context.CreateMode != CreateMode.Never) { new TimeSeriesCreator { Context = Context, Client = client }.CreateMissingTimeSeries(Context.TimeSeries); } var timeSeries = client.GetTimeSeriesInfo(Context.TimeSeries); var isReflected = Context.Command == CommandType.Reflected || timeSeries.TimeSeriesType == TimeSeriesType.Reflected; var hasTimeRange = isReflected || DeleteCommands.Contains(Context.Command) || Context.Command == CommandType.OverwriteAppend; if (hasTimeRange) { var timeRange = GetTimeRange(); if (Notes.Any(note => note.TimeRange != null && (!timeRange.Contains(note.TimeRange.Value.Start) || !timeRange.Contains(note.TimeRange.Value.End)))) { throw new ExpectedException($"All notes to append must be completely within the {timeRange} interval."); } } Log.Info(Context.Command == CommandType.DeleteAllPoints ? $"Deleting all existing points from {timeSeries.Identifier} ({timeSeries.TimeSeriesType}) ..." : hasTimeRange ? $"Appending {PointSummarizer.Summarize(Points)} and {"note".ToQuantity(Notes.Count)} within TimeRange={GetTimeRange()} to {timeSeries.Identifier} ({timeSeries.TimeSeriesType}) ..." : $"Appending {PointSummarizer.Summarize(Points)} and {"note".ToQuantity(Notes.Count)} to {timeSeries.Identifier} ({timeSeries.TimeSeriesType}) ..."); var numberOfPointsAppended = 0; var numberOfPointsDeleted = 0; var numberOfNotesAppended = 0; var numberOfNotesDeleted = 0; var stopwatch = Stopwatch.StartNew(); var pointBatches = GetPointBatches(Points).ToList(); var isBatched = pointBatches.Count > 1; var batchIndex = 1; foreach (var batch in pointBatches) { if (isBatched) { var batchSummary = $"Appending batch #{batchIndex}: {PointSummarizer.Summarize(batch.Points)}"; Log.Info(hasTimeRange ? $"{batchSummary} within TimeRange={batch.TimeRange} ..." : $"{batchSummary} ..."); } var result = AppendPointBatch(client, timeSeries, batch.Points, batch.TimeRange, isReflected, hasTimeRange); numberOfPointsAppended += result.NumberOfPointsAppended; numberOfPointsDeleted += result.NumberOfPointsDeleted; ++batchIndex; if (!ValidStatusCodesByWaitMode[Context.Wait].Contains(result.AppendStatus)) { throw new ExpectedException($"Unexpected append status={result.AppendStatus}"); } } if (DeleteCommands.Contains(Context.Command)) { numberOfNotesDeleted += DeleteNotesWithinTimeRange(client, timeSeries, GetTimeRange()); } else { numberOfNotesAppended += AppendNotes(client, timeSeries); } var batchText = isBatched ? $" using {"append".ToQuantity(pointBatches.Count)}" : ""; var waitText = Context.Wait ? string.Empty : " (without waiting for appends to complete)"; Log.Info($"Appended {"point".ToQuantity(numberOfPointsAppended)} and {"note".ToQuantity(numberOfNotesAppended)} (deleting {"point".ToQuantity(numberOfPointsDeleted)} and {"note".ToQuantity(numberOfNotesDeleted)}) in {stopwatch.ElapsedMilliseconds / 1000.0:F1} seconds{batchText}{waitText}."); } }
public void WritePoints(List <TimeSeriesPoint> points, List <TimeSeriesNote> notes) { var timeSeriesIdentifier = CreateTimeSeriesIdentifier(); var csvPath = Directory.Exists(Context.SaveCsvPath) ? Path.Combine(Context.SaveCsvPath, SanitizeFilename($"{timeSeriesIdentifier.Identifier}.{CreatePeriod(Context.SourceQueryFrom, Context.SourceQueryTo)}.csv")) : Context.SaveCsvPath; Log.Info($"Saving {PointSummarizer.Summarize(points, "extracted point")} to '{csvPath}' ..."); var dir = Path.GetDirectoryName(csvPath); if (!string.IsNullOrEmpty(dir)) { Directory.CreateDirectory(dir); } var publishNotes = notes .Select(Convert) .ToList(); var notesLookup = new MetadataLookup <PublishNote>(publishNotes); using (var writer = new StreamWriter(csvPath)) { var offsetPattern = OffsetPattern.CreateWithInvariantCulture("m"); var utcOffsetText = $"UTC{offsetPattern.Format(Context.UtcOffset ?? Offset.Zero)}"; var period = CreatePeriod(Context.SourceQueryFrom ?? Instant.MinValue, Context.SourceQueryTo ?? Instant.MaxValue); writer.WriteLine($"# {Path.GetFileName(csvPath)} generated by {Context.ExecutingFileVersion}"); writer.WriteLine($"#"); writer.WriteLine($"# Time series identifier: {timeSeriesIdentifier.Identifier}"); writer.WriteLine($"# Location: {timeSeriesIdentifier.LocationIdentifier}"); writer.WriteLine($"# UTC offset: ({utcOffsetText})"); writer.WriteLine($"# Value units: {Context.Unit}"); writer.WriteLine($"# Value parameter: {timeSeriesIdentifier.Parameter}"); writer.WriteLine($"# Interpolation type: {Context.InterpolationType}"); writer.WriteLine($"# Time series type: {Context.TimeSeriesType}"); writer.WriteLine($"#"); writer.WriteLine($"# Export options: Corrected signal from {period.StartText} to {period.EndText}"); writer.WriteLine($"#"); writer.WriteLine($"# CSV data starts at line 15."); writer.WriteLine($"#"); var optionalNotesHeader = Context.SaveNotesMode == SaveNotesMode.WithPoints ? ", Notes" : string.Empty; writer.WriteLine($"ISO 8601 UTC, Value, Grade, Qualifiers{optionalNotesHeader}"); foreach (var point in points) { var time = point.Time ?? Instant.MinValue; var line = $"{InstantPattern.ExtendedIso.Format(time)}, {point.Value:G12}, {point.GradeCode}, {FormatQualifiers(point.Qualifiers)}"; if (Context.SaveNotesMode == SaveNotesMode.WithPoints) { var pointNotes = string.Join("\r\n", notesLookup.GetMany(time.ToDateTimeOffset()).Select(note => note.NoteText)); line += $", {CsvEscapedColumn(pointNotes)}"; } writer.WriteLine(line); } if (Context.SaveNotesMode == SaveNotesMode.SeparateCsv) { var notesCsvPath = Path.ChangeExtension(csvPath, ".Notes.csv"); Log.Info($"Saving {"extracted note".ToQuantity(notes.Count)} to '{notesCsvPath}' ..."); // ReSharper disable once AssignNullToNotNullAttribute using (var notesWriter = new StreamWriter(notesCsvPath)) { notesWriter.WriteLine($"# {Path.GetFileName(notesCsvPath)} generated by {Context.ExecutingFileVersion}"); notesWriter.WriteLine($"#"); notesWriter.WriteLine($"# Time series identifier: {timeSeriesIdentifier.Identifier}"); notesWriter.WriteLine($"# Location: {timeSeriesIdentifier.LocationIdentifier}"); notesWriter.WriteLine($"# UTC offset: ({utcOffsetText})"); notesWriter.WriteLine($"#"); notesWriter.WriteLine($"# Export options: Corrected signal notes from {period.StartText} to {period.EndText}"); notesWriter.WriteLine($"#"); notesWriter.WriteLine($"# CSV data starts at line 11."); notesWriter.WriteLine($"#"); notesWriter.WriteLine($"StartTime, EndTime, NoteText"); foreach (var note in notes) { if (!note.TimeRange.HasValue) { continue; } notesWriter.WriteLine($"{InstantPattern.ExtendedIso.Format(note.TimeRange.Value.Start)}, {InstantPattern.ExtendedIso.Format(note.TimeRange.Value.End)}, {CsvEscapedColumn(note.NoteText)}"); } } } } }