unit View.Map;

interface

uses
  System.SysUtils, System.Classes, WEBLib.Graphics, WEBLib.Forms, WEBLib.Dialogs,
  Vcl.Controls, Vcl.StdCtrls, WEBLib.StdCtrls, WEBLib.Controls, WEBLib.Grids,
  WEBLib.ExtCtrls, DB, WEBLib.WebCtrls, WEBLib.REST, VCL.TMSFNCTypes, VCL.TMSFNCUtils,
  VCL.TMSFNCGraphics, VCL.TMSFNCGraphicsTypes, VCL.TMSFNCCustomControl, VCL.TMSFNCWebBrowser,
  VCL.TMSFNCMaps, VCL.TMSFNCLeaflet, VCL.TMSFNCMapsCommonTypes, System.StrUtils, XData.Web.Client,
  XData.Web.Connection, ConnectionModule, Utils;

type
  TFViewMap = class(TWebForm)
    btnMenu: TWebButton;
    btnAlerts: TWebButton;
    btnGroups: TWebButton;
    btnLocate: TWebButton;
    btnFilters: TWebButton;
    btnDisplay: TWebButton;
    pnlMap: TWebPanel;
    lfMap: TTMSFNCLeaflet;
    httpReqGeoJson: TWebHttpRequest;
    xdwcMap: TXDataWebClient;
    tmrRefresh: TWebTimer;

    procedure lfMapMapInitialized(Sender: TObject);
    [async] procedure httpReqGeoJsonResponse(Sender: TObject; AResponse: string);
    procedure lfMapPolyElementMouseEnter(Sender: TObject; AElement: TTMSFNCMapsPolyElement);
    procedure lfMapPolyElementMouseLeave(Sender: TObject; AElement: TTMSFNCMapsPolyElement);
    procedure lfMapCustomizeMarker(Sender: TObject;
      var ACustomizeMarker: string);
    procedure lfMapCustomizeCSS(Sender: TObject; var ACustomizeCSS: string);
  private
    FUnitsLoaded: Boolean;
    FComplaintsLoaded: Boolean;
    [async] procedure LoadPointsAsync;
    function CarIconForDistrict(const DistrictCode: string): string;
  public
  end;

var
  FViewMap: TFViewMap;

implementation

uses
  JS, Web;

{$R *.dfm}

procedure TFViewMap.lfMapMapInitialized(Sender: TObject);
begin
  ShowSpinner('spinner');
  FUnitsLoaded := False;
  FComplaintsLoaded := False;
  httpReqGeoJson.Execute;
end;


[async] procedure TFViewMap.httpReqGeoJsonResponse(Sender: TObject; AResponse: string);
var
  i: Integer;
  P: TTMSFNCMapsPolygon;
  nm: string;
begin
  lfMap.BeginUpdate;
  try
    lfMap.Polygons.Clear;

    Console.Log('GeoJSON len=' + AResponse.Length.ToString);
    lfMap.LoadGeoJSONFromText(AResponse, True, False);
    Console.Log('Loaded polygons count=' + lfMap.Polygons.Count.ToString);

    for i := 0 to lfMap.Polygons.Count - 1 do
    begin
      P := lfMap.Polygons[i];

      case i of
        0: begin
             P.DisplayName := 'District A';
             P.FillColor   := HTMLToColor('#d3ffbe'); // light green
             P.StrokeColor := HTMLToColor('#6ea85c'); // darker green
           end;
        1: begin
             P.DisplayName := 'District B';
             P.FillColor   := HTMLToColor('#ffbebe'); // light red
             P.StrokeColor := HTMLToColor('#b34a4a'); // darker red
           end;
        2: begin
             P.DisplayName := 'District C';
             P.FillColor   := HTMLToColor('#ffd37f'); // light orange
             P.StrokeColor := HTMLToColor('#b36e00'); // darker orange
           end;
        3: begin
             P.DisplayName := 'District D';
             P.FillColor   := HTMLToColor('#bed2ff'); // light blue
             P.StrokeColor := HTMLToColor('#3c5ca8'); // darker blue
           end;
        4: begin
             P.DisplayName := 'District E';
             P.FillColor   := HTMLToColor('#ffffbe'); // light yellow
             P.StrokeColor := HTMLToColor('#a8a85c'); // darker yellow/olive
           end;
      end;

      P.FillOpacity := 0.60;
      P.StrokeOpacity := 1.0;
      P.StrokeWidth := 2;

    end;

    if lfMap.Polygons.Count > 0 then
      lfMap.ZoomToBounds(lfMap.Polygons.ToCoordinateArray);
  finally
    lfMap.EndUpdate;
  end;

  await(LoadPointsAsync);
end;

function TFViewMap.CarIconForDistrict(const DistrictCode: string): string;
var
  U: string;
  L: Char;
begin
  U := UpperCase(Trim(DistrictCode));
  if U = '' then
    Exit('assets/markers/car_X.png');

  L := U[1];
  case L of
    'A','B','C','D','E','X':
      Result := 'assets/markers/car_' + L + '.png';
  else
      Result := 'assets/markers/default.png';
  end;
end;


[async] procedure TFViewMap.LoadPointsAsync;
var
  resp: TXDataClientResponse;
  root, item: TJSObject;
  data: TJSArray;
  i: Integer;
  m: TTMSFNCMapsMarker;
  lat, lng: Double;
  uname, dist: string;
  complaintId, codeDesc, dispatchDist: string;
  pngName, iconUrl: string;
