skip to main content
Maps > Programmer's documentation > Programming with JViews Maps > Creating a map application using the API > Using load-on-demand
 
Using load-on-demand
Describes load-on-demand and how to use it.
*Load-on-demand
*Describes the classes for load-on-demand.
*Structure of the tiling grid (indexed mode only)
*Explains what a tiled layer is and how it relates to load-on-demand.
*Size of the tiling grid in indexed mode
*Describes how to size a tiling grid in indexed mode.
*Structure and size of the tiled layer (free mode only)
*Describes how to size the tiled layer in free mode.
*Displaying the state of tiles
*Describes how to display the state of the tiles in a tiled layer.
*Controlling load-on-demand
*Describes different ways of controlling load-on-demand.
*Managing errors and load-on-demand events
*Describes the management of errors and other events.
*Caching tiles
*Describes the tile cache and how to use it.
*Saving a tiled layer
*Describes layer parameters and the impact on saving tiles.
*Writing a new cache algorithm
*Describes how to write a custom cache algorithm.
*Writing a tile loader for a custom data source
*Describes how to write a tile loader for load-on-demand.
*Load-on-demand for hierarchical data sources
*Describes how to write a custom data source with load-on-demand facilities.
Load-on-demand
JViews Maps offers a mechanism that lets you load into memory only the data that you want to display in a manager view. This mechanism, known as load-on-demand, is extremely valuable, especially when very large maps are concerned. Consider a database storing maps of the whole world with a scale of 1/25,000. If these maps were scanned with a resolution of 300 DPI, the required storage space would be as follows:
*654 kilobytes for 1 square kilometer
*64 megabytes for 100 square kilometers
*Approximately 310 terabytes for the whole world
Given the volume of this data, it is crucial to have a load-on-demand mechanism that will load and display only the portion of a map of direct interest.
The IlvTiledLayer Class
The IlvTiledLayer IlvTi extends the IlvManagerLayer class and allows the loading of large sets of data, for example, large maps. It is divided into a number of rectangular areas called tiles (organized into grid cells or into a list of specified areas) and provides a notification mechanism to load only the graphic objects that are required by the application, because a tile is visible in one of the views of the manager or because it has been explicitly required by the application.
An IlvTiledLayer is associated with three important objects:
*An IlvTileController, which manages the tile events.
*An IlvTileLoader, which loads the tiles from a data source.
*An IlvTileCache, which manages the cache and deletes the unused tiles.
When an IlvTiledLayer is saved into an .ivl file, it does not save the graphic objects it contains. It saves its tiling parameters (loader, cache, tile controller, tiling structure).
The IlvTileController Class
The IlvTileController class manages the load-on-demand mechanism. It divides the space into a number of rectangular areas called tiles, in two different fashions according to its mode:
*In indexed mode, tiles are distributed on a regular grid defined by its origin rectangle. A tile is then referenced by its line and column indices. All tiles have the same height and width, and are automatically created as needed by the IlvTileController, according to the visible parts of the tiled layer in the manager views.
*In free mode, however, the IlvTileController holds a list of IlvFreeTile instances that need to be created and then added to the tile controller. These tiles can have different sizes and locations, and can also overlap.
The tiling mode for an IlvTileController is set at creation time and can not be modified afterwards.
Each time a tile becomes visible in a view of the manager to which it is attached, the tile controller notifies a loader (called tile loader) to load the data attached to this tile. A cache releases the invisible tiles (called the cached tiles) when it is necessary to free memory.
The IlvTileLoader Interface
The IlvTileLoader interface is used to load a tile for an IlvTileController or an IlvTiledLayer.
The following example shows a tile loader that fills a tile with a generated list of graphic objects.
The file is: <installdir> /jviews-maps/codefragments/srs/src/Sample2.java
 
  class SimpleTileLoader
    implements IlvTileLoader
  {
    public void load(IlvTile tile)
    {
      IlvRect rect = new IlvRect();
      tile.boundingBox(rect);
      IlvPoint p = new IlvPoint();
      p.x = rect.x;
      for (int i = 0; i < 10; i++) {
        p.y = rect.y;
        for (int j = 0; j < 10; j++) {
          tile.addObject(new IlvMarker(p, IlvMarker.IlvMarkerPlus),
                         null);
          p.y += rect.height / 10;
        }
        p.x += rect.width / 10;
      }
      tile.loadComplete();
    }
    public void release(IlvTile tile)
    {
      tile.deleteAll();
    }
 
    public boolean isPersistent()
    {
      return false;
    }
 
    public void write(IlvOutputStream stream)
    {
      // do nothing
    }
  }
