unit uecOpenWeatherComponent;

interface

uses
  Windows, messages, forms, sysutils, Classes, Graphics, Controls, StdCtrls,
  ExtCtrls,Buttons,math,uecMapUtil,
  uecNativeMapControl, uecNativeShape,uecopenweather;

type

  

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

    FShowDescription : boolean;

    FWeatherMarker : TECShapeMarker;

    FECAirQuality : TECAirQuality;

    FStation : TOpenWeatherData;

    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;


    {$IF CompilerVersion >= 27 }
    FWaitWeather : boolean;
    procedure doASyncWeather(Lat,Lng:double);
    procedure doOnWeather;
    {$IFEND} // for Delphi 7


    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 UpdateClick;
    procedure UpdateHint(const AHint:string);

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

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

    procedure setShowDescription(Value:boolean);

  public
    constructor Create(AMap: TNativeMapControl); override;
    destructor Destroy; override;

    property HintAirQuality : string read FAirQuality write FAirQuality;

   // property BorderSize : integer read FBorderSize write setBorderSize;

    property CityAirQuality : TAirQualityCity read getCityAirQuality;

    property FontSize : integer read FFontSize write setFontSize;


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

    property MinZoom : byte read FMinZoom write setMinZoom;


    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;
    UpdateSize;
  end;

end;

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

procedure TOpenWeatherComponent.setMinWidthCP(value:integer);
begin
  FMinWidthCP := value;
  UpdateSize;
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.Font.Color := FECAirQuality.city.LevelColor;
  FQuality.Color :=  FECAirQuality.city.LevelColor;

  // change temp color resize label
  updateSize;

  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;



   RoundCornerOf( FOpenWeatherPanel,XYRadius);


end;

procedure TOpenWeatherComponent.UpdateSize;
begin

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

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

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


  FOpenWeatherPanel.Height:= FBorderSize + FQuality.Height+ FImage.Height   ;

  if FShowDescription then
   FOpenWeatherPanel.Height:= FOpenWeatherPanel.Height + FDescription.Height +  FSepa.Height;


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

  {$IF CompilerVersion >= 21 }
  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.Top   := 0;//FBorderSize;
  FImage.Left  :=0;
  FTemperature.left       := FImage.Width + 3;
  FTemperature.Top        := ((FImage.Height div 2)) - (FTemperature.Height div 2) - 3;
  FTemperature.width      := FOpenWeatherPanel.Width -  FTemperature.left - 2*FBorderSize;
  FDescription.Width      := FOpenWeatherPanel.Width -  2* FBorderSize;
  FSepa.Height            := 0;
  {$ELSE}
    FImage.Top   := FBorderSize;
    FImage.Left  := FBorderSize;


    FTemperature.left       := FBorderSize + FImage.Width + 3;
    FTemperature.Top        := (FBorderSize + (FImage.Height div 2)) - (FTemperature.Height div 2);
    FTemperature.width      := FOpenWeatherPanel.Width-FTemperature.left - FBorderSize;;
    FSepa.Height            := FBorderSize;
    FOpenWeatherPanel.Width := FOpenWeatherPanel.Width + 3*FBordersize;
  {$IFEND}


 // FDescription.Top        := FImage.top+FImage.Height;


  FQuality.Top        := FOpenWeatherPanel.Height;
  Fsepa.Top        := FOpenWeatherPanel.Height;

  RoundCornerOf( FOpenWeatherPanel,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
  if FFontSize=AValue then exit;

  FFontSize := AValue;

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

  UpdateSize;
end;


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

{$IF CompilerVersion >= 27 }

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;
      FWeatherMarker.Description := station.name;

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

      FWeatherMarker.Visible := true;

      FTemperature.Caption := 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 := FDescription.Canvas.TextWidth(' '+strtoken(s,' ')+' ');
        if FMinWidth < w then
         FMinWidth := w;
      end;

      FDescription.Caption := station.weather.description;

      (* the image is requested, if not already downloaded, in a secondary thread,
      and the response arrives in doMapLoadGraphic
      *)
      FImage.Picture.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;
{$ELSE}

(*
  If the component is active and the previous weather station is no longer on the map,
  the weather of the station closest to the center of the map is requested.
*)
procedure TOpenWeatherComponent.doWeather;
var s : string;
    w : integer;
begin
if (map.Zoom>=FMinZoom) and Visible and not Map.isVisible(FWeatherLat,FWeatherLng) then
   begin
    FStation := map.OpenWeather.Now.Weather(Map.latitude,Map.longitude);
    if station.name<>'' then
    begin
      FUrlIcon  := station.weather.filenameicon;

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

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

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

      FWeatherMarker.Visible := true;

      FTemperature.Caption := 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 := FDescription.Canvas.TextWidth(' '+strtoken(s,' ')+' ');
        if FMinWidth < w then
         FMinWidth := w;
      end;

      FDescription.Caption := station.weather.description;

      (* the image is requested, if not already downloaded, in a secondary thread,
      and the response arrives in doMapLoadGraphic
      *)
      FImage.Picture.Assign( map.getGraphic(FUrlIcon));
      updateSize;

      if assigned(FOnChange) then
       FOnChange(Self);

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

   end
   ;
end;
{$IFEND} // for Delphi 7

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);
begin

  inherited;

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

  FFontSize   := 8;

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

  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 := TPanel.Create(nil);
  FOpenWeatherPanel.BevelOuter := bvNone;
  FOpenWeatherPanel.ParentBackground := false;
  FOpenWeatherPanel.Color := clBtnFace;
  FOpenWeatherPanel.showHint := true;

