编程 · 2020年3月30日 0

Android Osmdroid-osmdroid

内容目录

title: Android Osmdroid
date: 2020-03-30 02:21:27.86
updated: 2020-06-08 03:35:43.852
url: https://ritoom.me/archives/osmdroid
categories:

  • Code
    tags:
  • android

Osmdroid地图框架

osmdroid是对Android的MapView的开源替代品,由于Android自带的MapView需要Google提供相关支持,因此采用该框架库去完成加载离线地图与相关地图标绘等操作.

osmdroid支持在线与离线地图包,支持以下

  • osmdroid’s flavor of a sqlite database (recommended)
  • osmdroid ZIP
  • MBTiles
  • GEMF
  • Mapsforge
  • GeoPackage (still under development)

类型的存储格式.

Demo应用

一. 引入osmdroid库

在demo源码app目录下的build.gradle中添加

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.osmdroid:osmdroid-android:6.1.5'
    implementation 'androidx.preference:preference:1.1.0'
}

注: osmdroid最低支持android版本为8,及Android2.2系统

二. 添加所需权限

在线模式下的地图所需权限

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"  />

离线模式下的地图所需权限

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

如需获取位置信息,需添加一下权限

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>

三. 页面设置

在demo中的layout页面目录下的page2_fragment.xml中添加osmdroid重写的MapView控件

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".page2.Page2Fragment">
<!--    MapView控件-->
    <org.osmdroid.views.MapView android:id="@+id/map"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" />

</FrameLayout>

对该控件绑定ID

android:id="@+id/map"

三. 控制类绑定初始化地图控件

在demo该页面的onCreateView方法中,初始化MapView变量,装载配置

private MapView map = null;
Configuration.getInstance().load(this, PreferenceManager.getDefaultSharedPreferences(this));
map = view.findViewById(R.id.map);

四. 操作地图

设置地图源

在线地图
map.setTileSource(TileSourceFactory.MAPNIK);
离线地图
map.setTileSource(new XYTileSource(
                "4uMaps",
                1,
                15,
                256,
                ".png",
                new String[]{
                        "http://tileserver.4umaps.eu/"
                }));

离线地图需预先放置在Android设备文件目录中的osmdroid目录下

在demo中使用的是osmdroid ZIP格式的离线地图,zip压缩包中的文件夹名称及XYTileSource构造函数的第一个变量,其他变量分别对应

final int aZoomMinLevel //地图最小等级
final int aZoomMaxLevel //地图最大等级
final int aTileSizePixels //Tile尺寸像素
final String aImageFilenameEnding //地图切片文件后缀名
final String[] aBaseUrl //基础url

注: 当使用离线地图时,使用 map.setUseDataConnection(false) 设置不要连接在线数据,以免对离线地图造成影响.

地图基本属性修改

地图设置中心点与地图初始化等级

地图设置需要使用Controller去控制地图元素

通过setZoom方法,设置地图初始化等级

通过GeoPoint新建一个点,再将该点设置为中心点

IMapController mapController = map.getController();
mapController.setZoom(12.5);
GeoPoint startPoint = new GeoPoint(36.6619, 117.020291);
mapController.setCenter(startPoint);

地图设置手势操作

mRotationGestureOverlay = new RotationGestureOverlay(this.getContext(), map);
mRotationGestureOverlay.setEnabled(true);
map.setMultiTouchControls(true);
map.getOverlays().add(this.mRotationGestureOverlay);

地图添加图标

Marker类是osmdroid用来处理图标相关业务的类,可以通过Marker设置图标等各种样式

Marker startMarker = new Marker(map);
startMarker.setIcon(getResources().getDrawable(R.drawable.ic_place_black_24dp));
startMarker.setPosition(startPoint);
startMarker.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM);
map.getOverlays().add(startMarker);

添加地图事件

地图事件主要包括单机事件与长按事件,新建MapEventsReceiver类后重新其中的方法即可

MapEventsReceiver mapEventsReceiver = new MapEventsReceiver() {
    @Override
    public boolean singleTapConfirmedHelper(GeoPoint p) {
        Marker newMarker = new Marker(map);
        newMarker.setPosition(p);
        map.getOverlays().add(newMarker);
        return false;
    }

    @Override
    public boolean longPressHelper(GeoPoint p) {
        return false;
    }
};
map.getOverlays().add(new MapEventsOverlay(mapEventsReceiver));

