{*** Inspired by C:\Prog\RADAddOns\TMS\TMS WEB Core RSXE12\Core Source\WEBLib.WebCtrls.pas ***}
unit CIC.TMSWeb.Cesium;

interface

uses
  Classes, WEBLib.WebCtrls, WEBLib.Controls, WEBLib.Graphics, SysUtils, Web, JS;

type
  { TCICGoogleMaps }
  TMapClickEvent = procedure(Sender: TObject; Lon, Lat: double) of object;
  TMapZoomEvent = procedure(Sender: TObject; ZoomLevel: integer) of object;
  TMapMarkerClickEvent = procedure(Sender: TObject; AIndex: integer; AMarker: TJSObject) of object;
  TMapPolygonClickEvent = procedure(Sender: TObject; AIndex: integer; APolygon: TJSObject) of object;
  TMapPolylineClickEvent = procedure(Sender: TObject; AIndex: integer; APolyline: TJSObject) of object;
  TMapCircleClickEvent = procedure(Sender: TObject; AIndex: integer; ACircle: TJSObject) of object;
  TMapRectangleClickEvent = procedure(Sender: TObject; AIndex: integer; ARectangle: TJSObject) of object;
  TMapKMLClickEvent = procedure(Sender: TObject; AIndex: integer; AKML: TJSObject) of object;

  TGoogleMarkerColor = (mcDefault, mcRed, mcBlue, mcGreen, mcPurple, mcYellow);
  TGoogleMarkerShape = (msPin, msPinDot, msFlag, msBookmark, msFlagSmall, msHome, msFavorite, msStar, msCustom);
  TGoogleTravelMode = (tmDriving, tmWalking, tmBicycling, tmTransit);
  TGoogleMapType = (mtDefault, mtSatellite, mtHybrid, mtTerrain);
  TCICGoogleMapstyle = (mstDefault, mstNightMode, mstCustom);

  TCICGoogleMaps = class;

  TCICGoogleMapsOptions = class(TPersistent)
  private
    FOwner: TCICGoogleMaps;
    FCustomStyle: TStringList;
    FMapStyle: TCICGoogleMapstyle;
    FDefaultLongitude: Double;
    FDefaultLatitude: Double;
    FDefaultZoomLevel: Integer;
    procedure SetCustomStyle(const Value: TStringList);
    procedure SetMapStyle(const Value: TCICGoogleMapstyle);
    procedure SetDefaultLatitude(const Value: Double);
    procedure SetDefaultLongitude(const Value: Double);
    procedure SetDefaultZoomLevel(const Value: Integer);
  public
    constructor Create(AGoogleMaps: TCICGoogleMaps);
    destructor Destroy; override;
    procedure Assign(Source: TPersistent); override;
  published
    property CustomStyle: TStringList read FCustomStyle write SetCustomStyle;
    property MapStyle: TCICGoogleMapstyle read FMapStyle write SetMapStyle default mstDefault;
    property DefaultLatitude: Double read FDefaultLatitude write SetDefaultLatitude default -34.397;
    property DefaultLongitude: Double read FDefaultLongitude write SetDefaultLongitude default 150.644;
    property DefaultZoomLevel: Integer read FDefaultZoomLevel write SetDefaultZoomLevel default 8;
  end;

  TCICGoogleMaps = class(TCustomControl)
  private
    FUpdateCount: integer;
    FReq: TJSXMLHttpRequest;
    FAPIKey: string;
    FMap: TJSHTMLElement;
    FDirectionsService: TJSElement;
    FDirectionsDisplay: TJSElement;
    FCode: boolean;
    FBound: boolean;
    FOnMapClick: TMapClickEvent;
    FOnMapDblClick: TMapClickEvent;
    FOnMapZoom: TMapZoomEvent;
    FOnMapPan: TMapClickEvent;
    FOnGeoCoded: TMapClickEvent;
    FOnMapIdle: TNotifyEvent;
    FOnMapLoaded: TNotifyEvent;
    FOnMarkerClick: TMapMarkerClickEvent;
    FOnPolygonClick: TMapPolygonClickEvent;
    FOnPolylineClick: TMapPolylineClickEvent;
    FOnCircleClick: TMapCircleClickEvent;
    FOnRectangleClick: TMapRectangleClickEvent;
    FOnKMLClick: TMapKMLClickEvent;
    FOptions: TCICGoogleMapsOptions;
    procedure SetAPIKey(const Value: string);
    function GetMarker(AIndex: integer): TJSObject;
    function GetCircle(AIndex: integer): TJSObject;
    function GetRectangle(AIndex: integer): TJSObject;
    function GetPolygon(AIndex: integer): TJSObject;
    function GetPolyline(AIndex: integer): TJSObject;
  protected
    function GetMap: TJSHTMLElement;
    function CreateElement: TJSElement; override;
    procedure UpdateElement; override;
    procedure BindEvents; override;
    procedure HandleMapClick(e: TJSEvent);
    procedure HandleMapDblClick(e: TJSEvent);
    procedure HandleMapPan(e: TJSEvent);
    procedure HandleMapZoom(e: TJSEvent);
    procedure HandleMapIdle(e: TJSEvent);
    procedure HandleMarkerClick(e: TJSEvent);
    procedure HandlePolygonClick(e: TJSEvent);
    procedure HandlePolylineClick(e: TJSEvent);
    procedure HandleCircleClick(e: TJSEvent);
    procedure HandleRectangleClick(e: TJSEvent);
    procedure HandleKMLClick(e: TJSEvent);
    function HandleResponse(Event: TEventListenerEvent): boolean;
    function HandleAbort(Event: TEventListenerEvent): boolean;
    function TravelModeStr(ATravelMode: TGoogleTravelMode): string;
    function DoLoaded(Event: TEventListenerEvent): boolean; virtual;
    procedure Loaded; override;
  public
    procedure CreateInitialize; override;
    procedure SetCenter(Lat, Lon: Double);
    function GetCenter(var Lat, Lon: Double): Boolean;
    function GetBounds(var NorthEastLat, NorthEastLon, SouthWestLat, SouthWestLon: Double): Boolean;
    procedure SetDoubleClickZoom(AValue: Boolean);
    procedure SetDraggable(AValue: Boolean);
    procedure SetScrollWheel(AValue: Boolean);
    procedure SetMapType(AMapType: TGoogleMapType = mtDefault);
    procedure SetZoom(Zoom: integer);
    procedure PanTo(Lat, Lon: double);
    procedure AddGPX(AGPX: string; AColor: TColor = clRed; AWidth: integer = 2; AOpacity: Double = 1);
    procedure AddKML(Url: string; ZoomToBounds: Boolean = true);
    procedure AddPolyLine(Points: TJSArray; AColor: TColor = clRed;AWidth: integer = 2; AOpacity: Double = 1);
    procedure AddPolygon(Points: TJSArray; AFillColor: TColor = clRed; AStrokeColor: TColor = clBlack; AWidth: Integer = 2; AOpacity: Double = 1);
    procedure AddCircle(Lat, Lon: double; Radius: Integer; AFillColor: TColor = clRed; AStrokeColor: TColor = clBlack; AWidth: Integer = 2; AOpacity: double = 1);
    procedure AddRectangle(NorthEastLat, NorthEastLon, SouthWestLat, SouthWestLon: double; AFillColor: TColor = clRed; AStrokeColor: TColor = clBlack; AWidth: integer = 2; AOpacity: double = 1);
    procedure AddMarker(Lat,Lon: Double; Title: string = ''); overload;
    procedure AddMarker(Lat,Lon: Double; Color: TGoogleMarkerColor; Title: string = ''); overload;
    procedure AddMarker(Lat,Lon: Double; Color: TColor; PinLetter: string; Title: string = ''); overload;
    procedure AddMarker(Lat,Lon: Double; PinIcon: string; Title: string); overload;
    procedure AddMarker(Lat,Lon: Double; Shape: TGoogleMarkerShape; Color: TColor; Title: string = ''); overload;
    procedure AddMarker(Lat,Lon: Double; Shape: TGoogleMarkerShape; Color: TColor = clRed; BorderColor: TColor = clBlack; Scale: Double = 1; CustomShape: string = ''; Title: string = ''); overload;
    procedure SetMarkerTitle(AIndex: Integer; ATitle: string);
    procedure SetMarkerLocation(AIndex: Integer; Lat, Lon: Double);
    procedure SetMarkerIcon(AIndex: Integer; Url: string);
    procedure SetCircleCenter(AIndex: Integer; Lat, Lon: Double);
    procedure SetCircleRadius(AIndex, Radius: Integer);
    procedure SetCircleColors(AIndex: Integer; AFillColor, AStrokeColor: TColor);
    procedure SetRectangleLocation(AIndex: Integer; NorthEastLat, NorthEastLon, SouthWestLat, SouthWestLon: Double);
    procedure SetRectangleColors(AIndex: Integer; AFillColor, AStrokeColor: TColor);
    procedure SetPolylineColor(AIndex: Integer; AColor: TColor);
    procedure SetPolylinePoints(AIndex: Integer; Points: TJSArray);
    procedure SetPolygonColors(AIndex: Integer; AFillColor, AStrokeColor: TColor);
    procedure SetPolygonPoints(AIndex: Integer; Points: TJSArray);
    procedure FitBounds(LatMin, LonMin, LatMax, LonMax: Double);
    procedure ClearMarkers;
    procedure ClearKMLs;
    procedure ClearPolylines;
    procedure ClearPolygons;
    procedure ClearCircles;
    procedure ClearRectangles;
    procedure ShowDirections(Source, Destination: string; ATravelMode: TGoogleTravelMode = tmDriving; WayPoints: TStringList = nil; OptimizeWayPoints: Boolean = False; AvoidHighways: Boolean = False; AvoidTolls: Boolean = False); overload;
    procedure ShowDirections(SourceLon, SourceLat, DestLon, DestLat: Double; ATravelMode: TGoogleTravelMode = tmDriving; WayPoints: TStringList = nil; OptimizeWayPoints: Boolean = False; AvoidHighways: Boolean = False; AvoidTolls: Boolean = False); overload;
    procedure ShowStreetView(Lat, Lon: Double; Heading: Integer = 0; Zoom: Integer = 0; Pitch: Integer = 0);
    procedure HideStreetView;
    procedure RemoveDirections;
    procedure RemoveMarker(AIndex: Integer);
    procedure RemovePolygon(AIndex: Integer);
    procedure RemovePolyline(AIndex: Integer);
    procedure RemoveCircle(AIndex: Integer);
    procedure RemoveRectangle(AIndex: Integer);
    procedure GeoCode(const Address: string);
    property Markers[AIndex: Integer]: TJSObject read GetMarker;
    property Polygons[AIndex: Integer]: TJSObject read GetPolygon;
    property Polylines[AIndex: Integer]: TJSObject read GetPolyline;
    property Circles[AIndex: Integer]: TJSObject read GetCircle;
    property Rectangles[AIndex: Integer]: TJSObject read GetRectangle;
  published
    property Align;
    property Options: TCICGoogleMapsOptions read FOptions write FOptions;
    property APIKey: string read FAPIKey write SetAPIKey;
    property OnGeoCoded: TMapClickEvent read FOnGeoCoded write FOnGeoCoded;
    property OnMapClick: TMapClickEvent read FOnMapClick write FOnMapClick;
    property OnMapDblClick: TMapClickEvent read FOnMapDblClick write FOnMapDblClick;
    property OnMapIdle: TNotifyEvent read FOnMapIdle write FOnMapIdle;
    property OnMapPan: TMapClickEvent read FOnMapPan write FOnMapPan;
    property OnMapLoaded: TNotifyEvent read FOnMapLoaded write FOnMapLoaded;
    property OnMapZoom: TMapZoomEvent read FOnMapZoom write FOnMapZoom;
    property OnMarkerClick: TMapMarkerClickEvent read FOnMarkerClick write FOnMarkerClick;
    property OnPolygonClick: TMapPolygonClickEvent read FOnPolygonClick write FOnPolygonClick;
    property OnPolylineClick: TMapPolylineClickEvent read FOnPolylineClick write FOnPolylineClick;
    property OnCircleClick: TMapCircleClickEvent read FOnCircleClick write FOnCircleClick;
    property OnRectangleClick: TMapRectangleClickEvent read FOnRectangleClick write FOnRectangleClick;
    property OnKMLClick: TMapKMLClickEvent read FOnKMLClick write FOnKMLClick;
  end;

  TCICWebGoogleMaps = class(TCICGoogleMaps);


  { TCICCesium }
  TCesiumClickEvent = procedure(Sender: TObject; pLat: double; pLon: double) of object;
  TCesiumMouseMoveEvent = procedure(Sender: TObject; pLat: double; pLon: double) of object;

  TCICCesium = class; // ALE 20200225 forward declaration not yet needed

  TCICCesium = class(TCustomControl)
  private
    FUpdateCount: integer;
    FReq: TJSXMLHttpRequest;
    FAPIKey: string;
    FMap: TJSHTMLElement;
    FDirectionsService: TJSElement;
    FDirectionsDisplay: TJSElement;
    FCode: boolean;
    FBound: boolean;
    FOnMapClick: TMapClickEvent;
    FOnMapDblClick: TMapClickEvent;
    FOnMapZoom: TMapZoomEvent;
    FOnMapPan: TMapClickEvent;
    FOnGeoCoded: TMapClickEvent;
    FOnMapIdle: TNotifyEvent;
    FOnMapLoaded: TNotifyEvent;
    FOnMarkerClick: TMapMarkerClickEvent;
    FOnPolygonClick: TMapPolygonClickEvent;
    FOnPolylineClick: TMapPolylineClickEvent;
    FOnCircleClick: TMapCircleClickEvent;
    FOnRectangleClick: TMapRectangleClickEvent;
    FOnKMLClick: TMapKMLClickEvent;
    FOnCeslumClick: TCesiumClickEvent; // ALE 20200301 Cesium
    FOnCesiumMouseMove: TCesiumMouseMoveEvent; // ALE 20200309 Cesium
    FOptions: TCICGoogleMapsOptions;
    procedure SetAPIKey(const Value: string);
    function GetMarker(AIndex: integer): TJSObject;
    function GetCircle(AIndex: integer): TJSObject;
    function GetRectangle(AIndex: integer): TJSObject;
    function GetPolygon(AIndex: integer): TJSObject;
    function GetPolyline(AIndex: integer): TJSObject;
  protected
    function GetMap: TJSHTMLElement;
    function CreateElement: TJSElement; override;
    procedure UpdateElement; override;
    procedure BindEvents; override;
    procedure HandleMapClick(e: TJSEvent);
    procedure HandleMapDblClick(e: TJSEvent);
    procedure HandleMapPan(e: TJSEvent);
    procedure HandleMapZoom(e: TJSEvent);
    procedure HandleMapIdle(e: TJSEvent);
    procedure HandleMarkerClick(e: TJSEvent);
    procedure HandlePolygonClick(e: TJSEvent);
    procedure HandlePolylineClick(e: TJSEvent);
    procedure HandleCircleClick(e: TJSEvent);
    procedure HandleRectangleClick(e: TJSEvent);
    procedure HandleKMLClick(e: TJSEvent);
    procedure HandleViewerClick(e: TJSEvent);
    procedure HandleViewerMouseMove(e: TJSEvent);
    function HandleResponse(Event: TEventListenerEvent): boolean;
    function HandleAbort(Event: TEventListenerEvent): boolean;
    function TravelModeStr(ATravelMode: TGoogleTravelMode): string;
    function DoLoaded(Event: TEventListenerEvent): boolean; virtual;
    procedure Loaded; override;
  public
    procedure CreateInitialize; override;
    procedure SetCenter(Lat, Lon: Double);
    function GetCenter(var Lat, Lon: Double): Boolean;
    function GetBounds(var NorthEastLat, NorthEastLon, SouthWestLat, SouthWestLon: Double): Boolean;
    procedure SetDoubleClickZoom(AValue: Boolean);
    procedure SetDraggable(AValue: Boolean);
    procedure SetScrollWheel(AValue: Boolean);
    procedure SetMapType(AMapType: TGoogleMapType = mtDefault);
    procedure SetZoom(Zoom: integer);
    procedure PanTo(Lat, Lon: double);
    procedure AddGPX(AGPX: string; AColor: TColor = clRed; AWidth: integer = 2; AOpacity: Double = 1);
    procedure AddKML(Url: string; ZoomToBounds: Boolean = true);
    procedure AddPolyLine(Points: TJSArray; AColor: TColor = clRed;AWidth: integer = 2; AOpacity: Double = 1);
    procedure AddPolygon(Points: TJSArray; AFillColor: TColor = clRed; AStrokeColor: TColor = clBlack; AWidth: Integer = 2; AOpacity: Double = 1);
    procedure AddCircle(Lat, Lon: double; Radius: Integer; AFillColor: TColor = clRed; AStrokeColor: TColor = clBlack; AWidth: Integer = 2; AOpacity: double = 1);
    procedure AddRectangle(NorthEastLat, NorthEastLon, SouthWestLat, SouthWestLon: double; AFillColor: TColor = clRed; AStrokeColor: TColor = clBlack; AWidth: integer = 2; AOpacity: double = 1);
    procedure AddMarker(Lat,Lon: Double; Title: string = ''); overload;
    procedure AddMarker(Lat,Lon: Double; Color: TGoogleMarkerColor; Title: string = ''); overload;
    procedure AddMarker(Lat,Lon: Double; Color: TColor; PinLetter: string; Title: string = ''); overload;
    procedure AddMarker(Lat,Lon: Double; PinIcon: string; Title: string); overload;
    procedure AddMarker(Lat,Lon: Double; Shape: TGoogleMarkerShape; Color: TColor; Title: string = ''); overload;
    procedure AddMarker(Lat,Lon: Double; Shape: TGoogleMarkerShape; Color: TColor = clRed; BorderColor: TColor = clBlack; Scale: Double = 1; CustomShape: string = ''; Title: string = ''); overload;
    procedure SetMarkerTitle(AIndex: Integer; ATitle: string);
    procedure SetMarkerLocation(AIndex: Integer; Lat, Lon: Double);
    procedure SetMarkerIcon(AIndex: Integer; Url: string);
    procedure SetCircleCenter(AIndex: Integer; Lat, Lon: Double);
    procedure SetCircleRadius(AIndex, Radius: Integer);
    procedure SetCircleColors(AIndex: Integer; AFillColor, AStrokeColor: TColor);
    procedure SetRectangleLocation(AIndex: Integer; NorthEastLat, NorthEastLon, SouthWestLat, SouthWestLon: Double);
    procedure SetRectangleColors(AIndex: Integer; AFillColor, AStrokeColor: TColor);
    procedure SetPolylineColor(AIndex: Integer; AColor: TColor);
    procedure SetPolylinePoints(AIndex: Integer; Points: TJSArray);
    procedure SetPolygonColors(AIndex: Integer; AFillColor, AStrokeColor: TColor);
    procedure SetPolygonPoints(AIndex: Integer; Points: TJSArray);
    procedure FitBounds(LatMin, LonMin, LatMax, LonMax: Double);
    procedure ClearMarkers;
    procedure ClearKMLs;
    procedure ClearPolylines;
    procedure ClearPolygons;
    procedure ClearCircles;
    procedure ClearRectangles;
    procedure ShowDirections(Source, Destination: string; ATravelMode: TGoogleTravelMode = tmDriving; WayPoints: TStringList = nil; OptimizeWayPoints: Boolean = False; AvoidHighways: Boolean = False; AvoidTolls: Boolean = False); overload;
    procedure ShowDirections(SourceLon, SourceLat, DestLon, DestLat: Double; ATravelMode: TGoogleTravelMode = tmDriving; WayPoints: TStringList = nil; OptimizeWayPoints: Boolean = False; AvoidHighways: Boolean = False; AvoidTolls: Boolean = False); overload;
    procedure ShowStreetView(Lat, Lon: Double; Heading: Integer = 0; Zoom: Integer = 0; Pitch: Integer = 0);
    procedure HideStreetView;
    procedure RemoveDirections;
    procedure RemoveMarker(AIndex: Integer);
    procedure RemovePolygon(AIndex: Integer);
    procedure RemovePolyline(AIndex: Integer);
    procedure RemoveCircle(AIndex: Integer);
    procedure RemoveRectangle(AIndex: Integer);
    procedure GeoCode(const Address: string);
    procedure ConsoleLog(const pLogMessage: string);  // ALE 20200228 Cesium
    property Markers[AIndex: Integer]: TJSObject read GetMarker;
    property Polygons[AIndex: Integer]: TJSObject read GetPolygon;
    property Polylines[AIndex: Integer]: TJSObject read GetPolyline;
    property Circles[AIndex: Integer]: TJSObject read GetCircle;
    property Rectangles[AIndex: Integer]: TJSObject read GetRectangle;
  published
    property Align;
    property Options: TCICGoogleMapsOptions read FOptions write FOptions;
    property APIKey: string read FAPIKey write SetAPIKey;
    property OnGeoCoded: TMapClickEvent read FOnGeoCoded write FOnGeoCoded;
    property OnMapClick: TMapClickEvent read FOnMapClick write FOnMapClick;
    property OnMapDblClick: TMapClickEvent read FOnMapDblClick write FOnMapDblClick;
    property OnMapIdle: TNotifyEvent read FOnMapIdle write FOnMapIdle;
    property OnMapPan: TMapClickEvent read FOnMapPan write FOnMapPan;
    property OnMapLoaded: TNotifyEvent read FOnMapLoaded write FOnMapLoaded;
    property OnMapZoom: TMapZoomEvent read FOnMapZoom write FOnMapZoom;
    property OnMarkerClick: TMapMarkerClickEvent read FOnMarkerClick write FOnMarkerClick;
    property OnPolygonClick: TMapPolygonClickEvent read FOnPolygonClick write FOnPolygonClick;
    property OnPolylineClick: TMapPolylineClickEvent read FOnPolylineClick write FOnPolylineClick;
    property OnCircleClick: TMapCircleClickEvent read FOnCircleClick write FOnCircleClick;
    property OnRectangleClick: TMapRectangleClickEvent read FOnRectangleClick write FOnRectangleClick;
    property OnKMLClick: TMapKMLClickEvent read FOnKMLClick write FOnKMLClick;
    property OnViewerClick: TCesiumClickEvent read FOnCeslumClick write FOnCeslumClick; // ALE 20200301 Cesium
    property OnViewerMouseMove: TCesiumMouseMoveEvent read FOnCesiumMouseMove write FOnCesiumMouseMove; // ALE 20200309 Cesium
  end;

  TCICWebCesium = class(TCICCesium);

