{$mode objfpc}{$H+}
unit uZoneLabels;

// CC 20260924 formation zone range labels (NM). Each ring's label sits on its
//  own sector spoke so closely spaced rings (e.g. 16/17/18) never overlap, and
//  the text is aligned outward so it clears both the ring and the spoke.

interface

uses
  Classes, Types, Graphics,
  TGClasses, Globe4, TGSysUtils, GlobeUtils, TGPresenters, TGObjects,
  uCICG4Presenters,
  uRangeRings;

const
  // CC 20260924 labels are hidden until a group's smallest ring is at least this
  //  many pixels across on screen, so they don't pile onto the group icon
  ZONE_LABEL_MIN_PX = 24;

  ZONE_LABEL_FONT_PX = 11;
  ZONE_LABEL_OFFSET_PX = 2;

  // CC 20260924 one spoke per ring, clockwise from NE, repeating every 8 rings
  //  (extended rings, decision 7); indexed by ring mod ZONE_LABEL_SPOKES
  ZONE_LABEL_SPOKES = 8;
  ZONE_LABEL_BEARINGS: array[0..ZONE_LABEL_SPOKES-1] of Integer =
    (45, 90, 135, 180, 225, 270, 315, 360);

  // CC 20260924 push each label away from the group centre on its spoke
  ZONE_LABEL_ALIGN: array[0..ZONE_LABEL_SPOKES-1] of TTitleAlignment = (
    taTopRight, taRight, taBottomRight, taBottom, taBottomLeft, taLeft,
    taTopLeft, taTop);

// CC 20260924 on-screen length in pixels of a rangeNM radius from pCenter,
//  or -1 when either end is not on the visible face of the globe
function ZoneRadiusPixels(aGlobe: TGlobe4; const pCenter: TPointLL;
  rangeNM: Integer): Integer;

procedure CreateZoneLabel(aGlobe: TGlobe4; aLayer: TGlobeLayer;
  const pCenter: TPointLL; zoneIdx, rangeNM: Integer;
  var ioNextID: Integer; out presID: Integer; out objID: Integer);

procedure UpdateZoneLabel(aLayer: TGlobeLayer; objID: Integer;
  const pCenter: TPointLL; zoneIdx, rangeNM: Integer);

procedure RemoveZoneLabel(aLayer: TGlobeLayer; presID, objID: Integer);

implementation

uses
  SysUtils;

function LabelPoint(const pCenter: TPointLL; zoneIdx, rangeNM: Integer): TPointLL;
begin
  Result := AngleDistToGCPoint(pCenter,
    DecimalToGlobeUnits(ZONE_LABEL_BEARINGS[zoneIdx mod ZONE_LABEL_SPOKES]),
    GlobeUnitsFrom(rangeNM, NauticalMile));
end;

function ZoneRadiusPixels(aGlobe: TGlobe4; const pCenter: TPointLL;
  rangeNM: Integer): Integer;
var
  lEdge: TPointLL;
  lC, lE: TPoint;
begin
  Result := -1;
  lC := Point(0, 0);
  lE := Point(0, 0);
  lEdge := AngleDistToGCPoint(pCenter, DecimalToGlobeUnits(90),
    GlobeUnitsFrom(rangeNM, NauticalMile));
  if aGlobe.Projection.LLToDeviceXY(pCenter.iLongX, pCenter.iLatY, lC) and
     aGlobe.Projection.LLToDeviceXY(lEdge.iLongX, lEdge.iLatY, lE) then
    Result := Round(Sqrt(Sqr(Double(lE.X - lC.X)) + Sqr(Double(lE.Y - lC.Y))));
end;

procedure CreateZoneLabel(aGlobe: TGlobe4; aLayer: TGlobeLayer;
  const pCenter: TPointLL; zoneIdx, rangeNM: Integer;
  var ioNextID: Integer; out presID: Integer; out objID: Integer);
var
  lPres: TCICG4PointPresenter;
  lDataObj: TGeoDataObject;
begin
  lPres := TCICG4PointPresenter.Create(aGlobe, 0);
  aLayer.Presenters.Add(lPres);
  with lPres do
  begin
    PointType := ppNone;  // text only
    TitleFont.FontColor := FORMATION_ZONE_COLOR;
    TitleFont.FontUnit := Pixel;
    TitleFont.FontSize := ZONE_LABEL_FONT_PX;
    TitleAlignment := ZONE_LABEL_ALIGN[zoneIdx mod ZONE_LABEL_SPOKES];  // CC 20260924
    TitleOffset := ZONE_LABEL_OFFSET_PX;
  end;
  presID := lPres.PresenterID;

  lDataObj := TGeoDataObject.Create(aLayer.Objects);
  lDataObj.Centroid := LabelPoint(pCenter, zoneIdx, rangeNM);
  lDataObj.Title := IntToStr(rangeNM);
  lDataObj.PresenterID := lPres.PresenterID;
  lDataObj.ID := ioNextID;
  objID := ioNextID;
  Inc(ioNextID);
end;

procedure UpdateZoneLabel(aLayer: TGlobeLayer; objID: Integer;
  const pCenter: TPointLL; zoneIdx, rangeNM: Integer);
var
  lObj: TGlobeObject;
begin
  lObj := aLayer.ObjectByID(objID);
  if lObj <> nil then
  begin
    TGeoDataObject(lObj).Centroid := LabelPoint(pCenter, zoneIdx, rangeNM);
    lObj.Title := IntToStr(rangeNM);  // the radius itself can change
  end;
end;

procedure RemoveZoneLabel(aLayer: TGlobeLayer; presID, objID: Integer);
var
  lObj: TGlobeObject;
  lPres: TGlobePresenter;
begin
  lObj := aLayer.ObjectByID(objID);
  if lObj <> nil then
  begin
    aLayer.Objects.Delete(lObj);
    lObj.Free;
  end;
  lPres := aLayer.Presenters.ByID(presID, False);
  if lPres <> nil then
  begin
    aLayer.Presenters.Delete(lPres);
    lPres.Free;
  end;
end;

end.
