jozefk

Offline database backed tile layer

Discussion created by jozefk on Feb 22, 2012
Latest reply on May 9, 2012 by lcatania
The offline abilities of the android runtime are limited to using compact caches. Even those are pretty much unusable when tiling large areas. My maps for example end up as 800+MB with thousands of files. Half the tiles are blank and completely unneeded. So I now import them into a sqlite database and throwaway all of the blank tiles, cutting my download size in half and making my file count one. As a result, the transfers of the map caches are much faster for offline use.

I can post the tile importer script also if anyone else is interested.


package com.main.utilinspect;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.List;

import org.codehaus.jackson.JsonFactory;
import org.codehaus.jackson.JsonParser;

import android.content.Context;
import android.util.Log;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;

import com.esri.core.internal.c.d;
import com.esri.core.internal.c.h;
import com.esri.core.internal.c.l;

import com.esri.android.map.TiledServiceLayer;

public class OfflineDbTiledLayer extends TiledServiceLayer {
 
 File workingDirectory;
 String mapDefinition;
 String databaseName;
 private SQLiteDatabase database;
 File blankImage;
 
 byte[] blankImageBytes;
 
 private final Object lock = new Object();
 
 
 private static final String TAG = "OfflineTiledLayer"; 
 

 public OfflineDbTiledLayer(Context paramContext, File workingDirectory, String mapDefinition, String databaseName)  {
  super("required");
  this.workingDirectory = workingDirectory;
  this.mapDefinition = mapDefinition;
  this.databaseName = databaseName;
  
  String databasePath = workingDirectory.getAbsolutePath() + File.separator + databaseName;
  
  this.database = SQLiteDatabase.openDatabase(databasePath, null, SQLiteDatabase.NO_LOCALIZED_COLLATORS); 
  
  this.blankImage = new File(workingDirectory.getAbsolutePath() + File.separator + "blank.png");
  
  RandomAccessFile raFile = null;
  
  try {
   raFile = new RandomAccessFile(this.blankImage, "r");
   
   blankImageBytes = new byte[(int) raFile.length()];
   raFile.readFully(blankImageBytes);
   
  } catch (FileNotFoundException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  } catch (IOException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }  
  finally {
   if(raFile != null) {
    try {
     raFile.close();
    } catch (IOException e) {
     // TODO Auto-generated catch block
     e.printStackTrace();
    }
   }
  } 
  
  h h1 = null;
  
  try
  {
  
  JsonParser paramJsonParser = new JsonFactory()
  .createJsonParser(new File(workingDirectory.getAbsolutePath() + File.separator + mapDefinition));
  paramJsonParser.nextToken(); 
  
  h1 = h.a(paramJsonParser, "test");
  }
  catch(Exception ex){
   
  }  
  
  
  setFullExtent(h1.f());  
  setDefaultSpatialReference(h1.c());
  setInitialExtent(h1.e());
  
  l l1;
        List list;
        int i;
        double ad[] = new double[i = (list = (l1 = h1.d()).h).size()];
        double ad1[] = new double[i];
        for(int j = 0; j < list.size(); j++)
        {
            ad[j] = ((d)list.get(j)).b();
            ad1[j] = ((d)list.get(j)).a();
        }
  
  setTileInfo(new com.esri.android.map.TiledServiceLayer.TileInfo(l1.f, ad, ad1, i, l1.c, l1.b, l1.a));
  
  super.initLayer();
        return;  
 } 
 
 private void openDatabase(){
  if(!database.isOpen()){
   String databasePath = workingDirectory.getAbsolutePath() + File.separator + databaseName;
   this.database = SQLiteDatabase.openDatabase(databasePath, null, SQLiteDatabase.NO_LOCALIZED_COLLATORS); 
  }
 }
 
 private void closeDatabase(){
  if(database.isOpen()){
   this.database.close();
  }
 }

 @Override
 protected byte[] getTile(int level, int column, int row) throws Exception {
  byte[] tileImage;  
  
  Log.i(TAG, "getTile");
  
  Log.i(TAG, "getTile - retrieving tile"); 
  
  
  synchronized(lock) {
   
   Log.i(TAG, "getTile - entered synchronized block");
  
   openDatabase();  
   
   // First check to see if the tile exists in the database
   Cursor tileCursor = database.rawQuery("SELECT image FROM tiles WHERE level = " + Integer.toString(level) + " AND row = " + Integer.toString(row) + " AND column = " + Integer.toString(column), null);
   
   if(tileCursor != null && tileCursor.getCount() > 0) {
    tileCursor.moveToFirst();
    tileImage = tileCursor.getBlob(0);
    Log.i(TAG, "getTile - tile found, returning image");      
   }
   else {
    // The tile does not exist in the database, read the blank placeholder tile and serve it
    tileImage = blankImageBytes;
    Log.i(TAG, "getTile - tile not found returning blank");
   } 
   
   tileCursor.close();  
   this.database.close();
  }
  
  Log.i(TAG, "getTile - exited synchronized block");
  
  return tileImage; 
 }

 

}

Outcomes