view tools/makegmaps.php @ 246:a0a788e1b82e gmap2

Prevent non-cli execution of the makegmaps script.
author Matti Hamalainen <ccr@tnsp.org>
date Mon, 17 Mar 2014 03:11:25 +0200
parents 72aea6651857
children 51921cbc3c4f 1c32c513d9ba
line wrap: on
line source

#!/usr/bin/php
<?php
// Prevent non-cli execution
if (php_sapi_name() != "cli" || !empty($_SERVER["REMOTE_ADDR"]))
  die("Dongs.\n");


$gmapsConfig = "config.php";


// Paths and files
$cfgDefaults = array(
  "binConvert" => array(1, "convert", "Path of 'convert' binary from ImageMagick or GraphicsMagick."),
  "binMercurial" => array(1, "hg", "Path of 'hg' binary of Mercurial DVCS."),
  "binMake" => array(1, "make", "Path of 'make', preferably GNU make."),

  "pathMapUtils" => array(2, "maputils/", "Path for maputils directory tree."),
  "pathImageCache" => array(2, "cache/", "Image cache directory."),
  "pathMarkerData" => array(2, "../", "Path to marker data files."),
  "pathTileData" => array(2, "../tiles/", "Path to map tiles directory structure."),

  "pathRawMaps" => array(3, FALSE, "Path to the raw ASCII maps."),
  "pathLocFiles" => array(3, FALSE, "Path to the location data LOC files."),
  "worldConfig" => array(3, FALSE, "Path of the world configuration file 'world.inc.php' from MapUtils."),
);


$fontFile = "./lucon.ttf";


// Internal data and settings
$tileDim = 256;

$minZoom = 1;
$maxZoom = 10;

// Font sizes
$fontSize = array();
$fontSize[ 8] = 7;
$fontSize[16] = 13;
$fontSize[32] = 26;

$modes = array(
  "xml"     => array("batclient.xml"     , 0, 0),
  "json"    => array("markers.json"      , 0, 0),
);

$mapPalette = array();
$mapPalette["!"]  = array(204,255,255);
$mapPalette["%"]  = array(  0,170,170);
$mapPalette["-"]  = array( 51, 51, 51);
$mapPalette["|"]  = array( 51, 51, 51);
$mapPalette["/"]  = array( 51, 51, 51);
$mapPalette["+"]  = array( 51, 51, 51);
$mapPalette["\\"] = array( 51, 51, 51);
$mapPalette["="]  = array( 72, 67, 57);
$mapPalette["@"]  = array(255,107,  0);
$mapPalette["F"]  = array(  0,136,  0);
$mapPalette["L"]  = array(255, 80,  0);
$mapPalette["S"]  = array( 68,204,204);
$mapPalette["^"]  = array(113,130,146);
$mapPalette["c"]  = array( 95, 86, 85);
$mapPalette["f"]  = array(  0,182,  0);
$mapPalette["i"]  = array(255,255,255);
$mapPalette["l"]  = array(100,100,255);
$mapPalette["s"]  = array(157,168, 10);
$mapPalette["v"]  = array( 34,221, 34);
$mapPalette["x"]  = array(138,131, 96);
$mapPalette["z"]  = array(177,164,133);
$mapPalette["#"]  = array( 79, 54, 69);
$mapPalette["."]  = array( 85,146,  0);
$mapPalette[","]  = array(140, 87, 56);
$mapPalette["?"]  = array(255,255,  0);
$mapPalette["C"]  = array(153,153,  0);
$mapPalette["H"]  = array(102, 63,  0);
$mapPalette["R"]  = array( 51,102,255);
$mapPalette["V"]  = array(255, 51,  0);
$mapPalette["b"]  = array(207,196,165);
$mapPalette["d"]  = array(238,187, 34);
$mapPalette["h"]  = array(153,102,  0);
$mapPalette["j"]  = array( 19,150, 54);
$mapPalette["r"]  = array(102,153,255);
$mapPalette["t"]  = array( 97,195,162);
$mapPalette["w"]  = array(119,170,255);
$mapPalette["y"]  = array(167,204, 20);
$mapPalette["~"]  = array( 51, 51,170);
$mapPalette["1"]  = array(255,102, 16);
$mapPalette[3]    = array(0,0, 0);


