GoogleMapsV3 CakePHP Helper

This is a helper to generate GoogleMap maps (dynamic and static ones) in your CakePHP views.

Note: Google Maps API V2 is marked "deprecated". API V3 is supposed to be faster and more compatible to mobile browsers. Details
Also new: A googlemaps key is not necessary anymore.

Preamble

You can either dynamically collect the geo data, or you can use my behavior to retreive and store them persistently. I prefer storing them in a table using lat/lng fields as it will make displaying the markers faster (no need to fetch at runtime) – it also makes it possible to make distance queries using radius and such.

Usage

I wanted to keep it as simple as possible (otherwise you could use the maps with plain js right away).

You may set up configs/defaults in your config file:

$config['Google'] = array(
    'zoom' => 6,
    'lat' => 51.1,
    'lng' => 11.2,
    'type' => 'H', // Roadmap, Satellite, Hybrid, Terrain
    'size' => array('width'=>'100%', 'height'=>400),
    'staticSize' => '500x450',
);

Please include the Tools Plugin in your bootstrap with CakePlugin::loadAll() or CakePlugin::load('Tools').

Now you need to add the helper to the controller (or the action):

// globally
public $helpers = array(..., 'Tools.GoogleMapV3');
// OR in the action:
public function map() {
    $this->helpers[] = 'Tools.GoogleMapV3';
    // rest of your code       
}

Since there are many possible ways to include the required javascript file I formed an own method for it:

<?php
// include jQuery >= 1.4
echo $this->Html->script('jquery'); // or wherever it is in your js folder

// include the Google js code
echo $this->Html->script($this->GoogleMapV3->apiUrl());

// OR include it manually without cake (or use your own asset stuff)
echo '<script type="text/javascript" src="'.$this->GoogleMapV3->apiUrl().'"></script>';

You may also let the helper include it automatically – just pass the option "autoScript" => true to the map() method in the next step.

Now we can use it.

Interactive Maps

// init map (prints container)
echo $this->GoogleMapV3->map(array('div'=>array('height'=>'400', 'width'=>'100%')));

// add markers
$options = array(
    'lat' => 48.95145,
    'lng' => 11.6981,
    'icon'=> 'url_to_icon', // optional
    'title' => 'Some title', // optional
    'content' => '<b>HTML</b> Content for the Bubble/InfoWindow' // optional
);
// tip: use it inside a for loop for multiple markers
$this->GoogleMapV3->addMarker($options);

// print js
echo $this->GoogleMapV3->script();

You can also add windows and (custom) events. See the code and its tests for details on that.

Buffering scripts instead of outputting them directly

With 2.x you can also write the JS to the buffer and output it combined anywhere you want in your layout.
Just call

$this->GoogleMapV3->finalize(); // replaces script()

instead of echoing the script() then. The script() call must not be used then. Don’t mix those two methods.

Also make sure you got echo $this->Js->writeBuffer(array('inline' => true)); somewhere in your layout then.
Most developers put CSS in the head and JS at the very bottom (before the closing </body> tag). This way, the layout renders very fast and the JavaScript will render last and does not impede the page loading process.
See the next chapter for details.

Directions (with or without additional text)

If you want to print directions from point A to point B, you can do so with:

echo $this->GoogleMapV3->map();

$from = 'Munich'; // needs to be geocoded at runtime
$to =  array('lat' => 50.51, 'lng' => 13.40);
$this->GoogleMapV3->addDirections($from, $to);

$this->GoogleMapV3->finalize();

You can either pass in a string to be automatically geocoded or pass an array with lat/lng coordinates.

Note: Geocoding on demand is possible, but slightly slower and less resourceful. It is recommended to geocode in the backend – using PHP and the Plugin classes listed below – and output the coordinates here directly.

Static Maps

These are just images. Very handy if you don’t need the js overhead.

