unit FMX.uecOpenWeatherComponent;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes,
  System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Objects,
  System.UIConsts,
  FMX.Layouts, FMX.Controls.Presentation, FMX.StdCtrls,
  FMX.uecMapUtil,  FMX.uecNativeMapControl, FMX.uecNativeShape,FMX.uecopenweather;

type

  

  TOpenWeatherComponent = class(TECCustomTool)
  private
    FOpenWeatherPanel,FQuality: TRectangle;
    FObserver : TNativeMapObserver;

    FWeatherMarker : TECShapeMarker;

    FECAirQuality : TECAirQuality;

    FStation : TOpenWeatherData;

    FShowDescription,
    FisVisible : boolean;

    FMinZoom    : byte;

    FMinWidthCP,
    FMinHeightCP : integer;

    FMinWidth,
    FFontSize   : integer;
    FXYRadius   : integer;
    FBorderSize : integer;

    FWeatherLat,FWeatherLng : double;
    FAirQuality,
    FUrlIcon : string;

    FImage: TImage;

    FDescription,
    FTemperature : TLabel;

    FOnChange,
    FOnClick : TNotifyEvent;

    FWaitWeather : boolean;

    procedure doMapEndMove(Sender : TObject);
    procedure doMapResize(Sender : TObject);
    procedure doMapZoom(Sender : TObject);
    procedure doStartMove(Sender : TObject);
    procedure doMapLoadGraphic(Sender : TObject);

    procedure doOnAirQuality(sender: TObject; const Query, JSon: string) ;

    procedure setMinZoom(const AValue:byte);
   // procedure setBorderSize(const AValue:integer);
    procedure setFontSize(const AValue:integer);
    procedure setXYRadius(const Value: integer);

    function getCityAirQuality : TAirQualityCity;

    procedure UpdateSize;
    procedure doOnChange(sender: TObject);

    procedure doWeather;

    procedure doASyncWeather(Lat,Lng:double);
    procedure doOnWeather;

    procedure UpdateClick;
    procedure UpdateHint(const AHint:string);

    procedure doOnClick(sender: TObject);
    procedure doOnShapeClick(sender: TObject; const item: TECShape);

    procedure setColor(const Value: TAlphaColor);
    function getColor: TAlphaColor;

    procedure setMinHeightCP(value:integer);
    procedure setMinWidthCP(value:integer);


     procedure setShowDescription(Value:boolean);
  public
    constructor Create(AMap: TNativeMapControl;const AName:string=''); override;
    destructor Destroy; override;


   // property BorderSize : integer read FBorderSize write setBorderSize;

    property CityAirQuality : TAirQualityCity read getCityAirQuality;

    property Color: TAlphaColor read getColor write setColor;


    property FontSize : integer read FFontSize write setFontSize;

    property HintAirQuality : string read FAirQuality write FAirQuality;

    property MinZoom : byte read FMinZoom write setMinZoom;

    property MinWidth : integer read FMinWidthCP   write setMinWidthCP;
    property MinHeight: integer read FMinHeightCP  write setMinHeightCP;

    property ShowDescription : boolean read FShowDescription write setShowDescription;
    property Station : TOpenWeatherData read FStation;

    property XYRadius : integer read FXYRadius write setXYRadius;

    property OnClick : TNotifyEvent read FOnClick write  FOnClick;
    property OnChange: TNotifyEvent read FOnChange write FOnChange;

  end;

implementation


procedure TOpenWeatherComponent.setShowDescription(Value:boolean);
begin
  FShowDescription := value;

  if value then
  begin
    if Map.AirQuality.key<>'' then
    begin
     FQuality.Height  := 5;
     FQuality.visible := true;
    end
    else
    begin
     FQuality.Height  := 0;
     FQuality.visible := false;
    end;
    FDescription.Visible := true;
  end
  else
  begin
    FQuality.visible := false;
    FQuality.Height  := 0;
    FDescription.Visible := false;
  end;

  if visible then
  begin
    doWeather;

  end;
    UpdateSize;
end;


procedure TOpenWeatherComponent.setMinHeightCP(value:integer);
begin
  FMinHeightCP := value;
  UpdateSize;
end;

procedure TOpenWeatherComponent.setMinWidthCP(value:integer);
begin
  FMinWidthCP := value;
  UpdateSize;
end;

procedure TOpenWeatherComponent.setColor(const Value: TAlphaColor);
begin
  FOpenWeatherPanel.Fill.Color := value;
end;

function  TOpenWeatherComponent.getColor: TAlphaColor;
begin
  result := FOpenWeatherPanel.Fill.Color;
end;

