D Creating and Registering a Custom Spatial Data Provider

This appendix shows a sample implementation of a spatial data provider, and explains how to register this provider to be used with MapViewer. The complete implementation can be found under the MapViewer web/demo/spatialprovider directory. The implementation uses then following files:

The us_bigcities.xml file has sections to define the data attributes, the data extents, and the feature information, including the geometry (in GML format) and the attribute values. This file includes the following:

<?xml version="1.0" standalone="yes"?>
<spatial_data>
 
<data_attributes>
  <attribute name="city" type="string" />
  <attribute name="state_abrv" type="string" />
  <attribute name="pop90" type="double" />
</data_attributes>
 
<data_extents>
   <xmin> -122.49586 </xmin>
   <ymin> 29.45765 </ymin>
   <xmax> -73.943849 </xmax>
   <ymax> 42.3831 </ymax>
</data_extents>
 
<geoFeature>
  <attributes> New York,NY,7322564 </attributes>
  <geometricProperty>
    <Point>
      <coordinates>-73.943849, 40.6698</coordinates>
    </Point>
   </geometricProperty>
 </geoFeature>
 
. . .
</spatial_data>

This appendix contains the following major sections:

D.1 Implementing the Spatial Provider Class

The provider must implement the class interface shown in Section 2.3.8. Example D-1 contains the partial code for the spatial provider in the supplied demo. Note that this sample code is deliberately simplified; it is not optimized, and the provider does not create any spatial indexing mechanism.

Example D-1 Implementing the Spatial Provider Class

package spatialprovider.samples;
 
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.File;
import java.util.ArrayList;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.Vector;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import oracle.mapviewer.share.Field;
import oracle.mapviewer.share.ext.SDataProvider;
import oracle.mapviewer.share.ext.SDataSet;
import oracle.mapviewer.share.ext.SObject;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import oracle.spatial.geometry.JGeometry;
import oracle.spatial.util.GML;
 
public class CustomSpatialProviderSample implements SDataProvider
{
 ...
 
 /**
  * Constructor.
  */
 public CustomSpatialProviderSample()
 {
   ...
 }
 
 /**
  * Returns the initialization parameters for the provider.
  * The "datadir" parameter should be registered on MapViewer
  * configuration file and can be used to access the data.
  * @return
  */
 public String[] getInitParameterNames()
 {
   return new String[]{ "datadir" };
 }
 
 /**
  * Returns runtime parameter names. Runtime parameters are additional parameters
  * that the provider may use when retrieving the data objects.
  * @return
  */
 public String[] getRuntimeParameterNames()
 {
   return new String[]{ "filename" };
 }
 
 /**
  * Initializes the provider
  * @param params  init properties
  * @return
  */
 public boolean init(Properties params)
 {
   dataDirectory = null;
     if(params == null)
     return true;
     dataDirectory = params.getProperty("datadir");
   if(dataDirectory == null || dataDirectory.trim().length() == 0)
   {
     // try upper case
     dataDirectory = params.getProperty("DATADIR");
     if(dataDirectory == null || dataDirectory.trim().length() == 0)
       System.out.println("FINE: Init properties does not define \"datadir\" parameter.");
   }
       return true;
 }
 
 /**
  * Returns the data set (geometries plus attributes) that intersects the
  * query window. In this sample the data is parsed just once and
  * there is no spatial index for searching. The search is sequential.
  * @param queryWin  search area
  * @param nonSpatialColumns   attribute columns
  * @param params      runtime properties
  * @return
  */
 public SDataSet buildDataSet(Rectangle2D queryWin,
                              String []nonSpatialColumns,
                              Properties params)
 {
   if(!dataParsed)
   {
     dataParsed = parseData(params);
     if(!dataParsed)
       return null;
   }
     if(geometries.size() == 0)
     return null;
 
   SDataSet dataset = new SDataSet();
     boolean fullExtent = isFullExtent(queryWin);
     if(fullExtent)
   {
     for(int i=0;i<geometries.size();i++)
     {
       JGeometry geom = (JGeometry)geometries.get(i);
       SObject obj = new SObject(geom,getGeometryAttributes(nonSpatialColumns,i));
       dataset.addObject(obj);      }
   }
   else
   {
     for(int i=0;i<geometries.size();i++)
     {
       JGeometry geom = (JGeometry)geometries.get(i);
       double []mbr = geom.getMBR();
       if(mbr == null)
         continue;
       Rectangle2D.Double rect = new Rectangle2D.Double(mbr[0],mbr[1],
                                   mbr[2]-mbr[0],
                                   mbr[3]-mbr[1]);
             if(rect.getWidth() == 0. && rect.getHeight() == 0.)
       {
         Point2D.Double pt = new Point2D.Double(mbr[0],mbr[1]);
         if(queryWin.contains(pt))
         {
           SObject obj = new SObject(geom,getGeometryAttributes(nonSpatialColumns,i));
           dataset.addObject(obj);                    }
       }
       else if(queryWin.contains(rect) || queryWin.intersects(rect))
       {
         SObject obj = new SObject(geom,getGeometryAttributes(nonSpatialColumns,i));
         dataset.addObject(obj);
       }
     }        }
     if(dataset.getSize() == 0)
     return null;
       return dataset;
 }
 
