index.php

<?php
// -----------------------------------------------------------------------------------------
// This code should be run from a PHP enabled web server.
// Call up the page with your choice of date, latitude, longitude, skins and debug mode
//
// There is an input form ...
// https://nbest.co.uk/kmlGeohash/testForm.php
//
// Or call the calculator page directly - all the parameters have default values if omitted
// https://nbest.co.uk/kmlGeohash/index.php?date=2015-12-12&lat=52&lon=-0&skins=2&debug=debug
// -----------------------------------------------------------------------------------------
  require_once("hash_up.php");     // Geohashing functions
  require_once("html_gen.php");    // html functions
  require_once("kml_gen.php");     // KML functions
// -----------------------------------------------------------------------------------------
  $djia_e = false;
  $djia_w = false;
  $msg    = "";
// -----------------------------------------------------------------------------------------

  // ---------------------------------------------------------------------------------------
  // Validate $_GET
  // ---------------------------------------------------------------------------------------
  $clean_get = clean_up_get_params($_GET);
  extract($clean_get);
  // ---------------------------------------------------------------------------------------

  // ==== FAIL FAIL FAIL FAIL FAIL FAIL FAIL FAIL FAIL FAIL FAIL FAIL FAIL ====
  //html_head();
  //echo "<p>This Geohashing KML Calculator is on the blink. I'm working on a fix. 2018-02-02</p>";
  //echo "<p>http://carabiner.peeron.com/xkcd/map/data/yyyy-mm-dd seems yo be refusung connections.</p>";
  //html_tail();
  //exit;
  // ==========================================================================

  // ---------------------------------------------------------------------------------------
  if ($get_debug)
  {
  	html_head();
  	echo "<h2>Debug Mode</h2>\n";
    echo "<hr>\n";
    echo "<pre>\$_GET\n"      . print_r($_GET, true)      . "</pre>\n";
    echo "<pre>\$clean_get\n" . print_r($clean_get, true) . "</pre>\n";
    echo "<p>\$get_date $get_date<br>\$get_lat $get_lat<br>\$get_lon $get_lon<br>\$get_skins $get_skins<br>\$get_debug $get_debug</p>\n";
  }
  // ---------------------------------------------------------------------------------------
  // Get djia values for date and day before - could be null
  // ---------------------------------------------------------------------------------------
  list($djia_e, $djia_w, $msg) = get_djias($get_date, $get_debug);
  // ---------------------------------------------------------------------------------------
  if ($get_debug)
  {
    echo "<p>";
    if ($djia_e) echo "\$djia_e = $djia_e<br>\n";
    if ($djia_w) echo "\$djia_w = $djia_w<br>\n";
    echo "\$msg: $msg";
    echo "</p>\n";
  }

  if (($djia_w === false) && ($djia_e === false))  // If both djia values are missing ...
  {
    if ($get_debug)
    {
      echo "<h3>$msg</h3>";
    }
    else
    {
      html_head();
      echo "<h3>$msg</h3>\n\n";
      html_tail();

      exit;
    }
  }

  // ---------------------------------------------------------------------------------------
  // Get coordinates for east and west of 30W
  // ---------------------------------------------------------------------------------------
  if ($djia_e) list($lat_e, $lon_e) = get_coords($get_date, $djia_e, $get_debug);
  if ($djia_e) list($lat_g, $lon_g) = get_global($get_date, $djia_e, $get_debug);
  if ($djia_w) list($lat_w, $lon_w) = get_coords($get_date, $djia_w, $get_debug);
  // ---------------------------------------------------------------------------------------

  if ($get_debug)
  {
    echo "<p>";
    if ($djia_e) echo "\$lat_e = $lat_e \$lon_e = $lon_e";
    if ($djia_w) echo "<br>\$lat_w = $lat_w \$lon_w = $lon_w<br><br>\$lat_g = $lat_g \$lon_g = $lon_g";
    echo "</p>\n\n";
  }

  $day    = date('D', strtotime($get_date));      // Get "Mon"    or something similar from 2016-01-25
  $day_nn = $day . " " . substr ($get_date, 8 );  // Get "Mon 25" or something similar from 2016-01-25

  $kml       = "";
  $countPins = 0;

  if ($djia_e || $djia_w)    // Do the nested for loops ...
  {
    if ($get_debug)
    {
      echo "<table style=\"border-collapse: collapse; border:solid 1px #bbb;\">\n";
      echo "  <tr>\n";
      echo "    <td style=\"border-style:solid; border:solid 1px #bbb; padding:2px;\">\$grat_lat</td>\n";
      echo "    <td style=\"border-style:solid; border:solid 1px #bbb; padding:2px;\">\$grat_lon</td>\n";
      echo "    <td style=\"border-style:solid; border:solid 1px #bbb; padding:2px;\">\$lat</td>\n";
      echo "    <td style=\"border-style:solid; border:solid 1px #bbb; padding:2px;\">\$lon</td>\n";
      echo "  <tr>\n";
    }

    $kml_head  = "";    // $kml_head  = kml_begin($get_date . "_$day.kml", $lat, $lon);    // It's more convenient to get this inside the nested loop below
    $kml      .= kml_style();                                       // push pin and styles

    $min_lat =   90;
    $min_lon =  180;
    $max_lat =  -90;
    $max_lon = -180;

    // -------------------------------------------------------------------------------------
    // GLOBAL HASH PUSH PIN
    // -------------------------------------------------------------------------------------
    if ($djia_e)
    {
      $kml .= kml_folder_start("Global Points");
      $kml .= kml_globalmark($get_date, $lat_g, $lon_g, $day_nn);    // kml global placemark
      $kml .= kml_folder_end();
    }
    // -------------------------------------------------------------------------------------

    // -------------------------------------------------------------------------------------
    // Generate the local push pins
    // -------------------------------------------------------------------------------------
    $count = 0;
    $kml .= kml_folder_start("Graticule Points");
    for ($yy_lat = -$get_skins; $yy_lat < ($get_skins + 1);  $yy_lat++) {      // Iterate through latitudes  (vertical)
      for ($xx_lon = -$get_skins; $xx_lon < ($get_skins + 1);  $xx_lon++) {    // Iterate through longitudes (horizontal)
        if ((($get_lon + $xx_lon < -30) && ($djia_w)) || ($get_lon + $xx_lon >= -30))
        {
          // -------------------------------------------------------------------------
          //  $yy_lat -3, -2, -1,  0,  1,  2,  3     Iterator Index
          //          -2, -1, -0,  0,  1,  2,  3     Graticule name
          // -------------------------------------------------------------------------
          if ($get_lon + $xx_lon >= -30)   // Use $lat_e and $lon_e
          {
            if ($get_lat + $yy_lat >= 0)   // North of the equator
            {
              $lat = $get_lat + $yy_lat + $lat_e;
            }
            else
            {
              $lat = 1 + $get_lat + $yy_lat - $lat_e;
            }

            if ($get_lon + $xx_lon >= 0)   // East of the meridian
            {
              $lon = $get_lon + $xx_lon + $lon_e;
            }
            else
            {
              $lon = 1 + $get_lon + $xx_lon - $lon_e;
            }
          }
          else                             // Use $lat_w  and $lon_w
          {
            if ($get_lat + $yy_lat >= 0)   // North of the equator
            {
              $lat = $get_lat + $yy_lat + $lat_w;
            }
            else
            {
              $lat = 1 + $get_lat + $yy_lat - $lat_w;
            }

            if ($get_lon + $xx_lon >= 0)   // East of the meridian
            {
              $lon = $get_lon + $xx_lon + $lon_w;
            }
            else
            {
              $lon = 1 + $get_lon + $xx_lon - $lon_w;
            }
          }
          // -------------------------------------------------------------------------
          if ($get_lat + $yy_lat >=  0) { $grat_lat = $get_lat + $yy_lat; } else { $grat_lat  = 1 + $get_lat + $yy_lat; }
          if ($get_lat + $yy_lat == -1) { $grat_lat = "-" . $grat_lat; }

          if ($get_lon + $xx_lon >=  0) { $grat_lon = $get_lon + $xx_lon; } else { $grat_lon  = 1 + $get_lon + $xx_lon; }
          if ($get_lon + $xx_lon == -1) { $grat_lon = "-" . $grat_lon; }
          // -------------------------------------------------------------------------

          if ($get_debug)
          {
            echo "  <tr>\n";
            echo "    <td style=\"border-style:solid; border:solid 1px #bbb; padding:2px;\">$grat_lat</td>\n";
            echo "    <td style=\"border-style:solid; border:solid 1px #bbb; padding:2px;\">$grat_lon</td>\n";
            echo "    <td style=\"border-style:solid; border:solid 1px #bbb; padding:2px;\">$lat</td>\n";
            echo "    <td style=\"border-style:solid; border:solid 1px #bbb; padding:2px;\">$lon</td>\n";
            echo "  <tr>\n";
          }

          if (($yy_lat == 0) && ($xx_lon == 0))
          {
            if (($get_clat != "") && ($get_clon != ""))
            {
              $kml_head = kml_begin($get_date . "_" . $day . "_" . round($lat_g) . "_" . round($lon_g) . ".kml", $get_clat, $get_clon, 150000);  // kml head section
            }
            else
            {
              $kml_head = kml_begin($get_date . "_" . $day . "_" . round($lat_g) . "_" . round($lon_g) . ".kml", $lat, $lon, 1000);              // kml head section
            }
          }

  		  if ($kml_head == "") $kml_head = kml_begin($get_date . "_" . $day . "_" . round($lat_g) . "_" . round($lon_g) . ".kml", $lat, $lon, 1000);

          $kml .= kml_placemark($get_date, $grat_lat, $grat_lon, $lat, $lon, $day_nn);                       // kml placemark
          $countPins++;

          // -------------------------------------------------------------------------------
          // mnn: Convert -0 notation to useable coordinates
          //      1 => 1    0 => 0    -0 => -1    -1 => -2   etc.
          // -------------------------------------------------------------------------------
          if (mnn($grat_lat) <= $min_lat) $min_lat = mnn($grat_lat);    // -2  -1  -0   0   1   2   3
          if (mnn($grat_lon) <= $min_lon) $min_lon = mnn($grat_lon);    // -3  -2  -1   0   1   2   3
          if (mnn($grat_lat) >= $max_lat) $max_lat = mnn($grat_lat);
          if (mnn($grat_lon) >= $max_lon) $max_lon = mnn($grat_lon);
          if ($get_debug) echo "! " . mnn($grat_lat) . " " . mnn($grat_lon) . "<br>";
          // -------------------------------------------------------------------------------

          if ($count++ > 200) break;    // Discourage infinite loops!
        }    // if ((($get_lon + $xx_lon < -30) && ($dija_w_found)) || ($get_lon + $xx_lon >= -30))
      }      // for ($xx_lon = -$get_skins; $xx_lon < ($get_skins + 1);  $xx_lon++) {    // Iterate through longitudes (horizontal)
    }        // for ($yy_lat = -$get_skins; $yy_lat < ($get_skins + 1);  $yy_lat++) {    // Iterate through latitudes  (vertical)
    // -------------------------------------------------------------------------------------

    $kml .= kml_folder_end();

    // -------------------------------------------------------------------------------------

    // -------------------------------------------------------------------------------------
    // Draw the grid lines
    // -------------------------------------------------------------------------------------
      if ($get_debug) echo "<p>\$min_lat $min_lat<br>" .
                            "\$min_lon $min_lon<br>" .
                            "\$max_lat $max_lat<br>" .
                            "\$max_lon $max_lon</p>";
    $count = 0;
    $kml .= kml_folder_start("Grid Lines");
    for ($yy = $min_lat; $yy < $max_lat + 2;  $yy++)      // Iterate through latitudes (vertical)
    {
      for ($xx = $min_lon; $xx < $max_lon + 2;  $xx++)    // Iterate through longitudes (horizontal)
      {
        if ($xx < $max_lon + 1)
        {
          $kml .= kml_grid($xx, $yy, $xx + 1, $yy, $count);    // kml horizontal grid line (lon1,lat1,lon2,lat2)
          $count++;
        }
        if ($yy < $max_lat + 1)
        {
          $kml .= kml_grid($xx, $yy, $xx, $yy + 1, $count);    // kml vertical   grid line (lon1,lat1,lon2,lat2)
          $count++;
        }
        if ($get_debug) echo "<p>\$xx $xx, \$yy $yy</p>";
        if ($count > 400) break;    // Discourage infinite loops!
      }
    }
    // -------------------------------------------------------------------------------------

    $kml .= kml_folder_end();

    $kml .= kml_end();         // kml tail section
    $kml  = $kml_head . $kml;  // prepend the head section

    if ($get_debug) echo "</table>\n\n";
  }

  // ---------------------------------------------------------------------------------------
  if ($get_debug)
  {
    echo "<pre>KML Data:\n\n" . str_replace("<", "&lt;", $kml) . "</pre>\n\n";
    html_tail();
  } else {
    if ($countPins > 0) {
      header('Content-type: application/vnd.google-earth.kml+xml');
      header("Content-Disposition: attachment; filename=\"" . $get_date . "_" . $day . "_" . round($lat_g) . "_" . round($lon_g) . ".kml");  
      echo $kml;
    } else {
      html_head();
      echo "<h3>30W zone - DJIA not available yet.</h3>\n\n";
      html_tail();
    }
  }
  // ---------------------------------------------------------------------------------------