在地图添加图片按钮控件

因为整个页面使用FrameLayout布局,控件如果不刻意去设置,会自动漂浮在页面左上角.

  1. 页面添加控件
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".page2.Page2Fragment">

<!--    MapView控件-->
    <org.osmdroid.views.MapView android:id="@+id/map"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" />
<!--    图片按钮控件-->
    <ImageButton
        android:id="@+id/imageButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@android:drawable/ic_menu_edit" />

</FrameLayout>
  1. 控制器类初始化该控件,并添加事件
drawButton = view.findViewById(R.id.imageButton);

drawButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        if (buttenStatus){
            mRotationGestureOverlay.setEnabled(false);
            map.setMultiTouchControls(false);
            buttenStatus = false;
        }else {
            mRotationGestureOverlay.setEnabled(true);
            map.setMultiTouchControls(true);
            map.setClickable(true);
            buttenStatus = true;
        }

    }
});

绘制地图

在osmdroid的基本绘制方法中,自身具有添加标点/画线/画圆的API,但是我所需的应用场景是不是通过程序预置好的经纬度来添加图标,而是通过用户在地图视图上进行触摸标绘,因此需要将标绘的方法进行逻辑处理.

通过参考osmdroid的demo,发现demo中是通过自定义View,使用View的onTouchEvent方法监控用户的触摸事件,onDraw方法绘制页面.

以下为demo代码举例

@Override
public boolean onTouchEvent(MotionEvent event) {
    float x = event.getX();
    float y = event.getY();
    pts.add(new Point((int)x,(int)y));
    switch (event.getAction()){
        case MotionEvent.ACTION_DOWN://用户按下事件
            touch_start(x,y);
            invalidate();
            break;
        case MotionEvent.ACTION_MOVE://用户触摸移动事件
            touch_move(x,y);
            invalidate();
            break;
        case MotionEvent.ACTION_UP://用户松开事件
            touch_up(x,y);
            invalidate();
            break;
    }
    return true;
}

@Override
protected void onDraw(Canvas canvas) {
    canvas.drawPath(mPath,mPaint);// 传入绘制的路径与样式参数
}

touch_start()与touch_move()方法负责视图绘制,touch_up()方法则将绘制后相关数据调用osmdroid中的标绘方法将图形添加到视图上.

private void touch_up(float x,float y){
    mPath.reset();
    canvas.drawPath(mPath,mPaint);
    mPath.reset();
    if (map != null){
        Projection projection = map.getProjection();
        ArrayList<GeoPoint> geoPoints = new ArrayList<>();
        final Point unrotatedPoint = new Point();
        for (int i=0; i < pts.size(); i++) {
            projection.unrotateAndScalePoint(pts.get(i).x, pts.get(i).y, unrotatedPoint);
            GeoPoint iGeoPoint = (GeoPoint) projection.fromPixels(unrotatedPoint.x, unrotatedPoint.y);
            geoPoints.add(iGeoPoint);
        }

        if (geoPoints.size() > 2){
            final int color = Color.argb(100, 100, 100, 100);
            final Polyline line = new Polyline(map);
            line.setInfoWindow(
                    new BasicInfoWindow(org.osmdroid.library.R.layout.bonuspack_bubble, map));
            line.getOutlinePaint().setColor(color);
            line.setPoints(geoPoints);
            line.showInfoWindow();
            line.getOutlinePaint().setStrokeCap(Paint.Cap.ROUND);

            final Paint arrowPaint = new Paint();
            arrowPaint.setColor(color);
            arrowPaint.setStrokeWidth(10.0f);
            arrowPaint.setStyle(Paint.Style.FILL_AND_STROKE);
            arrowPaint.setAntiAlias(true);
            final Path arrowPath = new Path(); // a simple arrow towards the right
            arrowPath.moveTo(- 10, - 10);
            arrowPath.lineTo(10, 0);
            arrowPath.lineTo(- 10, 10);
            arrowPath.close();
            map.getOverlayManager().add(line);
        }
    }

    pts.clear();
}