FMXで国土地理院の地理院タイルを使用して地図を表示する ~Delphiでお手軽プログラミング
~国土地理院のタイルマップを画像をインターネットからダウンロードして地図を表示する
1.Delphiを起動
Delphiを起動して「ファイル」⇒「新規作成」⇒「マルチデバイスアプリケーション -Delphi」⇒「空のアプリケーション」をクリックしてFMXプロジェクトを作成します。フォームにTPanelを配置しAlignプロパティをTopに設定します。
Panel1にTEditを3つ配置します。
フォームにTImageを配置しAlignプロパティをClientに設定します。
プロジェクトとユニットを保存します。
2.ユニットの追加
「ファイル」⇒「新規作成」⇒「ユニット」から新規ユニットを作成して、以下ソースを記述し、 ファイル名「UMamTileMap.pas」として保存します。
unit UMamTileMap;
interface
uses
FMX.Graphics,Math,system.classes, System.Types,
System.Net.URLClient, System.Net.HttpClient,
System.Net.HttpClientComponent,
System.SysUtils,System.Threading
,FMX.Dialogs;
type
TMapCompEvent=procedure(Sender:TObject;zxy:String) of object;
PMap=^TMamTileMap;
PObject=^TObject;
TMamTileMap=class(TObject)
private
fCachePath:string;
fNetHTTPClient:TNetHTTPClient;
fRefPtr:Pointer;
fNotify:TNotifyEvent;
fMapList:TStringList;
fZoom,fLat,fLng:Extended;
fRange:Integer;
fTileBmp:TBitmap;
fResultBmp:TBitmap;
procedure CreateResultBmp();
public
constructor Create(
RefPtr:Pointer;
CachePath:string;
Notify:TNotifyEvent=nil
);
destructor Destroy();override;
function getPxFromLat(zoom,lat:Extended):Extended;
function getPxFromLng(zoom,lng:Extended):Extended;
function getLatFromPx(zoom,px:Extended):Extended;
function getLngFromPx(zoom,px:Extended):Extended;
function getMapX(zoom,lat:Extended):Extended;
function getMapY(zoom,lng:Extended):Extended;
procedure getBitmap(zoom,lat,lng:Extended);
procedure mapDownloadComplete(Sender:TObject;zxy:String);
property ResultBmp:TBitmap read fResultBmp;
end;
TMapThread=class(TThread)
private
fz,fx,fy:Integer;
fCachePath:string;
fNetHTTPClient:TNetHTTPClient;
fMapUrl:string;
fTileBmp:TBitmap;
fTileX,fTileY:Integer;
fCanceled:Boolean;
fMapCompEvent:TMapCompEvent;
protected
procedure Execute;override;
public
constructor Create(
z,x,y:integer;
CachePath:string;
TileBmp:TBitmap;
TileX,TileY:integer;
MapCompEvent:TMapCompEvent);
destructor Destroy();override;
property z:Integer read fz;
property x:Integer read fx;
property y:Integer read fy;
property Canceled:Boolean read fCanceled write fCanceled;
end;
const
ll:Extended=85.0511287798;
implementation
{ TMap }
constructor TMamTileMap.Create(
RefPtr:Pointer;CachePath:string;
Notify:TNotifyEvent=nil);
begin
fRange:=2;
fTileBmp:=TBitmap.Create;
fTileBmp.Width :=256*(fRange*2+1);
fTileBmp.Height:=256*(fRange*2+1);
fResultBmp:=TBitmap.Create;
fRefPtr:=RefPtr;
fNetHTTPClient:=TNetHTTPClient.Create(nil);
fCachePath:=CachePath;
fNotify:=Notify;
fMapList:=TStringList.Create;
end;
destructor TMamTileMap.Destroy;
begin
fNetHTTPClient.Free;
PObject(fRefPtr)^:=nil;
fMapList.Free;
FreeAndNil(fTileBmp);
FreeAndNil(fResultBmp);
inherited;
end;
procedure TMamTileMap.mapDownloadComplete(Sender: TObject;zxy:String);
var i:Integer;
begin
i:=fMapList.IndexOf(zxy);
if i>=0 then fMapList.Delete(i);
CreateResultBmp();
if Assigned(fNotify) then fNotify(self);
end;
function TMamTileMap.getLatFromPx(zoom, px: Extended): Extended;
begin
//ズームとピクセルを与えると経度を返す
result:=180*(px/Math.Power(2,floor(zoom)+7)-1);
end;
function TMamTileMap.getLngFromPx(zoom, px: Extended): Extended;
begin
//ズームとピクセルを与えると緯度を返す
result:=
180/pi()*(
ArcSin(
Tanh(
-pi()/Power(2,floor(zoom)+7)*px+
ArcTanh(Sin(pi()/180*ll))
)
)
);
end;
function TMamTileMap.getMapX(zoom,lat: Extended): Extended;
begin
//ズームと経度を与えると何枚目のタイル(x)か返す
Result:=getPxFromLat(floor(zoom),lat)/256;
end;
function TMamTileMap.getMapY(zoom,lng: Extended): Extended;
begin
//ズームと緯度を与えると何枚目のタイル(y)か返す
Result:=getPxFromLng(floor(zoom),lng)/256;
end;
procedure TMamTileMap.getBitmap(zoom, lat, lng: Extended);
var i,x,y,xx,yy:Integer;
bmp:TBitmap;
fname:string;
st:string;
stl:TStringList;
begin
if(zoom>18.8)then zoom:=18.8;
if(zoom<7)then zoom:=7;
fZoom:=zoom;
fLat:=lat;
fLng:=lng;
x:=Floor(getMapX(fZoom,fLat));
y:=Floor(getMapY(fZoom,fLng));
stl:=TStringList.Create;
try
stl.Assign(fMapList);
for yy := y-fRange to y+fRange do
begin
for xx := x-fRange to x+fRange do
begin
fname:=format('%.6d%.6d%.6d.png',[floor(zoom),xx,yy]);
i:=fMapList.IndexOf(fname);
if i>=0 then
stl.Delete(stl.IndexOf(fname));
end;
end;
for i := 0 to stl.Count-1 do
begin
TMapThread(
fMapList.Objects[fMapList.IndexOf(stl[i])]
).Canceled:=True;
end;
finally
stl.Free;
end;
fTileBmp.Canvas.BeginScene();
fTileBmp.Canvas.Fill.Kind:=TBrushKind.Solid;
fTileBmp.Canvas.Fill.Color:=$FFFFFFFF;
fTileBmp.Canvas.FillRect(
RectF(0,0,fTileBmp.Width,fTileBmp.Height),
0,0,[],1
);
fTileBmp.Canvas.EndScene;
for yy := y-fRange to y+fRange do
begin
for xx := x-fRange to x+fRange do
begin
//先頭ゼロ埋め
fname:=format('%.6d%.6d%.6d.png',[Floor(zoom),xx,yy]);
if FileExists(fCachePath+'\'+fname) then
begin
bmp:=TBitmap.Create();
try
bmp.LoadFromFile(fCachePath+'\'+fname);
fTileBmp.Canvas.BeginScene();
fTileBmp.Canvas.DrawBitmap(
bmp,
RectF(0,0,255,255),
RectF(
(xx-(x-fRange) )*256,(yy-(y-fRange) )*256,
(xx-(x-fRange)+1)*256,(yy-(y-fRange)+1)*256),
1,true
);
fTileBmp.Canvas.EndScene;
finally
bmp.Free;
end;
end
else
begin
st:=format('%.6d%.6d%.6d.png',[floor(zoom),xx,yy]);
if fMapList.IndexOf(st)<0 then
begin
fMapList.AddObject(
st,
TMapThread.Create(
floor(zoom),xx,yy,
fCachePath,
fTileBmp,
xx-(x-fRange),yy-(y-fRange),
mapDownloadComplete
)
);
end;
end;
end;
end;
CreateResultBmp();
end;
function TMamTileMap.getPxFromLat(zoom,lat: Extended): Extended;
begin
//ズームと経度を与えるとピクセル座標(x)を返す
result:=Math.Power(2,floor(zoom)+7) * (lat/180+1);
end;
function TMamTileMap.getPxFromLng(zoom,lng: Extended): Extended;
begin
//ズームと緯度を与えるとピクセル座標(y)を返す
result:=Math.Power(2,floor(zoom)+7)/pi()*
(
-Math.ArcTanh(sin(pi()/180*lng))
+Math.ArcTanh(sin(pi()/180*ll))
);
end;
procedure TMamTileMap.CreateResultBmp;
var x,y:integer;
pxx,pxy,zz:extended;
begin
x:=floor(getMapX(fZoom,fLat));
y:=floor(getMapY(fZoom,fLng));
pxx:=getPxFromLat(fZoom,fLat)-(x-fRange)*256;
pxy:=getPxFromLng(fZoom,fLng)-(y-fRange)*256;
zz:=fZoom-floor(fZoom)+1;
fResultBmp.Width:=(fRange+1)*256;
fResultBmp.Height:=(fRange+1)*256;
fResultBmp.Canvas.BeginScene();
fResultBmp.Canvas.DrawBitmap(
fTileBmp,
RectF(
pxx-(fResultBmp.Width/2)/zz,pxy-(fResultBmp.Height/2)/zz,
pxx+(fResultBmp.Width/2)/zz,pxy+(fResultBmp.Height/2)/zz
),
RectF(0,0,fResultBmp.Width,fResultBmp.Height),
1,true
);
fResultBmp.Canvas.EndScene;
end;
{ TMapThread }
constructor TMapThread.Create(
z,x,y:integer;
CachePath:string;
TileBmp:TBitmap;
TileX,TileY:integer;
MapCompEvent:TMapCompEvent);
begin
inherited Create(false);
//国土地理院の地理院タイルを使用する場合
//利用規約は https://www.gsi.go.jp/kikakuchousei/kikakuchousei40182.html
fMapUrl:=
'https://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png';
FreeOnTerminate:=true;
fMapCompEvent:=MapCompEvent;
fz:=z;
fx:=x;
fy:=y;
fCachePath:=CachePath;
fTileBmp:=TileBmp;
fTileX:=TileX;
fTileY:=TileY;
fCanceled:=False;
fNetHTTPClient:=TNetHTTPClient.Create(nil);
fNetHTTPClient.ConnectionTimeout:=2000;
fNetHTTPClient.ResponseTimeout:=2000;
fNetHTTPClient.Asynchronous:=False;
end;
destructor TMapThread.Destroy;
begin
fNetHTTPClient.Free;
inherited;
end;
procedure TMapThread.Execute;
var
res:IHTTPResponse;
strm:TMemoryStream;
url:string;
fname:string;
bmp:TBitmap;
ct:Integer;
begin
inherited;
//先頭ゼロ埋め
fname:=format('%.6d%.6d%.6d.png',[fz,fx,fy]);
if not FileExists(fCachePath+'\'+fname) then
begin
url:=StringReplace(fMapUrl,'{z}',IntToStr(fz),[rfReplaceAll]);
url:=StringReplace(url,'{x}',IntToStr(fx),[rfReplaceAll]);
url:=StringReplace(url,'{y}',IntToStr(fy),[rfReplaceAll]);
strm:=TMemoryStream.Create;
try
ct:=0;
repeat
res:=fNetHTTPClient.Get(url,strm);
sleep(100);
inc(ct);
until (ct>10) or (res.StatusCode=200);
if res.StatusCode=200 then
begin
if not fCanceled then
begin
Synchronize(procedure begin
bmp:=TBitmap.Create;
try
bmp.LoadFromStream(strm);
fTileBmp.Canvas.BeginScene();
fTileBmp.Canvas.DrawBitmap(
bmp,
RectF(0,0,255,255),
RectF(
fTileX *256, fTileY *256,
(fTileX+1)*256,(fTileY+1)*256),
1,true
);
fTileBmp.Canvas.EndScene;
finally
bmp.Free;
end;
end);
end;
Synchronize(procedure begin
strm.SaveToFile(fCachePath+'\'+fname);
end);
end;
finally
strm.Free;
end;
if (not fCanceled) and Assigned(fMapCompEvent) then
begin
fMapCompEvent(Self,fname);
end;
end;
end;
end.
2.Form1のソースコードの記述
Form1(Unit1)のソースコードを記述します。
unit Unit1;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Objects,
System.Net.URLClient, System.Net.HttpClient, System.Net.HttpClientComponent,
FMX.Controls.Presentation, FMX.StdCtrls, FMX.Edit,
Winapi.ShlObj,
Winapi.windows,
FMX.Platform.win,
system.Math,
Unit2;
type
TForm1 = class(TForm)
Image1: TImage;
Panel1: TPanel;
Edit1: TEdit;
Edit2: TEdit;
Edit3: TEdit;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure Image1MouseWheel(Sender: TObject; Shift: TShiftState;
WheelDelta: Integer; var Handled: Boolean);
procedure Image1MouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Single);
private
{ private 宣言 }
//bmp:FMX.Graphics.TBitmap;
map:TMap;
fZoom,fLat,fLng:Extended;
procedure OnGetMap(Sender: TObject);
procedure ShowMap();
public
{ public 宣言 }
end;
var
Form1: TForm1;
implementation
{$R *.fmx}
procedure TForm1.FormCreate(Sender: TObject);
var pw:PWideChar;
CachePath:string;
begin
//キャッシュフォルダを取得する
GetMem(pw,2048);
try
ZeroMemory(pw,2048);
SHGetSpecialFolderPath(
WindowHandleToPlatform(Handle).Wnd,
pw,CSIDL_LOCAL_APPDATA,
false
);
CachePath:=pw;
finally
FreeMem(pw);
end;
CachePath:=CachePath+'\mam\gis\cache';
//キャッシュフォルダを作成する
if not DirectoryExists(CachePath) then
ForceDirectories(CachePath);
//インスタンスの作成
map:=TMap.Create(@map,CachePath,OnGetMap);
//初期値の設定
fZoom:=18; //ズーム18
fLat:=139.69167; //東経
fLng:=35.68944; //北緯
ShowMap();
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
map.Free;
end;
procedure TForm1.Image1MouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Single);
var px,py:Extended;
// bmp:FMX.Graphics.TBitmap;
ImgRatio:Extended;
// mxy:extended;
begin
//ズームと東経からピクセル値を取得
px:=map.getPxFromLat(fZoom,fLat);
//ズームと北緯からピクセル値を取得
py:=map.getPxFromLng(fZoom,fLng);
if Image1.Width<Image1.Height then
ImgRatio:=Image1.Bitmap.Width/Image1.Width
else
ImgRatio:=Image1.Bitmap.Height/Image1.Height;
px:=px+(X-Image1.Width/2)*ImgRatio;
py:=py+(Y-Image1.Height/2)*ImgRatio;
//マウス座標の経度緯度を取得
fLat:=map.getLatFromPx(fZoom,px);
fLng:=map.getLngFromPx(fZoom,py);
ShowMap();
end;
procedure TForm1.Image1MouseWheel(Sender: TObject; Shift: TShiftState;
WheelDelta: Integer; var Handled: Boolean);
begin
if WheelDelta<0 then fZoom:=fZoom-0.2 else fZoom:=fZoom+0.2;
if fZoom<7 then fZoom:=7;
if fZoom>18.8 then fZoom:=18.8;
ShowMap();
end;
procedure TForm1.OnGetMap(Sender: TObject);
begin
Image1.Bitmap.Assign(TMap(Sender).ResultBmp);
end;
procedure TForm1.ShowMap;
//var bmp:FMX.Graphics.TBitmap;
begin
Edit1.Text:=format('%4.1f',[fZoom]);
Edit2.Text:=format('%9.4f',[fLat]);
Edit3.Text:=format('%9.4f',[fLng]);
map.getBitmap(fZoom,fLat,fLng);
Image1.Bitmap.Assign(map.ResultBmp);
end;
end.
3.実行する
実行すると、マップが表示され、マップをクリックするとクリックした位置が中心に表示されます。また、マウスの中ボタンホイールで拡大縮小されます。出典:国土地理院ウェブサイト(https://maps.gsi.go.jp/development/ichiran.html)
