unit UnitLeafletExport;

// 08/04/2020: Point de contrôle temporel (contrôle de version)
// A copier vers GHCaveDraw

{$mode delphi}

interface

uses
  StructuresDonnees, GHTopoGeneralFunctions, unitDatesUtils, unitGHTopoViseesUtils,  unitColorsUtils, unitSolveEquationsPolynomes,  Classes, SysUtils, Math, // anciennement Include   
  Graphics;
const // Ne pas définir de type énuméré afin d'éviter une dépendance avec GHTopo
  OSM_MARKER_STYLE_DEFAULT = 0;
  OSM_MARKER_STYLE_CIRCLE  = 1;
  OSM_MARKER_STYLE_SQUARE  = 2;
  OSM_MARKER_STYLE_DELTA   = 3;
  OSM_MARKER_STYLE_NABLA   = 4;
  OSM_MARKER_STYLE_LOSANGE = 5;
  OSM_MARKER_STYLE_MOULIN  = 6;

type TLFStringArray = array of string;
type TOSMLayer = record
  LayerIsAMap     : boolean;
  LayerDisplayed  : boolean;
  LayerVarName    : string;
  LayerTitle      : string;
  LayerAttribution: string;
  LayerURL        : string;
  LayerMarkerStyle: integer;
  LayerMarkerSize : double;
  LayerMarkerOpacity: double;
  LayerMarkerColor: TColor;
end;

const
  JS_TMARKER_CLASSNAME           = 'TJSMarker';
  JS_TMARKER_FIELD_LayerName     = 'LayerName';
  JS_TMARKER_FIELD_NativeCoordoX = 'NativeCoordoX';
  JS_TMARKER_FIELD_NativeCoordoY = 'NativeCoordoY';
  JS_TMARKER_FIELD_NativeCoordoZ = 'NativeCoordoZ';

  JS_TMARKERLIST_CLASSNAME  = 'TListeMarkers';
  JS_TMARKERLIST_INSTANCE   = 'FListeMarkers';
  JS_TMARKERLIST_METHOD_FindMarkerByEtiquette  = 'FindMarkerByEtiquette';
  JS_MARKER_LOCAL_VARNAME   = 'MyMarker';

  // accès aux éléments de la page
  NAMEREF_WINDOW                      = 'window';
  NAMEREF_DOCUMENT                    = 'document';

  NAMEREF_BARRE_TITRE                 = 'BarreTitre';
  NAMEREF_PANNEAU_LATERAL             = 'PanneauLateral';
  NAMEREF_PANNEAU_SEARCH              = 'PanneauRecherche';
  NAMEREF_PANNEAU_PICK_COORDONNEES    = 'PanneauPickCoordonnees';
  NAMEREF_MAP                         = 'MyMap';


  JS_DOCUMENT_FORM_SEARCH_NAME                = 'frmSearchNode';
  JS_DOCUMENT_FORM_LISTE_NAME                 = 'frmListeNoeuds';
  JS_DOCUMENT_FORM_PICK_COORDS                = 'frmPickCoords';
  JS_DOCUMENT_FORM_editPickCoords             = 'editPickCoords';
  JS_DOCUMENT_LSBMARKER_NAME                  = 'lsbMarkers';
  JS_DOCUMENT_FORM_editFindWhat               = 'editFindWhat';
  JS_DOCUMENT_FORM_lbCoordinates              = 'lbCoordinates';
  JS_DOCUMENT_FORM_btnSearch                  = 'btnFindWhat';
  JS_DOCUMENT_FORM_btnSearch_ProcOnSubmit     = 'FindStationByEtiquette';

  JS_DOCUMENT_FORM_lsbMarkers_Name         = NAMEREF_DOCUMENT + '.' + JS_DOCUMENT_FORM_LISTE_NAME + '.' + JS_DOCUMENT_LSBMARKER_NAME;
  JS_DOCUMENT_FORM_lsbMarkers_ProcOnSelect = 'CentrerCarteSurItem';
  // Callbacks de la carte
  JS_MAP_CALLBACK_MapOnClick               = 'MyMapOnClick';
  JS_MAP_CALLBACK_MapOnMouseMove           = 'MyMapOnMouseMove';

const
  JS_PREFIX_OF_TYPE_MARKER           = 'TypeMarkerOf';
  JS_PREFIX_OF_SIZE_MARKER           = 'SizeMarkerOf';
  JS_PREFIX_OF_COLOR_VAR_MARKER      = 'ColorMarkerOf';
  JS_PREFIX_OF_OPACITY_VAR_MARKER    = 'OpacityMarkerOf';
  JS_VAR_LAYER_OPACITY               = 'QLayerOpacity';

//*******************************************************************************
type

{ TListeOSMLayers }

 TListeOSMLayers = class(TList)
  private
    function RemoveElement(const Idx: integer): boolean;
  public
    procedure ClearListe();
    procedure AddElement(const L: TOSMLayer);
    function  GetElement(const Idx: integer): TOSMLayer;
    procedure PutElement(const Idx: integer; const L: TOSMLayer);
    function  GetNbElements(): integer;
    function  GetNbTileMaps(): integer;
    function  GetNbOverlays(): integer;
end;

//******************************************************************************
// Génération du document-programme HTML incorporant une carte OSM
type

{ TLeafletExport }

  TLeafletExport = class
  strict private
    FP                 : TextFile;
    FCurrJSFuncName    : string;
    FListeLayers       : TListeOSMLayers;  // contient les couches
    FCurrentIndentation: string;
    procedure BeginBody(const OnLoadProc, OnResizeProc: string);
    procedure BeginCSS_Styles();
    procedure BeginDiv(const DivID: string);
    procedure BeginForm(const FormID: string);
    function CheckLatLon(const QLat, QLon: double): boolean;
    procedure DefineAllVarLayers();
    procedure DefineColorsOfLayers();
    procedure DefineCssStyles();
    procedure DefineListeLayers(const QIsMap: boolean; const NameVarListe: string);
    procedure EndDiv();
    procedure BeginHEAD(const DocTitle: string);
    procedure BeginScriptSection();
    procedure EndBody();
    procedure EndCSS_Styles();
    procedure EndForm();

    procedure EndHEAD();
    procedure EndingHTML();
    procedure EndScriptSection();
    procedure JSDeclareGlobalVariable(const varName, commentaire: string);
    procedure JSDefineClassMarkerList();
    procedure JSDefineClassTMarker();
    procedure JSDefineGlobalObject(const objname, objecttype, commentaire: string);
    procedure JSFuncCreateMarker();
    procedure JSFuncCreatePolygonPolyline(const IsPolygonThenPolyline: Boolean);
    procedure JSFuncDrawMarker();
    procedure JSFuncInitaliser();
    procedure JSFuncOnResize();
    procedure JSFuncOnSubmit();
    procedure RedigeBeginSectionJSScripts;
    procedure RedigeEndSectionJSScripts();

    procedure WriteRawLine(const S: string); overload;
    procedure WriteRawLine(const Fmt: String; const Args: array of const); overload;

    procedure IndentLine(const ResetIndent: boolean = false);
    procedure UnIndentLine(const ResetIndent: boolean = false);
    procedure BeginCSSClass(const QCSSClassName: string);
    procedure EndCSSClass();
    procedure AddLayer(const L: TOSMLayer); overload;
    // pour écriture directe de code JS (à des fins de test notamment)
    procedure BeginJSVerbatim(const Desc: string);
    procedure JSVerbatim(const L: string);
    procedure EndJSVerbatim();


    procedure BeginJSFunction(const JSProcname: string; const Args: string);
    procedure EndJSFunction();
    function  HasAlreadyLayerVarName(const L: string): boolean;

    procedure jsIF(const QCondition: string; const QTag: string = '');
    procedure jsELSE(const QTag: string = '');
    procedure jsENDIF(const QTag: string = '');

    procedure jsFOR(const QVarCounter, QVarNb: string; const QStart: integer=0; const QTag: string = '');
    procedure jsNEXT(const QVarCounter: string; const QTag: string = '');

    procedure jsWHILE(const QCondition: string; const QTag: string = '');
    procedure jsWEND(const QTag: string = '');

    procedure jsSELECT_CASE(const QSelecteur: string; const QTag: string = '');
    procedure jsCASE(const QItem: integer; const QTag: string = '');
    procedure jsBREAK();
    procedure jsCASE_ELSE(const QTag: string = '');
    procedure jsEND_SELECT(const QTag: string='');
  private
    FUseLocalJSLibraries: boolean;
    FNbMarkers         : integer;
    FDocTitle          : string;
    FMapWidthInPercent : integer;          // en pourcentage de la largeur d'écran
    FMapHeightinPixels : integer;          // en pixels
    FCentroideLat, FCentroideLon : Double; // la carte sera centrée sur ces coordonnées
    FCurrentLineWidth    : double;      // styles courants
    FCurrentLineColor    : TColor;

    FCurrentLineOpacity  : double;
    FCurrentFillColor    : TColor;

    FCurrentFillOpacity  : double;
    FCurrentTagString    : string;
    FCurrentObjectName   : string;

    procedure DefineVarLayer(const L: TOSMLayer);
    procedure WriteSectionSeparator();
    procedure WriteProcSeparator();

  public
    function  RealEscapeString(const S: string): string;
    function  Initialiser(const QFilename: string;
                          const QDocTitle: string;
                          const QMapWidthInPercent, QMapHeightinPixels: integer;
                          const QCentroideLat, QCentroideLon: Double;
                          const UseLocalJSLibraries: boolean): boolean;

    procedure Finaliser();
    procedure BeginHTML();
    procedure EndHTML();
    // construction du document-programme, déclaration des fonctions JavaScript
    procedure WriteHeader();
    procedure WriteFooter();


    // gestion des couches
    procedure AddLayer(const QLayerIsAMap, QLayerDisplayed: boolean;
                       const QLayerName : string;
                       const QLayerTitle: string;
                       const QLayerAttribution: string;
                       const QURL: String;
                       const QLayerMarkerStyle: TSymboleStyle;
                       const QLayerMarkerSize: double;
                       const QLayerMarkerColor: TColor;
                       const QLayerMarkerOpacity: double); overload;
    function  GetLayer(const Idx: integer): TOSMLayer;
    function  GetLastLayer(): TOSMLayer;
    function  GetNbLayers(): integer;
    function  IdxLayerOf(const QLayerName: string): integer;
    // feuilles de style
    procedure DefineStylePoly(const StyleName: string; const LineWidth: double; const LineColor, FillColor: TColor; const LineOpacity, FillOpacity: byte);
    // objets graphiques: marqueurs, polylignes et polygones
    procedure AddMarker( const QLayerName: string;
                         const QNativeCoordX, QNativeCoordY, QNativeCoordZ, Lat, Lon: double;
                         const QEtiquette, QDesignation, QDescription, QObservations, QPhoto: string);
    procedure FlushAllMarkers();
    procedure BeginPolygon(const QLayerName: string; const QPolyName: string; const QStyleURL: string);
    procedure AddVertex(const Lat, Lon, Alt: double; const IsLast: boolean);
    procedure EndPolygon();
    procedure BeginPolyline(const QLayerName: string; const QPolyName: string; const QStyleURL: string);
    procedure EndPolyline();
    // utilisé pour structurer le document
    procedure BeginConditionalSection(const B: boolean); //; const VariableName: string);
    procedure EndConditionalSection();
end;

implementation
uses
  DGCDummyUnit;