//
// Helper functions
//
function stMakeDir($path)
{
  if (file_exists($path))
    return TRUE;
  else
    return mkdir($path, 0755, TRUE);
}


function stOutputToFile($sfilename, $sdata)
{
  if (file_put_contents($sfilename, $sdata, LOCK_EX) === FALSE)
    die("Error writing to '".$sfilename."'.\n");
}


function stOutputToJSONFile($qfilename, $qdata)
{
  stOutputToFile($qfilename, json_encode($qdata));
}


// Calculate worldmap coordinates from continent coordinates
function stGetWorldCoords($cont, $xp, $yp, &$xc, &$yc)
{
  global $worldMap, $continentList;

  if (!isset($continentList[$cont]))
    return FALSE;

  $xc = $worldMap["ox"] + $continentList[$cont][1] + $xp - 1;
  $yc = $worldMap["oy"] + $continentList[$cont][2] + $yp - 1;
  return TRUE;
}


// Get worldmap coordinates for a given tradelane waypoint
function stGetWaypointCoords($waypoint, &$xc, &$yc)
{
  global $tradelanePoints;
  
  if (!isset($tradelanePoints[$waypoint]))
    return FALSE;

  return stGetWorldCoords($tradelanePoints[$waypoint][0],
    $tradelanePoints[$waypoint][1], $tradelanePoints[$waypoint][2], $xc, $yc);
}


function stYesNoPrompt($msg, $default = FALSE)
{
  echo $msg." [".($default ? "Y/n" : "y/N")."]? ";
  $sprompt = strtolower(trim(fgets(STDIN)));

  if ($default)
    return ($sprompt == "n");
  else
    return ($sprompt == "y");
}


function stInputPrompt($msg, $default = FALSE, $validate = null)
{
  $valid = FALSE;
  while (!$valid)
  {
    echo $msg."\n".($default !== FALSE ? "[".$default."]" : "")."> ";
    $sprompt = strtolower(trim(fgets(STDIN)));

    if ($sprompt == "")
      $sprompt = ($default !== FALSE ? $default : "");

    $valid =  !is_callable($validate) || call_user_func($validate, $sprompt);
  }
  return $sprompt;
}


function stValidateNotEmpty($val)
{
  if ($val == "")
  {
    echo "The value can't be empty.\n";
    return FALSE;
  }
  else
    return TRUE;
}


function stValidateURLPrefix($sprefix)
{
  if (substr($sprefix, 0, 7) != "http://" &&
      substr($sprefix, 0, 8) != "https://")
  {
    echo "Prefix must start with http:// or https://\n";
    return FALSE;
  }
  else
  if (substr($sprefix, -1) != "/")
  {
    echo "Prefix must end with /\n";
    return FALSE;
  }
  else
  if (preg_match("/^https?:\/\/[a-zA-Z0-9]+[a-zA-Z0-9\/\.\:\-]*?\//", $sprefix) === FALSE)
  {
    echo "Malformed URL (or atleast this silly regexp does not want it.\n";
    return FALSE;
  }
  else
    return TRUE;
}


function stMakeHtAccessFile($urlprefix, $sprefix, $spath)
{
  global $firstRun;
  $sfile = $sprefix.$spath.".htaccess";
  if (($firstRun || !file_exists($sfile)) && file_exists($sprefix.$spath."sea.png"))
  {
    echo "Creating ".$sfile."\n";
    stOutputToFile($sfile, "ErrorDocument 404 ".$urlprefix.$spath."sea.png\n");
  }
}


function stQueryConfigItems($level)
{
  global $cfgDefaults, $cfg;
  foreach ($cfgDefaults as $citem => $cdata)
  {
    if ($cdata[0] == $level)
    {
      $def = ($cdata[1] !== FALSE) ? $cdata[1] : $cfg[$citem];
      $cfg[$citem] = stInputPrompt($cdata[2], $def, "stValidateNotEmpty");
    }
  }
}


//
// Check for first run
//
echo
  "===========================================================\n".
  "GMaps TNG bootstrap and update script by Ggr & Jeskko\n".
  "===========================================================\n";
  