procedure TOpenWeatherComponent.doOnAirQuality(sender: TObject; const Query, JSon: string) ;
var s : string;
begin
 if FECAirQuality.city.ok then
 begin

  s := FAirQuality;

  if s<>'' then
   s := s+' : ';

  UpdateHint(s+Map.AirQuality.Legend[FECAirQuality.city.Level]);
  FTemperature.TextSettings.FontColor := FECAirQuality.city.LevelColor;
  FQuality.fill.Color :=  FECAirQuality.city.LevelColor;


  if assigned(FOnChange) then
   FOnChange(Self);
 end
 else UpdateHint('');
end;

function TOpenWeatherComponent.getCityAirQuality : TAirQualityCity;
begin
  result := FECAirQuality.city;
end;

procedure TOpenWeatherComponent.UpdateClick;
begin

  FOpenWeatherPanel.onclick   := doOnClick;
  FImage.onclick              := doOnClick;
  FImage.Tag := 1;
  FTemperature.onclick        := doOnClick;
  FTemperature.Tag := 2;
  FWeatherMarker.OnShapeClick := doOnShapeClick;
  FDescription.OnClick        := doOnClick;
  FDescription.Tag := 3;
  FQuality.onclick            := doOnClick;
  FQuality.Tag := 4;

  FOpenWeatherPanel.Cursor := crHandPoint;
  FImage.Cursor            := crHandPoint;
  FTemperature.Cursor      := crHandPoint;
  FQuality.Cursor          := crHandPoint;
  FDescription.Cursor      := crHandPoint;

end;

procedure TOpenWeatherComponent.UpdateHint(const AHint:string);
begin

  FOpenWeatherPanel.Hint := AHint;
  FImage.Hint            := AHint;
  FTemperature.Hint      := AHint;
  FQuality.Hint          := AHint;

end;

procedure TOpenWeatherComponent.doOnShapeClick(sender: TObject; const item: TECShape);
begin
  doOnClick(item);
end;

procedure TOpenWeatherComponent.doOnClick(sender: TObject);
begin
  if assigned(FOnClick) then
   FOnClick(Sender);
end;

procedure TOpenWeatherComponent.setXYRadius(const Value: integer);
begin

  if FXYRadius = value then exit;


  if Value <= 10 then
    FXYRadius := Value
  else
  if FXYRadius=10 then
   exit
  else
    FXYRadius := 10;

  FOpenWeatherPanel.XRadius := value;
  FOpenWeatherPanel.YRadius := value;



end;

procedure TOpenWeatherComponent.UpdateSize;
begin

  FOpenWeatherPanel.Width := FImage.Width {+ 3} + FTemperature.Width + 2*FBorderSize;

  if FOpenWeatherPanel.Width < FMinWidthCP then
     FOpenWeatherPanel.Width := FMinWidthCP;

  if FMinWidth>=FOpenWeatherPanel.width then
  begin
    FOpenWeatherPanel.width := FMinWidth + 2*FBorderSize;
    Align := Align;
  end;

  if FShowDescription then
   FOpenWeatherPanel.Height:= FQuality.Height+ FImage.Height + FDescription.Height - 5*FBorderSize
  else
  FOpenWeatherPanel.Height:= FQuality.Height+ FImage.Height - 8;

  if FOpenWeatherPanel.Height < FMinHeightCP then
     FOpenWeatherPanel.Height := FMinHeightCP;


  FOpenWeatherPanel.padding.left := FBordersize;
  FOpenWeatherPanel.padding.Right := FBordersize;
  FOpenWeatherPanel.padding.top := FBordersize;
  FOpenWeatherPanel.padding.bottom := FBordersize;

  FDescription.margins.left := FBordersize;
  FDescription.margins.Right := FBordersize;



  FImage.position.y   := -FBorderSize;
  FImage.position.X   := 0;
  FTemperature.position.x       := FImage.Width;// + 3;
  FTemperature.position.y        := ((FImage.Height / 2)) - (FTemperature.Height / 2) - 2*FBorderSize;
  FTemperature.width      := FOpenWeatherPanel.Width -  FTemperature.position.x - 2*FBorderSize;
  FDescription.Width      := FOpenWeatherPanel.Width -  2* FBorderSize;


  FQuality.position.y        := FOpenWeatherPanel.Height;

 FOpenWeatherPanel.XRadius := XYRadius;
 FOpenWeatherPanel.YRadius := XYRadius;

end;


(*
procedure TOpenWeatherComponent.setBorderSize(Const AValue:integer);
begin
  if FBorderSize=AValue then exit;

  FBorderSize := AValue;

  UpdateSize;
end;
*)

