Google Maps Package

The Morfik GoogleMaps package contains a single widget (GoogleMaps) that displays an interactive Google map in the widget. The Google Maps API is supported through additional classes that allow the creating of Markers, Polylines, Polygons, Directions, Geocoding and more.


package-view.png
package-down.png


A Quick Walkthrough

To use the Morfik GoogleMaps package simply add the package to the project by utilizing the “Used Packages” command on the project ribbon (see Figure 1 Used Packages Command), or simply drag the package file onto the application main client area. Once added [a single widget] will appear on the home ribbon when the form designer is active (see Figure 2 Widgets List). These widgets can be placed on application forms and provide immediate functionality with minimal coding.


security-fig1.png
Figure 1: Used Packages Command


Just one widget but it can do a lot!


2.PNG
Figure 2: Widgets List


You can create a fully functional Google Maps application just by dropping a map widget on a form and running the web application. To set the center of the map, use the Center Latitude and Center Longitude properties of the Property Inspector (notice the properties are specified in decimal format and not in degrees-minutes-seconds format).

The Navigation Control (for zoom and location) can take a number of forms; the one seen on http://maps.google.com is the “Large” variety of the control. Notice that changing many of the GoogleMap widget properties affects the display at design-time. These properties can also be changed at run-time using the maps object of the widget. For example you can set the zoom factor of the map to 15 (a number between 1 and 20) by using mfk_GoogleMaps1.map.setZoom(15).

To fully appreciate how to establish the appearance of the map at runtime, let’s look at some runtime code.


The MapType Control

First, we will determine the style of the MapType control. To do that, we first create a TgmMapTypeControlOptions object and then set some of its properties.


FX Code

    mapControlOptions := TgmMapTypeControlOptions.Create();
    { Section A - Set which type of maps are displayed in the control }
    If ShowHybridMapOption Then
        mapControlOptions.mapTypeIds[0] := MTI_HYBRID;
    If ShowRoadMapOption Then
        mapControlOptions.mapTypeIds[1] := MTI_ROADMAP;
    If ShowSatelliteMapOption Then
        mapControlOptions.mapTypeIds[2] := MTI_SATELLITE;
    If ShowTerrainMapOption Then
        mapControlOptions.mapTypeIds[3] := MTI_TERRAIN;
    { Section B - Set the appearance of the Map Type control }
    If UseDefaultStype Then
        mapControlOptions.style         := MTCS_DEFAULT
    Else If UseDropdownStyle Then
        mapControlOptions.style         := MTCS_DROPDOWN_MENU
    Else If UseHorizontalStyle Then
        mapControlOptions.style         := MTCS_HORIZONTAL_BAR;
    { Section C – Set the location of the Map Style control }
        If ShowMapControlBottom Then        
            mapControlOptions.position  := CP_BOTTOM_LEFT
        Else If ShowMapControlBottomRight Then
            mapControlOptions.position  := CP_BOTTOM_RIGHT
        …

In Section A above, the various map types that are displayed are controlled by assigning ones that are to be displayed to their appropriate array index in mapTypeIds. Those not assigned to their array subscript will not be displayed.

Section B of the code determines the physical appearance of the control and Section C sets the location on the map where the control will appear (Options for all control locations are CP_BOTTOM, CP_BOTTOM_LEFT, CP_BOTTOM_RIGHT, CP_LEFT, CP_RIGHT, CP_TOP, CP_TOP_LEFT and CP_TOP_RIGHT).

The final steps to complete the implementation of the map type options are discussed below.


The Navigation Control

As with the MapType control, the Navigation (Zoom, Scroll and Pan) control’s properties are set at runtime through the TgmNavigationControlOptions class. Once an object for this class in instantiated, the properties can be set through that class.


