Custom JDBC Access for Images¶
This plugin is an extension to the Image Mosaicing Pyramidal JDBC Plugin. It is targeted to users having a special database layout for storing their image data or use a special data base extension concerning raster data.
Credits: Thanks to some users on the mailing list for their inspiration.
Prerequisites
JDBC Driver jar file in your classpath
An existing database layout (tables,views) holding the raster data.
Image Mosaicing Pyramidal JDBC Plugin has an interface called
org.geotools.gce.imagemosaic.jdbc.JDBCAccess. Classes implementing this
interface are responsible for the jdbc communication. Additionally,
there is an abstract base class with some default implementations,
org.geotools.gce.imagemosaic.jdbc.JDBCAccessCustom.
A customized implementation should subclass this class and implement two methods. The name of this subclass is a configuration parameter in the XML configuration file.
The concrete procedure¶
Create the subclass
The abstract super class has some nice methods, especially
Connection getConnection()Config getConfig()CoordinateReferenceSystem getCRS()
Here is an example:
package org.mycompany.jdbc; public class MyJDBCAccessImpl extends JDBCAccessCustom { public MyJDBCAccessImpl(Config config) throws IOException { super(config); } @Override public void initialize() throws SQLException, IOException { } @Override public void startTileDecoders(Rectangle pixelDimension, GeneralEnvelope requestEnvelope, ImageLevelInfo info, LinkedBlockingQueue<TileQueueElement> tileQueue, GridCoverageFactory coverageFactory) throws IOException { } }
Initialize the meta info
This is done in the method initialize. This method is called only once for each coverage / XML configuration file.
The essential part is to create a list of
org.geotools.gce.imagemosaic.jdbc.ImageLevelInfoobjects.The
infoobject at index 0 describes the base image, at index 1 is the info for the first pyramid, and so on. If there are no pyramids, the list has only oneinfoobject.A code template:
@Override public void initialize() throws SQLException, IOException { Connection con = getConnection(); // get the extent in world coordinates, must be implemented Envelope extent = getExtent(con); // get the crs, method inherited from superclass CoordinateReferenceSystem crs = getCRS(); // fetch the pixel resolution for each pyramid level // in the correct order String stmt = "select RESX,RESY from ... order by level"; PreparedStatement ps = con.prepareStatement(stmt); ResultSet rs = ps.executeQuery(); while (rs.next()) { ImageLevelInfo li = new ImageLevelInfo(); getLevelInfos().add(li); li.setResX(rs.getDouble(1)); li.setResY(rs.getDouble(2)); li.setExtentMinX(extent.getMinX()); li.setExtentMaxX(extent.getMaxX()); li.setExtentMinY(extent.getMinY()); li.setExtentMaxY(extent.getMaxY()); li.setCrs(crs); } rs.close(); ps.close(); }
This is a minimal example setting the meta information absolutely needed. ImageLevelInfo has more properties which can be useful. Another option is to subclass from ImageLevelInfo and add customized properties.
Fetch raster data
This is done in the method
startTileDecoders. There are a lot of parameters, maybe not all are useful for a custom implementation.Rectangle pixelDimensionThe requested size in pixel of the result image, perhaps not needed
GeneralEnvelope requestEnvelopeThe requested size in world coordinates
ImageLevelInfo infoThe info object of the pyramid to use
LinkedBlockingQueue<TileQueueElement> tileQueueQueue for holding tile queue elements
GridCoverageFactory coverageFactoryperhaps not needed
This method is responsible for
Fetching the tiles for the given level, the area covered may be larger than the area requested in the
requestEnvelopeparameter. This is the minimum to implement.Additionally to 1. , mosaic the tiles to one image.
Additionally to 2. , crop the image according to the
requestEnvelopeparametersAdditionally to 3, use the pixel dimension of the image and the
pixelDimensionparameter to rescale the image.
The interesting construct is the tile queue and a tile queue element. Before this method is called, a tile queue is created. Additionally an
ImageComposerThreadis created an started. This thread is responsible for creating the result image. Depending on the implementation possibilities described above, this thread is responsible to do the missing steps.As an example:
if the custom implementation of
startTileDecodersimplements step 1 and 2,the
ImageComposerThreadwill do the missing steps 3 and 4.
The primary job of the
startTileDecodersmethod is to fetch the image data as fast as possible, creating on or more tile queue elements and put these elements into the queue. TheImageComposerThreadstarts working when the first element is in the queue. It stops working when it reads a special END Element.A tile queue element for itself has
an optional name
a
BufferedImageobjecta
GeneralEnvelopedescribing the the tile rectangle in world coordinates
A code template:
@Override public void startTileDecoders(Rectangle pixelDimension, GeneralEnvelope requestEnvelope, ImageLevelInfo info, LinkedBlockingQueue<TileQueueElement> tileQueue, GridCoverageFactory coverageFactory) throws IOException { try { Connection con = getConnection(); // getting the index of the level info object int level = getLevelInfos().indexOf(info); // this example reads exactly one tile BufferedImage img = getBufferedImage(level, con); GeneralEnvelope genv = new GeneralEnvelope(info.getCrs()); genv.setRange(0, info.getExtentMinX(), info.getExtentMaxX()); genv.setRange(1, info.getExtentMinY(), info.getExtentMaxY()); TileQueueElement tqElem = new TileQueueElement("oek",img,genv);; tileQueue.add(tqElem); con.close(); } catch (SQLException ex) { throw new RuntimeException(ex); } // IMPORTANT, this must be the last element tileQueue.add(TileQueueElement.ENDELEMENT); }
This is a simple template. A more complex implementation can be found in class
JDBCAccessBase. This implementation fetches tiles, starts decoder threads to utilize full CPU power, waits for all decoder threads to finish and sends the end element.HINT: Hurry up to bring your first tile queue element into the queue.
IMPORTANT: This method must be thread safe, do not modify instance variables or implement other actions causing problems under load.
The Configuration file¶
Here is an example configuration file:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<config version="1.0">
<coverageName name="oek"/>
<coordsys name="EPSG:4326"/>
<!-- interpolation 1 = nearest neighbor, 2 = bipolar, 3 = bicubic -->
<scaleop interpolation="1"/>
<axisOrder ignore="false"/>
<spatialExtension name="custom"/>
<jdbcAccessClassName name="org.mycompany.jdbc.MyJDBCAccessImpl" />
<connect>
<!-- value DBCP or JNDI -->
<dstype value="DBCP"/>
<!-- <jndiReferenceName value=""/> -->
<username value="geotools" />
<password value="geotools" />
<jdbcUrl value="jdbc:oracle:thin:@ux-mc01.ux-home.local:1521:geotools102" />
<driverClassName value="oracle.jdbc.OracleDriver"/>
<maxActive value="10"/>
<maxIdle value="0"/>
</connect>
</config>
Most elements are self explanatory, the detailed documentation is in Image Mosaicing Pyramidal JDBC Plugin
The name attribute of the
<spatialExtension>Element must be custom.The name attribute of the
<jdbcAccessClassName>Element holds the class name of your implementation,org.mycompany.jdbc.MyJDBCAccessImplin this example.
Deployment¶
Package the Java classes in a jar file and copy this jar file to your classpath.
Java Example¶
How to use the new coverage? Again, see at the end of Image Mosaicing Pyramidal JDBC Plugin.