Akeeba, Admin Tools, JCE, MediaBox, Sigplus, TDOC FB Display, TDOC Google KML Map, Articles Anywhere.

Display a KML file in Google Maps

Development Update - May 2024

Please see the Version 5.x.x article for the changes required.

Development Update - 16 Mar 2022

Translated into English. This is Joomla's Basic Language. Tested and published - all seems to work.

Development Update - 28 Jan 2022

Amazing, but someone has taken the trouble to translate the plugin into English and Spanish. My thanks to Grant Amaral of lrio.com, who placed the update on GitHub at: https://github.com/grantiago/tdocgmap. He has also made some modifications ...

A new version of TDOCGmap will be published in due course - when I have worked out what the mods are - and understood them !

Some notes:

  • This is a plugin. Noted the trigger is usually the plugin name. 
  • The use of the trigger can be  {trigger}data{/trigger} or more simply {trigger data}. Gone for the latter.
  • Default data required is: width, height, and Google API Key.
  • KML files are automatically centred by Google, and a suitable Zoom chosen depending on height and width.
  • There seem to be two main ways of display in Google Maps:
    • using an Iframe - no scripts required, but the map must be registered in Google Maps
    • using html with two Java Scripts - one to create the map, and the second to get authorisation from Google.

Help on writing the plugin:

Shown in the code as and when.

Control Structure within Plugin:

The basic steps are as follows:

  1. Obtain the common and default parameters - in particular the Google Maps API Key !
  2. Parse the article text to obtain the number of calls for a map.
  3. "For Each" of these:
    1. Get the details of the call "for each" of the parameters in the call,
    2. Create a DIV with a unique id to hold each map,
    3. Create the contents of the first script for each map.
  4.  Create two scripts:
    1. One containing the parts for each map - this script holds one item function initMaps(), and then for each map as follows:
      1. A variable to create the map object [var map1] with a unique id and insert it into the DIV of the same id. At the same time the data [var src1] for the map is created to hold the kml filepath.
      2. A variable to hold the map properties construct.
    2. One containing the authorisation call, which initiates the callback to InitMaps().
  5. Update the article for display, which will include:
    1. Inserting the divs 
    2. Inserting the two scripts - incluidng construction of the map script.

Google Code

Taken from these two helpful web sites, provided by Gene from the Google Maps Platform Technical Support team:https://developers.google.com/maps/documentation/javascript/kml
 
My initial attempts here were [very] wrong ! GoogleMaps Support got me back on track. I had also been following the GoogleMaps plugin by KKSou - https://www.kksou.com/php-gtk2/. It does not follow the "callback" methodology, and so is of limited use apart from excellent examples in the application of php's preg_match_all() function.
 
I was slightly put off by the JSFiddle example. I thinned down the code [checking that it carried on running] as follows:
  • HTML: removed the "capture" as it is not required
  • CSS: removed the #capture, and also the .html, .body styles.
  • Java Script: removed the kml event listener for which the capture is required. 
// The Divs - one per map
// The id must be unique, so needs to be based on a map counter starting at 0.
<div id="Map1" style="width: 500px; height: 500px; border: thin solid #333;">1</div>
Style information to position the map left [wrap to the right], centre [no wrap] or right [wrap to the left], plus a full width version for printing on A4 portrait via the user's browser's pring facilities.
  