// -----------------------------------------------------------------------------------------

hash_up.php

<?php
// -----------------------------------------------------------------------------------------
// Geohashing support functions
// -----------------------------------------------------------------------------------------
  $crox = "http://www.geo.crox.net/djia/";     // try www, www1, www2     INCLUDE TRAILING /
//$crox = "http://carabiner.peeron.com/xkcd/map/data/";     // yyyy/mm/dd
  // ---------------------------------------------------------------------------------------
  function mnn($latlon)                                      // Convert -0 notation to useable coordinates    1 => 1    0 => 0    -0 => -1    -1 => -2   etc.
  {
         if ($latlon === "-0") return -1;
    else if ($latlon >= 0)     return $latlon;
    else if ($latlon < 0)      return $latlon - 1;
  }
  function validateDate($date)                               // Returns true if $date is a valid date formatted as 'yyyy-mm-dd'
  {
    $d = DateTime::createFromFormat('Y-m-d', $date);
    return $d && $d->format('Y-m-d') == $date;
  }
  function tweekDate($ISO_8601_Date, $days, $debug = false)  // Adds $days to the input date formatted as yyyy-mm-dd    The returned date format is also yyyy-mm-dd
  {
    $date_array = explode("-", $ISO_8601_Date);
    if ($debug) { echo "<pre>\$date_array "; print_r($date_array); echo "</pre>\n"; }
    $newDate    = mktime(0, 0, 0, $date_array[1], $date_array[2] + $days, $date_array[0]);
    return date('Y-m-d', $newDate);
  }
  function clean_up_get_params($_get)                        // Clean up $_GET and return an array of validated inputs or provide default values
  {
    if (isset($_get['lat']))   $get_lat   = $_get['lat'];   else $get_lat   = 51;
    if (isset($_get['lon']))   $get_lon   = $_get['lon'];   else $get_lon   =  0;
    if (isset($_get['clat']))  $get_clat  = $_get['clat'];  else $get_clat  = "";
    if (isset($_get['clon']))  $get_clon  = $_get['clon'];  else $get_clon  = "";
    if (isset($_get['date']))  $get_date  = $_get['date'];  else $get_date  = date("Y-m-d");
    if (isset($_get['skins'])) $get_skins = $_get['skins']; else $get_skins =  1;
    if (isset($_get['debug'])) { if ($_get['debug'] =="debug") { $get_debug = true; } else { $get_debug = false; } } else { $get_debug = false; }

    // -------------------------------------------------------------------------------------
    // $get_skins default and range check
    // -------------------------------------------------------------------------------------
    if ($get_skins == "")             $get_skins =    1;
    if (! is_int($get_skins + 0))     $get_skins =    1;
    if ($get_skins > 6)               $get_skins =    6;
    if ($get_skins < 0)               $get_skins =    0;

    // -------------------------------------------------------------------------------------
    // LATITUDE and LONGITUDE INTEGERS
    // -------------------------------------------------------------------------------------
    // $get_lat default and range check
    // -------------------------------------------------------------------------------------
    if ($get_lat == "")               $get_lat   =   51;
    if (! is_int($get_lat + 0))       $get_lat   =   51;
    if ($get_lat < 0)                 $get_lat   = $get_lat - 1;
    if ($get_lat === "-0")            $get_lat   =   -1;               //                          ##
    if ($get_lat + $get_skins >   89) $get_lat   =   89 - $get_skins;  //  89  88  87  86  85  84  83  82  81  80  79  78  77
    if ($get_lat - $get_skins <  -89) $get_lat   =  -90 + $get_skins;  //  89  88  87  86  85  84  83  82  81  80  79  78  77
    // -------------------------------------------------------------------------------------

    // -------------------------------------------------------------------------------------
    // $get_lon default and range check
    // -------------------------------------------------------------------------------------
    if ($get_lon == "")               $get_lon   =    0;
    if (! is_int($get_lon + 0))       $get_lon   =    0;
    if ($get_lon < 0)                 $get_lon   = $get_lon - 1;
    if ($get_lon === "-0")            $get_lon   =   -1;               //                         ###
    if ($get_lon + $get_skins >  179) $get_lon   =  179 - $get_skins;  // 179 178 177 176 175 174 173 172 171 170 169 168 167
    if ($get_lon - $get_skins < -179) $get_lon   = -180 + $get_skins;  // 179 178 177 176 175 174 173 172 171 170 169 168 167
    // -------------------------------------------------------------------------------------

    // -------------------------------------------------------------------------------------
    // VIEW CENTERING LATITUDE and LONGITUDE REAL NUMBERS
    // -------------------------------------------------------------------------------------
    // $get_clat default and range check
    // -------------------------------------------------------------------------------------
    if (! is_numeric(floatval($get_clat) + 0)) $get_clat = "";
    if (floatval($get_clat) < 0)               $get_clat = $get_clat - 1;
    if (floatval($get_clat) === "-0")          $get_clat =   -1;
    if (floatval($get_clat) >  89.9999)        $get_clat =  89.9999;
    if (floatval($get_clat) < -89.9999)        $get_clat = -89.9999;
    // -------------------------------------------------------------------------------------

    // -------------------------------------------------------------------------------------
    // $get_clon default and range check
    // -------------------------------------------------------------------------------------
    if (! is_numeric(floatval($get_clon) + 0)) $get_clon = "";
    if (floatval($get_clon) < 0)               $get_clon = $get_clon - 1;
    if (floatval($get_clon) === "-0")          $get_clon =   -1;
    if (floatval($get_clon) >  179.9999)       $get_clon =  179.9999;
    if (floatval($get_clon) < -179.9999)       $get_clon = -179.9999;
    // -------------------------------------------------------------------------------------

    // -------------------------------------------------------------------------------------
    // $get_date default and validate
    // -------------------------------------------------------------------------------------
    if ($get_date == "") $get_date  = date("Y-m-d");      // Today is the default
    if (! validateDate($get_date))
    {
      if ((is_int($get_date + 0)) && ($get_date >= -7) && ($get_date <= 7)) $get_date = tweekDate(date("Y-m-d"), $get_date); else $get_date = date("Y-m-d");
    }

    $return_array = array("get_date" => $get_date, "get_lat" => $get_lat, "get_lon" => $get_lon, "get_clat" => $get_clat, "get_clon" => $get_clon, "get_skins" => $get_skins, "get_debug" => $get_debug);

    return $return_array;
  }
  function get_http_response_code($url, $debug = false)      // See if the input URL returns a page successfully - codes 404 or 200 etc.
  {
    $headers = get_headers($url);
    if ($debug) echo "<pre>\n\n" . print_r($headers, 1) . "\n\n</pre>\n";
    return substr($headers[0], 9, 3);
  }
  function get_djias($date, $debug = false)                  // Attempt to get the DJIAs for west and east of -30
  {                                                          // DJIA from and thank you to - http://wiki.xkcd.com/geohashing/User:Crox
  	global $crox;

    $djia_e = false;
    $djia_w = false;
    $msg    = "";

    if ($date >= "2008-05-27")                         // The day of the algorithm change
    {
      $dow_date_e = tweekDate($date, -1);              // Use yesterday's opening price
      $dow_date_w = $date;                             // Use today's opening price
      $yye        = substr($dow_date_e, 0, 4) . "/";   // yyyy-mm-dd
      $mme        = substr($dow_date_e, 5, 2) . "/";   // 0123456789
      $dde        = substr($dow_date_e, 8, 2);

      $yyw        = substr($dow_date_w, 0, 4) . "/";   // yyyy-mm-dd
      $mmw        = substr($dow_date_w, 5, 2) . "/";   // 0123456789
      $ddw        = substr($dow_date_w, 8, 2);
    }
    else
    {
      $dow_date_e = $date;                             // Use today's opening price
      $dow_date_w = $date;                             // Use today's opening price

      $yye        = substr($dow_date_e, 0, 4) . "/";   // yyyy-mm-dd
      $mme        = substr($dow_date_e, 5, 2) . "/";   // 0123456789
      $dde        = substr($dow_date_e, 8, 2);

      $yyw        = substr($dow_date_w, 0, 4) . "/";   // yyyy-mm-dd
      $mmw        = substr($dow_date_w, 5, 2) . "/";   // 0123456789
      $ddw        = substr($dow_date_w, 8, 2);
    }
    if ($debug) echo "<p>\$dow_date_e $dow_date_e<br>\$dow_date_w $dow_date_w</p>\n";

    $urle = $crox . $yye . $mme . $dde;

    if ($debug) echo "<p>" . file_get_contents($urle) . "</p>\n";

    // -------------------------------------------------------------------------------------
    // Get data east of -30
    // -------------------------------------------------------------------------------------
    if(get_http_response_code("$crox$yye$mme$dde", $debug) != "200")
    {
      $msg .= "$dow_date_e: DJIA not found. Sorry!<br>";
    }
    else
    {
      $djia_e = file_get_contents("$crox$yye$mme$dde");

      if (! is_numeric($djia_e))
      {
        $msg .= "DJIA \"$djia_e\" seems to be invalid - Sorry!<br>";
      }
    }
    // -------------------------------------------------------------------------------------
    // Get data west of -30
    // -------------------------------------------------------------------------------------
    if(get_http_response_code("$crox$yyw$mmw$ddw", $debug) != "200")
    {
      $msg .= "$dow_date_w: DJIA not found. Sorry!<br>";
    }
    else
    {
      $djia_w = file_get_contents("$crox$yyw$mmw$ddw");

      if (! is_numeric($djia_w))
      {
        $msg .= "DJIA \"$djia_w\" seems to be invalid - Sorry!<br>";
      }
    }
    // -------------------------------------------------------------------------------------

    return array($djia_e, $djia_w, $msg);
  }
  function hex2dec($var)                                     // Return the input number converted into hexadecimal
  {                                                          // This code is based on - http://wiki.xkcd.com/geohashing/User:Eupeodes - Thanks!
    $o = 0;
    for($i = 0;  $i < 16;  $i++)
    {
      $o += hexdec($var[$i]) * pow(16, -$i -1);
    }
    return $o;
  }
  function get_coords($date, $djia, $debug = false)          // Input a date and return an array containing the fractional lat and lon values
  {                                                          // This code is based on - http://wiki.xkcd.com/geohashing/User:Eupeodes - Thanks!
    $md5 = md5($date."-".$djia);
    list($lat, $lon) = str_split($md5, 16);

    $latlon = array(hex2dec($lat), hex2dec($lon));

    if ($debug) echo "<pre>local:\n" . print_r($latlon, true) . "</pre>\n";

    return $latlon;
  }
  function get_global($date, $djia, $debug = false)          // return the global hashpoint
  {
    // To generate this point take W30 decimals for a date (to make it global).
    // Multiply the latitude by 180 and subtract 90
    // Multiply the longitude by 360 subtracting 180.
    // This will return a single point on the globe which is today's only globalhash.
    // ---------------------------------------------------------------------------------------
  	$md5 = md5($date."-".$djia);
    list($lat, $lon) = str_split($md5, 16);

    $latlon = array(hex2dec($lat), hex2dec($lon));

    if ($debug) echo "<pre>local:\n" . print_r($latlon, true) . "</pre>\n";

    $latlon[0] = $latlon[0] * 180 -  90;
    $latlon[1] = $latlon[1] * 360 - 180;

    if ($debug) echo "<pre>global:\n" . print_r($latlon, true) . "</pre>\n";

    return $latlon;
  }
