国内外很多数据源都是tile服务,谷歌、必应、opensteetmap、高德……,这些服务的数据层基本均采用Mercator投影,只是服务地址稍有差异。
比如下面的数据地址为谷歌的一个tile的URL地址:
http://mt1.google.cn/maps/vt?lyrs=s%40689&hl=zh-CN&gl=CN&&x=23&y=11&z=5
其中mt后的数字可以是0-7之间的任意整数,可以理解成不同的服务器。xyz后面的数字可以理解成该图片的行列号以及缩放级别。
其它的数据服务地址类似,可能xyz的位置不同,服务器的标识可能不是数字而是abcd等。
数据地址基本上大同小异。
worldwind java对此投影数据服务显然是支持的,其功能通过BasicMercatorTiledImageLayer实现。
worldwind对数据源的访问过程一般如下:
- 通过makeLevels构建图层所要的信息LevelSet,其核心为TileUrlBuilder
- TileUrlBuilder构建针对不同tile的url服务地址
- worldwind java通过url访问服务器资源并下载到本地后加载显示。
理论上讲,构造相应的LevelSet,并构建出相应的TileUrlBuilder即可实现对上述各个数据源的引入,并在worldwind上展示这些数据。
而TileUrlBuilder只是一个接口,并不具备实际构建url的能力,因此,构建一个类,实现此接口即可。
public interface TileUrlBuilder
{
public URL getURL(Tile tile, String imageFormat) throws java.net.MalformedURLException;
}
典型的改造过程如下:
- 重写makeLevels函数
典型的例子代码如下:
private static LevelSet makeLevels(TileUrlBuilder ub, String labelName,String datasetName,String suffix,String serviceURL,int numLevelMin,int numLevelMax,int tileWidth,int tileHeight)
{
AVList params = new AVListImpl();
params.setValue(AVKey.TILE_WIDTH, tileWidth);
params.setValue(AVKey.TILE_HEIGHT, tileHeight);
params.setValue(AVKey.DATA_CACHE_NAME, "Earth/"+labelName);
params.setValue(AVKey.SERVICE, serviceURL);
params.setValue(AVKey.DATASET_NAME, datasetName);
params.setValue(AVKey.FORMAT_SUFFIX, suffix);
params.setValue(AVKey.NUM_LEVELS, numLevelMax);
params.setValue(AVKey.NUM_EMPTY_LEVELS, numLevelMin);
params.setValue(AVKey.LEVEL_ZERO_TILE_DELTA, new LatLon(Angle.fromDegrees(22.5d), Angle.fromDegrees(45d)));
params.setValue(AVKey.SECTOR, new MercatorSector(-1.0, 1.0, Angle.NEG180, Angle.POS180));
params.setValue(AVKey.TILE_URL_BUILDER, ub);
return new LevelSet(params);
}
- 构建TileUrlBuilder
这里的TileUrlBuilder并没有任何数据源特征。为了保证对上述N个数据源的支持,这里通过建立一个TileUrlBuilderCustom的抽象类,构建上述数据源的一些共同特征。
public abstract class TileUrlBuilderCustom implements TileUrlBuilder {
protected String urlFormat ;
protected String serverPre;
protected String[] serverlist;
private String strApikey="";
protected String strXTag = "{x}";
protected String strYTag = "{y}";
protected String strZTag = "{z}";
protected String strServerTag = "{s}";
protected String strApiKeyTag = "{k}";
protected String strQuadKeyTagTag = "{quadkey}";
public TileUrlBuilderCustom()
{
}
public TileUrlBuilderCustom(String urlFormat,String serverPre)
{
this.urlFormat = urlFormat;
this.serverPre = serverPre;
if(serverPre!="")
serverlist = serverPre.split("");
}
public TileUrlBuilderCustom(String urlFormat,String serverPre,String strApikey)
{
this.urlFormat = urlFormat;
this.serverPre = serverPre;
if(serverPre!="")
serverlist = serverPre.split("");
this.strApikey = strApikey;
}
@Override
public URL getURL(Tile tile, String imageFormat) throws MalformedURLException {
LatLon center = tile.getSector().getCentroid();
String urlString = computeTileUrl(center
.getLatitude().degrees, center.getLongitude().degrees, tile
.getLevelNumber() + 3);
URL url = new URL(urlString);
return url;
}
protected String computeTileUrl(double lat, double lon, int zoom) {
lat = computeTileUrlTMS(lat);
if (lon > 180.0) {
lon -= 360.0;
}
lon = (180.0 + lon) / 360.0;
lat = 0.5
- Math.log(Math.tan((Math.PI / 4.0)
+ ((Math.PI * lat) / (2.0 * 180.0)))) / (2.0 * Math.PI);
int scale = 1 << (int) zoom;
int x = (int) (lon * scale);
int y = (int) (lat * scale);
String urlFormatTemp = urlFormat;
urlFormatTemp = computeTileUrl(urlFormatTemp,x,y,zoom);
urlFormatTemp = urlFormatTemp.replace(strXTag,String.valueOf(x));
urlFormatTemp = urlFormatTemp.replace(strYTag,String.valueOf(y));
urlFormatTemp = urlFormatTemp.replace(strZTag,String.valueOf(zoom));
urlFormatTemp = urlFormatTemp.replace(strApiKeyTag,strApikey);
urlFormatTemp = urlFormatTemp.replace(strQuadKeyTagTag,TileXyToQuadKey(x,y,zoom));
return urlFormatTemp;
}
abstract protected double computeTileUrlTMS(double lat);
abstract protected String computeTileUrl(String strTemp,int x, int y, int zoom);
protected String computeTileUrlRandom(String strTemp,int x, int y, int zoom) {
Random r = new Random();
if(serverPre=="")
return strTemp;
else {
strTemp = strTemp.replace(strServerTag,serverlist[r.nextInt(serverPre.length())]);
return strTemp;
}
}
protected String TileXyToQuadKey(int tileX, int tileY, int levelId)
{
String quadKey="";
int levelOfDetail = levelId;
for (int i = levelOfDetail; i > 0; i--)
{
char digit = '0';
long mask = 1 << (i - 1);
if ((tileX & mask) != 0)
{
digit++;
}
if ((tileY & mask) != 0)
{
digit++;
digit++;
}
quadKey=quadKey+digit;
}
return quadKey;
}
}
上面的代码比较简单。定义了一些占位符,比如tile服务的横纵坐标、缩放级别,服务器标签,ApiKey标签等,在构建url时,这些占位符被替换成相应的实际值。
抽象类毕竟不能直接实例成对象,下面给出了一个对服务器字符串随机处理的派生类,实际上直接调用父类中的computeTileUrlRandom即可。
public class TileUrlBuilderCustomRandomServer extends TileUrlBuilderCustom {
public TileUrlBuilderCustomRandomServer(String urlFormat,String serverPre) {
super(urlFormat,serverPre);
}
protected double computeTileUrlTMS(double lat)
{
return lat;
}
@Override
protected String computeTileUrl(String strTemp, int x, int y, int zoom) {
return computeTileUrlRandom(strTemp,x,y,zoom);
}
}
这个类已经可以保证合成出正确的URL。特殊情况下,比如有些网络服务的服务器地址是根据某些算法计算得出,这个类就不合适,需要在继续派生新类,并重写一些相应的函数。比如对谷歌中国的数据就不能按照这种方式载入,因此,新建一个类如下
public class googleURLBuilder extends TileUrlBuilderCustom {
protected String _server;
protected String _request;
protected String _versionKey;
private String _version;
protected static String SecGoogleWord = "Galileo";
public googleURLBuilder( String _version, String _server, String _request, String _versionKey) {
super("http://%s%d.google.cn/%s/%s=%s&hl=%s&x={x}%s&y={y}&z={z}&s=%s","");
this._version = _version;
this._server = _server;
this._request = _request;
this._versionKey = _versionKey;
}
@Override
protected double computeTileUrlTMS(double lat) {
return lat;
}
protected String computeTileUrl(String strTemp,int x, int y, int zoom) {
String sec1="",sec2="";
int seclen = ((x * 3) + y) % 8;
sec2 = SecGoogleWord.substring(0, seclen);
if (y >= 10000 && y < 100000)
{
sec1 = "&s=";
}
return String.format(strTemp,_server, (x+2*y)% 4, _request,_versionKey,_version,"zh-CN", sec1, sec2);
}
}
只要实例化不同的TileUrlBuilder派生类,即可实现worldwind java对不同数据源的数据载入。
必应地图
TileUrlBuilder tb = new TileUrlBuilderCustomRandomServer("http://t{s}.tiles.virtualearth.net/tiles/a{quadkey}.jpg?g=517&token=soep","01234567");
高德地图
TileUrlBuilder tb = new TileUrlBuilderCustomRandomServer("http://webrd0{s}.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scale=1&style=7","1234");
谷歌卫星图
TileUrlBuilder tb = new new googleURLBuilder("190","khm","kh","v");
OSM地图
TileUrlBuilder tb = new TileUrlBuilderCustomRandomServer("http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png","abc")
把生成的TileUrlBuilder用makeLevels来构建一个LevelSet,即可实现不同种类数据的载入。
类似地,可以载入各个数据提供商(谷歌、必应、osm、阿里云……)的普通地图、卫星图、矢量图、地形图……