Wednesday Oct 12, 2011

BIP and Mapviewer Mash Up IV

Dang folks, we're on part four already, things start to get really groovy now.
If you're jumping in at part four, here are the previous episodes:

The current approach to get the maps into the output using encoded concatenated URL strings works but if you need to make changes to the map request call it gets fiddly and annoying quite quickly and if you need to drop another map into another output you'll need to embed the code into the second RTF template. What if we could simplify all of this and create a 'map request broker' that sat between the template and the map server? Even better if that broker could handle any map request and support as many layout templates that I want. I have just the solution built and ready to roll.

Of course all three components might be running on the same physical server inside the same weblogic domain. Its a pretty simple concept and it makes life so much easier when embedding a map into an output. The servlet is called from the RTF template using a URL that is constructed on the fly; we can also pass parameters on the URL to influence the map. For my example those parameters include the mapviewer server and port (to make the template portable) the map title, what needs to be mapped and the map request file to be used. The servlet receives the request grabs the parameters off the URL. It then reads the map request files and substitutes the appropriate variable values and then makes the call to mapviewer requesting a map. Once it receives the map it streams the image back to the template for rendering by BI Publisher. No messy encoded URL's minimal concatenation in the template to create the servlet URL and I have fine control over the map request. Best of all any template can call the same servlet, we just need the base map request on the server.

The Breakdown

There are three components to consider, what's in the RTF template the servlet code and the map request. Lets look at the map request first.

<?xml version="1.0" encoding="UTF-8"?>
<map_request title="param1" 
 basemap="world_map" 
 datasource = "obiee_navteq_sample" 
 width="640" 
 height="480" 
 bgcolor="#a6cae0" 
 antialiase="false" 
 format="param2">
 <center size="45">
  <geoFeature>
   <geometricProperty typeName="center">
    <Point srsName="SDO:8307">
     <coordinates>-96, 34</coordinates>
    </Point>
   </geometricProperty>
  </geoFeature>
 </center>
 <legend bgstyle="fill:#ffffff;stroke:#ff0000" 
         profile="MEDIUM" 
         position="SOUTH_WEST">
         <column>
          <entry text="Number of Renal Disease Cases:"/>
          <entry style="V.POPULATION_COUNTY" tab="1"/>
         </column>
 </legend>
 <themes>
  <theme name="theme1" min_scale="5.0E7" max_scale="0.0">
   <jdbc_query 
    datasource="obiee_navteq_sample" 
    jdbc_srid="8307" 
    spatial_column="geometry" 
    render_style="OBIEE_NAVTEQ:V.POPULATION_COUNTY">
    SELECT geometry, param3 
    from obiee_state 
    where iso_country_code='USA'</jdbc_query>
  </theme>
 </themes>
</map_request>

Its just a well formed XML file. This has loads going on and to find out more you're going to have to hit the books - the mapviewer documentation is here - check out the XML API section for some samples. I was playing with it last night and found that the 'size' attribute under the 'center' element controls the zoom level ... go figure. But combining that with the 'coordinates' value under geoFeature and some nifty LOVs in your BIP report and folks can move around a map and zoom in and out. More on that later.

 Testing your map requests is simple enough inside the mapviewer web console http://server:port/mapviewer >> Requests. Just copy, paste and submit.

The other features of note are my parameters highlighted in blue. Before I pass this request on to the mapviewer server I do a search and replace on the 3 parameters. You'll see that in the servlet code. Remember, this XML is not hard coded into the servlet, its a separate XML file in a 'resource' directory that is read by the servlet. Taking this approach I can re-use the servlet for multiple map requests.

Next, the servlet code, ready for my awesome java skills? Read on ...

package oracle.bipmapper;

//Read-write libs
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
//Handle the URL call
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
//Servlet libs
import javax.servlet.*;
import javax.servlet.http.*;


public class bipmap extends HttpServlet {
    private String CONTENT_TYPE = "";

    public void init(ServletConfig config) throws ServletException {
        super.init(config);
    }

    public void doGet(HttpServletRequest request,
                      HttpServletResponse response) throws ServletException,
                                                           IOException {

        doPost(request, response);
    }

