{**********************************************}
{   TWorldSeries GIS / Mapping Charts          }
{   Copyright (c) 2007-2020 by Steema Software }
{**********************************************}
unit TeeWorldSeries;
{$I TeeDefs.inc}

interface

uses
  {$IFNDEF LINUX}
  {$IFNDEF FMX}
  Windows,
  {$ENDIF}
  {$ENDIF}

  Classes, SysUtils,
  {$IFDEF D6}
  Types,
  {$ENDIF}

  {$IFDEF FMX}
  FMX.Types,
  {$ELSE}
  Graphics, Controls,
  {$ENDIF}

  {$IFDEF FMX}
  FMXTee.Canvas, FMXTee.Procs, FMXTee.Engine, FMXTee.Chart, FMXTee.Series,
  FMXTee.Series.Bubble, FMXTee.Series.Map
  {$ELSE}
  TeCanvas, TeeProcs, TeEngine, Chart, Series, BubbleCh, TeeMapSeries
  {$ENDIF};

type
  TTeeAfricaGroup=class(TTeePolygonGroup)
  end;

  TTeeUSAGroup=class(TTeePolygonGroup)
  private
    IAlaska : TTeePolygonGroup;
    IHawaii : TTeePolygonGroup;
    IPuertoRico : TTeePolygonGroup;
  public
    Destructor Destroy; override;

    function Alaska:TTeePolygonGroup;
    function Hawaii:TTeePolygonGroup;
    function PuertoRico:TTeePolygonGroup;
  end;

  TTeeNorthAmericaGroup=class(TTeePolygonGroup)
  private
    ICanada : TTeePolygonGroup;
    IUSA : TTeeUSAGroup;
  public
    Destructor Destroy; override;

    function Canada:TTeePolygonGroup;
    function USA:TTeeUSAGroup;
  end;

  TTeeSouthAmericaGroup=class(TTeePolygonGroup)
  end;

  TTeeAmericaGroup=class(TTeePolygonGroup)
  private
    ICentral : TTeePolygonGroup;
    INorth : TTeeNorthAmericaGroup;
    ISouth : TTeeSouthAmericaGroup;
  public
    Destructor Destroy; override;

    function Central:TTeePolygonGroup;
    function North:TTeeNorthAmericaGroup;
    function South:TTeeSouthAmericaGroup;
  end;

  TTeeAsiaGroup=class(TTeePolygonGroup)
  private
    IChina : TTeePolygonGroup;
    IJapan : TTeePolygonGroup;
  public
    Destructor Destroy; override;

    function China:TTeePolygonGroup;
    function Japan:TTeePolygonGroup;
  end;

  TTeeItalyGroup=class(TTeePolygonGroup)
  end;

  TTeeEuropeGroup=class(TTeePolygonGroup)
  private
    IUK,
    IFrance,
    IGermany,
    IRussia,
    ISpain : TTeePolygonGroup;

    IItaly : TTeeItalyGroup;
  public
    Destructor Destroy; override;

    function France:TTeePolygonGroup;
    function Germany:TTeePolygonGroup;
    function Italy:TTeeItalyGroup;
    function Russia:TTeePolygonGroup;
    function Spain:TTeePolygonGroup;
    function UK:TTeePolygonGroup;
  end;

  TTeePolygonGroupClass=class of TTeePolygonGroup;

  // Note: Adding more maps should be done at the end of this enumeration:

  TWorldMap=(wmWorld,wmAfrica,wmAsia,wmAustralia,wmCentralAmerica,
             wmEurope,wmEurope15,wmEurope27,wmSpain,
             wmMiddleEast,wmNorthAmerica,wmSouthAmerica,
             wmUSA,wmUSAHawaiiAlaska,wmEurasia,wmUK,
             wmGermany,wmUSACounties,wmUSAAlaskaCounties,
             wmJapan,wmChina,wmFrance,wmChinaProvinces,
             wmAustraliaCounties,wmAustraliaStates);

  TWorldEntities=class
  private
    IAfrica  : TTeeAfricaGroup;

    IAntarctica,
    IAustralia : TTeePolygonGroup;

    IAmerica : TTeeAmericaGroup;
    IAsia : TTeeAsiaGroup;
    IEurope : TTeeEuropeGroup;
    IMiddleEast : TTeePolygonGroup;

    {$IFDEF AUTOREFCOUNT}[weak]{$ENDIF}
    Map : TMapSeries;
  public
    Destructor Destroy; override;

    function Africa:TTeeAfricaGroup;
    function Antarctica:TTeePolygonGroup;
    function America:TTeeAmericaGroup;
    function Asia:TTeeAsiaGroup;
    function Australia:TTeePolygonGroup;
    function Europe:TTeeEuropeGroup;
    function MiddleEast:TTeePolygonGroup;
  end;

  TWorldFlags=class(TMapPersistent)
  private
    FVisible : Boolean;

    procedure SetVisible(const Value:Boolean);
  public
    procedure Assign(Source:TPersistent); override;
  published
    // property Style: All,NonNull <-- Show all flags or only for non-null values
    property Visible:Boolean read FVisible write SetVisible default False;
  end;

  TWorldSeries=class;

  TWorldCitiesSize=(csPopulation,csValue,csCustom);

  TCitySeries=class(TCustomBubbleSeries)
  private
    FSize : TWorldCitiesSize;

    IMap : TWorldSeries;
    IMapName : String;

    InMap : Array of Boolean;
    ISizeFactor : Single;

    function CalcProjection(const ValueIndex:Integer; out X,Y:Single):Boolean;
    procedure ReadMap(Reader: TReader);
    function ShouldDrawCity(ValueIndex:Integer):Boolean;
    procedure WriteMap(Writer: TWriter);
  protected
    function AxisLabelStyle(const AAxis:TChartAxis):TAxisLabelStyle; override;
    function CalcPointerSize(const ValueIndex:Integer):Single; override;
    function CanDrawMark(ValueIndex:Integer):Boolean; override;
    procedure DefineProperties(Filer: TFiler); override;
    Procedure DrawMark( ValueIndex:Integer; Const St:String;
                        APosition:TSeriesMarkPosition); override;
    Procedure DrawPointer(AX,AY:Integer; AColor:TColor; ValueIndex:Integer); override;
    procedure Loaded; override;
  public
    Constructor Create(AOwner:TComponent); override;
    Destructor Destroy; override;

    function ClickedPointer(ValueIndex,tmpX,tmpY:Integer;
                            x,y:Integer):Boolean; override;

    procedure DrawAllValues; override;
    procedure DrawValue(ValueIndex:Integer); override;
  end;

  TWorldCities=class(TMapPersistent)
  private
    FSize : TWorldCitiesSize;
    ICities : TCitySeries;

    function GetCities:TCitySeries;
    function GetVisible:Boolean;
    procedure SetSize(const Value:TWorldCitiesSize);
    procedure SetVisible(const Value:Boolean);
  public
    Destructor Destroy; override;

    procedure Assign(Source:TPersistent); override;

    property Series:TCitySeries read GetCities;
  published
    property Size:TWorldCitiesSize read FSize write SetSize default csPopulation;
    property Visible:Boolean read GetVisible write SetVisible default False;
  end;

  TWorldLayers=class(TMapPersistent)
  private
    FCities : TWorldCities;
    FStates : Boolean;
    IStates : TWorldSeries;

    procedure ChangedMap;
    Procedure Draw;
    function GetStates:TWorldSeries;
    procedure MoveAlaska;
    procedure SetCities(const Value:TWorldCities);
    procedure SetParentChart(const Value:TCustomAxisPanel);
    procedure SetStates(const Value:Boolean);
  protected
    function HasCities:Boolean;
  public
    Constructor Create(const AMap:TMapSeries); override;
    Destructor Destroy; override;

    procedure Assign(Source:TPersistent); override;
    property Series:TWorldSeries read GetStates;
  published
    property Cities:TWorldCities read FCities write SetCities;
    property States:Boolean read FStates write SetStates default False;
  end;

  TWorldSeries=class(TMapSeries)
  private
    FEntities   : TWorldEntities;
    FFlags      : TWorldFlags;
    FKeepAspect : Boolean;
    FLayers     : TWorldLayers;
    FMap        : TWorldMap;

    procedure AddToGroup(const AFilter:String; const AGroup:TTeePolygonGroup);
    procedure DrawFlag(const AShape:TTeePolygon);
    procedure DrawFlags;
    procedure FilterCities(const ASeries:TCitySeries);
    function GetEntities:TWorldEntities;
    function GetFlags:TWorldFlags;
    function GetLayers:TWorldLayers;
    procedure InternalDrawSeries(const ASeries:TChartSeries);
    class procedure LoadCities;
    procedure LoadMapShapes;

    {$IFDEF FMXLINUX}
    class function LoadANSIStrings(const AName:String):TStringList;
    {$ENDIF}
    
    class function LoadStrings(const AName:String):TStringList;
    procedure SetAxesGeo(const Value:Boolean);
    procedure SetFlags(const Value:TWorldFlags);
    procedure SetLayers(const Value:TWorldLayers);
    procedure SetKeepAspect(const Value:Boolean);
    procedure SetMap(const Value:TWorldMap);
    procedure TryLoadCachedMap;
    procedure TryLoadWorldMap;
  protected
    LoadLabels : Boolean;

    {$IFDEF TEECACHEDMAPS}
    Constructor CreateEmpty;
    {$ENDIF}

    Procedure Added; override;
    procedure AddSampleValues(NumValues:Integer; OnlyMandatory:Boolean=False); override;
    class procedure CreateSubGallery(AddSubChart: TChartSubGalleryProc); override;
    Procedure DrawAllValues; override;
    procedure GalleryChanged3D(Is3D: Boolean); override;
    class Function GetEditorClass:String; override;
    procedure Loaded; override;
    procedure PrepareForGallery; override;
    procedure RecalcAxisMargins(const Axis:TChartAxis; var R:TRect); override;
    Procedure Removed; override;
    Procedure SetParentChart(Const Value:TCustomAxisPanel); override;
    class procedure SetSubGallery(ASeries: TChartSeries; Index: Integer); override;
  public
    Constructor Create(AOwner:TComponent); override;
    Constructor CreateMap(const AOwner:TComponent; const AMap:TWorldMap);

    Destructor Destroy; override;

    class procedure AddCities(const DestSeries:TChartSeries);
    class procedure AddPlacemarks(const Values:TStringList; const DestSeries:TChartSeries);
    procedure Assign(Source:TPersistent); override;
    Procedure FillSampleValues(NumValues:Integer=0); override;
    class function IsWorldMap(const AMap:TWorldMap):Boolean;

    class procedure LoadMapFromResource(const Series:TMapSeries;
                                        const ShpName,ShxName:String);

    procedure ReloadMap;

    property Entities:TWorldEntities read GetEntities;
  published
    property Flags:TWorldFlags read GetFlags write SetFlags;
    property KeepAspect:Boolean read FKeepAspect write SetKeepAspect default True;
    property Layers:TWorldLayers read GetLayers write SetLayers;
    property Map:TWorldMap read FMap write SetMap default wmWorld;
    property Shapes stored False;
  end;