// markers
$markers = array(
    array('lat'=>48.2, 'lng'=>11.1),
    array('lat'=>48.1, 'lng'=>11.2, 'color' => 'green', ...)
);
// paths
$paths = array(
    array(
        'path' => array('Berlin', 'Stuttgart'),
        'color' => 'green',
    ),
    array(
        'path' => array('44.2,11.1', '43.1,12.2', '44.3,11.3', '43.3,12.3'),
    ),
    array(
        'path' => array(array('lat'=>'48.1','lng'=>'11.1'), array('lat'=>'48.4','lng'=>'11.2')), //'Frankfurt'
        'color' => 'red',
        'weight' => 10
    )
);

// some options and image attributes
$options = array(
    'size' => '500x400',
    'center' => true,
    'markers' => $this->GoogleMapV3->staticMarkers($markers),
    'paths' => $this->GoogleMapV3->staticPaths($paths), 
);
$attr = array(
    'title'=>'Yeah'
);

// now display the map image
echo $this->GoogleMapV3->staticMap($options, $attr);

// you can even add an url to click on
$attr['url'] = $this->GoogleMapV3->mapUrl(array('to'=>'Munich, Germany'));
echo $this->GoogleMapV3->staticMap($options, $attr);

As you can see, we can now mix lat/lng and normal addresses (which get automatically geocoded in the background).

Map Links

If you want to redirect to maps.google.com (for directions etc) you can use this method.