procedure TOpenWeatherComponent.setFontSize(Const AValue:integer);
begin
  
  FFontSize := AValue;

  FTemperature.TextSettings.Font.Size := FFontSize;
  FDescription.TextSettings.Font.Size := FFontSize;

  FDescription.Height := 3*FDescription.Canvas.TextHeight('W');

  UpdateSize;
end;


// an image has been uploaded, we'll ask again for our weather icon.
procedure TOpenWeatherComponent.doMapLoadGraphic(Sender : TObject);
begin
  FImage.bitmap.Assign(map.getGraphic(FUrlIcon)) ;
end;



procedure TOpenWeatherComponent.doASyncWeather(Lat,Lng:double);
begin
  // prohibit reentrance
  if FWaitWeather then exit;

  TThread.CreateAnonymousThread(
  procedure
    begin
     // only one call
     FWaitWeather := true;
     // Now.Weather is blocking
     map.OpenWeather.Now.Weather(Lat,Lng);
     TThread.Synchronize(nil,
     procedure
     begin
      doOnWeather;
      // ok now ASyncWeather is finish
      FWaitWeather := false;
     end
     )
    end).start;

end;

// call by doASyncWeather with synchronize !
procedure TOpenWeatherComponent.doOnWeather;
var s : string;
    w : integer;
begin

    if map.OpenWeather.Now.Count<0 then exit;

    FStation := map.OpenWeather.Now.Data[0];

    if station.name<>'' then
    begin
      FUrlIcon  := station.weather.filenameicon;

      FWeatherLat := station.coord.Lat;
      FWeatherLng := station.coord.Lng;

      FWeatherMarker.filename := '';
      FWeatherMarker.SetPosition(station.coord.Lat,station.coord.lng)  ;
      FWeatherMarker.filename := FUrlIcon;
      FWeatherMarker.Hint     := station.name;

      if Map.AirQuality.key<>'' then
       FECAirQuality.getJSON(station.coord.Lat,station.coord.lng)
      else
       FQuality.height := 0;

      FWeatherMarker.Visible := true;

      FTemperature.text := doubletostr(round(station.temp))+'°';
      s := station.weather.description;

      FMinWidth := 0;
      (*
       the width of the longest word is calculated, so that the widget size can be adapted.
      *)
      while s<>'' do
      begin
        w := round(FDescription.Canvas.TextWidth(' '+strtoken(s,' ')+' '));
        if FMinWidth < w then
         FMinWidth := w;
      end;

      FDescription.text := station.weather.description;
      (* the image is requested, if not already downloaded, in a secondary thread,
      and the response arrives in doMapLoadGraphic
      *)


      FImage.bitmap.Assign( map.getGraphic(FUrlIcon));
      updateSize;


        if assigned(FOnChange) then
       FOnChange(Self);



    end
    else begin
           FWeatherMarker.Visible := false;
         end;

end;

procedure TOpenWeatherComponent.doWeather;
begin

  if (Map.Zoom>=FMinZoom) and Visible and not Map.isVisible(FWeatherLat,FWeatherLng) then
  begin
    doASyncWeather(Map.latitude,Map.longitude);
   end;

end;




procedure TOpenWeatherComponent.doMapResize(Sender : TObject);
begin
  if visible then
   doWeather;
end;

procedure TOpenWeatherComponent.doStartMove(Sender : TObject);
begin
 Component.onchange := nil;

 if map.Zoom>=FMinZoom then
 begin
  FIsVisible := Visible;
  Visible := false;
 end;
 Component.onchange := doOnChange;

end;

procedure TOpenWeatherComponent.doMapEndMove(Sender : TObject);
begin

 Component.onchange := nil;
 map.BeginUpdate;
   doMapZoom(self);
   doWeather;
 map.EndUpdate;
 Component.onchange := doOnChange;


end;

procedure TOpenWeatherComponent.doOnChange(sender: TObject);
begin
  Component.onchange := nil;
  map.BeginUpdate;
   doWeather;
   updateSize;
   FIsVisible := visible;
   doMapZoom(self);
  map.EndUpdate;
  Component.onchange := doOnChange;
end;




procedure TOpenWeatherComponent.setMinZoom(const AValue:byte);
begin
  FMinZoom           := AValue;
  doMapZoom(Self);
end;

procedure TOpenWeatherComponent.doMapZoom(Sender : TObject);
begin
  Component.onchange := nil;

   if Visible and (Map.Zoom<FMinZoom) then
  begin
    FIsVisible := true;
    FWeatherMarker.Visible := false;
    Visible := false;
  end
  else
  if (Map.Zoom>=FMinZoom) then
  begin
    FWeatherMarker.Visible := FisVisible;
    Visible := FisVisible;
  end;

 Component.onchange := doOnChange;