?>

html_gen.php

<?php
// -----------------------------------------------------------------------------------------
// html support functions
// -----------------------------------------------------------------------------------------

  // ---------------------------------------------------------------------------------------
  // html head
  // ---------------------------------------------------------------------------------------
  function html_head()
  {
?><!DOCTYPE HTML>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>KML Geohash</title>
</head>

<body>

<?php
  }
  // ---------------------------------------------------------------------------------------

  // ---------------------------------------------------------------------------------------
  // html tail section if debugging
  // ---------------------------------------------------------------------------------------
  function html_tail()
  {
?></body>
</html><?php
  }
// -----------------------------------------------------------------------------------------
?>

kml_gen.php

<?php
// -----------------------------------------------------------------------------------------
// KML file support functions
// -----------------------------------------------------------------------------------------

  // ---------------------------------------------------------------------------------------
  // Build the KML file beginning text
  // ---------------------------------------------------------------------------------------
  function kml_begin($file_name, $lat, $lon, $range = 1000)
  {
    $kml  = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
    $kml .= "<kml xmlns=\"http://www.opengis.net/kml/2.2\" xmlns:gx=\"http://www.google.com/kml/ext/2.2\" xmlns:kml=\"http://www.opengis.net/kml/2.2\" xmlns:atom=\"http://www.w3.org/2005/Atom\">\n";
    $kml .= "<Document>\n";
    $kml .= "    <name>$file_name</name>\n";
    $kml .= "    <description><![CDATA[This data is designed to work with KML aware applications like <a href=\"http://www.google.com/earth/\">Google Earth</a> or <a href=\"https://marble.kde.org/\">Marble</a>.]]></description>\n";
    $kml .= "    <LookAt>\n";
    $kml .= "        <longitude>$lon</longitude>\n";
    $kml .= "        <latitude>$lat</latitude>\n";
    $kml .= "        <altitude>0</altitude>\n";
    $kml .= "        <heading>0</heading>\n";
    $kml .= "        <tilt>0</tilt>\n";
    $kml .= "        <range>" . $range . "</range>\n";
    $kml .= "    </LookAt>\n";

    return $kml;
  }
  //----------------------------------------------------------------------------------------
  
  // ---------------------------------------------------------------------------------------
  // Build the KML for a push pin and its style
  // ---------------------------------------------------------------------------------------
  function kml_style()
  {
    $kml  = "    <Style id=\"s_ylw-pushpin_hl\">\n";
    $kml .= "        <IconStyle>\n";
    $kml .= "            <scale>1.3</scale>\n";
    $kml .= "            <Icon>\n";
    $kml .= "                <href>http://maps.google.com/mapfiles/kml/paddle/grn-blank.png</href>\n";
    $kml .= "            </Icon>\n";
    $kml .= "            <hotSpot x=\"32\" y=\"1\" xunits=\"pixels\" yunits=\"pixels\"/>\n";
    $kml .= "        </IconStyle>\n";
    $kml .= "        <ListStyle>\n";
    $kml .= "            <ItemIcon>\n";
    $kml .= "                <href>http://maps.google.com/mapfiles/kml/paddle/grn-blank-lv.png</href>\n";
    $kml .= "            </ItemIcon>\n";
    $kml .= "        </ListStyle>\n";
    $kml .= "    </Style>\n";
    $kml .= "    <Style id=\"s_ylw-pushpin\">\n";
    $kml .= "        <IconStyle>\n";
    $kml .= "            <scale>1.1</scale>\n";
    $kml .= "            <Icon>\n";
    $kml .= "                <href>http://maps.google.com/mapfiles/kml/paddle/grn-blank.png</href>\n";
    $kml .= "            </Icon>\n";
    $kml .= "            <hotSpot x=\"32\" y=\"1\" xunits=\"pixels\" yunits=\"pixels\"/>\n";
    $kml .= "        </IconStyle>\n";
    $kml .= "        <ListStyle>\n";
    $kml .= "            <ItemIcon>\n";
    $kml .= "                <href>http://maps.google.com/mapfiles/kml/paddle/grn-blank-lv.png</href>\n";
    $kml .= "            </ItemIcon>\n";
    $kml .= "        </ListStyle>\n";
    $kml .= "    </Style>\n";
    $kml .= "    <StyleMap id=\"m_ylw-pushpin\">\n";
    $kml .= "        <Pair>\n";
    $kml .= "            <key>normal</key>\n";
    $kml .= "            <styleUrl>#s_ylw-pushpin</styleUrl>\n";
    $kml .= "        </Pair>\n";
    $kml .= "        <Pair>\n";
    $kml .= "            <key>highlight</key>\n";
    $kml .= "            <styleUrl>#s_ylw-pushpin_hl</styleUrl>\n";
    $kml .= "        </Pair>\n";
    $kml .= "    </StyleMap>\n";
    
    return $kml;
  }
  //----------------------------------------------------------------------------------------
  
  // ---------------------------------------------------------------------------------------
  // Start the folder
  // ---------------------------------------------------------------------------------------
  function kml_folder_start($folderName)
  {
    $kml  = "    <Folder>\n";
    $kml .= "        <name>$folderName</name>\n";
    
    return $kml;
  }
  // ---------------------------------------------------------------------------------------

  // ---------------------------------------------------------------------------------------
  // End the folder
  // ---------------------------------------------------------------------------------------
  function kml_folder_end()
  {
    $kml  = "    </Folder>\n";
    
    return $kml;
  }
  // ---------------------------------------------------------------------------------------

  // ---------------------------------------------------------------------------------------
  // Build a KML placemark
  // ---------------------------------------------------------------------------------------
  function kml_placemark($get_date, $grat_lat, $grat_lon, $lat, $lon, $day)
  {
    $kml  = "        <Placemark>\n";
    $kml .= "            <name>" . $grat_lat . " " . $grat_lon . " " . $day . "</name>\n";
    $kml .= "            <description><![CDATA[" . 
                              number_format($lat, 6) . " " . number_format($lon, 6) . "<br>" .
                              "<a href=\"http://wiki.xkcd.com/geohashing/" . $get_date . "_" . $grat_lat . "_" . $grat_lon . "\">" . $get_date . " " . $grat_lat . " " .  $grat_lon . "</a><br>" . 
                              "<a href=\"http://geo.crox.net/poster/" . $get_date . " " . $grat_lat . " " . $grat_lon . "\">Poster</a> &nbsp; &nbsp; " . 
                              "<a href=\"http://wiki.xkcd.com/geohashing/" . $grat_lat . "," . $grat_lon . "\">Graticule</a><br>" . 
                              "<a href=\"http://carabiner.peeron.com/xkcd/map/map.html?date=" . $get_date . "&lat=" . $grat_lat . "&long=" . $grat_lon . "&zoom=8\">Peeron</a> &nbsp; &nbsp; " . 
                              "<a href=\"http://geohashing.info/" . $get_date . "/s/z:8/" . $grat_lat . "," . $grat_lon . "\">Geohashing</a><br>" . 
                              "<a href=\"https://www.google.com/maps/dir/?api=1&destination=$lat,$lon\">Google Nav</a> &nbsp; &nbsp; " . 
                              "<a href=\"http://www.openstreetmap.org/?mlat=" . $lat . "&mlon=" . $lon . "&zoom=16\">OSM</a><br>" . 
                              "<a href=\"http://www.bing.com/maps/?cp=" . $lat . "~" . $lon . "&lvl=15&style=s&sp=point." . $lat . "_" . $lon . "_" . $get_date . " " . $grat_lat . " " . $grat_lon . "\">Bing for UK OS</a>" . 
                              "]]></description>\n";
    // 
    // "<a href=\"http://maps.google.com/?ie=UTF8&ll=" . $lat . "," . $lon . "&z=8&q=loc:" . $lat . "," . $lon . "\">Google Map</a><br>" .
    $kml .= "            <LookAt>\n";
    $kml .= "                <longitude>" . number_format($lon, 6) . "</longitude>\n";
    $kml .= "                <latitude>"  . number_format($lat, 6) . "</latitude>\n";
    $kml .= "                <altitude>0</altitude>\n";
    $kml .= "                <heading>0</heading>\n";
    $kml .= "                <tilt>0</tilt>\n";
    $kml .= "                <range>1000</range>\n";
  //$kml .= "                <gx:altitudeMode>relativeToSeaFloor</gx:altitudeMode>\n";
    $kml .= "            </LookAt>\n";
    $kml .= "            <styleUrl>#m_ylw-pushpin</styleUrl>\n";
    $kml .= "            <Point>\n";
    $kml .= "                <gx:drawOrder>1</gx:drawOrder>\n";
    $kml .= "                <coordinates>" . number_format($lon, 6) . "," . number_format($lat, 6) . ",0</coordinates>\n";
    $kml .= "            </Point>\n";
    $kml .= "        </Placemark>\n";
    
    return $kml;
  }
  // ---------------------------------------------------------------------------------------
  
  // ---------------------------------------------------------------------------------------
  // Build a KML global placemark
  // ---------------------------------------------------------------------------------------
  function kml_globalmark($get_date, $lat_g, $lon_g, $day)
  {
    $kml  = "        <Placemark>\n";
    $kml .= "            <name>Global - " .  $day . "</name>\n";
    $kml .= "            <description><![CDATA[" . 
                              number_format($lat_g, 6) . " " . number_format($lon_g, 6) . "<br>" .
                              "<a href=\"http://wiki.xkcd.com/geohashing/" . $get_date . "_global\">" . $get_date . " global</a><br>" . 
                              "<a href=\"http://wiki.xkcd.com/geohashing/Global\">Globalhash</a> &nbsp; &nbsp; " . 
                              "<a href=\"http://geo.crox.net/poster/" . $get_date . "_global\">Poster</a><br>" . 
                              "<a href=\"http://www.openstreetmap.org/?mlat=" . $lat_g . "&mlon=" . $lon_g . "&zoom=16\">OSM</a> &nbsp; &nbsp; " . 
                              "<a href=\"http://maps.google.com/?ie=UTF8&ll=" . $lat_g . "," . $lon_g . "&z=8&q=loc:" . $lat_g . "," . $lon_g . "\">Google</a> &nbsp; &nbsp; " . 
                              "<a href=\"http://www.bing.com/maps/?cp=" . $lat_g . "~" . $lon_g . "&lvl=15&style=s&sp=point." . $lat_g . "_" . $lon_g . "_" . $get_date . " global\">Bing</a>" . 
                              "]]></description>\n";
    $kml .= "            <LookAt>\n";
    $kml .= "                <longitude>" . number_format($lon_g, 6) . "</longitude>\n";
    $kml .= "                <latitude>"  . number_format($lat_g, 6) . "</latitude>\n";
    $kml .= "                <altitude>0</altitude>\n";
    $kml .= "                <heading>0</heading>\n";
    $kml .= "                <tilt>0</tilt>\n";
    $kml .= "                <range>4000000</range>\n";
  //$kml .= "                <gx:altitudeMode>relativeToSeaFloor</gx:altitudeMode>\n";
    $kml .= "            </LookAt>\n";
    $kml .= "            <styleUrl>#m_ylw-pushpin</styleUrl>\n";
    $kml .= "            <Point>\n";
    $kml .= "                <gx:drawOrder>1</gx:drawOrder>\n";
    $kml .= "                <coordinates>" . number_format($lon_g, 6) . "," . number_format($lat_g, 6) . ",0</coordinates>\n";
    $kml .= "            </Point>\n";
    $kml .= "        </Placemark>\n";
    
    return $kml;
  }
  // ---------------------------------------------------------------------------------------

  // ---------------------------------------------------------------------------------------
  // Build a KML gridline
  // ---------------------------------------------------------------------------------------
  function kml_grid($lon1, $lat1, $lon2, $lat2, $count)
  {
    $kml  = "        <Placemark>\n";
    $kml .= "            <name>Path_$count</name>\n";
    $kml .= "            <LineString>\n";
    $kml .= "                <tessellate>1</tessellate>\n";
    $kml .= "                <coordinates>\n";
    $kml .= "                   $lon1,$lat1,0 $lon2,$lat2,0\n";
    $kml .= "                </coordinates>\n";
    $kml .= "            </LineString>\n";
    $kml .= "        </Placemark>\n";
    
    return $kml;
  }
  // ---------------------------------------------------------------------------------------

  // ---------------------------------------------------------------------------------------
  // Build the KML file ending text
  // ---------------------------------------------------------------------------------------
  function kml_end()
  {
    $kml  = "</Document>\n";
    $kml .= "</kml>";
    
    return $kml;
  }
  // ---------------------------------------------------------------------------------------
  