implementation

uses
  {$IFDEF FMX}
  FMXTee.Import.ShapeFile, FMXTee.ProConstants, FMXTee.Series.Surface,
  FMXTee.Tools
  {$IFDEF D19}
  ,FMX.Graphics
  {$ENDIF}
  {$ELSE}
  TeeSHP, Math, TeeSurfa, TeeTools, TeeProCo
  {$ENDIF};

{$R TeeFlags.res} // ISO 3166-A2

{$R TeeWorld.res}
{$R TeeSpain.res}
{$R TeeUK.res}
{$R TeeGermany.res}
{$R TeeUSAStates.res}
{$R TeeJapan.res}
{$R TeeChina.res}
{$R TeeFrance.res}
{$R TeeAustralia.res}

{$IFDEF D15}
{$DEFINE TEEFORMAT}
{$ENDIF}

{$IFDEF LCL}
{$DEFINE TEEFORMAT}
{$ENDIF}

{ TWorldEntities }

Destructor TWorldEntities.Destroy;
begin
  IAfrica.Free;
  IAntarctica.Free;
  IAmerica.Free;
  IAsia.Free;
  IAustralia.Free;
  IEurope.Free;
  IMiddleEast.Free;

  inherited;
end;

function TWorldEntities.Antarctica:TTeePolygonGroup;
begin
  if not Assigned(IAntarctica) then
     IAntarctica:=TTeePolygonGroup.Create(Map);

  if TWorldSeries(Map).FMap=wmWorld then
  begin
    if Length(IAntarctica.Shapes)=0 then
       IAntarctica.Add(24,131);
  end
  else
     IAntarctica.Shapes:=nil;

  result:=IAntarctica;
end;

function TWorldEntities.Africa: TTeeAfricaGroup;
begin
  if not Assigned(IAfrica) then
     IAfrica:=TTeeAfricaGroup.Create(Map);

  if (TWorldSeries(Map).Map=wmWorld) or (TWorldSeries(Map).Map=wmAfrica) then
  begin
    if Length(IAfrica.Shapes)=0 then
    begin
      if TWorldSeries(Map).Map=wmAfrica then
         IAfrica.Add(Map)
      else
         TWorldSeries(Map).AddToGroup('TeeAfrica',IAfrica);
    end;
  end
  else
    IAfrica.Shapes:=nil;

  result:=IAfrica;
end;

function TWorldEntities.America: TTeeAmericaGroup;
begin
  if not Assigned(IAmerica) then
     IAmerica:=TTeeAmericaGroup.Create(Map);

  if TWorldSeries(Map).Map in [wmWorld,wmCentralAmerica,wmNorthAmerica,
                   wmSouthAmerica,wmUSA,wmUSAHawaiiAlaska,wmUSACounties,
                   wmUSAAlaskaCounties] then
  begin
    if Length(IAmerica.Shapes)=0 then
    begin
      if TWorldSeries(Map).Map=wmWorld then
      begin
         TWorldSeries(Map).AddToGroup('TeeNorthAmerica',IAmerica);
         TWorldSeries(Map).AddToGroup('TeeSouthAmerica',IAmerica);
         TWorldSeries(Map).AddToGroup('TeeCentralAmerica',IAmerica);
      end
      else
         IAmerica.Add(Map)
    end;
  end
  else
    IAmerica.Shapes:=nil;

  result:=IAmerica;
end;

function TWorldEntities.Asia: TTeeAsiaGroup;
begin
  if not Assigned(IAsia) then
     IAsia:=TTeeAsiaGroup.Create(Map);

  if TWorldSeries(Map).Map in [wmWorld,wmAsia,wmChina,wmChinaProvinces] then
  begin
    if Length(IAsia.Shapes)=0 then
    begin
      if TWorldSeries(Map).Map=wmWorld then
         TWorldSeries(Map).AddToGroup('TeeAsia',IAsia)
      else
         IAsia.Add(Map);
    end;
  end
  else
    IAsia.Shapes:=nil;

  result:=IAsia;
end;

function TWorldEntities.Australia: TTeePolygonGroup;
begin
  if not Assigned(IAustralia) then
     IAustralia:=TTeePolygonGroup.Create(Map);

  if (TWorldSeries(Map).Map=wmWorld) or (TWorldSeries(Map).Map=wmAustralia) then
  begin
    if Length(IAustralia.Shapes)=0 then
    begin
      if TWorldSeries(Map).Map=wmAustralia then
         IAustralia.Add(Map)
      else
         TWorldSeries(Map).AddToGroup('TeeAustralia',IAustralia);
    end;
  end
  else
    IAustralia.Shapes:=nil;

  result:=IAustralia;
end;

function TWorldEntities.Europe: TTeeEuropeGroup;
begin
  if not Assigned(IEurope) then
     IEurope:=TTeeEuropeGroup.Create(Map);

  if TWorldSeries(Map).Map in [wmWorld,wmEurope,wmEurope15,wmEurope27,wmSpain,wmFrance,wmUK,wmGermany] then
  begin
    if Length(IEurope.Shapes)=0 then
    begin
      if TWorldSeries(Map).Map=wmWorld then
         TWorldSeries(Map).AddToGroup('TeeEurope',IEurope)
      else
         IEurope.Add(Map);
    end;
  end
  else
    IEurope.Shapes:=nil;

  result:=IEurope;
end;

function TWorldEntities.MiddleEast:TTeePolygonGroup;
begin
  if not Assigned(IMiddleEast) then
     IMiddleEast:=TTeePolygonGroup.Create(Map);

  if TWorldSeries(Map).Map in [wmWorld,wmMiddleEast] then
  begin
    if Length(IMiddleEast.Shapes)=0 then
    begin
      if TWorldSeries(Map).Map=wmWorld then
         TWorldSeries(Map).AddToGroup('TeeMiddleEast',IMiddleEast)
      else
         IMiddleEast.Add(Map);
    end;
  end
  else
    IMiddleEast.Shapes:=nil;

  result:=IMiddleEast;
end;

type
  TSeriesAccess=class(TChartSeries);

{ TWorldFlags }

procedure TWorldFlags.Assign(Source:TPersistent);
begin
  if Source is TWorldFlags then
     Visible:=TWorldFlags(Source).FVisible
  else
     inherited;
end;

procedure TWorldFlags.SetVisible(const Value:Boolean);
begin
  if FVisible<>Value then
  begin
    FVisible:=Value;
    IMap.Repaint;
  end;
end;

{ TCitySeries }

type
  TCity=record
    Lat,
    Lon : Single;
    Size,
    Population : Integer;
    Name,
    Country : String;
  end;

var
  ICities : Array of TCity;

Constructor TCitySeries.Create(AOwner:TComponent);
begin
  inherited;

  XValues.Order:=loNone;

  ShowInLegend:=False;
  ManualData:=True;
  DontSaveData:=True;

  Pointer.Pen.Hide;
  Pointer.InflateMargins:=False;

  // Dont participate in automatic axes calc of min and max (don't resize map)
  INoAxisMinMax:=True;

  Marks.AutoPosition:=False;
  Marks.Arrow.Hide;

  DrawBetweenPoints:=False;
  CalcVisiblePoints:=False;

  TreatNulls:=tnDontPaint;
end;

Destructor TCitySeries.Destroy;
begin
  if IMap<>nil then
     IMap.Layers.Cities.ICities:=nil
  else
  if Owner is TWorldSeries then
     TWorldSeries(Owner).Layers.Cities.ICities:=nil;

  inherited;
end;

// Prevent City names to appear as chart axes labels
function TCitySeries.AxisLabelStyle(const AAxis:TChartAxis):TAxisLabelStyle;
begin
  result:=talValue;
end;

procedure TCitySeries.DrawAllValues;
begin
  ISizeFactor:=GetHorizAxis.CalcPosPoint(1)-GetHorizAxis.CalcPosPoint(0);

  if FSize=csPopulation then
  begin
    if ISizeFactor>=0.000001 then
       ISizeFactor:=0.05/ISizeFactor
    else
       ISizeFactor:=0;
  end
  else
    ISizeFactor:=ISizeFactor;

  inherited;
end;

function TCitySeries.CalcPointerSize(const ValueIndex:Integer):Single;
var tmp : Single;
begin
  case FSize of
    csPopulation: result:=ICities[ValueIndex].Size*ISizeFactor;

         csValue: begin
                    tmp:=RadiusValues.Range;

                    if tmp=0 then
                       result:=0
                    else
                       result:=ISizeFactor*(RadiusValues.Value[ValueIndex]-RadiusValues.MinValue)/tmp
                  end;
  else
    result:=Pointer.SizeFloat;
  end;
end;

function TCitySeries.ShouldDrawCity(ValueIndex:Integer):Boolean;
begin
  result:=(InMap<>nil) and InMap[ValueIndex] and (CalcPointerSize(ValueIndex)>1);
end;

function TCitySeries.CanDrawMark(ValueIndex:Integer):Boolean;
begin
  result:=InMap[ValueIndex];

  if result then
     result:=(CalcPointerSize(ValueIndex)>2) and inherited CanDrawMark(ValueIndex);
end;

procedure TCitySeries.ReadMap(Reader: TReader);
begin
  IMapName:=Reader.ReadString;
end;

procedure TCitySeries.WriteMap(Writer: TWriter);
begin
  Writer.WriteString(IMap.Name);
end;

procedure TCitySeries.DefineProperties(Filer: TFiler);
begin
  inherited;
  Filer.DefineProperty('Map',ReadMap,WriteMap,IMap<>nil);
