Android加载arcgis在线和离线底图过程中坐标系不同的解

2023-02-17  本文已影响0人  南太湖小蚂蚁

最近在工作中遇到一个问题,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年的底图

可以看到都定位到同样的地方和同样的缩放级别,不过由于我离线底图在此级别下没有注记,所以前两幅图看不到注记,在在线底图中可以看到注记。

上一篇下一篇

猜你喜欢

热点阅读