const
  NB_LAYERS_FOND_CARTE    = 2;

  URL_LEAFLET_LIBRARY        = 'https://unpkg.com/leaflet';
  //LEAFLET_VERSION_NUMBER     = '1.3.1';
  LEAFLET_VERSION_NUMBER     = '1.6.0';

  URL_LEAFLET_JS             = URL_LEAFLET_LIBRARY + '@' + LEAFLET_VERSION_NUMBER + '/dist/leaflet.js';  //'http://cdn.leafletjs.com/leaflet/v0.7.7/leaflet.js';
  URL_LEAFLET_CSS            = URL_LEAFLET_LIBRARY + '@' + LEAFLET_VERSION_NUMBER + '/dist/leaflet.css'; //'http://cdn.leafletjs.com/leaflet/v0.7.7/leaflet.css';
  URL_LOCAL_LEAFLET_JS       = './Leaflet/leaflet.js';
  URL_LOCAL_LEAFLET_CSS      = './Leaflet/leaflet.css';


  OSM_MAPNIK_VAR_LAYER       = 'LayerOsmMapnik';
  OSM_MAPNIK_MAP_TITLE       = 'OpenStreetMap';
  OSM_MAPNIK_URL_TILES       = 'https://{s}.tile.osm.org/{z}/{x}/{y}.png';
  OSM_MAPNIK_ATTRIBUTION     = '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors';

  OSM_TOPO_VAR_LAYER         = 'LayerOsmTopo';
  OSM_TOPO_MAP_TITLE         = 'OpenTopoMap';
  OSM_TOPO_URL_TILES         = 'https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png';
  OSM_TOPO_ATTRIBUTION       = 'Map data: &copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, ' +
                               '<a href="http://viewfinderpanoramas.org">SRTM</a> | ' +
                               'Map style: &copy; <a href="https://opentopomap.org">OpenTopoMap</a> '+
                               '(<a href="https://creativecommons.org/licenses/by-sa/3.0/">CC-BY-SA</a>)';

  GOOGLE_SAT_VAR_LAYER       = 'LayerGoogleSat';
  GOOGLE_SAT_TITLE           = 'Google Satellite';
  GOOGLE_SAT_URL_TILES       = 'https://www.google.com/maps/vt?lyrs=s@189&gl=cn&x={x}&y={y}&z={z}';
  GOOGLE_SAT_ATTRIBUTION     = 'Google Satellite';

  DUMMY_MAP_VAR_LAYER        = 'LayerDummyMap';
  DUMMY_MAP_TITLE            = 'Pas de carte';
  DUMMY_MAP_TILES            = '';
  DUMMY_MAP_ATTRIBUTION      = 'Blank map';



  JS_FUNCTION_INITIALISER    = 'Initialiser';
  JS_FUNCTION_ONRESIZE       = 'ResizeMap';

  JS_FUNCTION_CREATE_MARKER  = 'CreateMarker';
  JS_FUNCTION_DRAW_MARKER    = 'DrawMarker';

  JS_FUNCTION_CREATE_POLYGON   = 'CreatePolygon';
  JS_FUNCTION_CREATE_POLYLINE  = 'CreatePolyline';

const
  FMT001 = '       ''%s'': %s%s';
  VAR_baseLayers = 'baseLayers';
  VAR_overlays   = 'overlays';
  HAUTEUR_TITRE_IN_PIXELS       = 40;
  HAUTEUR_FORM_SEARCH_IN_PIXELS = 70;
  HAUTEUR_PNL_COORDS_IN_PIXELS  = 300;
  VAR_QDLR = 'QDemiLargeurRectangle';
  VAR_QDHR = 'QDemiHauteurRectangle';
  FMT_LINK_REL_HREF  = '  <link rel="%s" href="%s" />';
  JS_NAME_stylesheet = 'stylesheet';
  LOCAL_VAR_001        = 'QLatLon';
  LOCAL_VAR_latlng     = 'latlng';
  LOCAL_VAR_EWE        = 'EWE';
