MapBox定位与离线地图下载Android

36 篇文章 13 订阅

因为业务需要,在Android实现Mapbox离线地图下载和实时定位,仿照着官方的例子实现,网上的demo也比较少,

主要代码

如下所示:


public class MainActivity extends MapActicity implements OnLocationClickListener, PermissionsListener, OnCameraTrackingChangedListener {
    private static final String TAG = "地图下载";
    public static final String JSON_CHARSET = "UTF-8";
    public static final String JSON_FIELD_REGION_NAME = "FIELD_REGION_NAME";

    private NumberProgressBar progressBar;
    private MapboxMap map;

    private boolean isEndNotified;
    private int regionSelected;

    private int count_complete_err=0;

    // Offline objects
    private OfflineManager offlineManager;
    private OfflineRegion offlineRegion;

    private Button downloadButton;
    private Button listButton;

    private LocationComponent locationComponent;
    private PermissionsManager permissionsManager;
    private boolean isLocating = false;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mapView = findViewById(R.id.mapView);
        mapView.onCreate(savedInstanceState);
        mapView.getMapAsync(new OnMapReadyCallback() {
            @Override
            public void onMapReady(@NonNull MapboxMap mapboxMap) {
                map = mapboxMap;
                progressBar = findViewById(R.id.progress_bar);
                offlineManager = OfflineManager.getInstance(MainActivity.this);

                downloadButton = findViewById(R.id.down);
                listButton = findViewById(R.id.list);
                downloadButton.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        downloadRegionDialog();
                    }
                });


