3
* @package Helioviewer API
4
* @author Keith Hughitt <Vincent.K.Hughitt@nasa.gov>
7
* @package Helioviewer API
9
error_reporting(E_ALL | E_STRICT);
13
* @param array An array of parameters relevant to the API call
14
* @param string ["plain-text"|"json"] The format to return results in
16
public function __construct ($params, $format) {
17
require_once('DbConnection.php');
18
$this->params = $params;
19
$this->format = $format;
21
$_SERVER['HTTP_HOST'] == "localhost" ? require_once('../settings/Config.php') : require_once('../settings/Config.Server.php');
24
if (!$this->validate($params["action"]))
25
throw new Exception("Invalid parameters specified for <a href='http://www.helioviewer.org/api/index.php#" . $params['action'] . "'>" . $params['action'] . "</a>.");
27
#if (!call_user_func(array("API" ,"_" . $params["action"])))
28
if (!$this->{"_" . $params["action"]}() === 1)
29
throw new Exception("Unable to execute " . $params["action"] . ". Please make sure you are using valid input and contact the web-admin if problems persist.");
31
} catch (Exception $e) {
32
echo "<br><b>Error:</b> ", $e->getMessage(), "<br>";
39
* @return int Returns "1" if the action was completed successfully.
41
private function _getTile () {
42
require_once("Tile.php");
43
$tile = new Tile($this->params['imageId'], $this->params['zoom'], $this->params['x'], $this->params['y'], $this->params['ts']);
49
* @return int Returns "1" if the action was completed successfully.
51
private function _getClosestImage () {
52
require_once('ImgIndex.php');
53
$imgIndex = new ImgIndex(new DbConnection());
55
$queryForField = 'abbreviation';
56
foreach(array('observatory', 'instrument', 'detector', 'measurement') as $field) {
57
$src["$field.$queryForField"] = $this->params[$field];
60
$result = $imgIndex->getClosestImage($this->params['timestamp'], $src);
62
if ($this->format == "json") {
63
header('Content-type: application/json');
64
echo json_encode($result);
66
echo json_encode($result);
73
* getViewerImage (aka "getCompositeImage")
76
* http://helioviewer.org/api/index.php?action=getViewerImage&layers=SOHEITEIT195×tamps=1065312000&zoomLevel=10&tileSize=512&xRange=-1,0&yRange=-1,0
77
* http://helioviewer.org/api/index.php?action=getViewerImage&layers=SOHEITEIT195,SOHLAS0C20WL×tamps=1065312000,1065312360&zoomLevel=13&tileSize=512&xRange=-1,0&yRange=-1,0&edges=false
80
* Building a UTC timestamp in javascript
81
* var d = new Date(Date.UTC(2003, 9, 5));
82
* var unix_ts = d.getTime() * 1000;
85
* * If no params are passed, print out API usage description (and possibly a query builder form)...
86
* * Add support for fuzzy timestamp matching. Could default to exact matching unless user specifically requests fuzzy date-matching.
87
* * Separate out layer details into a Layer PHP class?
89
* @return int Returns "1" if the action was completed successfully.
92
private function _getViewerImage () {
93
require('lib/helioviewer/CompositeImage.php');
95
//Process query string
98
$timestamps = explode(",", $this->params['timestamps']);
99
if (strlen($this->params['timestamps']) == 0) {
100
throw new Exception("Error: Incorrect number of timestamps specified!");
103
// Region of interest
104
$x = explode(",", $this->params['xRange']);
105
$y = explode(",", $this->params['yRange']);
108
$xRange['start'] = $x[0];
109
$xRange['end'] = $x[1];
112
$yRange['start'] = $y[0];
113
$yRange['end'] = $y[1];
115
// Zoom-level & tilesize
116
$zoomLevel = $this->params['zoomLevel'];
117
$tileSize = $this->params['tileSize'];
122
foreach (explode(",", $this->params['layers']) as $layer) {
123
array_push($layers, new Layer($layer, $timestamps[$i], $timestamps[$i], $zoomLevel, $xRange, $yRange, $tileSize));
128
if ((sizeOf($layers) > 3) || (strlen($this->params['layers']) == 0)) {
129
throw new Exception("Error: Invalid layer choices! You must specify 1-3 command-separate layernames.");
132
// Optional parameters
134
$options["edgeEnhance"] = $this->params['edges'];
135
$options["sharpen"] = $this->params['sharpen'];
137
catch(Exception $e) {
138
echo 'Error: ' .$e->getMessage();
142
//Create and display composite image
143
$img = new CompositeImage($layers, $zoomLevel, $xRange, $yRange, $options);
150
* @return int Returns "1" if the action was completed successfully.
152
private function _getJP2Image () {
153
require('lib/helioviewer/ImgIndex.php');
154
$imgIndex = new ImgIndex(new DbConnection());
156
$queryForField = 'abbreviation';
157
foreach(array('observatory', 'instrument', 'detector', 'measurement') as $field) {
158
$src["$field.$queryForField"] = $this->params[$field];
161
$filepath = $imgIndex->getJP2Location($this->params['timestamp'], $src);
162
$filename = end(explode("/", $filepath));
164
if ($this->params['getURL'] == "true") {
165
$url = preg_replace(Config::WEB_ROOT_DIR_REGEX, Config::WEB_ROOT_URL, $filepath);
169
$fp = fopen($filepath, 'r');
171
header("Content-Length: " . filesize($filepath));
172
header("Content-Type: " . image_type_to_mime_type(IMAGETYPE_JP2));
173
header("Content-Disposition: attachment; filename=\"$filename\"");
175
$contents = fread($fp, filesize($filepath));
185
* @return int Returns "1" if the action was completed successfully.
187
private function _getJP2ImageSeries () {
188
require_once('ImgIndex.php');
189
//date_default_timezone_set('UTC');
191
$startTime = $this->params['startTime'];
192
$endTime = $this->params['endTime'];
193
$cadence = $this->params['cadence'];
194
$format = $this->params['format'];
197
foreach(array('observatory', 'instrument', 'detector', 'measurement') as $field) {
198
$src["$field.abbreviation"] = $this->params[$field];
201
// Connect to database
202
$imgIndex = new ImgIndex(new DbConnection());
204
// Determine number of frames to grab
205
$timeInSecs = $endTime - $startTime;
206
$numFrames = min(Config::MAX_MOVIE_FRAMES, ceil($timeInSecs / $cadence));
208
// Convert timestamp to a PHP DateTime (See http://us2.php.net/manual/en/function.date-create.php)
209
//$dt = new DateTime("@$startTime");
210
//echo $dt->format("U");
211
//date_add($dt, new DateInterval("T" . $cadence . "S"));
217
// Get nearest JP2 images to each time-step
218
for ($i = 0; $i < $numFrames; $i++) {
219
$jp2 = $imgIndex->getJP2Location($time, $src);
220
//$url = preg_replace($this->web_root_url_regex, $this->web_root_dir, $url);
221
array_push($images, $jp2);
225
// Append filepaths to kdu_merge command
226
$cmd = Config::KDU_MERGE_BIN . " -i ";
227
foreach($images as $jp2) {
231
// Drop trailing comma
232
$cmd = substr($cmd, 0, -1);
234
// Create a temporary directory to store image-series
236
$tmpdir = Config::TMP_ROOT_DIR . "/jp2-image-series/";
237
if (!file_exists($tmpdir)) {
239
chmod($tmpdir, 0777);
243
if (!file_exists($tmpdir)) {
245
chmod($tmpdir, 0777);
248
$filename = "jhv_image_series." . strtolower($format);
249
$tmpurl = Config::TMP_ROOT_URL . "/jp2-image-series/$now/" . $filename;
250
$output_file = "$tmpdir" . $filename;
252
$cmd .= " -o $output_file";
255
if ($format == "MJ2")
256
$cmd .= " -mj2_tracks P:0-@25";
258
// Execute kdu_merge command
259
exec('export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:' . Config::KDU_LIBS_DIR . "; " . escapeshellcmd($cmd), $output, $return);
268
* @return int Returns "1" if the action was completed successfully.
269
* NOTE: Add option to specify XML vs. JSON... FITS vs. Entire header?
271
private function _getJP2Header () {
272
$id = $this->params["imageId"];
274
$db = new DbConnection();
275
$sql = sprintf("SELECT uri FROM image WHERE id=%s;", mysqli_real_escape_string($db->link, $id));
277
$row = mysqli_fetch_array($db->query($sql), MYSQL_ASSOC);
280
// Query header information using Exiftool
281
$cmd = Config::EXIF_TOOL . " $url | grep Fits | grep -v Descr";
282
exec($cmd, $out, $ret);
285
foreach ($out as $index => $line) {
286
$data = explode(":", $line);
287
$param = substr(strtoupper(str_replace(" ", "", $data[0])), 4);
289
array_push($fits, $param . ": " . $value);
292
if ($this->format == "json") {
293
header('Content-type: application/json');
294
echo json_encode($fits);
297
echo json_encode($fits);
304
* @return int Returns "1" if the action was completed successfully.
306
private function _getEventCatalogs () {
307
if ($this->format == "text") {
308
header("Content-type: text/plain");
309
$url = Config::EVENT_SERVER_URL . $_SERVER["QUERY_STRING"] . "&debug=1";
312
header("Content-type: application/json");
313
$url = Config::EVENT_SERVER_URL . "action=getEventCatalogs";
315
echo file_get_contents($url);
320
* @return int Returns "1" if the action was completed successfully.
322
private function _getEvents () {
323
if ($this->format == "text") {
324
header("Content-type: text/plain");
325
$url = Config::EVENT_SERVER_URL . $_SERVER["QUERY_STRING"] . "&debug=1";
328
header("Content-type: application/json");
329
$url = Config::EVENT_SERVER_URL . "action=getEvents&date=" . $this->params["date"] . "&windowSize=" . $this->params["windowSize"] . "&catalogs=" . $this->params["catalogs"];
331
echo file_get_contents($url);
336
* @return int Returns "1" if the action was completed successfully.
338
private function _launchJHelioviewer () {
339
require_once('lib/helioviewer/JHV.php');
345
* @return int Returns "1" if the action was completed successfully.
347
private function _buildQuickMovie () {
348
require_once('ImageSeries.php');
350
// Required parameters
351
$startDate = $this->params['startDate'];
352
$zoomLevel = $this->params['zoomLevel'];
353
$numFrames = $this->params['numFrames'];
354
$frameRate = $this->params['frameRate'];
356
$xRange = $this->params['xRange'];
357
$yRange = $this->params['yRange'];
359
$hqFormat = $this->params['format'];
361
// Optional parameters
363
$options['enhanceEdges'] = $this->params['edges'] || false;
364
$options['sharpen'] = $this->params['sharpen'] || false;
366
//Check to make sure values are acceptable
368
$layers = explode(",", $this->params['layers']);
370
//Limit number of layers to three
371
if ((sizeOf($layers) > 3) || (strlen($this->params['layers']) == 0)) {
372
throw new Exception("Invalid layer choices! You must specify 1-3 command-separate layernames.");
375
//Limit number of frames to 100
376
if (($numFrames < 10) || ($numFrames > Config::MAX_MOVIE_FRAMES)) {
377
throw new Exception("Invalid number of frames. Number of frames should be at least 10 and no more than $maxFrames.");
380
$imgSeries = new ImageSeries($layers, $startDate, $zoomLevel, $numFrames, $frameRate, $hqFormat, $xRange, $yRange, $options);
381
$imgSeries->quickMovie();
383
} catch(Exception $e) {
384
echo 'Error: ' .$e->getMessage();
392
* @return int Returns "1" if the action was completed successfully.
394
private function _playMovie () {
395
$url = $this->params['url'];
396
$hqFormat = $this->params['format'];
400
$highQualityVersion = substr($url, 0, -3) . $hqFormat;
402
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
405
<title>Helioviewer.org QuickMovie</title>
407
<body style="background-color: #000, color: #FFF">
408
<!-- MC Media Player -->
409
<div style="text-align: center;">
410
<script type="text/javascript">
411
playerFile = "http://www.mcmediaplayer.com/public/mcmp_0.8.swf";
412
fpFileURL = "<?php print $url?>";
413
fpButtonSize = "48x48";
415
cpHidePanel = "mouseout";
417
defaultEndAction = "repeat";
418
playerSize = "<?php print $width . 'x' . $height?>";
420
<script type="text/javascript" src="http://www.mcmediaplayer.com/public/mcmp_0.8.js"></script>
421
<!-- / MC Media Player -->
424
<div style="text-align: center;">
425
<a href="<?php print $highQualityVersion;?>" style="text-decoration: none; color: white; font-weight: bold;">High-quality download.</a>
433
* @return int Returns "1" if the action was completed successfully.
435
private function _getLayerAvailability () {
436
$dbConnection = new DbConnection();
439
$obs = mysqli_real_escape_string($dbConnection->link, $this->params['observatory']);
440
$inst = mysqli_real_escape_string($dbConnection->link, $this->params['instrument']);
441
$det = mysqli_real_escape_string($dbConnection->link, $this->params['detector']);
442
$meas = mysqli_real_escape_string($dbConnection->link, $this->params['measurement']);
444
// Validate new combinations (Note: measurement changes are always valid)
445
if (isset($this->params['changed'])) {
446
$changed = mysqli_real_escape_string($dbConnection->link, $this->params['changed']);
447
$newValue = mysqli_real_escape_string($dbConnection->link, $this->params['value']);
449
// If query returns any matches then the new combination is valid
450
$query = sprintf("SELECT count(*) as count from observatory
451
INNER JOIN instrument ON observatory.id = instrument.observatoryId
452
INNER JOIN detector ON detector.instrumentId = instrument.id
453
INNER JOIN measurement ON measurement.detectorId = detector.id
455
observatory.abbreviation = '%s' AND instrument.abbreviation = '%s' and detector.abbreviation='%s' and measurement.abbreviation = '%s';",
456
$obs, $inst, $det, $meas);
458
$result = $dbConnection->query($query);
459
$row = mysqli_fetch_array($result, MYSQL_ASSOC);
460
$valid = $row['count'];
462
//If combination is invalid, adjust options to provide a valid combination
464
//CASE 1: Observatory changed
466
//CASE 2: Instrument changefirst grab a list of valid detectors for the chosen instrumentd
467
if ($changed == "instrument") {
468
//Find a valid detector for the chosen instrument
469
$query = sprintf("SELECT detector.abbreviation from detector INNER JOIN instrument ON instrument.id = detector.instrumentId
470
WHERE instrument.abbreviation = '%s' LIMIT 1;", $newValue);
471
$result = $dbConnection->query($query);
472
$row = mysqli_fetch_array($result, MYSQL_ASSOC);
473
$det = $row['abbreviation'];
475
//Measurements will be automatically updated...
478
//CASE 3: Detector changed
480
//CASE 4: Measurement change
487
* @return Array Allowed values for given field
488
* @param $db Object MySQL Database connection
489
* @param $f1 String Field Objectof interest
490
* @param $f2 String Limiting field
491
* @param $limit String Limiting field value
493
* Queries one field based on a limit in another. Performs queries of the sort
494
* "Give me all instruments where observatory equals SOHO."
496
function queryField($db, $format, $f1, $f2, $f1_value) {
498
$query = "SELECT $f2.name, $f2.abbreviation from $f2 INNER JOIN $f1 ON $f1.id = $f2.$f1" . "Id" . " WHERE $f1.abbreviation = '$f1_value';";
500
if ($format === "plaintext")
501
echo "<strong>query:</strong><br>$query<br><br>";
503
$result = $db->query($query);
504
while ($row = mysqli_fetch_array($result, MYSQL_ASSOC)) {
505
array_push($values, $row);
511
// Determine appropriate options to display given the current combination of layer parameters
513
"observatories" => array(array("name" => "SOHO", "abbreviation" => "SOH")),
514
"instruments" => queryField($dbConnection, $this->format, "observatory", "instrument", $obs),
515
"detectors" => queryField($dbConnection, $this->format, "instrument", "detector", $inst),
516
"measurements" => queryField($dbConnection, $this->format, "detector", "measurement", $det)
520
if ($this->format == "json")
521
header("Content-type: application/json");
523
echo json_encode($options);
528
* @return bool Input validity.
530
* Action-specific code for validating input. Currently only input for external-use API's are
531
* validated. All input destined for database use is secured at time of SQL construction.
533
private function validate ($action) {
534
// Some useful regexes
535
$layer_regex = "/^[a-zA-Z0-9]{12}$/";
536
$layer_list_regex = "/^[a-zA-Z0-9]{12}(,[a-zA-Z0-9]{12})*$/";
537
$timestamp_regex = "/^[0-9]{1,10}$/";
538
$timestamp_list_regex = "/^[0-9]{1,10}(,[0-9]{1,10})*$/";
539
$coordinate_range_regex = "/^[0-9]{1,2},[0-9]{1,2}$/";
544
case "getClosestImage":
546
case "getViewerImage":
547
if (!isset($this->params["layers"]) or !preg_match($layer_list_regex, $this->params["layers"]))
549
if (!isset($this->params["timestamps"]) or !preg_match($timestamp_list_regex, $this->params["timestamps"]))
551
if (!isset($this->params["zoomLevel"]) or !is_numeric($this->params["zoomLevel"]) or $this->params["zoomLevel"] < 0 or $this->params["zoomLevel"] > 20)
553
if (!isset($this->params["xRange"]))
555
if (!isset($this->params["yRange"]))
560
if (!isset($this->params["imageId"]) or !is_numeric($this->params["imageId"]))
563
case "getEventCatalogs":
567
case "getLayerAvailability":
571
case "getJP2ImageSeries":
573
case "launchJHelioviewer":
576
throw new Exception("Invalid action specified. See the <a href='http://www.helioviewer.org/api/'>API Documentation</a> for a list of valid actions.");