implementation

{ TCICGoogleMaps }

{$HINTS OFF}
procedure TCICGoogleMaps.AddPolyLine(Points: TJSArray; AColor: TColor = clRed; AWidth: integer = 2; AOpacity: double = 1);
var
  map: TJSHTMLElement;
  clr: string;
  ptr: Pointer;
begin
  ptr := @HandlePolylineClick;
  map := GetMap;
  clr := ColorToHTML(AColor);

  asm
    var poly = new google.maps.Polyline({
      map: map,
  	  path: Points,
	    strokeColor: clr,
  	  strokeOpacity: AOpacity,
  	  strokeWeight: AWidth
  	});

    poly.idx = this.FPolylines.length;
    poly.addListener('click', function() { ptr(poly); } );
    this.FPolylines.push(poly);
  end;
end;

procedure TCICGoogleMaps.AddRectangle(NorthEastLat, NorthEastLon, SouthWestLat,
  SouthWestLon: double; AFillColor, AStrokeColor: TColor; AWidth: integer;
  AOpacity: double);
var
  map: TJSHTMLElement;
  clrF,clrS: string;
  ptr: Pointer;
begin
  ptr := @HandleRectangleClick;
  map := GetMap;
  clrF := ColorToHTML(AFillColor);
  clrS := ColorToHTML(AStrokeColor);

  asm
    var NELatLng = {lat: NorthEastLat, lng: NorthEastLon};
    var SWLatLng = {lat: SouthWestLat, lng: SouthWestLon};
    var rect = new google.maps.Rectangle({
      map: map,
  	  bounds: new google.maps.LatLngBounds(NELatLng, SWLatLng),
      fillColor: clrF,
      fillOpacity: AOpacity,
	    strokeColor: clrS,
  	  strokeOpacity: AOpacity,
  	  strokeWeight: AWidth
  	});

    rect.idx = this.FRectangles.length;
    rect.addListener('click', function() { ptr(rect); } );
    this.FRectangles.push(rect);
  end;
end;

procedure TCICGoogleMaps.AddPolygon(Points: TJSArray; AFillColor: TColor = clRed; AStrokeColor: TColor = clBlack; AWidth: integer = 2; AOpacity: double = 1);
var
  map: TJSHTMLElement;
  clrF,clrS: string;
  ptr: Pointer;
begin
  ptr := @HandlePolygonClick;
  map := GetMap;
  clrF := ColorToHTML(AFillColor);
  clrS := ColorToHTML(AStrokeColor);

  asm
    var poly = new google.maps.Polygon({
      map: map,
  	  paths: Points,
      fillColor: clrF,
      fillOpacity: AOpacity,
	    strokeColor: clrS,
  	  strokeOpacity: AOpacity,
  	  strokeWeight: AWidth
  	});

    poly.idx = this.FPolygons.length;
    poly.addListener('click', function() { ptr(poly); } );
    this.FPolygons.push(poly);
  end;
end;

procedure TCICGoogleMaps.AddCircle(Lat, Lon: double; Radius: Integer;
  AFillColor, AStrokeColor: TColor; AWidth: integer; AOpacity: double);
var
  map: TJSHTMLElement;
  clrF,clrS: string;
  ptr: Pointer;
begin
  ptr := @HandleCircleClick;
  map := GetMap;
  clrF := ColorToHTML(AFillColor);
  clrS := ColorToHTML(AStrokeColor);

  asm
    var myLatLng = {lat: Lat, lng: Lon};
    var circle = new google.maps.Circle({
      map: map,
  	  center: myLatLng,
      radius: Radius,
      fillColor: clrF,
      fillOpacity: AOpacity,
	    strokeColor: clrS,
  	  strokeOpacity: AOpacity,
  	  strokeWeight: AWidth
  	});

    circle.idx = this.FCircles.length;
    circle.addListener('click', function() { ptr(circle); } );
    this.FCircles.push(circle);
  end;
