Adding Location Based Services for the Web - Part 2

January 4, 2002
Share

Sharing is Caring

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 NWCClient( Projection.UNPROJECTED );

            // connect client to server
            int status = klsClient.connect( klsHost, klsPort );
            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=0; i<dbs.length; i++ )
            {
                dbPrefs[i] = dbs[i].id;
            }

        }
        catch( Exception e )
        {
            if( klsClient != null )
            {
                try
                {
                    klsClient.disconnect();
                    klsClient = null;
                }
                catch( Exception 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 getGenericRequest( RequestBuilder reqBuilder )
    throws  MalformedURLException,
            ParameterFormatException,
            NoSuchParameterTypeException,
            IllegalParameterTypeException
    {
        GenericRequest  objReturn = null;

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

        // add common parameters
        addStringParameterToRequest( objReturn, reqBuilder, "user", gnsUser );
        addStringParameterToRequest( objReturn, reqBuilder, "pass", gnsPassword );
        addStringParameterToRequest( objReturn, reqBuilder, "mode", gnsMode );

        return objReturn;
    }

    /**
     * Convenience method to retrieve a XMLResponse object from a
     * GenericRequest object.
     */
    XMLResponse doRequest( GenericRequest doRequest, RequestBuilder reqBuilder )
    {
        XMLResponse xmlResponse = null;

        try
        {
            doRequest.checkSyntax( reqBuilder );
            String httpReq = doRequest.getHttpRequest();
            System.out.println( "httpReq = " + httpReq );

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

            InputStreamReader reader = new InputStreamReader( connection.getInputStream() );
            xmlResponse = XMLResponse.unmarshal( reader );
        }
        catch( Exception 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 getGeocode( NWCClient klsClient, String line1, String 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);

        }
        catch( Exception 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 getNavposResponse( RequestBuilder reqBuilder, String strNum, String strStreet, String strCity, String strState, String strCode )
    {
        XMLResponse xmlResponse = null;

        try
        {
            GenericRequest  gnsRequest = getGenericRequest( reqBuilder );
            addStringParameterToRequest( gnsRequest, reqBuilder, "function", "geo" );

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

            xmlResponse = doRequest( gnsRequest, reqBuilder );
        }
        catch( Exception e ) {}
        
        return xmlResponse;
    }

            XMLResponse clientResponse = getNavposResponse( reqBuilder, strNum, strStreet, formVars.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.setFieldError( msg.getCode(), msg.getContent() );
                }
                else
                {
                    List    l = gnsData.getList();
                    //out.println( l.getNavidCount() + ", " + l.getNavposCount() + ", " + l.getSize() + "<br>" );
                    if( l.getNavposCount() < 1 )
                    {
                        formErrors.setFieldError( "GNS data", "no location found" );
                    }
                    else
                    {
                        navposClient = l.getNavpos( 0 );
                    }
                }
            }

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 getRoute( NWCClient klsClient, NWCRTPoint pointStart, NWCRTPoint pointStop )
    {
        NWCRTOut    route = null;

        try
        {
            NWCRTReq    routeReq = new NWCRTReq(
                ( NWCRTReq.NW_RT_EXPL |     /* produce explication */
                NWCRTReq.NW_RT_SHAPE ),    /* produce shape data */
                0, 0,                        /* no avoids, edits */
                NWCRTReq.NW_LANG_ENGLISH, 0, /* 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.calculateRoute( routeReq );

        }
        catch( Exception 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 = getGenericRequest( reqBuilder );
    addStringParameterToRequest( routeRequest, reqBuilder, "function", "route" );

    // add form vars
    addStringParameterToRequest( routeRequest, reqBuilder, "navidstart", navposClient.getNavid().getContent() );
    addStringParameterToRequest( routeRequest, reqBuilder, "navidend", navposDealer.getNavid().getContent() );
    addIntegerParameterToRequest( routeRequest, reqBuilder, "transportmode", 1 );

    XMLResponse routeResponse = doRequest( routeRequest, reqBuilder );
    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.setFieldError( msg.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() == 0 )
            {
                // 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=0; i<elements.length-1; i++ )
        {
            NWCRTManv   el = (NWCRTManv)elements[i];
            StringBuffer    line = new StringBuffer( "<tr><td>");

            line.append( i+1 );
            line.append( "</td><td>" );
            line.append( el.text );
            line.append( "</td></tr>" );
            out.println( line.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=0; i<elements.length; i++ )
        {
            RoadmapElement  el = (

Share

Sharing is Caring


Geospatial Newsletters

Keep up to date with the latest geospatial trends!

Sign up

Search DM

Get Directions Magazine delivered to you
Please enter a valid email address
Please let us know that you're not a robot by using reCAPTCHA.
Sorry, there was a problem submitting your sign up request. Please try again or email editors@directionsmag.com

Thank You! We'll email you to verify your address.

In order to complete the subscription process, simply check your inbox and click on the link in the email we have just sent you. If it is not there, please check your junk mail folder.

Thank you!

It looks like you're already subscribed.

If you still experience difficulties subscribing to our newsletters, please contact us at editors@directionsmag.com