Tutorial : Google Maps
Goal: Introduce you to using integrating Google Maps into your applications.
Result: After creating a simple application, you understand how to include Google Map controls in the applications you create with Bungee Connect.
Contents |
[edit] Objectives
By completing this tutorial, you will:
- Learn the basics for using the GoogleMap control
- Learn how to use a geocoding REST API to retreive latitude and longitude for an address
[edit] Prerequisites
Before starting this tutorial, we recommend doing the following:
- Complete the Bungee Essentials Tutorials.
- Print the Bungee Builder Layout image to use as quick reference while doing the tutorial.
- If you get stuck at any point in the tutorial, post a question in the BCDN Forums thread for this tutorial.
[edit] Overview
In this tutorial you create a simple user interface for entering address data, and create a function that retrieves the geo coordinates (latitude and longitude) for the address entered. The function adds the coordinates to a collection, which then get displayed in a MultiColumnList control, and as a pin on a GoogleMap control.
[edit] Part 1: Design the Application's Object Model
[edit] Create the Solution and Project
- Create a new solution and TypeLib
- Solution name: Google_Maps_Tutorial
- Project type: TypeLib
- Project name: GoogleMaps
- Save your Solution
[edit] Add a Class for Addresses
For this tutorial, you create fields for a U.S. street address. The names for the fields that you create here correspond to the data elements returned by a geocoding API that you use later in the tutorial.
- Add a Class to the GoogleMaps Project
- Name: Address
- Add Seven Fields to Address
Important: Make sure that you use upper case for these field names since the XML results returned in the function you create below are provided in upper case. If you use lower case for these field names, the XML results do not convert properly.
- string Address
- string City
- string State
- string Zip
- string Country
- double Latitude (Don't overlook that you need to set the type to a double)
- double Longitude
- Save your Solution
Your class should now look like this:
[edit] Add a Main Class to the Project
The main class of your project contains a collection of address objects (based on Address), and a string field to be used for handling user input (for address lookups).
- Add a Class to the GoogleMaps Project
- Name: Main
- Add a Field to Main
- string inputAddress
- Add another Field to Main
- Name: addressList
- Type: Address (on the current solution tab, under TypeLib: GoogleMaps)
- Is Collection: Enabled
- Save your Solution
Your Main class should now look like this:
[edit] Part 2: Build a Function to Lookup Geo Codes
You need geo codes (latitude and longitude) in order to place location pins on the GoogleMap control. There are many web services that can provide you with this information. In the steps below, you use the Yahoo! Geocoding API, a REST-based web service. To see how this service works, click the following URL:
As you see from the link response, the Yahoo! Geocoding API returns XML data that corresponds to the fields that you added to Address. The trick now is to convert XML responses to objects based on the type defined by Address.
Note: This section assumes that you already have introductory familarity with constructing Bungee Logic from the Bungee Essentials tutorials.
[edit] Add a Function to Main
- Add a function to Main
- Name: getGeoCodes
- Double-click the getGeoCodes function to open it in the Design Editor
[edit] Convert the User Input to a URL-compatible String
In order to call the Yahoo! Geocoding API, you need to use a valid URL that includes the address that a user submits through the inputAddress field. However, an end user may sometimes include spaces and commas in the address. That means that you need to replace all the commas and spaces in inputAddress. In the following steps, you add a StringUtil var so that you can access a function called urlEscape. You add another var to hold the inputAddress data after it has been made URL-ready by the urlEscape function.
- Add a var statement
- Name: urlReadyAddress
- Type: string
- Add another var statement
- Name: strUtil
- Type: StringUtil (on the Dependencies tab, under TypeLib: Utility)
- Default Value: Object ... new StringUtil
- Add a call function() statement
- Function Path: Var ... strUtil.urlEncode
- Parameters:
- [in] string source = Path ... inputAddress
- [out] string dest = Var ... urlReadyAddress
- Save your Solution
Your function should now look like this:
Your function's procedure now has a var containing the user input with all spaces and commas converted to a URL-ready format. Now you can use urlReadyAddress when calling the Yahoo! Geocoding API.
Hint: The urlEncode function is one of several helpful functions in the StringUtil utility. When you need to perform text manipulations on a string, look around in StringUtil for a function that might help.
[edit] Use XML Document Utility to Get Geo Codes
Your next step is to submit the urlReadyAddress var to the Yahoo! Geocoding API. Because the API returns an XML document, you can declare an XMLDocObject var, and then use its readURL function to call the API. You construct the URL for the API by using an expression that combines the URL for the API with the urlReadyAddress var. The result is that the XMLDocObject var stores the XML response from the API.
- Add a var statement
- Name: xmlDoc
- Type: XMLDocObject (on the Dependencies tab, under TypeLib: Utility)
- Default Value: Object ... new XMLDocObject
- Add a call function() statement to the end of the function
- Function Path: Var ... xmlDoc.readURL
- Parameters:
- [in] string url = Expression = 'http://local.yahooapis.com/MapsService/V1/geocode?appid=BungeeTutorial&location=' + urlReadyAddress
Note: Be sure to include the single quotes in the expression, as shown.
- [in] string url = Expression = 'http://local.yahooapis.com/MapsService/V1/geocode?appid=BungeeTutorial&location=' + urlReadyAddress
- Save your Solution
Your function should now look like this:
XMLDocObject var now contains the XML response from the API. Next you must extract the relevant data from the response.
[edit] Extract the XML 'Result' Element
Now that the xmlDoc var contains the XML from the API's response, you need to construct an object from the response. Recall that the XML response is constructed like this:
<Result precision="address" warning="...">
<Latitude>38.898563</Latitude>
<Longitude>-77.037223</Longitude>
<Address>1600 PENNSYLVANIA AVE NW</Address>
<City>WASHINGTON</City>
<State>DC</State>
<Zip>20500-0003</Zip>
<Country>US</Country>
</Result>
</ResultSet>
The root element for this XML document is an element called ResultSet. The result data is actually contained below a child element called Result. So in order to convert the Result element into an object, you need an XML document that has Result as its root element. Therefore, your next step is to isolate the Result element into an XML Element object. After that, you can use a different XML Utility to create an object from the data under the Result element.
- Add a var statement
- Name: xmlResult
- Type: XMLElement (on the Dependencies tab, under TypeLib: Utility)
- Add a call function() statement
- Function Path: Var ... xmlDoc.root.getChild
- Parameters:
- [in] string name = Data = Result
- [out] XMLElement result = Var ... xmlResult
- Save your Solution
Your function should now look like this (the order of your vars does not matter):
XMLDocObject var now contains the XML response from the API.
[edit] Convert the 'Result' Element into an Object
Now that the var xmlResult contains the Result element and its element children, you can convert it into an object by using another utility function. First, you declare a var of the type XMLUtil. Then you use XMLUtil's function convertXMLtoObject to put the data into a temporary var based on the Address class.
- Add a var statement
- Name: xmlUtil
- Type: XMLUtil (on the Dependencies tab, under TypeLib: Utility)
- Default Value: Object ... new XMLUtil
- Add a var statement
- Name: tmpAddress
- Type: Address (on the current solution tab)
- Add a call function() statement
- Function Path: Var ... xmlUtil.convertXMLToObject
- Parameters:
- [in] XMLElement xml: Var ... xmlResult
- [in] Class type: Type ... Address (on the current solution tab)
- [out] Object obj: Var ... tmpAddress
- Parameters:
- Function Path: Var ... xmlUtil.convertXMLToObject
- Save your Solution
Your function should now look like this:
You now have the data from the Result element stored in a var object (of type Address). The final step is to add it to your addressList collection in Main.
[edit] Add the tmpAddress to Your Collection
Now that you have created an object from the API response (represented by the tempAddress var), you can add the object to your addressList collection. The collection will store each address lookup that you do.
- Add a call function() statement
- Function Path: Path ... Collection(Address) addressList.add
- Parameters:
- [in] Data element: Var ... tmpAddress
- Parameters:
- Function Path: Path ... Collection(Address) addressList.add
- Save your Solution
The final product of your function should look like this:
[edit] Part 3: Build the Application Interface
Now you can build the user interface for the application. In the interest of the expediency of this Tutorial, you build a very simple form without much attention to aesthetics.
[edit] Design your Form
- Add a form to Main
- Name: Main
- Double-click Main form to open it in the Design Editor
- Make the form 1 columns by 3 rows
- Column width: 500
- Top row:
- Height: 24
- Stretchable: Disabled
- Middle row:
- Height: 100
- Stretchable: Disabled
- Bottom row:
- Height: 250
- Stretchable: Enabled
- From the Toolbox's Containers category, drag a HorizontalFlow control to the top row
- In the Solution Detail, select the inputAddress field
- From the Toolbox's Selection category, drag a TextEdit control to the HorizontalFlow in the top row
- Width: 400 (in the Property Editor's Layout section)
- From the Toolbox's Selection category, drag a TextEdit control to the HorizontalFlow in the top row
- In the Solution Detail, select the getGeoCodes function
- From the Toolbox's Selection category, drag a Button control to the HorizontalFlow in the top row
- Button Label: Do Lookup
- Width: 100 (in the Property Editor's Layout section)
- From the Toolbox's Selection category, drag a Button control to the HorizontalFlow in the top row
- In the Solution Detail, select the inputAddress field
- In the Solution Detail, select the addressList collection
- From the Toolbox's Selection category, drag a MultiColumnList control to the middle row
- Click the Column Settings ... button
- In the dialog, click the Populate button
- Using drag and drop, rearrange the discovered labels into this order: Address, City, State, Zip, Country, Latitude, Longitude
- From the Toolbox's Selection category, drag a MultiColumnList control to the middle row
- In the Solution Detail, select the addressList collection
- From the Toolbox's Selection category, drag a GoogleMap control to the bottom row
- Map Key:
- This is a required field for the GoogleMap control.
- Obtain a Google Maps key here.
Use https://bungeegrid.com for the web site URL. Google requires separate keys for http and https URLs. The Builder uses https, but you may also want a key to use for building applications that you will deploy outside the Builder using http.
- Map Key:
- From the Toolbox's Selection category, drag a GoogleMap control to the bottom row
- Make the form 1 columns by 3 rows
- Save your Solution
Your completed form should look like this:
At this point, your form is now done. You can simulate the form to see whether your function works by entering an address, and then hitting the Do Lookup button. If address results appear in the MultiColumnList, then the function works.
This will also work as a test to see whether your Google Maps API key works. If the GoogleMap control displays a map view, then the API key is working. However, at this point you will find that the map does not change when you do lookups. To get this, you need to configure a GoogleMap Connection Adapter.
[edit] Enable Pins on your Google Map
In order to cause your address lookups to put pins on the GoogleMap control, you need to set up a Connection Adapter that links the Address class as an element on a Google Map.
- In the Solution Explorer, right-click Address and select Add Connection
- Select ConnectionAdapter: GoogleMapElementAdapter (on the Dependencies tab, under Control Adapter) and click OK.
- In the Design Editor, select double latitude
- In the Property Editor, set Path to Latitude
- In the Design Editor, select double longitude
- In the Property Editor, set Path to Longitude
- Save your Solution
Your Connection should now look like this:
By adding a Connection for the GoogleMapElementAdapter to the Address class, you have enabled instances of the class to be displayed as locator pins on a GoogleMap control.
[edit] Simulate the Main Form
Switch to Main form tab in Design Editor
Simulate Main form again
Enter the following addresses
- The White House: 1600 Pennsylvania Avenue, Washington DC
- Bungee Labs: 625 East Technology Avenue, Orem UT
- A Big Hole: Grand Canyon
- Ted's Former Residence: 2005 Pleasant Valley Ave, Oakland CA
[edit] Summary
In this tutorial you have:
- Developed a basic understanding of how to use the GoogleMap control
- Used Bungee Logic to build a function for calling a REST-based geocoding API
[edit] What Next?
We recommend doing the following next steps to continue your learning of Bungee Connect:
- Continue your learning of Bungee Connect with other Tutorials.
[edit] Further Reference
The following resources will further increase your understanding of the concepts covered in this tutorial:
- Documentation: GoogleMap
Did we leave something out of this Bungee Connect Core Curriculum Tutorial? Get answers to questions, or make suggestions in the BCDN Forums thread for this tutorial.