using Nonemm.Core; namespace Nonemm.Spotting; /// Works out where each callsign goes. Two stations a hundred hertz apart would /// have their text on top of each other, so a label that would cover the one /// above is pushed below it and a leader line runs back to the real frequency. /// This is what N1MM does. public static class BandmapLayout { /// N1MM leaves this much between two labels it had to separate. private const double Gap = 0.5; public static IReadOnlyList Place( IEnumerable spots, BandmapScale scale, double labelHeight) { List placed = []; double? above = null; foreach (Spot spot in spots .Where(s => scale.Shows(s.Frequency)) .OrderBy(s => s.Frequency.Hertz)) { double markY = scale.YFor(spot.Frequency); double labelY = markY - (labelHeight / 2); if (above is { } previous && previous + labelHeight > labelY) { labelY = previous + labelHeight + Gap; } placed.Add(new BandmapLabel(spot, markY, labelY)); above = labelY; } return placed; } /// The frequency ticks to draw, every `step` from the first round frequency /// at or below the top of the scale. public static IReadOnlyList Ticks(BandmapScale scale, Frequency step) { List ticks = []; long first = scale.Low.Hertz - (scale.Low.Hertz % step.Hertz); for (long at = first; at <= scale.High.Hertz; at += step.Hertz) { if (at >= scale.Low.Hertz) { ticks.Add(Frequency.FromHertz(at)); } } return ticks; } }