Adding Location Based Services for the Web - Part 2

By Robert Chou

Server Connectivity

Kivera and Webraska use two different methods of server connectivity.Kivera uses a stateful protocol while Webraska uses a stateless protocol.While a stateless protocol could scale to support more users, a persistent connection could be pooled for reuse (much like database connections) resulting in less overhead.

Accessing Kivera's service is analogous to using a database connection.You connect, make some requests, and then disconnect (see Listing 1).The NWCClient object represents a connection to a Kivera server which you can pool.The object has an isConnected() method which checks for dead connections.

Listing 1 - Connecting to Kivera

    /**
     * Returns a new connection to Kivera's LBS services.
     * Don't forget to call disconnect() when you are done!
     */
    
NWCClient getNWCClient()
    {
        
NWCClient klsClient null;

        
try
        
{
            
klsClient = new NWCClientProjection.UNPROJECTED );

            
// connect client to server
            
int status klsClient.connectklsHostklsPort );
            if( 
status != NWCReturnCode.NW_OK )
            {
                
throw new ServletException"connect failed: " status );
            }

            
// get available databases
            
dbs klsClient.getMdbList();
            if( 
dbs == null )
            {
                
throw new ServletException"no dbs found" );
            }

            
// select db to use
            
dbPrefs = new int[dbs.length];
            for( 
int i=0i<dbs.lengthi++ )
            {
                
dbPrefs[i] = dbs[i].id;
            }

        }
        
catchException e )
        {
            if( 
klsClient != null )
            {
                
try
                
{
                    
klsClient.disconnect();
                    
klsClient null;
                }
                
catchException ignore ) {}
            }
        }

        return 
klsClient;
    }

            
klsClient getNWCClient();
            ...
            
klsClient.disconnect();

Each request to Webraska's server makes a separate connection.You start by creating a GenericRequest object which you then populate with request parameters.Since each request is stateless, you have to include common parameters like your account name and password for every request.You issue the request by converting it to an URL and then can making a HTTP request to that URL (see Listing 2).The server response is always in XML format.Webraska provides an API to retrieve the XML data as objects.Since the input and output of each server call is transparent to developers, you could write your own API to access Webraska if you're developing for a platform they didn't support.

Listing 2 - Connecting to Webraska

    /**
     * All GenericRequest objects share some common parameters.
     * This method returns a new GenericRequest object
     * with these common parameters already set (ie. user name,
     * password, and server mode).
     */
    
GenericRequest getGenericRequestRequestBuilder reqBuilder )
    
throws  MalformedURLException,
            
ParameterFormatException,
            
NoSuchParameterTypeException,
            
IllegalParameterTypeException
    
{
        
GenericRequest  objReturn null;

        
objReturn = new GenericRequestreqBuilder.getTabSeparator(), "http://na12.wbska.com/gns" );

        
// add common parameters
        
addStringParameterToRequestobjReturnreqBuilder"user"gnsUser );
        
addStringParameterToRequestobjReturnreqBuilder"pass"gnsPassword );
        
addStringParameterToRequestobjReturnreqBuilder"mode"gnsMode );

        return 
objReturn;
    }

    
/**
     * Convenience method to retrieve a XMLResponse object from a
     * GenericRequest object.
     */
    
XMLResponse doRequestGenericRequest doRequestRequestBuilder reqBuilder )
    {
        
XMLResponse xmlResponse null;

        
try
        
{
            
doRequest.checkSyntaxreqBuilder );
            
String httpReq doRequest.getHttpRequest();
            
System.out.println"httpReq = " httpReq );

            
URL url = new URLhttpReq );
            
URLConnection connection url.openConnection();

            
InputStreamReader reader = new InputStreamReaderconnection.getInputStream() );
            
xmlResponse XMLResponse.unmarshalreader );
        }
        
catchException e ) {}

        return 
xmlResponse;
    }

Geocoding

In order to determine the driving distance for the shuttle service, you'll need to geocode both the dealer's address and the customer's address into vendor specific navigation points.The geocoded address will contain both the address and it's coordinates.The geocoded address information may contain fields which the customer did not enter (i.e.Zip Code).I'll show the geocoded address in the result JSP pages.

For Kivera, a NWCGeocode object contains both the address and it's coordinates.To get this object, create a geocoding request object (NWCGeoReq), populate it with request information, and pass it to NWCClient's geocode method (see Listing 3).

Listing 3 - Geocoding with Kivera

    /**
     * Geocode an address into a LBS vendor's navigation point.
     *
     * Be sure to check status != NWCReturnCode.NW_OK
     */
    
