<?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
*/
define ('JJFM_POLYGON_OUTLINE',1);
define ('JJFM_POLYGON_FILL',2);
define ('JJFM_SOLID_LINE',4);
define ('JJFM_DASHED_LINE',8);
class jjfProjection {
/* jjfProjection is a class for converting lat/lon coordinates
to x/y coordinates. Instantiate it with an associative array
whose keys include: xmin, xmax, ymin, ymax, imwidth, imheight,
and (optionally) padding. The projection itself is nonstandard.
The international dateline is the edge of the world, so this is
not the projection to use if you need to map say, Alaska. The
projection is distorted by the value of the cosine of the
latitude in the middle of the map. Maps that include only a few
degrees of latitude and longitude look best.
*/
//check variable $ok to find out if the object constructed successfully.
var $ok=true;
var $error=false;
var $mapimage;
var $palette = array();
var $params;
var $xmin,$ymin,$xmax,$ymax,$imwidth,$imheight,
$xdif,$ydif,$xmid,$ymid,$xrat,$xmul,$ymul,$xadd,$yadd;
var $reportentries;
function jjfProjection (&$options) {
$this->params = $options;//store parameters for caching
if (defined ('DEBUG')) $this->reportentries = new debugreport();
if (! (isset ($options['xmin']) &&
isset ($options['ymin']) &&
isset ($options['xmax']) &&
isset ($options['ymax']) &&
isset ($options['imwidth']) &&
isset ($options['imheight'])))
{
if (defined ('DEBUG'))
{
$this->reportentries->add(array('error' => 'jjfProjection::'.
'new called without enough '.
'parameters.'));
}
$this->ok = false;
$this->error = 'map dimensions were not passed.';
return;
}
if (($options['xmax'] <= $options['xmin'] ||
$options['ymax'] <= $options['ymin']) &&
isset($options['padding']) === false)
{
if (defined ('DEBUG'))
{
$this->reportentries->add(array('error' => 'jjfProjection'.
'::new called with invalid '.
'map dimensions.'));
}
$this->ok = false;
$this->error = 'invalid map dimensions';
return;
}
if ($options['imwidth'] == 0 || $options['imheight'] == 0)
{
if (defined ('DEBUG'))
{
$this->reportentries->add(array('error' => 'jjfProjection::'.
'new called with invalid '.
'image dimensions.'));
}
$this->ok = false;
$this->error = 'invalid image dimensions';
return;
}
$this->ProjectionSetup($options);
}
function CreateImage() {
if (defined ('JJFM_USE_GD2'))
{
$this->mapimage = imagecreatetruecolor($this->imwidth,
$this->imheight);
} else {
$this->mapimage = imagecreate($this->imwidth,$this->imheight);
}
$this->palette['white'] =
imagecolorallocate($this->mapimage,255,255,255);
$this->palette['black'] =
imagecolorallocate($this->mapimage,0,0,0);
$this->palette['bgcolor'] =
imagecolorallocate($this->mapimage,255,255,255);
}
function destroy() {
imagedestroy($this->mapimage);
}
function ProjectionSetup (&$options) {
$this->xmin = $options['xmin'];
$this->ymin = $options['ymin'];
$this->xmax = $options['xmax'];
$this->ymax = $options['ymax'];
$this->imwidth = $options['imwidth'];
$this->imheight = $options['imheight'];
$this->xdif = $this->xmax - $options['xmin'];
$this->ydif = $this->ymax - $options['ymin'];
$this->xmid = $this->xmin + ($this->xdif / 2);
$this->ymid = $this->ymin + ($this->ydif / 2);
$this->xrat = cos($this->ymid * ((atan2(1,1)*4)/180));
if (isset($options['padding']))
{
$padtype = $options['padding'][strlen($options['padding']) - 1];
if ($padtype == '*' || $padtype == '°')
{
$this->AddBorderByDegrees(
substr($options['padding'],0,
strlen($options['padding'])-1));
} elseif ($padtype =='%') {
$this->AddBorderByPercent(
substr($options['padding'],0,
strlen($options['padding'])-1));
} else {
$this->AddBorderByPercent($options['padding']);
}
} else {
//calculate final geographic mins and maxes
//TODO: These four lines cause division by zero
//TODO: when no adjustment is needed on the
//TODO: construction arguments.
$this->xmin = (0 - $this->xadd) / $this->xmul;
$this->xmax = (($this->imwidth-1) - $this->xadd) / $this->xmul;
$this->ymin = (0 - $this->yadd) / $this->ymul;
$this->ymax = (($this->imheight - 1) - $this->yadd) / $this->ymul;
}
}
function AddBorderByPercent($percentage) {
$xadd = $this->xdif * ($percentage / 100);
$yadd = $this->ydif * ($percentage / 100);
$this->xmin -= $xadd;
$this->xmax += $xadd;
$this->ymin -= $yadd;
$this->ymax += $yadd;
$this->xdif = $this->xmax - $this->xmin;
$this->ydif = $this->ymax - $this->ymin;
$this->xmid = $this->xmin + ($this->xdif / 2);
$this->ymid = $this->ymin + ($this->ydif / 2);
$this->xrat = cos($this->ymid * ((atan2(1,1)*4)/180));
$scalebywidthY = $this->imwidth / $this->xdif / $this->xrat;
$scalebywidthX = $this->imwidth / $this->xdif;
$scalebyheightY = $this->imheight / $this->ydif;
$scalebyheightX = $this->imheight / $this->ydif * $this->xrat;
if(($scalebywidthX > $scalebyheightY) &&
($this->ydif * $scalebywidthY < $this->imheight))
{
$this->xmul = $scalebywidthX;
$this->ymul = $scalebywidthY;
} elseif (($scalebywidthX > $scalebyheightY) &&
($this->ydif * $scalebywidthY > $this->imheight)) {
$this->xmul = $scalebyheightX;
$this->ymul = $scalebyheightY;
} elseif (($scalebywidthX < $scalebyheightY) &&
($this->xdif * $scalebyheightX < $this->imwidth)) {
$this->xmul = $scalebyheightX;
$this->ymul = $scalebyheightY;
} else {
$this->xmul = $scalebywidthX;
$this->ymul = $scalebywidthY;
}
$this->xadd = (0 - $this->xmin) * $this->xmul;
$this->yadd = (0 - $this->ymin) * $this->ymul;
# CENTER THE IMAGE
$this->xadd += ($this->imwidth - ($this->xdif * $this->xmul))/2;
$this->yadd += ($this->imheight - ($this->ydif * $this->ymul))/2;
# calculate final geographic mins and maxes
$this->xmin = (0 - $this->xadd) / $this->xmul;
$this->xmax = (($this->imwidth-1) - $this->xadd) / $this->xmul;
$this->ymin = (0 - $this->yadd) / $this->ymul;
$this->ymax = (($this->imheight - 1) - $this->yadd) / $this->ymul;
}
function AddBorderByDegrees($degrees) {
$this->xadd = $degrees;
$this->yadd = $degrees;
$this->xmin -= $degrees;
$this->xmax += $degrees;
$this->ymin -= $degrees;
$this->ymax += $degrees;
$this->xdif = $this->xmax - $this->xmin;
$this->ydif = $this->ymax - $this->ymin;
$this->xmid = $this->xmin + ($this->xdif / 2);
$this->ymid = $this->ymin + ($this->ydif / 2);
$this->xrat = cos($this->ymid * ((atan2(1,1)*4)/180));
$scalebywidthY = $this->imwidth / $this->xdif / $this->xrat;
$scalebywidthX = $this->imwidth / $this->xdif;
$scalebyheightY = $this->imheight / $this->ydif;
$scalebyheightX = $this->imheight / $this->ydif * $this->xrat;
if(($scalebywidthX > $scalebyheightY) &&
($this->ydif * $scalebywidthY < $this->imheight))
{
$this->xmul = $scalebywidthX;
$this->ymul = $scalebywidthY;
} elseif (($scalebywidthX > $scalebyheightY) &&
($this->ydif * $scalebywidthY > $this->imheight)) {
$this->xmul = $scalebyheightX;
$this->ymul = $scalebyheightY;
} elseif (($scalebywidthX < $scalebyheightY) &&
($this->xdif * $scalebyheightX < $this->imwidth)) {
$this->xmul = $scalebyheightX;
$this->ymul = $scalebyheightY;
} else {
$this->xmul = $scalebywidthX;
$this->ymul = $scalebywidthY;
}
$this->xadd = (0 - $this->xmin) * $this->xmul;
$this->yadd = (0 - $this->ymin) * $this->ymul;
# CENTER THE IMAGE
$this->xadd += ($this->imwidth - ($this->xdif * $this->xmul))/2;
$this->yadd += ($this->imheight - ($this->ydif * $this->ymul))/2;
# calculate final geographic mins and maxes
$this->xmin = (0 - $this->xadd) / $this->xmul;
$this->xmax = (($this->imwidth-1) - $this->xadd) / $this->xmul;
$this->ymin = (0 - $this->yadd) / $this->ymul;
$this->ymax = (($this->imheight - 1) - $this->yadd) / $this->ymul;
}
function Info() {
return "xmin: ". $this->xmin ."\n" .
"xmax: ". $this->xmax ."\n" .
"ymin: ". $this->ymin ."\n" .
"ymax: ". $this->ymax ."\n" .
"imwidth: ". $this->imwidth ."\n" .
"imheight: ". $this->imheight ."\n";
}
function original_params () {
$s = "projectiontype=jjfProjection\n";
foreach ($this->params as $p_k => $p_v) {
$s .= $p_k .'='. $p_v ."\n";
}
return $s;
}
function TranslateXY($x,$y) {
$x1=floor($x * $this->xmul + $this->xadd);
$y1=floor($this->imheight - ($y * $this->ymul + $this->yadd));
return array($x1,$y1);
}
function draw (&$struct) {
//print_r_html ($struct);
if (! is_array ($struct)) { return; }
if (count($struct) == 0) { return; }
foreach ($struct as $geometry_k => $geometry) {
switch ($geometry_k) {
case 'polygon':
$this->draw_polygons ($geometry);
break;
case 'point':
$this->draw_points ($geometry);
break;
case 'track':
$this->draw_track ($geometry);
break;
}
}
}//end function draw()
/* A polygon structure should have a key 'data' which is an array of
polygons, each stored in the format requred by imagepolygon(). Other
allowed keys:
weight: gd 2.0+, line weight, or thickness
style: may be 'LINE', 'FILL', or 'BOTH'
color: an allocated color number. If style is 'BOTH', then the color is
the fill color and the border color is black. This will be changed in
a future version to allow for more customization of the map.
*/
function draw_polygons (&$geometry) {
if (! isset ($geometry['data'])) return;
if (count ($geometry['data']) == 0) return;
$weight = isset ($geometry['weight']) ? $geometry['weight'] : 1;
$style = isset ($geometry['style']) ?
$geometry['style'] : JJFM_POLYGON_OUTLINE;
$color = isset ($geometry['color']) ? $geometry['color'] : 0;
$fillcolor = isset ($geometry['fillcolor']) ?
$geometry['fillcolor'] : $color;
$color = ColorStringToResource ($color, $this->mapimage);
$fillcolor = ColorStringToResource ($fillcolor, $this->mapimage);
lineweight ($weight, $this->mapimage);
foreach ($geometry['data'] as $polygon)
{
$verts = count ($polygon) / 2;
for ($a = 0; $a < $verts; ++$a)
{
list ($polygon[$a*2], $polygon[$a*2+1]) =
$this->TranslateXY ($polygon[$a*2], $polygon[$a*2+1]);
}
if($style & JJFM_POLYGON_FILL)
{
imagefilledpolygon($this->mapimage,$polygon,$verts,$fillcolor);
}
if($style & JJFM_POLYGON_OUTLINE)
{
imagepolygon($this->mapimage,$polygon,$verts,$color);
}
}
}
/*
data: an array like ($x, $y, $x1, $y1, $x2, $y2)
dot: will be tested as a file name, and if the file does not exist, it
will be passed to GetBuiltInIcon()
dotcolor: if set, will use ColorIcon() to colorize the dot. the color
must be a six digit hex triplet string. This will change to a
simple integer in the future.
*/
function draw_points (&$geometry) {
if (!isset ($geometry['data'])) return;
if (count ($geometry['data']) == 0) return;
$dot_data = false;
if (isset ($geometry['dot']) &&
file_exists($geometry['dot']))
{
$f = fopen ($geometry['dot'],'rb');
$dot_data = fread ($f,filesize($geometry['dot']));
fclose ($f);
} else {
$dotname = isset ($geometry['dot']) ? $geometry['dot'] : null;
$dot_data = GetBuiltInIcon($dotname);
}
$dot = imagecreatefromstring ($dot_data);
imagecolortransparent($dot,imagecolorat($dot,0,0));
$sx = imagesx ($dot);
$sy = imagesy ($dot);
$gx = $sx / 2;
$gy = $sy / 2;
if (isset ($geometry['dotcolor']) && $geometry['dotcolor'] != '') {
ColorIcon ($dot, $geometry['dotcolor']);
}
for ($a = 0; $a < (count ($geometry['data']) / 2); ++$a) {
list ($x,$y) = $this->TranslateXY ($geometry['data'][$a * 2],
$geometry['data'][$a * 2 + 1]);
imagecopymerge($this->mapimage,$dot,$x-$gx,$y-$gy,0,0,$sx,$sy,99);
}
imagedestroy ($dot);
}
/*
data: an array like ($x, $y, $x1, $y1, $x2, $y2)
trackcolor: if set, will be used for the foreground color
of a dashed line track. php4.x does not fully support
imagedashedline, so a styled line is used instead.
This makes a true dashed line impossible.
trackweight: line thickness of the track line. gd 2.0+
only. default 1.
style: may be JJFM_SOLID_LINE or JJFM_DASHED_LINE
*/
function draw_track (&$geometry) {
global $jjfm_dashedline;
if (!isset ($geometry['data'])) return;
if (count ($geometry['data']) == 0) return;
//draw track
if (isset ($geometry['weight']))
{
lineweight ($geometry['weight'], $this->mapimage);
}
if (! isset ($geometry['style']))
{
$geometry['style'] = JJFM_DASHED_LINE;
}
$color = isset ($geometry['color']) ? $geometry['color'] : 0;
$color = ColorStringToResource ($color, $this->mapimage);
$x1 = $y1 = false;
for ($a = 0; $a < (count ($geometry['data']) / 2); ++$a) {
list ($x,$y) = $this->TranslateXY ($geometry['data'][$a * 2],
$geometry['data'][$a * 2 + 1]);
if (! $x1 === false)
{
if ($geometry['style'] == JJFM_SOLID_LINE)
{
imageline ($this->mapimage, $x1, $y1, $x, $y, $color);
} else {
$jjfm_dashedline ($this->mapimage, $x1, $y1, $x, $y,
$color,$this->palette['bgcolor']);
}
}
$x1 = $x;
$y1 = $y;
}
}
}//JJFProjection
function jjfProjection_parse_info ($s) {
//this function will be moved to the base class
//when projections are abstracted to support more
//than just jjfProjection
//We will also very likely change this to use xml.
$ar = explode ("\n", $s);
$ret = array ();
foreach ($ar as $v) {
if ($v == '') continue;
$j = explode ('=',$v);
$ret[$j[0]] = $j[1];
}
return $ret;
}
?>