end;

Procedure TCitySeries.DrawMark( ValueIndex:Integer; Const St:String;
                                APosition:TSeriesMarkPosition);
begin
  APosition.Offset(TeePoint(0,-Round(CalcPointerSize(ValueIndex))));

  if TeeRectIntersects(APosition.Bounds,ParentChart.ChartRect) then
     inherited;
end;

Procedure TCitySeries.DrawPointer(AX,AY:Integer; AColor:TColor; ValueIndex:Integer);
begin
  Pointer.SizeFloat:=CalcPointerSize(ValueIndex);
  inherited;
end;

procedure TCitySeries.Loaded;
begin
  inherited;

  if IMapName<>'' then
     if ParentChart<>nil then
        IMap:=ParentChart.SeriesList.Find(IMapName) as TWorldSeries;
end;

type
  TMapRenderAccess=class(TMapRenderOptions);

function TCitySeries.CalcProjection(const ValueIndex:Integer; out X,Y:Single):Boolean;
var tmpZ : Single;
begin
  X:=XValues.Value[ValueIndex];
  Y:=YValues.Value[ValueIndex];

  TMapRenderAccess(IMap.RenderOptions).CalcSpherical(X,Y,tmpZ);

  result:=tmpZ>=0;
end;

procedure TCitySeries.DrawValue(ValueIndex:Integer);
var tmpX,
    tmpY : Single;
begin
  if ShouldDrawCity(ValueIndex) then
  begin
    if (IMap<>nil) and (IMap.RenderMode=rm3D) then
    begin
      if CalcProjection(ValueIndex,tmpX,tmpY) then
      begin
        PreparePointer(ValueIndex);
        DrawPointer(CalcXPosValue(tmpX),CalcYPosValue(tmpY),ValueColor[ValueIndex],ValueIndex);
      end;
    end
    else
      inherited;
  end;
end;

Function TCitySeries.ClickedPointer( ValueIndex,tmpX,tmpY:Integer;
                                     x,y:Integer):Boolean;
var tmpX2,
    tmpY2 : Single;
begin
  if ShouldDrawCity(ValueIndex) then
  begin
    if (IMap<>nil) and (IMap.RenderMode=rm3D) then
    begin
      if CalcProjection(ValueIndex,tmpX2,tmpY2) then
         result:=inherited ClickedPointer(ValueIndex,
                     CalcXPosValue(tmpX2),
                     CalcYPosValue(tmpY2),
                     x,y)
      else
         result:=False;
    end
    else
      result:=inherited ClickedPointer(ValueIndex,tmpX,tmpY,x,y);
  end
  else
    result:=False;
end;

{ TWorldCities }

Destructor TWorldCities.Destroy;
begin
  ICities.Free;
  inherited;
end;

function TWorldCities.GetCities:TCitySeries;
begin
  if ICities=nil then
  begin
    ICities:=TCitySeries.Create(IMap.Owner);
    ICities.IMap:=TWorldSeries(IMap);
    ICities.Visible:=False;

    TWorldSeries.AddCities(ICities);
    TWorldSeries(IMap).FilterCities(ICities);

    ICities.ParentChart:=IMap.ParentChart;
  end;

  result:=ICities;
end;

function TWorldCities.GetVisible:Boolean;
begin
  result:=(ICities<>nil) and ICities.Visible;
end;

procedure TWorldCities.SetSize(const Value:TWorldCitiesSize);
begin
  if FSize<>Value then
  begin
    FSize:=Value;

    if ICities<>nil then
    begin
      ICities.FSize:=FSize;
      ICities.Repaint;
    end;
  end;
end;

procedure TWorldCities.SetVisible(const Value:Boolean);
begin
  if Value<>Visible then
     if Value or (ICities<>nil) then
        Series.Visible:=Value;
end;

procedure TWorldCities.Assign(Source:TPersistent);
begin
  if Source is TWorldCities then
  begin
    Visible:=TWorldCities(Source).Visible;
    Size:=TWorldCities(Source).FSize;
  end
  else
     inherited;
end;

{ TWorldLayers }

Constructor TWorldLayers.Create(const AMap:TMapSeries);
begin
  inherited;
  FCities:=TWorldCities.Create(IMap);
end;

Destructor TWorldLayers.Destroy;
begin
  FCities.Free;
  IStates.Free;
  inherited;
end;

procedure TWorldLayers.Assign(Source:TPersistent);
begin
  if Source is TWorldLayers then
  begin
    FStates:=TWorldLayers(Source).FStates;
    Cities:=TWorldLayers(Source).FCities;
  end
  else
     inherited;
end;

function TWorldLayers.GetStates:TWorldSeries;
begin
  if IStates=nil then
  begin
    IStates:=TWorldSeries.CreateMap(IMap,wmUSA);
    IStates.Brush.Style:=bsClear;
    IStates.Pen.Color:=clDkGray;
    IStates.Visible:=False;
  end;

  result:=IStates;
end;

function TWorldLayers.HasCities:Boolean;
begin
  result:=Assigned(ICities);
end;

procedure TWorldLayers.SetStates(const Value:Boolean);
begin
  if FStates<>Value then
  begin
    FStates:=Value;
    IMap.Repaint;
  end;
end;

procedure TWorldLayers.MoveAlaska;
var tmp : TTeePolygonGroup;
    tmpWorld : TWorldSeries;
    tmpUSA : TTeeUSAGroup;

    tmpStates,
    tmpCounties : Boolean;
begin
  tmpWorld:=TWorldSeries(IMap);

  tmpStates:=(tmpWorld.Map=wmUSAHawaiiAlaska);
  tmpCounties:=(tmpWorld.Map=wmUSAAlaskaCounties);

  if tmpStates or tmpCounties then
  begin
    tmpUSA:=tmpWorld.Entities.America.North.USA;

    if tmpStates then
    begin
      tmp:=tmpUSA.Alaska;
      tmp.Resize(0.3,0.3);
      tmp.Offset(35,-35);

      tmp:=tmpUSA.Hawaii;
      tmp.Offset(50,5);
    end
    else
    begin
      tmp:=tmpUSA.Alaska;
      tmp.Resize(0.3,0.3);
      tmp.Offset(-72,-33);

      tmp:=tmpUSA.Hawaii;
      tmp.Offset(52,6);

      tmp:=tmpUSA.PuertoRico;
      tmp.Resize(2.5,2.5);
      tmp.Offset(-5,10);
    end;
  end;
end;

procedure TWorldLayers.ChangedMap;
begin
  if (FCities<>nil) and (FCities.ICities<>nil) then
     TWorldSeries(IMap).FilterCities(FCities.ICities);
end;

Procedure TWorldLayers.Draw;
var tmp : TWorldMap;
begin
  if FStates then
  begin
    tmp:=TWorldSeries(IMap).FMap;

    if (tmp=wmWorld) or (tmp=wmUSACounties) or (tmp=wmUSAAlaskaCounties) or
       (tmp=wmNorthAmerica) then
    begin
      GetStates;

      IStates.RenderMode:=IMap.RenderMode;

      if IStates.RenderMode=rm3D then
      begin
        IStates.RenderOptions.Rotation:=IMap.RenderOptions.Rotation;
        IStates.RenderOptions.Brush.Style:=bsClear;
        IStates.RenderOptions.Meridians.Hide;
        IStates.RenderOptions.Parallels.Hide;
      end;

      TWorldSeries(IMap).InternalDrawSeries(IStates);
    end;
  end;
end;

procedure TWorldLayers.SetCities(const Value:TWorldCities);
begin
  if Value=nil then
     FreeAndNil(FCities)
  else
     Cities.Assign(Value);
end;

procedure TWorldLayers.SetParentChart(const Value: TCustomAxisPanel);
begin
  if FCities.ICities<>nil then
     FCities.ICities.ParentChart:=Value;
end;

{ TWorldSeries }

Constructor TWorldSeries.Create(AOwner: TComponent);
begin
  CreateMap(AOwner,wmWorld);
end;

Constructor TWorldSeries.CreateMap(const AOwner:TComponent; const AMap:TWorldMap);
begin
  inherited Create(AOwner);

  FKeepAspect:=True;
  LoadLabels:=True;

  // Speed tradeoff.
  // Polygons less than 4 pixel size (when zoomed) aren't displayed.
  MinSize:=2;

  // Very light grey
  Pen.Color:=RGB($D0,$D0,$D0);

  Marks.Transparent:=True;

  if AMap=wmWorld then
     TryLoadWorldMap
  else
     Map:=AMap;

  ManualData:=True;
  CanSaveData:=True;
end;

Destructor TWorldSeries.Destroy;
begin
  FEntities.Free;
  FFlags.Free;
  FLayers.Free;
  inherited;
end;

type
  TAxisAccess=class(TChartAxis);

procedure TWorldSeries.SetAxesGeo(const Value:Boolean);

  procedure SetGeo(const Axis:TChartAxis);
  begin
    TAxisAccess(Axis).IAxisGeo:=Value;
  end;

begin
  if HorizAxis=aBothHorizAxis then
  begin
    SetGeo(ParentChart.Axes.Top);
    SetGeo(ParentChart.Axes.Bottom);
  end
  else
    SetGeo(GetHorizAxis);

  if VertAxis=aBothVertAxis then
  begin
    SetGeo(ParentChart.Axes.Left);
    SetGeo(ParentChart.Axes.Right);
  end
  else
    SetGeo(GetVertAxis);
end;

Procedure TWorldSeries.Added;
begin
  inherited;

  SetAxesGeo(True);

  // Set mouse wheel to zoom in-out
  ParentChart.Zoom.MouseWheel:=pmwNormal;
  ParentChart.Panning.MouseWheel:=pmwNone;

  // Zoom keeping ratio
  ParentChart.Zoom.KeepAspectRatio:=True;
end;

Procedure TWorldSeries.Removed;
begin
  SetAxesGeo(False);
  inherited;
end;

procedure TWorldSeries.TryLoadWorldMap;
begin
  if (csDesigning in ComponentState) or
     (not Assigned(Owner)) or
     (not (csLoading in Owner.ComponentState)) then
        if (not Assigned(Owner)) or
           (Owner.ClassName<>'TGalleryChart') then
              LoadMapShapes;
end;

