Includes enterprise licensing and support
This tutorial is intended for developers who are familiar with PHP/MySQL, Flex, and the Google Maps API for Flash, and want to learn how to integrate them together. After completing this tutorial, you will have a Google Map based off a database of places. The map will differentiate between two types of places—restaurants and bars—by giving their markers distinguishing icons. An info window with name and address information will display above a marker when clicked.
The tutorial is broken up into the following steps:
The diagram below shows the basic flow of the app. The Flex application contains a UIComponent for the map, and it uses function calls from the Google Maps API to create an interactive map inside the UIComponent. It then sends a call to a PHP script (usually on the same server as the app), which connects to a database of markers and outputs them as XML. The Flex application parses the XML and issues other calls to the Google Maps API to create markers on the map and add additional functionality. The rest of the article will show you how to create the various components of the diagram and then tie them together.
When you create the MySQL table, you want to pay particular
attention to the lat and lng attributes.
With the current zoom capabilities of Google Maps, you should only
need 6 digits of precision after the decimal. To keep the storage
space required for our table at a minimum, you can specify that the
lat and lng attributes are floats of size
(10,6). That will let the fields store 6 digits after the decimal,
plus up to 4 digits before the decimal, e.g. -123.456789 degrees.
Your table should also have an id attribute to serve as
the primary key, and a type attribute to distinguish
between restaurants and bars.
Note: This tutorial uses location data that already have latitude and longitude information needed to plot corresponding markers. If you're trying to use your own data that don't yet have that information, use a batch geocoding service to convert the addresses into latitudes/longitudes. Some sites make the mistake of geocoding addresses each time a page loads, but doing so will result in slower page loads and unnecessary repeat geocodes. It's always better to hardcode the latitude/longitude information when possible. This link contains a good list of geocoders: http://groups.google.com/group/Google-Maps-API/web/resources-non-google-geocoders
If you prefer interacting with your database through the phpMyAdmin interface, here's a screenshot of the table creation.
If you don't have access to phpMyAdmin or prefer using SQL commands instead, here's the SQL statement that creates the table (phpsqlajax_createtable.sql):
CREATE TABLE `markers` ( `id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY , `name` VARCHAR( 60 ) NOT NULL , `address` VARCHAR( 80 ) NOT NULL , `lat` FLOAT( 10, 6 ) NOT NULL , `lng` FLOAT( 10, 6 ) NOT NULL , `type` VARCHAR( 30 ) NOT NULL ) ENGINE = MYISAM ;
After creating the table, it's time to populate it with data. Sample data for 10 Seattle places are provided below. In phpMyAdmin, you can use the IMPORT tab to import various file formats, including CSV (comma-separated values). Microsoft Excel and Google Spreadsheets both export to CSV format, so you can easily transfer data from spreadsheets to MySQL tables through exporting/importing CSV files.
Here's the sample data in CSV format (phpsqlajax_data.csv):
Pan Africa Market,"1521 1st Ave, Seattle, WA",47.608941,-122.340145,restaurant Buddha Thai & Bar,"2222 2nd Ave, Seattle, WA",47.613591,-122.344394,bar The Melting Pot,"14 Mercer St, Seattle, WA",47.624562,-122.356442,restaurant Ipanema Grill,"1225 1st Ave, Seattle, WA",47.606366,-122.337656,restaurant Sake House,"2230 1st Ave, Seattle, WA",47.612825,-122.34567,bar Crab Pot,"1301 Alaskan Way, Seattle, WA",47.605961,-122.34036,restaurant Mama's Mexican Kitchen,"2234 2nd Ave, Seattle, WA",47.613975,-122.345467,bar Wingdome,"1416 E Olive Way, Seattle, WA",47.617215,-122.326584,bar Piroshky Piroshky,"1908 Pike pl, Seattle, WA",47.610127,-122.342838,restaurant
Here's a screenshot of the import options used to transform this CSV into table data:
If you'd rather not use the phpMyAdmin interface, here are the SQL statements that accomplish the same results ( phpsqlajax_data.sql):
INSERT INTO `markers` (`name`, `address`, `lat`, `lng`, `type`) VALUES ('Pan Africa Market', '1521 1st Ave, Seattle, WA', '47.608941', '-122.340145', 'restaurant');
INSERT INTO `markers` (`name`, `address`, `lat`, `lng`, `type`) VALUES ('Buddha Thai & Bar', '2222 2nd Ave, Seattle, WA', '47.613591', '-122.344394', 'bar');
INSERT INTO `markers` (`name`, `address`, `lat`, `lng`, `type`) VALUES ('The Melting Pot', '14 Mercer St, Seattle, WA', '47.624562', '-122.356442', 'restaurant');
INSERT INTO `markers` (`name`, `address`, `lat`, `lng`, `type`) VALUES ('Ipanema Grill', '1225 1st Ave, Seattle, WA', '47.606366', '-122.337656', 'restaurant');
INSERT INTO `markers` (`name`, `address`, `lat`, `lng`, `type`) VALUES ('Sake House', '2230 1st Ave, Seattle, WA', '47.612825', '-122.34567', 'bar');
INSERT INTO `markers` (`name`, `address`, `lat`, `lng`, `type`) VALUES ('Crab Pot', '1301 Alaskan Way, Seattle, WA', '47.605961', '-122.34036', 'restaurant');
INSERT INTO `markers` (`name`, `address`, `lat`, `lng`, `type`) VALUES ('Mama\'s Mexican Kitchen', '2234 2nd Ave, Seattle, WA', '47.613975', '-122.345467', 'bar');
INSERT INTO `markers` (`name`, `address`, `lat`, `lng`, `type`) VALUES ('Wingdome', '1416 E Olive Way, Seattle, WA', '47.617215', '-122.326584', 'bar');
INSERT INTO `markers` (`name`, `address`, `lat`, `lng`, `type`) VALUES ('Piroshky Piroshky', '1908 Pike pl, Seattle, WA', '47.610127', '-122.342838', 'restaurant');
At this point, you should have a table named markers
filled with sample data. You now need to write some PHP statements to
export the table data into an XML format that our map can retrieve
through asynchronous JavaScript calls. If you've never written PHP to
connect to a MySQL database, you should visit php.net and read up
on mysql_connect, mysql_select_db, my_sql_query, and mysql_error.
First, you should put your database connection information in a separate file. This is generally a good idea whenever you're using PHP to access a database, as it keeps your confidential information in a file that you won't be tempted to share. In the Maps API forum, we've occasionally had people accidentally publish their database connection information when they were just trying to debug their XML-outputting code. The file should look like this, but with your own database information filled in (phpsqlajax_dbinfo.php):
<? $username="username"; $password="password"; $database="username-databaseName"; ?>
Check your configuration or try initializing a
domxml_new_doc() to determine if your server's PHP has
dom_xml functionality. If you do have access to
dom_xml functions, you can use them to create XML nodes,
append child nodes, and output an XML document to the screen. The
dom_xml functions take care of subtleties such as
escaping special entities in the XML, and make it easy to create XML
with more complex structures.
In the PHP, first initialize a new XML document and create the
"markers" parent node. Then connect to the database, execute a
SELECT * (select all) query on the markers table,
and iterate through the results. For each row in the table (each location), create a new XML node with the
row attributes as XML attributes, and append it to the parent node. Then dump the XML to the screen.
Note: If your database contains international characters or you otherwise need to force UTF-8 output, you can use utf8_encode on the outputted data.
The PHP file that does all that is shown below (phpsqlajax_genxml.php):
<?php
require("phpsqlajax_dbinfo.php");
// Start XML file, create parent node
$doc = domxml_new_doc("1.0");
$node = $doc->create_element("markers");
$parnode = $doc->append_child($node);
// Opens a connection to a MySQL server
$connection=mysql_connect (localhost, $username, $password);
if (!$connection) {
die('Not connected : ' . mysql_error());
}
// Set the active MySQL database
$db_selected = mysql_select_db($database, $connection);
if (!$db_selected) {
die ('Can\'t use db : ' . mysql_error());
}
// Select all the rows in the markers table
$query = "SELECT * FROM markers WHERE 1";
$result = mysql_query($query);
if (!$result) {
die('Invalid query: ' . mysql_error());
}
header("Content-type: text/xml");
// Iterate through the rows, adding XML nodes for each
while ($row = @mysql_fetch_assoc($result)){
// ADD TO XML DOCUMENT NODE
$node = $doc->create_element("marker");
$newnode = $parnode->append_child($node);
$newnode->set_attribute("name", $row['name']);
$newnode->set_attribute("address", $row['address']);
$newnode->set_attribute("lat", $row['lat']);
$newnode->set_attribute("lng", $row['lng']);
$newnode->set_attribute("type", $row['type']);
}
$xmlfile = $doc->dump_mem();
echo $xmlfile;
?>
If you don't have access to PHP's dom_xml functions,
then you can simply output the XML with the echo
function. When using just the echo function, you'll need
to use a helper function (e.g. parseToXML)
that will correctly encode a few special entities (<,>,",') to be XML friendly.
In the PHP, first connect to the database and execute the
SELECT * (select all) query on the markers table. Then
echo out the parent markers node, and iterate through the
query results. For each row in the table (each location), you need to
echo out the XML node for that marker, sending the name and address
fields through the parseToXML function first in case
there are any special entities in them. Finish the script by echoing
out the closing markers tag.
Note: If your database contains international characters or you otherwise need to force UTF-8 output, you can use utf8_encode on the outputted data.
The PHP file that does all this is shown below (phpsqlajax_genxml2.php):
<?php
require("phpsqlajax_dbinfo.php");
function parseToXML($htmlStr)
{
$xmlStr=str_replace('<','<',$htmlStr);
$xmlStr=str_replace('>','>',$xmlStr);
$xmlStr=str_replace('"','"',$xmlStr);
$xmlStr=str_replace("'",''',$xmlStr);
$xmlStr=str_replace("&",'&',$xmlStr);
return $xmlStr;
}
// Opens a connection to a MySQL server
$connection=mysql_connect (localhost, $username, $password);
if (!$connection) {
die('Not connected : ' . mysql_error());
}
// Set the active MySQL database
$db_selected = mysql_select_db($database, $connection);
if (!$db_selected) {
die ('Can\'t use db : ' . mysql_error());
}
// Select all the rows in the markers table
$query = "SELECT * FROM markers WHERE 1";
$result = mysql_query($query);
if (!$result) {
die('Invalid query: ' . mysql_error());
}
header("Content-type: text/xml");
// Start XML file, echo parent node
echo '<markers>';
// Iterate through the rows, printing XML nodes for each
while ($row = @mysql_fetch_assoc($result)){
// ADD TO XML DOCUMENT NODE
echo '<marker ';
echo 'name="' . parseToXML($row['name']) . '" ';
echo 'address="' . parseToXML($row['address']) . '" ';
echo 'lat="' . $row['lat'] . '" ';
echo 'lng="' . $row['lng'] . '" ';
echo 'type="' . $row['type'] . '" ';
echo '/>';
}
// End XML file
echo '</markers>';
?>
First, check your configuration and make sure you are using PHP5. If you aren't, then use one of the previous techniques.
In PHP, first initialize a new XML document and create the "markers" parent node. Then connect to the database, execute a SELECT * (select all) query on the markers table, and iterate through the results. For each row in the table (each location), create a new XML node with the row attributes as XML attributes, and append it to the parent node. Then dump the XML to the screen.
Note: If your database contains international characters or you otherwise need to force UTF-8 output, you can use utf8_encode on the outputted data.
The PHP file that does all this is shown below (phpsqlajax_genxml3.php):
<?php
require("phpsqlajax_dbinfo.php");
// Start XML file, create parent node
$dom = new DOMDocument("1.0");
$node = $dom->createElement("markers");
$parnode = $dom->appendChild($node);
// Opens a connection to a MySQL server
$connection=mysql_connect (localhost, $username, $password);
if (!$connection) { die('Not connected : ' . mysql_error());}
// Set the active MySQL database
$db_selected = mysql_select_db($database, $connection);
if (!$db_selected) {
die ('Can\'t use db : ' . mysql_error());
}
// Select all the rows in the markers table
$query = "SELECT * FROM markers WHERE 1";
$result = mysql_query($query);
if (!$result) {
die('Invalid query: ' . mysql_error());
}
header("Content-type: text/xml");
// Iterate through the rows, adding XML nodes for each
while ($row = @mysql_fetch_assoc($result)){
// ADD TO XML DOCUMENT NODE
$node = $dom->createElement("marker");
$newnode = $parnode->appendChild($node);
$newnode->setAttribute("name",$row['name']);
$newnode->setAttribute("address", $row['address']);
$newnode->setAttribute("lat", $row['lat']);
$newnode->setAttribute("lng", $row['lng']);
$newnode->setAttribute("type", $row['type']);
}
echo $dom->saveXML();
?>
Call this PHP script from the browser to make sure it's producing
valid XML. If you suspect there's a problem with connecting to your
database, you may find it easier to debug if you remove the line in
the file that sets the header to the text/xml content
type, as that usually causes your browser to try to parse XML and may
make it difficult to see your debugging messages.
If the script is working correctly, you should see XML output like this (phpsqlajax_expectedoutput.xml):
<markers> <marker name="Pan Africa Market" address="1521 1st Ave, Seattle, WA" lat="47.608940" lng="-122.340141" type="restaurant"/> <marker name="Buddha Thai & Bar" address="2222 2nd Ave, Seattle, WA" lat="47.613590" lng="-122.344391" type="bar"/> <marker name="The Melting Pot" address="14 Mercer St, Seattle, WA" lat="47.624561" lng="-122.356445" type="restaurant"/> <marker name="Ipanema Grill" address="1225 1st Ave, Seattle, WA" lat="47.606365" lng="-122.337654" type="restaurant"/> <marker name="Sake House" address="2230 1st Ave, Seattle, WA" lat="47.612823" lng="-122.345673" type="bar"/> <marker name="Crab Pot" address="1301 Alaskan Way, Seattle, WA" lat="47.605961" lng="-122.340363" type="restaurant"/> <marker name="Mama's Mexican Kitchen" address="2234 2nd Ave, Seattle, WA" lat="47.613976" lng="-122.345467" type="bar"/> <marker name="Wingdome" address="1416 E Olive Way, Seattle, WA" lat="47.617214" lng="-122.326584" type="bar"/> <marker name="Piroshky Piroshky" address="1908 Pike pl, Seattle, WA" lat="47.610126" lng="-122.342834" type="restaurant"/> </markers>
Once the XML is working in the browser, it's time to build the Flex app that creates the map and parses the XML. To start off, just create the map using the example MXML from the developer's guide, and customize the map to be centered in Seattle, WA (47.614495, -122.341861).
To load the XML file in your Flex application, you can use the native URLLoader class. URLLoader can load any type of data (text, XML, even binary) from either the same domain as the app it's on or any domain that has a cross-domain policy file allowing access. The URLLoader object dispatches a "complete" event when it's done reading in the data from the URL specified, so you can assign a callback function to that event. The code for that looks like:
public function getXml():void {
var xmlString:URLRequest = new URLRequest("markers.xml");
var xmlLoader:URLLoader = new URLLoader(xmlString);
xmlLoader.addEventListener("complete", readXml);
}
The callback function is called when the URLLoader has read all of the data from the specified URL into its data property. You can then cast that into an XML object and parse it using E4X-style parsing (introduced in AS3). For each marker element you find, retrieve the name, address, type, and lat/lng attributes and pass them to a function that returns a marker that you can add to the map. The code for that looks like:
public function readXml(event:Event):void {
var markersXML:XML = new XML(event.target.data);
var markers:XMLList = markersXML..marker;
for (var i:Number = 0; i < markers.length(); i++) {
var marker:XML = markers[i];
var name:String = marker.@name;
var address:String = marker.@address;
var type:String = marker.@type;
var latlng:LatLng = new LatLng(marker.@lat, marker.@lng);
var marker = createMarker(latlng, name, address, type);
map.addOverlay(marker);
}
}
There are two basic ways of creating custom icons in the Maps API for Flash. The first way is to specify various visual properties in the MarkerOptions object, such as fillStyle, hasShadow, strokeStyle, and radius. The second way is to supply an object of type DisplayObject to the icon property in the MarkerOptions object, where the DisplayObject can be an embedded image, dynamically loaded file, or even a Shape. For this example, you'll use the second technique along with embedded images.
In the global declarations section of your script, embed two images and declare them as classes. Then create an object which associates each image class with one of your type strings: "restaurant" or "bar". This makes the icons easy to reference when you create the markers later.
[Embed(source="../../images/blue-dot.png")] private var blueIcon:Class;
[Embed(source="../../images/green-dot.png")] private var greenIcon:Class;
private var customIcons:Object =
{ "restaurant": blueIcon,
"bar": greenIcon
};
You should have all the marker creation code in a createMarker function. You can retrieve the appropriate embedded image class by using the type string (e.g. "restaurant") as the key for the global customIcons object and pass that image into the MarkerOptions' icon property. You'll also want to define the iconOffset property to get the bottom tip of the icon aligned on top of the latitude/longitude correctly. Then, construct the HTML that you want to show up in the info window by concatenating the name, address, and some markup to bold the name.
Tip: Some tutorials instruct you to store HTML-formatted descriptions in your database, but doing so means you then have to deal with escaping HTML entities and you'll be bound to that HTML output. By waiting until you've retrieved each attribute separately in the ActionScript, you are free to play around with the HTML on the client side and can quickly preview new formatting.
After constructing the HTML string, add an event listener to the marker so that, when clicked, an info window is displayed with that HTML as the content.
public function createMarker(latlng:LatLng, name:String, address:String, type:String): Marker {
var marker:Marker = new Marker(latlng, new MarkerOptions({icon: new customIcons[type], iconOffset: new Point(-16, -32)}));
var html:String = "<b>" + name + "</b> <br/>" + address;
marker.addEventListener(MapMouseEvent.CLICK, function(e:MapMouseEvent):void {
marker.openInfoWindow(new InfoWindowOptions({contentHTML:html}));
});
return marker;
}
Here's the app that ties the markers, icons, and XML parsing together.
When the map has loaded, the getXml function is called and the magic happens. You can right-click on it to view and download the source, or you can click here.