<?php

# If we're running under an older version of PHP which doesn't ship
# with a json_encode, provide a workaround.
if (!function_exists('json_encode'))
{
    function json_encode($a=false)
    {
        if (is_null($a)) return 'null';
        if ($a === false) return 'false';
        if ($a === true) return 'true';
        if (is_scalar($a))
        {
            if (is_float($a))
            {
                // Always use "." for floats.
                return floatval(str_replace(",", ".", strval($a)));
            }

            if (is_string($a))
            {
                static $jsonReplaces = array(
                    array("\\",    "/","\n", "\t", "\r", "\b", "\f", '"'),
                    array('\\\\','\\/','\\n','\\t','\\r','\\b','\\f','\"'));
                return '"' . str_replace($jsonReplaces[0],
                                         $jsonReplaces[1], $a) . '"';
            }
            else
                return $a;
        }
        $isList = true;
        for ($i = 0, reset($a); $i < count($a); $i++, next($a))
        {
            if (key($a) !== $i)
            {
                $isList = false;
                break;
            }
        }
        $result = array();
        if ($isList)
        {
            foreach ($a as $v) $result[] = json_encode($v);
            return '[' . join(',', $result) . ']';
        }
        else
        {
            foreach ($a as $k => $v) $result[] =
                json_encode($k).':'.json_encode($v);
            return '{' . join(',', $result) . '}';
        }
    }
}

/** Exclude points outside a square(-ish) bounding box determined by
 * the center and edges of the map.  Also computes unit-square
 * coordinates for the points that live within the bounding box that
 * can be used later for point selections.  Returns the selected
 * points and the unit-square coordinates as arrays.
 */
function BoundingBox($Points,
                     $CenterLat, $CenterLon,
                     $North, $East, $South, $West)
{
    $MyPlugin = $GLOBALS["G_PluginManager"]->GetPlugin("GoogleMaps");

    $GridHalfHeight = max(
        $MyPlugin->ComputeDistance($CenterLat, $CenterLon, $North, $CenterLon),
        $MyPlugin->ComputeDistance($CenterLat, $CenterLon, $South, $CenterLon));

    $GridHalfWidth = max(
        $MyPlugin->ComputeDistance($CenterLat, $CenterLon, $CenterLat, $East),
        $MyPlugin->ComputeDistance($CenterLat, $CenterLon, $CenterLat, $West));

    $SelectedPoints = array();
    $SelectedPointsUnitSquare = array();
    foreach($Points as $Point)
    {
        $PointLat = $Point[0];
        $PointLon = $Point[1];

        $PointR = $MyPlugin->ComputeDistance(
            $CenterLat, $CenterLon, $PointLat, $PointLon);
        $PointTheta = $MyPlugin->ComputeBearing(
            $CenterLat, $CenterLon, $PointLat, $PointLon);

        # Rotate 90 degrees to put North at 0 degrees.
        $PointX = $PointR * sin(deg2rad($PointTheta));
        $PointY = $PointR * cos(deg2rad($PointTheta));

        if ( abs($PointX) < $GridHalfWidth &&
             abs($PointY) < $GridHalfHeight )
        {
            $SelectedPoints []= $Point;
            $SelectedPointsUnitSquare []= array(
                ($PointX + $GridHalfWidth )/(2*$GridHalfWidth ),
                ($PointY + $GridHalfHeight)/(2*$GridHalfHeight));
        }
    }

    return array(
        "Normal" => $SelectedPoints,
        "UnitSquare" => $SelectedPointsUnitSquare );
}

/** Select a well-spaced subset of points. Expects an array of points
 * and their unit-square locations. Slices the unit-square to the
 * specified number of tiles, selecting not more than one point per
 * tile from the whole set.  Returns the selected points.
 */

function PerformPointSelection($GridSize, $NewPoints, $UnitSquareCoords)
{
    $SelectedPoints = array();

    // Initialize the grid
    for ($i=0; $i<$GridSize; $i++)
    {
        $MapGrid[$i] = array_fill(0, $GridSize, 0);
    }

    # iterate through the points given to us by the data
    # provider and scatter them into the grid.
    for ($i=0; $i< count($NewPoints); $i++){
        $Point = $NewPoints[$i];

        $PointXp = $UnitSquareCoords[$i][0];
        $PointYp = $UnitSquareCoords[$i][1];

        $mi = floor( $GridSize * $PointXp );
        $mj = floor( $GridSize * $PointYp );

        if ($MapGrid[$mi][$mj] == 0 ){
            $SelectedPoints []= $Point;
        }
        $MapGrid[$mi][$mj]++;
    }

    return $SelectedPoints;
}

# Page loading begins here:

global $AF;

$AF->SuppressHTMLOutput();

# Check to see that we've got all the required data
if (isset($_GET["N"]) && isset($_GET["S"]) &&
    isset($_GET["E"]) && isset($_GET["W"]) &&
    isset($_GET["Clat"]) && isset($_GET["Clon"]) &&
    isset($_GET["DP"]))
{
    # Pull out the callback, user spec'd paramters, and try to load
    # the callback.
    $Row = $DB->Query("SELECT Payload,Params FROM GoogleMaps_Callbacks "
                      ."WHERE Id='".addslashes($_GET["DP"])."'");
    $Row = $DB->FetchRow();

    $PPCallback = unserialize($Row["Payload"]);
    $Params     = unserialize($Row["Params"]);

    if ($AF->LoadFunction($PPCallback))
    {
        # If we can get the callback, suck out the get parameters:
        # Extract get parameters
        foreach ( array("N","E","S","W","Clat","Clon") as $Param )
        {
            eval('$'.$Param.' = round($_GET["'.$Param.'"],6);');
        }

        # Grab a handle to our plugin:
        $MyPlugin = $GLOBALS["G_PluginManager"]->GetPlugin("GoogleMaps");

        # Retrieve point selection parameters:
        $GridSize = $MyPlugin->ConfigSetting("DefaultGridSize");
        $DesiredPointCount = $MyPlugin->ConfigSetting("DesiredPointCount");
        $MaxIterationCount = $MyPlugin->ConfigSetting("MaxIterationCount");

        # Call the supplied DP, expecting an Array.
        $AllPoints = call_user_func_array( $PPCallback, array($Params));

        # Chop out those not visible on the map.
        $VisiblePoints = BoundingBox($AllPoints,
                                     $Clat, $Clon,
                                     $N, $E, $S, $W);

        if (count($VisiblePoints["Normal"]) > $DesiredPointCount)
        {
            # If there's too many points in the bounding box, select a subset.
            $IterationCount = 0;
            do {
                $SelectedPoints = PerformPointSelection(
                    $GridSize * pow(2,$IterationCount),
                    $VisiblePoints["Normal"],
                    $VisiblePoints["UnitSquare"]);
                $IterationCount++;
            } while (count($SelectedPoints) < 0.95*$DesiredPointCount &&
                     $IterationCount < $MaxIterationCount);
        }
        else
        {
            # Otherwise, just use them all.
            $SelectedPoints = $VisiblePoints["Normal"];
        }

        print( json_encode( $SelectedPoints ) );
    }
}

?>