end;

procedure TCICGoogleMaps.AddMarker(Lat, Lon: double; Title: string);
var
  map: TJSHTMLElement;
  ptr: Pointer;
begin
  ptr := @HandleMarkerClick;
  map := GetMap;
  asm
    var myLatLng = {lat: Lat, lng: Lon};
    var marker = new google.maps.Marker({
          position: myLatLng,
          map: map,
          title: Title
        });
    marker.idx = this.FMarkers.length;
    marker.addListener('click', function() { ptr(marker); } );
    this.FMarkers.push(marker);
  end;
end;

procedure TCICGoogleMaps.AddMarker(Lat, Lon: double; Color: TGoogleMarkerColor; Title: string);
var
  map: TJSHTMLElement;
  clr: string;
  ptr: Pointer;
begin
  ptr := @HandleMarkerClick;

  case Color of
  mcDefault: clr := '';
  mcRed: clr := 'red-dot.png';
  mcGreen: clr := 'green-dot.png';
  mcYellow: clr := 'yellow-dot.png';
  mcPurple: clr := 'purple-dot.png';
  mcBlue: clr := 'blue-dot.png';
  end;

  map := GetMap;
  asm
    var myLatLng = {lat: Lat, lng: Lon};

    var marker = new google.maps.Marker({
          position: myLatLng,
          map: map,
          title: Title
        });

    if (clr != "") {
      marker.setIcon('http://maps.google.com/mapfiles/ms/icons/' + clr);
    }
    marker.idx = this.FMarkers.length;
    marker.addListener('click', function() { ptr(marker); } );

    this.FMarkers.push(marker);
  end;
end;

procedure TCICGoogleMaps.AddMarker(Lat, Lon: double; Color: TColor; PinLetter: string; Title: string);
var
  map: TJSHTMLElement;
  clr,url: string;
  ch: string;
  ptr: Pointer;
begin
  ptr := @HandleMarkerClick;

  clr := ColorToHTML(Color);
  delete(clr,1,1);
  map := GetMap;
  asm
    var myLatLng = {lat: Lat, lng: Lon};

    url = "http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld="+ PinLetter + "|" + clr;

    var pinImage = new google.maps.MarkerImage(url);

    var marker = new google.maps.Marker({
          position: myLatLng,
          map: map,
          title: Title,
          icon: pinImage
        });
    marker.idx = this.FMarkers.length;
    marker.addListener('click', function() { ptr(marker); } );

    this.FMarkers.push(marker);
  end;
end;

procedure TCICGoogleMaps.AddMarker(Lat, Lon: double; PinIcon, Title: string);
var
  map: TJSHTMLElement;
  ptr: Pointer;
begin
  ptr := @HandleMarkerClick;

  map := GetMap;
  asm
    var pinImage = new google.maps.MarkerImage(PinIcon);
    var myLatLng = {lat: Lat, lng: Lon};
    var marker = new google.maps.Marker({
          position: myLatLng,
          map: map,
          title: Title,
          icon: pinImage
        });
    marker.idx = this.FMarkers.length;
    marker.addListener('click', function() { ptr(marker); } );

    this.FMarkers.push(marker);
  end;
end;

procedure TCICGoogleMaps.AddMarker(Lat, Lon: double; Shape: TGoogleMarkerShape;
  Color: TColor; Title: string);
 begin
   AddMarker(Lat, Lon, Shape, Color, clBlack, 1, Title);
 end;

procedure TCICGoogleMaps.AddGPX(AGPX: string; AColor: TColor; AWidth: integer;
  AOpacity: double);
var
  Points: TJSArray;
  LatMax,LonMax,LatMin,LonMin: double;
begin
  Points := TJSArray.new;
  asm
    var parser = new DOMParser();
    var doc = parser.parseFromString(AGPX, 'text/xml');
    var trk = doc.getElementsByTagName("trk")[0];
    var name = trk.getElementsByTagName("name")[0];
    var trkseg = doc.getElementsByTagName("trkseg")[0];
    var i = trkseg.getElementsByTagName("trkpt").length;
    var trkpt;
    var j;
    var lat;
    var lon;
    var bounds = new google.maps.LatLngBounds ();

    for (j= 0; j < i; j++)
    {
      trkpt = trkseg.getElementsByTagName("trkpt")[j];
      lat = trkpt.getAttribute('lat');
      lon = trkpt.getAttribute('lon');

      var p = new google.maps.LatLng(lat, lon);
  	  Points.push(p);
      bounds.extend(p);
    }
    LatMax = bounds.getNorthEast().lat();
    LonMax = bounds.getNorthEast().lng();

    LatMin = bounds.getSouthWest().lat();
    LonMin = bounds.getSouthWest().lng();
  end;

  AddPolyLine(Points, AColor, AWidth, AOpacity);
  FitBounds(LatMin, LonMin, LatMax, LonMax);
end;

procedure TCICGoogleMaps.AddKML(Url: string; ZoomToBounds: Boolean);
var
  map: TJSHTMLElement;
  clr: string;
  ptr: Pointer;
begin
  ptr := @HandleKMLClick;
  map := GetMap;

  asm
    var kml = new google.maps.KmlLayer(Url, {
      map: map,
      clickable: true,
      preserveViewport: !ZoomToBounds
  	});

    kml.idx = this.FKMLs.length;
    kml.addListener('click', function() { ptr(kml); } );
    this.FKMLs.push(kml);
  end;
end;

procedure TCICGoogleMaps.AddMarker(Lat, Lon: double; Shape: TGoogleMarkerShape;
  Color: TColor; BorderColor: TColor; Scale: Double; CustomShape: string; Title: string);
var
  map: TJSHTMLElement;
  clr: string;
  bclr: string;
  pth: string;
  scl: string;
  ptr: pointer;
begin
  ptr := @HandleMarkerClick;
  map := GetMap;
  clr := ColorToHTML(Color);
  bclr := ColorToHTML(BorderColor);
  scl := StringReplace(FloatToStr(Scale), ',', '.', []);

  case Shape of
  msPin: pth:= 'M 0,0 C -2,-20 -10,-22 -10,-30 A 10,10 0 1,1 10,-30 C 10,-22 2,-20 0,0 z';
  msPinDot: pth := 'M 0,0 C -2,-20 -10,-22 -10,-30 A 10,10 0 1,1 10,-30 C 10,-22 2,-20 0,0 z M -2,-30 a 2,2 0 1,1 4,0 2,2 0 1,1 -4,0';
  msFlag:  pth := 'M 0,0 -1,-2 V -43 H 1 V -2 z M 1,-40 H 30 V -20 H 1 z';
  msBookmark: pth := 'M17 3h-10c-1.1 0-1.99.9-1.99 2l-.01 16 7-3 7 3v-16c0-1.1-.9-2-2-2z';
  msFlagSmall: pth := 'M14.4 6l-.4-2h-9v17h2v-7h5.6l.4 2h7v-10z';
  msHome: pth := 'M10 20v-6h4v6h5v-8h3l-10-9-10 9h3v8z';
  msFavorite: pth := 'M12 21.35l-1.45-1.32c-5.15-4.67-8.55-7.75-8.55-11.53 0-3.08 2.42-5.5 5.5-5.5 1.74 0 3.41.81 4.5 2.09 1.09-1.28 2.76-2.09 4.5-2.09 3.08 0 5.5 2.42 5.5 5.5 0 3.78-3.4 6.86-8.55 11.54l-1.45 1.31z';
  msStar: pth := 'M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z';
  msCustom: pth := CustomShape;
  end;

  asm
    function pinSymbol(color, bcolor, scale) {
    return {
        path: pth,
        fillColor: color,
        fillOpacity: 1,
        strokeColor: bcolor,
        strokeWeight: 1,
        scale: parseFloat(scale),
        };
    }
    var myLatLng = {lat: Lat, lng: Lon};
    var marker = new google.maps.Marker({
          position: myLatLng,
          map: map,
          title: Title,
          icon: pinSymbol(clr, bclr, scl)
        });
    marker.idx = this.FMarkers.length;
    marker.addListener('click', function() { ptr(marker); } );

    this.FMarkers.push(marker);
  end;
end;

procedure TCICGoogleMaps.BindEvents;
var
  map: TJSHTMLElement;
  ptr1,ptr2,ptr3,ptr4,ptr5: pointer;
  dirsvc, dirdispl: TJSElement;
begin
  inherited;

  if IsUpdating then
    Exit;

  map := GetMap;

  if Assigned(map) and not FBound then
  begin
    ptr1 := @HandleMapClick;
    ptr2 := @HandleMapDblClick;
    ptr3 := @HandleMapPan;
    ptr4 := @HandleMapZoom;
    ptr5 := @HandleMapIdle;

    asm
      this.FMarkers = [];
      this.FPolygons = [];
      this.FPolylines = [];
      this.FCircles = [];
      this.FRectangles = [];
      this.FKMLs = [];
    end;


    asm
      dirsvc = new google.maps.DirectionsService;
      dirdispl = new google.maps.DirectionsRenderer;
      dirdispl.setMap(map);
      map.addListener('click', ptr1);
      map.addListener('dblclick', ptr2);
      map.addListener('center_changed',ptr3);
      map.addListener('zoom_changed',ptr4);
      map.addListener('tilesloaded',ptr5);
    end;

    FDirectionsService := dirsvc;
    FDirectionsDisplay := dirdispl;

    FBound := true;
  end;
end;
{$HINTS ON}


procedure TCICGoogleMaps.ClearCircles;
begin
  asm
    for (var i = 0; i < this.FCircles.length; i++) {
          this.FCircles[i].setMap(null);
          }
    this.FMarkers = [];
  end;
end;

procedure TCICGoogleMaps.ClearKMLs;
begin
  asm
    for (var i = 0; i < this.FKMLs.length; i++) {
          this.FKMLs[i].setMap(null);
          }
    this.FKMLs = [];
  end;
end;

procedure TCICGoogleMaps.ClearMarkers;
begin
  asm
    for (var i = 0; i < this.FMarkers.length; i++) {
          this.FMarkers[i].setMap(null);
          }
    this.FMarkers = [];
  end;
end;

procedure TCICGoogleMaps.ClearPolygons;
begin
  asm
    for (var i = 0; i < this.FPolygons.length; i++) {
          this.FPolygons[i].setMap(null);
          }
    this.FPolyLines = [];
  end;
end;

procedure TCICGoogleMaps.ClearPolyLines;
begin
  asm
    for (var i = 0; i < this.FPolylines.length; i++) {
          this.FPolylines[i].setMap(null);
          }
    this.FPolyLines = [];
  end;
end;

procedure TCICGoogleMaps.ClearRectangles;
begin
  asm
    for (var i = 0; i < this.FRectangles.length; i++) {
          this.FRectangles[i].setMap(null);
          }
    this.FRectangles = [];
  end;
end;

function TCICGoogleMaps.CreateElement: TJSElement;
begin
  FDirectionsService := nil;
  FDirectionsDisplay := nil;

  Result := document.createElement('DIV');
  Result.setAttribute('id', GetID);
  FMap := nil;
end;

procedure TCICGoogleMaps.CreateInitialize;
begin
  inherited;
  FOptions := TCICGoogleMapsOptions.Create(Self);
  Width := 400;
  Height := 300;
end;

function TCICGoogleMaps.DoLoaded(Event: TEventListenerEvent): boolean;
begin
  BindEvents;
  Result := true;

  Options.SetMapStyle(Options.MapStyle);

  if Assigned(OnMapLoaded) then
    OnMapLoaded(Self);
end;

{$HINTS OFF}
procedure TCICGoogleMaps.FitBounds(LatMin, LonMin, LatMax, LonMax: double);
var
  map: TJSHTMLElement;
begin
  map := GetMap;

  asm
    var ne = new google.maps.LatLng(LatMax, LonMax);
    var sw = new google.maps.LatLng(LatMin, LonMin);
    var bounds = new google.maps.LatLngBounds(sw,ne);
    map.fitBounds(bounds);
  end;
end;
{$HINTS ON}

procedure TCICGoogleMaps.Loaded;
begin
  inherited;
end;

procedure TCICGoogleMaps.GeoCode(const Address: string);
var
  FURL: string;
begin
  FReq := TJSXMLHttpRequest.new;
  FReq.addEventListener('load', @HandleResponse);
  FReq.addEventListener('abort',@HandleAbort);

  FURL := 'https://maps.googleapis.com/maps/api/geocode/json?address=' + encodeURIComponent(Address) +'&key=' + APIKey;
  FReq.open('GET', FURL);
  FReq.send;
end;

{$HINTS OFF}
procedure TCICGoogleMaps.PanTo(Lat, Lon: double);
var
  map: TJSHTMLElement;
begin
  map := GetMap;
  if Assigned(map) then
  begin
    asm
      map.panTo(new google.maps.LatLng(Lat, Lon));
    end;
  end;
end;

function TCICGoogleMaps.GetMap: TJSHTMLElement;
var
  map: TJSHTMLElement;
  id: string;