begin
  ShowSpinner('spinner');
  FUnitsLoaded := False;
  FComplaintsLoaded := False;

  try
    resp := await(xdwcMap.RawInvokeAsync('IApiService.GetUnitMap', []));
    root := TJSObject(resp.Result);
    data := TJSArray(root['data']);
    if data <> nil then
    begin
      lfMap.BeginUpdate;
      try
        for i := 0 to data.Length - 1 do
        begin
          item := TJSObject(data[i]);
          lat := Double(item['Lat']);
          lng := Double(item['Lng']);
          uname := string(item['UnitName']);
          dist := string(item['District']);

          m := lfMap.Markers.Add;
          m.Latitude := lat;
          m.Longitude := lng;
          m.Title := uname + IfThen(dist <> '', ' / ' + dist, '');
          m.DataString := 'unit';
          m.IconURL := CarIconForDistrict(dist);

        end;
      finally
        lfMap.EndUpdate;
      end;
    end;
    FUnitsLoaded := True;
  except
    on E: EXDataClientRequestException do
      Console.Log('Units XData error: ' + E.ErrorResult.ErrorMessage);
  end;

  try
    resp := await(xdwcMap.RawInvokeAsync('IApiService.GetComplaintMap', []));
    root := TJSObject(resp.Result);
    data := TJSArray(root['data']);
    if data <> nil then
    begin
      lfMap.BeginUpdate;
      try
        for i := 0 to data.Length - 1 do
        begin
          item        := TJSObject(data[i]);
          complaintId := string(item['ComplaintId']);
          codeDesc := string(item['DispatchCodeDesc']);
          dispatchDist := string(item['DispatchDistrict']);
          lat := Double(item['Lat']);
          lng := Double(item['Lng']);

          if ((lat = 0) and (lng = 0)) or (Abs(lat) > 90) or (Abs(lng) > 180) then
           Continue;

          pngName := string(item['pngName']);
          if Trim(pngName) <> '' then
            iconUrl := 'assets/markers/' + pngName
          else
            iconUrl := 'assets/markers/default.png';
          m := lfMap.Markers.Add;
          m.Latitude := lat;
          m.Longitude := lng;
          m.Title := Format('%s • %s (%s)', [complaintId, codeDesc, dispatchDist]);
          m.DataString := 'complaint|' + complaintId;
          m.IconURL    := iconUrl;
        end;
      finally
        lfMap.EndUpdate;
      end;
    end;
    FComplaintsLoaded := True;
  except
    on E: EXDataClientRequestException do
      Console.Log('Complaints XData error: ' + E.ErrorResult.ErrorMessage);
  end;

//  if FUnitsLoaded and FComplaintsLoaded then
//    FitBoundsPolysAndMarkers;
    HideSpinner('spinner');
end;



procedure TFViewMap.lfMapCustomizeMarker(Sender: TObject; var ACustomizeMarker: string);
begin
  ACustomizeMarker :=
    'var m=' + MARKERVAR + ', o=m.options||{};' + #13#10 +
    'if (o.icon && o.icon.options && o.icon.options.iconUrl) {' + #13#10 +
    '  var u = o.icon.options.iconUrl;' + #13#10 +
    '  m.setIcon(L.icon({' + #13#10 +
    '    iconUrl: u,' + #13#10 +
    '    iconSize: [32,32],' + #13#10 +
    '    iconAnchor: [16,32],' + #13#10 +
    '    popupAnchor: [0,-20]' + #13#10 +
    '  }));' + #13#10 +
    '}' + #13#10 +
    'var t = (o && o.title) ? o.title : "";' + #13#10 +
    'try { m.unbindTooltip(); } catch(e) {}' + #13#10 +
    'm.bindTooltip(t, {' + #13#10 +
    '  className: "emi-tip",' + #13#10 +
    '  direction: "top",' + #13#10 +
    '  offset: [0,-28],' + #13#10 +
    '  sticky: true' + #13#10 +
    '});';
end;


procedure TFViewMap.lfMapCustomizeCSS(Sender: TObject; var ACustomizeCSS: string);
begin
  ACustomizeCSS :=
    '.leaflet-tooltip.emi-tip {' + #13#10 +
    '  background: #111;' + #13#10 +
    '  color: #fff;' + #13#10 +
    '  border-radius: 6px;' + #13#10 +
    '  padding: 6px 8px;' + #13#10 +
    '  border: 1px solid rgba(255,255,255,.15);' + #13#10 +
    '  box-shadow: 0 4px 14px rgba(0,0,0,.35);' + #13#10 +
    '  font: 500 12px/1.2 system-ui, Segoe UI, Roboto, sans-serif;' + #13#10 +
    '}' + #13#10 +
    '.leaflet-tooltip-top.emi-tip:before    { border-top-color:    #111; }' + #13#10 +
    '.leaflet-tooltip-bottom.emi-tip:before { border-bottom-color: #111; }' + #13#10 +
    '.leaflet-tooltip-left.emi-tip:before   { border-left-color:   #111; }' + #13#10 +
    '.leaflet-tooltip-right.emi-tip:before  { border-right-color:  #111; }';
end;


procedure TFViewMap.lfMapPolyElementMouseEnter(Sender: TObject; AElement: TTMSFNCMapsPolyElement);
begin
  if AElement is TTMSFNCMapsPolygon then
  begin
    TTMSFNCMapsPolygon(AElement).StrokeWidth := 3;
    TTMSFNCMapsPolygon(AElement).FillOpacity := 0.55;
  end;
end;


procedure TFViewMap.lfMapPolyElementMouseLeave(Sender: TObject; AElement: TTMSFNCMapsPolyElement);
begin
  if AElement is TTMSFNCMapsPolygon then
  begin
    TTMSFNCMapsPolygon(AElement).StrokeWidth := 2;
    TTMSFNCMapsPolygon(AElement).FillOpacity := 0.42;
  end;
end;

end.

