<?php #-*-Mode: php; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
/*
JJFMapper, a cartography program for PHP 4.
Copyright (C) 2004 John J Foerch
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
# JJFMapper for PHP
# Written for compatibility with php 4.2.3.
include_once(JJFM_LIBDIR.'/helpers.php');
include_once(JJFM_LIBDIR.'/instruction_document_parser.class.php');
include_once(JJFM_LIBDIR.'/built_in_icons.php');
include_once(JJFM_LIBDIR.'/projection.php');
//The JJFMapper class is the class that
//you will instantiate to create your map.
class JJFMapper {
//check the $ok field to find out if the object constructed successfully.
var $ok=true;
var $error=false;
var $reportentries;
var $idoc;
var $geolayers=array();
var $projection;
var $supported_geoformats = array ();
//symbol table
//the symbol table contains values that can
//be used in jjfmapper config files.
var $symbols=array();
//The constructor takes one argument.
//The argument can be XML data or a file name.
//The constructor does not draw the map.
//It does initialize the projection and
//geographic layer objects. After construction,
//call jjfmapper->run() to draw the map.
function JJFMapper($arg) {
//Test if the content of $arg is xml data
//by testing for the existence of the <
//character.
if (strpos($arg,'<') === false)
{
if (! file_exists ($arg))
{
//debug report not available at this point.
$this->ok = false;
$this->error = 'instruction document file not found.';
return;
}
$f = fopen($arg,'r');
$arg = fread($f,filesize($arg));
fclose($f);
}
$this->init_symbols();
$this->idoc = new instruction_document_parser ($arg,$this->symbols);
if (! $this->idoc->ok)
{
$this->ok = false;
if ($this->idoc->error !== false)
$this->error = 'instruction document: '.$this->idoc->error;
return;
}
if (defined ('DEBUG'))
{
include_once (JJFM_LIBDIR.'/debugreport.php');
$this->reportentries = new debugreport();
}
//load cached projection data if available
//or else construct the projection as normal
if (defined ('HAVE_CACHED_PROJECTION'))
{//we are caching and we have projection data
//load the data
$f = fopen ($this->idoc->cached_projection, 'rb');
$s = fread ($f, filesize ($this->idoc->cached_projection));
fclose ($f);
$this->construct_projection ($s);
return;
}
$this->get_supported_geoformats ();
//Set up geolayers.
//This entails loading the needed php files,
//processing the attributes of each layer tag,
//and constructing the geoformat objects.
foreach ($this->idoc->layers as $layer_k => $layer_v) {
$T = & $this->idoc->layers[$layer_k];///$T points to current layer
$layer_format = strtolower($T['FORMAT']);
if (! isset ($this->supported_geoformats[$layer_format]))
{
if (defined ('DEBUG'))
{
$this->reportentries->add (
array ('warning' => 'unsupported layer type: '.
$layer_format));
}
continue;
}
//dynamic loading of geoformats
$format_include_file = JJFM_LIBDIR."/geoformat_$layer_format.php";
if (file_exists ($format_include_file))
{
include_once ($format_include_file);
} else {
if (defined ('DEBUG'))
{
$this->reportentries->add (
array ('error' => 'include file not found: '.
$format_include_file));
}
continue;
}
$pass_options = $layer_v;
//any data between the <layer> and </layer> will be
//loaded into the 'inline' key of the options array
//
//check for cdata
//
//
if (isset($T['cdata']))
{
$inline = $T['cdata'];
}
$layer_class = 'geoformat_'. $layer_format;
if (! class_exists ($layer_class))
{
if (defined ('DEBUG'))
{
$this->reportentries->add (
array ('warning' => "class \"$layer_class\" not ".
"found for layer $layer_k ($layer_format)"));
}
continue;
}
$pass_options = call_user_func_array (array($layer_class,
'parse_instructions'),
array($pass_options,
$this->symbols));
if ($pass_options === false)
{
if (defined ('DEBUG'))
{
$this->reportentries->add (
array ('warning' =>
"layer configuration failed for layer $layer_k".
" ($layer_format)"));
}
continue;
}
if (isset ($inline)) $pass_options['inline'] = $inline;
$Y = new $layer_class($pass_options);
if (! $Y->ok)
{
if (defined ('DEBUG'))
{
$warning_msg = "layer construction failed for layer ".
"$layer_k ($layer_format)";
if ($Y->error !== false) $warning_msg .= ' error: '.
$Y->error;
$this->reportentries->add (
array ('warning' => $warning_msg));
}
} else {
$this->geolayers[] = $Y;
}
}//done setting up geolayers
//
// fitting
//
if ($this->idoc->fit !== false)
{
$idx = $this->idoc->layer_names[$this->idoc->fit];
if ($this->idoc->fit_spec == '')
{
list($latmin,$latmax,$lonmin,$lonmax) =
$this->geolayers[$idx]->bounds();
} else {
list($latmin,$latmax,$lonmin,$lonmax) =
$this->geolayers[$idx]->bounds($this->idoc->fit_spec);
}
$this->idoc->projection['xmin'] = $lonmin;
$this->idoc->projection['xmax'] = $lonmax;
$this->idoc->projection['ymin'] = $latmin;
$this->idoc->projection['ymax'] = $latmax;
}
$this->construct_projection ($this->idoc->projection);
//if we are caching, cache the projection
if (defined ('JJFM_CACHING')) $this->write_projection_cache();
//END setup of projection
}
function construct_projection ($arg) {
if (is_string ($arg)) $arg = jjfProjection_parse_info ($arg);
if ($arg === false)
{
$this->ok = false;
$this->error = 'failed to parse projection parameters';
return;
}
$this->projection = new jjfProjection ($arg);
if (! $this->projection->ok)
{
if (isset ($this->projection->reportentries) && defined ('DEBUG'))
{
$this->reportentries->merge ($this->projection->reportentries);
}
$this->debugreport();
$this->ok = false;
$this->error = 'projection: '.$this->projection->error;
return;
}
}
function get_supported_geoformats () {
$m = get_defined_constants ();
$l = strlen ('JJFM_GEOFORMAT_');
foreach ($m as $m_k => $m_v) {
if (substr ($m_k, 0, $l) == 'JJFM_GEOFORMAT_')
{
$n = strtolower (substr ($m_k, $l));
$this->supported_geoformats[$n] = true;
}
}
}
function init_symbols () {
if (defined ('JJFM_BASEDIR')) $this->symbols['BASEDIR'] = JJFM_BASEDIR;
if (defined ('JJFM_GEODIR')) $this->symbols['GEODIR'] = JJFM_GEODIR;
//TODO: symbol for construction timestamp
}
//JJFMapper->run()
//This is the function that draws the map.
//Properly speaking, it is the function that
//calls the functions that draw the map.
function run() {
if ($this->ok === false) return;
//First, determine whether we are operating
//in normal mode or cache mode.
if (! defined ('JJFM_HAVE_CACHED_MAP'))
{
//Normal mode. The map will be drawn from scratch.
//the GD surface is inside of the projection.
//Call CreateImage() to initialize it.
$this->projection->CreateImage();
//Iterate through the geolayers and for each,
//call its draw() function.
foreach ($this->geolayers as $layer_k => $layer_v) {
$layer_v->draw ($this->projection);
if (defined ('DEBUG') &&
isset ($this->geolayers[$layer_k]->reportentries))
{
$this->reportentries->merge (
$this->geolayers[$layer_k]->reportentries);
}
}
} else {
//If we are operating in cache mode,
//create an image from the cached PNG file.
$f = fopen ($this->idoc->cached_map,'rb');
$content = fread ($f, filesize ($this->idoc->cached_map));
fclose ($f);
$this->projection->mapimage = imagecreatefromstring ($content);
}
if (! defined ('JJFM_RETURN')) {
//Output the map.
if (defined ('JJFM_FILE_OUTPUT'))
{
$this->make_image($this->idoc->output_flags,
$this->idoc->output_file);
} else {
$this->make_image($this->idoc->output_flags);
}
//Output the debug report, if requested.
if (defined ('DEBUG')) $this->debugreport();
//Clean up the GD surface.
$this->projection->destroy();
} else {
if (defined ('JJFM_CACHING') &&
! defined ('JJFM_HAVE_CACHED_MAP'))
{
$this->delete_old_cache ();
//cached maps are always stored in png format
//because png is lossless.
imagepng ($this->projection->mapimage,
$this->cached_map);
}
return $this->projection->mapimage;
}
}
function debugreport () {
if (defined ('DEBUG'))
{
if ($this->idoc->debug_file !== false)
{
$f = fopen ($this->idoc->debug_file,
$this->idoc->debug_output_mode);
fwrite ($f, $this->report());
fclose ($f);
} else {
//echo debug info to STDOUT
echo ($this->report());
}
}
}
/////////////////////////
// OUTPUT FUNCTIONS
//
function report() {
if (defined ('DEBUG'))
{
$report = LF;
$report .= TAB.'JJFMapper Report'.LF;
$report .= LF;
$report .= $this->reportentries->report();
return $report;
}
}
function make_image ($format) {
/* This function takes as an argument a flags integer, which is JJFM_
* output constants OR'd together. Supported flags are JJFM_PNG,
* JJFM_JPEG, and JJFM_RAW_OUTPUT.
*
* The optional second argument is a filename of the image file to
* write.
*/
if ($format & JJFM_PNG) $abbr = 'png';
else if ($format & JJFM_JPEG) $abbr = 'jpeg';
$outfunc = 'image'.$abbr;
$ctype = 'Content-type: image/'.$abbr;
if ($format ^ JJFM_RAW_OUTPUT && (! headers_sent ())) header ($ctype);
if (! defined ('JJFM_HAVE_CACHED_MAP') && defined ('JJFM_CACHING'))
{
$this->delete_old_cache ();
//cached maps are always stored in png format
//because png is lossless.
imagepng ($this->projection->mapimage,
$this->idoc->cached_map);
}
if (! defined ('JJFM_NO_IMAGE_OUTPUT'))
{
if (func_num_args() >= 2)
$outfunc ($this->projection->mapimage,
func_get_arg (1));//write to file
else $outfunc ($this->projection->mapimage);//write to stream
}
}
function write_projection_cache() {
$this->delete_old_cache ('projection');
$f = fopen ($this->idoc->cached_projection, 'wb');
fwrite ($f, $this->projection->original_params());
fclose ($f);
}
function delete_old_cache() {
if (func_num_args() > 0) $type = func_get_arg(0);
else $type = 'png';
$f = opendir ($this->idoc->cache_dir);
$L = strlen ($this->idoc->namecrc);
if ($L > 0) {
while ($e = readdir ($f)) {
if (substr ($e,0,$L) == $this->idoc->namecrc &&
substr ($e,$L - strlen($type)) == $type)
{
unlink ($this->idoc->cache_dir . $e);
}
}
}
closedir ($f);
}
}//jjfMapper
?>