Google Earth 卫星地图影像数据获取与应用
Google 公司通过Google Earth数字化平台和互联网络,向全世界免费发布其所拥有的全球卫星影像数据。本文阐述如何从Google Earth数字平台获得用户关心区域的地图卫星影像。
通过重写Google Earth的KML数据文件,将用户关心的区域以“路标”点阵的方式写入KML数据文件;然后利用Google Earth提供的游览功能,自动播放KML文件;与此同时使用智能抓图软件,同步将播放图像保存为本地数据文件,完成地图卫星影像获取。
Google Earth允许用户在地图上感兴趣的特定位置处设置标志,即“路标”(Place Mark),它是Google Earth定义的一种空间点类型,包括路标名称、位置(经纬度、高度)、视角、等信息。
为了能够一次获取一个地区的全部图像,需要在该地区所在的空间范围内布置“路标”点阵。Google Earth提供了添加和编辑“路标”的功能,但是要获取一个地区的地图卫星影像,就必须采用直接写入KML数据文件的方式,将区域所包含的大量的“路标”点阵写入Google Earth中。
Google Earth的KML数据文件是一种通用非结构化数据文件(XML格式),通过KML数据文件,用户可以实现与Google Earth软件的沟通与交流。
写入KML数据文件时, 通过设置Placemark数据类型,将用户关心的区域进行覆盖,重要的是将Placemark的空间位置(经度、纬度、高度)设置正确,其中经度、纬度数据单位是度,高度和视距单位是m。
经度、纬度的计算方法根据视距和每屏覆盖的经差范围和纬差范围确定。保证相邻图像适度重合,然后计算每一幅图像的中心位置。
将KML调入Google Earth,在Google Earth的Option菜单中设置控制参数。关键是要根据网速,设置图像的停顿时间,以便让Google Earth有充足的时间将图像数据通过网络从远端图像数据服务器传到本地。一般宽带网设置停顿时间为10-30秒,另外,为了加快抓图速度,可以先浏览一、两次,提前将数据下载到Google Earth在本地的Cache数据文件中,在实际抓图时,Google Earth可以以较快的速度从Cache数据文件中获取数据,此时可以将停顿时间缩短,一般4秒即可。
智能抓图与播放KML文件同时进行,抓图功能由DQMAP软件提供。DQMAP可以根据Google Earth播放KML数据文件的进程,抓到最清晰的图像,并保存到设置的文件夹中。
为了保证抓图与播放动作同步,DQMAP软件在写KML文件和抓图时,在安排“路标”阵列与抓图数据文件名称时,采用一对一的关系。
“路标”阵列的安排如下:
With DQ800[CurrentSec] do
For j:=0 to ACol -1 do
begin
Lon:=LonSt+j*DLon;
For i:=0 to ARow-1 do
begin
Lat:=LatSt-i*DLat;
XMLDoc_WritePlaceMark(F,Prefix+FormatDegSecRound(Lat)+'_'+
FormatDegSecRound(Lon),
Lon,Lat,DQ800[CurrentSec].Scale,False);
end;
end;
由定时器和图像自动识别单元组成的抓图图像文件列表安排如下:
With DQ800[CurrentSec] do
For j:=0 to ACol -1 do
begin
Lon:=LonSt+j*DLon;
For i:=0 to ARow-1 do
begin
Lat:=LatSt-i*DLat;
fn:=GetImageFileNameRound(Path,Prefix,Lat,Lon,NameLen,CheckBox1.Checked);
Inc(k);
SetLength(BMPFName,k);
BMPFName[k-1]:=fn;
end;
end;
声明了两个实例DQ800,DQUnits:TImageSecArr,分别代表区域数列和当前区域的图像数列。它们具有相同的数据类型。
TImageSecArr=Array of TImageSec;
其中ImageSect是为了描述一个区域或图像数据而定义的数据类型。
TImageSec=record
LatSt,LonSt:Real; //区域或图像的起点,左上
LatEn,LonEn:Real; //区域或图像的终点,右下
DLat,DLon:Real; //区域或图像的范围纬差,经差
NERange:RangeType; //区域或图像的范围(MinP(左下),MaxP(右上))
Rows,Cols:Integer; //图像的行数、列数
Scale:Real; //视距,视野宽度
Lats,Lons:TpVector; //图像行阵列的纬度、图像行阵列的经度
NameLen:Integer; //2 for DD_MM,3 for DD_MM_SS
SecDir, Path, Prefix, //图像的存放目录
SecName:String; //区域名称
SizeX,SizeY:Integer; //图像单元的像素尺寸,图像的宽度、高度像素数
Width,Height:Integer; //区域图像尺寸(估计),宽度、高度像素数
PixelToX,PixelToY:Real; //水平像素系数,垂直像素系数
WidToHei:Real; //垂直—水平比例(估计)
ImageLoaded:Boolean;
MinScale,MaxScale:Real; //可见图像的比例尺范围
ID:String[16]; //数据库中区域的ID号
end;
将DQ800,DQUnits数据存放到数据库文件中,以便访问。附录3是从数据库文件中读取DQ800数据的程序段。下面是写当前Section 的程序段。
With ASec do
For i := 0 to Rows do
For j := 0 to Cols do
Begin
FName:=GetImageFileNameRound(Path,Prefix,Lats.vec[i],Lons.vec[j],NameLen,IsGrided);
FieldByName('ID').asString:=UnitID;
FieldByName('SectName').asString:=FName;
FieldByName('SectDir').asString:=SecDir;
FieldByName('MainPath').asString:=Path;
UnitLatSt:=LatSt-DLat*i;
UnitLonSt:=LonSt+DLon*j;
FieldByName('LatSt').asFloat:=UnitLatSt;
FieldByName('LonSt').asFloat:=UnitLonSt;
Deg2DMS(UnitLatSt,DD,MM,SS);
FieldByName('LatStDD').asinteger:=DD;
FieldByName('LatStMM').asinteger:=MM;
FieldByName('LatStSS').asinteger:=SS;
Deg2DMS(UnitLonSt,DD,MM,SS);
FieldByName('LonStDD').asinteger:=DD;
FieldByName('LonStMM').asinteger:=MM;
FieldByName('LonStSS').asinteger:=SS;
UnitLatEn:=LatSt-DLat*(i+1);
UnitLonEn:=LonSt+DLon*(j+1);
FieldByName('LatEn').asFloat:=UnitLatEn;
Deg2DMS(UnitLatEn,DD,MM,SS);
FieldByName('LatEnDD').asinteger:=DD;
FieldByName('LatEnMM').asinteger:=MM;
FieldByName('LatEnSS').asinteger:=SS;
FieldByName('LonEn').asFloat:=UnitLonEn;
Deg2DMS(UnitLonEn,DD,MM,SS);
FieldByName('LonEnDD').asinteger:=DD;
FieldByName('LonEnMM').asinteger:=MM;
FieldByName('LonEnSS').asinteger:=SS;
FieldByName('DLat').asFloat:=DLat;
FieldByName('DLon').asFloat:=DLon;
FieldByName('Rows').asFloat:=Rows;
FieldByName('Cols').asFloat:=Cols;
FieldByName('Scale').asFloat:=Scale;
FieldByName('NameLen').asInteger:=NameLen;
FieldByName('Prefix').asString:=Prefix;
FieldByName('SizeX').asInteger:=SizeX;
FieldByName('SizeY').asInteger:=SizeY;
FieldByName('Width').asInteger:=Width;
FieldByName('Height').asInteger:=Height;
FieldByName('PixelToX').asFloat:=PixelToX;
FieldByName('PixelToY').asFloat:=PixelToY;
FieldByName('WidToHei').asFloat:=WidToHei;
l:=1;
For m:=i-1 to i+1do
For n:=j-1 to j+1do
If (m<>i)or(n<>j) then
Begin
XUnitID:=ID+I2S0(m,3)+I2S0(n,3);
XFName:=GetImageFileNameRound(Path,Prefix,Lats.vec[m],Lons.vec[n],NameLen,IsGrided);
IfFileExists(XFname)then
Begin
FieldByName('ID'+Inttostr(l)).AsString:=XUnitID;
End;
Inc(l);
End;
Post;
End;
下面是读当前Section 的程序段。
LoadAUnit(ATable,ASec)
With ATable,ASec,ImageSecOfs do
begin
SecName:=FieldByName('SectName').asString;
SecDir:=FieldByName('SectDir').asString;
Path:=FieldByName('MainPath').asString;
LatSt:=FieldByName('LatSt').asFloat;
LonSt:=FieldByName('LonSt').asFloat;
LatEn:=FieldByName('LatEn').asFloat;
LonEn:=FieldByName('LonEn').asFloat;
DLat:=FieldByName('DLat').asFloat;
DLon:=FieldByName('DLon').asFloat;
NERange.MinP:=Get3DPoint((LatEn+DLat/2+MinP.Y/3600)*PI/180,
(LonSt-DLon/2+MinP.X/3600)*PI/180,0);
NERange.MaxP:=Get3DPoint((LatSt+DLat/2+MaxP.Y/3600)*PI/180,
(LonEn-DLon/2+MaxP.X/3600)*PI/180,0);
NameLen:=FieldByName('NameLen').asInteger;
Prefix:=FieldByName('Prefix').asString;
SizeX:=FieldByName('SizeX').asInteger;
SizeY:=FieldByName('SizeY').asInteger;
Width:=FieldByName('Width').asInteger;
Height:=FieldByName('Height').asInteger;
PixelToX:=FieldByName('PixelToX').asFloat;
PixelToY:=FieldByName('PixelToY').asFloat;
WidToHei:=FieldByName('WidToHei').asFloat;
ImageLoaded:=False;
end;
读DQUnits数据
Procedure LoadUnit(ATable:TAdoQuery;
SecTbName,SecDir:String;
IsGrided:Boolean);OverLoad;
Var
k:Integer;
SqlStr:String;
begin
With ATable do
begin
Try
Open;
First;
k:=0;
If RecordCount=0 then exit;
SetLength(DQUnits,RecordCount);
While Not eof do
begin
LoadAUnit(ATable,DQUnits[k],IsGrided);
Inc(k);
Next;
end;
except
end;
end;
end;
包括可缩放的图像显示和原始尺寸图像显示。图像显示重点解决图像映射、图像变形等问题。图像映射需要考虑用户坐标、屏幕坐标和图像坐标之间的对应关系,参考附件4。
(1)图像缩放显示
图像缩放显示的原理是同步处理图像覆盖的实际用户区域与屏幕显示区域的关系以及实际图像与缩放后图像之间的关系。
图像覆盖的实际用户区域与屏幕显示区域的关系用MapScreen实现;实际图像与缩放后图像之间的关系是通过MapImage实现。见附件5。
显示流程如下:
1)使用MapScreen将图像的范围映射到屏幕上对应的区域Dest(可见部分);
2)如果Dest为空,退出;
3)使用CopyRect,将屏幕上Dest对应的内容复制到临时图象Temp中;
4)使用MapImage,将Dest对应的范围映射到原始图像上对应的区域Source;
5)使用CopyRect,将原始图像上区域Source对应的部分复制到Temp0;
6)使用DistortPic,将Temp0、Temp合成(暂存在Temp中);
7)使用CopyRect,将Temp中的图象复制到目标Canvas上。
下文是显示图像的程序段。
Procedure ShowMapImage(Var BBMP:TBitMap;Image:TRasterImage);OverLoad;
Var
Dest,Source:TRect;
Dest0,Source0:TRect;
Temp,Temp0:TBitMap;
VisRange:RangeType;
Scale:Real;
With BBMP,Canvas,Image,XYRange do
Begin
Dest:=MapScreenP(XYRange); //求图象在屏幕中的显示区域
Scale:=abs(Dest.Top-Dest.Bottom)/Height; //显示比例尺
Dest:=RectAnd(Dest,Rect(0,0,Width,Height)); //求能见的图像显示区,交集
Dest0:=RectTrim(Dest); //归一化为局部区域
If (Dest0.Right>0) and (Dest0.Bottom>0) then //目标区域不空时才显示,important
If Scale>ImageZoomScale then //在可见范围才显示
begin
Temp:=TBitmap.Create; //临时图像 ,存放当前屏幕内容
Try
SetBMPSize(Temp,Dest0); //用Dest0设置临时图像尺寸
Temp.Canvas.CopyRect(Dest0,Canvas,Dest); //将屏幕对应区复制到临时图像TEMP
VisRange:=AndRange(XYRange,CurrentRelPort.RVRange); //求图象在UCS中的可见区域
Source:=MapImage(VisRange,XYRange,BMP.Width,BMP.Height); //映射UCS可见区到图像坐标系
Source0:=RectTrim(Source); //将Source 归一化
If (Source0.Right>0) and (Source0.Bottom>0) then //
begin
Temp0:=TBitmap.Create;
Try
SetBMPSize(Temp0,Source0); //用设置临时图像尺寸
Temp0.Canvas.CopyRect(Source0,BMP.Canvas,Source); //将Source0复制到Temp0中
DistortPic(Temp0.Handle,Temp.Handle,Temp.Canvas.Handle,0,Transparent);//缩放后存Temp
CopyRect(Dest,Temp.Canvas,Dest0); //将Temp中的图象复制到目标Canvas
Finally
Temp0.Free;
end;
end;
Finally
Temp.Free;
end;
end;
end;
(2)图像变形显示
处理图像变形更复杂,需要考虑视角、球面与平面之间的映射等问题。通过设置局部用户坐标,可以在某种程度绕过处理图像变形显示的问题。
(3)阵列显示
作为底图显示时,通常需要显示大量的图像阵列。在显示大量的图像时,需要重点解决的问题是显示反映的速度和减少对内存的过度需求。
实际上,上述需求是相互矛盾的,为了加速显示速度,通常采用双缓存机制,这需要开辟一个内存区,用于后台的图像调入,这必然占用大量的内存。实际上,对于图像处理的硬件要求中,内存是一个关键数据。因此,好的解决方案在于寻求一个折衷的方案,在速度和内存上都能够达到用户的需求。
(4)由定时器控制显示
With CurrentMap,DM1 do
Begin
NE:=XY2BL(PR2.Y,PR2.X);
If AutoRasterImageSec then
Begin
NewSec:=SectionExists(NE);
If NewSec<>CurrentSec then
If NewSec>=0 then
Begin
FreeAllRasterImage;
CurrentSec:=NewSec;
ImageSecName:=DQ800[CurrentSec].SecName;
LoadImageOfs(AdoQuery1,'Sections',ImageSecName);
LoadUnit(AdoQuery1,'Units',ImageSecName,ImageGridFormat);
End;
End;
CurrImage:=ImageExists(NE);
If CurrImage<0 then exit;
With DQUnits[CurrImage],CurrentRelPort.RVRange do
Begin
If ImageLoaded then exit;
If ImageGridFormat then Fn:=Path+'Grid/'
else Fn:=Path+'/';
Fn:=fn+SecName;
If FileExists(Fn) Then
Begin
AppendRasterImage(Fn,LayerNo('底图'),NERange);
ImageLoaded:=True;
GMapLoaded;
LocalShowScater;
End;
End;
End;
end;
正确显示图像,需要对获得的图像进行重定位。通过调整图像范围(XYRange)、核心区域尺寸(SizeX、SizeY)或重叠区宽度(DX、DY)、图像偏移(Ofs)、图像的长宽比例(WidetoHeight)等。
3.1 图像偏移
ImageSec的ImageSecOfs从数据库中取数据的程序体。
With ImageSecOfs do
Begin
MinP.X:=FieldByName('OfsMinE').AsFloat
MinP.Y:=FieldByName('OfsMinN').AsFloat
MaxP.X:=FieldByName('OfsMaxE').AsFloat
MaxP.Y:=FieldByName('OfsMaxN').AsFloat
end;
With ImageSecOfs2 do
Begin
MinP.X:=FieldByName('OfsMinE2').AsFloat
MinP.Y:=FieldByName('OfsMinN2').AsFloat
MaxP.X:=FieldByName('OfsMaxE2').AsFloat
MaxP.Y:=FieldByName('OfsMaxN2').AsFloat
end;
If UseLocalCentreLon then
With ImageSecOfs do
Begin
MinP.X:=MinP.X+ImageSecOfs2.MinP.X;
MaxP.X:=MaxP.X+ImageSecOfs2.MaxP.X;
MinP.Y:=MinP.Y+ImageSecOfs2.MinP.Y;
MaxP.Y:=MaxP.Y+ImageSecOfs2.MaxP.Y;
End;
With ASec,ImageSecOfs do
begin
LatSt:=FieldByName('LatSt').asFloat;
LonSt:=FieldByName('LonSt').asFloat;
LatEn:=FieldByName('LatEn').asFloat;
LonEn:=FieldByName('LonEn').asFloat;
DLat:=FieldByName('DLat').asFloat;
DLon:=FieldByName('DLon').asFloat;
With NERange do
Begin
MinP:=Get3DPoint((LatEn+DLat/2+MinP.Y/3600)*PI/180,(LonSt-DLon/2+MinP.X/3600)*PI/180,0);
MaxP:=Get3DPoint((LatSt+DLat/2+MaxP.Y/3600)*PI/180,(LonEn-DLon/2+MaxP.X/3600)*PI/180,0);
End;
End;