NWCGeocode getGeocodeNWCClient klsClientString line1String line2 )
    {
        
NWCGeocode geo null;

        
try
        
{
            
NWCGeoReq   geoReq = new NWCGeoReq(
                
dbPrefs,
                
line1,  // address line 1
                
line2,  // address line 2
                
"0",   // address line 3 (n/a)
                
"USA",  // country
                
null,//cline,  // correction line
                
0,     // offset
                
NWCClient.NW_UNIT_MILE,   // offset units
                
0,      // flags (n/a)
                
0,//cfield, // correction type
                
5   // options limit
            
);

            
// perform geocode
            
geo klsClient.geocode(geoReq);

        }
        
catchException e ) {}

        return 
geo;
    }

Webraska's equivalent is a Navpos object.It also contains the address and it's coordinates.To get this object, create a generic request object (GenericRequest), populate it with geocoding parameters, make a HTTP connection, unmarshal the XML reply into a XMLResponse object, and finally extract it from the XMLResponse object (see Listing 4).

Listing 4 - Geocoding with Webraska

    /**
     * Convenience method to convert an address to Webraska's Navpos object.
     * Actually, it returns the XMLResponse object which may contain a Navpos.
     */
    
XMLResponse getNavposResponseRequestBuilder reqBuilderString strNumString strStreetString strCityString strStateString strCode )
    {
        
XMLResponse xmlResponse null;

        
try
        
{
            
GenericRequest  gnsRequest getGenericRequestreqBuilder );
            
addStringParameterToRequestgnsRequestreqBuilder"function""geo" );

            
// add form vars
            
if( strNum != null && strNum.length() > )
            {
                
addStringParameterToRequestgnsRequestreqBuilder"streetnum"strNum );
            }
            if( 
strStreet != null && strStreet.length() > )
            {
                
addStringParameterToRequestgnsRequestreqBuilder"street"strStreet );
            }
            if( 
strCity != null && strCity.length() > )
            {
                
addStringParameterToRequestgnsRequestreqBuilder"city"strCity );
            }
            if( 
strState != null && strState.length() > )
            {
                
addStringParameterToRequestgnsRequestreqBuilder"state"strState );
            }
            if( 
strCode != null && strCode.length() > )
            {
                
addStringParameterToRequestgnsRequestreqBuilder"code"strCode );
            }

            
xmlResponse doRequestgnsRequestreqBuilder );
        }
        
catchException e ) {}
        
        return 
xmlResponse;
    }

            
XMLResponse clientResponse getNavposResponsereqBuilderstrNumstrStreetformVars.get("City"), formVars.get("State"), formVars.get("Zip Code") );
            if( 
clientResponse == null )
            {
                
formErrors.setFieldError"GNS data""no server response" );
            }
            else
            {
                
Data    gnsData clientResponse.getData();
                if( 
gnsData == null )
                {
                    
// no city or street found
                    
Message msg clientResponse.getStatus().getMessage();
                    
formErrors.setFieldErrormsg.getCode(), msg.getContent() );
                }
                else
                {
                    List    
gnsData.getList();
                    
//out.println( l.getNavidCount() + ", " + l.getNavposCount() + ", " + l.getSize() + "<br>" );
                    
if( l.getNavposCount() < )
                    {
                        
formErrors.setFieldError"GNS data""no location found" );
                    }
                    else
                    {
                        
navposClient l.getNavpos);
                    }
                }
            }

During my testing with Webraska's geocoding, I found it was not able to resolve addresses with "Avenue", Street", "Drive", "Common" or with other street types fully spelled out.If you use abbreviations instead ("Ave", "Dr" or "Com") then it would work.Kivera did not have this problem.For the Webraska sample, I added a quick lookup to convert the above words to abbreviations before attempting to geocode.

Routing

With the two navigation points, you can generate a routing which contains the total distance, total time, and driving directions for a particular path.For the shuttle service, you'll only use the total distance.For the driving directions, you'll need all this information and more.

Kivera's NWCRTOut object represents the routing information mentioned above.To retrieve this object, create a routing request object (NWCRTReq), populate it with request information, and pass it to NWCClient's calculateRoute() method (see Listing 5).

Listing 5 - Routing with Kivera

    /**
     * Perform routing on the 2 given points.
     *
     * Be sure to check status != NWCReturnCode.NW_OK
     */
    
