In our new tutorial I would like to give you one interesting example of Google Maps API (v3) implementation. Let assume, that you have website with own members. And, you need to add possibility to save map positions of your members at your website. Or, you have to store map coordinates for something else (as example – business locations). I think that this is one of usual tasks in webdevelopment. For our practical realization I took the base of our chat system (I removed everything unnecessary except registration). Now, I will add here next features: index page block with map of 20 recent locations of members, profile page map (with single marker), and page to set coordinates at map.
Now – download the source files and lets start coding !
Step 1. SQL
If you have already had `cs_profiles` table in your database (due of tests), you don’t need remove previous version, keep it, and add second one new table `latlng268`:
01 | CREATE TABLE `cs_profiles` ( |
02 | `id` int(10) unsigned NOT NULL auto_increment, |
03 | `name` varchar(255) NOT NULL default '', |
04 | `first_name` varchar(255) NOT NULL default '', |
05 | `last_name` varchar(255) NOT NULL default '', |
06 | `email` varchar(255) NOT NULL default '', |
07 | `password` varchar(40) NOT NULL default '', |
08 | `salt` varchar(10) NOT NULL default '', |
09 | `status` enum('active','passive') NOT NULL default 'active', |
10 | `role` tinyint(4) unsigned NOT NULL default '1', |
11 | `about` varchar(255) NOT NULL, |
12 | `date_reg` datetime NOT NULL default '0000-00-00 00:00:00', |
13 | `date_nav` datetime NOT NULL default '0000-00-00 00:00:00', |
14 | `color` varchar(6) NOT NULL, |
15 | `rate` float NOT NULL, |
16 | `rate_count` int(11) NOT NULL, |
18 | ) ENGINE=MyISAM DEFAULT CHARSET=utf8; |
19 | CREATE TABLE `latlng268` ( |
20 | `id` int(11) unsigned NOT NULL auto_increment, |
21 | `profile` int(11) unsigned NOT NULL, |
22 | `name` varchar(255) NOT NULL default '', |
23 | `lat` double NOT NULL, |
24 | `lng` double NOT NULL, |
26 | ) ENGINE=MyISAM DEFAULT CHARSET=utf8; |
Step 2. HTML
This is Login / Join form template:
02 | <h3>Google Maps API v3 Practical Implementation</h3> |
03 | <p>Our demonstration contains next features: member registration, and possibility to add own custom marker at google map.</p> |
06 | <div class="tabs_container"> |
08 | <li class="active"><h3>Log In</h3></li> |
09 | <li><h3>Join</h3></li> |
12 | <div class="nav_container"> |
13 | <form class="login_form" action="index.php" method="post" id="1"> |
14 | <label>Username:</label><input type="text" name="username"> |
15 | <label>Password:</label><input type="password" name="password"> |
16 | <input type="submit" name="Login" value="Login"> |
18 | <form class="join_form" action="index.php" method="post" id="2" style="display: none;"> |
19 | <label>Full name:</label> |
20 | <input type="text" id="username" name="username" placeholder="Your name" maxlength="128" title="Your name" required> |
21 | <label>First name:</label> |
22 | <input type="text" id="firstname" name="firstname" placeholder="Your first name" maxlength="128" title="Your first name"> |
23 | <label>Last name:</label> |
24 | <input type="text" id="lastname" name="lastname" placeholder="Your last name" maxlength="128" title="Your last name"> |
26 | <input type="text" id="email" name="email" placeholder="Your email" maxlength="128" title="Format: *@gmail.com" pattern=".*@gmail\.com" required> |
27 | <label>Password:</label> |
28 | <input type="text" id="password" name="password" maxlength="128" title="Password" required> |
29 | <label>Repeat Password:</label> |
30 | <input type="text" id="password" name="password" maxlength="128" title="Repeat password" required oninput="checkpass(this)"> |
31 | <input type="submit" name="Join" value="Join"> |
38 | $(document).ready(function(){ |
39 | $('.tabs li h3').click(function () { |
40 | $('.tabs li').removeClass('active'); |
41 | $(this).parent().addClass('active'); |
42 | $('.nav_container form').hide(); |
43 | var index = $('.tabs li h3').index(this); |
44 | $('.nav_container form').eq(index).show(); |
48 | function checkpass(input) { |
49 | if (input.value != document.getElementById('password').value) { |
50 | input.setCustomValidity('Password must match.'); |
52 | input.setCustomValidity(''); |
Very easy form, isn’t it? Next template – small logout template (with link to page to set position):
2 | <span style="float:right"><a href="index.php?logout">Log Out</a></span> |
6 | <h3><a href="setpos.php">Set my position</a></h3> |
Again – very easy template. Next template – index (main) page template:
templates/main_page.html
04 | <title>Google Maps API v3 Practical Implementation</title> |
05 | <link href="css/main.css" rel="stylesheet" type="text/css" /> |
08 | <script src="js/update.js"></script> |
12 | <h2>Google Maps API v3 Practical Implementation</h2> |
15 | <div class="clear"></div> |
16 | <div class="container" id="con1"> |
19 | <div class="container" id="con2"> |
22 | <div id='gmap_canvas' style="position:relative; width:100%; height:550px;"></div> |
27 | <h2>Online Members Block</h2> |
35 | <script type="text/javascript"> |
37 | function initialize() { |
40 | var myLatlng = new google.maps.LatLng(iCoordX, iCoordY); |
44 | mapTypeId: google.maps.MapTypeId.ROADMAP |
46 | map = new google.maps.Map(document.getElementById('gmap_canvas'), myOptions); |
47 | var aMarkers = new Array(); |
48 | var aMarkerInfos = new Array(); |
51 | google.maps.event.addDomListener(window, 'load', initialize); |
Here you can see google maps initialization – this is very easy JS script, where we need prepare map options and initialize map object, and then – we need to add 20 (optonal) recent positions. Next template – profile page template:
templates/profile_page.html
04 | <title>Google Maps API v3 Practical Implementation</title> |
05 | <link href="css/main.css" rel="stylesheet" type="text/css" /> |
07 | <script src="js/update.js"></script> |
11 | <h2>Google Maps API v3 Practical Implementation</h2> |
14 | <div class="clear"></div> |
15 | <div class="container"> |
18 | <h3>First name: {fname}</h3> |
19 | <h3>Last name: {lname}</h3> |
20 | <h3>About: {about}</h3> |
21 | <h3>Date Reg: {datereg}</h3> |
24 | <p><a href="index.php">Back to main page</a></p> |
30 | <h2>Online Members Block</h2> |
At this page, we will see single profile view page, and, if he (that member) has set his coordinates, we will see google map at his page. Next template – html template of that profile’s map:
templates/profile_map.html
02 | <div class="clear"></div> |
03 | <div class="container"> |
04 | <div id='gmap_canvas' style="position:relative; width:100%; height:400px;"></div> |
06 | <script type="text/javascript"> |
09 | function initialize() { |
10 | // set profile coordinates |
11 | var myLatlng = new google.maps.LatLng({lat}, {lng}); |
15 | mapTypeId: google.maps.MapTypeId.ROADMAP |
17 | map = new google.maps.Map(document.getElementById('gmap_canvas'), myOptions); |
18 | // prepare info window |
19 | var infowindow = new google.maps.InfoWindow({ |
23 | marker = new google.maps.Marker({ |
27 | animation: google.maps.Animation.DROP |
29 | google.maps.event.addListener(marker, 'click', function() { |
30 | infowindow.open(map,marker); |
34 | google.maps.event.addDomListener(window, 'load', initialize); |
You can see here new google map initialization in certain coordinates with marker, and we add event listener (when we click at marker) to display popup info (InfoWindow). And finally, we should prepare last one template file – for page where member can setup own coordinates:
templates/setpos_page.html
04 | <title>Google Maps API v3 Practical Implementation</title> |
05 | <link href="css/main.css" rel="stylesheet" type="text/css" /> |
11 | <h2>Google Maps API v3 Practical Implementation</h2> |
14 | <div class="clear"></div> |
15 | <div class="container"> |
16 | <h3><a href="index.php">Back to main page</a></h3> |
17 | <div style="margin:10px;">Type full street address into field and click on FIND to center map on location.</div> |
18 | <div style="margin:10px;"><input id="gmap_where" type="text" name="gmap_where" /><input id="find" type="button" value="Find" name="find" onclick="findAddress(); return false;" /></div> |
19 | <div id='gmap_canvas' style="position:relative; width:100%; height:400px;margin-bottom:25px;"></div> |
21 | <h3>Please confirm your position</h3> |
22 | <form class="login_form" action="setpos.php" method="post" name="setpos_form"> |
23 | <input type="text" name="lat" value="{lat}" /> |
24 | <input type="text" name="lng" value="{lng}" /> |
25 | <input type="submit" name="Confirm" value="Confirm positions" /> |
28 | <script type="text/javascript"> |
32 | function initialize() { |
33 | geocoder = new google.maps.Geocoder(); |
34 | // set initial coordinates |
35 | var myLatlng = new google.maps.LatLng({lat}, {lng}); |
39 | mapTypeId: google.maps.MapTypeId.ROADMAP |
41 | map = new google.maps.Map(document.getElementById('gmap_canvas'), myOptions); |
42 | // prepare info window |
43 | var infowindow = new google.maps.InfoWindow({ |
44 | content: 'My position' |
47 | marker = new google.maps.Marker({ |
52 | animation: google.maps.Animation.DROP |
54 | google.maps.event.addListener(marker, 'click', function() { |
55 | infowindow.open(map,marker); |
57 | google.maps.event.addListener(marker, 'dragstart', function() { |
60 | google.maps.event.addListener(marker, 'dragend', function(obj) { |
61 | map.setCenter(obj.latLng); |
62 | $('form[name=setpos_form] input[name=lat]').val( obj.latLng.$a ); |
63 | $('form[name=setpos_form] input[name=lng]').val( obj.latLng.ab ); |
67 | google.maps.event.addDomListener(window, 'load', initialize); |
68 | // find custom address function |
69 | function findAddress() { |
70 | var address = document.getElementById("gmap_where").value; |
71 | geocoder.geocode( { 'address': address}, function(results, status) { |
72 | if (status == google.maps.GeocoderStatus.OK) { |
73 | map.setCenter(results[0].geometry.location); |
74 | marker.position = results[0].geometry.location; |
75 | $('form[name=setpos_form] input[name=lat]').val( results[0].geometry.location.$a ); |
76 | $('form[name=setpos_form] input[name=lng]').val( results[0].geometry.location.ab ); |
78 | alert("Geocode was not successful for the following reason: " + status); |
This Javascript is more complicated, I added here possibility to drag marker, and also I added search panel, so now we can search by exact names.
Step 3. CSS
Now, it’s time to apply styles:
css/main.css
007 | background-color:#eee; |
009 | font:14px/1.3 Arial,sans-serif; |
012 | background-color:#212121; |
013 | box-shadow: 0 -1px 2px #111111; |
028 | header a.stuts,a.stuts:visited{ |
030 | text-decoration:none; |
045 | text-decoration: none; |
049 | text-decoration: none; |
052 | background-color: #F2F4F8; |
053 | border: 1px solid rgba(0, 0, 0, 0.4); |
054 | box-shadow: 2px 0 2px -2px #B2B9C9 inset; |
074 | .column:first-child { |
084 | background-color: #F2F4F8; |
085 | border-left: 1px solid rgba(0, 0, 0, 0.4); |
086 | box-shadow: 2px 0 2px -2px #B2B9C9 inset; |
111 | .tabs li h3:first-child { |
115 | border: 1px solid #ddd; |
116 | border-bottom-width: 0; |
119 | padding: 6px 10px 4px |
122 | background-color: #ccc; |
123 | border: 1px solid #ddd; |
124 | border-bottom-width: 0; |
125 | -moz-border-radius: 4px 4px 0 0; |
126 | -ms-border-radius: 4px 4px 0 0; |
127 | -o-border-radius: 4px 4px 0 0; |
128 | -webkit-border-radius: 4px 4px 0 0; |
129 | border-radius: 4px 4px 0 0; |
132 | background-color: #bbb; |
135 | .tabs li.active h3:hover { |
136 | background-color: #ccc; |
139 | .nav_container form { |
140 | background-color: #ccc; |
144 | .login_form input,.login_form label, |
145 | .join_form input,.join_form label { |
149 | input[type=text], input[type=password], input[type=submit] { |
150 | -moz-border-radius: 5px; |
151 | -ms-border-radius: 5px; |
152 | -o-border-radius: 5px; |
153 | -webkit-border-radius: 5px; |
155 | border-style: groove; |
157 | input[type=text], input[type=password] { |
158 | border-style: groove; |
185 | padding: 2px 22px 2px 10px; |
188 | .profiles div a:hover { |
189 | background-color: #E0E4EE; |
190 | box-shadow: 2px 0 2px -2px #B2B9C9 inset; |
203 | text-overflow: ellipsis; |
206 | .profiles div img.status_img { |
Step 4. PHP
Our PHP files are separated into two types: action files and classes. I think that you have already saw sources of most of classes of chat project. So, first two files are same:
classes/CLogin.php
classes/CMySQL.php
We don’t need to re-publish sources of both files again. Both are available in our package. But I updated Profiles class file (I removed all unnecessary here, now it contains only registration and few other functions):
classes/CProfiles.php
02 | define('PROFILE_TIMEOUT', 5); |
06 | function CProfiles() {} |
08 | function registerProfile() { |
09 | $sUsername = $GLOBALS['MySQL']->escape($_POST['username']); |
10 | $sFirstname = $GLOBALS['MySQL']->escape($_POST['firstname']); |
11 | $sLastname = $GLOBALS['MySQL']->escape($_POST['lastname']); |
12 | $sEmail = $GLOBALS['MySQL']->escape($_POST['email']); |
13 | $sPassword = $GLOBALS['MySQL']->escape($_POST['password']); |
14 | if ($sUsername && $sEmail && $sPassword) { |
16 | $aProfile = $GLOBALS['MySQL']->getRow("SELECT * FROM `cs_profiles` WHERE `email`='{$sEmail}'"); |
17 | if ($aProfile['id'] > 0) { |
18 | $sErrors = '<h2>Another profile with same email already exist</h2>'; |
21 | $sSalt = $this->getRandCode(); |
22 | $sPass = sha1(md5($sPassword) . $sSalt); |
25 | INSERT INTO `cs_profiles` SET |
26 | `name` = '{$sUsername}', |
27 | `first_name` = '{$sFirstname}', |
28 | `last_name` = '{$sLastname}', |
29 | `email` = '{$sEmail}', |
30 | `password` = '{$sPass}', |
36 | $GLOBALS['MySQL']->res($sSQL); |
38 | $GLOBALS['CLogin']->performLogin($sUsername, $sPassword); |
43 | function getRandCode($iLen = 8) { |
45 | $sChars = "23456789abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ"; |
46 | for ($i = 0; $i < $iLen; $i++) { |
47 | $z = rand(0, strlen($sChars) -1); |
53 | function getProfilesBlock($iLim = 20, $bOnlineOnly = false) { |
54 | $iPLimit = PROFILE_TIMEOUT; |
55 | $sOnlineSQL = ($bOnlineOnly) ? 'AND (`date_nav` > SUBDATE(NOW(), INTERVAL ' . $iPLimit . ' MINUTE))' : ''; |
57 | SELECT `cs_profiles`.*, |
58 | if (`date_nav` > SUBDATE(NOW(), INTERVAL {$iPLimit} MINUTE ), 1, 0) AS `is_online` |
60 | WHERE `status` = 'active' |
62 | ORDER BY `date_reg` DESC |
65 | $aProfiles = $GLOBALS['MySQL']->getAll($sSQL); |
68 | foreach ($aProfiles as $i => $aProfile) { |
69 | $sName = $aProfile['name']; |
70 | $iPid = $aProfile['id']; |
71 | $sOnline = ($aProfile['is_online'] == 1) ? '<img alt="Google Maps API v3 Practical Implementation" src="images/online.png" class="status_img" />' : ''; |
72 | $sCode .= '<div id="'.$iPid.'" title="'.$sName.'"><a href="profile.php?id='.$iPid.'"><p>'.$sName.'</p>'.$sOnline.'</a></div>'; |
74 | $sClass = ($bOnlineOnly) ? 'profiles online_profiles' : 'profiles'; |
75 | return '<div class="'.$sClass.'">' . $sCode . '</div>'; |
78 | function getProfileInfo($i) { |
84 | $aInfos = $GLOBALS['MySQL']->getAll($sSQL); |
88 | $GLOBALS['CProfiles'] = new CProfiles(); |
Now lets review our main files:
index.php
03 | if (version_compare(phpversion(), '5.3.0', '>=') == 1) |
04 | error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED); |
06 | error_reporting(E_ALL & ~E_NOTICE); |
07 | require_once('classes/CMySQL.php'); |
08 | require_once('classes/CLogin.php'); |
09 | require_once('classes/CProfiles.php'); |
11 | if (! isset($_SESSION['member_id']) && $_POST['Join'] == 'Join') { |
12 | $GLOBALS['CProfiles']->registerProfile(); |
15 | $sLoginForm = $GLOBALS['CLogin']->getLoginBox(); |
16 | if ($_SESSION['member_id'] && $_SESSION['member_status'] == 'active') { |
17 | if ($_GET['action'] == 'update_last_nav') { |
18 | $iPid = (int)$_SESSION['member_id']; |
20 | $GLOBALS['MySQL']->res("UPDATE `cs_profiles` SET `date_nav` = NOW() WHERE `id` = '{$iPid}'"); |
26 | $sProfiles = $GLOBALS['CProfiles']->getProfilesBlock(); |
27 | $sOnlineMembers = $GLOBALS['CProfiles']->getProfilesBlock(10, true); |
30 | $aRecentMarkers = $GLOBALS['MySQL']->getAll("SELECT * FROM `latlng268` ORDER BY `id` DESC LIMIT 20"); |
31 | foreach ($aRecentMarkers as $i => $sMInfo) { |
32 | if ($sMInfo['lat'] != 0 && $sMInfo['lng'] != 0) { |
33 | $sUnitLocation = str_replace('"', '', $sMInfo['name']); |
35 | aMarkerInfos[{$sMInfo['id']}] = new google.maps.InfoWindow({content: "{$sUnitLocation}"}); |
36 | aMarkers[{$sMInfo['id']}] = new google.maps.Marker({position: new google.maps.LatLng({$sMInfo['lat']}, {$sMInfo['lng']}), map: map, title: "{$sUnitLocation}"}); |
37 | google.maps.event.addListener(aMarkers[{$sMInfo['id']}], 'click', function() { |
38 | aMarkerInfos[{$sMInfo['id']}].open(map,aMarkers[{$sMInfo['id']}]); |
45 | '{form}' => $sLoginForm, |
46 | '{profiles}' => $sProfiles, |
47 | '{online_members}' => $sOnlineMembers, |
48 | '{add_markers}' => $sMarkers |
50 | echo strtr(file_get_contents('templates/main_page.html'), $aKeys); |
Pay attention to code where we add markers for map. By default, there are only 20 last markers. You always can play with that limit. Our next file – profile page:
profile.php
03 | if (version_compare(phpversion(), '5.3.0', '>=') == 1) |
04 | error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED); |
06 | error_reporting(E_ALL & ~E_NOTICE); |
07 | require_once('classes/CMySQL.php'); |
08 | require_once('classes/CLogin.php'); |
09 | require_once('classes/CProfiles.php'); |
11 | $iPid = (int)$_GET['id']; |
12 | $aInfo = $GLOBALS['CProfiles']->getProfileInfo($iPid); |
13 | $sName = $aInfo['name']; |
14 | $sFName = $aInfo['first_name']; |
15 | $sLName = $aInfo['last_name']; |
16 | $sAbout = $aInfo['about']; |
17 | $sDate = $aInfo['date_reg']; |
20 | $aPosInfo = $GLOBALS['MySQL']->getRow("SELECT * FROM `latlng268` WHERE `profile` = '{$iPid}'"); |
21 | if ($aPosInfo['id']) { |
23 | '{lat}' => $aPosInfo['lat'], |
24 | '{lng}' => $aPosInfo['lng'], |
25 | '{name}' => str_replace('"', '', $sName) |
27 | $sMap = strtr(file_get_contents('templates/profile_map.html'), $aPosKeys); |
30 | $sProfiles = $GLOBALS['CProfiles']->getProfilesBlock(); |
31 | $sOnlineMembers = $GLOBALS['CProfiles']->getProfilesBlock(10, true); |
39 | '{datereg}' => $sDate, |
40 | '{profiles}' => $sProfiles, |
41 | '{online_members}' => $sOnlineMembers, |
44 | echo strtr(file_get_contents('templates/profile_page.html'), $aKeys); |
As you can see – most of the code are already commented. So I hope that this code are pretty understandable. And, in the long run – our new file which we use to set our coordinates:
setpos.php
03 | if (version_compare(phpversion(), '5.3.0', '>=') == 1) |
04 | error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED); |
06 | error_reporting(E_ALL & ~E_NOTICE); |
07 | require_once('classes/CMySQL.php'); |
08 | require_once('classes/CLogin.php'); |
09 | require_once('classes/CProfiles.php'); |
10 | $iPid = (int)$_SESSION['member_id']; |
12 | if ($_POST && $_POST['Confirm']) { |
13 | $dLat = (double)$_POST['lat']; |
14 | $dLng = (double)$_POST['lng']; |
15 | if ($iPid && $dLat && $dLng) { |
16 | $aInfo = $GLOBALS['MySQL']->getRow("SELECT * FROM `latlng268` WHERE `profile` = '{$iPid}'"); |
18 | $GLOBALS['MySQL']->res("UPDATE `latlng268` SET `lat` = '{$dLat}', `lng` = '{$dLng}' WHERE `profile` = '{$iPid}'"); |
20 | $aPInfo = $GLOBALS['CProfiles']->getProfileInfo($iPid); |
21 | $sName = $GLOBALS['MySQL']->escape($aPInfo['name']); |
22 | $GLOBALS['MySQL']->res("INSERT INTO `latlng268` SET `lat` = '{$dLat}', `lng` = '{$dLng}', `profile` = '{$iPid}', `name` = '{$sName}'"); |
24 | header('Location: profile.php?id=' . $iPid); |
28 | $aPosInfo = $GLOBALS['MySQL']->getRow("SELECT * FROM `latlng268` WHERE `profile` = '{$iPid}'"); |
31 | if ($aPosInfo['id']) { |
32 | $dLat = $aPosInfo['lat']; |
33 | $dLng = $aPosInfo['lng']; |
40 | echo strtr(file_get_contents('templates/setpos_page.html'), $aKeys); |
Step 5. Javascript
I added that simple script in order to update (periodically) time of our last navigation (simulation of online status)
js/update.js
03 | updateLastNav = function() { |
04 | $.getJSON('index.php?action=update_last_nav', function() { |
06 | setTimeout(function(){ |
Conclusion
I hope that our new tutorial was very useful and interesting for you. If you want to share your ideas, or you noticed any weakness – don’t hesitate to contact us. Good luck and welcome back!