    public void doPost(HttpServletRequest request,
                       HttpServletResponse response) throws ServletException,
                                                            IOException {

        ((1)) - Fetch the URL params. Check that we have values, if not, assign defaults.
        String measure1 =
            (request.getParameter("a1").toString() == "") ? "sqkm" :
            request.getParameter("a1");
        String server =
            (request.getParameter("serv").toString() == "") ? "75.101.156.237" :
            request.getParameter("serv");
        String port =
            (request.getParameter("port").toString() == "") ? "9704" :
            request.getParameter("port");
        String title =
            (request.getParameter("title").toString() == "") ? "Default Title" :
            request.getParameter("title");
        String format =
            (request.getParameter("format").toString() == "") ? "PNG_STREAM" :
            request.getParameter("format");
        String filename =
            (request.getParameter("file").toString() == "") ? "mapreq.xml" :
            request.getParameter("file") + ".xml";

        // Needed objects for the mapviewer call
        HttpURLConnection huc = null;
        URL mapAddress = null;
        ((2)) Set the content format based on the format parameter value
        // The else value is just text, thats for the SVG support
        if (format == "PNG_STREAM") {
            CONTENT_TYPE = "image/png";
        } else if (format == "JPG_STREAM") {
            CONTENT_TYPE = "image/jpg";
        } else {
            CONTENT_TYPE = "text/javascript";
        }

        response.setContentType(CONTENT_TYPE);

        ((3)) //Find the request file
        ServletContext context = request.getSession().getServletContext();
        String separator = System.getProperty("file.separator");
        final String TMPDIR =
            context.getAttribute("javax.servlet.context.tempdir").toString();
        // With WLS the tempdir drops you into the _WL_user/APP_NAME/RANDOM_DIR/public
        // we need the 'war' directory to find the resource directory
        String reqFile =
            TMPDIR.substring(0, TMPDIR.lastIndexOf(separator)) + "/war/resource/" +
            filename;

        ((4))// Construct the start of the URL map request
        String mapURL =
            "http://" + server + ":" + port + "/mapviewer/omserver?xml_request=";
        ((5)) // Load the XML request file
        String xmlReq = getMapReq(reqFile);
        ((6)) // Look for a replace the parameters in the XML request.
        xmlReq = xmlReq.replace("param1", title);
        xmlReq = xmlReq.replace("param2", format);
        xmlReq = xmlReq.replace("param3", measure1);

        InputStream is = null;

        ((7)) // Fetch the output stream from the mapviewer request
        try {
            mapAddress = new URL(mapURL + URLEncoder.encode(xmlReq));
            huc = (HttpURLConnection)mapAddress.openConnection();
            huc.setDoOutput(true);
            huc.setDoInput(true);
            huc.setUseCaches(false);
            huc.setRequestMethod("POST"); //Use HTTP POST method.
            is = huc.getInputStream();
            // Write the image stream back to the caller ie BIP in the template layer
            OutputStream out = response.getOutputStream();
            byte[] buf = new byte[1024];
            int len = 0;
            while ((len = is.read(buf)) >= 0) {
                out.write(buf, 0, len);
            }
            is.close();
            out.close();
        } catch (Exception e) {
            e.printStackTrace(System.err);
            System.exit(1);
        }
    }


    private static String getMapReq(String fname) throws java.io.IOException {
        ((5.5)) // Fetch the XML request file and load it into a string object
        byte[] buffer = new byte[(int)new File(fname).length()];
        BufferedInputStream f = null;
        try {
            f = new BufferedInputStream(new FileInputStream(fname));
            f.read(buffer);
        } finally {
            if (f != null)
                try {
                    f.close();
                } catch (IOException ignored) {
                }
        }
        return new String(buffer);
    }
}

I have highlighted the important bits:

(1) Standard stuff here, pulling the parameters off the URL and assigning them to string variables. For the un-initiated each entry is an inline if statement.

(2) Im checking the requested output so that I can set the appropriate content type. In my example Im supporting PNG, JPG and SVG. The SVG is handled in the else condition. More on that in another post.

(3) Here I fetch the request XML file. Its in a 'resource' directory in the deployment. This was a bit of a pain, I expected the 'javax.servlet.context.tempdir' to return he root of the web application but in weblogic you end up in a 'public' directory under the root. My resource directory is actually under the 'war' directory under the root so this piece of code:

ServletContext context = request.getSession().getServletContext();
String separator = System.getProperty("file.separator");
final String TMPDIR = context.getAttribute("javax.servlet.context.tempdir").toString();
String reqFile =
            TMPDIR.substring(0, TMPDIR.lastIndexOf(separator)) + "/war/resource/" +
            filename;

does some substringing and concatentation to build a path to the correct directory. Im sure there is an easier way but it works for now. Note the user of the 'separator' string so that the servlet can be deployed to windblows, unix or linux. I had a devil of a time catching that one. I developed on Linux but it would not run on windblows until I grabbed the appropriate separator ... grrr!

(4) Starting to construct the URL request for the XML map request

(5) Fetching the map request XML from the file in the resource directory

  (5.5) This is the function to read the map request file and load it int a string variable

(6) A simple replace for each of the parameters created in the map request. We could have gotten fancier here with an XML parser but its not a huge string and there are three replacements to do. Its not a slow process.

(7) Now we construct the full http request for the map. Note the encoding of the xml request string, thats a must. Once we get the result we just have the servlet write it back to the response object for BIP to render.

Nothing earth shattering, could it be better, sure, if you fail to pass enough parameters you get an ugly null pointer exception which could be handled better but just make sure you pass enough parameters :0)

Finally the RTF template, its a case of declaring parameters for the report parameters and then building the URL request to the servlet. You can see the fields in the graphic on the right. The only ones of note are:

  • The file parameter, allows you to point to any deployed request file
  • The mURL that constructs the URL. Its just a big long concat with the name|value pairs for the servlet parameters.

Then just use the external-graphic place holder as we have before to reference the mURL parameter. The curly braces {} get BIP to pre-process the value before processing the graphic.

Finally ...

Thats it, put the three pieces together et voila, you have a mapping solution that's more manageable and configurable. You can of course increase the number of parameterized values in the xml map request.

I have bundled up the JDeveloper (11.1.1.5) project as it is ... its only really got the servlet and XML request in it. You can just copy and paste into your own project - get the project here. I have also zipped the 11g BIP report with the RTF template (Mapviewer4.rtf) get that here. For 10g'ers just unzip the xdoz file to get at the RTF template.

So, we can now get maps into our BIP outputs, the quality is pretty good but it could be better. Having spent some time digging around in the mapviewer docs I spotted that they support SVG outputs ... more on that next time.

About

Follow bipublisher on Twitter Find Us on Facebook BI Publisher Youtube ChannelDiscussion Forum

Join our BI Publisher community to get the most and keep updated with the latest news, How-to, Solutions! Share your feedback and let us hear your voice @bipublisher on Twitter, on our official Facebook page, and Youtube!

Search

Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today