FX Code

    mapNaviControl := TgmNavigationControlOptions.Create();
    { Section D – Set the display style of the Navigation control }
    If UseDefaultNaviStyle Then
        mapNaviControl.style            := NCS_DEFAULT;
    If UseLargeNaviStyle Then
        mapNaviControl.style            := NCS_ZOOM_PAN;
    If UseSmallNaviStyle Then
        mapNaviControl.style            := NCS_SMALL;
    If UseAndroidNaviStyle Then
        mapNaviControl.style            := NCS_ANDROID;
    { Section E – Set the location of the Navigation control }
    If ShowNaviControlBottom Then        
        mapNaviControl.position      := CP_BOTTOM
    Else If ShowNaviControlBottomRight Then
        mapNaviControl.position      := CP_BOTTOM_RIGHT
    …
End;


Section D selects any of the four available display styles available for the Navigation control (strange Google should have an Android style). In Section E the location of the control is set to any of the same possible map location as above.

Again, applying these options will be discussed below.


The Scale Control

Google Maps offers only one style of scale control, but the remaining available properties can be set using the TgmScaleControlOptions class:


FX Code

    mapScaleOptions                     := TgmScaleControlOptions.Create();
    mapScaleOptions.style               := SCS_DEFAULT;
    { Section F – Set the location of the Navigation control }
    If ShowScaleControlBottom Then
        mapScaleOptions.position     := CP_BOTTOM
    …


Again, the position of the Scale control is set using the same options as above.


Applying the Map Control Options

The options described above must be applied using an object of the TgmMapOptions class, which has its own additional properties that can be set.


FX Code

    myOptions := TgmMapOptions.Create();
    myOptions.zoom := 15; {Value options 1 – 20 }
    myOptions.center := latlng;
    myOptions.draggable                 := AllowDraggingMap;
    myOptions.mapTypeControl            := ShowMapTypeControl;
    myOptions.mapTypeControlOptions     := mapControlOptions; { from above }
    myOptions.navigationControl         := ShowZoomControl;
    myOptions.navigationControlOptions  := mapNaviControl;    { from above } 
    myoptions.scrollwheel               := ScrollWheelZoomsMap;
    myOptions.scaleControl              := ShowScaleControl;
    myOptions.ScaleControlOptions       := mapScaleOptions;
    myOptions.disableDoubleClickZoom    := DontZoomOnDblClick;
    myOptions.disableDefaultUI          := False;
    myOptions.noClear                   := False;
    mfk_GoogleMaps1.map.setOptions(myOptions);
    If CurrentMapIsHybrid Then
        mfk_GoogleMaps1.map.setMapTypeId(MTI_HYBRID)        
    Else If CurrentMapIsRoadmap Then 
        mfk_GoogleMaps1.map.setMapTypeId(MTI_ROADMAP)
    Else If CurrentMapIsSatellite Then
        mfk_GoogleMaps1.map.setMapTypeId(MTI_SATELLITE)
    Else If CurrentMapIsTerrain Then
        mfk_GoogleMaps1.map.setMapTypeId(MTI_TERRAIN);
    End;
    myOptions.Free();
    mapControlOptions.Free();
    mapNaviControl.Free();
    mapScaleOptions.Free();


The mapTypeControl, navigationControl and scaleControl are all Boolean properties that determine if the associated control is displayed. The other options are pretty much self-explanatory except the myOptions.Center property, which is set to a latlng object that sets the center position of the map.

Latlng is of type TgmLatLng which holds a longitude and latitude pair of real numbers associated with a location on the map. One of the primary methods for obtaining a TgmLatLng value for a any map location is through Geocoding.


Obtaining Latitude and Longitude Coordinates

There are a number of systems in place for obtaining the coordinates for a location that can be displayed using the GoogleMaps widget. (Obtaining the location coordinates for an address is called “geocoding”.) The two primary methods for geocoding are to use a database or an online web service. Please note: geocoding results vary widely according to the area of the globe for which you are trying to obtain location information.

Microsoft MapPoint is a database that covers North America and Europe and can be used for geocoding. A COM add-in is available for communicating with the MapPoint server. Geonames.org contains a number of different text files that can be imported to create a database for your own geolocation service.