// leave "from" empty for the user
$url = $this->GoogleMapV3->mapUrl(array('to'=>'Munich, Germany'));
echo $this->Html->link('Visit Me', $url, array('target'=>'_blank')

// coming from a posted form or whatever
$url = $this->GoogleMapV3->mapUrl(array('to'=>'Munich, Germany', 'from'=>$from));
echo $this->Html->link('Directions to me', $url, array('target'=>'_blank')

Last but not least

Other updates:

  • multiple paths and markers
  • visible scope
  • custom icons possible

For more examples check out the test case. It contains several more sophisticated examples.

Helper Code

The code can be found in my github rep:
Tools Plugin GoogleMapV3 helper

Update 2012-02

All URLs/links are now HTTPS sensitive. So if you display the map on a secure site (https://...) it will also use the same connection for all the google stuff (images, js, …). This is necessary for HTTPS to be valid.

Update 2012-09: GoogleMapsV3Helper v1.3 – not backwards compatible

The helper is now E_STRICT compliant. The methods url() and link() are now mapUrl() and mapLink().

Update 2013-02

directions() has been added to print directions from point A to B. Also basic geocoding capabilities have been added for this method as well as addMarker().

Update 2013-10

The "open" option for markers allows you to display their infoWindow opened at loading. This was a feature request on github. It is available for single and multi windows mode. Note that for single window mode (default), only one marker can be shown as open at once (the last one declared).

Update 2018

The API key is now necessary/required, as such there is an update for 2.x.

And the CakePHP 3.x repo can be found at github.com/dereuromark/cakephp-geo.

106 Comments

  1. Hello Mark,

    Nice one !! for the community. Hope everybody likes your effort and make use of it. 🙂

  2. Hi Mark, Thanks for your nice work on the GoogleMaps helper for CakePHP.

    I am struggling to get the test code to work. When I run the GoogeMap case case (through Simpletest), I get:

    Fatal error: Class ‘MyCakeTestCase’ not found in /usr/local/src/foo/app/plugins/tools/tests/cases/helpers/google_map_v3.test.php on line 6

    Grateful for any hint you can give me!

  3. Yes, thats my own test case (thats what my comment was about in the github rep – you cannot use my test case right away – some modifications will have to be made).

    simply take my test as a guideline and build your own from scratch 🙂

  4. Hi mark,

    Great work.

    Thanks a lot for your previous help.

    In your code
    $options = array(
    ‘lat’ =&gt; 48.95145,
    ‘lng’ =&gt; 11.6981,
    ‘icon’=&gt; ‘url_to_icon’, # optional
    ‘title’ =&gt; ‘Some title’, # optional
    ‘content’ =&gt; ‘<b>HTML</b> Content for the Bubble/InfoWindow’ # optional
    );

    I want to give Location name instead of latitude and longitude values. How can I do it? thanks a lot for your help. Is it possible to use zip code?

  5. thats not the way googlemaps works!
    you need to manually geocode your addresses and then use the lat/lng in the view (see geocoding behaviors!).
    there are possibilities to manually do that with JS (just in time so to speak) – but this is not recommended and will only slow things down.

  6. hy mark thx for this helper it.s very useful for me &amp; so easy but i have one problem with geocode
    i try to use to extract the lat &amp; lng from the name of the place but i failed
    so can you give me any idea how can i use your helper to make it happen
    thx a lot &amp; i,m interesting about ur work

  7. Is there an example of using the clustering? I see some functions for it. Is clustering still in test or is it ready to be used? Forgive me if there is an example and I missed it.

  8. unfortunately I didn’t have time yet to include that. but it can easily be done with customJS I believe.

  9. I keep getting "missing } after property list"
    when referencing "var myOptions = {zoom: 4, streetViewControl: false, navigationControl: true, mapTypeControl: true, scaleControl: true, scrollwheel: false, keyboardShortcuts: true, center: initialLocation, mapTypeId: google.maps.MapTypeId.0 };"
    This line is created in the funciton _mapOptions()

    Any idea. It all looks good to me. Thanks

  10. To fix the error I was getting
    “missing } after property list”

    I had to go in the the google_map_v3.php and change line 944

    Previous version:
    $res[] = ‘mapTypeId: google.maps.MapTypeId.’.$type;

    New version:
    $res[] = ‘mapTypeId: "google.maps.MapTypeId.’.$type.’"’;

    All I did was add some quotes
    Just an FYI if anyone else has the same problem

  11. I was sure i fixed that in the current version. Maybe I didnt commit it yet
    Thats a known IE bug that the last comma has to be omitted.. quite annoying sometimes

  12. Hello, how are you?

    There’s a particular case, where forexample, you print out the map within a hidden container (i’m using jQuery tabs in this case), and when you first show it, it’s not correctly centered (center =&gt; true, doesn’t fix it), and it shows a lot of grey background.

    Whenever you resize the window, it works (however, this is not an acceptable solution)

    So, I added this line:

    google.maps.event.trigger(".$this-&gt;name().", ‘resize’);

    after the line (372, in my file, in the map() function) which reads:

    var ".$this-&gt;name()." = new google.maps.Map(document.getElementById(&quot;".$this-&gt;_currentOptions[‘div’][‘id’]."&quot;), myOptions);

    And now it works correctly. I thought I’d share. Thanks for sharing your work!

    Cheers.

  13. Actually, disregard that last comment… because I just realized that it was working because I was calling it when the tab was activated -which is actually the only way to get it to work-, so no need to update your helper.

    Cheers.

  14. Hi,

    Thanks for the helper. I have a query here. I have uncommented the autoCenter option in $_defaultOptions so that the map would automatically centered at the marker.

    However, when it centers at the marker, the zoom level increased to it’s maximum even though the default zoom value is set at 15 at $_defaultOptions.

    Am I doing it wrong here? Appreciate if you could help me out.

    Thank you in advance.

  15. If you mailed it to me – idealy as TestCase similar to the one existing – i could take a look at it.
    unfortunately i am quite busy right now to investigate it on my own.

  16. Hey Mark, nice rewrite! Is it also possible to create polygons and lines in a dynamic map with this helper?

  17. Well, via PHP it’s currently out of scope. Feel free to add the functionality and make a pull request 🙂 But you can always use custom JS code to inject into the helper. There is a method for this available.

  18. Hi, I have a strange problem:

    I added the helper in the right directory.
    I added the helper into app_controller.
    I added the call to the js Script in default.ctp.
    I added the global configs, I can debug them from the helper, so they are accessable.

    When I call echo $this-&gt;GoogleMapV3-&gt;map(array(‘div’=&gt;array(‘height’=&gt;’400’, ‘width’=&gt;’100%’)));
    and
    echo $this-&gt;GoogleMapV3-&gt;script();

    I can see the whole div – including zoom, scale, copyright and switch between map/satelite/etc

    But only a small cut out of the map, always somewhere in cech republik, regardless what lat/lng I set in the config defaults…

    I wish I could send you a screenshot.
    Did anyone had the same problems, or is it just me?

  19. sure that the configs are correctly set and read out from cake? that would be my guess.

    you can provide a link to an uploaded image somewhere on the web (image upload server).

  20. OK, it seams it has to do with opening the map in a modal dialog. I just found out that I have to add

    google.maps.event.trigger(map, ‘resize’);

    Can you please tell me where to add this when the map loads?

  21. Hi Mark, first thank you for you helper. Second i write this comment, because i have question: about zoom usage. I want generate simple map whitch 1 marker so i do in view

    GoogleMapV3-&gt;map(array(‘div’=&gt; array(‘id’=&gt; ‘google_map’, ‘height’=&gt; 250, ‘width’=&gt; 450), ‘zoom’=&gt; 10));
    $this-&gt;GoogleMapV3-&gt;addMarker(array(
    ‘lat’=&gt; 52.229387,
    ‘lng’=&gt; 20.99076
    ));
    echo $this-&gt;GoogleMapV3-&gt;script();
    ?&gt;

    Thic code generate maps with marker but is not centering maps on the marker. So i do that:

    $this-&amp;gt;GoogleMapV3-&amp;gt;addMarker(array(
        &#039;lat&#039;=&amp;gt; 52.229387,
        &#039;lng&#039;=&amp;gt; 20.99076,
        &#039;autoCenter=&amp;gt; true
    ));
    

    Now the map is centered but zoom is max even I write ‘zoom’=&gt; 15. I think that is a bug,

  22. i reproduced your steps.
    it is more a google maps bug. with autoCenter=>true the zoom is omitted and the map tries to find the "best" zoom value. for a single marker, though, it uses max zoom which might not be the best thing here.

    to avoid this issue always submit a default lat/lng (either in configs or in your initialization in map():
    array(‘lat=>$lat, ‘lng’=>$lng, …)

    also note that the initialization is done before any markers are set. so if you want your single marker to be the center (which your custom zoom value), use those lat/lng values as the above default values.

  23. Thank you for help.

    echo $this-&amp;gt;GoogleMapV3-&amp;gt;map(array(
        &#039;div&#039;=&amp;gt; array(&#039;id&#039;=&amp;gt; &#039;google_map&#039;, &#039;height&#039;=&amp;gt; 250, &#039;width&#039;=&amp;gt; 450), 
        &#039;zoom&#039;=&amp;gt; 15,
        &#039;lat&#039;=&amp;gt; 52.229387,
        &#039;lng&#039;=&amp;gt; 20.99076,
    ));
    $this-&amp;gt;GoogleMapV3-&amp;gt;addMarker(array(
        &#039;lat&#039;=&amp;gt; 52.229387,
        &#039;lng&#039;=&amp;gt; 20.99076,
    ));
    echo $this-&amp;gt;GoogleMapV3-&amp;gt;script();
    

    I paste this code for other visitors who have the same problem.

  24. works for me. did you use the current version of the script?
    you sure that no configure settings ("Google") interfere?

  25. Yeap this code too worls for me. I paste this code for another people who want see example for the most popular use google maps.

  26. hi, please help me with a problem,
    i am using this helper with jquery ui tabs, and i can’t display corectly Gmap.
    Map is located in the secound tab, and when the tab is visible, is displayed only few piexels.
    the controls of the map, are display ok, but the mas only few pixel in the top left corner.
    any ideea?

  27. I guess you need to force the width and height of the div container via css or inline width/height settings.

  28. i have the same problem , but now in ui dialog, has anyone enconter this problem?

    how i do resize using js, after dialog is open or tab is visbile?
    i have found the google.maps.event.trigger(map, ‘resize’);
    is the solution, but how i insert this in the script?

  29. Hi Mark. I´m trying to use the code but i have this line" Map cannot be displayed!" What i´m doing wrong?

  30. Not much to go on with your description. Please debug using Firefox and the Firebug extension. It will give you all js errors occurring.

  31. Good question.
    I always used the available tools and some custom js to produce that. It would be worth making it a little bit more sophisticated. Currently I don’t have time for it, though. Maybe someone wants to do that?
    Feel free to fork the project at github and open a poll request for it if you successed.

  32. Mark,
    I am having trouble getting my $options array to work. I can get a marker at the lat and lng I specify, but the map won’t center over these coordinates, and it won’t zoom to that level (it will work if I set them in the php file as a default so I know there is not a problem with my coordinates or zoom level). It’s like the options I set don’t override the default values in the original helper file. Any advice?

  33. did you try to set them via "map" key?
    ‘map’=>array(‘lat’=>…, ‘lng’=>…)
    ?
    you will need to show some code

  34. I’m a beginner at the whole thing, so sorry if the question is obvious.
    Is there a way to set the map’s setting without having a config file for $config[‘Google’]? I want to set the map’s zoom level in the view. I tried

    echo $this->GoogleMapV3->map(array('div'=>array('height'=>'400', 'width'=>'600'), 'map'=>array('zoom'=>3)));

    or Configure::write(‘Google.zoom’, 3); just before invoking map but that value seems to be overwritten by _defaultOptions.
    Thanks for the helper!

  35. yes, that should definitely be possible – both ways you describe. I will look into that.

  36. I made a little change to the _autoCenter function to limit its zoom (there’s probably a better solution and it should use a constant as a maximum zoom but….):

        protected function _autoCenter() {
            return '
            var bounds = new google.maps.LatLngBounds();
            $.each(gMarkers'.self::$MAP_COUNT.',function (index, marker) { bounds.extend(marker.position);});
            if ('.self::$MARKER_COUNT.'>1)
             {
              '.$this->name().'.fitBounds(bounds);
             }
            else
             {map'.self::$MAP_COUNT.'.setCenter(bounds.getCenter());
        map'.self::$MAP_COUNT.'.setZoom(17);
             }
            ';
        }
  37. The problem I had with the default maps was that it displayed all the businesses with popups and links to their google place pages which I really didn’t want. So I basically added this to the map options (featureType can be a dozen different options, I chose not to display any poi but you can choose not to display poi.business only for example):

    {styles:
        [
            {
                featureType: "poi",
                elementType: "labels",
                stylers:
                [
                    {
                        visibility: "off"
                    }
                ]
            }
        ]
  38. Hi,
    I want use your helper.
    But it responds "Map cannot be displayed"
    why ?

    thanks

  39. Because that’s the default text and your javascript doesn’t work? Did you include the libraries as outlined? Does Firebug give you any JS errors?

  40. It’s ok, i solved my problem, thanks.
    But i have a new issue…
    I would use the map with my JS functions, but i cannot call map object. What do you think can i do ?

    Thanks

  41. thanks but it’s during initialization ?
    Because i geocode an address from JQuery.
    I would like to show the result on the map.
    I don’t know how to do…

  42. Hi, I used the script today for Cake 2.1 and get the error in line 929: Fatal error: Call to a member function value() on a non-object in …..\View\Helper\GoogleMapV3Helper.php on line 929

    First I thought it was because of use helper ‘Javascript’ and in my project I use ‘Js’. But even I change the helper name in your script, I still get the same error.

    Do you have any idea what that could be?

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.