NWCRTOut getRouteNWCClient klsClientNWCRTPoint pointStartNWCRTPoint pointStop )
    {
        
NWCRTOut    route null;

        
try
        
{
            
NWCRTReq    routeReq = new NWCRTReq(
                ( 
NWCRTReq.NW_RT_EXPL |     /* produce explication */
                
NWCRTReq.NW_RT_SHAPE ),    /* produce shape data */
                
00,                        /* no avoids, edits */
                
NWCRTReq.NW_LANG_ENGLISH0/* first explication
                                          * language English,
                                          * second explication
                                          * language not
                                          * supported */
                
NWCClient.NW_UNIT_MILE' ',/* first explication unit
                                          * feet, second
                                          * explication unit not
                                          * supported */
                
dbPrefs,        /* databases to be used
                                          * in routing, most
                                          * preferred first */
                
pointStart,                      /* route origin */
                
pointStop,                 /* route destination */
                
null                       /* no stopovers/via points */
            
);

            
route klsClient.calculateRouterouteReq );

        }
        
catchException e ) {}

        return 
route;
    }

In Webraska's case, it's equivalent is a Roadmap object.The process of requesting and retrieving this object is similar to that of any other object: create a generic request object, populate it with routing parameters and start/stop points, make a HTTP connection, unmarshal the XML reply into a XMLResponse object, and finally extract it from the XMLResponse object (see Listing 6).

Listing 6 - Routing with Webraska

    GenericRequest  routeRequest getGenericRequestreqBuilder );
    
addStringParameterToRequestrouteRequestreqBuilder"function""route" );

    
// add form vars
    
addStringParameterToRequestrouteRequestreqBuilder"navidstart"navposClient.getNavid().getContent() );
    
addStringParameterToRequestrouteRequestreqBuilder"navidend"navposDealer.getNavid().getContent() );
    
addIntegerParameterToRequestrouteRequestreqBuilder"transportmode");

    
XMLResponse routeResponse doRequestrouteRequestreqBuilder );
    if( 
routeResponse == null )
    {
        
formErrors.setFieldError"GNS data""no server response" );
    }
    else
    {
        
Data    gnsData routeResponse.getData();
        if( 
gnsData == null )
        {
            
// no routing available
            
Message msg routeResponse.getStatus().getMessage();
            
formErrors.setFieldErrormsg.getCode(), msg.getContent() );
        }
        else
        {
            
// get all needed information
            
roadmap gnsData.getRoadmap();
            
boundingBox gnsData.getBoundingbox();
            
strSession gnsData.getId();
            if( 
roadmap == null ||
                
boundingBox == null ||
                
strSession == null ||
                
strSession.length() == )
            {
                
// we don't have everything needed to our route display
                
formErrors.setFieldError"GNS data""missing router data" );
            }
        }
    }

The total distance is stored in meters for both vendors.Kivera stores the value in a public NWCRTOut.rt_length variable.Webraska exposes the value in the Roadmap.getLength() method.Total travel time is exposed similarly.On a side note, I normally avoid allowing direct access to public member variables in public APIs for design reasons.

Now you have everything you need to implement the shuttle service functionality.For driving directions, you can simply copy and then extend the code some more.For routing, you need to extract the driving directions in human readable format.For Kivera, this is easy because the NWCRTOut object already contains readable directions (see Listing 7).For Webraska, you need to translate the data into a semi-readable format (see Listing 8).It would be nice if Webraska provided a utility class to help translate the directions so every developer doesn't translate it differently.The documentation is weak to nonexistent for this task.

Listing 7 - Driving Directions for Kivera

<table border="1" cellpadding="0" cellspacing="0">
<%
    if( 
route != null )
    {
        
NWCRTManv   elements[] = route.rt_manv;
        
// last element is a summary line
        
for( int i=0i<elements.length-1i++ )
        {
            
NWCRTManv   el = (NWCRTManv)elements[i];
            
StringBuffer    line = new StringBuffer"<tr><td>");

            
line.appendi+);
            
line.append"</td><td>" );
            
line.appendel.text );
            
line.append"</td></tr>" );
            
out.printlnline.toString() );
        }
    }
%>
</
table>

Listing 8 - Driving Directions for Webraska

<table border="1" cellpadding="0" cellspacing="0">
<%
    if( 
roadmap != null )
    {
        
Intersection    lastIntersection null;
        
String          strLastDirection null;
        
RoadmapElement  elements[] = roadmap.getRoadmapElement();
        
int             iStep 0;
        for( 
int i=0i<elements.lengthi++ )
        {
            
RoadmapElement  el = (


Published Friday, January 4th, 2002

Written by Robert Chou



If you liked this article subscribe to our newsletter...stay informed on the latest geospatial technology

© 2016 Directions Media. All Rights Reserved.