A more common source for geolocating is online web services. GoogleMaps provides an interface for the Google geolocating web service (see below), but there are a number of other web services available as well, including ones from Yahoo!, MapQuest and GeoNames.org.


GoogleMaps Geocoding

To use the GoogleMaps Geocoding service you will first need to create a TgmGeocoder object and a TgmGeorecorderRequest object. Below is sample code that shows how to create and use the objects:


FX Code

Procedure Form1.Button1Click(Event: TDOMEvent);
Var
    GeoCode    : TgmGeocoder;
    GeoCodeReq : TgmGeocoderRequest;
Begin
    GeoCodeReq := CreateGeocoderRequest();
    GeoCodeReq.address := '8039 Beach Blvd, Buena Park, CA, 90620';    
    GeoCodeReq.language := 'EN';
    GeoCodeReq.region := 'US';
    GeoCode := CreateGeocoder();
    { A callback function is need to handle the response from the web service request }
    GeoCode.geocode(GeoCodeReq, GetMethodPointer(Self, @GeocoderCallbackFunction));
End;


Notice that CreateGeocoder and not TgmGeocoder.Create is used (the same is true for a GeocoderRequest) to instantiate an object. The callback function specified needs to have two parameters: a TArray and a TgmGeocoderStatus. Below is example code for the callback function:


FX Code

Procedure Form1.GeocoderCallbackFunction(results : TArray; status : TgmGeocoderStatus);
Var
    geoMarker : TgmMarker;
Begin
    If status = GS_OK Then
    Begin
        mfk_GoogleMaps1.map.setCenter(TgmGeocoderResult(results[0]).geometry.location);
        geoMarker := CreateMarker;
        geoMarker.setPosition(TgmGeocoderResult(results[0]).geometry.location);
        geoMarker.SetVisible(True);
        geoMarker.setMap(mfk_GoogleMaps1.map);
        geoMarker.setZIndex(1000);
        geoMarker.setClickable(True);
        geoMarker.setTitle('We are here');
    End
    Else
        alert('Geocode was not successful for the following reason: ' + String(status));
End;


The TgmGeocoderStatus can have a number of values which can be found in the mfk_GoogleMaps module for the browser code by searching for “GS_”. When the web service returns, the results array can have a number of responses in it if the service finds more than one match for the address specified in the request. In the web service callback function code above, the map is initially centered on the location for the first result, after which a marker is created at the same location. Markers are discussed in more detail below.


Using Markers

Markers are used to pinpoint a location on the map. There are two overloaded methods for creating a new marker: CreateMarker() and CreateMarker(opts: TgmMarkerOptions). Both functions return a TgmMarker object. The sample code below creates an array of two markers using the two overloaded methods. All the GoogleMaps functionality can be found in the mfk_GoogleMaps module.


FX Code

Procedure Form1.Button1Click(Event: TDOMEvent);
Var
    MarkerOpts: TgmMarkerOptions;
    mapEvent:   TgmEvent;
    MarkerArray: Array of TgmMarker;
Begin
    SetLength(MarkerArray, 2);
    MarkerArray[0] := CreateMarker;
    MarkerArray[0].setPosition(CreateLatLng(47, 20));
    MarkerArray[0].SetVisible(True);
    MarkerArray[0].setMap(mfk_GoogleMaps1.map);
    MarkerArray[0].setZIndex(1000);
    MarkerArray[0].setClickable(True);
    MarkerArray[0].setTitle('We are here');
    MarkerOpts := TgmMarkerOptions.Create;
    MarkerOpts.position := CreateLatLng(50, 21);
    MarkerOpts.Visible := True;
    MarkerOpts.map := mfk_GoogleMaps1.map;
    MarkerOpts.zIndex := 1100;
    MarkerOpts.Clickable := True;
    MarkerOpts.Draggable := False;
    MarkerOpts.Flat := False;
    MarkerOpts.Title := 'Hello World';
    MarkerArray[1] := CreateMarker(MarkerOpts);