 /**
  * Returns the data provider attribute list.
  * @return
  */
 public Field[] getAttributeList(Properties params)
 {
   if(!dataParsed)
   {
     dataParsed = parseData(params);
     if(!dataParsed)
       return null;
   }
     if(attributes.size() == 0)
     return null;
 
   return (Field[])attributes.toArray(new Field[attributes.size()]);
 }
 
 /**
  * Returns the data extents.
  * @return
  */
 public Rectangle2D getDataExtents(Properties params)
 {
   if(!dataParsed)
   {
     dataParsed = parseData(params);
     if(!dataParsed)
       return null;
   }
     if(extents == null || extents.length < 4)
     return null;
   else
     return new Rectangle2D.Double(extents[0],extents[1],
                                   extents[2]-extents[0],
                                   extents[3]-extents[1]);
 }
 
 /**
  * Builds a spatial index for the data. In this sample there is no
  * spatial index. The data is loaded into memory when data is parsed.
  * @return
  */
 public boolean buildSpatialIndex(Properties params)
 {
   return true;
 }
 
}

After you have implemented the provider code, compile it and generate a jar file with this compiled class. The file spatialprovider.jar in the demo directory contains the compiled version of this sample code, and you can use it directly. Copy this jar file to a directory that is part of MapViewer's CLASSPATH definition, such as the web/WB-INF/lib directory.

D.2 Registering the Spatial Provider with MapViewer

To register the spatial provider with MapViewer, add the following in the spatial provider section of the MapViewer configuration file, and then restart MapViewer:

<s_data_provider
  id="xmlProvider"
  class="spatialprovider.samples.CustomSpatialProviderSample"
  >
  <parameters>
    <parameter name="datadir" value="/temp/data" />
  </parameters>
</s_data_provider>

When you restart MapViewer, you should see a console message that the spatial provider has been registered. For example:

2007-10-01 14:30:31.109 NOTIFICATION Spatial Provider xmlProvider has been registered.

D.3 Rendering the External Spatial Data

To enable you to render the sample external spatial data that comes with MapViewer kit., create a data source pointing to this data Example D-2 is an XML request that contains a dynamic custom geometry theme.

Example D-2 Map Request to Render External Spatial Data

<?xml version="1.0" standalone="yes"?>
<map_request
 title="Custom Geometry Theme"
 datasource="mvdemo"
 width="500"
 height="400"
 bgcolor="#a6caf0"
 antialiase="true"
 format="PNG_STREAM"
>
 <center size="40">
   <geoFeature>
     <geometricProperty typeName="center">
       <Point>
         <coordinates>-90,32</coordinates>
       </Point>
     </geometricProperty>
   </geoFeature>
 </center>
 
 <themes>
   <theme name="custom_theme" >
      <custom_geom_theme
        provider_id="xmlProvider"
        srid="8307"
        render_style="M.CIRCLE"
        label_column="city"
        label_style="T.CITY NAME"
        datasource="mvdemo">
      <parameters>
       <parameter name="filename" value="/lbs/demo/spatialprovider/us_bigcities.xml"/>
      </parameters>
     </custom_geom_theme>
   </theme>
 </themes>
</map_request>

In Example D-2, the file name in the <parameter> element points to /lbs/demo/spatialprovider/us_bigcities.xml. If the file path is not accessible to MapViewer, the map request may generate error messages in the log file, such as the following:

07/09/28 10:26:47 ParseData: Can not access file: /lbs/demo/spatialprovider/us_bigcities.xml
07/09/28 10:26:47 ParseData: File to be parsed: /temp/data\us_bigcities.xml
07/09/28 10:26:47 ParseData: File can not be accessed on provider data directory. Copy files there.

When MapViewer searches for the file, it first tries to access the file using the original theme definition parameter; and if that fails, it tries the data directory defined in the MapViewer configuration file (/temp/data in the preceding example error messages). Therefore, if the original theme definition data path is not accessible to MapViewer, copy the data files to the directory defined in the configuration file before you issue the map request.

If MapViewer can find the data file, the map request inExample D-2 should generate an image like the one in Figure D-1.

Figure D-1 Map Image Using Custom Geometry Theme and External Spatial Data

Description of Figure D-1 follows
Description of "Figure D-1 Map Image Using Custom Geometry Theme and External Spatial Data"