// Map Creating Script
<script> 
function initMaps() {  // this is the function called by callback in the authorisation.
 
The two variables:
var map1
var src1 = 'https//http://tdocplus.co.uk/lp_routes/lp_snuff_white_west.kml';
  
// the map properties
mapProp1= {
 
    center:new google.maps.LatLng($lat1,$long1),  // reset by google using the data in the kml 
    zoom:12,                     // reset by google based on the kml data and the display size
    scaleControl: true,          // show the scale on the map
    zoomControl: true,
    mapTypeControl: true,
    mapTypeId: 'terrain',
       };
// the map
map1 = new google.maps.Map(document.getElementById("Map1"),mapProp1);var kmlLayer = new google.maps.KmlLayer(src1, {
    suppressInfoWindows: true,
    preserveViewport: false,
    map: map1
        };
)    // end initmap
 
</script>
 
// Authorisation Script - the creation of the Divs has been separated out.
<script 
  src="https://maps.googleapis.com/maps/api/js?key=APIKey&callback=initMaps">
</script>  
 
Methodology.
The  main steps were as follows:
The Styles to set the width and height were included when creating the Div's to hold the maps. Relatively simple.
There are two ways that a script can be included:
  • By using the document->addscript function whuch places the scripts in the head part of the document.
  • By including them as cri[pts in the body, by simply appending them to the page text.

The main problem is to ensure that all the {}, and (), and separators [,] and end of lines [;] were all included. This can only be dome by eye, but using Goopgle Chrome's View page Source, and Inspect tools works well.

Testing

The Joomla 3.9.2x test page is here: http://tdocplus.co.uk/1a_39_Test/index.php/tdoc-gmap-test

Although the two kml samples shown above work in a simple html page, in Joomla - both 3 and 4 - having the authorisation script in the head section fails - meaning that no map is displayed. See the highlighted comment in tdocgmap.php below.

The code versions for the two versions are shown below in tdocgmap.php section.

Examining the resulting code [body version] after Google Maps initMaps() function has been called indicates the vast amount of code which is is required to show the map. In the "body" version, this includes:

  • In the head section
    • 6 styles
    • 1 link [which includes a style]
    • 8 scripts
  • In the body section
    • some 80 divs - including buttons and menu options.
    • 1 iFrame
    • 2 scripts - the map and the authorisation.

Printing Maps

Since the latest Google Maps API no longer contains the gmapprint() function, this not realistically possible. Due to the removal in J4 of the article print button, reliance must be placed in the capabilities of the user's browser.

The following browsers have been tested, and satisfactorily print an A4 portrait map:

  • Google Chrome,
  • Opera,
  • Firefox.

tdocgmap folder
Files are as follows: 

  • from the root
    • language subdirectory
      • en_GB
        • plg_content_tdocgmap.ini
        • plg_content_tdocgmap.sys.ini
    • index.html
    • tdocgmap.xml
    • tdocgmap.php
  • in the "upgrade" server directory - on the web
    • index.html
    • tdocgmap.zip [all of the above zipped]
    • tdocgmap_update.xml

1) tdocgmap.xml

Purpose: the installer.
Content:
<?xml version="1.0" encoding="utf-8"? >

<extension type="plugin" group="content" method="upgrade">
   <name> TDOC Google Maps</name>
   <author> Eur Ing Richard Townsend-Rose</author>
   <creationDate>9th May 2021</creationDate>
   <copyright> Copyright (C) 2005 - 2018 Open Source Matters. All rights reserved.</copyright>
   <license> GNU General Public License version 2 or later; see LICENSE.txt</license>
   <authorEmail> This email address is being protected from spambots. You need JavaScript enabled to view it.<;/authorEmail>
   <authorUrl> rgtr.tdocplus.co.uk</authorUrl>
   <version> 4.2.1</version>
   <description> Plugin to display KML files in Google Maps</description>

   <files>
      <filename plugin="tdocgmap">tdocgmap.php</filename>
   </files>

   <config>
      <fields name="params">
         <fieldset name="Basic">
            <!-- API Key -->
            <field type="spacer"
                name="mandatory"
                label="&lt;b&gt;Mandatory&lt;/b&gt;"
            />

            <field name="apikey"
                type="text"
                description="Google Maps API key"
                label="Google Maps API key"
                size="48"
            />
            <!-- google maps display settings -->
            <field type="spacer"
                name="defaults"
                label="&lt;b&gt;Default&lt;/b&gt;"
            />

            <field name="width"
                type="text"
                label="Width"
                default="500"
                description=""
                size="4"
             />
             <field name="height"
                 type="text"
                 label="Height"
                 default="500"
                 description=""
                 size="4"
              />
         </fieldset>
      </fields>
   </config>

   <updateservers>
      <server type="extension" name="Plugin TDOC Googlemap Update Site">http://www.tdopclus.co.uk/tdoc_j4_dev/tdoc_gmap/tdocgmap_update.xml</server>
   </updateservers>

</extension>

2) Some notes