The IlvTileCache Class
The IlvTileCache class is used to manage the cached tiles of one or more tile controllers. A cached tile is a tile that is not visible in any view and that is not locked by any application object. An IlvTileCache object releases the cached tiles to free memory when necessary.
The IlvTile Class
A tile represents an elementary rectangular area that is loaded or released when needed by the application. Tiles are managed by an IlvTileController.
In most cases, the tile controller will be associated with an IlvTiledLayer. This means that the tiles represent areas that must be filled with graphic objects when they become visible due to the user of the application scrolling or zooming on a view.
More sophisticated applications can use a tile controller without attaching it to a layer, for example, to load data that is needed to process an area that becomes visible.
For example, to define a geographic coordinate system expressing latitude and longitude with grads, instead of degrees, you can use the following code:
 
IlvAngularUnit unit = IlvAngularUnit.GRAD;
IlvGeographicCoordinateSystem gcs =
   new IlvGeographicCoordinateSystem("My coordsys",
                                      IlvHorizontalShiftDatum.WGS84,
                              IlvMeridian.GREENWICH,
                              unit,
                              null); // No altitude
 
// A transformation from WGS84 geographic coordinate system
// with degrees as unit to our own coordinate system:
IlvCoordinateTransformation ct =
   IlvCoordinateTransformation.CreateTransformation(
      IlvGeographicCoordinateSystem.WGS84,
      gcs);
 
// Example of conversion.
double lambda = IlvAngularUnit.DEGREE.toRadians(-45D);
double phi = IlvAngularUnit.DEGREE.toRadians(30D);
IlvCoordinate coord = new IlvCoordinate(lambda,phi);
 
// Convert coordinate, letting the transformation allocate the
// result.
IlvCoordinate result = ct.transform(coord,null);
 
System.out.println("The expression of point 45W 30N is ");
System.out.println("x = " + coord.x + " grad");
System.out.println("y = " + coord.y + " grad");
The IlvFreeTile Class
This class is similar to the IlvTile, except that instead of being part of a regular grid (and therefore indexed by line and column), its bounds can be freely specified. This is particularly useful when overlapping areas are needed, for example, in case of a projected map. Note that although IlvFreeTile is a subclass of IlvTile (for compatibility reasons), the member variables row and column are not relevant in terms of tile location. They are used to locate a superclass IlvTile instance on a grid, but given that IlvFreeTile objects have bounds of their own, there is no need for line and column properties.
Structure of the tiling grid (indexed mode only)
A tiled layer is a type of manager layer specifically designed to support load-on-demand. If the IlvTileController associated with this layer works in indexed mode, the layer is divided into a set of rectangular tiles of identical size that form a tiling grid (note that a constructor of IlvTiledLayer takes a mode as a parameter).
Tiling grid in indexed mode
The tiling grid is defined by its origin tile that is located at the intersection of the row and column of index 0, see Tiling grid in indexed mode.
The other tiles in the grid are identified by their column and row number, starting from the origin tile. The following code example displays the status of the tile that is at the intersection of column 10 and row 5:
 
  public static void displayTileStatus(IlvTiledLayer layer) {
    IlvTile tile = layer.getTileController().getTile(10, 5);
    if(tile == null)
      System.out.println("The tile is not loaded yet");
    else {
      int status = tile.getStatus();
      if(status == IlvTile.LOCKED)
        System.out.println("The tile is locked");
      else if(status == IlvTile.CACHED)
        System.out.println("The tile is cached");
      else
        System.out.println("The tile is empty");
    }
  }
