Peter is an assistant professor of computer science and mathematics at Rivier College in Nashua, New Hampshire.
At first glance, people working in fields such as geology, civil engineering, aviation, tele-
communications, sales, real estate, and insurance appear to have little in common. Nonetheless, they do share the need to manipulate geographical data, then integrate that data with electronic maps.
Conventional mapping software such as MapInfo meets some of their needs and, for small geographical areas, maps can be added to AutoCAD drawings. Neither approach, however, is flexible enough for you to customize maps to include highly-detailed information or to integrate maps with other applications.
What's really needed is a mapping engine that provides a programmer's toolkit for combining mapping functions with traditional database or spreadsheet data. TerraLogics' TerraView is a toolkit that does just that by providing a library of C function calls for displaying and manipulating maps. The library works with several different UNIX platforms (VAX/VMS, ULTRIX, SCO, and X Window), Macintosh, Microsoft Windows, and text environments such as DOS (for which it provides its own basic windowing systems). In addition to C, the library functions can be called from Basic, Fortran, Pascal, and Cobol.
To examine TerraView, I collected information on manufacturing facilities in Southern New Hampshire. This is typical of a database maintained by a traffic engineer who's looking at employers to determine rush-hour traffic patterns, or who's looking for new manufacturing facility sites.
My project involved displaying facility locations and identifications on a TerraView map of the area (TerraLogics provides street-level maps) as shown in Figure 1. This required getting data to a TerraView application, bringing up the appropriate map, and displaying the symbols in the proper locations. Since TerraView doesn't include data management or full-fledged user interface layout capabilities, it's necessary to either build them in by hand (a full development effort), or to make use of already-existing database development environments. I opted for the latter.
I chose Microsoft Access as the target platform for the database portion of the application because of its powerful tools for passing data to external functions and applications. To interact with a
TerraView mapping application from Access, I could either compile my
TerraView application as a DLL and link it to my Access application through the Call statement, or create a stand-alone TerraView application and call it from Access using either the RunApp or Shell instructions; see Figure 2.
Because in the future, I hope to build DDE capabilities into the TerraView application, I decided to take the RunApp approach. While the TerraView library doesn't specifically support DDE, the library is a regular Windows application, and the DDE support can be coded within the C-based application environment. For the time being, I decided to interact the old fashioned way--by writing a file out to disk and calling my TerraView application directly from Access. I was able to add DDE support by #include-ing DDE.H, which comes with Borland's Turbo C++ 3.1. (Turbo C++ had no problems compiling the Terraview shell as is.)
I began by creating Access database tables representing the company name and its location. While I had additional information (company CEO and primary products, which I included in other tables), it was name and location data I was going to plot on the map.
Because plotting by address involves a layer of complexity I hoped to avoid, I represented company locations using approximate latitudes and longitudes obtained manually from standard U.S. Geographical Service topographic maps rather than by street addresses. Plotting by street addresses makes it necessary to query the map to determine the range of addresses along a street, then match the known address to an approximate location on the map. The amount of code I'd have to write would be substantial. So that I might be able to plot with street addresses at a later time, I included street address and latitude/longitude in the database table. I considered writing my own algorithm to perform the conversion in Access, but without using the map I couldn't think of an appropriate computational approach.
I created Access input screens so that a user could easily enter, modify, and delete members of the database. One button I included in my Access user interface initiated the mapping sequence. The code behind this button created a text file, wrote out the company name and its location to the file, and called my TerraView application. This sequence was little more than a few lines of code in the Access programming language. Once the Access application was laid out, I turned to TerraView.
Creating a TerraView application is like creating any other Windows application. The WinMain function provides the basic window, processes the command-line arguments, and provides the event loop. TerraLogics includes a simple WinMain shell with the package, which I used as the basis of my own application.
While most of the other windowing routines come from TerraView, the actual application (see Listing One, page 114) is a sometimes confusing mixture of Windows and TerraView calls to drive the mapping functionality. Envision TerraView as a set of library functions. These functions can do things such as open windows, display maps, place locations (either latitude/longitude or street addresses) on the maps, and incorporate text information into the place locations. TerraView does all this (and more) from within the Windows application that you provide.
After setting up the appropriate Windows routines and interpreting the command line, I started the mapping portion of the application by initializing a window and bringing up a blank map in it. Then I loaded a map. This can be done in several ways. The WinMain function includes an argv call to read from the command line, so a map can be loaded automatically at program launch. Alternately, the shell provided by TerraLogics displays a simple command line in the empty TerraView window, which prompts the user to load a map file.
Another, more elegant, approach is to create a Windows dialog box within the TerraView shell that lets the user select a mapping file from the dialog file list. Even with no experience in doing so, there's enough sample code in Turbo C++ that adding a Select File dialog only takes a few minutes. This is both a better looking and easier way of prompting the user for a map.
However, since the mapping application was to be opened from an Access application, I supplied the name of the map from Access, and placed it into the Call statement as an argument to the map. Given a more robust database application, it's possible to set up a dialog box to let the user select the appropriate map, or even to have the application itself choose the proper map, based on the locations included in the data to be mapped.
Maps are loaded as overlays onto the blank map set up with the window. It's possible to load several different maps into the same window using overlays, but the most common use of the overlay is to add features to a base map. These features may include highways, railroads, natural features, or structures such as buildings or power lines. I used an overlay to add company locations to the map. It's also possible to include code so that the user can control the addition, deletion, or layering of overlays from a pull-down menu.
The TerraView application reads the data file, using a simple fopen and scanf, and loads the data into a structure inside the application. Next, the TerraView calls in Example 1 place the appropriate symbol and accompanying information onto a map overlay, which is then placed on the display. Like any Windows application, the menu structures are set up in the application resource (.RC) file. The menu operations are structured in the normal Windows fashion in C, usually with a switch statement containing calls to the appropriate functions. The functions are usually TerraView calls, although they can also be interspersed with regular Windows calls.
The final step was to use TerraLogics' IconEdit utility to create a simple triangle icon to represent the location of the companies on the map. IconEdit is a simple pixel paint application that enables you to create custom graphics to display on a map overlay. Presumably the bitmap painting capability in Windows Paintbrush can also be used to create icons, although I wasn't able to load a .BMP file into IconEdit.
I've already discovered enhancements to add to the mapping application. In addition to other ways of calling a mapping application and getting data into the application, you can scale and rotate maps, select and manipulate specific street segments, inquire about specific map features, and scroll a map.
For those of you who have used Maxis' SimCity, it is also possible (though quite ambitious) to use TerraView to create a city or geographical region simulation along these same lines. A TerraView application can be combined with a discrete-event simulator to include models of automobile traffic for examining traffic congestion. A city or regional planner could use such an application to study the effects of new development on traffic, or to examine the best locations for freeways or public transportation.
The best way to implement many of these enhancements would be via DDE. It would be nice to have a TerraView library that brings the tedious work of DDE to a higher level, to make it more convenient to the programmer. After all, this will undoubtedly be an ideal way to create a generic mapping application that could display geographic data from a wide variety of existing applications, such as spreadsheets, other relational database packages, and even word processors.
Developing a mapping application using TerraView isn't for the weak of heart. It's a full-fledged Windows application, with the addition of numerous library calls to open and display data on a map. It requires programming talent, along with a normal application development effort, to bring this type of mapping to the desktop.
The positive side of this cautionary statement is that TerraView is extremely flexible. I can envision the libraries being used in three different ways. First, users can construct a TerraView application, in much the same way I did, and communicate with other applications via a DDE interface. The TerraView application will probably be written to work with a specific application or even single data set, although it is also possible to imagine a generic mapping capability that can work with any data-driven application.
Second, TerraView calls may be compiled into a DLL and called by one or more applications specifically for mapping functions. This approach may be provided by the commercial application vendor, or it may be added on by a third party or even the end user through DLL interfaces increasingly being included in commercial products.
Third, and perhaps most interesting, the TerraView functions may be incorporated seamlessly into a commercial application to include integrated mapping. TerraLogics has a demo that includes a TerraView application integrated as a user-defined SmartButton in Lotus 1-2-3 for Windows. While this demo SmartButton macro simply calls an external TerraView application, future versions of Lotus may be compiled with this feature built in.
The combination of the TerraView mapping engine and libraries, along with inexpensive and widely available electronic maps provided by the Federal government and by commercial vendors, may herald a new type of application for handling geographic data. Not everyone has the need for geographic data but of those who do, many lack the technical ability to work with TerraView today. However, commercial developers may, in time, make these capabilities available to every user of ordinary commercial software.
stat = TvBeginFeature(ovl_sym, 0, 0, name, id);
stat = TvInsertSymbol(ovl_sym, symbol, 1, point);
stat = TvEndFeature(ovl_sym);
TvRefresh(map);
For More Information
TerraView TerraLogics Inc. 76 Northeastern Blvd., Suite 25B Nashua, NH 03062 603-889-1800
[LISTING ONE]
(Text begins on page 84.)
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <string.h>
#include <windows.h>
#include "TvTypes.h"
#include "Tv.h"
#include "windemo.h"
#define MAX_OVERLAYS 8
#define MAX_ARGS 10
struct WinEvent
{
HWND hWnd;
int code;
int x;
int y;
};
DOUBLE zoom_factor = 2.0;
file *companies;
int grid_up = 0, overlays = 0, first_screen = 1, curshow, stat;
char txtbuf[132];
TvWindowID nowin;
TvMapID map;
HANDLE hInst, hAccTable;
static DOUBLE proj_data[16];
static LONG proj_type = 0;
static TvOverlayID overlay_user[MAX_OVERLAYS], overlay_tmp = 0,
locator = 0, grid = 0,
compass = 0, scale = 0;
int PASCAL WinMain( HANDLE, HANDLE, LPSTR, int );
BOOL InitTvApplication( HANDLE );
TvWindowID InitTvWindow( VOID );
LONG FAR PASCAL TV_process( HWND, UINT, WPARAM, LONG );
BOOL FAR PASCAL Tv_About( HWND, UINT, WORD, LONG );
/***********************************************************************
This is a standard WinMain function that initializes the appropriate Windows
variables and checks and interprets the command line.
***********************************************************************/
int PASCAL WinMain( hInstance, hPrevInstance, lpCmdLine, nCmdShow )
HANDLE hInstance;
HANDLE hPrevInstance;
LPSTR lpCmdLine;
int nCmdShow;
{
TvWindowID win;
MSG msg;
LONG i, atol();
int argc = 0;
char *argv[MAX_ARGS], cmdline[80], *cp1, *cp2;
if ( !hPrevInstance )
if ( !InitTvApplication( hInstance ) )
return( (int)NULL );
hInst = hInstance;
curshow = nCmdShow;
strcpy( cmdline, "tv" );
argv[0] = cmdline;
argc++;
for( cp1 = &cmdline[strlen(cmdline)+1], cp2=lpCmdLine;
*cp2 && (argc <= MAX_ARGS);
)
{
argv[ argc++ ] = cp1;
while( *cp2 && (*cp2 != ' ') )
*cp1++ = *cp2++;
*cp1++ = '\0';
if ( *cp2 == ' ' ) cp2++;
}
TvInitialize();
/* Create blank map. All actual maps will be written as overlays to this. */
if ( !(win = InitTvWindow()) ||
!(map = TvCreateMap( win, "Map Demo Application" )) ||
!(nowin = TvCreateWindow( TvNoDevice, 0.0, 0.0, 1.0, 1.0 )) )
{
printf( "Cannot create map!\n" );
TvExit( 0 );
}
/* If no map file is entered on command line, this will open a simple prompt
in TerraView window and let user enter name of a map. My application shouldn't
display this, since I'm passing a map name from Access database */
{
char line[132];
while( (overlays == 0) || line[0] )
{
TvPrompt( map, "Enter overlay name: ", line );
if ( line[0] &&
(overlay_user[overlays++] = TvReadOverlay( line )) == TvStatusError )
{
sprintf( txtbuf, "Cannot open overlay %s. Press ENTER to exit.", line );
TvPrompt( map, txtbuf, line );
TvExit( 0 );
}
}
}
/* Read overlays from command-line */
while( (overlays < MAX_OVERLAYS) && (overlays < (argc-1)) )
if ( (overlay_user[overlays] = TvReadOverlay( argv[overlays+1] ))
!= TvStatusError )
overlays++;
else
{
sprintf( txtbuf, "Cannot open overlay %s. Press ENTER to exit.",
argv[overlays+1] );
TvPrompt( map, txtbuf, txtbuf );
TvExit( 0 );
}
/* Now add the overlays to the blank map created above */
for( i = 0; i < overlays; i++ )
TvAddOverlay( map, overlay_user[i] );
proj_type = TvInqOverlayProjection( overlay_user[0], 16, proj_data );
/* Generate a grid, compass, and scale overlays */
grid = TvGenerateGrid( TvGridStyleLabeled );
compass = TvGenerateCompass( TvPositionBottomRight );
scale = TvGenerateScale( TvPositionBottomRight );
TvAddOverlay( map, compass );
TvAddOverlay( map, scale );
/* Display the map on the screen */
TvRefresh( map, TvAllOverlays );
/* This reads the file and puts the data onto the map overlay. */
companies = fopen("outfile.txt", "r");
for companies != EOF;
{
stat = TvBeginFeature(ovl_sym, 0, 0, name, id);
stat = TvInsertSymbol(ovl_sym, symbol, 1, point);
stat = TvEndFeature(ovl_sym);
TvRefresh(map);
}
/* This is a very simple main event loop that is used to process menu
selections and mouse movements. */
while( GetMessage(&msg, (HWND)NULL, 0, 0) )
{
TranslateMessage( &msg );
DispatchMessage( &msg );
}
return( msg.wParam );
}
/* InitTvApplication initializes window data and registers window class. */
BOOL InitTvApplication( hInstance )
HANDLE hInstance;
{
WNDCLASS wc;
wc.lpszClassName = "TerraView";
wc.hInstance = hInstance;
wc.lpfnWndProc = TV_process;
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.hbrBackground = GetStockObject( BLACK_BRUSH );
wc.hCursor = LoadCursor( (HINSTANCE)NULL, IDC_ARROW );
wc.hIcon = LoadIcon( hInstance, (LPSTR)"TvIcon" );
wc.lpszMenuName = "ViewMenu";
wc.cbClsExtra = sizeof( LONG );
wc.cbWndExtra = sizeof( LONG );
}
/* InitTvWindow() creates MS Window and register it with TerraView */
TvWindowID InitTvWindow( VOID )
{
HWND hWnd;
TvWindowID win;
DOUBLE left, right, top, bottom;
hWnd = CreateWindow( (LPSTR)"TerraView", (LPSTR)"TerraView for Windows",
(DWORD)WS_OVERLAPPEDWINDOW, 0, 0,
GetSystemMetrics( SM_CXSCREEN ),
GetSystemMetrics( SM_CYSCREEN ),
(HWND)NULL, (HMENU)NULL,
(HANDLE)hInst, (LPSTR)NULL );
if (!hWnd) return( (TvWindowID)NULL );
win = TvUseWindow( TvDefaultDevice, (void *)&hWnd );
TvPDCtoNDC( TvDefaultDevice, GetSystemMetrics( SM_CXFRAME ),
GetSystemMetrics( SM_CYFRAME ) + GetSystemMetrics( SM_CYCAPTION ) +
GetSystemMetrics( SM_CYMENU ), &left, &top );
TvPDCtoNDC( TvDefaultDevice, GetSystemMetrics( SM_CXFRAME ),
GetSystemMetrics( SM_CYFRAME ), &right, &bottom );
TvSetWindowBorderSize( win, left, right, top, bottom );
ShowWindow( hWnd, curshow );
curshow = SW_SHOWNORMAL;
UpdateWindow( hWnd );
return( win );
}
/* TV_process(HWND, UINT, WPARAM, LONG) processes messages */
LONG FAR PASCAL TV_process( hWnd, message, wParam, lParam )
HWND hWnd;
UINT message;
WPARAM wParam;
LONG lParam;
{
FARPROC lpProcAbout;
struct WinEvent event;
event.hWnd = hWnd;
switch( message )
{
case WM_CREATE:
hAccTable = LoadAccelerators( hInst, "FunctionKeys" );
break;
case WM_DESTROY:
PostQuitMessage( 0 );
break;
case WM_COMMAND:
switch( wParam )
{
case IDM_EXIT:
case IDM_EDIT:
case IDM_HELP:
MessageBox( GetFocus(), " ",
"TerraView Mapping Application", MB_ICONASTERISK | MB_OK );
break;
case IDM_ABOUT:
lpProcAbout = MakeProcInstance( Tv_About, hInst );
DialogBox( hInst, "AboutBox", hWnd, lpProcAbout );
FreeProcInstance( lpProcAbout );
break;
}
case WM_PAINT:
event.code = KBD_WINDOW_RESIZE;
event.x = event.y = 0;
TvQueueEvent( TvDefaultDevice, &event );
TvResizeWindows( TvDefaultDevice );
return( DefWindowProc( hWnd, message, wParam, lParam ) );
case WM_SIZE:
TvResizeWindows( TvDefaultDevice );
if (map) TvRefresh(TvDefaultDevice, TvAllOverlays);
#if 0
case WM_PAINTICON:
#endif
case WM_QUIT:
case WM_CLOSE:
case WM_SYSCHAR:
case WM_SYSDEADCHAR:
case WM_DEADCHAR:
return( DefWindowProc( hWnd, message, wParam, lParam ) );
case WM_CHAR:
event.code = wParam;
event.x = event.y = 0;
TvQueueEvent( TvDefaultDevice, &event );
break;
case WM_LBUTTONDOWN:
event.code = KBD_MOUSE1_DOWN;
event.x = LOWORD( lParam );
event.y = HIWORD( lParam );
TvQueueEvent( TvDefaultDevice, &event );
break;
case WM_LBUTTONUP:
event.code = KBD_MOUSE1_UP;
event.x = LOWORD( lParam );
event.y = HIWORD( lParam );
TvQueueEvent( TvDefaultDevice, &event );
break;
case WM_MBUTTONDOWN:
event.code = KBD_MOUSE2_DOWN;
event.x = LOWORD( lParam );
event.y = HIWORD( lParam );
TvQueueEvent( TvDefaultDevice, &event );
break;
case WM_MBUTTONUP:
event.code = KBD_MOUSE2_UP;
event.x = LOWORD( lParam );
event.y = HIWORD( lParam );
TvQueueEvent( TvDefaultDevice, &event );
break;
case WM_MOUSEMOVE:
event.code = KBD_MOUSE_MOVED;
event.x = LOWORD( lParam );
event.y = HIWORD( lParam );
TvQueueEvent( TvDefaultDevice, &event );
break;
default:
return( DefWindowProc( hWnd, message, wParam, lParam ) );
}
return( (LONG)NULL );
}
/* Tv_About processes messages for "About" dialog box */
BOOL FAR PASCAL Tv_About( hDlg, message, wParam, lParam )
HWND hDlg;
UINT message;
WORD wParam;
LONG lParam;
{
switch (message)
{
case WM_INITDIALOG:
return (TRUE);
case WM_COMMAND:
if ( (wParam == IDOK) ||
(wParam == IDCANCEL) )
{
EndDialog( hDlg, TRUE );
return (TRUE);
}
break;
}
return (FALSE);
}
End Listing
Copyright © 1993, Dr. Dobb's Journal