Extending Tiled Service Layer

3067
10
07-22-2013 01:33 PM
JohnCo-Reyes
New Contributor
Has anyone successfully extended a TiledServiceLayer for android? I'm trying to implement one so that I can create a layer from a non arcgis server. I've overridden the getTile(int level, int row, int column) function and have set the full extent, default spatial reference, and tileinfo. The application never calls getTile for some reason though. Is there anything else I need to initialize or override?

I've found this example for SilverLight: http://help.arcgis.com/en/webapi/silverlight/apiref/ESRI.ArcGIS.Client~ESRI.ArcGIS.Client.TiledMapSe...
one for Flex: http://blogs.esri.com/esri/arcgis/2009/03/06/extending-tiled-layers-in-the-arcgis-api-for-flex/
and one for JS: http://resources.esri.com/help/9.3/arcgisserver/apis/javascript/arcgis/help/jssamples_start.htm#jssa...
0 Kudos
10 Replies
AndyGup
Esri Regular Contributor
Good question, I'm not aware of any Android examples to date. However...if it's a specific type of non-ArcGIS layer your work may have already been done for you. What type of layer are you working with?

For example, here are the existing capabilities for layers in our ArcGIS Runtime SDK for Android 10.1.1: https://developers.arcgis.com/en/android/guide/map-layer-types.htm

You can also go the route of creating a web map and adding a non-ArcGIS Tile Layers using the "Add > Add Layer from Web > Tiled layer" functionality as long as it matches the right level, columns, and row placeholders. You can build these web maps using this mapviewer along with an ArcGIS Online account: http://www.arcgis.com/home/webmap/viewer.html?useExisting=1. In addition to tile layers it also works with WMS, WMST, GeoRSS, KML, and Shapefiles.

-Andy
0 Kudos
JohnCo-Reyes
New Contributor
Thanks for replying.

It's a custom tiled layer where the url for each image contains a bounding box attribute. I need to write the getTile function because I have to calculate the bounding box for each tile based on the level and position. I might be able to create a web map but I'll have to look into it further.
0 Kudos
JozefKaslikowski
New Contributor
I just got done doing this for the new version of the SDK.

Mine loads tiles from an sqlite database.

package com.main.utilinspect;

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

import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.map.ObjectMapper;

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

import com.esri.android.map.TiledServiceLayer;
import com.esri.core.geometry.Envelope;
import com.esri.core.geometry.Point;
import com.esri.core.geometry.SpatialReference;

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();
    }
   }
  }
 
  try
  {  
   ObjectMapper m = new ObjectMapper();
  
   JsonNode root = m.readTree(new File(workingDirectory.getAbsolutePath() + File.separator + mapDefinition));
  
   JsonNode tileInfo = root.get("tileInfo");
  
   double x = tileInfo.get("origin").get("x").asDouble();
   double y = tileInfo.get("origin").get("y").asDouble();
  
   Point origin = new Point(x, y);
  
   int dpi = tileInfo.get("dpi").asInt();
   int width = tileInfo.get("rows").asInt();
   int height = tileInfo.get("cols").asInt();
  
   JsonNode lods = tileInfo.get("lods");
    
   int levels = lods.size();
  
   double resolutions[] = new double[levels];
   double scales[] = new double[levels];
  
   for(int i = 0; i < levels; i++)
   {
    JsonNode level = lods.get(i);
    resolutions = level.get("resolution").asDouble();
    scales = level.get("scale").asDouble();
   }
  
   int wkid = tileInfo.get("spatialReference").get("wkid").asInt(); 
  
   JsonNode initialExtent = root.get("initialExtent");
  
   Envelope initialExtentEnvelope = new Envelope(
     initialExtent.get("xmin").asDouble(),
     initialExtent.get("ymin").asDouble(),
     initialExtent.get("xmax").asDouble(),
     initialExtent.get("ymax").asDouble()
   );
  
   JsonNode fullExtent = root.get("fullExtent");
  
   Envelope fullExtentEnvelope = new Envelope(
     fullExtent.get("xmin").asDouble(),
     fullExtent.get("ymin").asDouble(),
     fullExtent.get("xmax").asDouble(),
     fullExtent.get("ymax").asDouble()
   );
  
  
   setDefaultSpatialReference(SpatialReference.create(wkid));
  
   setInitialExtent(initialExtentEnvelope);
   setFullExtent(fullExtentEnvelope);
  
   setTileInfo(new TileInfo(origin, scales, resolutions, levels, dpi, width, height));
 
  }
  catch(Exception ex){
  
  }
 
  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 - thread " + Integer.toString(android.os.Process.myTid()));
 
   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;
}
}
0 Kudos
JohnCo-Reyes
New Contributor
Except for the database I have what you have. However, when I call super.initLayer(), I get a fatal signal and the program terminates. Any ideas?
0 Kudos
JozefKaslikowski
New Contributor
How are you calculating your TileInfo and your initial and max extents? That would seem to be the first place to look. If you have any bad math, that could probably cause an issue.
0 Kudos
JohnCo-Reyes
New Contributor
So for extents I'm just setting both as
setFullExtent(new Envelope(-180.0, -90.0, 180.0, 90.0))
setInitialExtent(new Envelope(-180.0, -90.0, 180.0, 90.0))

resolution is the width of the bounding box for the image divided by the width of a tile.
resolution = tilePattern.getWidth() / TILE_WIDTH;

scale is calculated using resolution. I'm pretty sure both scale and resolution are calculated correctly.
imageWidth = 360.0 / resolution;
kmPerPixel = imageWidth / ((TILE_DPI/(CM_PER_INCH))*CM_PER_KM);
scale = EARTH_CIRCUM_KM / kmPerPixel;

tileInfo is
tileInfo = new TileInfo(new Point(0.0, 0.0), scale, resolution,
    tiledGroup.getTilePatterns().size(), TILE_DPI, TILE_WIDTH.intValue(), TILE_HEIGHT.intValue());
0 Kudos
JozefKaslikowski
New Contributor
Are you using multiple levels or just one level and scale?
0 Kudos
JohnCo-Reyes
New Contributor
Multiple levels. 7 or 9 depending on the layer.
0 Kudos
JozefKaslikowski
New Contributor
I dont really have any ideas what it is crashing (probably pretty obvious by now).

You first said it was not calling getTile, but now it won't even get through the constructor?

Is the signal it is throwing any help at all?

What about trying with one layer? I am still thinking it is the tileInfo, but only because there isn't really much else going on to get to that point. It would certainly be easier to check if you could build a map cache of the data and sanity check what it gives you in the map definition versus what you are calculating.

If you dont have arcgis server, I could give it a shot for you with some dummy data at the extents/coordinate system you want if that would help.
0 Kudos