You can see in the above code example that the getTile method can sometimes return a null value. Because the potential number of tiles can be very great—the number of tiles is even virtually infinite—the IlvTile objects are allocated only if the tile is loaded or is in the cache.
The complete source code of this example can be found in the following file:
<installdir> /jviews-maps/codefragments/lod/src/TileStatus.java.
Size of the tiling grid in indexed mode
You can set the size of the tiling grid using the following method:
setSize
The rectangle that delimits the tiling grid is expressed in the manager coordinates. Only the tiles that intersect with this rectangle can be loaded. You can see an example of a tiling grid whose size has been defined in the debug view illustrated in Load-on-demand debug view.
NOTE The tiling parameters introduced in this section (size of the tiles, origin tile, and size of the tiling grid) can be configured for each tiled layer in a manager. This allows you to have in the same manager large-tile layers containing objects displayed at a small scale and small-tile layers containing objects displayed at a large scale.
Structure and size of the tiled layer (free mode only)
When the IlvTiledLayer is set to free mode (or its associated IlvTileController), the structure is not a grid (unlike indexed mode described in Structure of the tiling grid (indexed mode only)), but a list of rectangular areas that must be added to the IlvTileController.
Tiled layer in free mode
These areas can have any bounds and can even overlap.
The size of the tiled layer is simply the union of all tile bounds.
Displaying the state of tiles
You can create a debug view to display the state of the tiles in a tiled layer using the following method:
setDebugView
As its name suggests, this debug view is particularly useful for debugging operations when implementing load-on-demand for a new cartographic format.
A tile can have three different states. It can be:
*Empty, meaning that its objects are not loaded into memory.
*Loaded, meaning that its objects are loaded into memory and visible.
*Cached, meaning that its objects are loaded into memory but not visible.
The debug view is of the IlvManagerView type, and must be attached to a tiled layer. The debug view does not increment nor decrement the tile lock counters. This is the role of the tile controller. It just displays the tiles in color according to their state, as in Load-on-demand debug view.
Load-on-demand debug view
The tiles whose lock counter is greater than 1 appear in blue. They are visible in at least one view. The tiles whose counter equals to 0 appear in yellow. They are cached. The white tiles are not loaded.
In the previous example, an IlvManagerMagViewInteractor was associated with the debug view. It is this interactor that displays a little yellow square inside the blue tile. The square shows the zone displayed by the main view.
The following is an example of code that creates a debug view for a layer.
 
  public static void createDebugView(IlvTiledLayer layer) {
    IlvManagerView view = new IlvManagerView(layer.getManager());
    layer.setDebugView(view);
 
    // Create a swing frame to display this debug view.
    JFrame frame = new JFrame("Debug View");
    frame.setLayout(new BorderLayout());
    frame.add(BorderLayout.CENTER, view);
    view.setAutoFitToContents(true);
    frame.setSize(200, 200);
    frame.setVisible(true);
  }