end;

constructor TOpenWeatherComponent.Create(AMap: TNativeMapControl;const AName:String='');
begin

  inherited;

  FWeatherLat := -777;
  FWeatherLng := -777;

  // automatic calcul
  FMinWidthCP  := 0;
  FMinHeightCP := 0;

  FFontSize   := 8;

  FObserver := TNativeMapObserver.Create;

  FObserver.OnMapEndMove     := doMapEndMove;
  FObserver.OnMapStartMove   := doStartMove;
  FObserver.OnMapzoom        := doMapZoom;

  FObserver.OnMapResize      := doMapResize;
  FObserver.OnMapLoadGraphic := doMapLoadGraphic;

  FECAirQuality := TECAirQuality.create(Map);



  // FOpenWeatherPanel will be the support that determines the total occupancy of our component
  // It will be connected to TECNativeMap

  FOpenWeatherPanel := TRectangle.Create(nil);
  {$IF CompilerVersion >= 27 }
  FOpenWeatherPanel.Stroke.Kind := TBrushKind.None;
  {$ELSE}
  FOpenWeatherPanel.Stroke.Kind := TBrushKind.bkNone;
 {$ENDIF}

  FOpenWeatherPanel.showHint := true;


  FOpenWeatherPanel.Width := 80;



  FImage := TImage.Create(FOpenWeatherPanel);
  FImage.Parent := FOpenWeatherPanel;

  FImage.Width := 48;
  FImage.Height:= 48;


  FTemperature            := TLabel.create(FOpenWeatherPanel);
  FTemperature.Parent     := FOpenWeatherPanel;
  FTemperature.Width      := 32;
  FTemperature.StyledSettings := [];
  FTemperature.TextSettings.Font.Style := [TFontStyle.fsBold];
  {$IF CompilerVersion >= 27 }
  FTemperature.TextSettings.HorzAlign := TTextAlign.Center;
  {$ELSE}
  FTemperature.TextSettings.HorzAlign := TTextAlign.taCenter;
  {$ENDIF}

  (*
  FTemperature.Font.Style := [fsBold];
  FTemperature.Font.Color := clBlack;   *)

  FDescription            := TLabel.create(FOpenWeatherPanel);
  FDescription.Parent     := FOpenWeatherPanel;
  {$IF CompilerVersion >= 27 }
  FDescription.Align       := TAlignLayout.Bottom;
  {$ELSE}
   FDescription.Align := TAlignLayout.alBottom;
 {$ENDIF}
  FDescription.TextSettings.WordWrap   := true;
  FDescription.StyledSettings := [];
  {$IF CompilerVersion >= 27 }
  FDescription.TextSettings.HorzAlign := TTextAlign.Center;
  {$ELSE}
  FDescription.TextSettings.HorzAlign := TTextAlign.taCenter;
  {$ENDIF}

  FDescription.onclick := doOnClick;

  FWeatherMarker := map['OpenWeather'].AddMarker(0,0);
  FWeatherMarker.Visible := false;
  FWeatherMarker.scale := 2;
  FWeatherMarker.Tag := 5;


  FQuality := TRectangle.Create(FOpenWeatherPanel);
  FQuality.Parent     := FOpenWeatherPanel;
  {$IF CompilerVersion >= 27 }
  FQuality.Stroke.Kind := TBrushKind.None;
  FQuality.Align       := TAlignLayout.Bottom;
  {$ELSE}
  FQuality.Stroke.Kind := TBrushKind.bkNone;
  FQuality.Align := TAlignLayout.alBottom;
 {$ENDIF}
  FQuality.showHint := true;

  FQuality.Height := 5;


  add('OpenWeather', FOpenWeatherPanel, ecTopRight);

  FAirQuality := 'Air quality';

  FBorderSize := 3;
  XYRadius    := 5;


  updateClick;

   if assigned(Map) then
  begin
   Map.Attach(FObserver) ;
   FECAirQuality.OnJson := doOnAirQuality;
   FECAirQuality.key    := Map.AirQuality.key;
  end;

  visible := false;

  MinZoom := 13;

  FontSize := 14;

  ShowDescription := false;

  Component.onChange := doOnChange;


end;

destructor TOpenWeatherComponent.Destroy;
begin
  Component.onChange := nil;
  FObserver.OnMapEndMove     := nil;
  FObserver.OnMapStartMove   := nil;
  FObserver.OnMapzoom        := nil;

  FObserver.OnMapResize      := nil;
  FObserver.OnMapLoadGraphic := nil;
  Map.Detach(FObserver);
  FObserver.Free;
  FECAirQuality.Free;
end;



end.