                listButton.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        downLoadMap();
                    }
                });

                map.setStyle(new Style.Builder().fromUrl(getString(R.string.style_hubei)), new Style.OnStyleLoaded() {
                    @Override
                    public void onStyleLoaded(@NonNull Style style) {
//                      map.getStyle().getLayer("ss").setProperties(PropertyFactory.);
                    }
                });

            }
        });
    }

    /**
     * 输入离线地图区域名称
     */
    private void downloadRegionDialog() {
        AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);

        final EditText regionNameEdit = new EditText(MainActivity.this);
        regionNameEdit.setHint(getString(R.string.set_region_name_hint));

        builder.setTitle(getString(R.string.dialog_title))
                .setView(regionNameEdit)
                .setMessage(getString(R.string.dialog_message))
                .setPositiveButton(getString(R.string.dialog_positive_button), new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        String regionName = regionNameEdit.getText().toString();
                        if (regionName.length() == 0) {
                            Toast.makeText(MainActivity.this, getString(R.string.dialog_toast), Toast.LENGTH_SHORT).show();
                        } else {
                            downloadRegion(regionName);
                        }
                    }
                })
                .setNegativeButton(getString(R.string.dialog_negative_button), new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        dialog.cancel();
                    }
                });

        builder.show();
    }

    /**
     * 下载当前视图范围内地图的离线地图
     *
     * @param regionName
     */
    private void downloadRegion(final String regionName) {

        startProgress();
        String styleUrl = map.getStyle().getUrl();
        //获取当前视图范围,地图下载范围
        LatLngBounds bounds = map.getProjection().getVisibleRegion().latLngBounds;
        //下载多少级之间的瓦片
        double minZoom = map.getCameraPosition().zoom;
//        double maxZoom = map.getMaxZoomLevel();
        double maxZoom = minZoom + 3;
        float pixelRatio = this.getResources().getDisplayMetrics().density;
        OfflineTilePyramidRegionDefinition definition = new OfflineTilePyramidRegionDefinition(
                styleUrl, bounds, minZoom, maxZoom, pixelRatio);


        byte[] metadata;
        try {
            JSONObject jsonObject = new JSONObject();
            jsonObject.put(JSON_FIELD_REGION_NAME, regionName);
            String json = jsonObject.toString();
            metadata = json.getBytes(JSON_CHARSET);
        } catch (Exception exception) {
            Timber.e("Failed to encode metadata: %s", exception.getMessage());
            metadata = null;
        }

        offlineManager.createOfflineRegion(definition, metadata, new OfflineManager.CreateOfflineRegionCallback() {
            @Override
            public void onCreate(OfflineRegion offlineRegion) {
                Timber.d("Offline region created: %s", regionName);
                MainActivity.this.offlineRegion = offlineRegion;
                launchDownload();
            }

            @Override
            public void onError(String error) {
                Timber.e("Error: %s", error);
            }
        });
    }

    /**
     * 监听下载进度
     */
    private void launchDownload() {

        offlineRegion.setObserver(new OfflineRegion.OfflineRegionObserver() {
            @Override
            public void onStatusChanged(OfflineRegionStatus status) {
// 计算百分比
                double percentage = status.getRequiredResourceCount() >= 0
                        ? (100.0 * (status.getCompletedResourceCount()+count_complete_err )/ status.getRequiredResourceCount()) :
                        0.0;

                if (status.isComplete()||percentage>=100) {
// 下载完
                    endProgress(getString(R.string.end_progress_success));
                    return;
                } else  {
                    setPercentage((int) Math.round(percentage));
                    Log.e(TAG, String.valueOf(percentage));
                }
                
            }

            @Override
            public void onError(OfflineRegionError error) {
              Log.e(TAG,"下载错误");
                Log.e(TAG,error.getMessage());
                count_complete_err++;

            }

            @Override
            public void mapboxTileCountLimitExceeded(long limit) {
                Timber.e("Mapbox tile count limit exceeded: %s", limit);
            }
        });

        // Change the region state
        offlineRegion.setDownloadState(OfflineRegion.STATE_ACTIVE);
    }

    public void downLoadMap() {
        regionSelected = 0;

// 查询离线列表
        offlineManager.listOfflineRegions(new OfflineManager.ListOfflineRegionsCallback() {
            @Override
            public void onList(final OfflineRegion[] offlineRegions) {

                if (offlineRegions == null || offlineRegions.length == 0) {
                    Toast.makeText(getApplicationContext(), getString(R.string.toast_no_regions_yet), Toast.LENGTH_SHORT).show();
                    return;
                }

// 将结果添加到列表
                ArrayList<String> offlineRegionsNames = new ArrayList<>();
                for (OfflineRegion offlineRegion : offlineRegions) {
                    offlineRegionsNames.add(getRegionName(offlineRegion));
                }
                final CharSequence[] items = offlineRegionsNames.toArray(new CharSequence[offlineRegionsNames.size()]);

//弹框
                AlertDialog dialog = new AlertDialog.Builder(MainActivity.this)
                        .setTitle(getString(R.string.navigate_title))
                        .setSingleChoiceItems(items, 0, new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {

                                regionSelected = which;
                            }
                        })
                        .setPositiveButton(getString(R.string.navigate_positive_button), new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int id) {

                                Toast.makeText(MainActivity.this, items[regionSelected], Toast.LENGTH_LONG).show();

//跳转到选中区域
                                LatLngBounds bounds = ((OfflineTilePyramidRegionDefinition)
                                        offlineRegions[regionSelected].getDefinition()).getBounds();
                                double regionZoom = ((OfflineTilePyramidRegionDefinition)
                                        offlineRegions[regionSelected].getDefinition()).getMinZoom();


                                CameraPosition cameraPosition = new CameraPosition.Builder()
                                        .target(bounds.getCenter())
                                        .zoom(regionZoom)
                                        .build();


                                map.moveCamera(CameraUpdateFactory.newCameraPosition(cameraPosition));

                            }
                        })
                        .setNeutralButton(getString(R.string.navigate_neutral_button_title), new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int id) {
//删除
                                progressBar.setProgress(0);
                                progressBar.setVisibility(View.VISIBLE);


                                offlineRegions[regionSelected].delete(new OfflineRegion.OfflineRegionDeleteCallback() {
                                    @Override
                                    public void onDelete() {

                                        progressBar.setVisibility(View.INVISIBLE);
                                        progressBar.setProgress(0);
                                        Toast.makeText(getApplicationContext(), getString(R.string.toast_region_deleted),
                                                Toast.LENGTH_LONG).show();
                                    }

                                    @Override
                                    public void onError(String error) {
                                        progressBar.setVisibility(View.INVISIBLE);
                                        progressBar.setProgress(0);

                                        Timber.e("Error: %s", error);
                                    }
                                });
                            }
                        })
                        .setNegativeButton(getString(R.string.navigate_negative_button_title), null).create();
                dialog.show();

            }

            @Override
            public void onError(String error) {
                Timber.e("Error: %s", error);
            }
        });
    }

    private String getRegionName(OfflineRegion offlineRegion) {
        String regionName;

        try {
            byte[] metadata = offlineRegion.getMetadata();
            String json = new String(metadata, JSON_CHARSET);
            JSONObject jsonObject = new JSONObject(json);
            regionName = jsonObject.getString(JSON_FIELD_REGION_NAME);
        } catch (Exception exception) {
            Timber.e("Failed to decode metadata: %s", exception.getMessage());
            regionName = "未命名";
        }
        return regionName;
    }

    // Progress bar methods
    private void startProgress() {
        count_complete_err=0;
        // Disable buttons
        downloadButton.setEnabled(false);
        listButton.setEnabled(false);

// Start and show the progress bar
        isEndNotified = false;
        progressBar.setProgress(0);
        progressBar.setVisibility(View.VISIBLE);
    }

    private void setPercentage(final int percentage) {
        progressBar.setProgress(0);
        progressBar.setProgress(percentage);
    }

    private void endProgress(final String message) {
// Don't notify more than once
        if (isEndNotified) {
            return;
        }

        // Enable buttons
        downloadButton.setEnabled(true);
        listButton.setEnabled(true);

        // Stop and hide the progress bar
        isEndNotified = true;
        progressBar.setProgress(0);
        progressBar.setVisibility(View.GONE);

// Show a toast
        Toast.makeText(MainActivity.this, message, Toast.LENGTH_LONG).show();
    }

    @Override
    public void onExplanationNeeded(List<String> permissionsToExplain) {
        Toast.makeText(this, "需要定位权限", Toast.LENGTH_LONG).show();

    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        permissionsManager.onRequestPermissionsResult(requestCode, permissions, grantResults);
    }

    @Override
    public void onPermissionResult(boolean granted) {
        if (granted) {
            map.getStyle(new Style.OnStyleLoaded() {
                @Override
                public void onStyleLoaded(@NonNull Style style) {
                    enableLocationComponent(style);
                }
            });
        } else {
            Toast.makeText(this, "获取定位权限失败", Toast.LENGTH_LONG).show();
            finish();
        }
    }

    @SuppressWarnings({"MissingPermission"})
    @Override
    public void onLocationComponentClick() {
        if (locationComponent.getLastKnownLocation() != null) {
            Toast.makeText(this,
                    "纬度" + locationComponent.getLastKnownLocation().getLatitude() + "经度" +
                            locationComponent.getLastKnownLocation().getLongitude(), Toast.LENGTH_LONG).show();
        }
    }

    @SuppressWarnings({"MissingPermission"})
    private void enableLocationComponent(@NonNull Style loadedMapStyle) {
// Check if permissions are enabled and if not request
        if (PermissionsManager.areLocationPermissionsGranted(this)) {

// Create and customize the LocationComponent's options
            LocationComponentOptions customLocationComponentOptions = LocationComponentOptions.builder(this)
                    .elevation(5)
                    .accuracyAlpha(.6f)
                    .accuracyColor(Color.RED)
                    .foregroundDrawable(android.R.drawable.ic_menu_mylocation)
                    .build();

// Get an instance of the component
            locationComponent = map.getLocationComponent();

            LocationComponentActivationOptions locationComponentActivationOptions =
                    LocationComponentActivationOptions.builder(this, loadedMapStyle)
                            .locationComponentOptions(customLocationComponentOptions)
                            .build();

            locationComponent.activateLocationComponent(locationComponentActivationOptions);

            locationComponent.setLocationComponentEnabled(true);

            locationComponent.setCameraMode(CameraMode.TRACKING);

            locationComponent.setRenderMode(RenderMode.COMPASS);

            locationComponent.addOnLocationClickListener(this);

// Add the camera tracking listener. Fires if the map camera is manually moved.
            locationComponent.addOnCameraTrackingChangedListener(this);


        } else {
            permissionsManager = new PermissionsManager(this);
            permissionsManager.requestLocationPermissions(this);
        }
    }

    @Override
    public void onCameraTrackingDismissed() {

    }

    @Override
    public void onCameraTrackingChanged(int currentMode) {

    }

    public void locate(View view) {
        Button button = (Button) view;
        if (isLocating) {
            isLocating = false;
            button.setText("定位");
            locationComponent.setLocationComponentEnabled(false);
            locationComponent.removeOnCameraTrackingChangedListener(this);
            locationComponent.removeOnLocationClickListener(this);
        } else {
            if (map.getStyle() != null) {
                button.setText("关闭定位");
                enableLocationComponent(map.getStyle());
                isLocating = true;
            }
        }

    }


}

效果图

效果如下图所示:

简要说明一下操作,点击开始下载,自动下载当前屏幕区域内的离线地图,根据下载进度,进度条自动计算,下载完成后,点击下载列表,打开离线列表,点击其中的选项,点击确定,会自动跳转到对应的离线地图区域,这时候断开网络连接,也还是继续显示的。点击定位按钮,自动跳转到当前位置。

说明

  1. Mapbox离线地图下载,会将所有图层均下载,即使是瓦片图层,它也会帮你下载瓦片。
  2. 下载瓦片失败时,失败的数量是不会计入到已经下载瓦片数量里的,所以下载的进度,计算的时候要把失败数量算上,不然永远不会到100%
  3. 全部代码地址:https://gitee.com/GISuser/offline
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

GIS开发者

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值