function TWorldSeries.GetFlags:TWorldFlags;
begin
  if FFlags=nil then
     FFlags:=TWorldFlags.Create(Self);

  result:=FFlags;
end;

function TWorldSeries.GetLayers:TWorldLayers;
begin
  if FLayers=nil then
     FLayers:=TWorldLayers.Create(Self);

  result:=FLayers;
end;

function TWorldSeries.GetEntities:TWorldEntities;
begin
  if not Assigned(FEntities) then
  begin
    FEntities:=TWorldEntities.Create;
    FEntities.Map:=Self;
  end;

  result:=FEntities;
end;

{$IFDEF TEECACHEDMAPS}
Constructor TWorldSeries.CreateEmpty;
begin
  inherited Create(nil);
  ManualData:=True;
  CanSaveData:=True;
end;
{$ENDIF}

procedure TWorldSeries.Assign(Source: TPersistent);
begin
  if Source is TWorldSeries then
  begin
    Map:=TWorldSeries(Source).Map;
    Flags:=TWorldSeries(Source).Flags;
    Layers:=TWorldSeries(Source).Layers;
  end;

  inherited;
end;

procedure TWorldSeries.AddToGroup(const AFilter:String; const AGroup:TTeePolygonGroup);
var st : TStringList;
    t,
    i,
    shapeIndex : Integer;
    s : String;
begin
  st:=LoadStrings(AFilter);
  try
    for t:=0 to st.Count-1 do
    begin
      s:=st[t];

      {$IFDEF NEXTGEN}
      i:=s.IndexOf(' ');
      shapeIndex:=StrToInt(s.SubString(0,i));
      {$ELSE}
      i:=Pos(' ',s);
      shapeIndex:=StrToInt(Copy(s,1,i-1));
      {$ENDIF}

      AGroup.Add(shapeIndex,shapeIndex);
    end;
  finally
    st.Free;
  end;
end;

procedure TWorldSeries.DrawFlag(const AShape:TTeePolygon);
var b : TBitmap;
    x,y : Single;
    tmp : {$IFDEF FMX}TPoint{$ELSE}TPointFloat{$ENDIF};
    tmpPic : TTeePicture;
begin
  if (AShape.Code<>'') and AShape.Points.Visible and AShape.Visible then
  begin
    tmpPic:=AShape.Picture;

    if not tmpPic.Valid then
       TeeLoadBitmap(tmpPic.Bitmap,'TEEFLAG'+AShape.Code);

    b:=tmpPic.Graphic as TBitmap;

    if MapMarks.Centroid then
    begin
      tmp:=AShape.Centroid;
      x:=tmp.x;
      y:=tmp.y;
    end
    else
    with AShape.Bounds do
    begin
      x:=(Left+Right-b.Width)*0.5;
      y:=(Top+Bottom-b.Height)*0.5;
    end;

    if Marks.Visible and (AShape.Text<>'') then
       y:=y+Marks.Height;

    ParentChart.Canvas.Draw(Round(x),Round(y),b);
  end;
end;

class procedure TWorldSeries.LoadMapFromResource(const Series:TMapSeries;
                                                 const ShpName,ShxName:String);
var shp : TResourceStream;
    shx : TResourceStream;
begin
  shp:=TeeResourceStream(ShpName);
  try
    shx:=TeeResourceStream(ShxName);
    try
      LoadMap(Series,shp,shx);
    finally
      shx.Free;
    end;
  finally
    shp.Free;
  end;
end;

function MapResource(const AName:String):TResourceStream;
begin
  result:=TeeResourceStream(AName);

  if not Assigned(result) then
     raise Exception.Create('Resource: '+AName+' not found');
end;

{$IFDEF FMXLINUX}
class function TWorldSeries.LoadANSIStrings(const AName:String):TStringList;
var tmp : TResourceStream;
begin
  tmp:=MapResource(AName);
  try
    result:=TStringList.Create;
    result.LoadFromStream(tmp,TEncoding.ANSI);
  finally
    tmp.Free;
  end;
end;
{$ENDIF}

class function TWorldSeries.LoadStrings(const AName:String):TStringList;
var r : TResourceStream;
begin
  r:=MapResource(AName);
  try
    result:=TStringList.Create;

    {$IFDEF FMXLINUX}
    result.LoadFromStream(r,TEncoding.ANSI);
    {$ELSE}
    result.LoadFromStream(r {$IFNDEF MSWINDOWS}{$IFNDEF LINUX},TEncoding.ANSI{$ENDIF}{$ENDIF});
    {$ENDIF}
  finally
    r.Free;
  end;
end;

// Note:
//   It is slower to maintain a cache of loaded maps, than to
//   reload again the same map resource each time:

{$IFDEF TEECACHEDMAPS}
var
  CachedSpain:TWorldSeries=nil;
  CachedUSA:TWorldSeries=nil;
  CachedWorld:TWorldSeries=nil;
{$ENDIF}

procedure TWorldSeries.TryLoadCachedMap;

  procedure TryLoadMapFromResource(const AName:String);
  {$IFDEF TEECACHEDMAPS}
  var tmp : TWorldSeries;
  {$ENDIF}
  begin
    {$IFDEF TEECACHEDMAPS}
    if AName='SPAIN' then
    begin
      if not Assigned(CachedSpain) then
      begin
        CachedSpain:=TWorldSeries.CreateEmpty;
        LoadMapFromResource(CachedSpain,'TEESPAINSHP','TEESPAINSHX');
      end;

      tmp:=CachedSpain;
    end
    else
    if AName='USA' then
    begin
      if not Assigned(CachedUSA) then
      begin
        CachedUSA:=TWorldSeries.CreateEmpty;
        LoadMapFromResource(CachedUSA,'TEEUSASTATESSHP','TEEUSASTATESSHX');
      end;

      tmp:=CachedUSA;
    end
    else
    if AName='WORLD' then
    begin
      if not Assigned(CachedWORLD) then
      begin
        CachedWORLD:=TWorldSeries.CreateEmpty;
        LoadMapFromResource(CachedWORLD,'TEEWORLDSHP','TEEWORLDSHX');
      end;

      tmp:=CachedWORLD;
    end;

    Shapes:=tmp.Shapes;
    {$ELSE}
    LoadMapFromResource(Self,'TEE'+AName+'SHP','TEE'+AName+'SHX');
    {$ENDIF}
  end;

  procedure ScaleCoordinates(const XDivisor,YDivisor,XOffset,YOffset:Single);
  var t,
      tt : Integer;
      tmpX,
      tmpY : TChartValues;
  begin
    for t:=0 to Shapes.Count-1 do
        with Shapes[t].Points do
        begin
          tmpX:=XValues.Value;
          tmpY:=YValues.Value;

          for tt:=0 to Count-1 do
          begin
            tmpX[tt]:=(tmpX[tt]*XDivisor)-XOffset;
            tmpY[tt]:=(tmpY[tt]*YDivisor)-YOffset;
          end;
        end;
  end;

var
  tmp : Single;

begin
  ILoading:=True;
  try
    // Load shapes
    case FMap of
           wmSpain : begin
                       TryLoadMapFromResource('SPAIN');

                       tmp:=0.0000119;
                       ScaleCoordinates(tmp,0.000009,9{7.28},-0.1{4.26});
                     end;

              wmUK : begin
                       TryLoadMapFromResource('UK');
                       ScaleCoordinates(0.000014,0.0000088,10,-1.17);
                     end;

         wmGermany : TryLoadMapFromResource('GERMANY');
            wmUSA,
 wmUSAHawaiiAlaska : TryLoadMapFromResource('USASTATES');
    wmUSACounties,
wmUSAAlaskaCounties: TryLoadMapFromResource('USACOUNTIES');
           wmJapan : TryLoadMapFromResource('JAPAN');
           wmChina : begin
                       TryLoadMapFromResource('CHINA');
                       ScaleCoordinates(0.0000115,0.0000088,114,-1);
                     end;

  wmChinaProvinces : TryLoadMapFromResource('CHINAPROV');

          wmFrance : TryLoadMapFromResource('FRANCE');
wmAustraliaCounties: TryLoadMapFromResource('AUSTRALIACOUNTIES');
 wmAustraliaStates : TryLoadMapFromResource('AUSTRALIASTATES');
    else
      TryLoadMapFromResource('WORLD');
    end;

  finally
    ILoading:=False;
  end;
end;

procedure TWorldSeries.InternalDrawSeries(const ASeries:TChartSeries);
var tmp : TTeeTransparency;
    tmpNew : Integer;
begin
  tmp:=ASeries.Transparency;

  if Transparency>0 then
  begin
    tmpNew:=tmp+Transparency;

    if tmpNew>100 then
       tmpNew:=100;

    ASeries.Transparency:=tmpNew;
  end;

  TSeriesAccess(ASeries).FParent:=ParentChart;

  TSeriesAccess(ASeries).RecalcGetAxis;
  TSeriesAccess(ASeries).CalcFirstLastVisibleIndex;

  TSeriesAccess(ASeries).DrawAllValues;
  TSeriesAccess(ASeries).FParent:=nil;

  ASeries.Transparency:=tmp;
end;

class function TWorldSeries.IsWorldMap(const AMap:TWorldMap):Boolean;
begin
  case AMap of
    wmWorld,
    wmAfrica,
    wmAsia,
    wmAustralia,
    wmCentralAmerica,
    wmEurope,
    wmEurope15,
    wmEurope27,
    wmMiddleEast,
    wmNorthAmerica,
    wmSouthAmerica,
    wmEurasia : result:=True;
  else
    result:=False;
  end;
end;

// Set all values to zero
procedure FillZero(const AList:TChartValues);
var t : Integer;
begin
  for t:=0 to High(AList) do
      AList[t]:=0;
end;

type
  TTeePolygonAccess=class(TTeePolygon);
  TTeePolygonListAccess=class(TTeePolygonList);