begin
  if Assigned(FMap) then
    Result := FMap
  else
  begin
    map := nil;

    id := GetID;
    asm
      var el = document.getElementById(id);
      if (el != null) {
        map = el.gMap;
      }
    end;

    Result := map;
    if Assigned(map) then
      FMap := Result;
  end;
end;

function TCICGoogleMaps.GetBounds(var NorthEastLat, NorthEastLon, SouthWestLat,
  SouthWestLon: Double): Boolean;
var
  neLon, neLat, swLon, swLat: String;
begin
  Result := false;
  NorthEastLon := -1;
  NorthEastLat := -1;
  SouthWestLon := -1;
  SouthWestLat := -1;

  asm
    neLon = map.getBounds().getNorthEast().lng().toString();
    neLat = map.getBounds().getNorthEast().lat().toString();
    swLon = map.getBounds().getSouthWest().lng().toString();
    swLat = map.getBounds().getSouthWest().lat().toString();
  end;

  if (neLon <> '') and (neLat <> '') and (swLon <> '') and (swLat <> '') then
  begin
    NorthEastLon := StrToFloat(swLon);
    NorthEastLat := StrToFloat(swLat);
    SouthWestLon := StrToFloat(swLon);
    SouthWestLat := StrToFloat(swLat);
    Result := true;
  end;
end;

function TCICGoogleMaps.GetCenter(var Lat, Lon: Double): Boolean;
var
  sLon, sLat: String;
begin
  Result := false;
  Lon := -1;
  Lat := -1;

  asm
    sLon = map.getCenter().lng().toString();
    sLat = map.getCenter().lat().toString();
  end;

  if (sLon <> '') and (sLat <> '') then
  begin
    Lon := StrToFloat(sLon);
    Lat := StrToFloat(sLat);
    Result := true;
  end;
end;

function TCICGoogleMaps.GetCircle(AIndex: integer): TJSObject;
var
  res: TJSObject;
begin
  asm
    res = this.FCircles[AIndex];
  end;

  Result := res;
end;


function TCICGoogleMaps.GetMarker(AIndex: integer): TJSObject;
var
  res: TJSObject;
begin
  asm
    res = this.FMarkers[AIndex];
  end;

  Result := res;
end;

function TCICGoogleMaps.GetPolygon(AIndex: integer): TJSObject;
var
  res: TJSObject;
begin
  asm
    res = this.FPolygon[AIndex];
  end;

  Result := res;
end;

function TCICGoogleMaps.GetPolyline(AIndex: integer): TJSObject;
var
  res: TJSObject;
begin
  asm
    res = this.FPolyline[AIndex];
  end;

  Result := res;
end;

function TCICGoogleMaps.GetRectangle(AIndex: integer): TJSObject;
var
  res: TJSObject;
begin
  asm
    res = this.FGetRectangle[AIndex];
  end;

  Result := res;
end;

function TCICGoogleMaps.HandleAbort(Event: TEventListenerEvent): boolean;
begin
  //
  Result := true;
end;

procedure TCICGoogleMaps.HandleCircleClick(e: TJSEvent);
var
  idx: integer;
begin
  asm
    idx = e.idx;
  end;

  if Assigned(OnCircleClick) then
    OnCircleClick(Self, idx, TJSObject(e));
end;

procedure TCICGoogleMaps.HandleKMLClick(e: TJSEvent);
var
  idx: integer;
begin
  asm
    idx = e.idx;
  end;

  if Assigned(OnKMLClick) then
    OnKMLClick(Self, idx, TJSObject(e));
end;

procedure TCICGoogleMaps.HandleRectangleClick(e: TJSEvent);
var
  idx: integer;
begin
  asm
    idx = e.idx;
  end;

  if Assigned(OnRectangleClick) then
    OnRectangleClick(Self, idx, TJSObject(e));
end;

procedure TCICGoogleMaps.HandleMapClick(e: TJSEvent);
var
  lon,lat: double;
begin
  asm
    lon = e.latLng.lng();
    lat = e.latLng.lat();
  end;

  if Assigned(OnMapClick) then
    OnMapClick(Self, lon, lat);
end;

procedure TCICGoogleMaps.HandleMapDblClick(e: TJSEvent);
var
  lon,lat: double;
begin
  asm
    lon = e.latLng.lng();
    lat = e.latLng.lat();
  end;

  if Assigned(OnMapDblClick) then
    OnMapDblClick(Self, lon, lat);
end;

procedure TCICGoogleMaps.HandleMapIdle(e: TJSEvent);
begin
  if Assigned(OnMapIdle) then
    OnMapIdle(Self);
end;

procedure TCICGoogleMaps.HandleMapPan(e: TJSEvent);
var
  lon,lat: double;
begin
  asm
    var c = map.getCenter();
    lon = c.lng();
    lat = c.lat();
  end;

  if Assigned(OnMapPan) then
    OnMapPan(Self, lon, lat);
end;

procedure TCICGoogleMaps.HandleMapZoom(e: TJSEvent);
var
  map: TJSHTMLElement;
  zoom: integer;
begin
  map := GetMap;
  asm
    zoom = map.getZoom();
  end;
  if Assigned(OnMapZoom) then
    OnMapZoom(Self, zoom);
end;

procedure TCICGoogleMaps.HandleMarkerClick(e: TJSEvent);
var
  idx: integer;
begin
  asm
    idx = e.idx;
  end;

  if Assigned(OnMarkerClick) then
    OnMarkerClick(Self, idx, TJSObject(e));
end;


procedure TCICGoogleMaps.HandlePolygonClick(e: TJSEvent);
var
  idx: integer;
begin
  asm
    idx = e.idx;
  end;

  if Assigned(OnPolygonClick) then
    OnPolygonClick(Self, idx, TJSObject(e));
end;

procedure TCICGoogleMaps.HandlePolylineClick(e: TJSEvent);
var
  idx: integer;
begin
  asm
    idx = e.idx;
  end;

  if Assigned(OnPolylineClick) then
    OnPolylineClick(Self, idx, TJSObject(e));
end;

function TCICGoogleMaps.HandleResponse(Event: TEventListenerEvent): boolean;
var
  fnd: boolean;
  reslat, reslon: double;
begin
  Result := true;
  fnd := false;

  asm
    var s = Event.target.responseText;
    var js = JSON.parse(s);
    if (js.status == "OK") {
      fnd = true;
      var lat = js.results[0].geometry.location.lat;
      var lng = js.results[0].geometry.location.lng;
      reslon = lng;
      reslat = lat;
    }
  end;

  if fnd and Assigned(OnGeoCoded) then
  begin
    OnGeoCoded(Self, reslon, reslat);
  end;
end;

procedure TCICGoogleMaps.RemoveDirections;
var
  dirdispl: TJSElement;
begin
  if not Assigned(FDirectionsDisplay) then
    BindEvents;

  dirdispl := FDirectionsDisplay;

  asm
    dirdispl.setMap(null);
  end;
end;
{$HINTS ON}


procedure TCICGoogleMaps.SetAPIKey(const Value: string);
begin
  if FAPIKey <> Value then
  begin
    FAPIKey := Value;
    UpdateElement;
  end;
end;

{$HINTS OFF}
procedure TCICGoogleMaps.SetCenter(Lat, Lon: double);
var
  map: TJSHTMLElement;
begin
  map := GetMap;
  asm
    map.setCenter(new google.maps.LatLng(Lat, Lon));
  end;
end;

procedure TCICGoogleMaps.SetDoubleClickZoom(AValue: Boolean);
var
  map: TJSHTMLElement;
begin
  map := GetMap;
  asm
    map.setOptions( {disableDoubleClickZoom:!AValue} );
  end;
end;

procedure TCICGoogleMaps.SetDraggable(AValue: Boolean);
var
  map: TJSHTMLElement;
begin
  map := GetMap;
  asm
    map.setOptions( {draggable:AValue} );
  end;
end;

procedure TCICGoogleMaps.SetMapType(AMapType: TGoogleMapType);
var
  map: TJSHTMLElement;
  smt: string;
begin
  map := GetMap;

  case AMapType of
    mtDefault: smt := 'roadmap';
    mtSatellite: smt := 'satellite';
    mtHybrid: smt := 'hybrid';
    mtTerrain: smt := 'terrain';
  end;

  asm
    map.setMapTypeId(smt);
  end;
end;

procedure TCICGoogleMaps.RemoveCircle(AIndex: Integer);
begin
  asm
    if (AIndex < this.FPolygons.length)
    {
      this.FCircles[AIndex].setMap(null);
      this.FCircles.splice(AIndex, 1);
    }
  end;
end;


procedure TCICGoogleMaps.RemoveMarker(AIndex: Integer);
begin
  asm
    if (AIndex < this.FMarkers.length)
    {
      this.FMarkers[AIndex].setMap(null);
      this.FMarkers.splice(AIndex, 1);
    }
  end;
end;

procedure TCICGoogleMaps.RemovePolygon(AIndex: Integer);
begin
  asm
    if (AIndex < this.FPolygons.length)
    {
      this.FPolygons[AIndex].setMap(null);
      this.FPolygons.splice(AIndex, 1);
    }
  end;
end;

procedure TCICGoogleMaps.RemovePolyline(AIndex: Integer);
begin
  asm
    if (AIndex < this.FPolylines.length)
    {
      this.FPolylines[AIndex].setMap(null);
      this.FPolylines.splice(AIndex, 1);
    }
  end;
end;

procedure TCICGoogleMaps.RemoveRectangle(AIndex: Integer);
begin
  asm
    if (AIndex < this.FPolygons.length)
    {
      this.FRectangles[AIndex].setMap(null);
      this.FRectangles.splice(AIndex, 1);
    }
  end;
end;

procedure TCICGoogleMaps.SetMarkerIcon(AIndex: Integer; Url: string);
begin
  asm
    if (AIndex < this.FMarkers.length)
    {
      this.FMarkers[AIndex].setIcon(Url);
    }
  end;
end;

procedure TCICGoogleMaps.SetMarkerLocation(AIndex: Integer; Lat, Lon: Double);
begin
  asm
    if (AIndex < this.FMarkers.length)
    {
      this.FMarkers[AIndex].setPosition(new google.maps.LatLng(Lat, Lon));
    }
  end;
end;

procedure TCICGoogleMaps.SetMarkerTitle(AIndex: Integer; ATitle: string);
begin
  asm
    if (AIndex < this.FMarkers.length)
    {
      this.FMarkers[AIndex].setTitle(ATitle);
    }
  end;
end;

procedure TCICGoogleMaps.SetPolygonColors(AIndex: Integer; AFillColor,
  AStrokeColor: TColor);
var
  clrF, clrS: string;
begin
  clrF := ColorToHTML(AFillColor);
  clrS := ColorToHTML(AStrokeColor);
  asm
    if (AIndex < this.FPolygons.length)
    {
      this.FPolygons[AIndex].setOptions({ fillColor: clrF, strokeColor: clrS });
    }
  end;
end;

procedure TCICGoogleMaps.SetPolygonPoints(AIndex: Integer; Points: TJSArray);
begin
  asm
    if (AIndex < this.FPolygons.length)
    {
      this.FPolygons[AIndex].setOptions({ path: Points });
    }
  end;
end;

procedure TCICGoogleMaps.SetPolylineColor(AIndex: Integer; AColor: TColor);
var
  clr: string;
begin
  clr := ColorToHTML(AColor);
  asm
    if (AIndex < this.FPolylines.length)
    {
      this.FPolylines[AIndex].setOptions({ strokeColor: clr });
    }
  end;
end;

procedure TCICGoogleMaps.SetPolylinePoints(AIndex: Integer; Points: TJSArray);
begin
  asm
    if (AIndex < this.FPolylines.length)
    {
      this.FPolylines[AIndex].setOptions({ path: Points });
    }
  end;
end;

procedure TCICGoogleMaps.SetRectangleColors(AIndex: Integer; AFillColor,
  AStrokeColor: TColor);
var
  clrF, clrS: string;
begin
  clrF := ColorToHTML(AFillColor);
  clrS := ColorToHTML(AStrokeColor);
  asm
    if (AIndex < this.FRectangles.length)
    {
      this.FRectangles[AIndex].setOptions({ fillColor: clrF, strokeColor: clrS });
    }
  end;
end;

procedure TCICGoogleMaps.SetRectangleLocation(AIndex: Integer; NorthEastLat,
  NorthEastLon, SouthWestLat, SouthWestLon: double);
begin
  asm
    if (AIndex < this.FRectangles.length)
    {
      var NELatLng = {lat: NorthEastLat, lng: NorthEastLon};
      var SWLatLng = {lat: SouthWestLat, lng: SouthWestLon};
      this.FRectangles[AIndex].setOptions({ bounds: new google.maps.LatLngBounds(NELatLng, SWLatLng) });
    }
  end;
end;

procedure TCICGoogleMaps.SetCircleCenter(AIndex: Integer; Lat, Lon: Double);
begin
  asm
    if (AIndex < this.FCircles.length)
    {
      this.FCircles[AIndex].setCenter(new google.maps.LatLng(Lat, Lon));
    }
  end;
end;

procedure TCICGoogleMaps.SetCircleColors(AIndex: Integer; AFillColor,
  AStrokeColor: TColor);
var
  clrF, clrS: string;