The complete source code of this example can be found in the following file:
<installdir> /jviews-maps/codefragments/lod/src/DebugView.java
Controlling load-on-demand
Using visibility filters to control load-on-demand
If you do not want to use the dynamic styles provided by the IlvMapStyleController class, you can use scale visibility filters to control the display of tiles in a manager layer.
When a scale visibility filter has been set for a tiled layer, this layer will be displayed if the scale factor set for its manager view is within specified scale limits. Otherwise, it will be hidden. When a tiled layer is visible because the zoom factor of the manager view allows it, visible tiles are automatically locked and thus loaded into memory. In the same way, when a tiled layer is hidden because the zoom level of its view exceeds a certain value, all the locks set on visible tiles are released and the tiles are removed.
Scale visibility filters are generally used to activate load-on-demand for zoom factors between a minimum and a maximum scale value. Let us consider a map scanned with a scale of 1/1,000,000, you can set visibility filters so that its layer is visible for scale factors ranging from 1/2,500,000 to 1/500,00.
Setting tile locking filters
You can use the IlvTileLockFilter class to dynamically prevent a tile from being loaded when it becomes visible in a view. To do so, you associate an IlvTileLockFilter object with a tile controller using the following method:
setLockFilter
If the tile controller possesses a lock filter, it calls the method isLockAllowed of the lock filter each time a tile becomes visible in a view. If this method returns false, the counter of the tile is not incremented.
Using a lock filter, you can prevent the loading of data displayed at a small scale (when a map is zoomed out greatly, for example) while leaving the tiles that were already loaded visible.
Loading tiles using the API
The load-on-demand mechanism is event-driven in that cartographic data is loaded or unloaded following user’s actions, such as zooming or panning. You can, however, use the lockTile() method for example—to preload a tile corresponding to a map zone that is visited frequently or to prevent the tile from being unloaded.
The lockTile() method shown below increments the tile lock counter:
lockTile
If the tile lock counter is equal to zero, the tile is loaded into memory and will not be unloaded as long as the lock is not released—with the IlvTileController.unlockTile method, for example.
Updating all the visible tiles in a view
Sometimes you may need to update immediately all the tiles that are currently viewed by an IlvManagerView. You can then use the updateView method. It can be useful when you reactivate a view that was ignored until then by the load-on-demand mechanism.
Managing errors and load-on-demand events
The load-on-demand mechanism might generate errors when the map cannot be entirely loaded due to memory problems, absence of data, loss of connection to a server and so on. Graphic applications must catch these errors to inform the user that the map being viewed is not complete.
To be notified of these events, and any other events related to load-on-demand, you can set a TileListener to an instance of the IlvTileController class. This listener is notified of all changes to the tiles, for example, the beginning or end of loading, moving tiles into the cache, or out of the cache for reuse, loading errors, and so on.
The following example displays all the events related to load-on-demand:
 
lodLayer.getTileController().addTileListener(new TileListener() {
  public void tileChanged(TileEvent e) {
    System.err.print(e);
  }
});
By default, the tile controller displays a message on the screen if an error occurs while a tile is being loaded. You can deactivate this behavior using the following method:
setPrintingErrors
The different types of events sent to the tile listeners are:
*TILE_ABOUT_TO_LOAD Triggered when a tile is about to be loaded into memory.
*TILE_LOADED Triggered when a tile has been loaded into memory.
*TILE_CACHED Triggered when a tile that is no longer being used is stored in the cache.
*TILE_RETRIEVED Triggered when the counter of a tile stored in the cache is incremented again. The tile can no longer be unloaded.
*TILE_RELEASED Triggered when a tile is completely freed.
*ERROR Triggered when an error occurs while a tile is being processed. In this case, the getThrowable method of TileEvent allows you to know the exception or the error that caused the problem.
*CONTROLLER_DISPOSED Triggered when the tile controller is deactivated. For example, if you remove its associated tiled layer from a manager.
*NO_CHANGE Triggered when the last event in a series of events is processed.
When an event in a view causes an action to be performed on a tile, the tile controller notifies the tile listener of the action. If this event provokes a series of transitional events, these are transmitted to the listener as a group. Therefore, modifying a scale factor can cause new tiles to be loaded and other tiles to be cached. Grouping events allows an action to be performed only when all the transitional events it causes are completed. The isAdjusting() method of the class TileEvent returns true if the event is part of a series of events. The NO_CHANGE event is sent once the last event in a series is processed and the isAdjustmentEnd() method returns true.
Caching tiles
The tile cache is the place where tiles whose lock counter has returned to 0 are stored. The tiles in the cache are eligible for unloading if memory is needed for loading new tiles.
The cache can be shared among several layers, which means that loading a tile in a layer can cause that tile to be unloaded in another layer.
The IlvDefaultTileCache class implements a cache algorithm consisting of a simple LRU (Least Recently Used) structure that unloads first the tiles that have been visited the least recently.
You can, however, implement another algorithm that will be more efficient with respect to the specific nature of your application. Here are a few criteria you might take into account when implementing a new backup cache algorithm.
*Unload first the tiles that require a larger number of pan or zoom operations to be reached from the current position.
*Unload first the tiles that have taken the longest to load.
*Unload first the tiles that contain the largest number of graphic objects.
An example of a simplified cache algorithm is given in Writing a new cache algorithm.
Saving a tiled layer
A layer has a number of associated parameters. Some parameters such as named properties, visibility filters, and names are common to all layers, whether they are tiled layers or just normal layers. Other parameters are specific to tiled layers. These are the tiling parameters, the lock filter and the tile loader, introduced in the previous sections.
Unlike normal layers, when you save a tiled layer to an .ivl file, only its attached parameters are saved with the layer, not the objects it holds.
Writing a new cache algorithm
This section explains how to write a custom cache algorithm to meet specific application requirements. The example in this section is a simplified version of the class IlvDefaultTileCache provided in the JViews Maps library.
The complete source code for this example can be found in the following file:
<installdir> /jviews-maps/codefragments/lod/src/SimpleTileCache.java
The class SimpleTileCache extends the class IlvTileCache.
 