// -----------------------------------------------------------------------------------------
?>

testForm.php

<!DOCTYPE HTML>
<html lang="en">
<head>
  <meta charset="UTF-8">

  <title>Geohashing KML Calculator</title>
  <link href="testForm.css" rel="stylesheet" type="text/css">
<script>
  // -------------------------------------------------------------
  // http://nbest.co.uk/kmlGeohash/index.php?date=2016-01-01&lat=52&lon=0&skins=1
  function urlGen() {
    var f_date  = document.forms["kmlForm"]["f_date"].value;
    var f_lat   = document.forms["kmlForm"]["f_lat"].value;
    var f_lon   = document.forms["kmlForm"]["f_lon"].value;
    var f_clat  = document.forms["kmlForm"]["f_clat"].value;
    var f_clon  = document.forms["kmlForm"]["f_clon"].value;
    var f_skins = document.forms["kmlForm"]["f_skins"].value;
    var f_debug = "";

    if (document.forms["kmlForm"]["f_debug"].checked){
      f_debug = "debug";
    }else{
      f_debug = "";
    }
    
    var f_url = "<pre><a href=\"http://nbest.co.uk/kmlGeohash/index.php?date=" + f_date;
        f_url = f_url + "&lat=" + f_lat;
        f_url = f_url + "&lon=" + f_lon;
        f_url = f_url + "&clat=" + f_clat;
        f_url = f_url + "&clon=" + f_clon;
        f_url = f_url + "&skins=" + f_skins;
        f_url = f_url + "&debug=" + f_debug;
        f_url = f_url + "\">http://nbest.co.uk/kmlGeohash/index.php?date=" + f_date;
        f_url = f_url + "&lat=" + f_lat;
        f_url = f_url + "&lon=" + f_lon ;
        f_url = f_url + "&clat=" + f_clat;
        f_url = f_url + "&clon=" + f_clon;
        f_url = f_url + "&skins=" + f_skins;
        f_url = f_url + "&debug=" + f_debug;
        f_url = f_url + "</a></pre>";
    
    // console.log(f_url);
    
    document.getElementById("f_url").innerHTML = f_url;
  }
  // -------------------------------------------------------------