begin
  clrF := ColorToHTML(AFillColor);
  clrS := ColorToHTML(AStrokeColor);
  asm
    if (AIndex < this.FCircles.length)
    {
      this.FCircles[AIndex].setOptions({ fillColor: clrF, strokeColor: clrS });
    }
  end;
end;

procedure TCICGoogleMaps.SetCircleRadius(AIndex, Radius: Integer);
begin
  asm
    if (AIndex < this.FCircles.length)
    {
      this.FCircles[AIndex].setRadius(Radius);
    }
  end;
end;

procedure TCICGoogleMaps.SetScrollWheel(AValue: Boolean);
var
  map: TJSHTMLElement;
begin
  map := GetMap;
  asm
    map.setOptions( {scrollWheel:AValue} );
  end;
end;

procedure TCICGoogleMaps.SetZoom(Zoom: Integer);
var
  map: TJSHTMLElement;
begin
  map := GetMap;
  if Assigned(map) then
  begin
    asm
      map.setZoom(Zoom);
    end;
  end;
end;

procedure TCICGoogleMaps.ShowDirections(SourceLon, SourceLat, DestLon,
  DestLat: double; ATravelMode: TGoogleTravelMode = tmDriving;
  WayPoints: TStringList = nil; OptimizeWayPoints: Boolean = False;
  AvoidHighways: Boolean = False; AvoidTolls: Boolean = False);
var
  dirsvc,dirdispl,map: TJSElement;
  tm: string;
  wp: TJSArray;
  I: Integer;
begin
  if not Assigned(FDirectionsDisplay) then
    BindEvents;

  dirsvc := FDirectionsService;
  dirdispl := FDirectionsDisplay;
  map := GetMap;
  tm := TravelModeStr(ATravelMode);
  wp := TJSArray.New;
  if WayPoints <> nil then
  begin
    for I := 0 to WayPoints.Count - 1 do
      wp.Push(New(['location', WayPoints[I]]));
  end;

  asm
    var sourcelatlon = new google.maps.LatLng(SourceLat, SourceLon);
    var destlatlon = new google.maps.LatLng(DestLat, DestLon);

    dirdispl.setMap(map);
    dirsvc.route({
          origin: sourcelatlon,
          destination: destlatlon,
          waypoints: wp,
          optimizeWaypoints: OptimizeWayPoints,
          avoidHighways: AvoidHighways,
          avoidTolls: AvoidTolls,
          travelMode: tm
        }, function(response, status) {
          if (status === 'OK') {
            dirdispl.setDirections(response);
          } else {
            window.alert('Directions request failed due to ' + status);
          }
        });
  end;
end;

procedure TCICGoogleMaps.HideStreetView;
var
  map: TJSElement;
begin
  map := GetMap;
  asm
    map.streetView.setVisible(false);
  end;
end;

procedure TCICGoogleMaps.ShowStreetView(Lat, Lon: Double; Heading, Zoom,
  Pitch: Integer);
var
  map: TJSElement;
  mapid: string;
begin
  map := GetMap;
  mapid := GetID;
  asm
    var LatLng = {lat: Lat, lng: Lon};
    var panorama = new google.maps.StreetViewPanorama(
        document.getElementById(mapid), {
          position: LatLng,
          pov: {
            heading: Heading,
            zoom: Zoom,
            pitch: Pitch
          }
        });
    map.setStreetView(panorama);
  end;
end;

procedure TCICGoogleMaps.ShowDirections(Source, Destination: string;
  ATravelMode: TGoogleTravelMode; WayPoints: TStringList; OptimizeWayPoints,
  AvoidHighways, AvoidTolls: Boolean);
var
  dirsvc,dirdispl,map: TJSElement;
  tm: string;
  wp: TJSArray;
  I: Integer;
begin
  if not Assigned(FDirectionsDisplay) then
    BindEvents;

  dirsvc := FDirectionsService;
  dirdispl := FDirectionsDisplay;
  map := GetMap;
  tm := TravelModeStr(ATravelMode);
  wp := TJSArray.New;
  if WayPoints <> nil then
  begin
    for I := 0 to WayPoints.Count - 1 do
      wp.Push(New(['location', WayPoints[I]]));
  end;

  asm
    dirdispl.setMap(map);

    dirsvc.route({
          origin: Source,
          destination: Destination,
          waypoints: wp,
          optimizeWaypoints: OptimizeWayPoints,
          avoidHighways: AvoidHighways,
          avoidTolls: AvoidTolls,
          travelMode: tm
        }, function(response, status) {
          if (status === 'OK') {
            dirdispl.setDirections(response);
          } else {
            window.alert('Directions request failed due to ' + status);
          }
        });
  end;
end;

function TCICGoogleMaps.TravelModeStr(ATravelMode: TGoogleTravelMode): string;
begin
  Result := 'DRIVING';
  case ATravelMode of
    tmDriving: Result := 'DRIVING';
    tmWalking: Result := 'WALKING';
    tmBicycling: Result := 'BICYCLING';
    tmTransit: Result := 'TRANSIT';
  end;
end;

procedure TCICGoogleMaps.UpdateElement;
var
  map: TJSHTMLElement;
  id: string;
  srcurl: string;
  scriptsrc: string;
  scr: TJSElement;
begin
  inherited;

  if IsUpdating then
    Exit;

  if FUpdateCount > 0 then
    Exit;

  if not FCode and (FAPIKey <> '') then
  begin
    FCode := true;
    id := GetID;

    srcurl := 'https://maps.googleapis.com/maps/api/js?key=' + FAPIKey + '&callback=initMap'+id;

    scriptsrc := 'function initMap'+id+'() { '+#13#10+
         'var el = document.getElementById("'+id+'");'+#13#10+
         'if (el == null) { alert("Google Maps DIV element not found!"); }'+#13#10+
         'map = new google.maps.Map(el, {'+#13#10+
         'center: {lat: ' + StringReplace(FloatToStr(Options.DefaultLatitude), ',', '.', [rfReplaceAll]) + ', lng: ' + StringReplace(FloatToStr(Options.DefaultLongitude), ',', '.', [rfReplaceAll]) + '},'+#13#10+
         'zoom: ' + IntToStr(Options.DefaultZoomLevel) + '});'+#13#10+
         'el.gMap = map;'+#13#10+
         '}';

    scr := document.createElement('script');
    scr.addEventListener('load',@DoLoaded);
    asm
      scr.defer = true;
      scr.async = true;
      scr.src = srcurl;
      scr.type = 'text/javascript';
    end;
    document.head.appendChild(scr);

    asm
      var script = document.createElement('script');
      script.innerHTML = scriptsrc;
      document.head.appendChild(script);
    end;
  end
  else if Assigned(ElementHandle) then
  begin
    ElementHandle.style.setProperty('border', '1px solid gray');
    ElementHandle.style.setProperty('background-color', '#eee');
  end;
end;
{$HINTS ON}

{ TCICGoogleMapsOptions }

procedure TCICGoogleMapsOptions.Assign(Source: TPersistent);
begin
  inherited;
  FCustomStyle := (Source as TCICGoogleMapsOptions).CustomStyle;
  FMapStyle := (Source as TCICGoogleMapsOptions).MapStyle;
  FDefaultLatitude := (Source as TCICGoogleMapsOptions).DefaultLatitude;
  FDefaultLongitude := (Source as TCICGoogleMapsOptions).DefaultLongitude;
  FDefaultZoomLevel := (Source as TCICGoogleMapsOptions).DefaultZoomLevel;
end;

constructor TCICGoogleMapsOptions.Create(AGoogleMaps: TCICGoogleMaps);
begin
  inherited Create;
  FCustomStyle := TStringList.Create;
  FMapStyle := mstDefault;
  FDefaultLatitude := -34.397;
  FDefaultLongitude := 150.644;
  FDefaultZoomLevel := 8;
  FOwner := AGoogleMaps;
end;

destructor TCICGoogleMapsOptions.Destroy;
begin
  FCustomStyle.Free;
  inherited;
end;

procedure TCICGoogleMapsOptions.SetCustomStyle(const Value: TStringList);
begin
  FCustomStyle.Assign(Value);
end;

procedure TCICGoogleMapsOptions.SetDefaultLatitude(const Value: Double);
begin
  if FDefaultLatitude <> Value then
  begin
    FDefaultLatitude := Value;
    FOwner.UpdateElement;
    FOwner.PanTo(DefaultLatitude, DefaultLongitude);
  end;
end;

procedure TCICGoogleMapsOptions.SetDefaultLongitude(const Value: Double);
begin
  if FDefaultLongitude <> Value then
  begin
    FDefaultLongitude := Value;
    FOwner.UpdateElement;
    FOwner.PanTo(DefaultLatitude, DefaultLongitude);
  end;
end;

procedure TCICGoogleMapsOptions.SetDefaultZoomLevel(const Value: Integer);
begin
  if (FDefaultZoomLevel <> Value) and (Value >= 0) and (Value <= 21) then
  begin
    FDefaultZoomLevel := Value;
    FOwner.UpdateElement;
    FOwner.SetZoom(Value);
  end;
end;


{$HINTS OFF}
procedure TCICGoogleMapsOptions.SetMapStyle(const Value: TCICGoogleMapstyle);
var
  map: TJSHTMLElement;
  cstyle: string;
  ostyle: TJSObject;
begin
  FMapStyle := Value;
  map := FOwner.GetMap;

  if not Assigned(map) then
    Exit;

  if FMapStyle = mstNightMode then
  begin
    asm
      var myStyle =
      [
        {
          "elementType": "geometry",
          "stylers": [
            {
              "color": "#242f3e"
            }
          ]
        },
        {
          "elementType": "labels.text.fill",
          "stylers": [
            {
              "color": "#746855"
            }
          ]
        },
        {
          "elementType": "labels.text.stroke",
          "stylers": [
            {
              "color": "#242f3e"
            }
          ]
        },
        {
          "featureType": "administrative.locality",
          "elementType": "labels.text.fill",
          "stylers": [
            {
              "color": "#d59563"
            }
          ]
        },
        {
          "featureType": "poi",
          "elementType": "labels.text.fill",
          "stylers": [
            {
              "color": "#d59563"
            }
          ]
        },
        {
          "featureType": "poi.park",
          "elementType": "geometry",
          "stylers": [
            {
              "color": "#263c3f"
            }
          ]
        },
        {
          "featureType": "poi.park",
          "elementType": "labels.text.fill",
          "stylers": [
            {
              "color": "#6b9a76"
            }
          ]
        },
        {
          "featureType": "road",
          "elementType": "geometry",
          "stylers": [
            {
              "color": "#38414e"
            }
          ]
        },
        {
          "featureType": "road",
          "elementType": "geometry.stroke",
          "stylers": [
            {
              "color": "#212a37"
            }
          ]
        },
        {
          "featureType": "road",
          "elementType": "labels.text.fill",
          "stylers": [
            {
              "color": "#9ca5b3"
            }
          ]
        },
        {
          "featureType": "road.highway",
          "elementType": "geometry",
          "stylers": [
            {
              "color": "#746855"
            }
          ]
        },
        {
          "featureType": "road.highway",
          "elementType": "geometry.stroke",
          "stylers": [
            {
              "color": "#1f2835"
            }
          ]
        },
        {
          "featureType": "road.highway",
          "elementType": "labels.text.fill",
          "stylers": [
            {
              "color": "#f3d19c"
            }
          ]
        },
        {
          "featureType": "transit",
          "elementType": "geometry",
          "stylers": [
            {
              "color": "#2f3948"
            }
          ]
        },
        {
          "featureType": "transit.station",
          "elementType": "labels.text.fill",
          "stylers": [
            {
              "color": "#d59563"
            }
          ]
        },
        {
          "featureType": "water",
          "elementType": "geometry",
          "stylers": [
            {
              "color": "#17263c"
            }
          ]
        },
        {
          "featureType": "water",
          "elementType": "labels.text.fill",
          "stylers": [
            {
              "color": "#515c6d"
            }
          ]
        },
        {
          "featureType": "water",
          "elementType": "labels.text.stroke",
          "stylers": [
            {
              "color": "#17263c"
            }
          ]
        }
      ];

      map.setOptions({styles: myStyle});
    end;
  end
  else if (FMapStyle = mstCustom) and (CustomStyle.Text <> '') then
  begin
    cstyle := CustomStyle.Text;
    ostyle := TJSObject(TJSJSON.Parse(cstyle));
    asm
      map.setOptions({styles: ostyle});
    end;
  end
  else
  begin
    asm
      map.setOptions({styles: []});
    end;
  end;
end;
{$HINTS ON}

{ TCICCesium }
{$HINTS OFF}
procedure TCICCesium.AddPolyLine(Points: TJSArray; AColor: TColor = clRed; AWidth: integer = 2; AOpacity: double = 1);
var
  map: TJSHTMLElement;
  clr: string;
  ptr: Pointer;
begin
  ptr := @HandlePolylineClick;
  map := GetMap;
  clr := ColorToHTML(AColor);

  asm
    var poly = new google.maps.Polyline({
      map: map,
  	  path: Points,
	    strokeColor: clr,
  	  strokeOpacity: AOpacity,
  	  strokeWeight: AWidth
  	});

    poly.idx = this.FPolylines.length;
    poly.addListener('click', function() { ptr(poly); } );
    this.FPolylines.push(poly);
  end;