see https://docs.joomla.org/J 3.x:Creating_a_Plugin_for_Joomla
$this->params: the parameters set for this plugin by the administrator [as AN array] - Used
$this->_name: the name of the plugin - Used
$this->_type: the group (type) of the plugin - not used
$this->db: the db object - not used
$this->app: the application object - not used

see https://docs.joomla.org/J3.x :Creating_a_content_plugin
context : The context of the content passed to the plugin - not used
article : A reference to the article that is being rendered by the view - Used
params : A reference to an associative array of relevant parameters. The view determines what it considers to be relevant and passes that information along - not used
limitstart : An integer that determines the "page" of the content that is to be generated  - not used
Note that in the context of views that might not generate HTML output, a page is a reasonably abstract concept that depends on the context.

see https://docs.joomla.org/J1.5:Creating_a_content_plugin

3) tdocgmap.php 
Purpose: To update an article to show maps included within the tags.
Content:

<?php

/**
* @package TDOC Google Maps Display
* @copyright Copyright (C) 2021. All rights reserved.
* @license http://www.gnu.org/licenses/quick-guide-gplv3.html
* @version 5.2.1
**/

/**
Many thanks to Gene from the Google Maps Platform Technical Support team
// see https://docs.joomla.org/J3.x:Creating_a_Plugin_for_Joomla
$this->params: the parameters set for this plugin by the administrator - rgtr note: as AN array - got Ok
$this->_name: the name of the plugin - got Ok
$this->_type: the group (type) of the plugin - got Ok
$this->db: the db object
$this->app: the application object
**/

/** // see https://docs.joomla.org/J3.x:Creating_a_content_plugin
context : The context of the content passed to the plugin.
article : A reference to the article that is being rendered by the view.
params : A reference to an associative array of relevant parameters.
The view determines what it considers to be relevant and passes that information along.
limitstart : An integer that determines the "page" of the content that is to be generated.
Note that in the context of views that might not generate HTML output, a page is a
reasonably abstract concept that depends on the context.
**/

use Joomla\CMS\Plugin\CMSPlugin;

// no direct access
defined( '_JEXEC' ) or die;

class plgcontenttdocgmap extends CMSPlugin {

/**
* Load the language file on instantiation.
*
* @var boolean
* @since 1.0.0
*/
protected $autoloadLanguage = true;