if (file_exists($gmapsConfig))
{
  $firstRun = FALSE;
  include $gmapsConfig;
}
else
{
  $firstRun = TRUE;
  echo
    "It seems you are running this for the first time ..\n".
    "You will be asked some information this time, which will be\n".
    "and saved for later use in file '".$gmapsConfig."'\n";

  stQueryConfigItems(1);
  stQueryConfigItems(2);

  $cfg["pathRawMaps"] = $cfg["pathMapUtils"]."maps/";
  $cfg["pathLocFiles"] = $cfg["pathMapUtils"]."maps/";
  $cfg["worldConfig"] = $cfg["pathMapUtils"]."www/world.inc.php";

  stQueryConfigItems(3);

  $sdone = FALSE;
  while (!$sdone)
  {
    $cfg["urlTilePrefix"] = stInputPrompt("Enter URL prefix for tiles:\n", FALSE, "stValidateURLPrefix");
    $sdone = stYesNoPrompt("The URL prefix to be used is \"".$cfg["urlTilePrefix"]."\", e.g.\n".
      "htaccess to be created would be: \"".$cfg["urlTilePrefix"]."sea.png\"\n".
      "Is this correct?");
    echo "\n";
  }

  stOutputToFile($gmapsConfig, "<?\n\$cfg = ".var_export($cfg, TRUE)."\n?>");
}


//
// Set rest of the paths etc
//
$worldJS = $cfg["pathMarkerData"]."world.js";
$tradelaneOut = $cfg["pathMarkerData"]."tradelane.json";
$tradelaneOverlay = $cfg["pathMarkerData"]."trlines.json";
$binMkLoc = $cfg["pathMapUtils"]."mkloc";
$rawSuffix = ".new";
$rawAltSuffix = ".map";


//
// Include continent and tradelane configuration 
//
if (!file_exists($cfg["worldConfig"]))
  die("Required continent/tradelane configuration file '".$cfg["worldConfig"]."' not found.\n");

require $cfg["worldConfig"];


//
// Create htaccess files
//
stMakeHtAccessFile($cfg["urlTilePrefix"], $cfg["pathTileData"], "");
for ($i = $minZoom; $i <= $maxZoom; $i++)
  stMakeHtAccessFile($cfg["urlTilePrefix"], $cfg["pathTileData"], $i."/");


//
// Build maputils and fetch latest map data
//
if (!file_exists($cfg["pathMapUtils"]))
{
  // If maputils does not exist, clone the repository
  $tmp = $cfg["binMercurial"]." clone http://pupunen.net/hg/maputils/ ".escapeshellarg($cfg["pathMapUtils"]);
  echo "* $tmp\n";
  passthru($tmp) == 0 or die("Error executing: ".$tmp."\n");

  // Clone th-libs
  $tmp = $cfg["binMercurial"]." clone http://tnsp.org/hg/th-libs/ ".escapeshellarg($cfg["pathMapUtils"]."th-libs/");
  echo "* $tmp\n";
  passthru($tmp) == 0 or die("Error executing: ".$tmp."\n");
}
else
{
  $tmp = "cd ".escapeshellarg($cfg["pathMapUtils"])." && ".$cfg["binMercurial"]." pull && ".$cfg["binMercurial"]." update";
  echo "* $tmp\n";
  passthru($tmp) == 0 or die("Error executing: ".$tmp."\n");

  $tmp = "cd ".escapeshellarg($cfg["pathMapUtils"]."th-libs/")." && ".$cfg["binMercurial"]." pull && ".$cfg["binMercurial"]." update";
  echo "* $tmp\n";
  passthru($tmp) == 0 or die("Error executing: ".$tmp."\n");
}

$tmp = "cd ".escapeshellarg($cfg["pathMapUtils"])." && ".$cfg["binMake"]." ".escapeshellarg(basename($binMkLoc));
echo "* $tmp\n";
passthru($tmp) == 0 or die("Error executing: ".$tmp."\n");


if (!file_exists($binMkLoc))
  die($binMkLoc." not found. Maputils package not built, or some other error occured.\n");

$tmp = "cd ".escapeshellarg($cfg["pathMapUtils"])." && ".$cfg["binMake"];
passthru($tmp) == 0 or die("Error executing: ".$tmp."\n");