End;


Please note: when creating a marker using the second method, a number of the MarkerOpts properties must be specifically set before passing them to the create function. The zIndex property controls which objects in the GoogleMap widget are displayed on top of any other objects – the object with the highest zIndex will be displayed on top. Additionally, call the CreateLatLng method to obtain a TgmLatLng object that can be used as a position property. Once a marker has been created, you can add an OnClick event to it as demonstrated in the code below (which should be added before the End in the last line above):


FX Code

    mapEvent := TgmEvent.Create;
    mapEvent.AddListener(MarkerArray[0], 'click', GetMethodPointer(Self, @HandleMarkerClick));
    mapEvent.AddListener(MarkerArray[1], 'click', GetMethodPointer(Self, @HandleMarkerClick));


where HandleMarkerClick is a method with no parameters as see below:


FX Code

Procedure Form1.HandleMarkerClick;
Begin
    ShowMessage('Marker clicked');
End;


Returning to the GeocoderCallbackFunction above, lets create some code that establishes a marker for each location in the result set.


FX Code

Procedure Form1.GeocoderCallbackFunction(results : TArray; status : TgmGeocoderStatus);
Var
    geoMarker : array of TgmMarker;
    i         : Integer;
    mapEvent  : TgmEvent;
Begin
    If status = GS_OK Then
    Begin
        mfk_GoogleMaps1.map.setCenter(TgmGeocoderResult(results[0]).geometry.location);
        mapEvent := TgmEvent.Create;
        SetLength(geoMarker, results.Count);
        For i := 0 To results.Count - 1 Do
        Begin
            geoMarker[i] := CreateMarker;
            geoMarker[i].setPosition(TgmGeocoderResult(results[i]).geometry.location);
            geoMarker[i].SetVisible(True);
            geoMarker[i].setMap(mfk_GoogleMaps1.map);
            geoMarker[i].setZIndex(1000 - i);
            geoMarker[i].setClickable(True);
            geoMarker[i].setTitle(TgmGeocoderResult(results[i]).address_components[0].long_name
                          + ' ' + TgmGeocoderResult(results[i]).address_components[1].long_name);
            mapEvent.AddListener(geoMarker[i], 'click', GetMethodPointer(Self,  HandleMarkerClick));
        End;
    End
    Else
        Alert('Geocode was not successful for the following reason: ' + String(status));
End;


The title property has been set to a concatenation of the address_components array and a common event has been defined for each of the markers created. If there is more than one marker created, however, there will be no way to tell which marker has been clicked. To do that, we need to add a new class and make some slight modifications to the code.

Add the following type definition to the interface section:


FX Code

TmyMarker = Class
    marker : TgmMarker;
    instructions : String;  { see Directions section for how this is used }
    win : TgmInfoWindow;    { see Directions section for how this is used }
    Function onClick(event: TDomEvent); 
End;


Then make the following changes to the callback function:


FX Code

Procedure Form1.GeocoderCallbackFunction(results : TArray; status : TgmGeocoderStatus);
Var
    geoMarker : array of TmyMarker;
    i         : Integer;
    mapEvent  : TgmEvent;
Begin
    If status = GS_OK Then
    Begin
        mfk_GoogleMaps1.map.setCenter(TgmGeocoderResult(results[0]).geometry.location);
        mapEvent := TgmEvent.Create;
        SetLength(geoMarker, results.Count);
        For i := 0 To results.Count - 1 Do
        Begin
            geoMarker[i] := TmyMarker.Create;
            geoMarker[i].marker := CreateMarker;
            geoMarker[i].marker.setPosition(TgmGeocoderResult(results[i]).geometry.location);
            geoMarker[i].marker.SetVisible(True);
            geoMarker[i].marker.setMap(mfk_GoogleMaps1.map);
            geoMarker[i].marker.setZIndex(1000 - i);
            geoMarker[i].marker.setClickable(True);
            geoMarker[i].marker.setTitle(
                          TgmGeocoderResult(results[i]).address_components[0].long_name
                          + ' ' + TgmGeocoderResult(results[i]).address_components[1].long_name);
            mapEvent.AddListener(geoMarker[i].marker, 'click', GetMethodPointer(geoMarker[i], @geoMarker[i].OnClick));
        End;
    End
    Else
        Alert('Geocode was not successful for the following reason: ' + String(status));