{$IF CompilerVersion >= 21 }
  FOpenWeatherPanel.AlignWithMargins := true;
{$IFEND}
  FOpenWeatherPanel.Width := 60;


  FSepa := TPanel.Create(FOpenWeatherPanel);
  FSepa.Parent := FOpenWeatherPanel;
  FSepa.BevelOuter := bvNone;
  FSepa.Align      := alBottom;
  FSepa.Height     := 0;
  FSepa.Visible := false;


  FImage := TImage.Create(FOpenWeatherPanel);
  FImage.Parent := FOpenWeatherPanel;
  FImage.Proportional := true;
{$IF CompilerVersion >= 21 }
  FImage.AlignWithMargins := true;
{$IFEND}
  FImage.Width := 38;
  FImage.Height:= 38;


  FTemperature            := TLabel.create(FOpenWeatherPanel);
  FTemperature.Parent     := FOpenWeatherPanel;
  FTemperature.Alignment  := taCenter;

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

  FDescription            := TLabel.create(FOpenWeatherPanel);
  FDescription.Parent     := FOpenWeatherPanel;
  FDescription.Align      := alBottom;
  FDescription.Alignment  := taCenter;
  FDescription.WordWrap   := true;
  {$IF CompilerVersion >= 21 }
  FDescription.AlignWithMargins := true;
  {$IFEND}
  FDescription.onclick := doOnClick;

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


  FQuality := TPanel.Create(FOpenWeatherPanel);
  FQuality.Parent     := FOpenWeatherPanel;
  FQuality.BevelOuter := bvNone;
  FQuality.ParentBackground := false;
  FQuality.Align      := alBottom;
  FQuality.showHint := true;


{$IF CompilerVersion >= 21 }
  FQuality.AlignWithMargins := true;
{$IFEND}

  add('OpenWeather', FOpenWeatherPanel, ecTopRight);

  FAirQuality := 'Air quality';

  FBorderSize := 0;
  XYRadius    := 5;
  FontSize    := 10;

  updateClick;

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

  visible := false;

  MinZoom := 13;

  ShowDescription := false;

  Component.onChange := doOnChange;


end;

destructor TOpenWeatherComponent.Destroy;
begin
  Map.Detach(FObserver);
  FObserver.Free;
  FECAirQuality.Free;
end;



end.