$tmp = "cd ".escapeshellarg($cfg["pathRawMaps"])." && ".$cfg["binMake"]." fetch 2> /dev/null";
passthru($tmp) == 0 or die("Error executing: ".$tmp."\n");


//
// Generate continents JavasScript data file
///
echo "* Generating $worldJS ...\n";
$str  = "var pmapWorld = {";
foreach ($worldMap as $wkey => $wval)
  $str .= "\"".$wkey."\": ".$wval.", ";
$str .= "};\n";

$str .= "var pmapContinents =\n[\n";
foreach ($continentList as $name => $data)
if ($data[4] && $data[7])
{
  $str .= sprintf("  [%-15s, %5d, %5d, %5d, %5d, %5d, %5d],\n",
  "\"".$data[0]."\"",
  $data[1],
  $data[2],
  $data[1] + $data[5] - 1,
  $data[2] + $data[6] - 1,
  $data[5], $data[6]);
}
$str .= "];\n";

stOutputToFile($worldJS, $str);



//
// Generate marker files from LOC data
///
echo "Converting location data from LOC files to GMaps markers...\n";

foreach ($modes as $mode => $mdata)
{
  $tmp = escapeshellcmd($binMkLoc)." -v -o ".escapeshellarg($cfg["pathMarkerData"].$mdata[0])." -G ".$mode." ";

  foreach ($continentList as $name => $data)
  {
    if ($data[4] && $data[7])
    {
      // has a map
      $tmp .=
      "-l ".escapeshellarg($cfg["pathLocFiles"].$name.".loc")." ".
      "-c ".escapeshellarg($data[0])." ".
      "-x ".escapeshellarg($worldMap["ox"] + $data[1] + $mdata[1])." ".
      "-y ".escapeshellarg($worldMap["oy"] + $data[2] + $mdata[2])." ";
    }
  }

  passthru($tmp) == 0 or die("Error executing: ".$tmp."\n");
}


//
// Export tradelane waypoint data
//
if (!isset($tradelanePoints))
  die("PHP array \$tradelanePoints not set, '".$cfg["worldConfig"]."' is old or incompatible.\n");

echo "\nCreating tradelane waypoint data '".$tradelaneOut."' ...\n";

$qdata = array();

foreach ($tradelanePoints as $name => $data)
{
  $html = "<b>TRADELANE WPT</b><br>".htmlentities($name);

  if (!stGetWorldCoords($data[0], $data[1], $data[2], $xc, $yc))
    die("Invalid tradelane waypoint '".$name."', continent '".$data[0]."' not defined.\n");

  $qdata[] = array(
    "x" => $xc,
    "y" => $yc,
    "name" => $name,
    "html" => $html,
    "continent" => "",
    "type" => "tradelane",
    "flags" => 0,
  );
}

stOutputToJSONFile($tradelaneOut, $qdata);


//
// Export tradelane polyline data
//
echo "\nCreating tradelane polyline data '".$tradelaneOverlay."' ...\n";
if (!isset($tradelaneDefs))
  die("PHP array \$tradelaneDefs not set, '".$cfg["worldConfig"]."' is old or incompatible.\n");

$qdata = array();
foreach ($tradelaneDefs as $index => $points)
{
  $qline = array();

  foreach ($points as $point)
  {
    if (!stGetWaypointCoords($point, $xc, $yc))
      die("Invalid tradelane definition #$index: waypoint '".$point."' not defined.\n");

    $qline[] = array(
      "x" => $xc,
      "y" => $yc
    );
  }
  
  $qdata[] = $qline;
}


stOutputToJSONFile($tradelaneOverlay, $qdata);