public class SimpleTileCache
  extends IlvTileCache
{
cacheSize defines the maximum number of tiles that can be stored in the cache.
 
  // default cache size
  private int cacheSize = 5;
  // To Store the tiles
  transient private Vector tiles = new Vector();
The following constructor creates an instance of the default cache with the specified size (in this example, 5).
 
 public SimpleTileCache(int size)
  {
    cacheSize = size;
  }
The following constructor reads the cache from the provided input stream. Caches created using this constructor implement the IlvPersistentObject interface and can thus be saved with an IlvTiledLayer object.
 
  public SimpleTileCache(IlvInputStream stream)
  throws IlvReadFileException
  {
    cacheSize = stream.readInt("size");
  }
The write() method writes the cache to an output stream.
 
 public void write(IlvOutputStream stream)
    throws IOException
  {
    super.write(stream);
    stream.write("size", cacheSize);
  }
The following method belongs to the IlvTileCache interface. It is called when a tile is cached. In this implementation, the tile is added at the end of the internal tile list.
 
  public void tileCached(IlvTile tile)
  {
    tiles.addElement(tile);
  }
The following method belongs to the IlvTileCache interface. It is called when a tile is removed from the cache and locked again. With this implementation, the tile is removed from the internal tile list.
 
  public void tileRetrieved(IlvTile tile)
  {
    tiles.removeElement(tile);
  }
The following method belongs to the IlvTileCache interface. It is called when a tile is about to be loaded. With this implementation, if the number of tiles in the cache exceeds the cache size, the least recently used tiles, at the top the internal tile list, will be unloaded to make room for new tiles.
 
  public void tileAboutToLoad(IlvTile tile)
  {
    int toRemove = tiles.size() - cacheSize;
    if (toRemove <= 0)
      return;
    for (int i = toRemove; i > 0; i--) {
      IlvTile current = (IlvTile) tiles.elementAt(0);
      tiles.removeElementAt(0);
      releaseTile(current);
    }
  }
The following method belongs to the IlvTileCache interface. It is called when a tiled layer is taken out of the manager to remove the tiles managed by its tile controller from the cache.
 
  public void controllerDisposed(IlvTileController controller)
  {
    int i = 0;
    while (i < tiles.size()) {
      IlvTile tile = (IlvTile) tiles.elementAt(i);
      if (tile.getController() == controller)
        tiles.removeElementAt(i);
      else
        i++;
    }
  }
Writing a tile loader for a custom data source
To implement load-on-demand for a new data source, all you have to do is write a specific tile loader that implements the IlvTileLoader or the IlvMapTileLoader interface. Notice, however, that for the predefined map formats supplied with JViews Maps, load-on-demand has been implemented in a subclass of IlvTiledLayer that defines both the tile loader (as a private class) and the tiling parameters appropriate for the concerned format. The IlvMapTileLoader class and the predefined formats are described in Readers and writers.
For your tile loader to be fully efficient, the following requirements should be satisfied:
*You should be able to determine which objects are to be loaded on the tile. These objects can be read from a file whose name is known, or be the result of a query to a cartographic database.
*You should be able to have a direct random access to data.
*The size of the data to be loaded should be in proportion to the size of the tiles to allow fast loading. For example, raster images with a size of 100x100 are faster to load than images with a size of 6000x6000.
The following example of a tile loader simulates the loading of two graphic objects, a rectangle and a label.
Its load() method takes the tile to be loaded as its parameter. It generates the graphic objects to be displayed within the tile and adds them to the tiled layer by calling the tile.addObject() method. When loading is complete, it calls the tile.loadComplete method to notify the listeners that the data in the tile is ready for use.
 
public class LodLoader implements IlvTileLoader
{
  [...]
 
  public void load(IlvTile tile) throws Exception
  {
    IlvRect bbox = new IlvRect();
    tile.boundingBox(bbox);
 
    // Add a rectangle inside the tile bounding box
    tile.addObject(new IlvRectangle(bbox),null);
 
    // Add text
    String text = this.layerName +
      " (" + tile.getColumn() + "," + tile.getRow() + ")";
    IlvPoint textCenter = new IlvPoint(bbox.x + bbox.width / 2,
                                       bbox.y + bbox.height / 2);
    tile.addObject(new IlvLabel(textCenter,text),null);
 
    tile.loadComplete();
  }
   [...]
Its release() method is invoked when the tile cache releases a tile. The tile.deleteAll method clears the tile.
 
  /**
   * Releases the objects on this tile.
   */
  public void release(IlvTile tile)
  {
    tile.deleteAll();
  }
The complete source code of this example can be found in the following file:
<installdir> /jviews-maps/codefragments/lod/src/LodLoader.java
Load-on-demand for hierarchical data sources
To write a new IlvMapDataSource for a format of your own using the load-on-demand mechanism, you should inherit from the IlvTilableDataSource class. This class performs all of the steps required to create the appropriate tiles, and to configure tiled layers to make efficient use of load-on-demand. You can choose the number of rows and columns into which to divide the map, or even deactivate the tiling mechanism if you prefer. However, you need to implement two abstract methods:
 
protected abstract void createTiledLayers();
protected IlvMapRegionOfInterestIterator createTiledIterator(IlvMapLayer
layer);
The source code for the Map Builder demonstration, which contains all of the code described in this section, can be found at <installdir> /jviews-maps/samples/mapbuilder/index.html
Creating tiled layers
The createTiledLayers method is called by the startmethod of the IlvMapDataSource. Its role is to create the tiled layers that make up this data source. For example, if your data source sorts map features into different layers, all of the tiled layers must be created by this method.
Here is a sample implementation of this method:
 
protected void createTiledLayers() {
  // get the insertion layer of this datasource (i.e. the ‘root’ layer
  // of this data source, potentially containing children layers)
  IlvMapLayer mapLayer = getInsertionLayer();
 
  // Create a tiled layer to associated with this IlvMapLayer (refer to the
chapter
  // on tiled layers)
  IlvTiledLayer currentTiledLayer = new IlvTiledLayer(new IlvRect(),new
  IlvDefaultTileCache(),IlvTileController.FREE);
 
  // associate this layer with the IlvMapLayer
  mapLayer.insert(currentTiledLayer);
}
Reading map features
The createTiledIterator method returns a feature iterator over map features located in a specified region of interest. The tile loaders of the layers use this iterator to load the tiles when they become available on screen. That is, they read the map features of the tiles in the region of interest only.
Here is a basic implementation of this method:
 
protected IlvMapRegionOfInterestIterator createTiledIterator(IlvMapLayer layer)
throws IOException {
  // IlvMapLayer mapLayer = getInsertionLayer();
  if (layer != mapLayer) {
    // the layer in parameter has nothing to do with this data source
    return null;
  }
  // Create your region of interest feature iterator here,
  // basically an iterator over map features in specified region
  MyRegionOfInterestIterator iterator = new MyRegionOfInterestIterator();
 
  // configure it as needed (we assume that there is a ‘configure’ method here
for
  // clarity purposes)
  iterator.configure();
 
  // return it
  return iterator;
}
If multiple tiled layers are created by the createTiledLayers method in Creating tiled layers, a different iterator might be returned for each layer (hence the layer parameter in the createTiledIterator() method), see Data tiling.

Copyright © 2018, Rogue Wave Software, Inc. All Rights Reserved.