        function onContentPrepare($context, &$article, &$params, $limitstart) {

    //step zero - kick everything else out
    name=$this->_name;
    //echo "<br>"."Name: ".$name;
    if ($this->_name !== "tdocgmap"){return true;}

    // required to setup the default styles, and one of two js scripts.

    // step one - read settings - apikey, defaults for width, height
    $apikey = $this->params->get('apikey','');
    $height = $this->params->get('height','');
    $width  = $this->params->get('width' ,'');
    //echo "<br>Step One - Default Settings: ".$apikey .", ". $height .", ". $width ;

    // step two locate number of instances of {tdocgmap}
    // https://docs.joomla.org/J1.5:Creating_a_content_plugin
    $occurrences = array();
    // set counter for replacing etc etc
    $counter = 0;

    $regex = "/\{".'tdocgmap'."\s+(.*?)\}/is";
    $found = preg_match_all($regex, $article->text, $occurrences);
    //echo "<br>Step Two - Occurrences: ". sizeof($occurrences) ;

    // step three get data for each map
    if ($found){
        // arrays to collect map stuff
        $mydivs = array() ; // used to replace occurrences in text
        $mydata = array() ; // used to construct map script before init function
        $mymaps = array() ; // used to construct map script within init function

        foreach ($occurrences[0] as $value) {
            // create array for hold three data - kml, height width
            $gotdata=array('','','','') ; // 4.1.2
            // remove tag and get local data [kml file, height, width]
            $data = $value ;
            $data = str_replace('{tdocgmap ', '', $data);
            $data = str_replace('}', '', $data);

            // explode data into an array
            $arr = explode(" ", $data);
            foreach ($arr as $phrase) {
                    // echo "<br>Step Three B" .$phrase ;
                if (strstr(strtolower($phrase), 'kml=')) {
                $tpm = explode('=', $phrase);
               $gotdata[0]=$tpm[1];
               }
               if (strstr(strtolower($phrase), 'height=')) {
                    $tpm = explode('=', $phrase);
                  $gotdata[1]=$tpm[1];
                 }
               if (strstr(strtolower($phrase), 'width=')) {
                  $tpm = explode('=', $phrase);
                      $gotdata[2]=$tpm[1];
                }

                              // 4.1.0
                        if ($phrase =='left') {
                            $gotdata[3]="left" ;
                        } elseif ($phrase =='right') {
                            $gotdata[3]="right" ;
                        } elseif ($phrase =='print') {
                            $gotdata[3]="print" ;
                        }

                 } // end for each data

             // check if height & width set, if not use defaults, no kml - zappo !
             if (empty($gotdata[0])){return true ; } // kml
             if (empty($gotdata[1])){$gotdata[1] = $height ;}
              if (empty($gotdata[2])){$gotdata[2] = $width ; }

             // three things for each in the body: div with style, map data, and creator for script
              // create divs
              $mydivs[$counter] = getmydiv($counter, $gotdata[1], $gotdata[2]) ; // height, width
              // create data
              $mydata[$counter] = getmydata($counter, $gotdata[0]) ; // kml
              // create maps
              $mymaps[$counter] = getmymap($counter) ;

              $counter = $counter + 1 ;
         } // end for each occurrence

         // Replace tag occurences with divs.
         for ($i = 0; $i < count($mydivs); $i++) {
             $article->text = preg_replace($regex, $mydivs[$i], $article->text, 1);
         } // end for replace tags with maps

         // assemble maps script
         $mapscript ='' ;
         // add header
         $mapscript .='<script>' ;
         $mapscript .='function initMaps() { ';
         // parse maps
         // data stuff
         for ($i = 0; $i < count($mydata); $i++) {
            // add data
            $mapscript .= $mydata[$i];
         }  // end each data
          // map stuff
         for ($i = 0; $i < count($mymaps); $i++) {
            // add map
            $mapscript .= $mymaps[$i];
         } // end each map
         // script end
         $mapscript .='}</script>' ;

          // there are two options - script in head, or script in body - one or the other !
          // script in head - FAILS in J3 and in J4
           // $document = JFactory::getDocument();
           // $document->addscript($mapscript);
           // script in body
           $article->text .= $mapscript ;
 

    
    
// Get authority, add to end of page
         $article->text .= getmyauth($apikey) ;

         } // end found

    return true;
    } // end oncontentprepare

} // end plgcontenttdocgmap class

// these are the functions that are called above.
// below each is the code from https://codebeautify.org/html-to-php-converter

