com.nextgis.mobile.map.LocalTMSLayer.java Source code

Java tutorial

Introduction

Here is the source code for com.nextgis.mobile.map.LocalTMSLayer.java

Source

/******************************************************************************
 * Project:  NextGIS mobile
 * Purpose:  Mobile GIS for Android.
 * Author:   Dmitry Baryshnikov (aka Bishop), polimax@mail.ru
 ******************************************************************************
 *   Copyright (C) 2014 NextGIS
 *
 *    This program is free software: you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation, either version 2 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 ****************************************************************************/
package com.nextgis.mobile.map;

import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;

import com.nextgis.mobile.R;
import com.nextgis.mobile.datasource.TileCacheLevelDescItem;
import com.nextgis.mobile.datasource.TileItem;
import com.nextgis.mobile.util.FileUtil;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import static com.nextgis.mobile.util.Constants.BUNDLE_HASERROR_KEY;
import static com.nextgis.mobile.util.Constants.BUNDLE_MSG_KEY;
import static com.nextgis.mobile.util.Constants.BUNDLE_PATH_KEY;
import static com.nextgis.mobile.util.Constants.BUNDLE_TYPE_KEY;
import static com.nextgis.mobile.util.Constants.JSON_LEVELS_KEY;
import static com.nextgis.mobile.util.Constants.JSON_LEVEL_KEY;
import static com.nextgis.mobile.util.Constants.JSON_MAXLEVEL_KEY;
import static com.nextgis.mobile.util.Constants.JSON_MAXX_KEY;
import static com.nextgis.mobile.util.Constants.JSON_MAXY_KEY;
import static com.nextgis.mobile.util.Constants.JSON_MINLEVEL_KEY;
import static com.nextgis.mobile.util.Constants.JSON_MINX_KEY;
import static com.nextgis.mobile.util.Constants.JSON_MINY_KEY;
import static com.nextgis.mobile.util.Constants.JSON_NAME_KEY;
import static com.nextgis.mobile.util.Constants.JSON_TMSTYPE_KEY;
import static com.nextgis.mobile.util.Constants.JSON_TYPE_KEY;
import static com.nextgis.mobile.util.Constants.JSON_VISIBILITY_KEY;
import static com.nextgis.mobile.util.Constants.LAYERTYPE_LOCAL_TMS;
import static com.nextgis.mobile.util.Constants.LAYER_CONFIG;
import static com.nextgis.mobile.util.Constants.MSGTYPE_LAYER_ADDED;
import static com.nextgis.mobile.util.Constants.TAG;
import static com.nextgis.mobile.util.Constants.TILE_EXT;
import static com.nextgis.mobile.util.GeoConstants.TMSTYPE_NORMAL;
import static com.nextgis.mobile.util.GeoConstants.TMSTYPE_OSM;

public class LocalTMSLayer extends TMSLayer {
    protected Map<Integer, TileCacheLevelDescItem> mLimits;

    public LocalTMSLayer() {
        super();
        mLimits = new HashMap<Integer, TileCacheLevelDescItem>();
    }

    public LocalTMSLayer(MapBase map, File path, JSONObject config) {
        super(map, path, config);
    }

    @Override
    public Bitmap getBitmap(TileItem tile) {
        //check if present
        TileCacheLevelDescItem item = mLimits.get(tile.getZoomLevel());
        if (item != null && item.isInside(tile.getX(), tile.getY())) {
            File tilePath = new File(mPath, tile.toString("{z}/{x}/{y}.tile"));
            if (tilePath.exists())
                return BitmapFactory.decodeFile(tilePath.getAbsolutePath());
        }
        return null;
    }

    @Override
    protected void setDetails(JSONObject config) {
        super.setDetails(config);
        try {
            mLimits = new HashMap<Integer, TileCacheLevelDescItem>();
            final JSONArray jsonArray = config.getJSONArray(JSON_LEVELS_KEY);
            for (int i = 0; i < jsonArray.length(); i++) {
                JSONObject jsonLevel = jsonArray.getJSONObject(i);
                int nLevel = jsonLevel.getInt(JSON_LEVEL_KEY);
                int nMaxX = jsonLevel.getInt(JSON_MAXX_KEY);
                int nMaxY = jsonLevel.getInt(JSON_MAXY_KEY);
                int nMinX = jsonLevel.getInt(JSON_MINX_KEY);
                int nMinY = jsonLevel.getInt(JSON_MINY_KEY);

                mLimits.put(nLevel, new TileCacheLevelDescItem(nMaxX, nMinX, nMaxY, nMinY));
            }
        } catch (JSONException e) {
            reportError(e.getLocalizedMessage());
        }
    }