end;

procedure TCICCesium.AddRectangle(NorthEastLat, NorthEastLon, SouthWestLat,
  SouthWestLon: double; AFillColor, AStrokeColor: TColor; AWidth: integer;
  AOpacity: double);
var
  map: TJSHTMLElement;
  clrF,clrS: string;
  ptr: Pointer;
begin
  ptr := @HandleRectangleClick;
  map := GetMap;
  clrF := ColorToHTML(AFillColor);
  clrS := ColorToHTML(AStrokeColor);

  asm
    var NELatLng = {lat: NorthEastLat, lng: NorthEastLon};
    var SWLatLng = {lat: SouthWestLat, lng: SouthWestLon};
    var rect = new google.maps.Rectangle({
      map: map,
  	  bounds: new google.maps.LatLngBounds(NELatLng, SWLatLng),
      fillColor: clrF,
      fillOpacity: AOpacity,
	    strokeColor: clrS,
  	  strokeOpacity: AOpacity,
  	  strokeWeight: AWidth
  	});

    rect.idx = this.FRectangles.length;
    rect.addListener('click', function() { ptr(rect); } );
    this.FRectangles.push(rect);
  end;
end;

procedure TCICCesium.AddPolygon(Points: TJSArray; AFillColor: TColor = clRed; AStrokeColor: TColor = clBlack; AWidth: integer = 2; AOpacity: double = 1);
var
  map: TJSHTMLElement;
  clrF,clrS: string;
  ptr: Pointer;
begin
  ptr := @HandlePolygonClick;
  map := GetMap;
  clrF := ColorToHTML(AFillColor);
  clrS := ColorToHTML(AStrokeColor);

  asm
    var poly = new google.maps.Polygon({
      map: map,
  	  paths: Points,
      fillColor: clrF,
      fillOpacity: AOpacity,
	    strokeColor: clrS,
  	  strokeOpacity: AOpacity,
  	  strokeWeight: AWidth
  	});

    poly.idx = this.FPolygons.length;
    poly.addListener('click', function() { ptr(poly); } );
    this.FPolygons.push(poly);
  end;
end;

procedure TCICCesium.AddCircle(Lat, Lon: double; Radius: Integer;
  AFillColor, AStrokeColor: TColor; AWidth: integer; AOpacity: double);
var
  map: TJSHTMLElement;
  clrF,clrS: string;
  ptr: Pointer;
begin
  ptr := @HandleCircleClick;
  map := GetMap;
  clrF := ColorToHTML(AFillColor);
  clrS := ColorToHTML(AStrokeColor);

  asm
    var myLatLng = {lat: Lat, lng: Lon};
    var circle = new google.maps.Circle({
      map: map,
  	  center: myLatLng,
      radius: Radius,
      fillColor: clrF,
      fillOpacity: AOpacity,
	    strokeColor: clrS,
  	  strokeOpacity: AOpacity,
  	  strokeWeight: AWidth
  	});

    circle.idx = this.FCircles.length;
    circle.addListener('click', function() { ptr(circle); } );
    this.FCircles.push(circle);
  end;
end;

procedure TCICCesium.AddMarker(Lat, Lon: double; Title: string);
var
  map: TJSHTMLElement;
  ptr: Pointer;
begin
  ptr := @HandleMarkerClick;
  map := GetMap;
  asm
    var myLatLng = {lat: Lat, lng: Lon};
    var marker = new google.maps.Marker({
          position: myLatLng,
          map: map,
          title: Title
        });
    marker.idx = this.FMarkers.length;
    marker.addListener('click', function() { ptr(marker); } );
    this.FMarkers.push(marker);
  end;
end;

procedure TCICCesium.AddMarker(Lat, Lon: double; Color: TGoogleMarkerColor; Title: string);
var
  map: TJSHTMLElement;
  clr: string;
  ptr: Pointer;
begin
  ptr := @HandleMarkerClick;

  case Color of
  mcDefault: clr := '';
  mcRed: clr := 'red-dot.png';
  mcGreen: clr := 'green-dot.png';
  mcYellow: clr := 'yellow-dot.png';
  mcPurple: clr := 'purple-dot.png';
  mcBlue: clr := 'blue-dot.png';
  end;

  map := GetMap;
  asm
    var myLatLng = {lat: Lat, lng: Lon};

    var marker = new google.maps.Marker({
          position: myLatLng,
          map: map,
          title: Title
        });

    if (clr != "") {
      marker.setIcon('http://maps.google.com/mapfiles/ms/icons/' + clr);
    }
    marker.idx = this.FMarkers.length;
    marker.addListener('click', function() { ptr(marker); } );

    this.FMarkers.push(marker);
  end;
end;

procedure TCICCesium.AddMarker(Lat, Lon: double; Color: TColor; PinLetter: string; Title: string);
var
  map: TJSHTMLElement;
  clr,url: string;
  ch: string;
  ptr: Pointer;
begin
  ptr := @HandleMarkerClick;

  clr := ColorToHTML(Color);
  delete(clr,1,1);
  map := GetMap;
  asm
    var myLatLng = {lat: Lat, lng: Lon};

    url = "http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld="+ PinLetter + "|" + clr;

    var pinImage = new google.maps.MarkerImage(url);

    var marker = new google.maps.Marker({
          position: myLatLng,
          map: map,
          title: Title,
          icon: pinImage
        });
    marker.idx = this.FMarkers.length;
    marker.addListener('click', function() { ptr(marker); } );

    this.FMarkers.push(marker);
  end;
end;

procedure TCICCesium.AddMarker(Lat, Lon: double; PinIcon, Title: string);
var
  map: TJSHTMLElement;
  ptr: Pointer;
begin
  ptr := @HandleMarkerClick;

  map := GetMap;
  asm
    var pinImage = new google.maps.MarkerImage(PinIcon);
    var myLatLng = {lat: Lat, lng: Lon};
    var marker = new google.maps.Marker({
          position: myLatLng,
          map: map,
          title: Title,
          icon: pinImage
        });
    marker.idx = this.FMarkers.length;
    marker.addListener('click', function() { ptr(marker); } );

    this.FMarkers.push(marker);
  end;
end;

procedure TCICCesium.AddMarker(Lat, Lon: double; Shape: TGoogleMarkerShape;
  Color: TColor; Title: string);
 begin
   AddMarker(Lat, Lon, Shape, Color, clBlack, 1, Title);
 end;

procedure TCICCesium.AddGPX(AGPX: string; AColor: TColor; AWidth: integer;
  AOpacity: double);
var
  Points: TJSArray;
  LatMax,LonMax,LatMin,LonMin: double;
begin
  Points := TJSArray.new;
  asm
    var parser = new DOMParser();
    var doc = parser.parseFromString(AGPX, 'text/xml');
    var trk = doc.getElementsByTagName("trk")[0];
    var name = trk.getElementsByTagName("name")[0];
    var trkseg = doc.getElementsByTagName("trkseg")[0];
    var i = trkseg.getElementsByTagName("trkpt").length;
    var trkpt;
    var j;
    var lat;
    var lon;
    var bounds = new google.maps.LatLngBounds ();

    for (j= 0; j < i; j++)
    {
      trkpt = trkseg.getElementsByTagName("trkpt")[j];
      lat = trkpt.getAttribute('lat');
      lon = trkpt.getAttribute('lon');

      var p = new google.maps.LatLng(lat, lon);
  	  Points.push(p);
      bounds.extend(p);
    }
    LatMax = bounds.getNorthEast().lat();
    LonMax = bounds.getNorthEast().lng();

    LatMin = bounds.getSouthWest().lat();
    LonMin = bounds.getSouthWest().lng();
  end;

  AddPolyLine(Points, AColor, AWidth, AOpacity);
  FitBounds(LatMin, LonMin, LatMax, LonMax);
end;

procedure TCICCesium.AddKML(Url: string; ZoomToBounds: Boolean);
var
  map: TJSHTMLElement;
  clr: string;
  ptr: Pointer;
begin
  ptr := @HandleKMLClick;
  map := GetMap;

  asm
    var kml = new google.maps.KmlLayer(Url, {
      map: map,
      clickable: true,
      preserveViewport: !ZoomToBounds
  	});

    kml.idx = this.FKMLs.length;
    kml.addListener('click', function() { ptr(kml); } );
    this.FKMLs.push(kml);
  end;
end;

procedure TCICCesium.AddMarker(Lat, Lon: double; Shape: TGoogleMarkerShape;
  Color: TColor; BorderColor: TColor; Scale: Double; CustomShape: string; Title: string);
var
  map: TJSHTMLElement;
  clr: string;
  bclr: string;
  pth: string;
  scl: string;
  ptr: pointer;
begin
  ptr := @HandleMarkerClick;
  map := GetMap;
  clr := ColorToHTML(Color);
  bclr := ColorToHTML(BorderColor);
  scl := StringReplace(FloatToStr(Scale), ',', '.', []);

  case Shape of
  msPin: pth:= 'M 0,0 C -2,-20 -10,-22 -10,-30 A 10,10 0 1,1 10,-30 C 10,-22 2,-20 0,0 z';
  msPinDot: pth := 'M 0,0 C -2,-20 -10,-22 -10,-30 A 10,10 0 1,1 10,-30 C 10,-22 2,-20 0,0 z M -2,-30 a 2,2 0 1,1 4,0 2,2 0 1,1 -4,0';
  msFlag:  pth := 'M 0,0 -1,-2 V -43 H 1 V -2 z M 1,-40 H 30 V -20 H 1 z';
  msBookmark: pth := 'M17 3h-10c-1.1 0-1.99.9-1.99 2l-.01 16 7-3 7 3v-16c0-1.1-.9-2-2-2z';
  msFlagSmall: pth := 'M14.4 6l-.4-2h-9v17h2v-7h5.6l.4 2h7v-10z';
  msHome: pth := 'M10 20v-6h4v6h5v-8h3l-10-9-10 9h3v8z';
  msFavorite: pth := 'M12 21.35l-1.45-1.32c-5.15-4.67-8.55-7.75-8.55-11.53 0-3.08 2.42-5.5 5.5-5.5 1.74 0 3.41.81 4.5 2.09 1.09-1.28 2.76-2.09 4.5-2.09 3.08 0 5.5 2.42 5.5 5.5 0 3.78-3.4 6.86-8.55 11.54l-1.45 1.31z';
  msStar: pth := 'M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z';
  msCustom: pth := CustomShape;
  end;

  asm
    function pinSymbol(color, bcolor, scale) {
    return {
        path: pth,
        fillColor: color,
        fillOpacity: 1,
        strokeColor: bcolor,
        strokeWeight: 1,
        scale: parseFloat(scale),
        };
    }
    var myLatLng = {lat: Lat, lng: Lon};
    var marker = new google.maps.Marker({
          position: myLatLng,
          map: map,
          title: Title,
          icon: pinSymbol(clr, bclr, scl)
        });
    marker.idx = this.FMarkers.length;
    marker.addListener('click', function() { ptr(marker); } );

    this.FMarkers.push(marker);
  end;
end;

procedure TCICCesium.BindEvents;
var
  map: TJSHTMLElement;
  ptr1,ptr2,ptr3,ptr4,ptr5: pointer;
  ptr6, ptr7: pointer;
  dirsvc, dirdispl: TJSElement;
begin
  inherited;

  if IsUpdating then
    Exit;

  map := GetMap;
  //ConsoleLog('BindEvents ' + map.innerHTML);

  if Assigned(map) and not FBound then
  begin
    ptr1 := @HandleMapClick;
    ptr2 := @HandleMapDblClick;
    ptr3 := @HandleMapPan;
    ptr4 := @HandleMapZoom;
    ptr5 := @HandleMapIdle;
    ptr6 := @HandleViewerClick; // ALE 20200301 Cesium Clicks of the map
    ptr7 := @HandleViewerMouseMove; // ALE 20200309 Cesium move move over map

    asm
      this.FMarkers = [];
      this.FPolygons = [];
      this.FPolylines = [];
      this.FCircles = [];
      this.FRectangles = [];
      this.FKMLs = [];
    end;


    asm
      dirsvc = new google.maps.DirectionsService;
      dirdispl = new google.maps.DirectionsRenderer;
      dirdispl.setMap(map);
      map.scene.canvas.addEventListener('click', ptr6);
      map.scene.canvas.addEventListener('mousemove', ptr7);
      //map.addListener('click', ptr1);
      //map.addListener('dblclick', ptr2);
      //map.addListener('center_changed',ptr3);
      //map.addListener('zoom_changed',ptr4);
      //map.addListener('tilesloaded',ptr5);
    end;

    FDirectionsService := dirsvc;
    FDirectionsDisplay := dirdispl;

    FBound := true;
  end;
end;
{$HINTS ON}


procedure TCICCesium.ClearCircles;
begin
  asm
    for (var i = 0; i < this.FCircles.length; i++) {
          this.FCircles[i].setMap(null);
          }
    this.FMarkers = [];
  end;
end;

procedure TCICCesium.ClearKMLs;
begin
  asm
    for (var i = 0; i < this.FKMLs.length; i++) {
          this.FKMLs[i].setMap(null);
          }
    this.FKMLs = [];
  end;