// set up the divs to hold the maps, used in the tag replacement in the article.
function getmydiv($i, $h, $w) {
    // 4.1.0
    if($xx=="print") {
        $output = '<div id="Map'.$i.'" style="width:100%; height:700px; border: thin solid #333;">'.$i.'</div>' ;
    } elseif($xx=="left") {
        $output = '<div id="Map'.$i.'" style="width:'.$w.'px; height:'.$h.'px; float:left; margin: 15px; border: thin solid #333;">'.$i.'</div>' ;
    } elseif($xx=="right") {
        $output = '<div id="Map'.$i.'" style="width:'.$w.'px; height:'.$h.'px; float:right; margin: 15px; border: thin solid #333;">'.$i.'</div>' ;
    } else {
        $output = '<div id="Map'.$i.'" style="width:'.$w.'px; height:'.$h.'px; margin:auto; border: thin solid #333;">'.$i.'</div>' ;
}

/**
echo '<div id="Map1" style="width: 500px; height: 500px;">1</div>';
**/

// define the var to hold a map, and the kml file to show. goes in the map script before init function.
function getmydata($i, $kml) {
    $output = 'var map'.$i.';';
    $output .= 'var src'.$i.'='.$kml.';';
    return $output ;
}
/**
echo 'var map1;';
echo 'var src1 = 'http://tdocplus.co.uk/lp_routes/lp_snuff_white_east_2.kml';';
**/

// create the stuff to go within the map scipt''s init function. change ' to " for element id, terrain
function getmymap($i) {
    $output = 'map'.$i.' = new google.maps.Map(document.getElementById("Map'.$i.'"), {' ;
    $output .= 'center: new google.maps.LatLng(0, 0),';  // reset by Google
    $output .= 'zoom: 12,';                              // reset by Google
    $output .= 'scaleControl: true,';
                    // show scale

    $output .= 'mapTypeId: "terrain"';
    $output .= '});';
    $output .= '';
    $output .= 'var kmlLayer'.$i.' = new google.maps.KmlLayer(src'.$i.', {';
    $output .= 'suppressInfoWindows: true,';
    $output .= 'preserveViewport: false,';
    $output .= 'map: map'.$i.'';
    $output .= '});';
    return $output ;
}
/**
echo 'map1 = new google.maps.Map(document.getElementById('Map1'), {';
echo 'center: new google.maps.LatLng(0, 0),';
echo 'zoom: 12,';
echo 'mapTypeId: 'terrain'';
echo '});';
echo '';
echo 'var kmlLayer1 = new google.maps.KmlLayer(src1, {';
echo 'suppressInfoWindows: true,';
echo 'preserveViewport: false,';
echo 'map: map1';
echo '});';
**/

// script for authorisation and callback to map script
function getmyauth($apikey) {
    $output = '<script src="https://maps.googleapis.com/maps/api/js?    key='.$apikey.'&callback=initMaps"></script>' ;
return $output ;
}
/**
echo '<script';
echo 'src="https://maps.googleapis.com/maps/api/js?key=xyzxyzxyzxyz1234567890&amp;callback=initMaps">';
echo '</script>';
**/
?>

4) tdocgmap_update.xml

Purpose: Used by Joomla to check the latest version and the whereabouts of the installation.
Nota Bene: https://docs.joomla.org/Deploying_an_Update_Server - The <folder tag> is mandatory for plugins !
Content:
<?xml version="1.0" encoding="utf-8"?>

<updates>
   <update>
      <name>tdocgmap</name>
      <description>Google Maps KML Display</description>
      <element>tdocgmap</element>
      <type>plugin</type>
      <folder>content</folder>

      <client>0</client>
      <version>4.1.2</version>
      <infourl title="tdoc">http://www.tdocplus.co.uk</infourl>
      <downloads>
         <downloadurl type="full" format="zip">http://www.tdopclus.co.uk/tdoc_j4_dev/tdoc_gmap/tdocgmap.zip</downloadurl>
      </downloads>

      <tags>
      <!-- dev, alpha, beta, rc, stable -->
         <tag>stable</tag>
      </tags>

      <maintainer>Eur Ing Richard Townsend_Rose</maintainer>
      <maintainerurl>http://www.rgtr.tdocplus.co.uk</maintainerurl>
      <targetplatform name="joomla" version="4.*"/>

      <php_minimum>7.2</php_minimum>
   </update>

</updates>