Android加载arcgis在线和离线底图过程中坐标系不同的解
最近在工作中遇到一个问题,Android中需要加载离线底图TPK和在线底图,在不同的底图间可以自由切换。做过GIS系统开发的同学都知道,如果在不同底图间切换,我们一般是需要保证之前的操作定位和缩放级别的,也就是在前一个底图里面定位到哪里,在下一个底图中也要定位到哪里。但是由于这两套底图的空间坐标系不同,因此如果我在加载的离线底图中操作,定位到某个位置,缩放到某个级别后,再切换到在线底图后,会无法显示。为了解决这个问题,我尝试了一种办法。下面记录一下我的解决过程。
首先,我定义了一个spinner,下拉框中代表了不同的底图,当点击的时候可以切换到相应的底图。我这里2021年,2018年,2016年,2014年的影像底图都是离线的,但是2022年的影像是在线的。
我自定义了一个spinner下拉框,并把数据塞到了spinner中。
List<String> tpk_list = new ArrayList<String>();
List<String> tpkNameList = new ArrayList();
tpk_list.add("2021年影像");tpkNameList.add(getString(R.string.IMAGE_2021));
tpk_list.add("2018年影像");tpkNameList.add(getString(R.string.IMAGE_2018));
tpk_list.add("2016年影像");tpkNameList.add(getString(R.string.IMAGE_2016));
tpk_list.add("2014年影像");tpkNameList.add(getString(R.string.IMAGE_2014));
tpk_list.add("2022年影像(在线)");tpkNameList.add("");
MySpinner tpkSwitchSpinner = findViewById(R.id.switch_spinner);
tpkSwitchSpinner.setData(tpk_list);
当点击每个底图后,实现底图的切换,下面是离线底图的切换方法,如果不考虑坐标系的不同,这种方法是可行的。也就是切换之前先获取到当前mapview的viewpoint,然后在切换底图后,再把viewpoint设置到新的mapview中。如下代码就可以实现这个功能:
tpkSwitchSpinner.setOnItemSelectedListener(new MySpinner.OnItemSelectedListener() {
@Override
public void onItemSelected(int pos) {
originViewpoint = leftMapView.getCurrentViewpoint(Viewpoint.Type.CENTER_AND_SCALE);
switchToOfflineMap(tpkNameList.get(pos));
}
});
private void switchToOfflineMap(String tpkName) {
String path = Const.FILEPATH + "/TPK/"+tpkName;
Log.d(TAG, path);
List<Layer> baseLayers = new ArrayList<>();
TileCache tileCache = new TileCache(path);
ArcGISTiledLayer arcGISTiledLayer = new ArcGISTiledLayer(tileCache);
String bzTPKpath = Const.FILEPATH + "/TPK/BZ.tpk";
TileCache bzTpkCache = new TileCache(bzTPKpath);
mArcGISTiledLayer = new ArcGISTiledLayer(bzTpkCache);
baseLayers.add(arcGISTiledLayer);
baseLayers.add(mArcGISTiledLayer);
Basemap basemap = new Basemap(baseLayers, null);
ArcGISMap map = new ArcGISMap(basemap);
map.loadAsync();
map.setBackgroundColor(Color.WHITE);
restoreOriginViewpoint();
leftMapView.setMap(map);
}
private void restoreOriginViewpoint() {
Log.d(TAG, "switchToOtherTpk: " + originViewpoint);
leftMapView.setViewpointAsync(originViewpoint);
}
如果仅仅是离线底图,没有问题,因为这些TPK都是同一套坐标系的,可如果加载在线底图后,坐标系很可能是有差别的,因此需要分别进行处理。
tpkSwitchSpinner.setOnItemSelectedListener(new MySpinner.OnItemSelectedListener() {
@Override
public void onItemSelected(int pos) {
originViewpoint = leftMapView.getCurrentViewpoint(Viewpoint.Type.CENTER_AND_SCALE);
Log.d(TAG, originViewpoint.getTargetGeometry().getExtent().toString());
Log.d(TAG, String.valueOf(leftMapView.getMap().getSpatialReference().getWkid()));
Log.d(TAG, String.valueOf(originViewpoint.getTargetGeometry().getSpatialReference().getWkid()));
if(tpk_list.get(pos).contains("在线")) {
// 在线底图加载
}else {
System.out.println("--------------离线");
originViewpoint = change(originViewpoint, false);
Log.d(TAG, originViewpoint.getTargetGeometry().getExtent().toString());
switchToOfflineMap(tpkNameList.get(pos));
}
}
});
为了保持离线底图的操作点位和缩放级别,切换到在线底图时可以把viewpoint保存下来,把mapview中的map控件的初始viewpoint设置成上一个viewpoint即可。在坐标系相同的情况下是可以这么做的,不过如果坐标系不同的话,上一个viewpoint中的坐标值并不适用于下一个底图的viewpoint。因此,这里可以通过转换viewpoint的坐标系来解决:
首先获取到viewpoint的extent以及当前的缩放级别,并基于extent的坐标值以及当前坐标系,构建一个Point对象。这里envelope的xmin和xmax,ymin和ymax是一样的,因为这里的extent本身是一个点。
Envelope envelope = viewPoint.getTargetGeometry().getExtent();
double scale = viewPoint.getTargetScale();
System.out.println("target extent------------"+envelope.toString());
Point p = new Point(envelope.getXMin(), envelope.getYMin(), leftMapView.getSpatialReference());
接下来,将这个Point对象转换成需要切换的底图的坐标系,并保持缩放级别。这里,我的在线底图空间坐标系的WKID是4490的,也就是2000坐标系的,离线底图是墨卡托坐标系的。采用arcgis for android api,GeometryEngine的project方法转换后,返回一个新的viewpoint。
if(isOnline) {
Point point=(Point) GeometryEngine.project(p, SpatialReference.create(4490));
return new Viewpoint(point, scale);
}else {
Point point=(Point) GeometryEngine.project(p, SpatialReferences.getWebMercator());
return new Viewpoint(point, scale);
}
最后在新的底图中,使用setInitialViewpoint设置为新的viewpoint,就可以实现底图的正常切换了。完整的转换代码如下:
// 转换Viewpoint坐标系
private Viewpoint change(Viewpoint viewPoint, boolean isOnline) {
Envelope envelope = viewPoint.getTargetGeometry().getExtent();
double scale = viewPoint.getTargetScale();
System.out.println("target extent------------"+envelope.toString());
Point p = new Point(envelope.getXMin(), envelope.getYMin(), leftMapView.getSpatialReference());
if(isOnline) {
Point point=(Point) GeometryEngine.project(p, SpatialReference.create(4490));
return new Viewpoint(point, scale);
}else {
Point point=(Point) GeometryEngine.project(p, SpatialReferences.getWebMercator());
return new Viewpoint(point, scale);
}
}
离线底图切换代码如下:
private void switchToOfflineMap(String tpkName) {
String path = Const.FILEPATH + "/TPK/"+tpkName;
Log.d(TAG, path);
List<Layer> baseLayers = new ArrayList<>();
TileCache tileCache = new TileCache(path);
ArcGISTiledLayer arcGISTiledLayer = new ArcGISTiledLayer(tileCache);
String bzTPKpath = Const.FILEPATH + "/TPK/BZ.tpk";
TileCache bzTpkCache = new TileCache(bzTPKpath);
mArcGISTiledLayer = new ArcGISTiledLayer(bzTpkCache);
baseLayers.add(arcGISTiledLayer);
baseLayers.add(mArcGISTiledLayer);
Basemap basemap = new Basemap(baseLayers, null);
ArcGISMap map = new ArcGISMap(basemap);
map.setBackgroundColor(Color.WHITE);
map.setInitialViewpoint(originViewpoint);
leftMapView.setMap(map);
}
spinner的点击事件代码如下:
tpkSwitchSpinner.setOnItemSelectedListener(new MySpinner.OnItemSelectedListener() {
@Override
public void onItemSelected(int pos) {
originViewpoint = leftMapView.getCurrentViewpoint(Viewpoint.Type.CENTER_AND_SCALE);
Log.d(TAG, originViewpoint.getTargetGeometry().getExtent().toString());
Log.d(TAG, String.valueOf(leftMapView.getMap().getSpatialReference().getWkid()));
Log.d(TAG, String.valueOf(originViewpoint.getTargetGeometry().getSpatialReference().getWkid()));
if(tpk_list.get(pos).contains("在线")) {
System.out.println("--------------在线");
originViewpoint = change(originViewpoint, true);
Log.d(TAG, originViewpoint.getTargetGeometry().getExtent().toString());
List<Layer> baseLayers = new ArrayList<>();
// 在线获取底图
String url = "http://<img_ip>/MapServer"; // 影像地址
ArcGISTiledLayer arcGISTiledLayer = new ArcGISTiledLayer(url);
baseLayers.add(arcGISTiledLayer);
String bzUrl = "http://<bz_ip>/MapServer"; // 影像注记地址
ArcGISTiledLayer bzTiledLayer = new ArcGISTiledLayer(bzUrl);
baseLayers.add(bzTiledLayer);
Basemap basemap = new Basemap(baseLayers, null);
ArcGISMap arcGISMap = new ArcGISMap(basemap);
arcGISMap.setBackgroundColor(Color.WHITE);
arcGISMap.setInitialViewpoint(originViewpoint);
leftMapView.setMap(arcGISMap);
}else {
System.out.println("--------------离线");
originViewpoint = change(originViewpoint, false);
Log.d(TAG, originViewpoint.getTargetGeometry().getExtent().toString());
switchToOfflineMap(tpkNameList.get(pos));
}
}
});
}
下面看一下效果。我加载2021年的离线底图,选择南京市玄武湖景区,效果如下:
此时,我切换到2018年的底图
最后,我再切换到在线的2022年的底图
可以看到都定位到同样的地方和同样的缩放级别,不过由于我离线底图在此级别下没有注记,所以前两幅图看不到注记,在在线底图中可以看到注记。