end;

procedure TCICCesium.ClearMarkers;
begin
  asm
    for (var i = 0; i < this.FMarkers.length; i++) {
          this.FMarkers[i].setMap(null);
          }
    this.FMarkers = [];
  end;
end;

procedure TCICCesium.ClearPolygons;
begin
  asm
    for (var i = 0; i < this.FPolygons.length; i++) {
          this.FPolygons[i].setMap(null);
          }
    this.FPolyLines = [];
  end;
end;

procedure TCICCesium.ClearPolyLines;
begin
  asm
    for (var i = 0; i < this.FPolylines.length; i++) {
          this.FPolylines[i].setMap(null);
          }
    this.FPolyLines = [];
  end;
end;

procedure TCICCesium.ClearRectangles;
begin
  asm
    for (var i = 0; i < this.FRectangles.length; i++) {
          this.FRectangles[i].setMap(null);
          }
    this.FRectangles = [];
  end;
end;

procedure TCICCesium.ConsoleLog(const pLogMessage: string);
begin
  // ALE 20200227 sends an entry to the browser console, not Cesium specific
  asm
    console.log(pLogMessage);
  end;
end;

function TCICCesium.CreateElement: TJSElement;
begin
  FDirectionsService := nil;
  FDirectionsDisplay := nil;

  Result := document.createElement('DIV');
  Result.setAttribute('id', GetID);
  FMap := nil;
end;

procedure TCICCesium.CreateInitialize;
begin
  inherited;
  // TODO ALE FOptions := TCICGoogleMapsOptions.Create(Self);
  Width := 400;
  Height := 300;
end;

{$HINTS OFF}
procedure TCICCesium.FitBounds(LatMin, LonMin, LatMax, LonMax: double);
var
  map: TJSHTMLElement;
begin
  map := GetMap;

  asm
    var ne = new google.maps.LatLng(LatMax, LonMax);
    var sw = new google.maps.LatLng(LatMin, LonMin);
    var bounds = new google.maps.LatLngBounds(sw,ne);
    map.fitBounds(bounds);
  end;
end;
{$HINTS ON}

procedure TCICCesium.Loaded;
begin
  inherited;
end;

procedure TCICCesium.GeoCode(const Address: string);
var
  FURL: string;
begin
  FReq := TJSXMLHttpRequest.new;
  FReq.addEventListener('load', @HandleResponse);
  FReq.addEventListener('abort',@HandleAbort);

  FURL := 'https://maps.googleapis.com/maps/api/geocode/json?address=' + encodeURIComponent(Address) +'&key=' + APIKey;
  FReq.open('GET', FURL);
  FReq.send;
end;

{$HINTS OFF}
procedure TCICCesium.PanTo(Lat, Lon: double);
var
  map: TJSHTMLElement;
begin
  map := GetMap;
  if Assigned(map) then
  begin
    asm
      map.panTo(new google.maps.LatLng(Lat, Lon));
    end;
  end;
end;

function TCICCesium.GetMap: TJSHTMLElement;
var
  map: TJSHTMLElement;
  id: string;
begin
  if Assigned(FMap) then
  begin
    Result := FMap;
    ConsoleLog('GetMap FMap already assigned');
  end
  else
  begin
    map := nil;

    id := GetID;
    ConsoleLog('GetMap ' + id);
    asm
      var el = document.getElementById(id);
      if (el != null) {
        map = viewer;
        //alert(map);
      }
      else {
        alert('GetMap did not find element');
      }
    end;

    Result := map;
    // TODO ConsoleLog('GetMap ' + map.innerHTML);
    if Assigned(map) then
      FMap := Result;
  end;
end;

function TCICCesium.GetBounds(var NorthEastLat, NorthEastLon, SouthWestLat,
  SouthWestLon: Double): Boolean;
var
  neLon, neLat, swLon, swLat: String;
begin
  Result := false;
  NorthEastLon := -1;
  NorthEastLat := -1;
  SouthWestLon := -1;
  SouthWestLat := -1;

  asm
    neLon = map.getBounds().getNorthEast().lng().toString();
    neLat = map.getBounds().getNorthEast().lat().toString();
    swLon = map.getBounds().getSouthWest().lng().toString();
    swLat = map.getBounds().getSouthWest().lat().toString();
  end;

  if (neLon <> '') and (neLat <> '') and (swLon <> '') and (swLat <> '') then
  begin
    NorthEastLon := StrToFloat(swLon);
    NorthEastLat := StrToFloat(swLat);
    SouthWestLon := StrToFloat(swLon);
    SouthWestLat := StrToFloat(swLat);
    Result := true;
  end;
end;

function TCICCesium.GetCenter(var Lat, Lon: Double): Boolean;
var
  sLon, sLat: String;
begin
  Result := false;
  Lon := -1;
  Lat := -1;

  asm
    sLon = map.getCenter().lng().toString();
    sLat = map.getCenter().lat().toString();
  end;

  if (sLon <> '') and (sLat <> '') then
  begin
    Lon := StrToFloat(sLon);
    Lat := StrToFloat(sLat);
    Result := true;
  end;
end;

function TCICCesium.GetCircle(AIndex: integer): TJSObject;
var
  res: TJSObject;
begin
  asm
    res = this.FCircles[AIndex];
  end;

  Result := res;
end;


function TCICCesium.GetMarker(AIndex: integer): TJSObject;
var
  res: TJSObject;
begin
  asm
    res = this.FMarkers[AIndex];
  end;

  Result := res;
end;

function TCICCesium.GetPolygon(AIndex: integer): TJSObject;
var
  res: TJSObject;
begin
  asm
    res = this.FPolygon[AIndex];
  end;

  Result := res;
end;

function TCICCesium.GetPolyline(AIndex: integer): TJSObject;
var
  res: TJSObject;
begin
  asm
    res = this.FPolyline[AIndex];
  end;

  Result := res;
end;

function TCICCesium.GetRectangle(AIndex: integer): TJSObject;
var
  res: TJSObject;
begin
  asm
    res = this.FGetRectangle[AIndex];
  end;

  Result := res;
end;

function TCICCesium.HandleAbort(Event: TEventListenerEvent): boolean;
begin
  //
  Result := true;
end;

procedure TCICCesium.HandleCircleClick(e: TJSEvent);
var
  idx: integer;
begin
  asm
    idx = e.idx;
  end;

  if Assigned(OnCircleClick) then
    OnCircleClick(Self, idx, TJSObject(e));
end;

procedure TCICCesium.HandleKMLClick(e: TJSEvent);
var
  idx: integer;
begin
  asm
    idx = e.idx;
  end;

  if Assigned(OnKMLClick) then
    OnKMLClick(Self, idx, TJSObject(e));
end;

procedure TCICCesium.HandleRectangleClick(e: TJSEvent);
var
  idx: integer;
begin
  asm
    idx = e.idx;
  end;

  if Assigned(OnRectangleClick) then
    OnRectangleClick(Self, idx, TJSObject(e));
end;

procedure TCICCesium.HandleMapClick(e: TJSEvent);
var
  lon,lat: double;
begin
  asm
    lon = e.latLng.lng();
    lat = e.latLng.lat();
  end;

  if Assigned(OnMapClick) then
    OnMapClick(Self, lon, lat);
end;

procedure TCICCesium.HandleMapDblClick(e: TJSEvent);
var
  lon,lat: double;
begin
  asm
    lon = e.latLng.lng();
    lat = e.latLng.lat();
  end;

  if Assigned(OnMapDblClick) then
    OnMapDblClick(Self, lon, lat);
end;

procedure TCICCesium.HandleMapIdle(e: TJSEvent);
begin
  if Assigned(OnMapIdle) then
    OnMapIdle(Self);
end;

procedure TCICCesium.HandleMapPan(e: TJSEvent);
var
  lon,lat: double;
begin
  asm
    var c = map.getCenter();
    lon = c.lng();
    lat = c.lat();
  end;

  if Assigned(OnMapPan) then
    OnMapPan(Self, lon, lat);
end;

procedure TCICCesium.HandleMapZoom(e: TJSEvent);
var
  map: TJSHTMLElement;
  zoom: integer;
begin
  map := GetMap;
  asm
    zoom = map.getZoom();
  end;
  if Assigned(OnMapZoom) then
    OnMapZoom(Self, zoom);
end;

procedure TCICCesium.HandleMarkerClick(e: TJSEvent);
var
  idx: integer;
begin
  asm
    idx = e.idx;
  end;

  if Assigned(OnMarkerClick) then
    OnMarkerClick(Self, idx, TJSObject(e));
end;


procedure TCICCesium.HandlePolygonClick(e: TJSEvent);
var
  idx: integer;
begin
  asm
    idx = e.idx;
  end;

  if Assigned(OnPolygonClick) then
    OnPolygonClick(Self, idx, TJSObject(e));
end;

procedure TCICCesium.HandlePolylineClick(e: TJSEvent);
var
  idx: integer;
begin
  asm
    idx = e.idx;
  end;

  if Assigned(OnPolylineClick) then
    OnPolylineClick(Self, idx, TJSObject(e));
end;

function TCICCesium.HandleResponse(Event: TEventListenerEvent): boolean;
var
  fnd: boolean;
  reslat, reslon: double;
begin
  Result := true;
  fnd := false;

  asm
    var s = Event.target.responseText;
    var js = JSON.parse(s);
    if (js.status == "OK") {
      fnd = true;
      var lat = js.results[0].geometry.location.lat;
      var lng = js.results[0].geometry.location.lng;
      reslon = lng;
      reslat = lat;
    }
  end;

  if fnd and Assigned(OnGeoCoded) then
  begin
    OnGeoCoded(Self, reslon, reslat);
  end;
end;

procedure TCICCesium.HandleViewerClick(e: TJSEvent);
var
  lLat, lLon: Double;
  lOnMap: Boolean;
begin
  ConsoleLog('HandleViewerClick');
  if Assigned(OnViewerClick) then
  begin
    // ALE 20200309 calculate lat/lon
    asm
      var mousePosition = new Cesium.Cartesian2(e.clientX, e.clientY);
      var ellipsoid = viewer.scene.globe.ellipsoid;
      var cartesian = viewer.camera.pickEllipsoid(mousePosition, ellipsoid);
      if (cartesian) {
        var cartographic = ellipsoid.cartesianToCartographic(cartesian);
        lLon = Cesium.Math.toDegrees(cartographic.longitude);
        lLat = Cesium.Math.toDegrees(cartographic.latitude);
      } else {
        //  alert('Globe was not picked');
      }
      lOnMap = Boolean(cartesian);
    end;
    if (lOnMap) then
      OnViewerClick(Self, lLat, lLon);
  end;
end;

procedure TCICCesium.HandleViewerMouseMove(e: TJSEvent);
var
  lLat, lLon: Double;
  lOnMap: Boolean;
begin
  ConsoleLog('HandleViewerMouseMove');
  if Assigned(OnViewerMouseMove) then
  begin
    // ALE 20200309 calculate lat/lon
    asm
      var mousePosition = new Cesium.Cartesian2(e.clientX, e.clientY);
      var ellipsoid = viewer.scene.globe.ellipsoid;
      var cartesian = viewer.camera.pickEllipsoid(mousePosition, ellipsoid);
      if (cartesian) {
          var cartographic = Cesium.Cartographic.fromCartesian(cartesian);
          lLon = Cesium.Math.toDegrees(cartographic.longitude);
          lLat = Cesium.Math.toDegrees(cartographic.latitude);
      }
      lOnMap = Boolean(cartesian);
    end;
    if (lOnMap) then
      OnViewerMouseMove(Self, lLat, lLon);
  end;

// TODO Mouse position code
//    // Mouse over the globe to see the cartographic position
//    handler = new Cesium.ScreenSpaceEventHandler(scene.canvas);
//    handler.setInputAction(function(movement) {
//        var cartesian = viewer.camera.pickEllipsoid(movement.endPosition, scene.globe.ellipsoid);
//        if (cartesian) {
//            var cartographic = Cesium.Cartographic.fromCartesian(cartesian);
//            var longitudeString = Cesium.Math.toDegrees(cartographic.longitude).toFixed(2);
//            var latitudeString = Cesium.Math.toDegrees(cartographic.latitude).toFixed(2);
//
//            entity.position = cartesian;
//            entity.label.show = true;
//            entity.label.text =
//                'Lon: ' + ('   ' + longitudeString).slice(-7) + '\u00B0' +
//                '\nLat: ' + ('   ' + latitudeString).slice(-7) + '\u00B0';
//        } else {
//            entity.label.show = false;
//        }
//    }, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
//});
end;

procedure TCICCesium.RemoveDirections;
var
  dirdispl: TJSElement;
begin
  if not Assigned(FDirectionsDisplay) then
    BindEvents;

  dirdispl := FDirectionsDisplay;

  asm
    dirdispl.setMap(null);
  end;
end;
{$HINTS ON}


procedure TCICCesium.SetAPIKey(const Value: string);
begin
  if FAPIKey <> Value then
  begin
    FAPIKey := Value;
    UpdateElement;
  end;
end;

{$HINTS OFF}
procedure TCICCesium.SetCenter(Lat, Lon: double);
var
  map: TJSHTMLElement;
