{ (c) 2014 ti_dic Parts of this component are based on : Map Viewer Copyright (C) 2011 Maciej Kaczkowski / keit.co This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. } unit mvEngine; {$mode objfpc}{$H+} interface uses Classes, SysUtils, IntfGraphics, Controls, mvTypes, mvJobQueue, mvMapProvider, mvDownloadEngine, mvCache, mvDragObj; const EARTH_RADIUS = 6378137; MIN_LATITUDE = -85.05112878; MAX_LATITUDE = 85.05112878; MIN_LONGITUDE = -180; MAX_LONGITUDE = 180; SHIFT = 2 * pi * EARTH_RADIUS / 2.0; Type TDrawTileEvent = Procedure (const TileId: TTileId; X,Y: integer; TileImg: TLazIntfImage) of object; TTileIdArray = Array of TTileId; TDistanceUnits = (duMeters, duKilometers, duMiles); { TMapWindow } TMapWindow = Record MapProvider: TMapProvider; X: Int64; Y: Int64; Center: TRealPoint; Zoom: integer; Height: integer; Width: integer; end; { TMapViewerEngine } TMapViewerEngine = Class(TComponent) private DragObj : TDragObj; Cache : TPictureCache; FActive: boolean; FDownloadEngine: TMvCustomDownloadEngine; FDrawTitleInGuiThread: boolean; FOnCenterMove: TNotifyEvent; FOnChange: TNotifyEvent; FOnDrawTile: TDrawTileEvent; FOnZoomChange: TNotifyEvent; lstProvider : TStringList; Queue : TJobQueue; MapWin : TMapWindow; function GetCacheOnDisk: Boolean; function GetCachePath: String; function GetCenter: TRealPoint; function GetHeight: integer; function GetMapProvider: String; function GetUseThreads: Boolean; function GetWidth: integer; function GetZoom: integer; function IsValidTile(const aWin: TMapWindow; const aTile: TTIleId): boolean; procedure MoveMapCenter(Sender: TDragObj); procedure SetActive(AValue: boolean); procedure SetCacheOnDisk(AValue: Boolean); procedure SetCachePath(AValue: String); procedure SetCenter(aCenter: TRealPoint); procedure SetDownloadEngine(AValue: TMvCustomDownloadEngine); procedure SetHeight(AValue: integer); procedure SetMapProvider(AValue: String); procedure SetUseThreads(AValue: Boolean); procedure SetWidth(AValue: integer); procedure SetZoom(AValue: integer); function LonLatToMapWin(const aWin: TMapWindow; aPt: TRealPoint): TPoint; Function MapWinToLonLat(const aWin: TMapWindow; aPt : TPoint) : TRealPoint; Procedure CalculateWin(var aWin: TMapWindow); Procedure Redraw(const aWin: TmapWindow); function CalculateVisibleTiles(const aWin: TMapWindow) : TArea; function IsCurrentWin(const aWin: TMapWindow) : boolean; protected procedure ConstraintZoom(var aWin: TMapWindow); function GetTileName(const Id: TTileId): String; procedure evDownload(Data: TObject; Job: TJob); procedure TileDownloaded(Data: PtrInt); Procedure RegisterProviders; Procedure DrawTile(const TileId: TTileId; X,Y: integer; TileImg: TLazIntfImage); Procedure DoDrag(Sender: TDragObj); public constructor Create(aOwner: TComponent); override; destructor Destroy; override; function AddMapProvider(OpeName: String; Url: String; MinZoom, MaxZoom, NbSvr: integer; GetSvrStr: TGetSvrStr = nil; GetXStr: TGetValStr = nil; GetYStr: TGetValStr = nil; GetZStr: TGetValStr = nil): TMapProvider; procedure CancelCurrentDrawing; procedure ClearMapProviders; procedure GetMapProviders(AList: TStrings); function LonLatToScreen(aPt: TRealPoint): TPoint; function LonLatToWorldScreen(aPt: TRealPoint): TPoint; function ReadProvidersFromXML(AFileName: String; out AMsg: String): Boolean; procedure Redraw; function ScreenToLonLat(aPt: TPoint): TRealPoint; procedure SetSize(aWidth, aHeight: integer); function WorldScreenToLonLat(aPt: TPoint): TRealPoint; procedure WriteProvidersToXML(AFileName: String); procedure DblClick(Sender: TObject); procedure MouseDown(Sender: TObject; Button: TMouseButton; {%H-}Shift: TShiftState; X, Y: Integer); procedure MouseMove(Sender: TObject; {%H-}Shift: TShiftState; X, Y: Integer); procedure MouseUp(Sender: TObject; Button: TMouseButton; {%H-}Shift: TShiftState; X, Y: Integer); procedure MouseWheel(Sender: TObject; {%H-}Shift: TShiftState; WheelDelta: Integer; {%H-}MousePos: TPoint; var Handled: Boolean); procedure ZoomOnArea(const aArea: TRealArea); property Center: TRealPoint read GetCenter write SetCenter; published property Active: Boolean read FActive write SetActive default false; property CacheOnDisk: Boolean read GetCacheOnDisk write SetCacheOnDisk; property CachePath: String read GetCachePath write SetCachePath; property DownloadEngine: TMvCustomDownloadEngine read FDownloadEngine write SetDownloadEngine; property DrawTitleInGuiThread: boolean read FDrawTitleInGuiThread write FDrawTitleInGuiThread; property Height: integer read GetHeight write SetHeight; property JobQueue: TJobQueue read Queue; property MapProvider: String read GetMapProvider write SetMapProvider; property UseThreads: Boolean read GetUseThreads write SetUseThreads; property Width: integer read GetWidth write SetWidth; property Zoom: integer read GetZoom write SetZoom; property OnCenterMove: TNotifyEvent read FOnCenterMove write FOnCenterMove; property OnChange: TNotifyEvent Read FOnChange write FOnchange; //called when visiable area change property OnDrawTile: TDrawTileEvent read FOnDrawTile write FOnDrawTile; property OnZoomChange: TNotifyEvent read FOnZoomChange write FOnZoomChange; end; function CalcGeoDistance(Lat1, Lon1, Lat2, Lon2: double; AUnits: TDistanceUnits = duKilometers): double; function GPSToDMS(Angle: Double): string; function LatToStr(ALatitude: Double; DMS: Boolean): String; function LonToStr(ALongitude: Double; DMS: Boolean): String; procedure SplitGps(AValue: Double; out ADegs, AMins, ASecs: Double); implementation uses Math, Forms, laz2_xmlread, laz2_xmlwrite, laz2_dom, mvJobs, mvGpsObj; type { TLaunchDownloadJob } TLaunchDownloadJob = class(TJob) private AllRun: boolean; Win: TMapWindow; Engine: TMapViewerEngine; FRunning: boolean; FTiles: TTileIdArray; FStates: Array of integer; protected function pGetTask: integer; override; procedure pTaskStarted(aTask: integer); override; procedure pTaskEnded(aTask: integer; aExcept: Exception); override; public procedure ExecuteTask(aTask: integer; FromWaiting: boolean); override; function Running: boolean; override; public constructor Create(Eng: TMapViewerEngine; const Tiles: TTileIdArray; const aWin: TMapWindow); end; { TEnvTile } TEnvTile = Class private Tile: TTileId; Win: TMapWindow; public constructor Create(const aTile: TTileId; const aWin: TMapWindow); end; { TMemObj } TMemObj = Class private FWin: TMapWindow; public constructor Create(const aWin: TMapWindow); end; constructor TMemObj.Create(const aWin: TMapWindow); begin FWin := aWin; end; { TLaunchDownloadJob } function TLaunchDownloadJob.pGetTask: integer; var i: integer; begin if not AllRun and not Cancelled then begin for i:=Low(FStates) to High(FStates) do if FStates[i] = 0 then begin Result := i + 1; Exit; end; AllRun := True; end; Result := ALL_TASK_COMPLETED; for i := Low(FStates) to High(FStates) do if FStates[i] = 1 then begin Result := NO_MORE_TASK; Exit; end; end; procedure TLaunchDownloadJob.pTaskStarted(aTask: integer); begin FRunning := True; FStates[aTask-1] := 1; end; procedure TLaunchDownloadJob.pTaskEnded(aTask: integer; aExcept: Exception); begin if Assigned(aExcept) then FStates[aTask - 1] := 3 Else FStates[aTask - 1] := 2; end; procedure TLaunchDownloadJob.ExecuteTask(aTask: integer; FromWaiting: boolean); var iTile: integer; begin iTile := aTask - 1; Queue.AddUniqueJob(TEventJob.Create ( @Engine.evDownload, TEnvTile.Create(FTiles[iTile], Win), false, // owns data Engine.GetTileName(FTiles[iTile]) ), Launcher ); end; function TLaunchDownloadJob.Running: boolean; begin Result := FRunning; end; constructor TLaunchDownloadJob.Create(Eng: TMapViewerEngine; const Tiles: TTileIdArray; const aWin: TMapWindow); var i: integer; begin Engine := Eng; SetLength(FTiles, Length(Tiles)); For i:=Low(FTiles) to High(FTiles) do FTiles[i] := Tiles[i]; SetLength(FStates, Length(Tiles)); AllRun := false; Name := 'LaunchDownload'; Win := aWin; end; { TEnvTile } constructor TEnvTile.Create(const aTile: TTileId; const aWin: TMapWindow); begin Tile := aTile; Win := aWin; end; { TMapViewerEngine } constructor TMapViewerEngine.Create(aOwner: TComponent); begin DrawTitleInGuiThread := true; DragObj := TDragObj.Create; DragObj.OnDrag := @DoDrag; Cache := TPictureCache.Create(self); lstProvider := TStringList.Create; RegisterProviders; Queue := TJobQueue.Create(8); Queue.OnIdle := @Cache.CheckCacheSize; inherited Create(aOwner); ConstraintZoom(MapWin); CalculateWin(mapWin); end; destructor TMapViewerEngine.Destroy; begin ClearMapProviders; FreeAndNil(DragObj); FreeAndNil(lstProvider); FreeAndNil(Cache); FreeAndNil(Queue); inherited Destroy; end; function TMapViewerEngine.AddMapProvider(OpeName: String; Url: String; MinZoom, MaxZoom, NbSvr: integer; GetSvrStr: TGetSvrStr; GetXStr: TGetValStr; GetYStr: TGetValStr; GetZStr: TGetValStr): TMapProvider; var idx :integer; Begin idx := lstProvider.IndexOf(OpeName); if idx = -1 then begin Result := TMapProvider.Create(OpeName); lstProvider.AddObject(OpeName, Result); end else Result := TMapProvider(lstProvider.Objects[idx]); Result.AddUrl(Url, NbSvr, MinZoom, MaxZoom, GetSvrStr, GetXStr, GetYStr, GetZStr); end; function TMapViewerEngine.CalculateVisibleTiles(const aWin: TMapWindow): TArea; var MaxX, MaxY, startX, startY: int64; begin MaxX := (Int64(aWin.Width) div TILE_SIZE) + 1; MaxY := (Int64(aWin.Height) div TILE_SIZE) + 1; startX := -aWin.X div TILE_SIZE; startY := -aWin.Y div TILE_SIZE; Result.Left := startX; Result.Right := startX + MaxX; Result.Top := startY; Result.Bottom := startY + MaxY; end; procedure TMapViewerEngine.CalculateWin(var aWin: TMapWindow); var mx, my: Extended; res: Extended; px, py: Int64; begin mx := aWin.Center.Lon * SHIFT / 180.0; my := ln( tan((90 - aWin.Center.Lat) * pi / 360.0 )) / (pi / 180.0); my := my * SHIFT / 180.0; res := (2 * pi * EARTH_RADIUS) / (TILE_SIZE * (1 shl aWin.Zoom)); px := Round((mx + shift) / res); py := Round((my + shift) / res); aWin.X := aWin.Width div 2 - px; aWin.Y := aWin.Height div 2 - py; end; procedure TMapViewerEngine.CancelCurrentDrawing; var Jobs: TJobArray; begin Jobs := Queue.CancelAllJob(self); Queue.WaitForTerminate(Jobs); end; procedure TMapViewerEngine.ClearMapProviders; var i: Integer; begin for i:=0 to lstProvider.Count-1 do TObject(lstProvider.Objects[i]).Free; lstProvider.Clear; end; procedure TMapViewerEngine.ConstraintZoom(var aWin: TMapWindow); var zMin, zMax: integer; begin if Assigned(aWin.MapProvider) then begin aWin.MapProvider.GetZoomInfos(zMin, zMax); if aWin.Zoom < zMin then aWin.Zoom := zMin; if aWin.Zoom > zMax then aWin.Zoom := zMax; end; end; procedure TMapViewerEngine.DblClick(Sender: TObject); var pt: TPoint; begin pt.X := DragObj.MouseX; pt.Y := DragObj.MouseY; SetCenter(ScreenToLonLat(pt)); end; procedure TMapViewerEngine.DoDrag(Sender: TDragObj); begin if Sender.DragSrc = self then MoveMapCenter(Sender); end; procedure TMapViewerEngine.DrawTile(const TileId: TTileId; X, Y: integer; TileImg: TLazIntfImage); begin if Assigned(FOnDrawTile) then FOnDrawTile(TileId, X, Y, TileImg); end; procedure TMapViewerEngine.evDownload(Data: TObject; Job: TJob); var Id: TTileId; Url: String; Env: TEnvTile; MapO: TMapProvider; lStream: TMemoryStream; begin Env := TEnvTile(Data); Id := Env.Tile; MapO := Env.Win.MapProvider; if Assigned(MapO) then begin if not Cache.InCache(MapO, Id) then begin if Assigned(FDownloadEngine) then begin Url := MapO.GetUrlForTile(Id); if Url <> '' then begin lStream := TMemoryStream.Create; try try FDownloadEngine.DownloadFile(Url, lStream); Cache.Add(MapO, Id, lStream); except end; finally FreeAndNil(lStream); end; end; end; end; end; if Job.Cancelled then Exit; if DrawTitleInGuiThread then Queue.QueueAsyncCall(@TileDownloaded, PtrInt(Env)) else TileDownloaded(PtrInt(Env)); end; function TMapViewerEngine.GetCacheOnDisk: Boolean; begin Result := Cache.UseDisk; end; function TMapViewerEngine.GetCachePath: String; begin Result := Cache.BasePath; end; function TMapViewerEngine.GetCenter: TRealPoint; begin Result := MapWin.Center; end; function TMapViewerEngine.GetHeight: integer; begin Result := MapWin.Height end; function TMapViewerEngine.GetMapProvider: String; begin if Assigned(MapWin.MapProvider) then Result := MapWin.MapProvider.Name else Result := ''; end; procedure TMapViewerEngine.GetMapProviders(AList: TStrings); begin AList.Assign(lstProvider); end; function TMapViewerEngine.GetTileName(const Id: TTileId): String; begin Result := IntToStr(Id.X) + '.' + IntToStr(Id.Y) + '.' + IntToStr(Id.Z); end; function TMapViewerEngine.GetUseThreads: Boolean; begin Result := Queue.UseThreads; end; function TMapViewerEngine.GetWidth: integer; begin Result := MapWin.Width; end; function TMapViewerEngine.GetZoom: integer; begin Result := MapWin.Zoom; end; function TMapViewerEngine.IsCurrentWin(const aWin: TMapWindow): boolean; begin Result := (aWin.Zoom = MapWin.Zoom) and (aWin.Center.Lat = MapWin.Center.Lat) and (aWin.Center.Lon = MapWin.Center.Lon) and (aWin.Width = MapWin.Width) and (aWin.Height = MapWin.Height); end; function TMapViewerEngine.IsValidTile(const aWin: TMapWindow; const aTile: TTileId): boolean; var tiles: int64; begin tiles := 1 shl aWin.Zoom; Result := (aTile.X >= 0) and (aTile.X <= tiles-1) and (aTile.Y >= 0) and (aTile.Y <= tiles-1); end; function TMapViewerEngine.LonLatToMapWin(const aWin: TMapWindow; aPt: TRealPoint): TPoint; var tiles: Int64; circumference: Int64; res: Extended; tmpX,tmpY : Double; begin tiles := 1 shl aWin.Zoom; circumference := tiles * TILE_SIZE; tmpX := ((aPt.Lon+ 180.0)*circumference)/360.0; res := (2 * pi * EARTH_RADIUS) / circumference; tmpY := -aPt.Lat; tmpY := ln(tan((degToRad(tmpY) + pi / 2.0) / 2)) *180 / pi; tmpY:= (((tmpY / 180.0) * SHIFT) + SHIFT) / res; tmpX := tmpX + aWin.X; tmpY := tmpY + aWin.Y; Result.X := trunc(tmpX); Result.Y := trunc(tmpY); end; function TMapViewerEngine.LonLatToScreen(aPt: TRealPoint): TPoint; Begin Result := LonLatToMapWin(MapWin, aPt); end; function TMapViewerEngine.LonLatToWorldScreen(aPt: TRealPoint): TPoint; begin Result := LonLatToScreen(aPt); Result.X := Result.X + MapWin.X; Result.Y := Result.Y + MapWin.Y; end; function TMapViewerEngine.MapWinToLonLat(const aWin: TMapWindow; aPt: TPoint): TRealPoint; var tiles: Int64; circumference: Int64; lat: Extended; res: Extended; mPoint : TPoint; begin tiles := 1 shl aWin.Zoom; circumference := tiles * TILE_SIZE; mPoint.X := aPt.X - aWin.X; mPoint.Y := aPt.Y - aWin.Y; if mPoint.X < 0 then mPoint.X := 0 else if mPoint.X > circumference then mPoint.X := circumference; if mPoint.Y < 0 then mPoint.Y := 0 else if mPoint.Y > circumference then mPoint.Y := circumference; Result.Lon := ((mPoint.X * 360.0) / circumference) - 180.0; res := (2 * pi * EARTH_RADIUS) / circumference; lat := ((mPoint.Y * res - SHIFT) / SHIFT) * 180.0; lat := radtodeg (2 * arctan( exp( lat * pi / 180.0)) - pi / 2.0); Result.Lat := -lat; if Result.Lat > MAX_LATITUDE then Result.Lat := MAX_LATITUDE else if Result.Lat < MIN_LATITUDE then Result.Lat := MIN_LATITUDE; if Result.Lon > MAX_LONGITUDE then Result.Lon := MAX_LONGITUDE else if Result.Lon < MIN_LONGITUDE then Result.Lon := MIN_LONGITUDE; end; procedure TMapViewerEngine.MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin if Button = mbLeft then DragObj.MouseDown(self,X,Y); end; procedure TMapViewerEngine.MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer); begin DragObj.MouseMove(X,Y); end; procedure TMapViewerEngine.MouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin if Button = mbLeft then DragObj.MouseUp(X,Y); end; procedure TMapViewerEngine.MouseWheel(Sender: TObject; Shift: TShiftState; WheelDelta: Integer; MousePos: TPoint; var Handled: Boolean); var Val: Integer; nZoom: integer; begin Val := 0; if WheelDelta > 0 then Val := 1; if WheelDelta < 0 then Val := -1; nZoom := Zoom + Val; if (nZoom > 0) and (nZoom < 20) then Zoom := nZoom; Handled := true; end; procedure TMapViewerEngine.MoveMapCenter(Sender: TDragObj); var old: TMemObj; nCenter: TRealPoint; aPt: TPoint; Begin if Sender.LnkObj = nil then Sender.LnkObj := TMemObj.Create(MapWin); old := TMemObj(Sender.LnkObj); aPt.X := old.FWin.Width DIV 2-Sender.OfsX; aPt.Y := old.FWin.Height DIV 2-Sender.OfsY; nCenter := MapWinToLonLat(old.FWin,aPt); SetCenter(nCenter); end; function TMapViewerEngine.ReadProvidersFromXML(AFileName: String; out AMsg: String): Boolean; function GetSvrStr(AName: String): TGetSvrStr; var lcName: String; begin lcName := LowerCase(AName); case lcName of 'letter': Result := @GetLetterSvr; 'yahoo': Result := @GetYahooSvr; else Result := nil; end; end; function GetValStr(AName: String): TGetValStr; var lcName: String; begin lcName := Lowercase(AName); case lcName of 'quadkey': Result := @GetQuadKey; 'yahooy': Result := @GetYahooY; 'yahooz': Result := @GetYahooZ; else Result := nil; end; end; function GetAttrValue(ANode: TDOMNode; AttrName: String): String; var node: TDOMNode; begin Result := ''; if ANode.HasAttributes then begin node := ANode.Attributes.GetNamedItem(AttrName); if Assigned(node) then Result := node.NodeValue; end; end; var stream: TFileStream; doc: TXMLDocument = nil; node, layerNode: TDOMNode; attr: TDOMNamedNodeMap; providerName: String; url: String; minZoom: Integer; maxZoom: Integer; svrCount: Integer; s: String; svrProc: String; xProc: String; yProc: String; zProc: String; first: Boolean; begin Result := false; AMsg := ''; stream := TFileStream.Create(AFileName, fmOpenread or fmShareDenyWrite); try ReadXMLFile(doc, stream, [xrfAllowSpecialCharsInAttributeValue, xrfAllowLowerThanInAttributeValue]); node := doc.FindNode('map_providers'); if node = nil then begin AMsg := 'No map providers in file.'; exit; end; first := true; node := node.FirstChild; while node <> nil do begin providerName := GetAttrValue(node, 'name'); layerNode := node.FirstChild; while layerNode <> nil do begin url := GetAttrValue(layerNode, 'url'); if url = '' then continue; s := GetAttrValue(layerNode, 'minZom'); if s = '' then minZoom := 0 else minZoom := StrToInt(s); s := GetAttrValue(layerNode, 'maxZoom'); if s = '' then maxzoom := 9 else maxZoom := StrToInt(s); s := GetAttrValue(layerNode, 'serverCount'); if s = '' then svrCount := 1 else svrCount := StrToInt(s); svrProc := GetAttrValue(layerNode, 'serverProc'); xProc := GetAttrValue(layerNode, 'xProc'); yProc := GetAttrValue(layerNode, 'yProc'); zProc := GetAttrValue(layerNode, 'zProc'); layerNode := layerNode.NextSibling; end; if first then begin ClearMapProviders; first := false; end; AddMapProvider(providerName, url, minZoom, maxZoom, svrCount, GetSvrStr(svrProc), GetValStr(xProc), GetValStr(yProc), GetValStr(zProc) ); node := node.NextSibling; end; Result := true; finally stream.Free; doc.Free; end; end; procedure TMapViewerEngine.Redraw; begin Redraw(MapWin); end; procedure TMapViewerEngine.Redraw(const aWin: TmapWindow); var TilesVis: TArea; x, y : Integer; //int64; Tiles: TTileIdArray; iTile: Integer; begin if not(Active) then Exit; Queue.CancelAllJob(self); TilesVis := CalculateVisibleTiles(aWin); SetLength(Tiles, (TilesVis.Bottom - TilesVis.Top + 1) * (TilesVis.Right - TilesVis.Left + 1)); iTile := Low(Tiles); for y := TilesVis.Top to TilesVis.Bottom do for X := TilesVis.Left to TilesVis.Right do begin Tiles[iTile].X := X; Tiles[iTile].Y := Y; Tiles[iTile].Z := aWin.Zoom; if IsValidTile(aWin, Tiles[iTile]) then iTile += 1; end; SetLength(Tiles, iTile); if Length(Tiles) > 0 then Queue.AddJob(TLaunchDownloadJob.Create(self, Tiles, aWin), self); end; procedure TMapViewerEngine.RegisterProviders; begin // AddMapProvider('Aucun','',0,30, 0); ??? AddMapProvider('Google Normal', 'http://mt%serv%.google.com/vt/lyrs=m@145&v=w2.104&x=%x%&y=%y%&z=%z%', 0, 19, 4, nil); AddMapProvider('Google Hybrid', 'http://mt%serv%.google.com/vt/lyrs=h@145&v=w2.104&x=%x%&y=%y%&z=%z%', 0, 19, 4, nil); AddMapProvider('Google Physical', 'http://mt%serv%.google.com/vt/lyrs=t@145&v=w2.104&x=%x%&y=%y%&z=%z%', 0, 19, 4, nil); { AddMapProvider('Google Hybrid','http://khm%d.google.com/kh/v=82&x=%x%&y=%y%&z=%z%&s=Ga',4); AddMapProvider('Google Hybrid','http://mt%d.google.com/vt/lyrs=h@145&v=w2.104&x=%d&y=%d&z=%z%',4); AddMapProvider('Google physical','http://mt%d.google.com/vt/lyrs=t@145&v=w2.104&x=%d&y=%d&z=%z%',4); AddMapProvider('Google Physical Hybrid','http://mt%d.google.com/vt/lyrs=t@145&v=w2.104&x=%x%&y=%y%&z=%z%',4); AddMapProvider('Google Physical Hybrid','http://mt%d.google.com/vt/lyrs=h@145&v=w2.104&x=%x%&y=%y%&z=%z%',4); } //AddMapProvider('OpenStreetMap Osmarender','http://%serv%.tah.openstreetmap.org/Tiles/tile/%z%/%x%/%y%.png',0,20,3, @getLetterSvr); // [Char(Ord('a')+Random(3)), Z, X, Y])); //AddMapProvider('Yahoo Normal','http://maps%serv%.yimg.com/hx/tl?b=1&v=4.3&.intl=en&x=%x%&y=%y%d&z=%d&r=1' , 0,20,3,@GetYahooSvr, nil, @getYahooY, @GetYahooZ); //(Z+1])); //AddMapProvider('Yahoo Satellite','http://maps%serv%.yimg.com/ae/ximg?v=1.9&t=a&s=256&.intl=en&x=%d&y=%d&z=%d&r=1', 0,20,3,@GetYahooSvr, nil, @getYahooY, @GetYahooZ); //[Random(3)+1, X, YahooY(Y), Z+1])); //AddMapProvider('Yahoo Hybrid','http://maps%serv%.yimg.com/ae/ximg?v=1.9&t=a&s=256&.intl=en&x=%x%&y=%y%&z=%z%&r=1', 0,20,3,@GetYahooSvr, nil, @getYahooY, @GetYahooZ); //[Random(3)+1, X, YahooY(Y), Z+1])); //AddMapProvider('Yahoo Hybrid','http://maps%serv%.yimg.com/hx/tl?b=1&v=4.3&t=h&.intl=en&x=%x%&y=%y%&z=%z%&r=1' , 0,20,3,@GetYahooSvr, nil, @getYahooY, @GetYahooZ); //[Random(3)+1, X, YahooY(Y), Z+1])); // opeName, Url, MinZoom, MaxZoom, NbSvr, GetSvrStr, GetXStr, GetYStr, GetZStr MapWin.MapProvider := AddMapProvider('OpenStreetMap Mapnik', 'http://%serv%.tile.openstreetmap.org/%z%/%x%/%y%.png', 0, 19, 3, @GetLetterSvr); AddMapProvider('Open Cycle Map', 'http://%serv%.tile.opencyclemap.org/cycle/%z%/%x%/%y%.png', 0, 18, 3, @getLetterSvr); AddMapProvider('Open Topo Map', 'http://%serv%.tile.opentopomap.org/%z%/%x%/%y%.png', 0, 19, 3, @getLetterSvr); AddMapProvider('Virtual Earth Bing', 'http://ecn.t%serv%.tiles.virtualearth.net/tiles/r%x%?g=671&mkt=en-us&lbl=l1&stl=h&shading=hill', 1, 19, 8, nil, @GetQuadKey); AddMapProvider('Virtual Earth Road', 'http://r%serv%.ortho.tiles.virtualearth.net/tiles/r%x%.png?g=72&shading=hill', 1, 19, 4, nil, @GetQuadKey); AddMapProvider('Virtual Earth Aerial', 'http://a%serv%.ortho.tiles.virtualearth.net/tiles/a%x%.jpg?g=72&shading=hill', 1, 19, 4, nil, @GetQuadKey); AddMapProvider('Virtual Earth Hybrid', 'http://h%serv%.ortho.tiles.virtualearth.net/tiles/h%x%.jpg?g=72&shading=hill', 1, 19, 4, nil, @GetQuadKey); AddMapProvider('Ovi Normal', 'http://%serv%.maptile.maps.svc.ovi.com/maptiler/v2/maptile/newest/normal.day/%z%/%x%/%y%/256/png8', 0, 20, 5, @GetLetterSvr); AddMapProvider('Ovi Satellite', 'http://%serv%.maptile.maps.svc.ovi.com/maptiler/v2/maptile/newest/satellite.day/%z%/%x%/%y%/256/png8', 0, 20, 5, @GetLetterSvr); AddMapProvider('Ovi Hybrid', 'http://%serv%.maptile.maps.svc.ovi.com/maptiler/v2/maptile/newest/hybrid.day/%z%/%x%/%y%/256/png8', 0, 20, 5, @GetLetterSvr); AddMapProvider('Ovi Physical', 'http://%serv%.maptile.maps.svc.ovi.com/maptiler/v2/maptile/newest/terrain.day/%z%/%x%/%y%/256/png8', 0, 20, 5, @GetLetterSvr); { AddMapProvider('Yahoo Normal','http://maps%serv%.yimg.com/hx/tl?b=1&v=4.3&.intl=en&x=%x%&y=%y%d&z=%d&r=1' , 0,20,3,@GetYahooSvr, nil, @getYahooY, @GetYahooZ); //(Z+1])); AddMapProvider('Yahoo Satellite','http://maps%serv%.yimg.com/ae/ximg?v=1.9&t=a&s=256&.intl=en&x=%d&y=%d&z=%d&r=1', 0,20,3,@GetYahooSvr, nil, @getYahooY, @GetYahooZ); //[Random(3)+1, X, YahooY(Y), Z+1])); AddMapProvider('Yahoo Hybrid','http://maps%serv%.yimg.com/ae/ximg?v=1.9&t=a&s=256&.intl=en&x=%x%&y=%y%&z=%z%&r=1', 0,20,3,@GetYahooSvr, nil, @getYahooY, @GetYahooZ); //[Random(3)+1, X, YahooY(Y), Z+1])); AddMapProvider('Yahoo Hybrid','http://maps%serv%.yimg.com/hx/tl?b=1&v=4.3&t=h&.intl=en&x=%x%&y=%y%&z=%z%&r=1' , 0,20,3,@GetYahooSvr, nil, @getYahooY, @GetYahooZ); //[Random(3)+1, X, YahooY(Y), Z+1])); } end; function TMapViewerEngine.ScreenToLonLat(aPt: TPoint): TRealPoint; begin Result := MapWinToLonLat(MapWin, aPt); end; procedure TMapViewerEngine.SetActive(AValue: boolean); begin if FActive = AValue then Exit; FActive := AValue; if not(FActive) then Queue.CancelAllJob(self) else begin if Cache.UseDisk then ForceDirectories(Cache.BasePath); Redraw(MapWin); end; end; procedure TMapViewerEngine.SetCacheOnDisk(AValue: Boolean); begin if Cache.UseDisk = AValue then Exit; Cache.UseDisk := AValue; end; procedure TMapViewerEngine.SetCachePath(AValue: String); begin Cache.BasePath := aValue; end; procedure TMapViewerEngine.SetCenter(aCenter: TRealPoint); begin if (MapWin.Center.Lon <> aCenter.Lon) and (MapWin.Center.Lat <> aCenter.Lat) then begin Mapwin.Center := aCenter; CalculateWin(MapWin); Redraw(MapWin); if assigned(OnCenterMove) then OnCenterMove(Self); if Assigned(OnChange) then OnChange(Self); end; end; procedure TMapViewerEngine.SetDownloadEngine(AValue: TMvCustomDownloadEngine); begin if FDownloadEngine = AValue then Exit; FDownloadEngine := AValue; if Assigned(FDownloadEngine) then FDownloadEngine.FreeNotification(self); end; procedure TMapViewerEngine.SetHeight(AValue: integer); begin if MapWin.Height = AValue then Exit; MapWin.Height := AValue; CalculateWin(MapWin); Redraw(MapWin); end; procedure TMapViewerEngine.SetMapProvider(AValue: String); var idx: integer; begin idx := lstProvider.IndexOf(aValue); if not ((aValue = '') or (idx <> -1)) then raise Exception.Create('Unknow Provider: ' + aValue); if Assigned(MapWin.MapProvider) and (MapWin.MapProvider.Name = AValue) then Exit; if idx <> -1 then begin MapWin.MapProvider := TMapProvider(lstProvider.Objects[idx]); ConstraintZoom(MapWin); end else MapWin.MapProvider := nil; if Assigned(MapWin.MapProvider) then Redraw(MapWin); end; procedure TMapViewerEngine.SetSize(aWidth, aHeight: integer); begin if (MapWin.Width = aWidth) and (MapWin.Height = aHeight) then Exit; CancelCurrentDrawing; MapWin.Width := aWidth; MapWin.Height := aHeight; CalculateWin(MapWin); Redraw(MapWin); if Assigned(OnChange) then OnChange(Self); end; procedure TMapViewerEngine.SetUseThreads(AValue: Boolean); begin if Queue.UseThreads = AValue then Exit; Queue.UseThreads := AValue; Cache.UseThreads := AValue; end; procedure TMapViewerEngine.SetWidth(AValue: integer); begin if MapWin.Width = AValue then Exit; MapWin.Width := AValue; CalculateWin(MapWin); Redraw(MapWin); end; procedure TMapViewerEngine.SetZoom(AValue: integer); begin if MapWin.Zoom = AValue then Exit; MapWin.Zoom := AValue; ConstraintZoom(MapWin); CalculateWin(MapWin); Redraw(MapWin); if Assigned(OnZoomChange) then OnZoomChange(Self); if Assigned(OnChange) then OnChange(Self); end; procedure TMapViewerEngine.TileDownloaded(Data: PtrInt); var EnvTile: TEnvTile; img: TLazIntfImage; X, Y: integer; begin EnvTile := TEnvTile(Data); try if IsCurrentWin(EnvTile.Win)then begin Cache.GetFromCache(EnvTile.Win.MapProvider, EnvTile.Tile, img); X := EnvTile.Win.X + EnvTile.Tile.X * TILE_SIZE; // begin of X Y := EnvTile.Win.Y + EnvTile.Tile.Y * TILE_SIZE; // begin of X DrawTile(EnvTile.Tile, X, Y, img); end; finally FreeAndNil(EnvTile); end; end; function TMapViewerEngine.WorldScreenToLonLat(aPt: TPoint): TRealPoint; begin aPt.X := aPt.X - MapWin.X; aPt.Y := aPt.Y - MapWin.Y; Result := ScreenToLonLat(aPt); end; procedure TMapViewerEngine.WriteProvidersToXML(AFileName: String); var doc: TXMLDocument; root: TDOMNode; i: Integer; prov: TMapProvider; begin doc := TXMLDocument.Create; try root := doc.CreateElement('map_providers'); doc.AppendChild(root); for i := 0 to lstProvider.Count - 1 do begin prov := TMapProvider(lstProvider.Objects[i]); prov.ToXML(doc, root); end; WriteXMLFile(doc, AFileName); finally doc.Free; end; end; procedure TMapViewerEngine.ZoomOnArea(const aArea: TRealArea); var tmpWin: TMapWindow; visArea: TRealArea; TopLeft, BottomRight: TPoint; begin tmpWin := MapWin; tmpWin.Center.Lon := (aArea.TopLeft.Lon + aArea.BottomRight.Lon) / 2; tmpWin.Center.Lat := (aArea.TopLeft.Lat + aArea.BottomRight.Lat) / 2; tmpWin.Zoom := 15; TopLeft.X := 0; TopLeft.Y := 0; BottomRight.X := tmpWin.Width; BottomRight.Y := tmpWin.Height; Repeat CalculateWin(tmpWin); visArea.TopLeft := MapWinToLonLat(tmpWin, TopLeft); visArea.BottomRight := MapWinToLonLat(tmpWin, BottomRight); if AreaInsideArea(aArea, visArea) then break; dec(tmpWin.Zoom); until (tmpWin.Zoom = 2); MapWin := tmpWin; Redraw(MapWin); end; //------------------------------------------------------------------------------ procedure SplitGps(AValue: Double; out ADegs, AMins: Double); begin AValue := abs(AValue); AMins := frac(AValue) * 60; ADegs := trunc(AValue); end; procedure SplitGps(AValue: Double; out ADegs, AMins, ASecs: Double); begin SplitGps(AValue, ADegs, AMins); ASecs := frac(AMins) * 60; AMins := trunc(AMins); end; function GPSToDMS(Angle: Double): string; var deg, min, sec: Double; begin SplitGPS(Angle, deg, min, sec); Result := Format('%.0f° %.0f'' %.1f"', [deg, min, sec]); end; function LatToStr(ALatitude: Double; DMS: Boolean): String; begin if DMS then Result := GPSToDMS(abs(ALatitude)) else Result := Format('%.6f°',[abs(ALatitude)]); if ALatitude > 0 then Result := Result + ' N' else if ALatitude < 0 then Result := Result + 'E'; end; function LonToStr(ALongitude: Double; DMS: Boolean): String; begin if DMS then Result := GPSToDMS(abs(ALongitude)) else Result := Format('%.6f°', [abs(ALongitude)]); if ALongitude > 0 then Result := Result + ' E' else if ALongitude < 0 then Result := Result + ' W'; end; { Returns the direct distance (air-line) between two geo coordinates If latitude NOT between -90°..+90° and longitude NOT between -180°..+180° the function returns -1. Usage: FindDistance(51.53323, -2.90130, 51.29442, -2.27275, duKilometers); } function CalcGeoDistance(Lat1, Lon1, Lat2, Lon2: double; AUnits: TDistanceUnits = duKilometers): double; const EPS = 1E-12; var d_radians: double; // distance in radians lat1r, lon1r, lat2r, lon2r: double; arg: Double; begin // Validate if (Lat1 < -90.0) or (Lat1 > 90.0) then exit(NaN); // if (Lon1 < -180.0) or (Lon1 > 180.0) then exit(NaN); if (Lat2 < -90.0) or (Lat2 > 90.0) then exit(NaN); // if (Lon2 < -180.0) or (Lon2 > 180.0) then exit(NaN); // Turn lat and lon into radian measures lat1r := (PI / 180.0) * Lat1; lon1r := (PI / 180.0) * Lon1; lat2r := (PI / 180.0) * Lat2; lon2r := (PI / 180.0) * Lon2; // calc arg := sin(lat1r) * sin(lat2r) + cos(lat1r) * cos(lat2r) * cos(lon1r - lon2r); if (arg < -1) or (arg > +1) then exit(NaN); if SameValue(abs(Lon1-Lon2), 360, EPS) and SameValue(abs(arg), 1.0, EPS) then d_radians := PI * 2.0 else d_radians := arccos(arg); Result := EARTH_RADIUS * d_radians; case AUnits of duMeters: ; duKilometers: Result := Result * 1E-3; duMiles: Result := Result * 0.62137E-3; end; end; end.