    @Override
    protected JSONObject getDetails() throws JSONException {
        JSONObject rootConfig = super.getDetails();
        JSONArray jsonArray = new JSONArray();
        rootConfig.put(JSON_LEVELS_KEY, jsonArray);
        int nMaxLevel = 0;
        int nMinLevel = 512;
        for (Map.Entry<Integer, TileCacheLevelDescItem> entry : mLimits.entrySet()) {
            int nLevelZ = entry.getKey();
            TileCacheLevelDescItem item = entry.getValue();
            JSONObject oJSONLevel = new JSONObject();
            oJSONLevel.put(JSON_LEVEL_KEY, nLevelZ);
            oJSONLevel.put(JSON_MAXX_KEY, item.getMaxX());
            oJSONLevel.put(JSON_MAXY_KEY, item.getMaxY());
            oJSONLevel.put(JSON_MINX_KEY, item.getMinX());
            oJSONLevel.put(JSON_MINY_KEY, item.getMinY());

            jsonArray.put(oJSONLevel);

            if (nMaxLevel < nLevelZ)
                nMaxLevel = nLevelZ;
            if (nMinLevel > nLevelZ)
                nMinLevel = nLevelZ;
        }

        rootConfig.put(JSON_MAXLEVEL_KEY, nMaxLevel);
        rootConfig.put(JSON_MINLEVEL_KEY, nMinLevel);

        return rootConfig;
    }

    @Override
    public Drawable getIcon() {
        return getContext().getResources().getDrawable(R.drawable.ic_local_tms);
    }

    @Override
    public int getType() {
        return LAYERTYPE_LOCAL_TMS;
    }

    public static void create(final MapBase map, Uri uri) {
        String sName = getFileNameByUri(map.getContext(), uri, "new layer.zip");
        sName = (String) sName.subSequence(0, sName.length() - 4);
        showPropertiesDialog(map, true, sName, TMSTYPE_OSM, uri, null);
    }

    @Override
    public void changeProperties() {
        showPropertiesDialog(mMap, false, mName, getTMSType(), null, this);
    }