End;


Of course, you will need to implement an OnClick event for the TmyMarker class. A sample event is shown below:


FX Code

Procedure TmyMarker.onClick(event: TDomEvent);
var
    win : TgmInfoWindow;
Begin
    win := createInfoWindow();
    win.setContent(TObject('This is ' + marker.getTitle()));
    win.open(marker.getMap, marker);
End;


This event creates a GoogleMaps InfoWindow and displays the Title property in the window.


But Wait – There’s More! Directions

Another set of classes for the GoogleMaps package deals with obtaining Directions for traveling between two map locations. When obtaining directions you need to specify a starting and ending address and a route type. Let’s explore the directions aspect by developing a sample application.

Start by creating a Routes table with the following fields: Id (AutoNumber – Integer); PointName (Memo) and PointAddress (Memo). Then add the following values to the table:

PointName PointAddress
Penn Station Penn Station, New York, NY
Grand Central Station Grand Central Station, New York, NY
Port Authority Bus Terminal 625 8th Avenue, New York, NY, 10018
Staten Island Ferry Terminal Staten Island Ferry Terminal, New York, NY
Harlem - 125th St Station 260 Broadway New York NY 10007
City Hall 260 Broadway New York NY 10007
Rockefeller Center W 49th St & 5th Ave, New York, NY 10020
MOMA MOMA, New York, NY
Empire State Building 350 5th Ave, New York, NY, 10118
Apollo Theater 253 West 125th Street, New York, NY
Wall St 1 Wall St, New York, NY


Drop a GoogleMaps widget on the form and set the Center Latitude to 40.7711329 and the Center Longitude to -73.9741874. Also set the width to something near 840 and the height to around 525.

Add two ComboBox controls beneath the map and label the first one cmbStartPoint and the second cmbEndPoint. For both comboboxes set the Lookup Data Source to Routes; the Lookup Data Field to Point Address; the Lookup Text Field to PointName. Add two text labels to identify the comboboxes with captions Start and End. Finally, add a button named btnSearch with the caption set to Search and a multiline TextLabel named teWarnings beneath the comboboxes. You should have something like the form show in Figure 3 Sample Directions Application.


3.png
Figure 3: Sample Directions Application


Add the following Private variable declarations to the Form class:


FX Code

    renderOpt         : TgmDirectionsRendererOptions;
    directionsDisplay : TgmDirectionsRenderer;
    directionsService : TgmDirectionsService;
    stepDisplay       : TgmInfoWindow;
    markerArray       : TArray;
    mapEvent          : TgmEvent;


Then create an event handler for the form’s OnShow event:


FX Code

Procedure frmDirections.WebFormShow(Var Show: Boolean);
Begin
    directionsService := CreateDirectionsService();
    mapEvent := TgmEvent.Create;
    renderOpt := CreateDirectionsRendererOptions();
    renderOpt.map := mfk_GoogleMaps1.map;
    directionsDisplay := CreateDirectionsRenderer(renderOpt);
    stepDisplay := CreateInfoWindow();
End;


The TgmInfoWindow will be used to display each step of the directions once they are obtained and the TgmDirectionsService will be used to request the route between two locations. The TgmDirectionsRendererOptions primarily holds the id of the associated map widget and the TgmDirectionsRenderer will be assigned the results of the directions request.

Next, create a short and sweet event handler for the button OnClick:


FX Code