(*
JS_TMARKER_ARG_LIST            = 'Q' + JS_TMARKER_FIELD_LayerName +
                                   ', Q' + JS_TMARKER_FIELD_NativeCoordoX +
                                   ', Q' + JS_TMARKER_FIELD_NativeCoordoY +
                                   ', Q' + JS_TMARKER_FIELD_NativeCoordoZ +
                                   ', QLat, QLon, QEtiquette, QDesignation, QDescription, QObservations, QTypeMarker, QSize, QLineColor, QFillColor, QFillOpacity, QPhoto';     //*)
const
    JS_TMARKER_ARG_LIST =
    'QLayerName, QNativeCoordoX, QNativeCoordoY, QNativeCoordoZ, ' +
    'QLat, QLon, QEtiquette, QDesignation, QDescription, QObservations, ' +
    'QTypeMarker, QSize, QLineColor, QFillColor, QFillOpacity, QPhoto';


// pour se rendre indépendant des unités de GHTopo
function QFormatterNombreReel(const Value: double; const NbDecs: integer = 2): string;
var
  EWE: String;
begin
  EWE := Format('%%.%df', [NbDecs]);
  // remplacer les points par des virgules
  Result := Format(EWE, [Value]);
  Result := StringReplace(Result, DefaultFormatSettings.DecimalSeparator, '.', [rfReplaceAll, rfIgnoreCase]);
end;
function QColorToHTMLColor(const Couleur: TColor): string;
var
  R, V, B: byte;
begin
  R := Red(Couleur);
  V := Green(Couleur);
  B := Blue(Couleur);
  Result := Format('#%.2X%.2X%.2X', [R, V, B]);
end;
function QIIF(const Condition: boolean; const V1, V2: string): string; overload;
begin
  if (Condition) then Result := V1 else Result := V2;
end;
function QIIF(const Condition: boolean; const V1, V2: Integer): Integer; overload;
begin
  if (Condition) then Result := V1 else Result := V2;
end;
function QIIF(const Condition: boolean; const V1, V2: Double): Double; overload;
begin
  if (Condition) then Result := V1 else Result := V2;
end;

function QColorToHTML(const C: TColor): string;
begin
  Result := Format('"#%.2X%.2X%.2X"', [Red(C), Green(C), Blue(C)]);
end;

{ TListeOSMLayers }
procedure TListeOSMLayers.ClearListe();
var
  i, n: Integer;
begin
  n := self.Count;
  if (n > 0) then
  for i := Count-1 downto 0 Do
  begin
    if (self.Items[i] <> Nil) then Dispose(self.Items[i]); // Libération
    self.Delete(i);                                        // Suppression de l'élément
  end;
end;

procedure TListeOSMLayers.AddElement(const L: TOSMLayer);
var
  pE: ^TOSMLayer;
begin
  New(pE);
  pE^ := L;
  self.Add(pE);
end;
function TListeOSMLayers.RemoveElement(const Idx: integer): boolean;
begin
  Result := False;
  try
    Dispose(self.Items[Idx]);
    self.Delete(Idx);
    Result := True;
  except
  end;
end;


function TListeOSMLayers.GetElement(const Idx: integer): TOSMLayer;
begin

  Result := TOSMLayer(Items[Idx]^);
end;

procedure TListeOSMLayers.PutElement(const Idx: integer; const L: TOSMLayer);
begin
  TOSMLayer(Items[Idx]^) := L;
end;


function TListeOSMLayers.GetNbElements(): integer;
begin
  Result := self.Count;
end;

function TListeOSMLayers.GetNbTileMaps(): integer;
var
  Nb, i: Integer;
  L: TOSMLayer;
begin
  result := 0;
  Nb := self.Count;
  if (0 = Nb) then exit;
  for i := 0 to Nb - 1 do
  begin
    L := GetElement(i);
    if (L.LayerIsAMap) then Result += 1;
  end;
end;

function TListeOSMLayers.GetNbOverlays(): integer;
var
  Nb, i: Integer;
  L: TOSMLayer;
begin
  result := 0;
  Nb := self.Count;
  if (0 = Nb) then exit;
  for i := 0 to Nb - 1 do
  begin
    L := GetElement(i);
    if (not L.LayerIsAMap) then Result += 1;
  end;
end;

//==============================================================================
// TLeafletExport
function TLeafletExport.RealEscapeString(const S: string): string;
  function toto(const QS: string; const OldPattern, NewPattern: string): string;
  begin
    Result := StringReplace(QS, OldPattern, NewPattern, [rfReplaceAll]);
  end;
begin
  Result := S;
  Result := toto(Result, '"', '&quot;');
  Result := toto(Result, '&', '&amp;');
  Result := toto(Result, '<', '&lt;');
  Result := toto(Result, '>', '&gt;');
  // TODO: à compléter
end;

procedure TLeafletExport.WriteRawLine(const S: string); overload;
begin
  Writeln(FP, FCurrentIndentation + S);
end;
procedure TLeafletExport.WriteRawLine(const Fmt: String; const Args: array of const); overload;
begin
  WriteLn(FP, Format(Fmt, Args));
end;


procedure TLeafletExport.IndentLine(const ResetIndent: boolean = false);
var
  n: Integer;
begin
  n := length(FCurrentIndentation) + 2;
  if (ResetIndent) then n := 0;
  FCurrentIndentation := StringOfChar(' ', n);
end;

procedure TLeafletExport.UnIndentLine(const ResetIndent: boolean = false);
var
  n: Integer;
begin
  n := length(FCurrentIndentation) - 2;
  if (n < 0) then n := 0;
  if (ResetIndent) then n := 0;
  FCurrentIndentation := StringOfChar(' ', n);
end;

procedure TLeafletExport.BeginCSSClass(const QCSSClassName: string);
begin
  WriteRawLine(' #%s{', [QCSSClassName]);
  IndentLine();
end;

procedure TLeafletExport.EndCSSClass();
begin
  WriteRawLine(' }');
  UnIndentLine();
end;

procedure TLeafletExport.jsFOR(const QVarCounter, QVarNb: string; const QStart: integer = 0; const QTag: string = '');
begin
  WriteRawLine('for (var %s = %d; %s < %s; %s++) // -- FOR %s', [QVarCounter, QStart, QVarCounter, QVarNb, QVarCounter, QTag]);
  WriteRawLine('{');
  IndentLine();
end;
procedure TLeafletExport.jsNEXT(const QVarCounter: string; const QTag: string = '');
begin
  UnIndentLine();
  WriteRawLine('} // -- NEXT %s ... %s', [QVarCounter, QTag]);
end;
procedure TLeafletExport.jsWHILE(const QCondition: string; const QTag: string = '');
begin
  WriteRawLine('while (%s) // -- WHILE %s', [QCondition, QTag]);
  WriteRawLine('{');
  IndentLine();
end;
procedure TLeafletExport.jsWEND(const QTag: string = '');
begin
  UnIndentLine();
  WriteRawLine('} // -- WEND %s', [QTag]);
end;
procedure TLeafletExport.jsIF(const QCondition: string; const QTag: string = '');
begin
  WriteRawLine('if (%s) // %s', [QCondition, QTag]);
  WriteRawLine('{');
  IndentLine();
end;
procedure TLeafletExport.jsELSE(const QTag: string = '');
begin
  UnIndentLine();
  WriteRawLine('} else { // %s', [QTag]);
  IndentLine();
end;
procedure TLeafletExport.jsENDIF(const QTag: string = '');
begin
  UnIndentLine();
  WriteRawLine('} // %s', [QTag]);
end;

procedure TLeafletExport.jsSELECT_CASE(const QSelecteur: string; const QTag: string = '');
begin
  WriteRawLine('switch(%s) { // %s', [QSelecteur, QTag]);
  IndentLine();
end;
procedure TLeafletExport.jsCASE(const QItem: integer; const QTag: string = '');
begin
  WriteRawLine('case %d: // %s', [QItem, QTag]);
end;
procedure TLeafletExport.jsBREAK();
begin
  WriteRawLine('break;');
end;
procedure TLeafletExport.jsCASE_ELSE(const QTag: string = '');
begin
  WriteRawLine('default: // %s', [QTag]);
end;
procedure TLeafletExport.jsEND_SELECT(const QTag: string = '');
begin
  UnIndentLine();
  WriteRawLine('} // switch %s', [QTag]);
end;

function TLeafletExport.GetNbLayers(): integer;
begin
  Result := FListeLayers.GetNbElements();
end;

function TLeafletExport.IdxLayerOf(const QLayerName: string): integer;
var
  i, Nb, P: Integer;
  L: TOSMLayer;
  EWE: String;
begin
  Result := -1;
  EWE := LowerCase(QLayerName);
  Nb := GetNbLayers();
  for i := 0 to Nb -1 do
  begin
    L := GetLayer(i);
    P := Pos(LowerCase(L.LayerVarName), EWE);
    if (P > 0) then exit(i);
  end;
end;

function TLeafletExport.Initialiser(const QFilename: string;
                                    const QDocTitle: string;
                                    const QMapWidthInPercent, QMapHeightinPixels: integer;
                                    const QCentroideLat, QCentroideLon: Double;
                                    const UseLocalJSLibraries: boolean): boolean;
var
  i: Integer;
  QLayerMapOpacity: Double;
begin
  Result := false;
  FCurrentIndentation := '';
  FCurrJSFuncName     := '';
  FNbMarkers          := 0;
  FUseLocalJSLibraries:= UseLocalJSLibraries;
  FMapWidthInPercent  := QMapWidthInPercent;
  FMapHeightinPixels  := QMapHeightinPixels;

  FCurrentLineColor   := clRed;
  FCurrentLineOpacity := 255;
  FCurrentFillColor   := clRed;
  FCurrentFillOpacity := 128;
  // Liste des couches
  FListeLayers        := TListeOSMLayers.Create;
  FListeLayers.ClearListe();
  QLayerMapOpacity    := FCurrentFillOpacity / 256.0;

  AssignFile(FP, QFileName);
  try

    ReWrite(FP);
    {$IFDEF FPC}
    SetTextCodePage(FP, CP_UTF8);  // Forcer l’encodage UTF-8 sous FPC
    {$ENDIF}
    Write(FP, #$EF#$BB#$BF); // BOM UTF-8

    AddLayer(True, True, OSM_MAPNIK_VAR_LAYER      , OSM_MAPNIK_MAP_TITLE    , OSM_MAPNIK_ATTRIBUTION         , OSM_MAPNIK_URL_TILES    , ssDEFAULT, 10.00, clRed   , QLayerMapOpacity);
    AddLayer(True, True, OSM_TOPO_VAR_LAYER        , OSM_TOPO_MAP_TITLE      , OSM_TOPO_ATTRIBUTION           , OSM_TOPO_URL_TILES      , ssDEFAULT, 10.00, clGreen , QLayerMapOpacity);
    AddLayer(True, True, GOOGLE_SAT_VAR_LAYER      , GOOGLE_SAT_TITLE        , GOOGLE_SAT_ATTRIBUTION         , GOOGLE_SAT_URL_TILES    , ssDEFAULT, 10.00, clBlue  , QLayerMapOpacity);
    AddLayer(True, True, DUMMY_MAP_VAR_LAYER       , DUMMY_MAP_TITLE         , DUMMY_MAP_ATTRIBUTION          , DUMMY_MAP_TILES         , ssDEFAULT, 10.00, clBlue  , QLayerMapOpacity);
    result := true;
  except
    if not Result then
    begin
      // En cas d’échec, fermer proprement
      try CloseFile(FP); except end;
    end;
  end;
  FDocTitle        := QDocTitle;
  FCentroideLat    := QCentroideLat;
  FCentroideLon    := QCentroideLon;
end;



procedure TLeafletExport.Finaliser();
begin
  // --- Fermer le fichier si ouvert
  try
    if (IOResult = 0) then
    begin
      try
        CloseFile(FP);
      except
        pass; // On ignore les erreurs de fermeture
      end;
    end;
  except
    // Ignorer toute erreur inattendue
  end;

  // --- Libération de la liste de couches
  try
    if (Assigned(FListeLayers)) then
      FreeAndNil(FListeLayers);
  except
    // Ignorer les erreurs de libération (en théorie, aucune ne devrait survenir)
  end;
end;

procedure TLeafletExport.BeginHTML();
begin
  WriteRawLine('<!DOCTYPE html>');
  WriteRawLine('<!-- Generated %s -->', [DateTimeToStr(Now)]);
  WriteRawLine('<HTML>');
end;

procedure TLeafletExport.EndHTML();
begin
  WriteRawLine('</HTML>');
end;

procedure TLeafletExport.BeginCSS_Styles();
begin
   WriteRawLine('<!-- Styles -->');
   WriteRawLine('  <STYLE>');
end;
procedure TLeafletExport.EndCSS_Styles();
begin
  WriteRawLine('  </STYLE>');
end;
procedure TLeafletExport.BeginHEAD(const DocTitle: string);
begin
  WriteRawLine('  <HEAD>');
  WriteRawLine('    <TITLE>%s</TITLE>', [DocTitle]);
  WriteRawLine('    <META charset="%s" />', ['utf-8']);
  WriteRawLine('    <META name="%s" content="width=%s, initial-scale=%.1f">', ['viewport', 'device-width', 1.0]);
end;
procedure TLeafletExport.EndHEAD();
begin
  WriteRawLine('  </HEAD>');
end;
procedure TLeafletExport.BeginScriptSection();
const
  FMT_MIME_SCRIPT = '  <script type="%s" src="%s"></script>';
begin
  WriteRawLine('<!-- Scripts JS de la Leaflet -->');
  if (FUseLocalJSLibraries) then WriteRawLine(FMT_MIME_SCRIPT, [MIME_TEXT_JAVASCRIPT, URL_LOCAL_LEAFLET_JS])
                            else WriteRawLine(FMT_MIME_SCRIPT, [MIME_TEXT_JAVASCRIPT, URL_LEAFLET_JS]);
  WriteRawLine('<!-- Scripts JS internes -->');
  WriteRawLine('  <SCRIPT type="%s">', [MIME_TEXT_JAVASCRIPT]);
end;
procedure TLeafletExport.EndScriptSection();
begin
  WriteRawLine('  </SCRIPT>');
end;
procedure TLeafletExport.BeginBody(const OnLoadProc, OnResizeProc: string);
begin
  WriteRawLine('<!-- Mise en page du document -->');
  WriteRawLine('<BODY onLoad="%s()" onResize="%s()">', [OnLoadProc, OnResizeProc]);
end;
procedure TLeafletExport.EndBody();
begin
  WriteRawLine('  <noscript>' + 'JavaScript is required' + '</noscript>');
  WriteRawLine('</BODY>');
end;

procedure TLeafletExport.BeginDiv(const DivID: string);
begin
  WriteRawLine('  <div id="%s">', [DivID]);
end;
procedure TLeafletExport.EndDiv();
begin
  WriteRawLine('  </div>');
end;
procedure TLeafletExport.BeginForm(const FormID: string);
begin
  WriteRawLine('  <form name="%s">', [FormID]);
end;
procedure TLeafletExport.EndForm();
begin
  WriteRawLine('  </form>');
end;
procedure TLeafletExport.BeginJSFunction(const JSProcname: string; const Args: string);
begin
  FCurrJSFuncName := JSProcname;
  WriteRawLine('function %s(%s)', [FCurrJSFuncName, Args]);
  WriteRawLine('{');
  IndentLine();
end;
procedure TLeafletExport.EndJSFunction();
begin
  WriteRawLine('} // %s', [FCurrJSFuncName]);
  UnIndentLine();
  WriteProcSeparator();
end;

procedure TLeafletExport.DefineVarLayer(const L: TOSMLayer);
const
  FMT_VAR_BOOL_DISP_LAYER = 'var DoDisplay%s = %s;';
begin
  if (L.LayerIsAMap) then
  begin
    WriteRawLine('var %s =  L.tileLayer(''%s'',' , [L.LayerVarName, L.LayerURL]);
    WriteRawLine('{');
    IndentLine();
      WriteRawLine('attribution: ''%s'',', [L.LayerAttribution]);
      WriteRawLine('maxZoom: %d'         , [21]);
      UnIndentLine();
    WriteRawLine('});');
  end else
  begin
     WriteRawLine('var %s = new L.LayerGroup(); // %s', [L.LayerVarName, L.LayerTitle]);
  end;
  WriteRawLine(FMT_VAR_BOOL_DISP_LAYER, [L.LayerVarName, BoolToStr(L.LayerDisplayed, 'true', 'false')]);
  WriteRawLine('%s.addLayer(%s);', [NAMEREF_MAP, L.LayerVarName]);
end;

procedure TLeafletExport.WriteSectionSeparator();
begin
  WriteRawLine('//' + StringOfChar('*', 132));
end;

procedure TLeafletExport.WriteProcSeparator();
begin
  WriteRawLine('//' + StringOfChar('-', 132));
end;



procedure TLeafletExport.DefineStylePoly(const StyleName: string; const LineWidth: double; const LineColor, FillColor: TColor;  const LineOpacity, FillOpacity: byte);
begin
  FCurrentLineWidth   := LineWidth;
  FCurrentLineColor   := LineColor;
  FCurrentFillColor   := FillColor;
  FCurrentLineOpacity := 1.0 * LineOpacity / 256.0;
  FCurrentFillOpacity := 1.0 * FillOpacity / 256.0;
end;

// function CreateMarker(QMyMap, QL, QLayerName, QLat, QLon, QAlt, QEtiquette, QDesignation, QDescription, QObservations, QSize, QLineColor, QFillColor, QFillOpacity)
procedure TLeafletExport.AddMarker(const QLayerName: string;
                                   const QNativeCoordX, QNativeCoordY, QNativeCoordZ, Lat, Lon: double;
                                   const QEtiquette, QDesignation, QDescription, QObservations, QPhoto: string);
const FMT_MARKER = '%s(%s, %s, %s, %s, %s, %s, "%s", "%s", "%s", "%s", %s, %s, %s, %s, %s, "%s");';

begin
  // QLayerName,  QNativeCoordX, QNativeCoordY, QNativeCoordZ, QLat, QLon, QEtiquette, QDesignation, QDescription, QObservations, QTypeMarker, QSize, QLineColor, QFillColor, QFillOpacity, QPhoto'
  //         CreateMarker(1, 423278.540, 6416307.620, 14.000, 44.79071599, -0.49979704, "Entrée Foussat I.1", "Entrée Foussat I.1", "", "Coordonnées provisoires", TypeMarkerOk1, SizeMarkerOf1, ColorMarkerOf1, ColorMarkerOf1, QLayerOpacity, "1");
  //                          0   2   3   4   5   6   7   8     9     10     11  12  13  14  15  16   17
  try
    if (not CheckLatLon(Lat, Lon)) then raise Exception.CreateFmt('Lat %.5f out of [%.5f; %.5f] or Lon %.5f out of [%.5f; %.5f]', [Lat, -90.0, 90.0, Lon, -180.0, 180.0]);
    WriteRawLine(FMT_MARKER, [
               JS_FUNCTION_CREATE_MARKER,                        //0                  CreateMarker(1, 423278.540, 6416307.620, 14.000, 44.79071599, -0.49979704, "1.0", "Entrée Foussat I.1", "1", "1", TypeMarkerOf1, SizeMarkerOf1, ColorMarkerOf1, ColorMarkerOf1, QLayerOpacity, "1");
               QLayerName,                                       // 2
               QFormatterNombreReel(QNativeCoordX, 3),           // 3
               QFormatterNombreReel(QNativeCoordY, 3),           // 4
               QFormatterNombreReel(QNativeCoordZ, 3),           // 5
               QFormatterNombreReel(Lat, 8),                     // 6
               QFormatterNombreReel(Lon, 8),                     // 7
               RealEscapeString(QEtiquette),                     // 8
               RealEscapeString(QDesignation),                   // 9
               RealEscapeString(QDescription),                   // 10
               RealEscapeString(QObservations),                  // 11
               JS_PREFIX_OF_TYPE_MARKER + QLayerName,            // 12
               JS_PREFIX_OF_SIZE_MARKER + QLayerName,            // 13
               JS_PREFIX_OF_COLOR_VAR_MARKER + QLayerName,       // 14             //QColorToHTML(QLineColor),
               JS_PREFIX_OF_COLOR_VAR_MARKER + QLayerName,       // 15   //QColorToHTML(QFillColor),
               JS_PREFIX_OF_OPACITY_VAR_MARKER + QLayerName,     // 16
               RealEscapeString(QPhoto)                          // 17
             ]);
    FNbMarkers += 1;

  except
     raise; // Redéclencher pour l'appelant
  end;
end;
procedure TLeafletExport.FlushAllMarkers();
begin
  WriteRawLine('// Flushing all markers');
  WriteRawLine('var Nb = %s.GetNbMarkers();', [JS_TMARKERLIST_INSTANCE]);
  jsFOR('i', 'Nb', 0, '');
    WriteRawLine('var %s = %s.GetMarker(i);', [JS_MARKER_LOCAL_VARNAME, JS_TMARKERLIST_INSTANCE]);
    // ✅ C’est ici qu’on appelle DrawMarker pour chaque marker
    WriteRawLine('DrawMarker(MyMap, %s.LayerName, %s);', [JS_MARKER_LOCAL_VARNAME, JS_MARKER_LOCAL_VARNAME]);

  jsNEXT('i', '');
  WriteRawLine('// Filling listbox');
  WriteRawLine('%s.length = Nb;', [JS_DOCUMENT_FORM_lsbMarkers_Name]);
  jsFOR('i', 'Nb', 0, '');
    WriteRawLine('var %s = %s.GetMarker(i);', [JS_MARKER_LOCAL_VARNAME, JS_TMARKERLIST_INSTANCE]);
    WriteRawLine('%s.options[i].text = %s.Etiquette + " - " + %s.Designation;', [JS_DOCUMENT_FORM_lsbMarkers_Name, JS_MARKER_LOCAL_VARNAME, JS_MARKER_LOCAL_VARNAME]);
  jsNEXT('i', '');
end;
procedure TLeafletExport.AddVertex(const Lat, Lon, Alt: double; const IsLast: boolean);
begin
  try
    if (not CheckLatLon(Lat, Lon)) then raise Exception.CreateFmt('Lat %.5f out of [%.5f; %.5f] or Lon %.5f out of [%.5f; %.5f]', [Lat, -90.0, 90.0, Lon, -180.0, 180.0]);
    WriteRawLine('[%s, %s]%s', [QFormatterNombreReel(Lat, 8), QFormatterNombreReel(Lon, 8), QIIF(IsLast, '', ',')]);

  except
    raise; // Redéclencher pour l'appelant
  end;
end;

procedure TLeafletExport.BeginPolygon(const QLayerName: string; const QPolyName: string; const QStyleURL: string);
begin
  FCurrentTagString  := QStyleURL;
  FCurrentObjectName := QPolyName;
  WriteRawLine('%s(%s, L, %s, "%s", "%s",  "%s", %s, [',
                   [JS_FUNCTION_CREATE_POLYGON,
                    NAMEREF_MAP,
                    QLayerName,
                    RealEscapeString(QPolyName),
                    QColorToHTMLColor(FCurrentLineColor),
                    QColorToHTMLColor(FCurrentFillColor),
                    QFormatterNombreReel(FCurrentFillOpacity)]);
  IndentLine();
end;
procedure TLeafletExport.EndPolygon();
begin
  UnIndentLine();
  WriteRawLine(']);');
end;
procedure TLeafletExport.BeginPolyline(const QLayerName: string; const QPolyName: string; const QStyleURL: string);
begin
  FCurrentTagString  := QStyleURL;
  FCurrentObjectName := QPolyName;
  WriteRawLine('      %s(%s, L, %s, "%s", "%s",  "%s", %s, [',
                   [JS_FUNCTION_CREATE_POLYLINE,
                    NAMEREF_MAP,
                    QLayerName,
                    RealEscapeString   (QPolyName),
                    QColorToHTMLColor(FCurrentLineColor),
                    QColorToHTMLColor(FCurrentFillColor),
                    QFormatterNombreReel(FCurrentFillOpacity)]);
  IndentLine();
end;
procedure TLeafletExport.EndPolyline();
begin
  UnIndentLine();
  WriteRawLine('      ]);');
end;
//******************************************************************************
// M O N S T E R Procedure
// Réorganisation en cours
procedure TLeafletExport.DefineAllVarLayers();
var
  i: Integer;
begin
  WriteRawLine('       // Define %d layers', [GetNbLayers()]);
  for i := 0 to GetNbLayers() - 1 do DefineVarLayer(GetLayer(i));
end;
procedure TLeafletExport.DefineColorsOfLayers();
var
  i, QNbLayers: Integer;
  QLayer: TOSMLayer;
begin
  QNbLayers := GetNbLayers();
  UnIndentLine(True);
  WriteRawLine('// Define colors for %d layers', [QNbLayers]);
  WriteRawLine('var %s = %s;', [JS_VAR_LAYER_OPACITY, QFormatterNombreReel(0.55)]);
  IndentLine();
  for i := 0 to QNbLayers - 1 do
  begin
    QLayer := GetLayer(i);
    WriteRawLine('var %s = %s; ', [JS_PREFIX_OF_COLOR_VAR_MARKER  + QLayer.LayerVarName, QColorToHTML(QLayer.LayerMarkerColor)]);
  end;
  WriteRawLine('');
  UnIndentLine();
  WriteRawLine('// Define opacity for %d layers', [QNbLayers]);
  IndentLine();
  for i := 0 to QNbLayers - 1 do
  begin
    QLayer := GetLayer(i);
    WriteRawLine('var %s = %s; ', [JS_PREFIX_OF_OPACITY_VAR_MARKER +  QLayer.LayerVarName, QFormatterNombreReel(QLayer.LayerMarkerOpacity)]);
  end;
  UnIndentLine();
  WriteRawLine('// Define styles for %d layers', [QNbLayers]);
  IndentLine();
  for i := 0 to QNbLayers - 1 do
  begin
    QLayer := GetLayer(i);
    WriteRawLine('var %s = %d; ', [JS_PREFIX_OF_TYPE_MARKER + QLayer.LayerVarName, QLayer.LayerMarkerStyle]);
  end;
  UnIndentLine();
  WriteRawLine('// Define symbols size for %d layers', [QNbLayers]);
  for i := 0 to QNbLayers - 1 do
  begin
    QLayer := GetLayer(i);
    WriteRawLine('var %s = %s; ', [JS_PREFIX_OF_SIZE_MARKER + QLayer.LayerVarName, QFormatterNombreReel(QLayer.LayerMarkerSize)]);
  end;
  UnIndentLine(True);
end;

procedure TLeafletExport.DefineListeLayers(const QIsMap: boolean; const NameVarListe: string);
var
  QNbLaysOfType: integer;
  QNbC, i, q: Integer;
  QLayer: TOSMLayer;
begin
  UnIndentLine(True);
  WriteRawLine('var %s = {', [NameVarListe]);
  IndentLine();
  QNbLaysOfType := QIIF(QIsMap, FListeLayers.GetNbTileMaps(), FListeLayers.GetNbOverlays());
  QNbC := 0;
  for q := 0 to GetNbLayers() - 1 do
  begin
    QLayer := GetLayer(q);
    if (QIsMap = QLayer.LayerIsAMap) then
    begin
      QNbC +=1;
      WriteRawLine('"%s": %s%s', [QLayer.LayerTitle, QLayer.LayerVarName, QIIF(QNbC < QNbLaysOfType, ', ', '')]);
    end;
  end;
  UnIndentLine();
  WriteRawLine('};');
end;

// Fonctions JS
//**************
procedure TLeafletExport.JSDeclareGlobalVariable(const varName, commentaire: string);
begin
  WriteRawLine('var %s; // %s', [varName, commentaire]);
end;
procedure TLeafletExport.JSDefineGlobalObject(const objname, objecttype, commentaire: string);
begin
  WriteRawLine('var %s = new %s; // %s', [objname, objecttype, commentaire]);
end;
procedure TLeafletExport.JSFuncCreatePolygonPolyline(const IsPolygonThenPolyline: Boolean);
var
  FN: String;
begin
  FN := QIIF(IsPolygonThenPolyline, JS_FUNCTION_CREATE_POLYGON, JS_FUNCTION_CREATE_POLYLINE);
  BeginJSFunction(FN, 'QMyMap, QL, QLayer, QNomPoly, QLineColor, QFillColor, QFillOpacity, QListeVertex');
   WriteRawLine('QL.poly%s(QListeVertex, {', [QIIF(IsPolygonThenPolyline, 'gon', 'line')]);
   IndentLine();
     WriteRawLine('color: QLineColor,');
     WriteRawLine('weight: %s,', [QFormatterNombreReel(QIIF(IsPolygonThenPolyline, 0.00, FCurrentLineWidth), 2)]);
     WriteRawLine('opacity: 1.00,');
     WriteRawLine('fillColor: QFillColor,');
     WriteRawLine('fillOpacity: QFillOpacity');
   UnIndentLine();
   WriteRawLine('}).bindPopup(QNomPoly).addTo(QLayer);');
  EndJSFunction();
end;
(*
procedure TLeafletExport.JSFuncCreateMarker();
begin
  WriteRawLine('    // Creation de marqueur');
  BeginJSFunction(JS_FUNCTION_CREATE_MARKER, JS_TMARKER_ARG_LIST);
    WriteRawLine('%s.AddMarker(%s)', [JS_TMARKERLIST_INSTANCE, JS_TMARKER_ARG_LIST]);
  EndJSFunction();
end;
//*)
procedure TLeafletExport.JSFuncCreateMarker();
begin
  // Commentaire descriptif
  WriteRawLine('    // Creation de marqueur');

  // Début de la fonction JS CreateMarker()
  BeginJSFunction(JS_FUNCTION_CREATE_MARKER, JS_TMARKER_ARG_LIST);

  // ⚠️ Important :
  // On force le premier paramètre (QLayerName) à être stocké comme une chaîne.
  // Ainsi le JS généré aura "Entrance" et non Entrance.
  WriteRawLine(
    'FListeMarkers.AddMarker(' +
    'QLayerName, QNativeCoordoX, QNativeCoordoY, QNativeCoordoZ, ' +
    'QLat, QLon, QEtiquette, QDesignation, QDescription, QObservations, ' +
    'QTypeMarker, QSize, QLineColor, QFillColor, QFillOpacity, QPhoto);'
  );

  // Fin de la fonction
  EndJSFunction();
end;


// Rechercher un item
procedure TLeafletExport.JSFuncOnSubmit();
begin
  try
    BeginJSFunction(JS_DOCUMENT_FORM_btnSearch_ProcOnSubmit, '');
       WriteRawLine('var EWE  = %s.value;', [JS_DOCUMENT_FORM_editFindWhat]);
      WriteRawLine('var QIdx = %s.%s(EWE);', [JS_TMARKERLIST_INSTANCE, JS_TMARKERLIST_METHOD_FindMarkerByEtiquette]);
      jsIF('QIdx >= 0', '');
        WriteRawLine('var %s = %s.GetMarker(QIdx);', [JS_MARKER_LOCAL_VARNAME, JS_TMARKERLIST_INSTANCE]);
        WriteRawLine('CentrerSurPoint(%s.Lat, %s.Lon, %s.getZoom());', [JS_MARKER_LOCAL_VARNAME, JS_MARKER_LOCAL_VARNAME, NAMEREF_MAP]);
        WriteRawLine('%s.selectedIndex = QIdx;', [JS_DOCUMENT_FORM_lsbMarkers_Name]);
      jsELSE('');
        WriteRawLine('%s.alert("Elément " + EWE + " introuvable");', [NAMEREF_WINDOW]);
      jsENDIF('');
    EndJSFunction();

  except
    on E: exception do
    begin
      pass; // En attente
    end;
  end;

end;
procedure TLeafletExport.JSFuncOnResize(); // En attente
begin
  try
    BeginJSFunction(JS_FUNCTION_ONRESIZE, '');
      (*
      WriteRawLine('var %s = %s.getElementById("%s");', [LOCAL_VAR_BARTITRE, NAMEREF_DOCUMENT, NAMEREF_BARRE_TITRE]);
      WriteRawLine('var %s = %s.getElementById("%s");', [LOCAL_VAR_CARTE   , NAMEREF_DOCUMENT, NAMEREF_MAP]);
      WriteRawLine('var %s = %s.innerHeight - %s.clientHeight;', [LOCAL_VAR_MAP_HEIGHT, NAMEREF_WINDOW, NAMEREF_BARRE_TITRE]);
      WriteRawLine('%s.setAttribute("style", "height:" + %s.toString() + "px;");', [LOCAL_VAR_CARTE, LOCAL_VAR_MAP_HEIGHT]);
      //*)
      //console.log("OnResize: " + window.innerWidth.toString() + "x" + window.innerHeight.toString() + "  - Hauteur titre: " + BarTitre.clientHeight.toString() +  "  - Hauteur carte: " +MyCarte.style.height.toString());
    EndJSFunction();
  except
    on E: exception do
    begin
      pass; // En attente
    end;
  end;
end;




(*
procedure TLeafletExport.JSFuncDrawMarker();
const

  LOCAL_VAR_QMyMap        = 'QMyMap';
  LOCAL_VAR_QMyLayer      = 'QMyLayer';

  LOCAL_VAR_QMyMarker     = 'QMyMarker';
  LOCAL_VAR_QContentPopUp = 'QContentPopUp';
  ANGLE_FOR_ONE_METER_IN_DEGREES = 8.994 * 1E-6;
var
  TOTO: String;
// = ArcTan(1/6 370 000) en degrés
  procedure QDefineColors();
  begin
    WriteRawLine('color:%s.LineColor,'        , [LOCAL_VAR_QMyMarker]);
    WriteRawLine('fillColor:%s.FillColor,'    , [LOCAL_VAR_QMyMarker]);
    WriteRawLine('fillOpacity:%s.FillOpacity,', [LOCAL_VAR_QMyMarker]);
  end;
  function QBindPopUp(): string;
  begin
    //result := Format('.bindPopup(%s).addTo(%s.LayerName);', [LOCAL_VAR_QContentPopUp, LOCAL_VAR_QMyMarker]);
    result := Format('.bindPopup(%s).addTo(%s);', [LOCAL_VAR_QContentPopUp, LOCAL_VAR_QMyMarker]); // remplacer .addTo(QMyMarker.LayerName) par .addTo(QLayer)
    // Le deuxième paramètre est QL (la couche Leaflet réelle), pas QMyMarker
    result := Format('.bindPopup(%s).addTo(QL);', [LOCAL_VAR_QContentPopUp]);
 end;
begin
  try
    // création de marqueurs
    WriteRawLine('    // Dessin de marqueur');
    BeginJSFunction(JS_FUNCTION_DRAW_MARKER, Format('%s, QL, %s', [LOCAL_VAR_QMyMap, LOCAL_VAR_QMyMarker]));
      WriteRawLine('var %s = %s * %s.Size;'       , [VAR_QDLR, QFormatterNombreReel(ANGLE_FOR_ONE_METER_IN_DEGREES, 10), LOCAL_VAR_QMyMarker]);
      WriteRawLine('var %s = %s * %s.Size * 0.71;', [VAR_QDHR, QFormatterNombreReel(ANGLE_FOR_ONE_METER_IN_DEGREES, 10), LOCAL_VAR_QMyMarker]);
      WriteRawLine('var %s = "";', [LOCAL_VAR_QContentPopUp]);
      jsIF(Format('%s.Photo.length > %d', [LOCAL_VAR_QMyMarker, 3]), '');
        WriteRawLine('%s += "<IMG width=''%d'' SRC= ''" + %s.Photo + "''</IMG>";', [LOCAL_VAR_QContentPopUp, 300, LOCAL_VAR_QMyMarker]);
      jsENDIF('');
	    WriteRawLine('%s += "<B>" + %s.Designation + "</B><BR>" +', [LOCAL_VAR_QContentPopUp, LOCAL_VAR_QMyMarker]);
      IndentLine();
  	  WriteRawLine('%s.Etiquette +', [LOCAL_VAR_QMyMarker]);
      WriteRawLine('"<HR>" +');
      WriteRawLine('"<P>" + %s.Description  + "</P>" +', [LOCAL_VAR_QMyMarker]);
      WriteRawLine('"<P>" + %s.Observations + "</P>" +', [LOCAL_VAR_QMyMarker]);
      WriteRawLine('"<HR>" +');
      WriteRawLine('"X = " + %s.%s + '           , [LOCAL_VAR_QMyMarker, JS_TMARKER_FIELD_NativeCoordoX]);
      WriteRawLine('" - Y = " + %s.%s + '        , [LOCAL_VAR_QMyMarker, JS_TMARKER_FIELD_NativeCoordoY]);
      WriteRawLine('" - Z = " + %s.%s +'         , [LOCAL_VAR_QMyMarker, JS_TMARKER_FIELD_NativeCoordoZ]);

      WriteRawLine('"<HR><B> Layer: " + %s.%s + "</B>";', [LOCAL_VAR_QMyMarker, JS_TMARKER_FIELD_LayerName]);


      WriteRawLine('%s += "<HR>" +', [LOCAL_VAR_QContentPopUp]);
      WriteRawLine('"Lat: " + %s.Lat.toFixed(%d) + " - Lon: " + %s.Lon.toFixed(%d);' , [LOCAL_VAR_QMyMarker, 8, LOCAL_VAR_QMyMarker, 8]);
      UnIndentLine();
      jsSELECT_CASE(Format('%s.TypeMarker', [LOCAL_VAR_QMyMarker]), '');
        jsCASE(0, 'defaut');
        jsCASE(1, 'cercle');
          IndentLine();
            WriteRawLine('QL.circle([%s.Lat, %s.Lon], {', [LOCAL_VAR_QMyMarker, LOCAL_VAR_QMyMarker]);
            QDefineColors();
            WriteRawLine('radius:%s.Size', [LOCAL_VAR_QMyMarker]);
            WriteRawLine('})' + QBindPopUp());
            jsBREAK();
          UnIndentLine();

        jsCASE(2, 'carré');
          IndentLine();
            WriteRawLine('QL.rectangle([[%s.Lat - %s, %s.Lon - %s], [%s.Lat + %s, %s.Lon + %s * 0.71]], {',
                         [LOCAL_VAR_QMyMarker, VAR_QDHR, LOCAL_VAR_QMyMarker, VAR_QDLR,
                          LOCAL_VAR_QMyMarker, VAR_QDHR, LOCAL_VAR_QMyMarker, VAR_QDLR]);
            QDefineColors();
            WriteRawLine('})' + QBindPopUp());
            jsBREAK();
          UnIndentLine();
        jsCASE(3, 'triangle');
          IndentLine();
            WriteRawLine('QL.polygon([[%s.Lat + %s, %s.Lon], [%s.Lat - 0.5 * %s, %s.Lon - %s], [%s.Lat - 0.5 * %s, %s.Lon + %s]], {',
                         [LOCAL_VAR_QMyMarker, VAR_QDHR, LOCAL_VAR_QMyMarker,
                          LOCAL_VAR_QMyMarker, VAR_QDHR, LOCAL_VAR_QMyMarker, VAR_QDLR,
                          LOCAL_VAR_QMyMarker, VAR_QDHR, LOCAL_VAR_QMyMarker, VAR_QDLR]);
            QDefineColors();
            WriteRawLine('})' + QBindPopUp());
            jsBREAK();
          UnIndentLine();
        jsCASE(4, 'nabla');
          IndentLine();
            WriteRawLine('QL.polygon([[%s.Lat - %s, %s.Lon], [%s.Lat + 0.5 * %s, %s.Lon + %s], [%s.Lat + 0.5 * %s, %s.Lon - %s]], {',
                             [LOCAL_VAR_QMyMarker, VAR_QDHR, LOCAL_VAR_QMyMarker,
                              LOCAL_VAR_QMyMarker, VAR_QDHR, LOCAL_VAR_QMyMarker, VAR_QDLR,
                              LOCAL_VAR_QMyMarker, VAR_QDHR, LOCAL_VAR_QMyMarker, VAR_QDLR]);
            QDefineColors();
            WriteRawLine('        })' + QBindPopUp());
            jsBREAK();
          UnIndentLine();
        jsCASE(5, 'losange');
          IndentLine();
            WriteRawLine('QL.polygon([[%s.Lat - %s, %s.Lon], [%s.Lat,  %s.Lon + %s], [%s.Lat + %s, %s.Lon], [%s.Lat , %s.Lon - %s]], {',
                                [LOCAL_VAR_QMyMarker, VAR_QDHR, LOCAL_VAR_QMyMarker,
                                 LOCAL_VAR_QMyMarker, LOCAL_VAR_QMyMarker, VAR_QDLR,
                                 LOCAL_VAR_QMyMarker, VAR_QDHR, LOCAL_VAR_QMyMarker,
                                 LOCAL_VAR_QMyMarker, LOCAL_VAR_QMyMarker, VAR_QDLR]);
            QDefineColors();
            WriteRawLine('})' + QBindPopUp());
            jsBREAK();
          UnIndentLine();
        jsCASE(6, 'moulin');
          IndentLine();
            //try
              TOTO := 'QL.polygon([';
              //AfficherMessageErreur('  ***  001');
              TOTO += Format('[%s.Lat, %s.Lon], '                  , [LOCAL_VAR_QMyMarker, LOCAL_VAR_QMyMarker]);
              //AfficherMessageErreur('  ***  002' + TOTO);

              TOTO += Format('[%s.Lat, %s.Lon  + %s], '            , [LOCAL_VAR_QMyMarker, LOCAL_VAR_QMyMarker, VAR_QDLR]);
              //AfficherMessageErreur('  ***  003' + TOTO);
              TOTO += Format('[%s.Lat + %.2f * %s, %s.Lon  + %s], ', [LOCAL_VAR_QMyMarker, 0.25, VAR_QDHR, LOCAL_VAR_QMyMarker, VAR_QDLR]);
              //AfficherMessageErreur('  ***  004' + TOTO);
              TOTO += Format('[%s.Lat, %s.Lon], '                  , [LOCAL_VAR_QMyMarker, LOCAL_VAR_QMyMarker]);
              //AfficherMessageErreur('  ***  005' + TOTO);
              TOTO += Format('[%s.Lat + %s, %s.Lon], '             , [LOCAL_VAR_QMyMarker, VAR_QDHR, LOCAL_VAR_QMyMarker]);
              //AfficherMessageErreur('  ***  006' + TOTO);
              TOTO += Format('[%s.Lat + %s, %s.Lon - %.2f * %s], ' , [LOCAL_VAR_QMyMarker, VAR_QDHR, LOCAL_VAR_QMyMarker, 0.25, VAR_QDLR]);
              //AfficherMessageErreur('  ***  007' + TOTO);
              TOTO += Format('[%s.Lat, %s.Lon], '                  , [LOCAL_VAR_QMyMarker, LOCAL_VAR_QMyMarker]);
              //AfficherMessageErreur('  ***  008' + TOTO);
              TOTO += Format('[%s.Lat, %s.Lon  - %s], '            , [LOCAL_VAR_QMyMarker, LOCAL_VAR_QMyMarker, VAR_QDLR]);
              //AfficherMessageErreur('  ***  009' + TOTO);
              TOTO += Format('[%s.Lat - %.2f * %s, %s.Lon  - %s], ', [LOCAL_VAR_QMyMarker, 0.25, VAR_QDHR, LOCAL_VAR_QMyMarker, VAR_QDLR]);
              //AfficherMessageErreur('  ***  010' + TOTO);
              TOTO += Format('[%s.Lat, %s.Lon], '                  , [LOCAL_VAR_QMyMarker, LOCAL_VAR_QMyMarker]);
              //AfficherMessageErreur('  ***  011' + TOTO);
              TOTO += Format('[%s.Lat - %s, %s.Lon], '             , [LOCAL_VAR_QMyMarker, VAR_QDHR, LOCAL_VAR_QMyMarker]);
              //AfficherMessageErreur('  ***  012' + TOTO);
              TOTO += Format('[%s.Lat - %s, %s.Lon + %.2f * %s] ' , [LOCAL_VAR_QMyMarker, VAR_QDHR, LOCAL_VAR_QMyMarker, 0.25, VAR_QDLR]);
              TOTO += '], {';


            //except
              ;;
            //end;
            WriteRawLine(TOTO);
            QDefineColors();
            WriteRawLine('})' + QBindPopUp());
            jsBREAK();
          UnIndentLine();
          //*)
        (*jsCASE_ELSE('');
          IndentLine();
            WriteRawLine('QL.marker([%s.Lat, %s.Lon])%s', [LOCAL_VAR_QMyMarker, LOCAL_VAR_QMyMarker, QBindPopUp()]);
          UnIndentLine();
        jsEND_SELECT('');
     EndJSFunction();
   except
     on E: exception do
      begin
        AfficherMessageErreur('%s: %s: Err: %s', [{$I %FILE%}, {$I %LINE%}, E.Message]);
      end;
   end;
end;
//*)
// Fonction JS d'initialisation
procedure TLeafletExport.JSFuncDrawMarker();
const
  LOCAL_VAR_QMyMap        = 'QMyMap';
  LOCAL_VAR_QLayer        = 'QLayer';
  LOCAL_VAR_QMyMarker     = 'QMyMarker';
  LOCAL_VAR_QContentPopUp = 'QContentPopUp';
  ANGLE_FOR_ONE_METER_IN_DEGREES = 8.994 * 1E-6;
var
  TOTO: String;

  // Sous-fonction pour générer les attributs couleur/opacité
  procedure QDefineColors();
  begin
    WriteRawLine('color:%s.LineColor,', [LOCAL_VAR_QMyMarker]);
    WriteRawLine('fillColor:%s.FillColor,', [LOCAL_VAR_QMyMarker]);
    WriteRawLine('fillOpacity:%s.FillOpacity,', [LOCAL_VAR_QMyMarker]);
  end;

  // Génère le chaînage final Leaflet avec le bon layer
  function QBindPopUp(): string;
  begin
    Result := Format('.bindPopup(%s).addTo(%s);', [LOCAL_VAR_QContentPopUp, LOCAL_VAR_QLayer]);
  end;

begin
  //try
    WriteRawLine('// Dessin de marqueur');
    BeginJSFunction(JS_FUNCTION_DRAW_MARKER, Format('%s, %s, %s', [LOCAL_VAR_QMyMap, LOCAL_VAR_QLayer, LOCAL_VAR_QMyMarker]));


    // Variables locales JS
    WriteRawLine('var QDemiLargeurRectangle = %s * %s.Size;', [QFormatterNombreReel(ANGLE_FOR_ONE_METER_IN_DEGREES, 10), LOCAL_VAR_QMyMarker]);
    WriteRawLine('var QDemiHauteurRectangle = %s * %s.Size * 0.71;', [QFormatterNombreReel(ANGLE_FOR_ONE_METER_IN_DEGREES, 10), LOCAL_VAR_QMyMarker]);
    WriteRawLine('var %s = "";', [LOCAL_VAR_QContentPopUp]);

    // Bloc d’infos et popup
    jsIF(Format('%s.Photo.length > 3', [LOCAL_VAR_QMyMarker]), '');
      WriteRawLine('%s += "<IMG width=''300'' SRC=''" + %s.Photo + "''></IMG>";', [LOCAL_VAR_QContentPopUp, LOCAL_VAR_QMyMarker]);
    jsENDIF('');

    WriteRawLine('%s += "<B>" + %s.Designation + "</B><BR>" +', [LOCAL_VAR_QContentPopUp, LOCAL_VAR_QMyMarker]);
    IndentLine();
      WriteRawLine('%s.Etiquette +', [LOCAL_VAR_QMyMarker]);
      WriteRawLine('"<HR>" +');
      WriteRawLine('"<P>" + %s.Description  + "</P>" +', [LOCAL_VAR_QMyMarker]);
      WriteRawLine('"<P>" + %s.Observations + "</P>" +', [LOCAL_VAR_QMyMarker]);
      WriteRawLine('"<HR>" +');
      WriteRawLine('"X = " + %s.%s + '           , [LOCAL_VAR_QMyMarker, JS_TMARKER_FIELD_NativeCoordoX]);
      WriteRawLine('" - Y = " + %s.%s + '        , [LOCAL_VAR_QMyMarker, JS_TMARKER_FIELD_NativeCoordoY]);
      WriteRawLine('" - Z = " + %s.%s +'         , [LOCAL_VAR_QMyMarker, JS_TMARKER_FIELD_NativeCoordoZ]);
      WriteRawLine('"<HR><B>Layer: " + %s.%s + "</B>";', [LOCAL_VAR_QMyMarker, JS_TMARKER_FIELD_LayerName]);
      WriteRawLine('%s += "<HR>" +', [LOCAL_VAR_QContentPopUp]);
      WriteRawLine('"Lat: " + %s.Lat.toFixed(8) + " - Lon: " + %s.Lon.toFixed(8);', [LOCAL_VAR_QMyMarker, LOCAL_VAR_QMyMarker]);
    UnIndentLine();

    // Sélecteur de forme (cercle, carré, etc.)
    jsSELECT_CASE(Format('%s.TypeMarker', [LOCAL_VAR_QMyMarker]), '');

      jsCASE(0, 'defaut');
      jsCASE(1, 'cercle');
        IndentLine();
          WriteRawLine('L.circle([%s.Lat, %s.Lon], {', [LOCAL_VAR_QMyMarker, LOCAL_VAR_QMyMarker]);
          QDefineColors();
          WriteRawLine('radius:%s.Size', [LOCAL_VAR_QMyMarker]);
          WriteRawLine('})' + QBindPopUp());
          jsBREAK();
        UnIndentLine();

      jsCASE(2, 'carré');
        IndentLine();
          WriteRawLine('L.rectangle([[%s.Lat - QDemiHauteurRectangle, %s.Lon - QDemiLargeurRectangle], [%s.Lat + QDemiHauteurRectangle, %s.Lon + QDemiLargeurRectangle * 0.71]], {',
                       [LOCAL_VAR_QMyMarker, LOCAL_VAR_QMyMarker, LOCAL_VAR_QMyMarker, LOCAL_VAR_QMyMarker]);
          QDefineColors();
          WriteRawLine('})' + QBindPopUp());
          jsBREAK();
        UnIndentLine();

      jsCASE(3, 'triangle');
        IndentLine();
          WriteRawLine('L.polygon([[%s.Lat + QDemiHauteurRectangle, %s.Lon], [%s.Lat - 0.5 * QDemiHauteurRectangle, %s.Lon - QDemiLargeurRectangle], [%s.Lat - 0.5 * QDemiHauteurRectangle, %s.Lon + QDemiLargeurRectangle]], {',
                       [LOCAL_VAR_QMyMarker, LOCAL_VAR_QMyMarker, LOCAL_VAR_QMyMarker, LOCAL_VAR_QMyMarker, LOCAL_VAR_QMyMarker, LOCAL_VAR_QMyMarker]);
          QDefineColors();
          WriteRawLine('})' + QBindPopUp());
          jsBREAK();
        UnIndentLine();

      jsCASE(4, 'nabla');
        IndentLine();
          WriteRawLine('L.polygon([[%s.Lat - QDemiHauteurRectangle, %s.Lon], [%s.Lat + 0.5 * QDemiHauteurRectangle, %s.Lon + QDemiLargeurRectangle], [%s.Lat + 0.5 * QDemiHauteurRectangle, %s.Lon - QDemiLargeurRectangle]], {',
                       [LOCAL_VAR_QMyMarker, LOCAL_VAR_QMyMarker, LOCAL_VAR_QMyMarker, LOCAL_VAR_QMyMarker, LOCAL_VAR_QMyMarker, LOCAL_VAR_QMyMarker]);
          QDefineColors();
          WriteRawLine('})' + QBindPopUp());
          jsBREAK();
        UnIndentLine();

      jsCASE(5, 'losange');
        IndentLine();
          WriteRawLine('L.polygon([[%s.Lat - QDemiHauteurRectangle, %s.Lon], [%s.Lat, %s.Lon + QDemiLargeurRectangle], [%s.Lat + QDemiHauteurRectangle, %s.Lon], [%s.Lat, %s.Lon - QDemiLargeurRectangle]], {',
                       [LOCAL_VAR_QMyMarker, LOCAL_VAR_QMyMarker, LOCAL_VAR_QMyMarker, LOCAL_VAR_QMyMarker,
                        LOCAL_VAR_QMyMarker, LOCAL_VAR_QMyMarker, LOCAL_VAR_QMyMarker, LOCAL_VAR_QMyMarker]);
          QDefineColors();
          WriteRawLine('})' + QBindPopUp());
          jsBREAK();
        UnIndentLine();
      (*
      jsCASE(6, 'moulin');
        IndentLine();
          AfficherMessageErreur('001');
          try
          WriteRawLine('L.polygon([[%s.Lat, %s.Lon], [%s.Lat, %s.Lon + QDemiLargeurRectangle], [%s.Lat + 0.25 * QDemiHauteurRectangle, %s.Lon + QDemiLargeurRectangle], [%s.Lat + QDemiHauteurRectangle, %s.Lon], [%s.Lat, %s.Lon - QDemiLargeurRectangle], [%s.Lat - 0.25 * QDemiHauteurRectangle, %s.Lon - QDemiLargeurRectangle], [%s.Lat - QDemiHauteurRectangle, %s.Lon]], {',
                       [LOCAL_VAR_QMyMarker, LOCAL_VAR_QMyMarker,
                        LOCAL_VAR_QMyMarker, LOCAL_VAR_QMyMarker,
                        LOCAL_VAR_QMyMarker, LOCAL_VAR_QMyMarker,
                        LOCAL_VAR_QMyMarker, LOCAL_VAR_QMyMarker,
                        LOCAL_VAR_QMyMarker, LOCAL_VAR_QMyMarker,
                        LOCAL_VAR_QMyMarker, LOCAL_VAR_QMyMarker]);

          except
            on E: Exception do   WriteRawLine('// %s: %s: Err: %s', [{$I %FILE%}, {$I %LINE%}, E.Message]);
          end;
          AfficherMessageErreur('002');
          QDefineColors();
          WriteRawLine('})' + QBindPopUp());
          jsBREAK();
        UnIndentLine();
      //*)

      jsCASE_ELSE('');
        IndentLine();
          WriteRawLine('L.marker([%s.Lat, %s.Lon])%s', [LOCAL_VAR_QMyMarker, LOCAL_VAR_QMyMarker, QBindPopUp()]);
        UnIndentLine();

    jsEND_SELECT('');

    // ✅ Corrige l’ordre ici
    UnIndentLine();   // d’abord sortir de tout niveau d’indentation
    EndJSFunction();  // puis fermer la fonction JS proprement

  //except
  //  on E: Exception do   AfficherMessageErreur('%s: %s: Err: %s', [{$I %FILE%}, {$I %LINE%}, E.Message]);
  //end;
end;

procedure TLeafletExport.JSFuncInitaliser();
begin
  try
    // Ne pas modifier l'indentation
    UnIndentLine(True);
        WriteRawLine('//===============================================================================');
        //WriteRawLine('} // **** Begin of %s function', [JS_FUNCTION_INITIALISER]);

        WriteRawLine('function %s()', [JS_FUNCTION_INITIALISER]);
        WriteRawLine('{');
        IndentLine();
          // définir les couleurs des couches
          DefineColorsOfLayers();
          WriteRawLine('// Define center of the map and zoom factor');
          WriteRawLine('%s = L.map(''%s'').setView([%s, %s], %d); // La variable %s est globale',
                               [NAMEREF_MAP, NAMEREF_MAP,
                                QFormatterNombreReel(FCentroideLat, 8),
                                QFormatterNombreReel(FCentroideLon, 8),
                                15,
                                NAMEREF_MAP]);
          // Callback de la carte
          WriteRawLine('%s.on("%s", %s);', [NAMEREF_MAP, 'click'    , JS_MAP_CALLBACK_MapOnClick]);
          WriteRawLine('%s.on("%s", %s);', [NAMEREF_MAP, 'mousemove', JS_MAP_CALLBACK_MapOnMouseMove]);
          // Les couches
          DefineAllVarLayers();
          // Les fonds de cartes
          DefineListeLayers(True , VAR_baseLayers);
          DefineListeLayers(false, VAR_overlays);
          WriteRawLine('L.control.layers(%s, %s).addTo(%s);', [VAR_baseLayers, VAR_overlays, NAMEREF_MAP]);
          WriteRawLine('L.control.scale().addTo(%s); // %s', [NAMEREF_MAP, 'Ajout de l''échelle']); // map.addLayer(osmLayer);
          WriteSectionSeparator();
          UnIndentLine(True);
          IndentLine();
          WriteRawLine('/* ***  BEGIN DATA *** */');

      // Pas de EndJSFunction() ici: ;
  except
    on E: exception do
    begin
      AfficherMessageErreur('%s: %s: Err: %s', [{$I %FILE%}, {$I %LINE%}, E.Message]);
    end;
  end;
end;
procedure TLeafletExport.JSDefineClassTMarker();
  procedure MiouMiou(const Miou: string);
  begin
    WriteRawLine('this.%s = Q%s;', [Miou, Miou]);
  end;
begin
  try
    BeginJSFunction(JS_TMARKER_CLASSNAME, JS_TMARKER_ARG_LIST);
      MiouMiou('LayerName');
      MiouMiou(JS_TMARKER_FIELD_NativeCoordoX);
      MiouMiou(JS_TMARKER_FIELD_NativeCoordoY);
      MiouMiou(JS_TMARKER_FIELD_NativeCoordoZ);
      MiouMiou('Lat');
      MiouMiou('Lon');
      MiouMiou('Etiquette');
      MiouMiou('Designation');
      MiouMiou('Description');
      MiouMiou('Observations');
      MiouMiou('TypeMarker');
      MiouMiou('Size');
      MiouMiou('LineColor');
      MiouMiou('FillColor');
      MiouMiou('FillOpacity');
      MiouMiou('Photo');
    EndJSFunction();
  except
    on E: exception do
    begin
      AfficherMessageErreur('%s: %s: Err: %s', [{$I %FILE%}, {$I %LINE%}, E.Message]);
    end;
  end;
end;
procedure TLeafletExport.JSDefineClassMarkerList();
const
  QNAME_ARG_INDEX           = 'Index';
  QNAME_FIELD_NB_ELEMENTS   = 'this.FNbElements';
  QNAME_FIELD_LISTE_MARKERS = 'this.FListeMarkers';

begin
  try
    UnIndentLine(True);
    WriteRawLine('function %s() // %s', [JS_TMARKERLIST_CLASSNAME, 'Base de données locale contenant la liste des marqueurs ']);      //
    WriteRawLine('{');
    IndentLine();
      WriteRawLine('%s = %d;', [QNAME_FIELD_NB_ELEMENTS, 0]);
      WriteRawLine('%s = new Array(); // %s', [QNAME_FIELD_LISTE_MARKERS, ' (en JS, un tableau équivaut à un TList)']);
      WriteRawLine('this.Init = function()');
      WriteRawLine('{');
      WriteRawLine('}');
      WriteRawLine('this.AddMarker = function(%s)', [JS_TMARKER_ARG_LIST]);
      WriteRawLine('{');
      WriteRawLine('  %s[%s] = new %s(%s); ', [QNAME_FIELD_LISTE_MARKERS, QNAME_FIELD_NB_ELEMENTS, JS_TMARKER_CLASSNAME, JS_TMARKER_ARG_LIST]);
      WriteRawLine('  %s = %s.length; ', [QNAME_FIELD_NB_ELEMENTS, QNAME_FIELD_LISTE_MARKERS]);
      WriteRawLine('}');

      WriteRawLine('this.GetMarker = function(%s)', [QNAME_ARG_INDEX]);
      WriteRawLine('{');
      WriteRawLine('  return %s[%s]; ', [QNAME_FIELD_LISTE_MARKERS, QNAME_ARG_INDEX]);
      WriteRawLine('}');
      WriteRawLine('this.GetNbMarkers = function()');
      WriteRawLine('{');
      WriteRawLine('  return %s.length; ', [QNAME_FIELD_LISTE_MARKERS]);
      WriteRawLine('}');


      WriteRawLine('this.%s = function(FindWhat)', [JS_TMARKERLIST_METHOD_FindMarkerByEtiquette]);
      WriteRawLine('{');
      IndentLine();
      WriteRawLine('var Result = -1;');
      WriteRawLine('var Nb =  %s.length;', [QNAME_FIELD_LISTE_MARKERS]);
      WriteRawLine('if (Nb == 0) return -1;');
      jsFOR('i', 'Nb', 0, '');
        WriteRawLine('var %s = %s[i];', [JS_MARKER_LOCAL_VARNAME, QNAME_FIELD_LISTE_MARKERS]);
        WriteRawLine('var QFindWhat = FindWhat.toLowerCase();');
        WriteRawLine('var EWE = %s.Etiquette +', [JS_MARKER_LOCAL_VARNAME]);
        WriteRawLine('          %s.Designation +', [JS_MARKER_LOCAL_VARNAME]);
        WriteRawLine('          %s.Description;', [JS_MARKER_LOCAL_VARNAME]);
        WriteRawLine('EWE = EWE.toLowerCase();');
        WriteRawLine('if (EWE.indexOf(QFindWhat) >= 0) return i;');
      jsNEXT('i', '');
      WriteRawLine('}');
      UnIndentLine();

    WriteRawLine('} // End of %s class', [JS_TMARKERLIST_CLASSNAME]);
    UnIndentLine();
    WriteSectionSeparator();
  except
    on E: exception do
    begin
      AfficherMessageErreur('%s: %s: Err: %s', [{$I %FILE%}, {$I %LINE%}, E.Message]);
    end;
  end;
end;
// CSS
procedure TLeafletExport.DefineCssStyles();
begin
  try
     BeginCSS_Styles();
       WriteRawLine('  html, body { margin:%dpx; padding:%d; }', [0, 0]);
       // CSS de la barre de titre
       BeginCSSClass(NAMEREF_BARRE_TITRE);
         WriteRawLine('%s:%s;'     , ['color', QColorToHTMLColor(clWhite)]);
         WriteRawLine('%s:%dpx %s;', ['font', 28, DEFAULT_FONT_NAME]);
         WriteRawLine('%s:%s;'     , ['font-weight', 'bold']);
         WriteRawLine('%s:%dpx;'   , ['height', HAUTEUR_TITRE_IN_PIXELS]);
         WriteRawLine('%s:%s;'     , ['background', QColorToHTMLColor(clMaroon)]);
       EndCSSClass();
       // CSS de la zone de recherche
       BeginCSSClass(NAMEREF_PANNEAU_SEARCH);
         WriteRawLine('%s:%s;'     , ['float', 'right']);
         WriteRawLine('%s:%s;'     , ['color', QColorToHTMLColor(clBlack)]);
         WriteRawLine('%s:%dpx %s;', ['font', 15, DEFAULT_FONT_NAME]);
         WriteRawLine('%s:%s;'     , ['font-weight', 'bold']);
         WriteRawLine('%s:%s;'     , ['background', QColorToHTMLColor(clLime)]);
         WriteRawLine('%s:%s;'     , ['border-style', 'none']);
         WriteRawLine('%s:%d%%;'   , ['width', 100 - FMapWidthInPercent]);
         WriteRawLine('%s:%dpx;'   , ['height', HAUTEUR_FORM_SEARCH_IN_PIXELS]);
       EndCSSClass();
       // CSS du menu latéral
       BeginCSSClass(NAMEREF_PANNEAU_LATERAL);
         WriteRawLine('%s:%s;'     , ['float', 'right']);
         WriteRawLine('%s:%s;'     , ['color', QColorToHTMLColor(clBlack)]);
         WriteRawLine('%s:%dpx %s;', ['font', 15, DEFAULT_FONT_NAME]);
         WriteRawLine('%s:%s;'     , ['font-weight', 'bold']);
         WriteRawLine('%s:%s;'     , ['background', QColorToHTMLColor(clAqua)]);
         WriteRawLine('%s:%s;'     , ['border-style', 'none']);
         WriteRawLine('%s:%d%%;'   , ['width', 100 - FMapWidthInPercent]);
         WriteRawLine('%s:%dpx;'   , ['height', FMapHeightinPixels - HAUTEUR_TITRE_IN_PIXELS - HAUTEUR_FORM_SEARCH_IN_PIXELS - HAUTEUR_PNL_COORDS_IN_PIXELS]);
       EndCSSClass();
       // CSS du panneau de coordonnées
       BeginCSSClass(NAMEREF_PANNEAU_PICK_COORDONNEES);
         WriteRawLine('%s:%s;'     , ['float', 'right']);
         WriteRawLine('%s:%s;'     , ['color', QColorToHTMLColor(clBlack)]);
         WriteRawLine('%s:%dpx %s;', ['font', 15, DEFAULT_FONT_NAME]);
         WriteRawLine('%s:%s;'     , ['font-weight', 'bold']);
         WriteRawLine('%s:%s;'     , ['background', QColorToHTMLColor(clYellow)]);
         WriteRawLine('%s:%s;'     , ['border-style', 'none']);
         WriteRawLine('%s:%d%%;'   , ['width', 100 - FMapWidthInPercent]);
         WriteRawLine('%s:%dpx;'   , ['height', HAUTEUR_PNL_COORDS_IN_PIXELS]);
       WriteRawLine('       }');
       // CSS de la carte
       BeginCSSClass(NAMEREF_MAP);
         WriteRawLine('%s:%s;', ['float', 'left']);
         WriteRawLine('%s:%d%%;', ['width', FMapWidthInPercent]);
         WriteRawLine('%s:%dpx;', ['height', FMapHeightinPixels - HAUTEUR_TITRE_IN_PIXELS]);
         WriteRawLine('%s:%dpx;'     , ['border', 0]);
         WriteRawLine('%s:%dpx;'     , ['margin', 0]);
       EndCSSClass();
     EndCSS_Styles();
  except
    on E: exception do
    begin
      AfficherMessageErreur('%s: %s: Err: %s', [{$I %FILE%}, {$I %LINE%}, E.Message]);
    end;
  end;
end;

//**************


// M O N S T E R Procedure
// TODO: Réorganiser
procedure TLeafletExport.RedigeBeginSectionJSScripts();
begin
  BeginScriptSection();
    // déclaration des variables globales
    WriteRawLine('    // Classe contenant les marqueurs');
    JSDefineClassTMarker();
    JSDefineClassMarkerList();
    WriteRawLine('    // Variables globales');
    JSDeclareGlobalVariable(NAMEREF_MAP, 'Pointeur sur la carte');
    JSDefineGlobalObject(JS_TMARKERLIST_INSTANCE, JS_TMARKERLIST_CLASSNAME, 'Liste des markers');

    // mini-converter coordonnées LT3
    WriteSectionSeparator();
    // création de polygones silhouettes
    JSFuncCreatePolygonPolyline(true);         // Polygones
    JSFuncCreatePolygonPolyline(false);        // Polylignes
    JSFuncDrawMarker();
    JSFuncCreateMarker();                      // Markers
    AfficherMessageErreur(' --- RedigeJSSection()');
    JSFuncInitaliser();
    // La fonction JS Initialiser() se termine dans RedigeEndSectionJSScripts();
end;
procedure TLeafletExport.RedigeEndSectionJSScripts();
begin
  try
   // Laisser l'indentation telle quelle
      UnIndentLine(True);
      IndentLine();
      WriteRawLine('/* ***  END DATA *** */');
      UnIndentLine();
      WriteRawLine('} // **** End of %s function', [JS_FUNCTION_INITIALISER]);
      // Fin de La fonction JS Initialiser()
      UnIndentLine(True);
      WriteSectionSeparator();
      WriteRawLine('// Callback functions');
      // Centrer sur point
      BeginJSFunction('CentrerSurPoint', 'QLat, QLon, QZoom');
	      WriteRawLine('%s.setView([QLat, QLon], QZoom);', [NAMEREF_MAP]);
      EndJSFunction();
      BeginJSFunction(JS_DOCUMENT_FORM_lsbMarkers_ProcOnSelect, '');
        WriteRawLine('var QIdx = %s.selectedIndex;', [JS_DOCUMENT_FORM_lsbMarkers_Name]);
        WriteRawLine('var %s = %s.GetMarker(QIdx);', [JS_MARKER_LOCAL_VARNAME, JS_TMARKERLIST_INSTANCE]);
        WriteRawLine('CentrerSurPoint(%s.Lat, %s.Lon, %s.getZoom());', [JS_MARKER_LOCAL_VARNAME, JS_MARKER_LOCAL_VARNAME, NAMEREF_MAP]);
        WriteRawLine('var %s = L.latLng(%s.Lat, %s.Lon);', [LOCAL_VAR_latlng, JS_MARKER_LOCAL_VARNAME, JS_MARKER_LOCAL_VARNAME]);
        WriteRawLine('var %s = "<B>" + %s.Etiquette + " - " + %s.Designation + "</B><BR>" +', [LOCAl_VAR_EWE, JS_MARKER_LOCAL_VARNAME, JS_MARKER_LOCAL_VARNAME]);
        WriteRawLine('         "X = " + %s.%s + "<BR>" +', [JS_MARKER_LOCAL_VARNAME, JS_TMARKER_FIELD_NativeCoordoX, 0]);
        WriteRawLine('         "Y = " + %s.%s + "<BR>" +', [JS_MARKER_LOCAL_VARNAME, JS_TMARKER_FIELD_NativeCoordoY, 0]);
        WriteRawLine('         "Z = " + %s.%s + "<HR>" +' , [JS_MARKER_LOCAL_VARNAME, JS_TMARKER_FIELD_NativeCoordoZ,0]);
        WriteRawLine('         "Lat: " + %s.Lat.toFixed(%d) + " - Lon: " + %s.Lon.toFixed(%d);' , [JS_MARKER_LOCAL_VARNAME, 8, JS_MARKER_LOCAL_VARNAME, 8]);
        WriteRawLine('%s.openPopup(%s, %s);', [NAMEREF_MAP, LOCAl_VAR_EWE, LOCAL_VAR_latlng]);
	    EndJSFunction();
      WriteRawLine('// Callbacks de la carte');
      BeginJSFunction(JS_MAP_CALLBACK_MapOnClick, 'event');
        WriteRawLine('var %s = event.latlng;', [LOCAL_VAR_001]);
        WriteRawLine('var LB = %s.getElementById("%s");', [NAMEREF_DOCUMENT, JS_DOCUMENT_FORM_editPickCoords]);
        WriteRawLine('LB.value = LB.value + "\n" + %s.lat.toFixed(%d) + ", " + %s.lng.toFixed(%d);', [LOCAL_VAR_001, 8, LOCAL_VAR_001, 8]);
      EndJSFunction();
      BeginJSFunction(JS_MAP_CALLBACK_MapOnMouseMove, 'event');
        WriteRawLine('var %s = event.latlng;', [LOCAL_VAR_001]);
        WriteRawLine('var LB = %s.getElementById("%s");', [NAMEREF_DOCUMENT, JS_DOCUMENT_FORM_lbCoordinates]);
        WriteRawLine('LB.innerHTML = "%s" + %s.lat.toFixed(%d) + "%s" + %s.lng.toFixed(%d);', ['Lat = ', LOCAL_VAR_001, 8, ' - Lon = ', LOCAL_VAR_001, 8]);
      EndJSFunction();
      // Rédige la fonction JSFuncOnResize();
      JSFuncOnResize();
      // Rédige ja fonction OnSubmit
      JSFuncOnSubmit();
     EndScriptSection();

  except
    on E: exception do
    begin
      AfficherMessageErreur('%s: %s: %s', [{$I %FILE%}, {$I %LINE%}, E.Message]);
    end;
  end;
end;

procedure TLeafletExport.WriteHeader();
begin
  try
    BeginHTML();
      BeginHEAD('Report sur OpenStreetMap: ' + FDocTitle);
      if (FUseLocalJSLibraries) then WriteRawLine(FMT_LINK_REL_HREF, [JS_NAME_stylesheet, URL_LOCAL_LEAFLET_CSS])
                                else WriteRawLine(FMT_LINK_REL_HREF, [JS_NAME_stylesheet, URL_LEAFLET_CSS]);
      DefineCssStyles();
        // Scripts Javascript
        RedigeBeginSectionJSScripts();
  except
    on E: exception do
    begin
      AfficherMessageErreur('%s: %s: %s', [{$I %FILE%}, {$I %LINE%}, E.Message]);
    end;
  end;
end;
////**************************************************************************

procedure TLeafletExport.WriteFooter();  // ok
begin
  try
        RedigeEndSectionJSScripts();
      EndHEAD();
    // Body du HTML et cloture
    EndingHTML();
  except
    on E: exception do
    begin
      AfficherMessageErreur('%s: %s: Err: %s', [{$I %FILE%}, {$I %LINE%}, E.Message]);
    end;
  end;
end;

procedure TLeafletExport.EndingHTML();
begin
  try
    BeginBody(JS_FUNCTION_INITIALISER, JS_FUNCTION_ONRESIZE);
        // Le titre
        BeginDiv(NAMEREF_BARRE_TITRE);
        WriteRawLine('    %s', [FDocTitle]);
        EndDiv();
        // La carte .
        BeginDiv(NAMEREF_MAP);
        EndDiv();
        // La zone de recherche
        BeginDiv(NAMEREF_PANNEAU_SEARCH);
          BeginForm(JS_DOCUMENT_FORM_SEARCH_NAME);
            WriteRawLine('    <LABEL for="%s" style="width:%d%%">%s<BR></LABEL>'    , [JS_DOCUMENT_FORM_editFindWhat, 100, 'Localiser un point']);
            WriteRawLine('    <INPUT type="text" id="%s" style="width:%d%%" name="%s">'    , [JS_DOCUMENT_FORM_editFindWhat, 100, JS_DOCUMENT_FORM_editFindWhat]);
            WriteRawLine('    <BR>');
            WriteRawLine('    <INPUT type="button" name="%s" value="%s" onclick="%s()">'  , [JS_DOCUMENT_FORM_btnSearch, 'Rechercher', JS_DOCUMENT_FORM_btnSearch_ProcOnSubmit]);
          EndForm();
        EndDiv();
        // Le menu latéral
        BeginDiv(NAMEREF_PANNEAU_LATERAL);
          WriteRawLine('    Liste des %d éléments <BR>', [FNbMarkers]);
          BeginForm(JS_DOCUMENT_FORM_LISTE_NAME);
            WriteRawLine('      <select name="%s" size="%d" style="width:%d%%" onChange="%s()">', [JS_DOCUMENT_LSBMARKER_NAME, 24, 100, JS_DOCUMENT_FORM_lsbMarkers_ProcOnSelect]);
            WriteRawLine('      </select>');
          EndForm();
        EndDiv();
        // Le panneau de picking
        BeginDiv(NAMEREF_PANNEAU_PICK_COORDONNEES);
          BeginForm(JS_DOCUMENT_FORM_PICK_COORDS);
            WriteRawLine('      <LABEL id="%s" name="%s" style="width:%d%%">%s</LABEL>'    , [JS_DOCUMENT_FORM_lbCoordinates, JS_DOCUMENT_FORM_lbCoordinates, 100, '--']);
            //WriteRawLine('      <INPUT type="text" id="%s" style="width:%d%%" name="%s">'    , [JS_DOCUMENT_FORM_editPickCoords, 100, JS_DOCUMENT_FORM_editPickCoords]);
            WriteRawLine('      <TEXTAREA id="%s" name="%s" rows="%d" cols="%d"></TEXTAREA>'    , [JS_DOCUMENT_FORM_editPickCoords, JS_DOCUMENT_FORM_editPickCoords, 5, 40]);

          EndForm();
        EndDiv();
      EndBody();
    EndHTML();

  except
    on E: exception do
    begin
      AfficherMessageErreur('%s: %s: Err: %s', [{$I %FILE%}, {$I %LINE%}, E.Message]);
    end;
  end;
end;
//END MONSTER PROC

// style = "width: 100%;
function TLeafletExport.HasAlreadyLayerVarName(const L: string): boolean;
var
  n, i: Integer;
  MyLayer: TOSMLayer;
  LL: String;
begin
  LL := LowerCase(L);
  result := false;
  n := GetNbLayers();
  if (0 = n) then exit(false);
  for i := 0 to n - 1 do
  begin
    MyLayer := GetLayer(i);
    if (LowerCase(MyLayer.LayerVarName) = LL) then exit(True);
  end;
end;

procedure TLeafletExport.AddLayer(const L: TOSMLayer);
begin
  FListeLayers.AddElement(L);
end;
// utilisé notamment pour tester les nouvelles fonctions
// sans avoir à les sauvegarder ailleurs
procedure TLeafletExport.BeginJSVerbatim(const Desc: string);
begin
  WriteRawLine(StringOfChar('/', 132));
  WriteRawLine('// VERBATIM: ' + Desc);
  WriteRawLine(StringOfChar('/', 132));
end;

procedure TLeafletExport.JSVerbatim(const L: string);
begin
  WriteRawLine(L);
end;

procedure TLeafletExport.EndJSVerbatim();
begin
  WriteRawLine(StringOfChar('/', 132));
  WriteRawLine('// END VERBATIM:');
  WriteRawLine(StringOfChar('/', 132));
end;

procedure TLeafletExport.AddLayer(const QLayerIsAMap, QLayerDisplayed: boolean;
                                  const QLayerName : string;
                                  const QLayerTitle: string;
                                  const QLayerAttribution: string;
                                  const QURL: String;
                                  const QLayerMarkerStyle: TSymboleStyle;
                                  const QLayerMarkerSize: double;
                                  const QLayerMarkerColor: TColor;
                                  const QLayerMarkerOpacity: double); overload;
var
  L: TOSMLayer;
  function EnsureLayerVarName(const LVN: string): string;
  const DUMMY_LAYER = 'Layer00001';
  var
    i, n: Integer;
  begin
    Result := Trim(LVN);
    n := length(Result);
    if (n = 0) then exit(DUMMY_LAYER);
    // On vire tout ce qui n'est pas valable dans un nom de variable
    for i := 1 to n do
    begin
      if (not (Result[i] in ['0' .. '9', 'a' .. 'z', 'A' .. 'Z'])) then System.Delete(Result, i, 1);
    end;
    // épurage + un autre test de longueur nulle
    Result := Trim(Result); n := length(Result);
    if (n = 0) then exit(DUMMY_LAYER);
    // Si le premier caractère est un chiffre, on ajoute une lettre en tête
    if (Result[1] in ['0' .. '9']) then System.Insert('L', Result, 1);
    // épurage et sortie
    Result := Trim(Result);
    n := length(Result);   // MAJ du nombre de caractères
  end;
begin
  L.LayerIsAMap      := QLayerIsAMap;
  L.LayerDisplayed   := QLayerDisplayed;
  L.LayerVarName     := EnsureLayerVarName(QLayerName);
  L.LayerTitle       := QLayerTitle;
  L.LayerAttribution := QLayerAttribution;
  L.LayerURL         := QURL;
  L.LayerMarkerStyle := Ord(QLayerMarkerStyle);
  L.LayerMarkerSize  := QLayerMarkerSize;
  L.LayerMarkerColor := QLayerMarkerColor;
  L.LayerMarkerOpacity := QLayerMarkerOpacity;
  if (not HasAlreadyLayerVarName(L.LayerVarName)) then FListeLayers.AddElement(L);
end;

function TLeafletExport.GetLayer(const Idx: integer): TOSMLayer;
begin
  Result := FListeLayers.GetElement(Idx);
end;

function TLeafletExport.GetLastLayer(): TOSMLayer;
var
  n: Integer;
begin
  n := self.GetNbLayers();
  Result := self.GetLayer(n-1);
end;

procedure TLeafletExport.BeginConditionalSection(const B: boolean);
begin
  WriteRawLine('      if (%s)', [BoolToStr(b, 'true', 'false')]);
  WriteRawLine('      {');
end;

procedure TLeafletExport.EndConditionalSection();
begin
  WriteRawLine('      } // if (%s)', ['']);
end;
function TLeafletExport.CheckLatLon(const QLat, QLon: double): boolean;
begin
  Result := Math.InRange(QLat, -90.0, 90.0) AND InRange(QLon, -180.0, 180.0);
end;



end.