//
// Generate PNG maps
//
function makeMap($inFilename, $outFilename, $zlevel, $data)
{
  global $mapPalette, $fontFile, $fontSize;

  // Try to open input file
  $file = @fopen($inFilename, "r");
  if ($file === FALSE)
  {
    echo "Could not open input '".$inFilename."'\n";
    return FALSE;
  }

  // Derp
  $zoom = pow(2, $zlevel - 1);
  $width = $data[5];
  $height = $data[6];

  // Create image and assign colors
  $im = @imagecreate($width*$zoom, $height*$zoom);
  if ($im === FALSE)
  {
    echo "Could not allocate image data for '".$inFilename."'\n";
    return FALSE;
  }

  $black = imagecolorallocate($im, 0, 0, 0);
  foreach ($mapPalette as $id => $val)
    $colors[$id] = imagecolorallocate($im, $val[0], $val[1], $val[2]);

  imagefilledrectangle($im, 0, 0, $width*$zoom, $height*$zoom, $black);

  // Read input raw
  $y = 0;
  while ($y < $height && ($data = fgets($file, 4096)) !== FALSE) 
  {
    for ($x = 0; $x < $width; $x++)
    {
      if ($zoom == 1)
      {
        imagesetpixel($im, $x, $y, $colors[$data[$x]]);
      }
      else 
      if ($zoom < 6)
      {
        imagefilledrectangle($im, $x*$zoom, $y*$zoom, ($x+1)*$zoom-1, ($y+1)*$zoom-1, $colors[$data[$x]]);
      }
      else
      {
        imagettftext($im,
          $fontSize[$zoom], 0,
          $x*$zoom + $fontSize[$zoom]/4,
          $y*$zoom + $fontSize[$zoom],
          $colors[$data[$x]],
          $fontFile,
          $data[$x]);
      }
    }
    $y++;
    echo ".";
  }

  if (imagepng($im, $outFilename) === FALSE)
  {
    echo "Error creating '".$outFilename."'\n";
    imagedestroy($im);
    return FALSE;
  }

  imagedestroy($im);
  return TRUE;
}

if (!stMakeDir($cfg["pathImageCache"])) {
  die("Failed to create cache directory '".$cfg["pathImageCache"]."'!\n");
}

echo "Generating basic map data...\n";
foreach ($continentList as $name => $data)
if ($data[4] && $data[7])
{
  $inFilename = $cfg["pathRawMaps"].$name.$rawSuffix;
  if (!file_exists($inFilename))
  {
    $inFilename = $cfg["pathRawMaps"].$name.$rawAltSuffix;
    if (!file_exists($inFilename))
      die("Required file '".$cfg["pathRawMaps"].$name."(".$rawSuffix."|".$rawAltSuffix.")' does not exist.\n");
  }
  $inMtime = filemtime($inFilename);
  
  for ($zoom = 1; $zoom <= 5; $zoom++)
  {
    $outFilename = $cfg["pathImageCache"].$name."_".($zoom + 4).".png";
    $outMtime = file_exists($outFilename) ? filemtime($outFilename) : -1;
    echo "- ".$name." (".$data[0]."): ";
    if ($inMtime > $outMtime)
    {
      $res = makeMap($inFilename, $outFilename, $zoom, $data);
      echo ($res ? "OK" : "FAIL")."\n";
    }
    else
      echo "SKIPPED\n";
  }
}


/*
 * Generate small versions
 */
echo "\nGenerating scaled small map data...\n";
$mapScales = array("50", "25", "12.5", "6.25", "3.125");
foreach ($continentList as $name => $data)
if ($data[4] && $data[7])
{
  $n = count($mapScales);
  $inFilename = $cfg["pathImageCache"].$name."_".$n.".png";
  if (!file_exists($inFilename))
  {
    die("Required file '".$inFilename."' does not exist.\n");
  }
  $inMtime = filemtime($inFilename);

  foreach ($mapScales as $scale)
  {
    $n--;
    $outFilename = $cfg["pathImageCache"].$name."_".$n.".png";
    $outMtime = file_exists($outFilename) ? filemtime($outFilename) : -1;

    echo "* ".$inFilename." -> ".$outFilename.": ";
    if ($inMtime > $outMtime)
    {
      $tmp = escapeshellcmd($cfg["binConvert"])." ".escapeshellarg($inFilename)." -scale ".escapeshellarg($scale."%")." -type Palette ".escapeshellarg($outFilename);
      passthru($tmp) == 0 or die("Error executing: ".$tmp."\n");
      echo "OK\n";
    }
    else
      echo "SKIPPED\n";
  }
}


/*
 * Build tiles
 */