const
  AfricaShapes:Array[0..48] of String=
 ('ma','za','dz','ly','tn','na','eg','sd','er','et','so','ke','tz','mz','mg',
  'sz','ls','bw','ao','cd','zm','zw','mw','cg','ga','gn','cm','cf','bi',
  'rw','ug','ng','td','ne','bj','tg','gh','ci','bf','lr','sl','ml','gw',
  'sn','gm','mr','eh','dj','ss');

  AsiaShapes:Array[0..18] of String=
 ('ru','cn','tw','np','bt','mn','in','lk','jp','kr','kp','bd','th','la',
  'vn','kh','my','id','ph');

  AustraliaShapes:Array[0..6] of String=
 ('au','sb','nz','pg','nc','fj','vu');

  CentralAmericaShapes:Array[0..18] of String=
 ('gt','bz','sv','hn','ni','cr','pa','jm','cu','ht','do','bs','ky','vg','vi',
  'tc','ag','kn','ai'); // Guadeloupe: 'gp' missing ????

  Europe15Shapes:Array[0..15] of String=
 ('pt','fr','gb','ie','it','de','dk','be','nl','at','lu','se','fi','es','gr',
  'fo');

  Europe28Shapes:Array[0..11] of String=
 ('si','cz','sk','pl','hu','bg','ro','lv','lt','ee','mt','hr');

  EuropeShapes:Array[0..16] of String=
  // Missing:
  // 'gi' Gibraltar
  // '??' Kosovo (used here: _KO)
 ('ad','is','no','al','by','tr','md','ge','az','am','ch','ua','ba','rs','me',
  '_KO','mk');

  NorthAmericaShapes:Array[0..2] of String=('us','ca','mx');

  SouthAmericaShapes:Array[0..19] of String=

  // Missing in map:
  // 'gf' French Guiana
  // 'mq' Martinique
  // 'an' Antilles (Netherlands)

 ('br','ar','fk','cl','ve','co','ec','pe','gy','tt','sr','bo','py','uy',
  'bb','gd','aw','vc','lc','gs');

  MiddleEastShapes:Array[0..23] of String=
 ('af','am','az','cy','ge','iq','ir','il','jo','kz','kw','kg','lb','om','pk',
  'qa','sa','sy','tj','tm','ae','uz','ps','ye');


function StringIn(const S:String; const AList:Array of String):Boolean;
var t : Integer;
begin
  for t:=0 to High(AList) do
      if AList[t]=S then
      begin
        result:=True;
        Exit;
      end;

  result:=False
end;

procedure TWorldSeries.FilterCities(const ASeries:TCitySeries);
var
  tmpCountry : String;

  function IsAllEurope:Boolean;
  begin
    result:=StringIn(tmpCountry,Europe15Shapes) or
            StringIn(tmpCountry,Europe28Shapes) or
            StringIn(tmpCountry,EuropeShapes);
  end;

  function IsAsia:Boolean;
  begin
    result:=StringIn(tmpCountry,AsiaShapes);;
  end;

var t : Integer;
    tmp : Boolean;
begin
  SetLength(ASeries.InMap,ASeries.Count);

  if FMap=wmWorld then
     for t:=0 to ASeries.Count-1 do
         ASeries.InMap[t]:=True
  else
  begin
    for t:=0 to ASeries.Count-1 do
    begin
      tmpCountry:=LowerCase(ICities[t].Country);

      case FMap of
        wmEurope: tmp:=IsAllEurope;

      wmEurope15: tmp:=StringIn(tmpCountry,Europe15Shapes);

      wmEurope27: tmp:=StringIn(tmpCountry,Europe15Shapes) or
                       StringIn(tmpCountry,Europe28Shapes);

          wmAsia: tmp:=IsAsia;
       wmEurasia: tmp:=IsAllEurope or IsAsia;
        wmAfrica: tmp:=StringIn(tmpCountry,AfricaShapes);
     wmAustralia: tmp:=StringIn(tmpCountry,AustraliaShapes);
wmCentralAmerica: tmp:=StringIn(tmpCountry,CentralAmericaShapes);
    wmMiddleEast: tmp:=StringIn(tmpCountry,MiddleEastShapes);
  wmNorthAmerica: tmp:=StringIn(tmpCountry,NorthAmericaShapes);
  wmSouthAmerica: tmp:=StringIn(tmpCountry,SouthAmericaShapes);

     wmUSA,
     wmUSAHawaiiAlaska,
     wmUSACounties,
     wmUSAAlaskaCounties: tmp:=tmpCountry='us';

       wmGermany: tmp:=tmpCountry='de';
         wmSpain: tmp:=tmpCountry='es';
         wmChina,
         wmChinaProvinces: tmp:=tmpCountry='cn';
        wmFrance: tmp:=tmpCountry='fr';
            wmUK: tmp:=tmpCountry='gb';
         wmJapan: tmp:=tmpCountry='jp';

     wmAustraliaStates,
     wmAustraliaCounties: tmp:=tmpCountry='au';

      else
        tmp:=False;
      end;

      ASeries.InMap[t]:=tmp;
    end;
  end;

  // if FMap=wmSpain then
  //    MoveCanaryCities;
end;