    protected static void showPropertiesDialog(final MapBase map, final boolean bCreate, String layerName, int type,
            final Uri uri, final LocalTMSLayer layer) {
        final LinearLayout linearLayout = new LinearLayout(map.getContext());
        final EditText input = new EditText(map.getContext());
        input.setText(layerName);

        final ArrayAdapter<CharSequence> adapter = new ArrayAdapter<CharSequence>(map.getContext(),
                android.R.layout.simple_spinner_item);
        final Spinner spinner = new Spinner(map.getContext());
        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        spinner.setAdapter(adapter);

        adapter.add(map.getContext().getString(R.string.tmstype_qtiles));
        adapter.add(map.getContext().getString(R.string.tmstype_osm));
        adapter.add(map.getContext().getString(R.string.tmstype_normal));
        adapter.add(map.getContext().getString(R.string.tmstype_ngw));

        if (type == TMSTYPE_OSM) {
            spinner.setSelection(1);
        } else {
            spinner.setSelection(2);
        }

        final TextView stLayerName = new TextView(map.getContext());
        stLayerName.setText(map.getContext().getString(R.string.layer_name) + ":");

        final TextView stLayerType = new TextView(map.getContext());
        stLayerType.setText(map.getContext().getString(R.string.layer_type) + ":");

        linearLayout.setOrientation(LinearLayout.VERTICAL);
        linearLayout.addView(stLayerName);
        linearLayout.addView(input);
        linearLayout.addView(stLayerType);
        linearLayout.addView(spinner);

        new AlertDialog.Builder(map.getContext())
                .setTitle(bCreate ? R.string.input_layer_properties : R.string.change_layer_properties)
                //                                    .setMessage(message)
                .setView(linearLayout).setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int whichButton) {
                        int tmsType = 0;
                        switch (spinner.getSelectedItemPosition()) {
                        case 0:
                        case 1:
                            tmsType = TMSTYPE_OSM;
                            break;
                        case 2:
                        case 3:
                            tmsType = TMSTYPE_NORMAL;
                            break;
                        }

                        if (bCreate) {
                            create(map, input.getText().toString(), tmsType, uri);
                        } else {
                            layer.setName(input.getText().toString());
                            layer.setTMSType(tmsType);
                            map.onLayerChanged(layer);
                        }
                    }
                }).setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int whichButton) {
                        // Do nothing.
                        Toast.makeText(map.getContext(), R.string.error_cancel_by_user, Toast.LENGTH_SHORT).show();
                    }
                }).show();
    }

    protected static void create(final MapBase map, String layerName, int tmsType, Uri uri) {
        String sErr = map.getContext().getString(R.string.error_occurred);
        try {
            InputStream inputStream = map.getContext().getContentResolver().openInputStream(uri);
            if (inputStream != null) {
                ProgressDialog progressDialog = new ProgressDialog(map.getContext());
                progressDialog.setMessage(map.getContext().getString(R.string.message_zip_extract_progress));
                progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
                progressDialog.setCancelable(true);
                progressDialog.show();

                File outputPath = map.cretateLayerStorage();
                //create layer description file
                JSONObject oJSONRoot = new JSONObject();
                oJSONRoot.put(JSON_NAME_KEY, layerName);
                oJSONRoot.put(JSON_VISIBILITY_KEY, true);
                oJSONRoot.put(JSON_TYPE_KEY, LAYERTYPE_LOCAL_TMS);
                oJSONRoot.put(JSON_TMSTYPE_KEY, tmsType);

                new UnZipTask(map.getMapEventsHandler(), inputStream, outputPath, oJSONRoot, progressDialog)
                        .execute();
                return;
            }
        } catch (FileNotFoundException e) {
            Log.d(TAG, "Exception: " + e.getLocalizedMessage());
            sErr += ": " + e.getLocalizedMessage();
        } catch (JSONException e) {
            Log.d(TAG, "Exception: " + e.getLocalizedMessage());
            sErr += ": " + e.getLocalizedMessage();
        }
        //if we here something wrong occurred
        Toast.makeText(map.getContext(), sErr, Toast.LENGTH_SHORT).show();
    }

    protected static class UnZipTask extends AsyncTask<String, Void, Boolean> {
        protected InputStream mInputStream;
        protected ProgressDialog mProgressDialog;
        protected File mOutputPath;
        protected JSONObject mLayerConfig;
        protected Handler mEventReceiver;

        public UnZipTask(Handler eventReceiver, InputStream inputStream, File outputPath, JSONObject layerConfig,
                ProgressDialog progressDialog) {
            super();
            mInputStream = inputStream;
            mProgressDialog = progressDialog;
            mOutputPath = outputPath;
            mLayerConfig = layerConfig;
            mEventReceiver = eventReceiver;
        }

        private void unzipEntry(ZipInputStream zis, ZipEntry entry, File outputDir) throws IOException {
            String entryName = entry.getName();
            //for backward capability where the zip haz root directory named "mapnik"
            entryName = entryName.replace("Mapnik/", "");
            entryName = entryName.replace("mapnik/", "");

            //for prevent searching by media library
            entryName = entryName.replace(".png", TILE_EXT);
            entryName = entryName.replace(".jpg", TILE_EXT);
            entryName = entryName.replace(".jpeg", TILE_EXT);

            if (entry.isDirectory()) {
                FileUtil.createDir(new File(outputDir, entryName));
                return;
            }
            File outputFile = new File(outputDir, entryName);
            if (!outputFile.getParentFile().exists()) {
                FileUtil.createDir(outputFile.getParentFile());
            }

            FileOutputStream fout = new FileOutputStream(outputFile);
            int nCount;
            byte[] buffer = new byte[1024];
            while ((nCount = zis.read(buffer)) > 0) {
                fout.write(buffer, 0, nCount);
            }
            //fout.flush();
            fout.close();
        }

        @Override
        protected void onPostExecute(Boolean result) {
            mProgressDialog.dismiss();
        }

        @Override
        protected Boolean doInBackground(String... params) {
            String sMsg = mProgressDialog.getContext().getString(R.string.error_occurred);
            try {
                //                deleteRecursive(mOutputPath);
                int nSize = mInputStream.available();
                ZipInputStream zis = new ZipInputStream(mInputStream);
                int nIncrement = 0;
                mProgressDialog.setMax(nSize);

                ZipEntry ze;
                while ((ze = zis.getNextEntry()) != null) {
                    unzipEntry(zis, ze, mOutputPath);
                    nIncrement += ze.getSize();
                    zis.closeEntry();
                    mProgressDialog.setProgress(nIncrement);

                }
                zis.close();

                sMsg = mProgressDialog.getContext().getString(R.string.message_layer_added);
                getLocalCacheDetails();
                File file = new File(mOutputPath, LAYER_CONFIG);
                FileUtil.writeToFile(file, mLayerConfig.toString());

                if (mEventReceiver != null) {
                    Bundle bundle = new Bundle();
                    bundle.putBoolean(BUNDLE_HASERROR_KEY, false);
                    bundle.putString(BUNDLE_MSG_KEY, sMsg);
                    bundle.putInt(BUNDLE_TYPE_KEY, MSGTYPE_LAYER_ADDED);
                    bundle.putSerializable(BUNDLE_PATH_KEY, mOutputPath);

                    Message msg = new Message();
                    msg.setData(bundle);
                    mEventReceiver.sendMessage(msg);
                }
                return true;

            } catch (IOException e) {
                Log.d(TAG, "Exception: " + e.getLocalizedMessage());
                sMsg += ": " + e.getLocalizedMessage();
            } catch (JSONException e) {
                Log.d(TAG, "Exception: " + e.getLocalizedMessage());
                sMsg += ": " + e.getLocalizedMessage();
            } catch (NumberFormatException e) {
                Log.d(TAG, "Exception: " + e.getLocalizedMessage());
                sMsg += ": " + e.getLocalizedMessage();
            }

            //send message to handler to show error or add new layer

            if (mEventReceiver != null) {
                Bundle bundle = new Bundle();
                bundle.putBoolean(BUNDLE_HASERROR_KEY, true);
                bundle.putString(BUNDLE_MSG_KEY, sMsg);
                bundle.putInt(BUNDLE_TYPE_KEY, MSGTYPE_LAYER_ADDED);

                Message msg = new Message();
                msg.setData(bundle);
                mEventReceiver.sendMessage(msg);
            }

            return false;
        }

        protected void getLocalCacheDetails() throws JSONException, NumberFormatException {
            int nMaxLevel = 0;
            int nMinLevel = 512;

            JSONArray jsonArray = new JSONArray();
            mLayerConfig.put(JSON_LEVELS_KEY, jsonArray);

            //get cache levels
            File[] zoomLevels = mOutputPath.listFiles();
            for (File zoomLevel : zoomLevels) {
                int nMaxX = 0;
                int nMinX = 10000000;
                int nMaxY = 0;
                int nMinY = 10000000;
                //Log.d(TAG, zoomLevel.getName());
                int nLevelZ = Integer.parseInt(zoomLevel.getName());
                if (nLevelZ > nMaxLevel)
                    nMaxLevel = nLevelZ;
                if (nLevelZ < nMinLevel)
                    nMinLevel = nLevelZ;
                File[] levelsX = zoomLevel.listFiles();

                boolean bFirstTurn = true;
                for (File inLevelX : levelsX) {
                    //Log.d(TAG, inLevelX.getName());
                    int nX = Integer.parseInt(inLevelX.getName());
                    if (nX > nMaxX)
                        nMaxX = nX;
                    if (nX < nMinX)
                        nMinX = nX;

                    File[] levelsY = inLevelX.listFiles();

                    if (bFirstTurn) {
                        for (File inLevelY : levelsY) {
                            String sLevelY = inLevelY.getName();

                            //Log.d(TAG, sLevelY);
                            int nY = Integer.parseInt(sLevelY.replace(TILE_EXT, ""));
                            if (nY > nMaxY)
                                nMaxY = nY;
                            if (nY < nMinY)
                                nMinY = nY;
                        }
                        bFirstTurn = false;
                    }
                }

                JSONObject oJSONLevel = new JSONObject();
                oJSONLevel.put(JSON_LEVEL_KEY, nLevelZ);
                oJSONLevel.put(JSON_MAXX_KEY, nMaxX);
                oJSONLevel.put(JSON_MAXY_KEY, nMaxY);
                oJSONLevel.put(JSON_MINX_KEY, nMinX);
                oJSONLevel.put(JSON_MINY_KEY, nMinY);

                jsonArray.put(oJSONLevel);
            }
            mLayerConfig.put(JSON_MAXLEVEL_KEY, nMaxLevel);
            mLayerConfig.put(JSON_MINLEVEL_KEY, nMinLevel);

        }
    }
}