</script>
</head>

<body>
<h1> Geohashing KML Calculator</h1>
<p><img src="Sourcerer%20KML.jpg" alt="KML Calculator" width="615" height="451" /></p>
<ul>
  <li>This tool works in conjunction with KML aware applications like  <a href="https://www.google.com/earth/versions/#earth-pro">Google Earth Pro Desktop</a> and <a href="https://marble.kde.org/">Marble</a>.</li>
  <li>When you submit the form, if your PC is correctly set up, Google Earth Desktop will launch and show the calculated hashpoints.</li>
  <li>This seems to work on Android mobile phones and perhaps others too.</li>
  <li>UK geohashers might  find the link to the Ordnance Survey maps particularly useful. Outside the UK you just get the normal Bing maps.</li>
  <li>Use this form or make a bookmark or shortcut - like this<br>
    <a href="https://nbest.co.uk/kmlGeohash/index.php?date=2016-01-31&lat=52&lon=-1&clat=52.5&clon=1.5&skins=3">https://nbest.co.uk/kmlGeohash/index.php?date=2016-01-31&lat=52&lon=-1&clat=52.5&clon=1.5&skins=3</a>
  </li>
  <li>For the <strong>Globalhash</strong>, zoom out until it comes into view.</li>
</ul>
<form action="index.php" id="kmlForm">
<table>
  <tr>
    <td style="text-align:center;"><strong>date</strong></td>
    <td><input name="date" type="text" id="f_date" size="12" maxlength="10" onKeyUp="urlGen()"></td>
    <td><strong>Date: yyyy-mm-dd</strong> - if blank, the date defaults to today - alternatively use -7 to 7,  1 means tomorrow, -2 is the day before yesterday.</td>
  </tr>
  <tr>
    <td style="text-align:center;"><strong>lat</strong></td>
    <td><input name="lat" type="text" id="f_lat" size="12" maxlength="4" onKeyUp="urlGen()"></td>
    <td><strong>Latitude: -89 to 89</strong> - if blank, the latitude defaults to  51 (Grenwich). Whole numbers please.</td>
  </tr>
  <tr>
    <td style="text-align:center;"><strong>lon</strong></td>
    <td><input name="lon" type="text" id="f_lon" size="12" maxlength="4" onKeyUp="urlGen()"></td>
    <td><strong>Longitude: -179 to 179</strong> - if blank, the longitude defaults to  0 (Grenwich meridian). Whole numbers please.</td>
  </tr>
  <tr>
    <td style="text-align:center;"><strong>clat</strong></td>
    <td><input name="clat" type="text" id="f_clat" size="12" maxlength="4" onKeyUp="urlGen()"></td>
    <td><strong>Centre Latitude: -89.9999 to 89.9999</strong> - optional view centre. It's OK to leave this blank.  </tr>
  <tr>
    <td style="text-align:center;"><strong>clon</strong></td>
    <td><input name="clon" type="text" id="f_clon" size="12" maxlength="4" onKeyUp="urlGen()"></td>
    <td><strong>Centre Longitude: -179.9999 to 179.9999</strong> -  - optional view centre. It's OK to leave this blank.  </tr>
  <tr>
    <td style="text-align:center;"><strong>skins</strong></td>
    <td><input name="skins" type="text" id="f_skins" size="12" maxlength="1" onKeyUp="urlGen()"></td>
    <td><strong>Skins: 0 to 6.</strong> The default is 1. This gives 9 hashpoints. 6 skins will give you 169 hashpoints.</td>
  </tr>
  <tr>
    <td style="text-align:center;"><strong>debug</strong></td>
    <td style="text-align:center;"><input name="debug" type="checkbox" id="f_debug" value="debug" onClick="urlGen()"></td>
    <td><strong>Debug:</strong> Check this to get debuging information. The KML data will be shown as text.</td>
  </tr>
  <tr>
    <td> </td>
    <td style="text-align:center;"><p><input type="submit" name="Submit" id="Submit" value="Submit"></p></td>
    <td><span id="f_url"></spanp></td>
  </tr>
</table>
</form>
<p><strong>Disclaimer:</strong> Do no damage. Don't disturb people, animals or the environment. Stay safe!</p>
<p><a href="http://wiki.xkcd.com/geohashing/User:Sourcerer/KML_tool">Geohashing Wiki</a> - <a href="https://github.com/nbauers/Geohashing-KML-Calculator">Code on GitHub</a> - <a href="source.php">Calculator Live Source</a></p>
</body>
</html>

testForm.css

/* CSS Document */

a {
  text-decoration:none;
}

a:hover {
  color:#ff0000;
}

table {
  border-collapse: collapse;
}

td {
  border-style:solid;
  border-width: 1px;
  padding: 4px;
  border-color: gray;
  vertical-align:top;
}