function createTile($scale, $zoom, $x, $y, $mapData, $mapMtime)
{
  global $continentList, $worldMap, $cfg, $tileDim;

  $outFilename = $cfg["pathTileData"].$zoom."/".$y."/".$x.".png";
  if (file_exists($outFilename) && filemtime($outFilename) >= $mapMtime)
  {
    echo "!";
    return;
  }

  $drawn = false;
  $im = false;

  foreach ($continentList as $continent => $data)
  {
    if (!$data[4] || !$data[7])
      continue;
    
    $cx = $data[1] + $worldMap["ox"];
    $cy = $data[2] + $worldMap["oy"];
    $cw = $data[5];
    $ch = $data[6];
    
    $tx = -($cx*$scale - $x*$tileDim);
    $ty = -($cy*$scale - $y*$tileDim);

    if (($cx + $cw)*$scale > $x*$tileDim && 
        ($cy + $ch)*$scale > $y*$tileDim &&
        ($cx * $scale)     < ($x+1)*$tileDim && 
        ($cy * $scale)     < ($y+1)*$tileDim)
    {
      if (!$drawn)
      {
        if ($zoom < 9)
        {
          $im = @imagecreate($tileDim, $tileDim);
          $sea = imagecolorallocate($im, 51, 51, 170);
          imagefilledrectangle($im, 0, 0, $tileDim, $tileDim, $sea);
        }
        else
        {
          $inFilename = $cfg["pathTileData"].$zoom."/sea.png";
          $im = @imagecreatefrompng($inFilename);
          if ($im === false)
            die("\nCould not open '".$inFilename."'.\n");
        }
      }

      $dx = $tileDim;
      $dy = $tileDim;
      $xx = 0;
      $yy = 0;

      if ($tx < 0)
      {
        $xx -= $tx;
        $dx += $tx+1;
        $tx  = 0;
      }

      if ($ty < 0)
      {
        $yy -= $ty;
        $dy += $ty+1;
        $ty  = 0;
      }

      if ($dx > $cw*$scale-$tx)
        $dx = $cw*$scale - $tx;

      if ($dy > $ch*$scale-$ty)
        $dy = $ch*$scale - $ty;

      if ($im !== false)
      {
        imagecopy($im, $mapData[$continent], $xx, $yy, $tx, $ty, $dx, $dy);
        $drawn = TRUE;
      }
    }
  }

  if ($drawn)
  {
    stMakeDir($cfg["pathTileData"].$zoom."/".$y, 0755);
    imagepng($im, $outFilename);
    imagedestroy($im);
  }
/* 
   else {
    if (file_exists($outFilename))
      unlink($outFilename);

    symlink($cfg["pathTileData"]."sea.png", $outFilename);
    echo "+";
  }
*/
}


echo "\nBuilding tiles data for all zoom levels ...\n";

$mapData = array();

for ($zoom = $minZoom; $zoom <= $maxZoom; $zoom++)
{
  $zoom2 = $zoom - 1;
  $scale = pow(2, $zoom2 - 5);

  stMakeDir($cfg["pathTileData"].$zoom);

  echo "\nZoom level $zoom: ";

  $mx = ceil(($worldMap["w"] * $scale) / $tileDim);
  $my = ceil(($worldMap["h"] * $scale) / $tileDim);
  $mw = ceil( $worldMap["w"] * $scale);
  $mh = ceil( $worldMap["h"] * $scale);

  $mapMtime = -1;
  foreach ($continentList as $continent => $data)
  if ($data[4] && $data[7])
  {
    $mapFile = $cfg["pathImageCache"].$continent."_".$zoom2.".png";
    $mapData[$continent] = @imagecreatefrompng($mapFile);
    $tmp = filemtime($mapFile);
    if ($tmp > $mapMtime)
      $mapMtime = $tmp;
  }

  for ($y = 0; $y < $mx; $y++)
  {
    for ($x = 0; $x < $my; $x++)
      createTile($scale, $zoom, $x, $y, $mapData, $mapMtime);
    echo ".";
  }
  echo "\n";

  // Free image data for each continent
  foreach($continentList as $continent => $data)
  {
    if ($data[4] && $data[7])
      imagedestroy($mapData[$continent]);
  }
}

?>