Procedure frmDirections.btnSearchClick(Event: TDOMEvent);
Begin
    calcRoute();
End;


Then add the private calcRoute function:


FX Code

Function frmDirections.calcRoute();
Var
    i          : integer;
    startPoint : String;
    endPoint   : String;
    request    : TgmDirectionsRequest;
Begin
    { First, remove any existing markers from the map. }
    For i := 0 to markerArray.length - 1 do
        TmyMarker(markerArray[i]).marker.setMap(nil);
    { Now, clear the array itself. }
    markerArray.Clear;
    { Retrieve the start and end locations  }
    { and create a DirectionsRequest using WALKING directions. }
    { Other options are DTM_BICYCLING and DTM_DRIVING } 
    startPoint := cmbStartPoint.Value;
    endPoint := cmbEndPoint.Value ;
    request := CreateDirectionsRequest();
    request.origin := startPoint;
    request.destination := endPoint;
    request.travelMode := DTM_WALKING;
    { Route the directions and pass the response to a }
    { function to create markers for each step. }
    directionsService.route(request, GetMethodPointer(Self, @calcRouteCallback));
End;


Notice that a TgmDirectionsRequest was created as part of the function which was then passed as a parameter to the route request. Next, add the callback function calcRouteCallback:


FX Code

Function frmDirections.calcRouteCallback(response :  TgmDirectionsResult; status: TgmDirectionsStatus);
Begin
    If status = DS_OK Then
    Begin
        teWarnings.Caption := '<b>' + String(response.routes[0].warnings) + '</b>';
        directionsDisplay.setDirections(response);
        showSteps(response);
    End;
End;


Any warnings are displayed in the teWarnings text box and the directionsDisplay object created in the OnShow event are now set to the response in the callback function and are used below in the showSteps function (private scope):


FX Code

Function frmDirections.showSteps(directionResult : TgmDirectionsResult);
Var
    myRoute : TgmDirectionsLeg;
    marker  : TgmMarker;
    mrTemp  : TmyMarker;
    i       : integer;
Begin
    SetLength(markerArray, 0);
    { For each step, place a marker, and add the text to the marker's }
    { info window. Also attach the marker to an array so we }
    { can keep track of it and remove it when calculating new routes. }
    myRoute := directionResult.routes[0].legs[0];
    For i := 0 to Length(myRoute.steps) - 1 do
    Begin
        marker := CreateMarker();
        marker.SetPosition(myRoute.steps[i].start_point);
        marker.SetMap(mfk_GoogleMaps1.map);
        TmyMarker(markerArray[i]) := TmyMarker.Create();
        TmyMarker(markerArray[i]).marker := marker;
        TmyMarker(markerArray[i]).instructions := myRoute.steps[i].instructions;
        TmyMarker(markerArray[i]).win := stepDisplay;
        mrTemp := TmyMarker(markerArray[i]);
        mapEvent.addListener(mrTemp.marker, 'click', GetMethodPointer(mrTemp, @mrTemp.onClick));
    End;
End;


The code above takes the first route from the TgmDirectionsResult object and assigns its first leg to a TgmDirectionsLeg object, since the TgmDirectionsResult can contain multiple routes and each route can contain multiple legs. The function then creates a marker at each step of the directions that have been returned and establishes the text for the instructions at that step. These instructions could as easily be assigned to a TextLabel’s caption or added to a TextEditBox. Each marker then is assigned to an OnClick event, which is shown below:


FX Code

Procedure TmyMarker.onClick(event: TDomEvent);
Begin
    win.setContent(self.instructions);
    win.open(self.marker.getMap, self.marker);
End;


There’s Still More

This is not a complete description of the GoogleMaps package content which a review of the mfk_GoogleMaps module will reveal. There are many properties, methods and classes that are not discussed in this document but are described in Google’s own documentation for the Google Maps API at http://code.google.com/apis/maps/documentation/javascript/reference.html. The code above displays the instructions for the marker in an InfoWindow.


See Also

Back to top