基础底图
移动地图程序的基础,在于“一张图”,外业调绘、导航类、成果展示、统计应用等都需要一张基本的底图来支撑。往往底图的好坏决定了整个移动地图的根基。
加载在线基础底图
在线基础底图包括传统GIS(ArcGIS Server)和WebGIS(Online & Portal)。使用方式上确实不同,传统GIS是通过Layer直接对接地图服务(ArcGIS Server发布的)。WebGIS是同过ArcGISMap来对接WebMap。
1.传统GIS
对于一直使用ArcGIS Server发布地图服务的,可以直接使用地图服务(Layer)来加载在线的地图服务(ArcGIS Server)。
// 在线地图网址
// String web_url = "http://map.geoq.cn/ArcGIS/rest/services/ChinaOnlineCommunity/MapServer";
String url = "http://map.geoq.cn/arcgis/rest/services/ChinaOnlineCommunity/MapServer";
ArcGISTiledLayer arcGISTiledLayer = new ArcGISTiledLayer(url);
Basemap basemap = new Basemap(arcGISTiledLayer);
ArcGISMap arcGISMap = new ArcGISMap(basemap);
mMapView.setMap(arcGISMap);
// 长春 - 初始化范围(仅仅是本图层)
Envelope targetExtent = new Envelope(13909984.0, 5437387.0, 13986734.0, 5458866.0,
SpatialReferences.getWebMercator());
Viewpoint initViewpoint = new Viewpoint(targetExtent);
mMapView.getMap().setInitialViewpoint(initViewpoint);
/*
// 设置初始化范围(tpk图层)
Envelope targetExtent = new Envelope(-302859.47,-183912.51,349580.85,261109.45,SpatialReference.create(21481));
Viewpoint initViewpoint = new Viewpoint(targetExtent);
mapView.getMap().setInitialViewpoint(initViewpoint);
*/
2.WebGIS(Online & Portal)
通过Online或者Portal可以便捷快速的制作出更简洁智能的地图资源,用以表达我们的目的,对于移动端而言这些Web Map可以直接应用。
String url = "http://www.arcgis.com/home/webmap/viewer.html?webmap=55c1665bcd064552944a9e8296271ec3";
ArcGISMap arcGISMap = new ArcGISMap(url);
mMapView.setMap(arcGISMap);
Basemap basemap = arcGISMap.getBasemap(); //获取底图
LayerList operationalLayers = arcGISMap.getOperationalLayers(); //获取业务图层
3.在线矢量切片:ArcGISVectorTiledLayer
String url = "https://www.arcgis.com/home/item.html?id=e19e9330bf08490ca8353d76b5e2e658";
ArcGISVectorTiledLayer arcGISVectorTiledLayer = new ArcGISVectorTiledLayer(url);
Basemap basemap = new Basemap(arcGISVectorTiledLayer);
ArcGISMap arcGISMap = new ArcGISMap(basemap);
mMapView.setMap(arcGISMap);
Viewpoint vp = new Viewpoint(47.606726, -122.335564, 72223.819286);
arcGISMap.setInitialViewpoint(vp);
加载离线基础底图
离线基础底图最传统的方式是直接拷贝ArcGIS Server服务的切片成果,存在的问题是无论使用松散型还是紧凑型都包含太多碎小文件,部署不便。
TPK文件便是为了解决多碎小文件问题。但是依然存在文件太大的问题,动辄十G甚至几十G。而矢量切片(VTPK)在于解决文件太大的问题。
1.TPK
String map_path = null;
// 加载tpk地图(大于api 24---android 7)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N){
map_path =getExternalFilesDir("/TPK/dlst.tpk").getPath();
} else {
map_path = Environment.getExternalStorageDirectory().getAbsolutePath() +
"/TPK/dlst.tpk";
}
TileCache titleCache = new TileCache(map_path);
ArcGISTiledLayer arcGISTiledLayer = new ArcGISTiledLayer(titleCache);
Basemap basemap = new Basemap(arcGISTiledLayer);
ArcGISMap arcGISMap = new ArcGISMap(basemap);
mMapView.setMap(arcGISMap);
加载多个TPK数据:
// TPK 底图
String map_path1 = null;
String map_path2 = null;
// 加载tpk地图(大于api 24---android 7)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N){
map_path1 =getExternalFilesDir("/TPK/2021.tpk").getPath();
map_path2 =getExternalFilesDir("/TPK/lw.tpk").getPath();
} else {
map_path1 = Environment.getExternalStorageDirectory().getAbsolutePath() + "/TPK/2021.tpk";
map_path2 =getExternalFilesDir("/TPK/lw.tpk").getPath();
}
TileCache titleCache1 = new TileCache(map_path1);
TileCache titleCache2 = new TileCache(map_path2);
// 创建Layer列表,用来存放多个tpk数据
List<Layer> baseLayers = new ArrayList<>();
ArcGISTiledLayer arcGISTiledLayer1 = new ArcGISTiledLayer(titleCache1);
ArcGISTiledLayer arcGISTiledLayer2 = new ArcGISTiledLayer(titleCache2);
baseLayers.add(arcGISTiledLayer1);
baseLayers.add(arcGISTiledLayer2);
Basemap basemap= new Basemap(baseLayers,null);
ArcGISMap arcGISMap = new ArcGISMap();
arcGISMap.setBasemap(basemap);
// 设置最小比例尺
arcGISMap.setMinScale(1500000);
// 设置最大比例尺
arcGISMap.setMaxScale(5000);
mapView.setMap(arcGISMap);
2.矢量切片:VTPK
String path = "/sdcard/Hymn/basemap/dzzhdjfb.vtpk";
ArcGISVectorTiledLayer mainArcGISVectorTiledLayer = new ArcGISVectorTiledLayer(path);
Basemap mainBasemap = new Basemap(mainArcGISVectorTiledLayer);
ArcGISMap mainArcGISMap = new ArcGISMap(mainBasemap);
mMapView.setMap(mainArcGISMap);
3.MMPK(Basemap)
通过ArcGIS Pro可以制作包含基础底图(Basemap)的MMPK,MMPK文件解析后,基础底图(Basemap)中的图层会解析为MobileBasemapLayer,只提供浏览功能。当然,亚洲字符的支持情况在安卓端不是特别好。
String path = "/sdcard/Hymn/basemap/MobileBasemapLayer.mmpk";
final MobileMapPackage mobileMapPackage = new MobileMapPackage(path);
mobileMapPackage.loadAsync();
mobileMapPackage.addDoneLoadingListener(new Runnable() {
@Override
public void run() {
LoadStatus loadStatus = mobileMapPackage.getLoadStatus();
if (loadStatus == LoadStatus.LOADED) {
List<ArcGISMap> maps = mobileMapPackage.getMaps();
ArcGISMap arcGISMap = maps.get(0);
Basemap basemap = arcGISMap.getBasemap();
LayerList operationalLayers = arcGISMap.getOperationalLayers();
mMapView.setMap(arcGISMap);
}
}
});
底图的切换
在Runtime100里,MapView是通过ArcGISMap类来完成图层的管理。
ArcGISMap类是将底图和业务图层分开的,对于底图,ArcGISMap里用了Baemap类来进行管理。
上面已经介绍了加载底图,现在就讲讲底图的切换。
如果我们要切换底图时候,仅需要给ArcGISMap类重新赋值一个底图即可。
Basemap basemap = new Basemap(layer);
arcGISMap.setBasemap(basemap);
布局:
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- The main content view -->
<FrameLayout
android:id="@+id/fl_content"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.esri.arcgisruntime.mapping.view.MapView
android:id="@+id/mapview"
android:layout_width="match_parent"
android:layout_height="match_parent"></com.esri.arcgisruntime.mapping.view.MapView>
</FrameLayout>
<!-- The navigation drawer -->
<ListView
android:id="@+id/listview"
android:layout_width="200dp"
android:layout_height="match_parent"
android:layout_gravity="start"
android:background="@android:color/background_light"
android:choiceMode="singleChoice"
android:divider="#BDBDBD"
android:dividerHeight="1dp" />
</android.support.v4.widget.DrawerLayout>
Activity:
public class ChangeBasemapActivity extends AppCompatActivity {
@BindView(R.id.mapview)
MapView mMapview;
@BindView(R.id.fl_content)
FrameLayout mFlContent;
@BindView(R.id.listview)
ListView mListview;
@BindView(R.id.drawer_layout)
DrawerLayout mDrawerLayout;
private ArcGISMap mArcGISMap;
private String[] mNavigationDrawerItemTitles;
private ActionBarDrawerToggle mDrawerToggle;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_change_basemap);
ButterKnife.bind(this);
//侧滑页面数据
mNavigationDrawerItemTitles = getResources().getStringArray(R.array.basemap_types);
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setHomeButtonEnabled(true);
getSupportActionBar().setTitle("底图切换");
}
mArcGISMap = new ArcGISMap(Basemap.Type.TOPOGRAPHIC, 47.6047381, -122.3334255, 12);
mMapview.setMap(mArcGISMap);
addDrawerItems();
setupDrawer();
}
private void addDrawerItems() {
ArrayAdapter<String> mAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1,
mNavigationDrawerItemTitles);
mListview.setAdapter(mAdapter);
mListview.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
selectBasemap(position);
}
});
}
/**
* Set up the navigation drawer
*/
private void setupDrawer() {
mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout, R.string.drawer_open, R.string.drawer_close) {
/** Called when a drawer has settled in a completely open state. */
public void onDrawerOpened(View drawerView) {
super.onDrawerOpened(drawerView);
//getSupportActionBar().setTitle(mActivityTitle);
invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu()
}
/** Called when a drawer has settled in a completely closed state. */
public void onDrawerClosed(View view) {
super.onDrawerClosed(view);
invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu()
}
};
mDrawerToggle.setDrawerIndicatorEnabled(true);
mDrawerLayout.addDrawerListener(mDrawerToggle);
}
/**
* Select the Basemap item based on position in the navigation drawer
*
* @param position order int in navigation drawer
*/
private void selectBasemap(int position) {
// update selected item and title, then close the drawer
mListview.setItemChecked(position, true);
mDrawerLayout.closeDrawer(mListview);
// if-else is used because this sample is used elsewhere as a Library module
if (position == 0) {
// position 0 = Streets
mArcGISMap.setBasemap(Basemap.createStreets());
getSupportActionBar().setTitle(mNavigationDrawerItemTitles[position]);
} else if (position == 1) {
// position 1 = Navigation Vector
mArcGISMap.setBasemap(Basemap.createNavigationVector());
getSupportActionBar().setTitle(mNavigationDrawerItemTitles[position]);
} else if (position == 2) {
// position 2 = Topographic
mArcGISMap.setBasemap(Basemap.createTopographic());
getSupportActionBar().setTitle(mNavigationDrawerItemTitles[position]);
} else if (position == 3) {
// position 3 = Topographic Vector
mArcGISMap.setBasemap(Basemap.createTopographicVector());
getSupportActionBar().setTitle(mNavigationDrawerItemTitles[position]);
} else if (position == 4) {
// position 3 = Gray Canvas
mArcGISMap.setBasemap(Basemap.createLightGrayCanvas());
getSupportActionBar().setTitle(mNavigationDrawerItemTitles[position]);
} else if (position == 5) {
// position 3 = Gray Canvas Vector
mArcGISMap.setBasemap(Basemap.createLightGrayCanvasVector());
getSupportActionBar().setTitle(mNavigationDrawerItemTitles[position]);
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Activate the navigation drawer toggle
return (mDrawerToggle.onOptionsItemSelected(item)) || super.onOptionsItemSelected(item);
}
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
mDrawerToggle.syncState();
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
mDrawerToggle.onConfigurationChanged(newConfig);
}
@Override
protected void onResume() {
super.onResume();
mMapview.resume();
}
@Override
protected void onPause() {
super.onPause();
mMapview.pause();
}
@Override
protected void onDestroy() {
super.onDestroy();
mMapview.dispose();
}
}
遍历mapview上加载的基础图层(.tpk、.tpkx)
// 获取地图上加载基础图层(底图)
LayerList layers = mapView.getMap().getBasemap().getBaseLayers();
// 遍历layers
for (Layer layer:layers) {
if (layer instanceof ArcGISTiledLayer) {
ArcGISTiledLayer arcGISTiledLayer = (ArcGISTiledLayer) layer;
System.out.println(arcGISTiledLayer.getName());
}
}
加载三维地图
注意:最好使用arcgis-android:100.5.0及以上,否则没法加载离线xxx.slpk文件
activity_main.xml:
<!--三维地图-->
<com.esri.arcgisruntime.mapping.view.SceneView
android:id="@+id/sceneView"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent">
MainActivity.java:
mSceneView = findViewById(R.id.sceneView);
//mSceneView.setAttributionTextVisible(false); //去掉Esri logo
// 网络地图
String brest_buildings = " http://tiles.arcgis.com/tiles/P3ePLMYs2RVChkJx/arcgis/rest/services/Buildings_Brest/SceneServer";
// add a scene service to the scene for viewing buildings
ArcGISSceneLayer sceneLayer = new ArcGISSceneLayer(brest_buildings);
ArcGISScene scene = new ArcGISScene();
scene.setBasemap(Basemap.createImagery());
scene.getOperationalLayers().add(sceneLayer);
mSceneView.setScene(scene);
// 本地地图
mSceneView = findViewById(R.id.sceneView);
ArcGISScene scene = new ArcGISScene(Basemap.createImagery());
mSceneView.setScene(scene);
// 路径 \Android\data\com.chy.a2dand3d\files\ThreedData\xxx.slpk
final IntegratedMeshLayer gironaIntegratedMeshLayer = new IntegratedMeshLayer(
getExternalFilesDir("ThreedData") + getString(R.string.meshlayer));
scene.getOperationalLayers().add(gironaIntegratedMeshLayer);
gironaIntegratedMeshLayer.addDoneLoadingListener(new Runnable() {
@Override
public void run() {
mSceneView.setViewpointAsync(new Viewpoint(gironaIntegratedMeshLayer.getFullExtent()));
}
});
设置三维场景视角镜头:
/**
* 设置三维场景视角镜头(camera)
* @parm latitude 纬度
* @parm longitude 经度
* @parm altitude 海拔高度
* @parm heading 镜头水平朝向 - 0度表示指北,从0度逐渐增加,镜头顺时针旋转,360度回到0度
指北
* @parm pitch 镜头垂直朝向 - 0度表示垂直俯视地球,从0度逐渐增加,镜头沿其水平朝向,从俯
视地球朝天空旋转,360度回到0度俯视地球
* @parm roll 旋转
* */
Camera camera = new Camera(43.88, 125.35, 200, 0, 0, 0);
mSceneView.setViewpointCamera(camera);
控制三维垂直视角在一定角度内:
/**
* 三维地图垂直视角监听方法
* */
private void sceneViewListener(){
mSceneView.setOnTouchListener(new DefaultSceneViewOnTouchListener(mSceneView){
private float touchY=0f;// 触摸屏幕的y值坐标
private float currentY=0f;// 当前手指滑动y值坐标
@Override
public boolean onTouch(View view,MotionEvent motionEvent) {
if (motionEvent.getAction() == MotionEvent.ACTION_DOWN){
touchY = motionEvent.getY();
}
return super.onTouch(view,motionEvent);
}
@Override
public boolean onTwoPointerPitch(MotionEvent motionEvent,double pitchDelta) {
// 获取当前滑动的y值坐标
if (motionEvent.getAction() == MotionEvent.ACTION_MOVE){
currentY = motionEvent.getY();
}
// 求差值
float dirDis = touchY - currentY;
// 向上滑动
if (dirDis > 0 && Math.abs(dirDis) >= 20){
// 获取当前三维地图Camera
Camera camera = mSceneView.getCurrentViewpointCamera();
// 垂直视角角度大于等于80禁止继续扩大角度
if (camera.getPitch() >= 80){
return false;
}
}
// 向下滑动
if (dirDis <= 0 && Math.abs(dirDis) >= 20){
// 开启垂直视角角度变化
return super.onTwoPointerPitch(motionEvent,pitchDelta);
}
return super.onTwoPointerPitch(motionEvent,pitchDelta);
}
});
}