procedure TWorldSeries.LoadMapShapes;
const
  MagicFlag=12345678;

  function PosOfChar(const S:String; const C:Char):Integer;
  begin
    result:={$IFDEF NEXTGEN}S.IndexOf(C){$ELSE}Pos(C,S){$ENDIF};
  end;

  procedure SetNameAndCode(const AIndex:Integer; const S:String);
  var i : Integer;
  begin
    i:=PosOfChar(S,',');

    if i=0 then
       Labels[AIndex]:=S
    else
    begin
      Labels[AIndex]:={$IFDEF NEXTGEN}S.SubString(0,i){$ELSE}Copy(S,1,i-1){$ENDIF};
      Shapes[AIndex].Code:={$IFDEF NEXTGEN}S.SubString(i+1){$ELSE}Copy(S,i+1,Length(S)){$ENDIF};
    end;
  end;

  procedure FilterShapes(const ACodes:Array of String); overload;
  var t : Integer;
      tmp : Integer;
  begin
    for t:=Low(ACodes) to High(ACodes) do
    begin
      tmp:=TTeePolygonListAccess(Shapes).IndexOfCode(ACodes[t]);

      if tmp=-1 then
         raise Exception.Create('Code not found: '+ACodes[t]);

      if MandatoryValueList.Value[tmp]=MagicFlag then
         raise Exception.Create('Duplicated Code: '+ACodes[t]);

      MandatoryValueList.Value[tmp]:=MagicFlag;
    end;
  end;

  procedure FilterShapes(const FilterName:String; const ContainsIndex:Boolean=True;
       const FlagToDelete:Boolean=True); overload;
  var st : TStringList;
      t,
      i,
      shapeIndex : Integer;
      s : String;
  begin
    st:=LoadStrings(FilterName);
    try
      shapeIndex:=0;

      for t:=0 to st.Count-1 do
      begin
        s:=st[t];

        if ContainsIndex then
        begin
          if {$IFDEF NEXTGEN}S.SubString(0,1){$ELSE}Copy(S,1,1){$ENDIF}='#' then
             FilterShapes({$IFDEF NEXTGEN}s.SubString(1){$ELSE}Copy(s,2,Length(s)){$ENDIF})
          else
          begin
            i:=PosOfChar(S,' ');

            if i>0 then
            begin
              shapeIndex:=StrToInt({$IFDEF NEXTGEN}S.SubString(0,i){$ELSE}Copy(S,1,i-1){$ENDIF});

              if shapeIndex<>-1 then
              begin
                if LoadLabels then
                   SetNameAndCode(shapeIndex,{$IFDEF NEXTGEN}S.SubString(i+1){$ELSE}Copy(S,i+1,Length(S)){$ENDIF});

                if FlagToDelete then
                   MandatoryValueList.Value[shapeIndex]:=MagicFlag;
              end;
            end;
          end;
        end
        else
        begin
          while Shapes[shapeIndex].ParentShape<>nil do
                Inc(shapeIndex);

          if LoadLabels then
          begin
            SetNameAndCode(shapeIndex,s);
            Inc(shapeIndex);
          end;

          if FlagToDelete then
             MandatoryValueList.Value[shapeIndex]:=MagicFlag;
        end;
      end;
    finally
      st.Free;
    end;
  end;

  procedure FilterSiblings(const AText:String);
  var st : TStringList;
      t,
      tmpLast,
      i : Integer;

      tmpLabel,
      s : String;
      tmp : Integer;
      tmpShape : TTeePolygon;
  begin
    st:=LoadStrings(AText);
    try
      tmp:=0;

      tmpLast:=Shapes.Count-1;

      for t:=0 to st.Count-1 do
      begin
        s:=st[t];
        i:=PosOfChar(S,#9);

        tmpLabel:={$IFDEF NEXTGEN}S.SubString(i+1){$ELSE}Copy(S,i+1,Length(S)){$ENDIF};

        tmpShape:=Shapes[tmp];

        tmpShape.Code:={$IFDEF NEXTGEN}S.SubString(0,i){$ELSE}Copy(S,1,i-1){$ENDIF};
        Labels[tmp]:=tmpLabel;

        Inc(tmp);

        // Assign same code and label for sibling shapes
        while tmp<tmpLast do
          if Shapes[tmp].ParentShape=tmpShape then
          begin
            Shapes[tmp].Code:=tmpShape.Code;
            Labels[tmp]:=tmpLabel;

            Inc(tmp);
          end
          else
             break;

        if tmp>tmpLast then
           break;
      end;
    finally
      st.Free;
    end;
  end;

  // Flag shapes by index, for later removal
  procedure RemoveShapes(AFrom,ATo:Integer); overload;
  var t : Integer;
  begin
    for t:=AFrom to ATo do
        MandatoryValueList.Value[t]:=MagicFlag;
  end;

  procedure RemoveShapes(const CodePrefix:String); overload;
  var t,
      L : Integer;
  begin
    L:=Length(CodePrefix);

    for t:=0 to Shapes.Count-1 do
        if Copy(Shapes[t].Code,1,L)=CodePrefix then
           MandatoryValueList.Value[t]:=MagicFlag;
  end;

  // Flag a polygon to removal (AIndex means "before cleaning" index)
  procedure RemoveNonFlaggedShape(const AIndex:Integer);
  begin
    MandatoryValueList.Value[AIndex]:=0;
    Shapes[AIndex].ParentShape:=nil;
  end;

  // Set Siblings Text and Code
  procedure SetSiblings;
  var t : Integer;
      tmp,
      tmpParent : TTeePolygon;
  begin
    for t:=0 to Shapes.Count-1 do
    begin
      tmp:=Shapes[t];
      tmpParent:=tmp.ParentShape;

      if tmpParent<>nil then
      begin
        Labels[t]:=tmpParent.Text; // faster than tmp.Text:=tmpParent.Text
        tmp.Code:=tmpParent.Code;
      end;
    end;
  end;

  procedure CleanShapes;
  var
    tmpList : TChartValues;

    procedure DeleteFlagged;
    var t : Integer;
    begin
      // Slow in XE4, due to Series DeleteLabel:
      t:=Shapes.Count-1;

      while t>=0 do
      begin
        if tmpList[t]=MagicFlag then
           Delete(t);

        Dec(t);
      end;
    end;

    procedure DeleteNotFlagged(t:Integer);
    var tmp : TTeePolygon;
    begin
      // Slow in XE4, due to Series DeleteLabel:
      while t>=0 do
      begin
        if tmpList[t]<>MagicFlag then
        begin
          tmp:=Shapes[t].ParentShape;

          if (tmp=nil) or (tmpList[tmp.Index]<>MagicFlag) then
             Delete(t);
        end;

        Dec(t);
      end;
    end;

  begin
    tmpList:=MandatoryValueList.Value;

    if (FMap=wmUSACounties) or (FMap=wmUSAAlaskaCounties) then
       DeleteFlagged
    else
    if (FMap<>wmWorld) and (IsWorldMap(FMap) or (FMap=wmUSA) or (FMap=wmUSAHawaiiAlaska)) then
       DeleteNotFlagged(Shapes.Count-1);

    FillZero(tmpList);
  end;

  procedure Transform(const P:TTeePolygon; const X,Y:Single);
  var t : Integer;
      tmpP : TTeePolygons;
  begin
    TTeePolygonAccess(P).DoOffset(X,Y);

    tmpP:=P.Siblings;

    for t:=0 to High(tmpP) do
        TTeePolygonAccess(tmpP[t]).DoOffset(X,Y);
  end;

  procedure MoveCanaryIslands;
  begin
    Transform(Shapes.ByCode['35'],21,7);
    Transform(Shapes.ByCode['38'],21,7);
  end;

  procedure Offset360(const AShapes:Array of Integer);
  var t : Integer;
  begin
    for t:=Low(AShapes) to High(AShapes) do
        Shapes[AShapes[t]].OffsetValues(360,0);
  end;

  procedure RemoveParentShape(const AIndex:Integer);
  var tmp : TTeePolygon;
  begin
    MandatoryValueList.Value[AIndex]:=MagicFlag;

    tmp:=Shapes[AIndex];

    tmp.Text:=tmp.ParentShape.Text;
    tmp.Code:=tmp.ParentShape.Code;

    tmp.ParentShape:=nil;
  end;

  procedure FilterAsia;
  begin
    FilterShapes(AsiaShapes);

    RemoveNonFlaggedShape(1270);
    RemoveNonFlaggedShape(1281);
  end;

  procedure FixFrance;
  var t : Integer;
  begin
    // Fix parentship:

    // France mainland
    RemoveParentShape(560);

    // Corse
    Shapes[558].ParentShape:=Shapes[560];

    // Now remove:

    for t:=551 to 557 do
    begin
      Shapes[t].ParentShape:=nil;
      RemoveNonFlaggedShape(t);
    end;
  end;

  procedure FixNetherLands;
  var t : Integer;
  begin
    RemoveParentShape(1063);

    RemoveNonFlaggedShape(1056);
    RemoveNonFlaggedShape(1057);
    RemoveNonFlaggedShape(1058);

    for t:=1059 to 1067 do
        if t<>1063 then
           Shapes[t].ParentShape:=Shapes[1063];
  end;

  procedure FilterEurope15;
  begin
    FilterShapes(Europe15Shapes);
    FixFrance;
    FixNetherlands;
  end;

  procedure FilterEurope28;
  begin
    FilterEurope15;
    FilterShapes(Europe28Shapes);
  end;

  procedure FilterEurope;
  begin
    FilterEurope28;
    FilterShapes(EuropeShapes);
  end;

  procedure FilterShapesOfMap;
  begin
    if IsWorldMap(FMap) then
       FilterShapes('TeeWorld',False,False);

    case FMap of
      wmAfrica         : FilterShapes(AfricaShapes);

      wmAsia           : FilterAsia;

      wmAustralia      : begin
                           FilterShapes(AustraliaShapes);

                           // New Zealand
                           Offset360([1107,1108,1113,1114]);

                           // Fiji
                           Offset360([525,526,528,529,531,532,536,539,541,543]);

                           //Shapes[528].OffsetValues(360,0);

                           //RemoveNonFlaggedShape(832); // Fiji (move -180 to 180 ?)
                           //RemoveNonFlaggedShape(1506); // New Zealand island?
                         end;

   wmAustraliaCounties : FilterShapes('TeeAustraliaCounties',False);
   wmAustraliaStates   : FilterShapes('TeeAustraliaStates',False);

      wmCentralAmerica : FilterShapes(CentralAmericaShapes);

      wmEurasia        : begin
                           FilterEurope;
                           FilterAsia;
                         end;

      wmEurope         : FilterEurope;
      wmEurope15       : FilterEurope15;
      wmEurope27       : FilterEurope28;

      wmSpain          : begin
                           FilterShapes('TeeSpain',False); // Provincias
                           MoveCanaryIslands;
                         end;

      wmMiddleEast     : FilterShapes(MiddleEastShapes);

      wmNorthAmerica   : begin
                           FilterShapes(NorthAmericaShapes);

                           // include Greenland
                           MandatoryValueList.Value[650]:=MagicFlag;

                           RemoveNonFlaggedShape(1506);
                           RemoveNonFlaggedShape(1512);
                           RemoveNonFlaggedShape(1513);
                           RemoveNonFlaggedShape(1515);
                           RemoveNonFlaggedShape(1519);
                           RemoveNonFlaggedShape(1522);
                         end;

      wmSouthAmerica   : FilterShapes(SouthAmericaShapes);

      wmUSA            : FilterShapes('TeeUSA'); // States

     wmUSAHawaiiAlaska : begin
                           FilterShapes('TeeUSAAlaska'); // States
                           Layers.MoveAlaska;
                         end;

      wmUK             : FilterSiblings('TeeUK'); // Counties

      wmUSACounties    : begin
                           FilterSiblings('TeeUSACounties');

                           RemoveShapes(3263,3286); // Aleutians West

                           RemoveShapes('15'); // Hawaii FIPS
                           RemoveShapes('72'); // Puerto Rico FIPS
                           RemoveShapes('02'); // Alaska FIPS
                         end;

   wmUSAAlaskaCounties: begin
                          FilterSiblings('TeeUSACounties');
                          RemoveShapes(3263,3286); // Aleutians West
                          Layers.MoveAlaska;
                        end;

      wmJapan         : FilterShapes('TeeJapan'); // Prefectures
      wmChina         : FilterShapes('TeeChina',False); // Prefectures
     wmChinaProvinces : FilterShapes('TeeChinaProv',False); // Provinces
      wmFrance        : FilterShapes('TeeFrance'); // Départements
      wmGermany       : FilterShapes('TeeGermany',False); // Kreise (Districts)

    end;
  end;

  procedure DestroyParentShape(const AIndex:Integer);
  var tmp : TTeePolygon;
      tmpS : TTeePolygons;
      t : Integer;
  begin
    tmp:=Shapes[AIndex];

    tmpS:=tmp.Siblings;

    for t:=0 to High(tmpS) do
        tmpS[t].Free;

    tmp.Free;
  end;

  // Move polygons to reduce total size of Map.
  // Shape Indexes are post-cleaning
  procedure FixMaps;
  begin
    case FMap of
     wmAustraliaCounties : TTeePolygonAccess(Shapes[1327]).DoOffset(-4,12);
       wmAustraliaStates : TTeePolygonAccess(Shapes[295]).DoOffset(-4,12);
                wmAfrica : TTeePolygonAccess(Shapes[71]).DoOffset(0,12);

                 wmChina : DestroyParentShape(329);
        wmChinaProvinces : Shapes[78].{$IFDEF AUTOREFCOUNT}DisposeOf{$ELSE}Free{$ENDIF};
    end;
  end;

var Old : Boolean;
begin
  if Assigned(ParentChart) then
  begin
    Old:=ParentChart.AutoRepaint;
    ParentChart.AutoRepaint:=False;
  end
  else
    Old:=False;

  BeginUpdate;

  Clear;

  TryLoadCachedMap;
  FilterShapesOfMap;
  SetSiblings;
  CleanShapes;
  FixMaps;

  EndUpdate;

  if Assigned(ParentChart) then
     ParentChart.AutoRepaint:=Old;
end;

function FixDecimalSeparator:Char;
begin
  result:={$IFDEF TEEFORMAT}FormatSettings.{$ENDIF}DecimalSeparator;
  {$IFDEF TEEFORMAT}FormatSettings.{$ENDIF}DecimalSeparator:='.';
end;

class procedure TWorldSeries.AddPlacemarks(const Values:TStringList; const DestSeries:TChartSeries);
var Old : Char;
    t   : Integer;
    st  : String;
begin
  DestSeries.Clear;

  Old:=FixDecimalSeparator;
  try
    for t:=0 to Values.Count-1 do
    begin
      st:=Values[t];

      DestSeries.AddXY( StrToFloat(TeeExtractField(st,2,';')),
                        StrToFloat(TeeExtractField(st,3,';')),
                        TeeExtractField(st,1,';'));
    end;
  finally
    {$IFDEF TEEFORMAT}FormatSettings.{$ENDIF}DecimalSeparator:=Old;
  end;
end;

class procedure TWorldSeries.LoadCities;

  // Hardcoded "appropiate" diameter in pixels for city's population ranges
  function SizeOfPopulation(const APopulation:Integer):Integer;
  begin
    if APopulation>5000000 then
       result:=5
    else
    if APopulation>3000000 then
       result:=4
    else
    if APopulation>1000000 then
       result:=3
    else
    if APopulation>100000 then
       result:=2
    else
       result:=1;
  end;

  procedure AddCity(S:String; var ACity:TCity);

    function NextField:String;
    var i : Integer;
    begin
      i:=Pos(';',S);
      result:=Copy(S,1,i-1);
      System.Delete(S,1,i);
    end;

  begin
    ACity.Name:=NextField;
    ACity.Lat:=StrToFloat(NextField);
    ACity.Lon:=StrToFloat(NextField);
    ACity.Population:=StrToInt(NextField);

    ACity.Country:=S;
    ACity.Size:=SizeOfPopulation(ACity.Population);
  end;

var s : TStringList;
    t : Integer;
    Old : Char;
begin
  s:={$IFDEF FMXLINUX}LoadANSIStrings{$ELSE}LoadStrings{$ENDIF}('TEEWORLDCITIES');
  try
    Old:=FixDecimalSeparator;
    try
      SetLength(ICities,s.Count);

      for t:=0 to s.Count-1 do
          AddCity(s[t],ICities[t]);
    finally
      {$IFDEF TEEFORMAT}FormatSettings.{$ENDIF}DecimalSeparator:=Old;
    end;
  finally
    s.Free;
  end;
end;

class procedure TWorldSeries.AddCities(const DestSeries:TChartSeries);
var t : Integer;
begin
  if ICities=nil then
     LoadCities;

  for t:=0 to High(ICities) do
      DestSeries.AddXY(ICities[t].Lat,ICities[t].Lon,ICities[t].Name);
end;

procedure TWorldSeries.ReloadMap;
begin
  FreeAndNil(FEntities);

  LoadMapShapes;

  if FLayers<>nil then
     FLayers.ChangedMap;

  Repaint;
end;

procedure TWorldSeries.SetMap(const Value: TWorldMap);
begin
  if FMap<>Value then
  begin
    FMap:=Value;
    ReloadMap;
  end;
end;

Procedure TWorldSeries.FillSampleValues(NumValues:Integer=0);
begin
  // Do not call "Clear" here. We dont want to remove all Map shapes.
  RandSeed:=Random(7774444);

  BeginUpdate;
  try
    AddSampleValues(NumValues);
    CheckOrder;
  finally
    EndUpdate;
  end;
end;

Procedure TWorldSeries.AddSampleValues(NumValues:Integer; OnlyMandatory:Boolean=False);
var t : Integer;
    tmpList : TChartValues;
    tmpH,
    tmpHalf : Single;
begin
  tmpH:=0.01*RandomBounds(NumValues).DifY*0.25;
  tmpHalf:=tmpH*0.5;

  tmpList:=MandatoryValueList.Value;

  for t:=0 to Count-1 do
      tmpList[t]:=Abs(tmpH*RandomValue(100)-tmpHalf);

  MandatoryValueList.Modified:=True;
  Repaint;
end;

class procedure TWorldSeries.SetSubGallery(ASeries: TChartSeries;
  Index: Integer);
begin
  TWorldSeries(ASeries).Map:=TWorldMap(Index);
end;

class procedure TWorldSeries.CreateSubGallery(AddSubChart: TChartSubGalleryProc);
var t : Integer;
begin
  // DO NOT CALL: inherited;

  for t:=0 to TeeSeriesTypes.Count-1 do
  with TeeSeriesTypes[t] do
  if SeriesClass=TWorldSeries then
     AddSubChart(Description^);
end;

procedure TWorldSeries.PrepareForGallery;
begin
//  LoadLabels:=False;

  Map:=TWorldMap(Owner.Tag);

  if Map=wmWorld then
     LoadMapShapes;

  // Add random values to each country / polygon:
  inherited;

  UsePalette:=True;
  UseColorRange:=False;

  PaletteStyle:=psRainbow;
  CreateDefaultPalette;

  ParentChart.Tools.Add(TMarksTipTool);

  ParentChart.Axes.Hide;
  ParentChart.Axes.FastCalc:=True;

  Pen.Width:=0;

  if Shapes.Count>100 then
     Pen.Hide;
end;

Procedure TWorldSeries.DrawFlags;
var t : Integer;
    tmp : TTeePolygon;
begin
  for t:=0 to Shapes.Count-1 do
  begin
    tmp:=Shapes[t];

    if tmp.Visible then
       DrawFlag(tmp);
  end;
end;

Procedure TWorldSeries.DrawAllValues;
begin
  inherited;

  if FFlags<>nil then
     if FFlags.Visible and IsWorldMap(FMap) then
        DrawFlags;

  if FLayers<>nil then
     FLayers.Draw;
end;

procedure TWorldSeries.GalleryChanged3D(Is3D: Boolean);
begin
  inherited;
  ParentChart.View3D:=False;
end;

procedure TWorldSeries.SetParentChart(const Value: TCustomAxisPanel);
begin
  inherited;

  // When this Series is added at design-time for first time into a Form,
  // set Chart to 2D mode.
  if Assigned(ParentChart) then
     if csDesigning in ComponentState then
        if not (csLoading in ComponentState) then
           ParentChart.View3D:=False;

  if not (csDestroying in ComponentState) then
     if FLayers<>nil then
        FLayers.SetParentChart(ParentChart);
end;

class function TWorldSeries.GetEditorClass: String;
begin
  result:='TWorldSeriesEditor';
end;

procedure TWorldSeries.Loaded;
var t : Integer;
begin
  inherited;

  if FMap=wmWorld then
     LoadMapShapes;

  // Protection:
  // Just in case the Data property was empty, re-add values.
  if Count=0 then
     for t:=0 to Shapes.Count-1 do
         Add(0);
end;

procedure RegisterWorld(var Title:String; SubIndex:Integer);
begin
  RegisterTeeSeriesFunction( TWorldSeries,nil, @Title,
                                 @TeeMsg_GalleryMaps,1,SubIndex);
end;

procedure TWorldSeries.RecalcAxisMargins(const Axis: TChartAxis;
  var R: TRect);
var tmpH,
    tmpW,
    tmpMargin : TCoordinate;

    tmpR : TRect;
    tmpRatio : Single;
begin
  inherited;

  if KeepAspect and (Count>0) then
  begin
    tmpR:=ParentChart.ChartRect;
    tmpW:=tmpR.Right-tmpR.Left;
    tmpH:=tmpR.Bottom-tmpR.Top;

    if FMap=wmWorld then
       tmpRatio:=0.6
    else
    if FMap=wmSpain then
       tmpRatio:=0.7
    else
    if FMap=wmUK then
       tmpRatio:=1.4
    else
    if FMap in [wmUSA,wmUSAHawaiiAlaska,wmUSACounties,wmUSAAlaskaCounties] then
       tmpRatio:=0.6
    else
       tmpRatio:=1;

    if tmpH<>0 then
      if (tmpW/tmpH)>tmpRatio then
      begin
        tmpMargin:={$IFNDEF FMX}Round{$ENDIF}(0.5*tmpRatio*(tmpW-tmpH));
        R.Left:=tmpMargin;
        R.Right:=R.Left;
      end
      else
      begin
        tmpMargin:={$IFNDEF FMX}Round{$ENDIF}(0.5*tmpRatio*(tmpH-tmpW));
        R.Top:=tmpMargin;
        R.Bottom:=R.Top;
      end;
  end;
end;

procedure TWorldSeries.SetFlags(const Value:TWorldFlags);
begin
  if Value=nil then
     FreeAndNil(FFlags)
  else
     Flags.Assign(Value);
end;

procedure TWorldSeries.SetLayers(const Value:TWorldLayers);
begin
  if Value=nil then
     FreeAndNil(FLayers)
  else
     Layers.Assign(Value);
end;

procedure TWorldSeries.SetKeepAspect(const Value: Boolean);
begin
  SetBooleanProperty(FKeepAspect,Value);
end;

{ TTeeUSAGroup }

Destructor TTeeUSAGroup.Destroy;
begin
  IAlaska.Free;
  IHawaii.Free;
  IPuertoRico.Free;

  inherited;
end;

procedure SetGroupByCode(const AGroup:TTeePolygonGroup;
                         const AMap:TMapSeries;
                         const ACode:String);
var tmp : TTeePolygon;
begin
  tmp:=AMap.Shapes.ByCode[ACode];

  if tmp.ParentShape<>nil then
     tmp:=tmp.ParentShape;

  AGroup.Shapes:=tmp.Siblings;
  AGroup.Add(tmp);
end;

procedure SetGroupByPrefix(const AGroup:TTeePolygonGroup;
                           const AMap:TMapSeries;
                           const APrefix:String);
var tmp : TTeePolygonList;
    t : Integer;
    tmpP : TTeePolygon;
    L : Integer;
begin
  tmp:=AMap.Shapes;

  L:=Length(APrefix);

  for t:=0 to tmp.Count-1 do
  begin
    tmpP:=tmp[t];

    if Copy(tmpP.Code,1,L)=APrefix then
       AGroup.Add(tmpP);
  end;
end;

function TTeeUSAGroup.Alaska: TTeePolygonGroup;
var tmpMap : TWorldMap;
begin
  if not Assigned(IAlaska) then
     IAlaska:=TTeePolygonGroup.Create(Map);

  if IAlaska.Shapes=nil then
  begin
    tmpMap:=TWorldSeries(Map).Map;

    if tmpMap=wmWorld then
    begin
    end
    else
    if tmpMap=wmUSAHawaiiAlaska then
       SetGroupByCode(IAlaska,Map,'AK')
    else
    if tmpMap=wmUSAAlaskaCounties then
       SetGroupByPrefix(IAlaska,Map,'02');  // 02xxx = FIPS Alaska
  end
  else
    IAlaska.Shapes:=nil;

  result:=IAlaska;
end;

function TTeeUSAGroup.Hawaii: TTeePolygonGroup;
var tmpMap : TWorldMap;
begin
  if not Assigned(IHawaii) then
     IHawaii:=TTeePolygonGroup.Create(Map);

  if IHawaii.Shapes=nil then
  begin
    tmpMap:=TWorldSeries(Map).Map;

    if tmpMap=wmWorld then
    begin
    end
    else
    if tmpMap=wmUSAHawaiiAlaska then
       SetGroupByCode(IHawaii,Map,'HI')
    else
    if tmpMap=wmUSAAlaskaCounties then
       SetGroupByPrefix(IHawaii,Map,'15');  // 15xxx = FIPS Hawaii
  end
  else
    IHawaii.Shapes:=nil;

  result:=IHawaii;
end;

function TTeeUSAGroup.PuertoRico: TTeePolygonGroup;
var tmpMap : TWorldMap;
begin
  if not Assigned(IPuertoRico) then
     IPuertoRico:=TTeePolygonGroup.Create(Map);

  if IPuertoRico.Shapes=nil then
  begin
    tmpMap:=TWorldSeries(Map).Map;

    if tmpMap=wmWorld then
    begin
    end
    else
    if tmpMap=wmUSAHawaiiAlaska then
       SetGroupByCode(IPuertoRico,Map,'PR')
    else
    if tmpMap=wmUSAAlaskaCounties then
       SetGroupByPrefix(IPuertoRico,Map,'72');  // 72xxx = FIPS Puerto Rico
  end
  else
    IPuertoRico.Shapes:=nil;

  result:=IPuertoRico;
end;

{ TTeeNorthAmericaGroup }

Destructor TTeeNorthAmericaGroup.Destroy;
begin
  IUSA.Free;
  inherited;
end;

function TTeeNorthAmericaGroup.Canada:TTeePolygonGroup;
begin
  if not Assigned(ICanada) then
     ICanada:=TTeePolygonGroup.Create(Map);

  if TWorldSeries(Map).Map in [wmWorld,wmNorthAmerica] then
  begin
    if ICanada.Shapes=nil then
       ICanada.Add([381,524,535,569,556,557,368,461,481,487,430,373,362,330,
                    338,322,367,364,342,313,310,306,335,369,396,511,586]);
  end
  else
    ICanada.Shapes:=nil;

  result:=ICanada;
end;

function TTeeNorthAmericaGroup.USA: TTeeUSAGroup;
begin
  if not Assigned(IUSA) then
     IUSA:=TTeeUSAGroup.Create(Map);

  if TWorldSeries(Map).Map in [wmWorld,wmNorthAmerica,
               wmUSA,wmUSAHawaiiAlaska,wmUSACounties,wmUSAAlaskaCounties] then
  begin
    if Length(IUSA.Shapes)=0 then
    begin
      if TWorldSeries(Map).Map=wmWorld then
         IUSA.Add([1959,1962,1970,2087,2088,2089,2090,2091,2092,2093,2058,2070,2072,1623])
      else
      //if TWorldSeries(Map).Map=wmNorthAmerica then
         IUSA.Add(Map);
    end;
  end
  else
    IUSA.Shapes:=nil;

  result:=IUSA;
end;

{ TTeeAmericaGroup }

Destructor TTeeAmericaGroup.Destroy;
begin
  ICentral.Free;
  INorth.Free;
  ISouth.Free;
  inherited;
end;

function TTeeAmericaGroup.Central: TTeePolygonGroup;
begin
  if not Assigned(ICentral) then
     ICentral:=TTeePolygonGroup.Create(Map);

  if TWorldSeries(Map).Map in [wmWorld,wmCentralAmerica] then
  begin
    if Length(ICentral.Shapes)=0 then
    begin
      if TWorldSeries(Map).Map=wmWorld then
         TWorldSeries(Map).AddToGroup('TeeCentralAmerica',ICentral)
      else
         ICentral.Add(Map);
    end;
  end
  else
    ICentral.Shapes:=nil;

  result:=ICentral;
end;

function TTeeAmericaGroup.North: TTeeNorthAmericaGroup;
begin
  if not Assigned(INorth) then
     INorth:=TTeeNorthAmericaGroup.Create(Map);

  if TWorldSeries(Map).Map in [wmWorld,wmNorthAmerica,wmUSA,wmUSAHawaiiAlaska,
                               wmUSACounties,wmUSAAlaskaCounties] then
  begin
    if Length(INorth.Shapes)=0 then
    begin
      if TWorldSeries(Map).Map=wmWorld then
         TWorldSeries(Map).AddToGroup('TeeNorthAmerica',INorth)
      else
         INorth.Add(Map);
    end;
  end
  else
    INorth.Shapes:=nil;

  result:=INorth;
end;

function TTeeAmericaGroup.South: TTeeSouthAmericaGroup;
begin
  if not Assigned(ISouth) then
     ISouth:=TTeeSouthAmericaGroup.Create(Map);

  if TWorldSeries(Map).Map in [wmWorld,wmSouthAmerica] then
  begin
    if Length(ISouth.Shapes)=0 then
    begin
      if TWorldSeries(Map).Map=wmWorld then
         TWorldSeries(Map).AddToGroup('TeeSouthAmerica',ISouth)
      else
         ISouth.Add(Map);
    end;
  end
  else
    ISouth.Shapes:=nil;

  result:=ISouth;
end;

{ TTeeAsiaGroup }

Destructor TTeeAsiaGroup.Destroy;
begin
  IChina.Free;
  inherited;
end;

function TTeeAsiaGroup.China: TTeePolygonGroup;
begin
  if not Assigned(IChina) then
     IChina:=TTeePolygonGroup.Create(Map);

  if TWorldSeries(Map).Map=wmWorld then
  begin
    if IChina.Shapes=nil then
       IChina.Add(596,596)
  end
  else
    IChina.Shapes:=nil;

  result:=IChina;
end;

function TTeeAsiaGroup.Japan: TTeePolygonGroup;
begin
  if not Assigned(IJapan) then
     IJapan:=TTeePolygonGroup.Create(Map);

  if TWorldSeries(Map).Map=wmWorld then
  begin
    if IJapan.Shapes=nil then
       IJapan.Add([1257,1260,1261,1262,1263,1267,1271,1280,1282])
  end
  else
    IJapan.Shapes:=nil;

  result:=IJapan;
end;

{ TTeeEuropeGroup }

Destructor TTeeEuropeGroup.Destroy;
begin
  IFrance.Free;
  IGermany.Free;
  ISpain.Free;
  IUK.Free;

  inherited;
end;

function TTeeEuropeGroup.France: TTeePolygonGroup;
begin
  if not Assigned(IFrance) then
     IFrance:=TTeePolygonGroup.Create(Map);

  if TWorldSeries(Map).Map=wmWorld then
  begin
    if IFrance.Shapes=nil then
       IFrance.Add(857,857)
  end
  else
    IFrance.Shapes:=nil;

  result:=IFrance;
end;

function TTeeEuropeGroup.Germany: TTeePolygonGroup;
begin
  if not Assigned(IGermany) then
     IGermany:=TTeePolygonGroup.Create(Map);

  if TWorldSeries(Map).Map=wmWorld then
  begin
    if IGermany.Shapes=nil then
       IGermany.Add(1018,1018)
  end
  else
    IGermany.Shapes:=nil;

  result:=IGermany;
end;

function TTeeEuropeGroup.Italy:TTeeItalyGroup;
begin
  if not Assigned(IItaly) then
     IItaly:=TTeeItalyGroup.Create(Map);

  if TWorldSeries(Map).Map=wmWorld then
  begin
    if IItaly.Shapes=nil then
       IItaly.Add(1248,1252);
  end
  else
    IItaly.Shapes:=nil;

  result:=IItaly;
end;

function TTeeEuropeGroup.Russia: TTeePolygonGroup;
begin
  if not Assigned(IRussia) then
     IRussia:=TTeePolygonGroup.Create(Map);

  if TWorldSeries(Map).Map=wmWorld then
  begin
    if IRussia.Shapes=nil then
       IRussia.Add([1634,1667,1679,1683,1692,1707,1717,1735,1747,1776,1788,
                    1807,1815,1817,1818]);
  end
  else
    IRussia.Shapes:=nil;

  result:=IRussia;
end;

function TTeeEuropeGroup.Spain: TTeePolygonGroup;
begin
  if not Assigned(ISpain) then
     ISpain:=TTeePolygonGroup.Create(Map);

  if TWorldSeries(Map).Map=wmWorld then
  begin
    if ISpain.Shapes=nil then
       ISpain.Add(1841,1854)
  end
  else
    ISpain.Shapes:=nil;

  result:=ISpain;
end;

function TTeeEuropeGroup.UK: TTeePolygonGroup;
begin
  if not Assigned(IUK) then
     IUK:=TTeePolygonGroup.Create(Map);

  if TWorldSeries(Map).Map=wmWorld then
  begin
    if IUK.Shapes=nil then
       IUK.Add(1938,1938)
  end
  else
    IUK.Shapes:=nil;

  result:=IUK;
end;

initialization
  RegisterWorld(TeeMsg_GalleryWorld,0);
  RegisterWorld(TeeMsg_GalleryAfrica,1);
  RegisterWorld(TeeMsg_GalleryAsia,2);
  RegisterWorld(TeeMsg_GalleryAustralia,3);
  RegisterWorld(TeeMsg_GalleryCentralAmerica,4);
  RegisterWorld(TeeMsg_GalleryEurope,5);
  RegisterWorld(TeeMsg_GalleryEurope15,6);
  RegisterWorld(TeeMsg_GalleryEurope28,7);
  RegisterWorld(TeeMsg_GalleryMiddleEast,9);
  RegisterWorld(TeeMsg_GalleryNorthAmerica,10);
  RegisterWorld(TeeMsg_GallerySouthAmerica,11);
  RegisterWorld(TeeMsg_GalleryUSA,12);
  RegisterWorld(TeeMsg_GalleryUSAHawaiiAlaska,13);
  RegisterWorld(TeeMsg_GalleryUSACounties,17);
  RegisterWorld(TeeMsg_GalleryUSAAlaskaCounties,18);
  RegisterWorld(TeeMsg_GalleryEurAsia,14);
  RegisterWorld(TeeMsg_GalleryUK,15);
  RegisterWorld(TeeMsg_GalleryGermany,16);
  RegisterWorld(TeeMsg_GallerySpain,8);
  RegisterWorld(TeeMsg_GalleryJapan,19);
  RegisterWorld(TeeMsg_GalleryChina,20);
  RegisterWorld(TeeMsg_GalleryFrance,21);
  RegisterWorld(TeeMsg_GalleryChinaProvinces,22);
  RegisterWorld(TeeMsg_GalleryAustraliaStates,24);
  RegisterWorld(TeeMsg_GalleryAustraliaCounties,23);

  TeeActivateGroup;
  RegisterClass(TCitySeries);
finalization
  {$IFDEF TEECACHEDMAPS}
  FreeAndNil(CachedSpain);
  FreeAndNil(CachedUSA);
  FreeAndNil(CachedWorld);
  {$ENDIF}

  UnRegisterTeeSeries([TWorldSeries]);
end.