begin
  map := GetMap;
  asm
    map.setCenter(new google.maps.LatLng(Lat, Lon));
  end;
end;

procedure TCICCesium.SetDoubleClickZoom(AValue: Boolean);
var
  map: TJSHTMLElement;
begin
  map := GetMap;
  asm
    map.setOptions( {disableDoubleClickZoom:!AValue} );
  end;
end;

procedure TCICCesium.SetDraggable(AValue: Boolean);
var
  map: TJSHTMLElement;
begin
  map := GetMap;
  asm
    map.setOptions( {draggable:AValue} );
  end;
end;

procedure TCICCesium.SetMapType(AMapType: TGoogleMapType);
var
  map: TJSHTMLElement;
  smt: string;
begin
  map := GetMap;

  case AMapType of
    mtDefault: smt := 'roadmap';
    mtSatellite: smt := 'satellite';
    mtHybrid: smt := 'hybrid';
    mtTerrain: smt := 'terrain';
  end;

  asm
    map.setMapTypeId(smt);
  end;
end;

procedure TCICCesium.RemoveCircle(AIndex: Integer);
begin
  asm
    if (AIndex < this.FPolygons.length)
    {
      this.FCircles[AIndex].setMap(null);
      this.FCircles.splice(AIndex, 1);
    }
  end;
end;


procedure TCICCesium.RemoveMarker(AIndex: Integer);
begin
  asm
    if (AIndex < this.FMarkers.length)
    {
      this.FMarkers[AIndex].setMap(null);
      this.FMarkers.splice(AIndex, 1);
    }
  end;
end;

procedure TCICCesium.RemovePolygon(AIndex: Integer);
begin
  asm
    if (AIndex < this.FPolygons.length)
    {
      this.FPolygons[AIndex].setMap(null);
      this.FPolygons.splice(AIndex, 1);
    }
  end;
end;

procedure TCICCesium.RemovePolyline(AIndex: Integer);
begin
  asm
    if (AIndex < this.FPolylines.length)
    {
      this.FPolylines[AIndex].setMap(null);
      this.FPolylines.splice(AIndex, 1);
    }
  end;
end;

procedure TCICCesium.RemoveRectangle(AIndex: Integer);
begin
  asm
    if (AIndex < this.FPolygons.length)
    {
      this.FRectangles[AIndex].setMap(null);
      this.FRectangles.splice(AIndex, 1);
    }
  end;
end;

procedure TCICCesium.SetMarkerIcon(AIndex: Integer; Url: string);
begin
  asm
    if (AIndex < this.FMarkers.length)
    {
      this.FMarkers[AIndex].setIcon(Url);
    }
  end;
end;

procedure TCICCesium.SetMarkerLocation(AIndex: Integer; Lat, Lon: Double);
begin
  asm
    if (AIndex < this.FMarkers.length)
    {
      this.FMarkers[AIndex].setPosition(new google.maps.LatLng(Lat, Lon));
    }
  end;
end;

procedure TCICCesium.SetMarkerTitle(AIndex: Integer; ATitle: string);
begin
  asm
    if (AIndex < this.FMarkers.length)
    {
      this.FMarkers[AIndex].setTitle(ATitle);
    }
  end;
end;

procedure TCICCesium.SetPolygonColors(AIndex: Integer; AFillColor,
  AStrokeColor: TColor);
var
  clrF, clrS: string;
begin
  clrF := ColorToHTML(AFillColor);
  clrS := ColorToHTML(AStrokeColor);
  asm
    if (AIndex < this.FPolygons.length)
    {
      this.FPolygons[AIndex].setOptions({ fillColor: clrF, strokeColor: clrS });
    }
  end;
end;

procedure TCICCesium.SetPolygonPoints(AIndex: Integer; Points: TJSArray);
begin
  asm
    if (AIndex < this.FPolygons.length)
    {
      this.FPolygons[AIndex].setOptions({ path: Points });
    }
  end;
end;

procedure TCICCesium.SetPolylineColor(AIndex: Integer; AColor: TColor);
var
  clr: string;
begin
  clr := ColorToHTML(AColor);
  asm
    if (AIndex < this.FPolylines.length)
    {
      this.FPolylines[AIndex].setOptions({ strokeColor: clr });
    }
  end;
end;

procedure TCICCesium.SetPolylinePoints(AIndex: Integer; Points: TJSArray);
begin
  asm
    if (AIndex < this.FPolylines.length)
    {
      this.FPolylines[AIndex].setOptions({ path: Points });
    }
  end;
end;

procedure TCICCesium.SetRectangleColors(AIndex: Integer; AFillColor,
  AStrokeColor: TColor);
var
  clrF, clrS: string;
begin
  clrF := ColorToHTML(AFillColor);
  clrS := ColorToHTML(AStrokeColor);
  asm
    if (AIndex < this.FRectangles.length)
    {
      this.FRectangles[AIndex].setOptions({ fillColor: clrF, strokeColor: clrS });
    }
  end;
end;

procedure TCICCesium.SetRectangleLocation(AIndex: Integer; NorthEastLat,
  NorthEastLon, SouthWestLat, SouthWestLon: double);
begin
  asm
    if (AIndex < this.FRectangles.length)
    {
      var NELatLng = {lat: NorthEastLat, lng: NorthEastLon};
      var SWLatLng = {lat: SouthWestLat, lng: SouthWestLon};
      this.FRectangles[AIndex].setOptions({ bounds: new google.maps.LatLngBounds(NELatLng, SWLatLng) });
    }
  end;
end;

procedure TCICCesium.SetCircleCenter(AIndex: Integer; Lat, Lon: Double);
begin
  asm
    if (AIndex < this.FCircles.length)
    {
      this.FCircles[AIndex].setCenter(new google.maps.LatLng(Lat, Lon));
    }
  end;
end;

procedure TCICCesium.SetCircleColors(AIndex: Integer; AFillColor,
  AStrokeColor: TColor);
var
  clrF, clrS: string;
begin
  clrF := ColorToHTML(AFillColor);
  clrS := ColorToHTML(AStrokeColor);
  asm
    if (AIndex < this.FCircles.length)
    {
      this.FCircles[AIndex].setOptions({ fillColor: clrF, strokeColor: clrS });
    }
  end;
end;

procedure TCICCesium.SetCircleRadius(AIndex, Radius: Integer);
begin
  asm
    if (AIndex < this.FCircles.length)
    {
      this.FCircles[AIndex].setRadius(Radius);
    }
  end;
end;

procedure TCICCesium.SetScrollWheel(AValue: Boolean);
var
  map: TJSHTMLElement;
begin
  map := GetMap;
  asm
    map.setOptions( {scrollWheel:AValue} );
  end;
end;

procedure TCICCesium.SetZoom(Zoom: Integer);
var
  map: TJSHTMLElement;
begin
  map := GetMap;
  if Assigned(map) then
  begin
    asm
      map.setZoom(Zoom);
    end;
  end;
end;

procedure TCICCesium.ShowDirections(SourceLon, SourceLat, DestLon,
  DestLat: double; ATravelMode: TGoogleTravelMode = tmDriving;
  WayPoints: TStringList = nil; OptimizeWayPoints: Boolean = False;
  AvoidHighways: Boolean = False; AvoidTolls: Boolean = False);
var
  dirsvc,dirdispl,map: TJSElement;
  tm: string;
  wp: TJSArray;
  I: Integer;
begin
  if not Assigned(FDirectionsDisplay) then
    BindEvents;

  dirsvc := FDirectionsService;
  dirdispl := FDirectionsDisplay;
  map := GetMap;
  tm := TravelModeStr(ATravelMode);
  wp := TJSArray.New;
  if WayPoints <> nil then
  begin
    for I := 0 to WayPoints.Count - 1 do
      wp.Push(New(['location', WayPoints[I]]));
  end;

  asm
    var sourcelatlon = new google.maps.LatLng(SourceLat, SourceLon);
    var destlatlon = new google.maps.LatLng(DestLat, DestLon);

    dirdispl.setMap(map);
    dirsvc.route({
          origin: sourcelatlon,
          destination: destlatlon,
          waypoints: wp,
          optimizeWaypoints: OptimizeWayPoints,
          avoidHighways: AvoidHighways,
          avoidTolls: AvoidTolls,
          travelMode: tm
        }, function(response, status) {
          if (status === 'OK') {
            dirdispl.setDirections(response);
          } else {
            window.alert('Directions request failed due to ' + status);
          }
        });
  end;
end;

procedure TCICCesium.HideStreetView;
var
  map: TJSElement;
begin
  map := GetMap;
  asm
    map.streetView.setVisible(false);
  end;
end;

procedure TCICCesium.ShowStreetView(Lat, Lon: Double; Heading, Zoom,
  Pitch: Integer);
var
  map: TJSElement;
  mapid: string;
begin
  map := GetMap;
  mapid := GetID;
  asm
    var LatLng = {lat: Lat, lng: Lon};
    var panorama = new google.maps.StreetViewPanorama(
        document.getElementById(mapid), {
          position: LatLng,
          pov: {
            heading: Heading,
            zoom: Zoom,
            pitch: Pitch
          }
        });
    map.setStreetView(panorama);
  end;
end;

procedure TCICCesium.ShowDirections(Source, Destination: string;
  ATravelMode: TGoogleTravelMode; WayPoints: TStringList; OptimizeWayPoints,
  AvoidHighways, AvoidTolls: Boolean);
var
  dirsvc,dirdispl,map: TJSElement;
  tm: string;
  wp: TJSArray;
  I: Integer;
begin
  if not Assigned(FDirectionsDisplay) then
    BindEvents;

  dirsvc := FDirectionsService;
  dirdispl := FDirectionsDisplay;
  map := GetMap;
  tm := TravelModeStr(ATravelMode);
  wp := TJSArray.New;
  if WayPoints <> nil then
  begin
    for I := 0 to WayPoints.Count - 1 do
      wp.Push(New(['location', WayPoints[I]]));
  end;

  asm
    dirdispl.setMap(map);

    dirsvc.route({
          origin: Source,
          destination: Destination,
          waypoints: wp,
          optimizeWaypoints: OptimizeWayPoints,
          avoidHighways: AvoidHighways,
          avoidTolls: AvoidTolls,
          travelMode: tm
        }, function(response, status) {
          if (status === 'OK') {
            dirdispl.setDirections(response);
          } else {
            window.alert('Directions request failed due to ' + status);
          }
        });
  end;
end;

function TCICCesium.TravelModeStr(ATravelMode: TGoogleTravelMode): string;
begin
  Result := 'DRIVING';
  case ATravelMode of
    tmDriving: Result := 'DRIVING';
    tmWalking: Result := 'WALKING';
    tmBicycling: Result := 'BICYCLING';
    tmTransit: Result := 'TRANSIT';
  end;
end;

procedure TCICCesium.UpdateElement;
var
  map: TJSHTMLElement;
  id: string;
  srcurl: string;
  scriptsrc: string;
  stylesrc: string;
  scr: TJSElement;
begin
  inherited;

  if IsUpdating then
    Exit;

  if FUpdateCount > 0 then
    Exit;

  if not FCode and (FAPIKey <> '') then
  begin
    FCode := true;
    id := GetID;

    srcurl := 'https://cesium.com/downloads/cesiumjs/releases/1.67/Build/Cesium/Cesium.js';

    // ALE 20200227 css for proper map display
    //  <link rel="stylesheet" type="text/css" href="mystyle.css">
    stylesrc :=
      '@import url("https://cesium.com/downloads/cesiumjs/releases/1.67/Build/Cesium/Widgets/widgets.css");' + #13#10 +
      'html, body, #' + id + '{' + #13#10 +
      '  width: 100%; height: 100%; margin: 0; padding: 0; overflow: hidden;' + #13#10 +
      '}';
    asm
      var style = document.createElement('style');
      style.innerHTML = stylesrc;
      document.head.appendChild(style);
    end;

    scr := document.createElement('script');
    scr.addEventListener('load',@DoLoaded);
    asm
      scr.defer = true;
      scr.async = true;
      scr.src = srcurl;
      scr.type = 'text/javascript';
    end;
    document.head.appendChild(scr);
  end
  else if Assigned(ElementHandle) then
  begin
    ElementHandle.style.setProperty('border', '1px solid gray');
    ElementHandle.style.setProperty('background-color', '#eee');
  end;
end;
{$HINTS ON}

{$HINTS OFF}
function TCICCesium.DoLoaded(Event: TEventListenerEvent): boolean;
var
  id: string;
  scriptsrc: string;
begin

  // TODO ALE Options.SetMapStyle(Options.MapStyle);
  id := GetId;
  scriptsrc :=
   'Cesium.Ion.defaultAccessToken = "' + FAPIKey + '";' + #13#10 +
   'var el = document.getElementById("' + id + '");' + #13#10 +
   'var viewer = new Cesium.Viewer(el);' + #13#10 +
   'FMap = viewer;' + #13#10;
  asm
    var script = document.createElement('script');
    script.innerHTML = scriptsrc;
    document.head.appendChild(script);
  end;

  GetMap;
  BindEvents;
  Result := true;

  if Assigned(OnMapLoaded) then
    OnMapLoaded(Self);
end;
{$HINTS ON}
end.
