diff --git a/docroot/docs/index.php b/docroot/docs/index.php
index b74496b3d..bcb7abecd 100755
--- a/docroot/docs/index.php
+++ b/docroot/docs/index.php
@@ -16,16 +16,15 @@
// Script was called directly. Redirect to an API-version specific URL.
if (realpath(__FILE__) == $_SERVER['SCRIPT_FILENAME']) {
require_once dirname(realpath(__FILE__)).'/../../src/Config.php';
- $config = new Config(dirname(realpath(__FILE__))
- . '/../../settings/Config.ini');
+ $config = new Config(dirname(realpath(__FILE__)) . '/../../settings/Config.ini');
header('Location: '.HV_WEB_ROOT_URL.'/docs/'.$api_version);
}
function import_xml($api_version, &$api_xml_path, &$xml) {
- $api_xml_path = dirname(realpath(__FILE__)) . '/' . $api_version
- . '/api_definitions.xml';
- $xml = simplexml_load_file($api_xml_path);
+ $api_xml_url = sprintf("%s/docs/%s/api_definitions.xml", "https://api.helioviewer.org", $api_version);
+ $xml = simplexml_load_file($api_xml_url);
+ $api_xml_path = dirname(realpath(__FILE__)) . '/' . $api_version. '/api_definitions.xml';
}
function output_html($api_version) {
diff --git a/docroot/index.php b/docroot/index.php
index 2bf6b9895..2e8207ef8 100644
--- a/docroot/index.php
+++ b/docroot/index.php
@@ -33,8 +33,10 @@
date_default_timezone_set('UTC');
register_shutdown_function('shutdownFunction');
-if ( array_key_exists('docs', $_GET) ) {
- printAPIDocs();
+// Options requests are just for validating CORS
+// Lets just pass them through
+if ( array_key_exists('REQUEST_METHOD', $_SERVER) && $_SERVER['REQUEST_METHOD'] == 'OPTIONS' ) {
+ echo 'OK';
exit;
}
@@ -52,6 +54,7 @@
echo json_encode([
'success' => false,
'message' => $re->getMessage(),
+ 'data' => [],
]);
exit;
}
@@ -75,68 +78,65 @@
function loadModule($params) {
$valid_actions = array(
- 'downloadScreenshot' => 'WebClient',
- 'getClosestImage' => 'WebClient',
- 'getDataSources' => 'WebClient',
- 'getJP2Header' => 'WebClient',
- 'getNewsFeed' => 'WebClient',
- 'getStatus' => 'WebClient',
- 'getSciDataScript' => 'WebClient',
- 'getTile' => 'WebClient',
- 'downloadImage' => 'WebClient',
- 'getUsageStatistics' => 'WebClient',
- 'getDataCoverageTimeline' => 'WebClient',
- 'getDataCoverage' => 'WebClient',
- 'updateDataCoverage' => 'WebClient', // Deprecated, remove in V3, replaced by management scripts
- 'shortenURL' => 'WebClient',
- 'goto' => 'WebClient',
- 'saveWebClientState' => 'WebClient',
- 'getWebClientState' => 'WebClient',
- 'takeScreenshot' => 'WebClient',
- 'getRandomSeed' => 'WebClient',
- 'getJP2Image' => 'JHelioviewer',
- 'getJPX' => 'JHelioviewer',
- 'getJPXClosestToMidPoint' => 'JHelioviewer',
- 'launchJHelioviewer' => 'JHelioviewer',
- 'downloadMovie' => 'Movies',
- 'getMovieStatus' => 'Movies',
- 'playMovie' => 'Movies',
- 'queueMovie' => 'Movies',
- 'reQueueMovie' => 'Movies',
- 'uploadMovieToYouTube' => 'Movies',
- 'checkYouTubeAuth' => 'Movies',
- 'getYouTubeAuth' => 'Movies',
- 'getUserVideos' => 'Movies',
- 'getObservationDateVideos' => 'Movies',
- 'events' => 'SolarEvents',
- 'getEventFRMs' => 'SolarEvents',
- 'getEvent' => 'SolarEvents',
- 'getFRMs' => 'SolarEvents',
- 'getDefaultEventTypes' => 'SolarEvents',
- 'getEvents' => 'SolarEvents',
- 'importEvents' => 'SolarEvents', // Deprecated, remove in V3, replaced by management scripts
- 'getEventsByEventLayers' => 'SolarEvents',
- 'getEventGlossary' => 'SolarEvents',
- 'getSolarBodiesGlossary' => 'SolarBodies',
- 'getSolarBodies' => 'SolarBodies',
- 'getTrajectoryTime' => 'SolarBodies',
- 'logNotificationStatistics' => 'WebClient',
- 'getEclipseImage' => 'WebClient'
+ 'downloadScreenshot' => 'WebClient',
+ 'getClosestImage' => 'WebClient',
+ 'getDataSources' => 'WebClient',
+ 'getJP2Header' => 'WebClient',
+ 'getNewsFeed' => 'WebClient',
+ 'getStatus' => 'WebClient',
+ 'getSciDataScript' => 'WebClient',
+ 'getTile' => 'WebClient',
+ 'downloadImage' => 'WebClient',
+ 'getUsageStatistics' => 'WebClient',
+ 'getDataCoverageTimeline' => 'WebClient',
+ 'getDataCoverage' => 'WebClient',
+ 'updateDataCoverage' => 'WebClient', // Deprecated, remove in V3, replaced by management scripts
+ 'shortenURL' => 'WebClient',
+ 'goto' => 'WebClient',
+ 'saveWebClientState' => 'WebClient',
+ 'getWebClientState' => 'WebClient',
+ 'takeScreenshot' => 'WebClient',
+ 'postScreenshot' => 'WebClient',
+ 'getRandomSeed' => 'WebClient',
+ 'getJP2Image' => 'JHelioviewer',
+ 'getJPX' => 'JHelioviewer',
+ 'getJPXClosestToMidPoint' => 'JHelioviewer',
+ 'launchJHelioviewer' => 'JHelioviewer',
+ 'downloadMovie' => 'Movies',
+ 'getMovieStatus' => 'Movies',
+ 'playMovie' => 'Movies',
+ 'queueMovie' => 'Movies',
+ 'postMovie' => 'Movies',
+ 'reQueueMovie' => 'Movies',
+ 'uploadMovieToYouTube' => 'Movies',
+ 'checkYouTubeAuth' => 'Movies',
+ 'getYouTubeAuth' => 'Movies',
+ 'getUserVideos' => 'Movies',
+ 'getObservationDateVideos' => 'Movies',
+ 'events' => 'SolarEvents',
+ 'getEventFRMs' => 'SolarEvents',
+ 'getEvent' => 'SolarEvents',
+ 'getFRMs' => 'SolarEvents',
+ 'getDefaultEventTypes' => 'SolarEvents',
+ 'getEvents' => 'SolarEvents',
+ 'importEvents' => 'SolarEvents', // Deprecated, remove in V3, replaced by management scripts
+ 'getEventsByEventLayers' => 'SolarEvents',
+ 'getEventGlossary' => 'SolarEvents',
+ 'getSolarBodiesGlossary' => 'SolarBodies',
+ 'getSolarBodies' => 'SolarBodies',
+ 'getTrajectoryTime' => 'SolarBodies',
+ 'logNotificationStatistics' => 'WebClient',
+ 'getEclipseImage' => 'WebClient',
+ 'getClosestImageDatesForSources' => 'WebClient',
);
include_once HV_ROOT_DIR.'/../src/Validation/InputValidator.php';
try {
- if ( !array_key_exists('action', $params) ||
- !array_key_exists($params['action'], $valid_actions) ) {
-
- $url = HV_WEB_ROOT_URL.'/docs/';
- throw new Exception(
- 'Invalid action specified.
Consult the ' .
- 'API Documentation for a list of valid actions.', 26
- );
- }
- else {
+ if ( !array_key_exists($params['action'], $valid_actions) ) {
+ throw new \InvalidArgumentException('Invalid action specified.
Consult the API Documentation for a list of valid actions.');
+ } else {
+
//Set-up variables for rate-limiting
$prefix = HV_RATE_LIMIT_PREFIX;
//Use IP address as identifier.
@@ -170,13 +170,19 @@ function loadModule($params) {
$module->execute();
// Update usage stats
- $actions_to_keep_stats_for = array('getClosestImage',
- 'takeScreenshot', 'getJPX', 'getJPXClosestToMidPoint', 'uploadMovieToYouTube', 'getRandomSeed');
+ $actions_to_keep_stats_for = [
+ 'getClosestImage',
+ 'takeScreenshot',
+ 'postScreenshot',
+ 'getJPX',
+ 'getJPXClosestToMidPoint',
+ 'uploadMovieToYouTube',
+ 'getRandomSeed',
+ ];
// Note that in addition to the above, buildMovie requests and
// addition to getTile when the tile was already in the cache.
- if ( HV_ENABLE_STATISTICS_COLLECTION &&
- in_array($params['action'], $actions_to_keep_stats_for) ) {
+ if ( HV_ENABLE_STATISTICS_COLLECTION && in_array($params['action'], $actions_to_keep_stats_for) ) {
include_once HV_ROOT_DIR.'/../src/Database/Statistics.php';
$statistics = new Database_Statistics();
@@ -198,9 +204,34 @@ function loadModule($params) {
//limit exceeded
}
}
- }
- catch (Exception $e) {
+ } catch (\InvalidArgumentException $e) {
+
+ // Proper response code
+ http_response_code(400);
+
+ // Determine the content type of the request
+ $content_type = $_SERVER['CONTENT_TYPE'] ?? '';
+
+ // If the request is posting JSON
+ if('application/json' === $content_type) {
+
+ // Set the content type to JSON
+ header('Content-Type: application/json');
+
+ echo json_encode([
+ 'success' => false,
+ 'message' => $e->getMessage(),
+ 'data' => [],
+ ]);
+ exit;
+ }
+
+ printHTMLErrorMsg($e->getMessage());
+
+ } catch (Exception $e) {
+
printHTMLErrorMsg($e->getMessage());
+
}
return true;
@@ -263,8 +294,7 @@ function shutDownFunction() {
$error = error_get_last();
if (!is_null($error) && $error['type'] == 1) {
- handleError(sprintf("%s:%d - %s", $error['file'], $error['line'],
- $error['message']), $error->getCode());
+ handleError(sprintf("%s:%d - %s", $error['file'], $error['line'], $error['message']));
}
}
?>
diff --git a/docroot/statistics/statistics.js b/docroot/statistics/statistics.js
index 12f16e889..57d482300 100644
--- a/docroot/statistics/statistics.js
+++ b/docroot/statistics/statistics.js
@@ -15,10 +15,10 @@ var colors = ["#D32F2F", "#9bd927", "#27d9be", "#6527d9", "#0091EA", "#FF6F00",
var heirarchy = {
"Total":["total","rate_limit_exceeded"],
"Client Sites":["standard","embed","minimal"],
- "Images":["takeScreenshot","getTile","getClosestImage","getJP2Image-web","getJP2Image-jpip","getJP2Image","downloadScreenshot","getJPX","getJPXClosestToMidPoint", "downloadImage"],
- "Movies":["buildMovie","getMovieStatus","queueMovie","reQueueMovie","playMovie","downloadMovie","getUserVideos","getObservationDateVideos","uploadMovieToYouTube","checkYouTubeAuth","getYouTubeAuth"],
+ "Images":["takeScreenshot","postScreenshot","getTile","getClosestImage","getJP2Image-web","getJP2Image-jpip","getJP2Image","downloadScreenshot","getJPX","getJPXClosestToMidPoint", "downloadImage"],
+ "Movies":["buildMovie","getMovieStatus","queueMovie","postMovie","reQueueMovie","playMovie","downloadMovie","getUserVideos","getObservationDateVideos","uploadMovieToYouTube","checkYouTubeAuth","getYouTubeAuth"],
"Events":["getEventGlossary", "events", "getEvents","getFRMs","getEvent","getEventFRMs","getDefaultEventTypes","getEventsByEventLayers","importEvents"],
- "Data":["getRandomSeed","getDataSources","getJP2Header","getDataCoverage","getStatus","getNewsFeed","getDataCoverageTimeline","getClosestData","getSolarBodiesGlossary","getSolarBodies","getTrajectoryTime","sciScript-SSWIDL","sciScript-SunPy","getSciDataScript","updateDataCoverage","getEclipseImage"],
+ "Data":["getRandomSeed","getDataSources","getJP2Header","getDataCoverage","getStatus","getNewsFeed","getDataCoverageTimeline","getClosestData","getSolarBodiesGlossary","getSolarBodies","getTrajectoryTime","sciScript-SSWIDL","sciScript-SunPy","getSciDataScript","updateDataCoverage","getEclipseImage","getClosestImageDatesForSources"],
"Other":["shortenURL", "goto", "getUsageStatistics","movie-notifications-granted","movie-notifications-denied","logNotificationStatistics","launchJHelioviewer", "saveWebClientState", "getWebClientState"],
"WebGL":["getTexture","getGeometryServiceData"]
};
diff --git a/docs/src/source/api/api_groups/movies.rst b/docs/src/source/api/api_groups/movies.rst
index 911768a7a..8b8703c72 100644
--- a/docs/src/source/api/api_groups/movies.rst
+++ b/docs/src/source/api/api_groups/movies.rst
@@ -26,6 +26,7 @@ See the Coordinates Appendix for more infomration about working with the coordin
used by Helioviewer.org.
.. include:: movies/queueMovie.rst
+.. include:: movies/postMovie.rst
.. include:: movies/reQueueMovie.rst
.. include:: movies/getMovieStatus.rst
.. include:: movies/downloadMovie.rst
diff --git a/docs/src/source/api/api_groups/movies/downloadMovie.rst b/docs/src/source/api/api_groups/movies/downloadMovie.rst
index 003236fd0..74af88864 100644
--- a/docs/src/source/api/api_groups/movies/downloadMovie.rst
+++ b/docs/src/source/api/api_groups/movies/downloadMovie.rst
@@ -11,7 +11,7 @@ Download a custom movie in one of three file formats.
+===========+==========+=========+=========+==================================================================================================+
| id | Required | string | VXvX5 | Unique movie identifier (provided by the response to a `queueMovie` request). |
+-----------+----------+---------+---------+--------------------------------------------------------------------------------------------------+
- | format | Required | string | mp4 | Movie Format (`mp4`, `webm`, or `flv`). |
+ | format | Required | string | mp4 | Movie Format (`mp4`, `webm`). |
+-----------+----------+---------+---------+--------------------------------------------------------------------------------------------------+
| hq | Optional | boolean | true | Optionally download a higher-quality movie file (valid for .mp4 movies only, ignored otherwise). |
+-----------+----------+---------+---------+--------------------------------------------------------------------------------------------------+
diff --git a/docs/src/source/api/api_groups/movies/getMovieStatus.rst b/docs/src/source/api/api_groups/movies/getMovieStatus.rst
index 11e4c05b4..351a28c34 100644
--- a/docs/src/source/api/api_groups/movies/getMovieStatus.rst
+++ b/docs/src/source/api/api_groups/movies/getMovieStatus.rst
@@ -9,7 +9,7 @@ GET /v2/getMovieStatus/
+===========+==========+=========+=========+================================================================================+
| id | Required | string | VXvX5 | Unique movie identifier (provided by the response to a `queueMovie` request). |
+-----------+----------+---------+---------+--------------------------------------------------------------------------------+
- | format | Required | string | mp4 | Movie format (`mp4`, `webm`, or `flv`). |
+ | format | Required | string | mp4 | Movie format (`mp4`, `webm`). |
+-----------+----------+---------+---------+--------------------------------------------------------------------------------+
| verbose | Optional | boolean | true | Optionally include extra metadata in the response. |
+-----------+----------+---------+---------+--------------------------------------------------------------------------------+
diff --git a/docs/src/source/api/api_groups/movies/playMovie.rst b/docs/src/source/api/api_groups/movies/playMovie.rst
index a3c548598..d197500fe 100644
--- a/docs/src/source/api/api_groups/movies/playMovie.rst
+++ b/docs/src/source/api/api_groups/movies/playMovie.rst
@@ -11,7 +11,7 @@ Output an HTML web page with the requested movie embedded within.
+===========+==========+=========+=========+==================================================================================================+
| id | Required | string | VXvX5 | Unique movie identifier (provided by the response to a `queueMovie` request). |
+-----------+----------+---------+---------+--------------------------------------------------------------------------------------------------+
- | format | Required | string | mp4 | Movie format (mp4, webm, or flv). |
+ | format | Required | string | mp4 | Movie format (mp4, webm). |
+-----------+----------+---------+---------+--------------------------------------------------------------------------------------------------+
| hq | Optional | boolean | true | Optionally download a higher-quality movie file (valid for .mp4 movies only, ignored otherwise). |
+-----------+----------+---------+---------+--------------------------------------------------------------------------------------------------+
diff --git a/docs/src/source/api/api_groups/movies/postMovie.rst b/docs/src/source/api/api_groups/movies/postMovie.rst
new file mode 100644
index 000000000..48565410f
--- /dev/null
+++ b/docs/src/source/api/api_groups/movies/postMovie.rst
@@ -0,0 +1,265 @@
+postMovie
+^^^^^^^^^
+
+**URL:** ``/v2/postMovie/``
+
+**Method:** ``POST``
+
+**Content-Type:** ``application/json``
+
+Create a custom movie with a POST request by submitting JSON to the movie generation queue.
+The response returned will provide you with a unique Movie ID that can be used
+to check the status of your movie (via `getMovieStatus <#getmoviestatus>`_)
+and to download your movie (via `downloadMovie <#downloadmovie>`_).
+
+Request Format
+~~~~~~~~~~~~~~
+
+The request must be a JSON object with the following structure:
+
+.. code-block:: json
+
+ {
+ "date" : "2014-01-01T23:59:59Z",
+ "imageScale" : 2.4204409,
+ "layers" : "[3,1,100]"
+ }
+
+Parameters
+~~~~~~~~~~
+
+Request JSON object consist of following parameters
+
+.. list-table:: JSON Request Parameters:
+ :header-rows: 1
+
+ * - Parameter
+ - Required
+ - Type
+ - Example
+ - Description
+ * - ``startTime``
+ - Required
+ - string
+ - 2010-03-01T12:12:12Z
+ - Desired date and time of the first frame of the movie. ISO 8601 combined UTC date and time UTC format.
+ * - ``endTime``
+ - Required
+ - string
+ - 2010-03-04T12:12:12Z
+ - Desired date and time of the final frame of the movie. ISO 8601 combined UTC date and time UTC format.
+ * - ``layers``
+ - Required
+ - string
+ - | [3,1,100]
+ | or
+ | [3,1,100,2,60,1,2010-03-01T12:12:12.000Z]
+ - Image datasource layer(s) to include in the movie.
+ * - ``eventsState``
+ - Optional
+ - object
+ - | {
+ | "tree_HEK": {
+ | "labels_visible": true,
+ | "layers": [
+ | {
+ | "event_type": "flare",
+ | "frms": ["frm10", "frm20"],
+ | "event_instances": ["flare--frm1--event1", "flare--frm2--event2"]
+ | }
+ | ]
+ | },
+ | ....
+ | }
+ - | Optional list of feature/event types to use to annotate the movie.
+ | To get more information about this structure, please see document : :ref:`events-state-page`
+ * - ``imageScale``
+ - Required
+ - number
+ - 21.04
+ - Image scale in arcseconds per pixel.
+ * - ``format``
+ - Optional
+ - string
+ - mp4
+ - Movie format (`mp4`, `webm`). Default value is `mp4`.
+ * - ``frameRate``
+ - Optional
+ - string
+ - 15
+ - Movie frames per second. 15 frames per second by default.
+ * - ``maxFrames``
+ - Optional
+ - string
+ - 300
+ - Maximum number of frames in the movie. May be capped by the server.
+ * - ``scale``
+ - Optional
+ - boolean
+ - false
+ - Optionally overlay an image scale indicator.
+ * - ``scaleType``
+ - Optional
+ - string
+ - earth
+ - Image scale indicator.
+ * - ``scaleX``
+ - Optional
+ - number
+ - -1000
+ - Horizontal offset of the image scale indicator in arcseconds with respect to the center of the Sun.
+ * - ``scaleY``
+ - Optional
+ - number
+ - -500
+ - Vertical offset of the image scale indicator in arcseconds with respect to the center of the Sun.
+ * - ``movieLength``
+ - Optional
+ - number
+ - 4.3333
+ - Movie length in seconds.
+ * - ``watermark``
+ - Optional
+ - boolean
+ - true
+ - Optionally overlay a Helioviewer.org watermark image. Enabled by default.
+ * - ``width``
+ - Optional
+ - string
+ - 1920
+ - Width of the field of view in pixels. (Used in conjunction width `x0`,`y0`, and `height`).
+ * - ``height``
+ - Optional
+ - string
+ - 1200
+ - Height of the field of view in pixels. (Used in conjunction width `x0`,`y0`, and `width`).
+ * - ``x0``
+ - Optional
+ - string
+ - 0
+ - The horizontal offset of the center of the field of view from the center of the Sun. Used in conjunction with `y0`, `width`, and `height`.
+ * - ``y0``
+ - Optional
+ - string
+ - 0
+ - The vertical offset of the center of the field of view from the center of the Sun. Used in conjunction with `x0`, `width`, and `height`.
+ * - ``x1``
+ - Optional
+ - string
+ - -5000
+ - The horizontal offset of the top-left corner of the field of view with respect to the center of the Sun (in arcseconds). Used in conjunction with `y1`, `x2`, and `y2`.
+ * - ``y1``
+ - Optional
+ - string
+ - -5000
+ - The vertical offset of the top-left corner of the field of view with respect to the center of the Sun (in arcseconds). Used in conjunction with `x1`, `x2`, and `y2`.
+ * - ``x2``
+ - Optional
+ - string
+ - 5000
+ - The horizontal offset of the bottom-right corner of the field of view with respect to the center of the Sun (in arcseconds). Used in conjunction with `x1`, `y1`, and `y2`.
+ * - ``y2``
+ - Optional
+ - string
+ - 5000
+ - The vertical offset of the bottom-right corner of the field of view with respect to the center of the Sun (in arcseconds). Used in conjunction with `x1`, `y1`, and `x2`.
+ * - ``callback``
+ - Optional
+ - string
+ -
+ - Wrap the response object in a function call of your choosing.
+ * - ``size``
+ - Optional
+ - number
+ - 0
+ - | Scale video to preset size
+ | 0 - Original size
+ | 1 - 720p (1280 x 720, HD Ready);
+ | 2 - 1080p (1920 x 1080, Full HD);
+ | 3 - 1440p (2560 x 1440, Quad HD);
+ | 4 - 2160p (3840 x 2160, 4K or Ultra HD).
+ * - ``movieIcons``
+ - Optional
+ - number
+ - 0
+ - Display other user generated movies on the video.
+ * - ``followViewport``
+ - Optional
+ - number
+ - 0
+ - Rotate field of view of movie with Sun.
+ * - ``reqObservationDate``
+ - Optional
+ - string
+ - 2017-08-30T14:45:53.000Z
+ - Viewport time. Used when 'followViewport' enabled to shift viewport area to correct coordinates.
+
+Example: Queued Movie (JSON)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+JSON response to "postMovie" API requests.
+
+.. code-block:: http
+ :caption: Example Request:
+
+ POST /v2/postMovie/ HTTP/1.1
+ Host: api.helioviewer.org
+
+ Content-Type: application/json
+ {
+ "startTime" : "2010-03-01T12:12:12Z",
+ "endTime" : "2010-03-04T12:12:12Z",
+ "imageScale" : 21.04,
+ "layers" : "[3,1,100]",
+ "eventsState" : {
+ "tree_HEK": {
+ "labels_visible": true,
+ "layers": [
+ {
+ "event_type": "flare",
+ "frms": ["frm10", "frm20"],
+ "event_instances": ["flare--frm1--event1", "flare--frm2--event2"]
+ }
+ ]
+ },
+ },
+ "x1" : -5000,
+ "y1" : -5000,
+ "x2" : 5000,
+ "y2" : 5000,
+ }
+
+.. code-block:: json
+ :caption: Example Response:
+
+ {
+ "id": "z6vX5",
+ "eta": 376,
+ "queue": 0,
+ "token": "50e0d98f645b42d159ec1c8a1e15de3e"
+ }
+
+.. list-table:: JSON Response Parameters:
+ :header-rows: 1
+
+ * - Parameter
+ - Required
+ - Type
+ - Description
+ * - ``id``
+ - Required
+ - string
+ - Unique movie identifier (e.g. "z6vX5")
+ * - ``eta``
+ - Required
+ - number
+ - Estimated time until movie generation will be completed in seconds
+ * - ``queue``
+ - Required
+ - number
+ - Position in movie generation queue
+ * - ``token``
+ - Required
+ - string
+ - Handle to job in the movie builder queue
+
diff --git a/docs/src/source/api/api_groups/movies/queueMovie.rst b/docs/src/source/api/api_groups/movies/queueMovie.rst
index 741ff39be..cc39ec3ab 100644
--- a/docs/src/source/api/api_groups/movies/queueMovie.rst
+++ b/docs/src/source/api/api_groups/movies/queueMovie.rst
@@ -26,7 +26,7 @@ and to download your movie (via `downloadMovie <#downloadmovie>`_).
+--------------------+----------+---------+------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| imageScale | Required | number | 21.04 | Image scale in arcseconds per pixel. |
+--------------------+----------+---------+------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
- | format | Optional | string | mp4 | Movie format (`mp4`, `webm`, `flv`). Default value is `mp4`. |
+ | format | Optional | string | mp4 | Movie format (`mp4`, `webm`). Default value is `mp4`. |
+--------------------+----------+---------+------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| frameRate | Optional | string | 15 | Movie frames per second. 15 frames per second by default. |
+--------------------+----------+---------+------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
diff --git a/docs/src/source/api/api_groups/screenshots.rst b/docs/src/source/api/api_groups/screenshots.rst
index ac0dc4b36..e9918f114 100644
--- a/docs/src/source/api/api_groups/screenshots.rst
+++ b/docs/src/source/api/api_groups/screenshots.rst
@@ -7,5 +7,6 @@ extended region polygons, associated text labels, and a size-of-earth scale
indicator can optionally be overlayed onto a movie.
.. include:: screenshots/takeScreenshot.rst
+.. include:: screenshots/postScreenshot.rst
.. include:: screenshots/downloadScreenshot.rst
.. include:: screenshots/getEclipseImage.rst
diff --git a/docs/src/source/api/api_groups/screenshots/postScreenshot.rst b/docs/src/source/api/api_groups/screenshots/postScreenshot.rst
new file mode 100644
index 000000000..1bec34a03
--- /dev/null
+++ b/docs/src/source/api/api_groups/screenshots/postScreenshot.rst
@@ -0,0 +1,215 @@
+postScreenshot
+^^^^^^^^^^^^^^
+
+**URL:** ``/v2/postScreenshot/``
+
+**Method:** ``POST``
+
+**Content-Type:** ``application/json``
+
+Generate a custom screenshot with JSON POST request.
+
+You must specify values for either `x1`, `y1`, `x2`, and `y2`
+or `x0`, `y0`, `width` and `height` inside JSON.
+
+By default, the response is a JSON object containing a unique screenshot
+identifier (`id`) that can be used to with the `downloadScreenshot` API endpoint.
+
+Set the `display` parameter to `true` to directly return the screenshot as
+binary PNG image data in the response.
+
+Please note that each request causes the server to generate a screenshot from
+scratch and is resource intensive. For performance reasons, you should cache the
+response if you simply intend to serve exactly the same screenshot to multiple
+users.
+
+Request Format
+~~~~~~~~~~~~~~
+
+The request must be a JSON object with the following structure:
+
+.. code-block:: json
+
+ {
+ "date" : "2014-01-01T23:59:59Z",
+ "imageScale" : 2.4204409,
+ "layers" : "[3,1,100]"
+ }
+
+Parameters
+~~~~~~~~~~
+
+Request JSON object consist of following parameters
+
+.. list-table:: JSON Request Parameters:
+ :header-rows: 1
+
+ * - Parameter
+ - Required
+ - Type
+ - Example
+ - Description
+ * - ``date``
+ - Required
+ - string
+ - 2014-01-01T23:59:59Z
+ - Desired date/time of the image. ISO 8601 combined UTC date and time UTC format.
+ * - ``imageScale``
+ - Required
+ - number
+ - 2.4204409
+ - Image scale in arcseconds per pixel.
+ * - ``layers``
+ - Required
+ - string
+ - | [3,1,100]
+ | or
+ | [3,1,100,2,60,1,2010-03-01T12:12:12.000Z]
+ - Image datasource layer(s) to include in the screenshot.
+ * - ``eventsState``
+ - Optional
+ - object
+ - | {
+ | "tree_HEK": {
+ | "labels_visible": true,
+ | "layers": [
+ | {
+ | "event_type": "flare",
+ | "frms": ["frm10", "frm20"],
+ | "event_instances": ["flare--frm1--event1", "flare--frm2--event2"]
+ | }
+ | ]
+ | },
+ | ....
+ | }
+ - | List feature/event types and FRMs to use to annotate the screenshot. Use the empty string to indicate that no feature/event annotations should be shown.
+ | To get more information about this structure, please see document : :ref:`events-state-page`
+ * - ``scale``
+ - Optional
+ - boolean
+ - false
+ - Optionally overlay an image scale indicator.
+ * - ``scaleType``
+ - Optional
+ - string
+ - earth
+ - Image scale indicator.
+ * - ``scaleX``
+ - Optional
+ - number
+ - -1000
+ - Horizontal offset of the image scale indicator in arcseconds with respect to the center of the Sun.
+ * - ``scaleY``
+ - Optional
+ - number
+ - -500
+ - Vertical offset of the image scale indicator in arcseconds with respect to the center of the Sun.
+ * - ``width``
+ - Optional
+ - string
+ - 1920
+ - Width of the field of view in pixels. (Used in conjunction width `x0`,`y0`, and `height`).
+ * - ``height``
+ - Optional
+ - string
+ - 1200
+ - Height of the field of view in pixels. (Used in conjunction width `x0`,`y0`, and `width`).
+ * - ``x0``
+ - Optional
+ - string
+ - 0
+ - The horizontal offset of the center of the field of view from the center of the Sun. Used in conjunction with `y0`, `width`, and `height`.
+ * - ``y0``
+ - Optional
+ - string
+ - 0
+ - The vertical offset of the center of the field of view from the center of the Sun. Used in conjunction with `x0`, `width`, and `height`.
+ * - ``x1``
+ - Optional
+ - string
+ - -5000
+ - The horizontal offset of the top-left corner of the field of view with respect to the center of the Sun (in arcseconds). Used in conjunction with `y1`, `x2`, and `y2`.
+ * - ``y1``
+ - Optional
+ - string
+ - -5000
+ - The vertical offset of the top-left corner of the field of view with respect to the center of the Sun (in arcseconds). Used in conjunction with `x1`, `x2`, and `y2`.
+ * - ``x2``
+ - Optional
+ - string
+ - 5000
+ - The horizontal offset of the bottom-right corner of the field of view with respect to the center of the Sun (in arcseconds). Used in conjunction with `x1`, `y1`, and `y2`.
+ * - ``y2``
+ - Optional
+ - string
+ - 5000
+ - The vertical offset of the bottom-right corner of the field of view with respect to the center of the Sun (in arcseconds). Used in conjunction with `x1`, `y1`, and `x2`.
+ * - ``display``
+ - Optional
+ - boolean
+ - false
+ - Set to `true` to directly output binary PNG image data. Default is `false` (which outputs a JSON object).
+ * - ``watermark``
+ - Optional
+ - boolean
+ - true
+ - Optionally overlay a watermark consisting of a Helioviewer logo and the datasource abbreviation(s) and timestamp(s) displayed in the screenshot.
+ * - ``callback``
+ - Optional
+ - string
+ -
+ - Wrap the response object in a function call of your choosing.
+
+Example: Post Screenshot (JSON)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+JSON response to "postScreenshot" API requests. Assumes that the `display`
+parameter was omitted or set to `false`.
+
+.. code-block:: http
+ :caption: Example Request:
+
+ POST /v2/postScreenshot/ HTTP/1.1
+ Host: api.helioviewer.org
+
+ Content-Type: application/json
+ {
+ "date" : "2014-01-01T23:59:59Z",
+ "imageScale" : 2.4204409,
+ "layers" : "[3,1,100]"
+ }
+
+.. code-block:: json
+ :caption: Example Response:
+
+ {
+ "id": 3285980
+ }
+
+.. table:: Response Description
+
+ +-----------+----------+--------+-----------------------------------------------+
+ | Parameter | Required | Type | Description |
+ +===========+==========+========+===============================================+
+ | id | Required | string | Unique screenshot identifier (e.g. "3285980") |
+ +-----------+----------+--------+-----------------------------------------------+
+
+Example: binary (PNG image data)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Set the `display` parameter to `true` to directly return binary PNG image data
+in the response.
+
+.. code-block:: http
+ :caption: Example Request:
+
+ POST /v2/postScreenshot/ HTTP/1.1
+ Host: api.Helioviewer.org
+
+ Content-Type: application/json
+ {
+ "date" : "2014-01-01T23:59:59Z",
+ "imageScale" : "2.4204409",
+ "layers" : "[3,1,100]",
+ "display" : true
+ }
diff --git a/docs/src/source/appendix.rst b/docs/src/source/appendix.rst
index e57830fff..20d115ff3 100644
--- a/docs/src/source/appendix.rst
+++ b/docs/src/source/appendix.rst
@@ -9,3 +9,4 @@ The appendices below provide further context for Helioviewer API usage.
appendix/data_sources.rst
appendix/coordinates.rst
appendix/helioviewer_event_format.rst
+ appendix/events_state.rst
diff --git a/docs/src/source/appendix/events_state.rst b/docs/src/source/appendix/events_state.rst
new file mode 100644
index 000000000..e30b562e0
--- /dev/null
+++ b/docs/src/source/appendix/events_state.rst
@@ -0,0 +1,135 @@
+.. _events-state-page:
+
+Events State
+============
+
+This document describes the structure of the events state JSON string,
+which is used for filtering included event markers into the screenshots and movies, also for controlling the visibility of their labels.
+
+Structure
+---------
+
+The Event State data type is a JSON object with the following structure:
+
+.. code-block:: json
+
+ {
+ "event_group_key": {
+ "labels_visible": true,
+ "layers": [
+ {
+ "event_type": "flare",
+ "frms": ["frm10", "frm20"],
+ "event_instances": ["flare--frm1--event1", "flare--frm2--event2"]
+ }
+ ]
+ }
+ }
+
+Parameters
+----------
+
+The following table describes each parameter in the JSON object:
+
+.. list-table:: Event State Parameters
+ :header-rows: 1
+
+ * - Parameter
+ - Type
+ - Description
+ * - ``event_group_key``
+ - object
+ - The root object representing an event layer grouping, such as CCMC or HEK. This contains the configuration for the grouping.
+ * - ``event_group_key.labels_visible``
+ - boolean
+ - Controls the visibility of all event labels under this event layer grouping.
+ * - ``event_group_key.layers``
+ - array
+ - An array of layer objects specifying which events to include in the generated screenshots and movies.
+
+Each layer object within the ``layers`` array contains the following fields:
+
+.. list-table:: Layer Parameters
+ :header-rows: 1
+
+ * - Parameter
+ - Type
+ - Description
+ * - ``event_type``
+ - string
+ - The pin of the event (e.g., "FP") to be included into the screenshot and movies. Please see :ref:`helioviewer-event-format` for getting more information about event pin
+ * - ``frms``
+ - array
+ - An array of strings representing for the event group names to be included into the screenshot and movies. Please see :ref:`helioviewer-event-format` for getting more information about event group title
+ * - ``event_instances``
+ - array
+ - An array of strings representing the unique IDs of the event instances. Please see :ref:`event-instance-algorithm` for details on generating these IDs from events.
+
+Example
+-------
+
+Below is an example of a Event State JSON object:
+
+.. code-block:: json
+
+ {
+ "tree_HEK": {
+ "labels_visible": true,
+ "layers": [
+ {
+ "event_type": "flare",
+ "frms": ["frm10", "frm20"],
+ "event_instances": ["flare--frm1--event1", "flare--frm2--event2"]
+ }
+ ]
+ }
+ }
+
+Description
+-----------
+
+- **labels_visible**: This boolean field indicates whether the labels for all the event labels under this tree configuration should be visible. If set to `true`, labels are visible; if set to `false`, labels are hidden.
+- **layers**: This array contains filtering configuration specifying which events should be included in to the generated screenshots and movies. Each layer provides different levels of filtering:
+
+ - **event_type**: Includes all the events under this event pin.
+ - **frms**: Includes all of the events associated with the group names in this array. Please see :ref:`helioviewer-event-format` for getting more information about event group names.
+ - **event_instances**: Includes specific event instances identified by their unique IDs. Each ID follows the format `event_type--frm--event_id`. Please see :ref:`event-instance-algorithm` for details on generating these IDs.
+
+This structure allows you to filter which event markers are included in the generated screenshots and movies.
+
+.. _event-instance-algorithm:
+
+Individual Event IDs
+--------------------
+
+.. warning::
+ This event ID generation is undergoing active development and may change without notice.
+
+Event IDs are generated from three components:
+
+- **event_pin**: The pin of the event.
+- **event_group_name**: The name of the event group.
+- **event_id**: The unique identifier of the event.
+
+Please see :ref:`helioviewer-event-format` for more information about these fields. After obtaining these fields, users should base64 encode ``event_id``, perform some cleaning, and join them with ``--``.
+
+Here is our implementation in PHP.
+
+.. code-block:: php
+
+
+
+
+This method ensures that the event IDs are unique and suitable for use in filtering events.
+
+
diff --git a/docs/src/source/conf.py b/docs/src/source/conf.py
index b9e324479..2cf58c2c0 100644
--- a/docs/src/source/conf.py
+++ b/docs/src/source/conf.py
@@ -50,4 +50,6 @@
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
-html_static_path = ['_static']
+# html_static_path = ['appendix/images']
+
+pygments_style = 'solarized-dark'
diff --git a/install/database/2024_05_31_add_events_state_to_both_screenshots_and_movies_tables.sql b/install/database/2024_05_31_add_events_state_to_both_screenshots_and_movies_tables.sql
new file mode 100644
index 000000000..4dea9ea79
--- /dev/null
+++ b/install/database/2024_05_31_add_events_state_to_both_screenshots_and_movies_tables.sql
@@ -0,0 +1,20 @@
+-- Add eventState JSON column with default JSON value
+ALTER TABLE screenshots ADD COLUMN eventsState JSON NOT NULL DEFAULT '{}' AFTER eventsLabels;
+
+-- Update existing rows to have the eventsState as {}
+UPDATE screenshots SET eventsState = '{}' WHERE eventsState IS NULL OR eventsState = '';
+
+-- Add eventState JSON column with default JSON value
+ALTER TABLE movies ADD COLUMN eventsState JSON NOT NULL DEFAULT '{}' AFTER eventsLabels;
+
+-- Update existing rows to have the eventsState as {}
+UPDATE movies SET eventsState = '{}' WHERE eventsState IS NULL OR eventsState = '';
+
+
+
+
+
+
+
+
+
diff --git a/install/helioviewer/db.py b/install/helioviewer/db.py
index c0da0bee8..af7dce02e 100644
--- a/install/helioviewer/db.py
+++ b/install/helioviewer/db.py
@@ -942,6 +942,7 @@ def create_movies_table(cursor):
`dataSourceBitMask` BIGINT UNSIGNED,
`eventSourceString` VARCHAR(1024) DEFAULT NULL,
`eventsLabels` TINYINT(1) UNSIGNED NOT NULL DEFAULT '0',
+ `eventsState` JSON NOT NULL DEFAULT '{}',
`movieIcons` tinyint(1) UNSIGNED NOT NULL DEFAULT '0',
`followViewport` tinyint(1) DEFAULT '0',
`scale` TINYINT(1) UNSIGNED NOT NULL DEFAULT '0',
@@ -1148,6 +1149,7 @@ def create_screenshots_table(cursor):
`dataSourceBitMask` BIGINT UNSIGNED,
`eventSourceString` VARCHAR(1024) DEFAULT NULL,
`eventsLabels` TINYINT(1) UNSIGNED NOT NULL,
+ `eventsState` JSON NOT NULL DEFAULT '{}',
`movieIcons` tinyint(1) UNSIGNED NOT NULL DEFAULT '0',
`scale` TINYINT(1) unsigned NOT NULL DEFAULT '0',
`scaleType` VARCHAR(12) DEFAULT 'earth',
diff --git a/scripts/hv_stats/hv_stats.py b/scripts/hv_stats/hv_stats.py
index 957878a81..4948a945b 100644
--- a/scripts/hv_stats/hv_stats.py
+++ b/scripts/hv_stats/hv_stats.py
@@ -2271,10 +2271,10 @@ def add_harea_if_timestamp_in_range(start_time, end_time, color, alpha, label):
heirarchy = {
"Total":["total","rate_limit_exceeded"],
"Client Sites":["standard","embed","minimal"],
- "Images":["takeScreenshot","getTile","getClosestImage","getJP2Image-web","getJP2Image-jpip","getJP2Image","downloadScreenshot","getJPX","getJPXClosestToMidPoint"],
- "Movies":["buildMovie","getMovieStatus","queueMovie","reQueueMovie","playMovie","downloadMovie","getUserVideos","getObservationDateVideos","uploadMovieToYouTube","checkYouTubeAuth","getYouTubeAuth"],
+ "Images":["takeScreenshot","postScreenshot","getTile","getClosestImage","getJP2Image-web","getJP2Image-jpip","getJP2Image","downloadScreenshot","getJPX","getJPXClosestToMidPoint"],
+ "Movies":["buildMovie","postMovie","getMovieStatus","queueMovie","postMovie","reQueueMovie","playMovie","downloadMovie","getUserVideos","getObservationDateVideos","uploadMovieToYouTube","checkYouTubeAuth","getYouTubeAuth"],
"Events":["getEventGlossary","getEvents","getFRMs","getEvent","getEventFRMs","getDefaultEventTypes","getEventsByEventLayers","importEvents"],
- "Data":["getRandomSeed","getDataSources","getJP2Header","getDataCoverage","getStatus","getNewsFeed","getDataCoverageTimeline","getClosestData","getSolarBodiesGlossary","getSolarBodies","getTrajectoryTime","sciScript-SSWIDL","sciScript-SunPy","getSciDataScript","updateDataCoverage","getEclipseImage"],
+ "Data":["getRandomSeed","getDataSources","getJP2Header","getDataCoverage","getStatus","getNewsFeed","getDataCoverageTimeline","getClosestData","getSolarBodiesGlossary","getSolarBodies","getTrajectoryTime","sciScript-SSWIDL","sciScript-SunPy","getSciDataScript","updateDataCoverage","getEclipseImage", "getClosestImageDatesForSources"],
"Other":["shortenURL","getUsageStatistics","movie-notifications-granted","movie-notifications-denied","logNotificationStatistics","launchJHelioviewer", "saveWebClientState", "getWebClientState"],
"WebGL":["getTexture","getGeometryServiceData"]
};
diff --git a/scripts/resque.php b/scripts/resque.php
index 436ead29c..4232bc033 100644
--- a/scripts/resque.php
+++ b/scripts/resque.php
@@ -6,6 +6,7 @@
require_once __DIR__.'/../lib/Resque.php';
require_once __DIR__.'/../lib/Resque/Worker.php';
+require_once __DIR__.'/../vendor/autoload.php';
$REDIS_BACKEND = getenv('REDIS_BACKEND');
if(!empty($REDIS_BACKEND)) {
diff --git a/src/Database/ImgIndex.php b/src/Database/ImgIndex.php
index 672d2f212..d23e13bf5 100644
--- a/src/Database/ImgIndex.php
+++ b/src/Database/ImgIndex.php
@@ -43,14 +43,24 @@ protected function _dbConnect() {
*
* @return int Identifier in the `screenshots` table
*/
- public function insertScreenshot($date, $imageScale, $roi, $watermark,
- $layers, $bitmask, $events, $eventsLabels, $movieIcons, $scale, $scaleType,
- $scaleX, $scaleY, $numLayers, $switchSources, $celestialLabels, $celestialTrajectories) {
+ public function insertScreenshot($date, $imageScale, $roi, $watermark, $layers, $bitmask, $eventsStateString, $movieIcons, $scale, $scaleType,$scaleX, $scaleY, $numLayers, $switchSources, $celestialLabels, $celestialTrajectories) {
include_once HV_ROOT_DIR.'/../src/Helper/DateTimeConversions.php';
$this->_dbConnect();
+ // ATTENTION! These two fields eventsLabels and eventSourceString needs to be kept in DB schema
+ // We are keeping them to support old takeScreenshot , queueMovie requests
+
+ // old implementation removed for events strings
+ // used to be $this->events->serialize();
+ $old_events_layer_string = "";
+
+ // old if events labels are shown switch , removed for new implementation
+ // used to be $this->eventsLabels;
+ $old_events_labels_bool = false;
+
+
$sql = sprintf(
"INSERT INTO screenshots "
. "SET "
@@ -64,7 +74,8 @@ public function insertScreenshot($date, $imageScale, $roi, $watermark,
. "dataSourceBitMask " . " = %d, "
. "eventSourceString " . " ='%s', "
. "eventsLabels " . " = %b, "
- . "movieIcons " . " = %b, "
+ . "eventsState " . " = '%s', "
+ . "movieIcons " . " = %b, "
. "scale " . " = %b, "
. "scaleType " . " ='%s', "
. "scaleX " . " = %f, "
@@ -74,23 +85,18 @@ public function insertScreenshot($date, $imageScale, $roi, $watermark,
. "celestialBodiesLabels" . " = '%s', "
. "celestialBodiesTrajectories" . " = '%s';",
- $this->_dbConnection->link->real_escape_string(
- isoDateToMySQL($date) ),
+ $this->_dbConnection->link->real_escape_string(isoDateToMySQL($date)),
(float)$imageScale,
- $this->_dbConnection->link->real_escape_string(
- $roi ),
+ $this->_dbConnection->link->real_escape_string($roi),
(bool)$watermark,
- $this->_dbConnection->link->real_escape_string(
- $layers ),
- bindec($this->_dbConnection->link->real_escape_string(
- (binary)$bitmask ) ),
- $this->_dbConnection->link->real_escape_string(
- $events ),
- (bool)$eventsLabels,
+ $this->_dbConnection->link->real_escape_string($layers),
+ bindec($this->_dbConnection->link->real_escape_string((binary)$bitmask)),
+ $old_events_layer_string, // eventsSourceString is always empty not used any more
+ $old_events_labels_bool, // eventLabels is not used anymore
+ $this->_dbConnection->link->real_escape_string($eventsStateString),
(bool)$movieIcons,
(bool)$scale,
- $this->_dbConnection->link->real_escape_string(
- $scaleType ),
+ $this->_dbConnection->link->real_escape_string($scaleType),
(float)$scaleX,
(float)$scaleY,
(int)$numLayers,
@@ -98,11 +104,11 @@ public function insertScreenshot($date, $imageScale, $roi, $watermark,
$celestialLabels,
$celestialTrajectories
);
+
try {
$result = $this->_dbConnection->query($sql);
- }
- catch (Exception $e) {
- return false;
+ } catch (Exception $e) {
+ throw new \Exception("Could not create screenshot in our database", 2, $e);
}
return $this->_dbConnection->getInsertId();
@@ -502,6 +508,74 @@ protected function _getGroupForSourceId($sourceId) {
return "";
}
+ /**
+ * Return the closest matches from the `data` table whose time is just before and just after of the given time
+ * there can be null dates, if there is before or after image
+ * @param string $date UTC date string like "2003-10-05T00:00:00Z"
+ * @param int $sourceId The data source identifier in the database
+ *
+ * @return array Array containing 1 next image and 1 prev date image
+ */
+ public function getClosestDataBeforeAndAfter($date, $sourceId)
+ {
+ include_once HV_ROOT_DIR.'/../src/Helper/DateTimeConversions.php';
+
+ $this->_dbConnect();
+
+ $datestr = isoDateToMySQL($date);
+
+ // Before date first image
+ $sql_before = sprintf(
+ "SELECT date "
+ . "FROM data "
+ . "WHERE "
+ . "sourceId " . " = %d AND "
+ . "date " . "< '%s' "
+ . "ORDER BY date DESC "
+ . "LIMIT 1;",
+ (int)$sourceId,
+ $this->_dbConnection->link->real_escape_string($datestr)
+ );
+
+ try {
+ $result = $this->_dbConnection->query($sql_before);
+ } catch (Exception $e) {
+ throw new \Exception("Unable to find before image for ".$date." and sourceId:".$sourceId, 2, $e);
+ }
+
+ $before_date = $result->fetch_array(MYSQLI_ASSOC);
+
+ // After date first image
+ $sql_after = sprintf(
+ "SELECT date "
+ . "FROM data "
+ . "WHERE "
+ . "sourceId " . " = %d AND "
+ . "date " . "> '%s' "
+ . "ORDER BY date ASC "
+ . "LIMIT 1;",
+ (int)$sourceId,
+ $this->_dbConnection->link->real_escape_string($datestr)
+ );
+
+ try {
+ $result = $this->_dbConnection->query($sql_after);
+ } catch (Exception $e) {
+ throw new \Exception("Unable to find before image for ".$date." and sourceId:".$sourceId, 2, $e);
+ }
+
+ // pre($result);
+ $after_date = $result->fetch_array(MYSQLI_ASSOC);
+
+ return [
+ 'prev_date' => $before_date ? $before_date['date']: null,
+ 'next_date' => $after_date ? $after_date['date'] : null,
+ ];
+
+ }
+
+
+
/**
* Return the closest match from the `data` table whose time is on
* or before the specified time.
diff --git a/src/Database/MovieDatabase.php b/src/Database/MovieDatabase.php
index 9ec90858f..3d680da1a 100644
--- a/src/Database/MovieDatabase.php
+++ b/src/Database/MovieDatabase.php
@@ -48,20 +48,32 @@ private function _dbConnect() {
* @return int Identifier in the `movies` table or boolean false
*/
public function insertMovie($startTime, $endTime, $reqObservationDate, $imageScale, $roi,
- $maxFrames, $watermark, $layerString, $layerBitMask, $eventString,
- $eventsLabels, $movieIcons, $followViewport, $scale, $scaleType, $scaleX, $scaleY, $numLayers,
+ $maxFrames, $watermark, $layerString, $layerBitMask, $eventsStateString,
+ $movieIcons, $followViewport, $scale, $scaleType, $scaleX, $scaleY, $numLayers,
$queueNum, $frameRate, $movieLength, $size, $switchSources, $celestialBodies) {
$this->_dbConnect();
$startTime = isoDateToMySQL($startTime);
$endTime = isoDateToMySQL($endTime);
+
if($reqObservationDate != false){
- $reqObservationDate = '"'.$this->_dbConnection->link->real_escape_string(isoDateToMySQL($reqObservationDate)).'"';
+ $reqObservationDate = '"'.$this->_dbConnection->link->real_escape_string(isoDateToMySQL($reqObservationDate)).'"';
}else{
- $reqObservationDate = "NULL";
+ $reqObservationDate = "NULL";
}
+ // ATTENTION! These two fields eventsLabels and eventSourceString needs to be kept in DB schema
+ // We are keeping them to support old takeScreenshot , queueMovie requests
+
+ // old implementation removed for events strings
+ // used to be $this->events->serialize();
+ $old_events_layer_string = "";
+
+ // old if events labels are shown switch , removed for new implementation
+ // used to be $this->eventsLabels;
+ $old_events_labels_bool = false;
+
$sql = sprintf(
'INSERT INTO movies '
. 'SET '
@@ -78,6 +90,7 @@ public function insertMovie($startTime, $endTime, $reqObservationDate, $imageSca
. 'dataSourceBitMask ' . ' = %d, '
. 'eventSourceString ' . ' ="%s", '
. 'eventsLabels ' . ' = %b, '
+ . 'eventsState ' . ' = "%s", '
. 'movieIcons ' . ' = %b, '
. 'followViewport ' . ' = %b, '
. 'scale ' . ' = %b, '
@@ -107,8 +120,9 @@ public function insertMovie($startTime, $endTime, $reqObservationDate, $imageSca
(bool)$watermark,
$this->_dbConnection->link->real_escape_string($layerString),
$layerBitMask,
- $this->_dbConnection->link->real_escape_string($eventString),
- (bool)$eventsLabels,
+ $old_events_layer_string, // eventsSourceString is always empty not used any more
+ $old_events_labels_bool, // eventLabels is not used anymore
+ $this->_dbConnection->link->real_escape_string($eventsStateString),
(bool)$movieIcons,
(bool)$followViewport,
(bool)$scale,
@@ -130,7 +144,7 @@ public function insertMovie($startTime, $endTime, $reqObservationDate, $imageSca
}
catch (Exception $e) {
error_log("Failed to insert movie: " . $e->getMessage());
- return false;
+ throw new \Exception("Could create movie in our database", 2, $e);
}
return $this->_dbConnection->getInsertId();
diff --git a/src/Database/Statistics.php b/src/Database/Statistics.php
index 2017d981d..eb20ef56d 100644
--- a/src/Database/Statistics.php
+++ b/src/Database/Statistics.php
@@ -424,11 +424,14 @@ public function getUsageStatistics($resolution, $dateStart = null, $dateEnd = nu
// Array to keep track of counts for each action
$counts = array(
"buildMovie" => array(),
+ "postMovie" => array(),
"getClosestData" => array(),
"getClosestImage" => array(),
+ "getClosestImageDatesForSources" => array(),
"getJPX" => array(),
"getJPXClosestToMidPoint" => array(),
"takeScreenshot" => array(),
+ "postScreenshot" => array(),
"uploadMovieToYouTube" => array(),
"embed" => array(),
"minimal" => array(),
@@ -446,11 +449,14 @@ public function getUsageStatistics($resolution, $dateStart = null, $dateEnd = nu
// Summary array
$summary = array(
"buildMovie" => 0,
+ "postMovie" => 0,
"getClosestData" => 0,
"getClosestImage" => 0,
"getJPX" => 0,
"getJPXClosestToMidPoint" => 0,
+ "getClosestImageDatesForSources" => 0,
"takeScreenshot" => 0,
+ "postScreenshot" => 0,
"uploadMovieToYouTube" => 0,
"embed" => 0,
"minimal" => 0,
@@ -830,6 +836,7 @@ private function _createCountsArray(){
'getWebClientState' => array(),
'goto' => array(),
'takeScreenshot' => array(),
+ 'postScreenshot' => array(),
'getRandomSeed' => array(),
'getJP2Image' => array(),
'getJPX' => array(),
@@ -839,6 +846,7 @@ private function _createCountsArray(){
'getMovieStatus' => array(),
'playMovie' => array(),
'queueMovie' => array(),
+ 'postMovie' => array(),
'reQueueMovie' => array(),
'uploadMovieToYouTube' => array(),
'checkYouTubeAuth' => array(),
@@ -862,6 +870,7 @@ private function _createCountsArray(){
'getGeometryServiceData' => array(),
'buildMovie' => array(),//this one happens in HelioviewerMovie.php
"getClosestData" => array(),
+ "getClosestImageDatesForSources" => array(),
"embed" => array(),
"minimal" => array(),
"standard" => array(),
@@ -880,6 +889,7 @@ private function _createSummaryArray(){
"total" => 0,
'downloadScreenshot' => 0,
'getClosestImage' => 0,
+ "getClosestImageDatesForSources" => 0,
'getDataSources' => 0,
'getJP2Header' => 0,
'getNewsFeed' => 0,
@@ -896,6 +906,7 @@ private function _createSummaryArray(){
'getWebClientState' => 0,
'goto' => 0,
'takeScreenshot' => 0,
+ 'postScreenshot' => 0,
'getRandomSeed' => 0,
'getJP2Image' => 0,
'getJPX' => 0,
@@ -906,6 +917,7 @@ private function _createSummaryArray(){
'playMovie' => 0,
'queueMovie' => 0,
'reQueueMovie' => 0,
+ 'postMovie' => 0,
'uploadMovieToYouTube' => 0,
'checkYouTubeAuth' => 0,
'getYouTubeAuth' => 0,
diff --git a/src/Event/EventsStateManager.php b/src/Event/EventsStateManager.php
new file mode 100644
index 000000000..a6357f2eb
--- /dev/null
+++ b/src/Event/EventsStateManager.php
@@ -0,0 +1,313 @@
+
+ * @license http://www.mozilla.org/MPL/MPL-1.1.html Mozilla Public License 1.1
+ * @link https://github.com/Helioviewer-Project
+ */
+
+namespace Helioviewer\Api\Event;
+
+class EventsStateManager
+{
+ // internal events state original
+ public array $events_state;
+
+ // internal structure to process random events, if they are OK to be in above events_state
+ private array $events_tree;
+
+ // internal structure to process random events, if their labels are visible
+ private array $events_tree_label_visibility;
+
+ /**
+ * Creates a new EventsStateManager
+ * @param array $events_state, events state posted from frontend
+ * @return void
+ */
+ private function __construct(array $events_state)
+ {
+ $this->events_state = $events_state;
+ $this->events_tree = [];
+ $this->events_tree_label_visibility = [];
+
+ foreach($events_state as $eventHelioGroupName => $eventHelioGroupState) { // CCMC or HEK state
+
+ // If we don't have visible markers for CCMC or HEK then no need to handle them
+ if (array_key_exists('markers_visible', $eventHelioGroupState) && $eventHelioGroupState['markers_visible'] != true) {
+ continue;
+ }
+
+
+ foreach($eventHelioGroupState['layers'] as $eventHelioGroupLayer) {
+
+ $layer_event_type = $eventHelioGroupLayer['event_type'];
+
+ if (!array_key_exists($layer_event_type, $this->events_tree)) {
+ $this->events_tree[$layer_event_type] = [];
+ $this->events_tree_label_visibility[$layer_event_type] = $eventHelioGroupState['labels_visible'];
+ }
+
+ // This damn all fix
+ if (in_array("all",$eventHelioGroupLayer['frms'])) {
+ $this->events_tree[$layer_event_type] = "all_frms";
+ } else {
+
+ foreach($eventHelioGroupLayer['frms'] as $eventLayerFrm) {
+ if (!array_key_exists($eventLayerFrm, $this->events_tree[$layer_event_type])) {
+ $this->events_tree[$layer_event_type][$eventLayerFrm] = 'all_event_instances';
+ }
+ }
+
+ foreach($eventHelioGroupLayer['event_instances'] as $eventLayerEventInstance) {
+
+ $event_instance_frm_pieces = explode('--',$eventLayerEventInstance);
+ $event_instance_frm = $event_instance_frm_pieces[1];
+
+ // if we have frms all included like "frm1" and in event instance "flare--frm1--event1"
+ // we just ignore those since they are all included into the tree with frm1 anyways
+ // this is also indicates, eventsState is invalid somehow
+ if (in_array($event_instance_frm, $eventHelioGroupLayer['frms'])) {
+ continue;
+ }
+
+ if (!array_key_exists($event_instance_frm, $this->events_tree[$layer_event_type])) {
+ $this->events_tree[$layer_event_type][$event_instance_frm] = [];
+ }
+
+ $this->events_tree[$layer_event_type][$event_instance_frm][] = $eventLayerEventInstance;
+ }
+ }
+
+ }
+ }
+
+ }
+
+ /**
+ * Creates a new EventsStateManager from events_state
+ * @param array $events_state, events state posted from frontend
+ * @return EventsStateManager
+ */
+ public static function buildFromEventsState(array $events_state) : EventsStateManager
+ {
+ return new self($events_state);
+ }
+
+ /**
+ * Creates a new EventsStateManager from events_state
+ * @param array $events_state, events state posted from frontend
+ * @return EventsStateManager
+ */
+ public static function buildFromLegacyEventStrings(string $events_state_string, bool $events_label) : EventsStateManager
+ {
+ $events_layers = [];
+
+ // Prevent possible bugs
+ $events_state_string = trim($events_state_string);
+
+ // this is one of the bloody cases
+ if(!empty($events_state_string)) {
+ $event_strings = explode("],[", trim(stripslashes($events_state_string), ']['));
+
+ // Process individual events in string
+ foreach ($event_strings as $es) {
+
+ $event_pieces = explode(",", $es);
+
+ // just don't take risks in this environment
+ // there should be 3 element
+ if (count($event_pieces) < 3) {
+ continue;
+ }
+
+ list($event_type, $combined_frms, $visible) = $event_pieces;
+
+ $frms = explode(";", $combined_frms);
+ if (!empty($combined_frms) && !empty($frms)) {
+ // if frms not empty
+ if (!empty($frms)) {
+ // if this event type defined earlier
+ if(array_key_exists($event_type, $events_layers)) {
+ $event_layers[$event_type]['frms'] = array_unique(array_merge($events_layers[$event_type]['frms'], $frms));
+ } else {
+ $events_layers[$event_type] = [
+ 'event_type' => $event_type,
+ 'frms' => $frms,
+ 'event_instances' => [],
+ 'open' => true, // do not know this fields function better to keep it
+ ];
+ }
+ }
+ }
+ }
+ }
+
+ $events_state = [
+ 'tree_HEK' => [
+ 'id' => 'HEK',
+ 'markers_visible' => true,
+ 'labels_visible' => $events_label,
+ 'layers' => array_values($events_layers),
+ ]
+ ];
+
+ return new self($events_state);
+ }
+
+
+ /**
+ * Export events state to stream into database tables
+ * @return string
+ */
+ public function export() : string
+ {
+ return json_encode($this->events_state);
+ }
+
+ /**
+ * Tells if there is events in this manager
+ * @return bool
+ */
+ public function hasEvents() : bool
+ {
+ return count($this->events_tree) > 0;
+ }
+
+ /**
+ * Lets you to access to events_state
+ * @return array
+ */
+ public function getState(): array
+ {
+ return $this->events_state;
+ }
+
+ /**
+ * Lets you to access to events_tree
+ * @return array
+ */
+ public function getStateTree(): array
+ {
+ return $this->events_tree;
+ }
+
+ /**
+ * Lets you to access to events_tree_label_visibility
+ * @return array
+ */
+ public function getStateTreeLabelVisibility(): array
+ {
+ return $this->events_tree_label_visibility;
+ }
+
+ /**
+ * Checks if this event_category has events in this state
+ * @param string event_category_pin , given event_type
+ * @return bool
+ */
+ public function hasEventsForEventType(string $event_category_pin): bool
+ {
+ return array_key_exists($event_category_pin, $this->events_tree);
+ }
+
+ /**
+ * Checks if this event state allows all events for this event_type
+ * @param string event_category_pin , given event_type
+ * @return bool
+ */
+ public function appliesAllEventsForEventType(string $event_category_pin): bool
+ {
+ return $this->hasEventsForEventType($event_category_pin) && $this->events_tree[$event_category_pin] == "all_frms";
+ }
+
+ /**
+ * Checks if this event_category and frm_name has events in this state
+ * @param string event_category_pin , given event_type
+ * @param string frm_name , given frm_name
+ * @return bool
+ */
+ public function appliesFrmForEventType(string $event_category_pin, string $frm_name): bool
+ {
+ // We keep IDs with underscores to reduce bugs
+ $frm_underscored_name = str_replace(' ', '_', $frm_name);
+
+ // Check if we want the events for this group
+ return in_array($frm_underscored_name, array_keys($this->events_tree[$event_category_pin]));
+ }
+
+ /**
+ * Checks if this event state allows all events for frm of this event_type
+ * @param string event_category_pin , given event_type
+ * @param string frm_name , given frm_name
+ * @return bool
+ */
+ public function appliesAllEventInstancesForFrm(string $event_category_pin, string $frm_name): bool
+ {
+ // We keep IDs with underscores to reduce bugs
+ $frm_underscored_name = str_replace(' ', '_', $frm_name);
+
+ // All event instances works for this frm
+ $all_event_instances_work_for_frm = $this->events_tree[$event_category_pin][$frm_underscored_name] == "all_event_instances";
+
+ return $this->appliesFrmForEventType($event_category_pin, $frm_name) && $all_event_instances_work_for_frm;
+ }
+
+
+ /**
+ * Checks if this event state allows this particular event_instance
+ * @param string event_category_pin , given event_type
+ * @param string frm_name , given frm_name
+ * @param array event , given event to check
+ * @return bool
+ */
+ public function appliesEventInstance(string $event_category_pin, string $frm_name, array $event): bool
+ {
+ $event_instance_id = self::makeEventId($event_category_pin, $frm_name, $event);
+
+ // We keep IDs with underscores to reduce bugs
+ $frm_underscored_name = str_replace(' ', '_', $frm_name);
+
+ return in_array($event_instance_id, $this->events_tree[$event_category_pin][$frm_underscored_name]);
+ }
+
+ /**
+ * Checks if this event state allows events labels are visiblle for given event_category
+ * @param string event_category_pin , given event_type
+ * @return bool
+ */
+ public function isEventTypeLabelVisible(string $event_category_pin): bool
+ {
+ $is_defined_visiblity = array_key_exists($event_category_pin, $this->events_tree_label_visibility);
+ return $is_defined_visiblity && $this->events_tree_label_visibility[$event_category_pin];
+ }
+
+
+ /**
+ * Makes event id from given event and its belonging event_type and frm_name
+ * @param string event_category_pin , given event_type
+ * @param string frm_name , given frm_name
+ * @param array event , given event to check
+ * @return string
+ */
+ public static function makeEventId(string $event_category_pin, string $frm_name, array $event): string
+ {
+ $event_id_pieces = [
+ $event_category_pin,
+ $frm_name,
+ base64_encode($event['id']),
+ ];
+
+ $cleaned_event_id_pieces = array_map(function($p) {
+ return str_replace([' ','=','+','.','(',')'], ['_','_','\+','\.','\(','\)'], $p);
+ }, $event_id_pieces);
+
+ return join('--', $cleaned_event_id_pieces);
+ }
+
+}
+
+?>
diff --git a/src/Event/HEKEventNormalizer.php b/src/Event/HEKEventNormalizer.php
index 26ff49fb3..c8c0d2730 100644
--- a/src/Event/HEKEventNormalizer.php
+++ b/src/Event/HEKEventNormalizer.php
@@ -13,11 +13,25 @@ static public function Normalize(array &$event_types, array &$events): array {
// Normalizes event FRMs into the main data container.
// FRMs make up everything in the Helioviewer Event Format except for the data itself.
$event_container = self::NormalizeFRMs($event_types);
+
+ // Count if any label in our events is more than once
+ $all_event_labels = array_map(fn($e): string => self::CreateEventLabel($e), $events);
+ $all_event_labels_counts = array_count_values($all_event_labels);
+ $dublicate_labels = array_filter($all_event_labels_counts, fn($v, $k): bool => $v > 1, ARRAY_FILTER_USE_BOTH);
+ $dublicate_labels = array_keys($dublicate_labels);
+
foreach ($events as &$event) {
// Operates in-place to assign eve
$event['hv_hpc_x'] = $event['hv_hpc_x_final'];
$event['hv_hpc_y'] = $event['hv_hpc_y_final'];
+
$event['label'] = self::CreateEventLabel($event);
+
+ if(in_array($event['label'], $dublicate_labels)) {
+ $event['label'] = self::CreateEventLabel($event) . " ". number_format($event['event_coord1'],2).",".number_format($event['event_coord2'],2);
+ $event['short_label'] = self::CreateEventLabel($event) ." ". number_format($event['event_coord1'],2).",".number_format($event['event_coord2'],2);
+ }
+
$event['version'] = $event['frm_specificid'];
$event['id'] = $event['kb_archivid'];
$event['type'] = $event['event_type'];
@@ -25,6 +39,7 @@ static public function Normalize(array &$event_types, array &$events): array {
$event['end'] = $event['event_endtime'];
self::AssignEventToFRM($event_container, $event);
}
+
return $event_container;
}
diff --git a/src/Image/Composite/HelioviewerCompositeImage.php b/src/Image/Composite/HelioviewerCompositeImage.php
index a9dceedbe..d2ebe304b 100644
--- a/src/Image/Composite/HelioviewerCompositeImage.php
+++ b/src/Image/Composite/HelioviewerCompositeImage.php
@@ -35,8 +35,7 @@ class Image_Composite_HelioviewerCompositeImage {
protected $height;
protected $interlace;
protected $layers;
- protected $events;
- protected $eventsLabels;
+ protected $eventsManager;
protected $movieIcons;
protected $scale;
protected $scaleType;
@@ -75,8 +74,7 @@ class Image_Composite_HelioviewerCompositeImage {
*
* @return void
*/
- public function __construct($layers, $events, $eventLabels, $movieIcons, $celestialBodies, $scale,
- $scaleType, $scaleX, $scaleY, $obsDate, $roi, $options) {
+ public function __construct($layers, $eventsManager, $movieIcons, $celestialBodies, $scale, $scaleType, $scaleX, $scaleY, $obsDate, $roi, $options) {
set_time_limit(90); // Extend time limit to avoid timeouts
@@ -107,8 +105,7 @@ public function __construct($layers, $events, $eventLabels, $movieIcons, $celest
$this->db = $options['database'] ? $options['database'] : new Database_ImgIndex();
$this->layers = $layers;
- $this->events = $events;
- $this->eventsLabels = $eventLabels;
+ $this->eventsManager = $eventsManager;
$this->movieIcons = $movieIcons;
$this->scale = $scale;
$this->scaleType = $scaleType;
@@ -202,7 +199,8 @@ private function _buildCompositeImageLayers() {
*
* @return object A HelioviewerImage instance (e.g. AIAImage or LASCOImage)
*/
- private function _buildImageLayer($layer) {
+ private function _buildImageLayer($layer)
+ {
$image = $this->db->getClosestData($this->date, $layer['sourceId']);
// Instantiate a JP2Image
@@ -418,11 +416,9 @@ private function _buildCompositeImage() {
// For single layer images the composite image is simply the first
// image layer
$image = $this->_imageLayers[0]->getIMagickImage();
- }
-
- if ( count($this->events) > 0 &&
- $this->date != '2999-01-01T00:00:00.000Z') {
+ }
+ if ( $this->eventsManager->hasEvents() && $this->date != '2999-01-01T00:00:00.000Z') {
$this->_addEventLayer($image);
}
@@ -579,6 +575,7 @@ private function _compressImage($imagickImage) {
* @return void
*/
private function _addEventLayer($imagickImage) {
+
if ( $this->width < 200 || $this->height < 200 ) {
return;
}
@@ -599,30 +596,74 @@ private function _addEventLayer($imagickImage) {
$event_categories = array_merge($event_categories, Helper_EventInterface::GetEvents($startDate, $length, $observationTime));
// Lay down all relevant event REGIONS first
- $allowedFRMs = $this->events->toArray();
+
$events_to_render = [];
- foreach($event_categories as $event_category) {
- foreach( $allowedFRMs as $j => $frm ) {
- // Match the 2 letter abbreviation to all the event groups.
- if ($event_category['pin'] == $frm['event_type']) {
- // Find the specific FRM within the group
- foreach ($event_category['groups'] as $group) {
- // Match the group name to the frm name
- $underscored_name = str_replace(' ', '_', $group['name']);
- if ($frm['frm_name'] == 'all' ||
- strpos($frm['frm_name'], $underscored_name) !== false) {
- // This group of events was selected to show up in the image.
- // Merge them into the final list of events.
- foreach ($group['data'] as &$event) {
- $event['concept'] = $event_category['name'];
+ $events_manager = $this->eventsManager;
+ $add_label_visibility_and_concept = function($events_data, $event_cat_pin, $event_group_name) use ($events_manager) {
+ return array_map(function($ed) use ($events_manager, $event_cat_pin, $event_group_name) {
+ $ed['concept'] = $event_group_name;
+ $ed['label_visibility'] = $events_manager->isEventTypeLabelVisible($event_cat_pin) ? true : false;
+ return $ed;
+ }, $events_data);
+ };
+
+
+ foreach($event_categories as $event_cat) {
+
+ $event_cat_pin = $event_cat['pin'];
+
+ // if we dont have any configuration for this event_type
+ if (!$this->eventsManager->hasEventsForEventType($event_cat_pin)) {
+ continue;
+ }
+
+ // Are we going to go for all children of this event type
+ if ($this->eventsManager->appliesAllEventsForEventType($event_cat_pin)) {
+
+ foreach($event_cat['groups'] as $ecg) {
+ $events_to_render = array_merge(
+ $events_to_render,
+ $add_label_visibility_and_concept($ecg['data'], $event_cat_pin, $ecg['name'])
+ );
+ }
+
+ continue;
+
+ }
+
+ // Check each group now
+ foreach($event_cat['groups'] as $event_cat_group) {
+
+ // Applies for event type
+ if($this->eventsManager->appliesFrmForEventType($event_cat_pin, $event_cat_group['name'])) {
+
+ // applies all events for this group
+ if($this->eventsManager->appliesAllEventInstancesForFrm($event_cat_pin, $event_cat_group['name'])) {
+ $events_to_render = array_merge(
+ $events_to_render,
+ $add_label_visibility_and_concept($event_cat_group['data'], $event_cat_pin, $event_cat_group['name'])
+ );
+ } else {
+
+ // applies some events for this group
+ $events_filtered_for_event_instances = [];
+
+ foreach($event_cat_group['data'] as $ev) {
+
+ if ($this->eventsManager->appliesEventInstance($event_cat_pin, $event_cat_group['name'], $ev)) {
+ $events_filtered_for_event_instances[] = $ev;
}
- // The variable $event is re-used later so it must be unset so that it is not left pointing to the last member of the group array.
- // See the warning here https://www.php.net/manual/en/control-structures.foreach.php
- unset($event);
- $events_to_render = array_merge($events_to_render, $group['data']);
+
}
+
+ $events_to_render = array_merge(
+ $events_to_render,
+ $add_label_visibility_and_concept($events_filtered_for_event_instances, $event_cat_pin, $event_cat_group['name'])
+ );
+
}
}
+
}
}
@@ -637,16 +678,15 @@ private function _addEventLayer($imagickImage) {
if ( $width >= 1 && $height >= 1 ) {
- $region_polygon = new IMagick(
- HV_ROOT_DIR.'/'.urldecode($event['hv_poly_url']) );
+ $region_polygon = new IMagick(HV_ROOT_DIR.'/'.urldecode($event['hv_poly_url']) );
$x = (( $event['hv_poly_hpc_x_final']
- $this->roi->left()) / $this->roi->imageScale());
$y = (( $event['hv_poly_hpc_y_final']
- $this->roi->top() ) / $this->roi->imageScale());
- $x = $x - $this->_timeOffsetX;
- $y = $y - $this->_timeOffsetY;
+ $x = $x - $this->_timeOffsetX;
+ $y = $y - $this->_timeOffsetY;
$region_polygon->resizeImage(
$width, $height, Imagick::FILTER_LANCZOS,1);
@@ -688,8 +728,7 @@ private function _addEventLayer($imagickImage) {
$y = $y - $this->_timeOffsetY;
$imagickImage->compositeImage($marker, IMagick::COMPOSITE_DISSOLVE, $x - $markerPinPixelOffsetX, $y - $markerPinPixelOffsetY);
-
- if ( $this->eventsLabels == true ) {
+ if ($event['label_visibility']) {
$x = $x + 11;
$y = $y - 24;
@@ -1562,8 +1601,7 @@ public function build($filepath) {
public function display() {
$fileinfo = new finfo(FILEINFO_MIME);
$mimetype = $fileinfo->file($this->_filepath);
- header("Content-Disposition: inline; filename=\"" .
- $this->_filename . "\"");
+ header("Content-Disposition: inline; filename=\"". $this->_filename ."\"");
header('Content-type: ' . $mimetype);
$this->_composite->setImageFormat('png32');
echo $this->_composite;
diff --git a/src/Image/Composite/HelioviewerMovieFrame.php b/src/Image/Composite/HelioviewerMovieFrame.php
index 5e3724376..3a680565e 100644
--- a/src/Image/Composite/HelioviewerMovieFrame.php
+++ b/src/Image/Composite/HelioviewerMovieFrame.php
@@ -13,19 +13,16 @@
*/
require_once HV_ROOT_DIR.'/../src/Image/Composite/HelioviewerCompositeImage.php';
-class Image_Composite_HelioviewerMovieFrame
- extends Image_Composite_HelioviewerCompositeImage {
+class Image_Composite_HelioviewerMovieFrame extends Image_Composite_HelioviewerCompositeImage {
/**
* Helioviewer movie frame
*/
- public function __construct($filepath, $layers, $events, $eventsLabels, $movieIcons, $celestialBodies,
- $scale, $scaleType, $scaleX, $scaleY, $obsDate, $roi, $options) {
-
- parent::__construct($layers, $events, $eventsLabels, $movieIcons, $celestialBodies,
- $scale, $scaleType, $scaleX, $scaleY, $obsDate, $roi, $options);
+ public function __construct($filepath, $layers, $eventsManager, $movieIcons, $celestialBodies, $scale, $scaleType, $scaleX, $scaleY, $obsDate, $roi, $options)
+ {
+ parent::__construct($layers, $eventsManager, $movieIcons, $celestialBodies, $scale, $scaleType, $scaleX, $scaleY, $obsDate, $roi, $options);
$this->build($filepath);
}
}
-?>
\ No newline at end of file
+?>
diff --git a/src/Image/Composite/HelioviewerScreenshot.php b/src/Image/Composite/HelioviewerScreenshot.php
index c1921f051..f26019c62 100644
--- a/src/Image/Composite/HelioviewerScreenshot.php
+++ b/src/Image/Composite/HelioviewerScreenshot.php
@@ -13,8 +13,8 @@
*/
require_once HV_ROOT_DIR.'/../src/Image/Composite/HelioviewerCompositeImage.php';
-class Image_Composite_HelioviewerScreenshot
- extends Image_Composite_HelioviewerCompositeImage {
+class Image_Composite_HelioviewerScreenshot extends Image_Composite_HelioviewerCompositeImage
+{
public $id;
public $timestamp;
@@ -22,23 +22,23 @@ class Image_Composite_HelioviewerScreenshot
/**
* Creates a new screenshot
*/
- public function __construct($layers, $events, $eventLabels, $movieIcons, $celestialBodies, $scale,
- $scaleType, $scaleX, $scaleY, $obsDate, $roi, $options) {
+ public function __construct($layers, $eventsManager, $movieIcons, $celestialBodies, $scale, $scaleType, $scaleX, $scaleY, $obsDate, $roi, $options) {
- parent::__construct($layers, $events, $eventLabels, $movieIcons, $celestialBodies, $scale,
- $scaleType, $scaleX, $scaleY, $obsDate, $roi, $options);
+ parent::__construct($layers, $eventsManager, $movieIcons, $celestialBodies, $scale, $scaleType, $scaleX, $scaleY, $obsDate, $roi, $options);
- if ( array_key_exists('action', $options) &&
- $options['action'] == 'downloadScreenshot' ) {
+ if ( array_key_exists('action', $options) && $options['action'] == 'downloadScreenshot' ) {
$this->id = $options['id'];
$this->timestamp = $options['timestamp'];
$this->date = $options['observationDate'];
- }
- else {
+
+ } else {
+
$this->id = $this->_getScreenshotId();
$this->timestamp = date('Y-m-d');
+
}
+
$this->build($this->_buildFilepath());
//TODO: Either include a status field in db, or remove entry if
@@ -57,12 +57,7 @@ private function _buildFilepath() {
HV_CACHE_DIR,
date('Y/m/d', $created->getTimestamp()),
$this->id,
- substr(
- str_replace(
- array(':', '-', 'T', 'Z', ' '),
- '_',
- $this->date),
- 0, 19),
+ substr(str_replace(array(':', '-', 'T', 'Z', ' '), '_', $this->date), 0, 19),
$this->layers->toString()
);
}
@@ -72,7 +67,8 @@ private function _buildFilepath() {
*
* @return int Screenshot id
*/
- private function _getScreenshotId() {
+ private function _getScreenshotId()
+ {
return $this->db->insertScreenshot(
$this->date,
$this->imageScale,
@@ -80,8 +76,7 @@ private function _getScreenshotId() {
$this->watermark,
$this->layers->serialize(),
$this->layers->getBitMask(),
- $this->events->serialize(),
- $this->eventsLabels,
+ $this->eventsManager->export(),
$this->movieIcons,
$this->scale,
$this->scaleType,
@@ -94,4 +89,4 @@ private function _getScreenshotId() {
);
}
}
-?>
\ No newline at end of file
+?>
diff --git a/src/Module/Movies.php b/src/Module/Movies.php
index 14c8932e9..3bfacc95f 100644
--- a/src/Module/Movies.php
+++ b/src/Module/Movies.php
@@ -1,5 +1,4 @@
validate()) {
try {
$this->{$this->_params['action']}();
- }
- catch (Exception $e) {
+ } catch (Exception $e) {
handleError($e->getMessage(), $e->getCode());
}
}
}
+
/**
* Queues a request for a Helioviewer.org movie
+ * Does it with HTTP POST request
*/
- public function queueMovie() {
+ public function postMovie() {
+
include_once HV_ROOT_DIR.'/../lib/alphaID/alphaID.php';
include_once HV_ROOT_DIR.'/../lib/Resque.php';
include_once HV_ROOT_DIR.'/../lib/Redisent/Redisent.php';
@@ -59,6 +62,181 @@ public function queueMovie() {
include_once HV_ROOT_DIR.'/../src/Database/MovieDatabase.php';
include_once HV_ROOT_DIR.'/../src/Database/ImgIndex.php';
+ $json_params = $this->_params['json'];
+ // Connect to redis
+ $redis = new Redisent(HV_REDIS_HOST, HV_REDIS_PORT);
+
+ // If the queue is currently full, don't process the request
+ $queueSize = Resque::size(HV_MOVIE_QUEUE);
+ if ( $queueSize >= MOVIE_QUEUE_MAX_SIZE ) {
+ throw new Exception(
+ 'Sorry, due to current high demand, we are currently unable ' .
+ 'to process your request. Please try again later.', 40);
+ }
+
+ // Get current number of HV_MOVIE_QUEUE workers
+ $workers = Resque::redis()->smembers('workers');
+ $movieWorkers = array_filter($workers, function ($elem) {
+ return strpos($elem, HV_MOVIE_QUEUE) !== false;
+ });
+
+ if( isset($json_params['celestialBodiesLabels']) && isset($json_params['celestialBodiesTrajectories']) ){
+ $celestialBodies = array( "labels" => $json_params['celestialBodiesLabels'],
+ "trajectories" => $json_params['celestialBodiesTrajectories']);
+ }else{
+ $celestialBodies = array( "labels" => "",
+ "trajectories" => "");
+ }
+
+ // Default options
+ $defaults = array(
+ "format" => 'mp4',
+ "frameRate" => null,
+ "movieLength" => null,
+ "maxFrames" => HV_MAX_MOVIE_FRAMES,
+ "watermark" => true,
+ "scale" => false,
+ "scaleType" => 'earth',
+ "scaleX" => 0,
+ "scaleY" => 0,
+ "size" => 0,
+ "movieIcons" => 0,
+ "followViewport" => 0,
+ "reqStartTime" => $json_params['startTime'],
+ "reqEndTime" => $json_params['endTime'],
+ "reqObservationDate" => null,
+ "switchSources" => false,
+ "celestialBodies" => $celestialBodies
+ );
+
+ $options = array_replace($defaults, $json_params);
+
+ // Default to 15fps if no frame-rate or length was specified
+ if ( is_null($options['frameRate']) && is_null($options['movieLength'])) {
+ $options['frameRate'] = 15;
+ }
+
+ // Limit movies to three layers
+ $layers = new Helper_HelioviewerLayers($json_params['layers']);
+ if ( $layers->length() < 1 || $layers->length() > 3 ) {
+ throw new Exception('Invalid layer choices! You must specify 1-3 comma-separated layer names.', 22);
+ }
+
+ $events_manager = EventsStateManager::buildFromEventsState($json_params['eventsState']);
+
+ // TODO 2012/04/11
+ // Discard any layers which do not share an overlap with the roi to
+ // avoid generating kdu_expand errors later. Check is performed already
+ // on front-end, but should also be done before queuing a request.
+
+ // Determine the ROI
+ $roi = $this->_getMovieROI($options, $json_params);
+ $roiString = $roi->getPolygonString();
+
+ $numPixels = $roi->getPixelWidth() * $roi->getPixelHeight();
+
+ // Use reduce image scale if necessary
+ $imageScale = $roi->imageScale();
+
+ // Max number of frames
+ $maxFrames = min($this->_getMaxFrames($queueSize), $options['maxFrames']);
+
+ // Create a connection to the database
+ $db = new Database_ImgIndex();
+ $movieDb = new Database_MovieDatabase();
+
+ // Estimate the number of frames
+ $numFrames = $this->_estimateNumFrames($db, $layers, $json_params['startTime'], $json_params['endTime']);
+ $numFrames = min($numFrames, $maxFrames);
+
+ // Estimate the time to create movie frames
+ // @TODO 06/2012: Factor in total number of workers and number of
+ // workers that are currently available?
+ $estBuildTime = $this->_estimateMovieBuildTime($movieDb, $numFrames, $numPixels, $options['format']);
+
+ // If all workers are in use, increment and use estimated wait counter
+ if ( $queueSize +1 >= sizeOf($movieWorkers) ) {
+ $eta = $redis->incrby('helioviewer:movie_queue_wait', $estBuildTime);
+ $updateCounter = true;
+ }
+ else {
+ // Otherwise simply use the time estimated for the single movie
+ $eta = $estBuildTime;
+ $updateCounter = false;
+ }
+
+ // Get datasource bitmask
+ $bitmask = bindec($layers->getBitMask());
+
+ // Create entry in the movies table in MySQL
+ $dbId = $movieDb->insertMovie(
+ $json_params['startTime'],
+ $json_params['endTime'],
+ (isset($json_params['reqObservationDate']) ? $json_params['reqObservationDate'] : false),
+ $imageScale,
+ $roiString,
+ $maxFrames,
+ $options['watermark'],
+ $json_params['layers'],
+ $bitmask,
+ $events_manager->export(),
+ (isset($json_params['movieIcons']) ? $json_params['movieIcons'] : false),
+ (isset($json_params['followViewport']) ? $json_params['followViewport'] : false),
+ $options['scale'],
+ $options['scaleType'],
+ $options['scaleX'],
+ $options['scaleY'],
+ $layers->length(),
+ $queueSize,
+ $options['frameRate'],
+ $options['movieLength'],
+ $options['size'],
+ $options['switchSources'],
+ $options['celestialBodies']
+ );
+
+ // Convert id
+ $publicId = alphaID($dbId, false, 5, HV_MOVIE_ID_PASS);
+
+ // Queue movie request
+ $args = array(
+ 'movieId' => $publicId,
+ 'eta' => $estBuildTime,
+ 'format' => $options['format'],
+ 'counter' => $updateCounter
+ );
+
+ // Create entries for each version of the movie in the movieFormats
+ // table
+ foreach(array('mp4', 'webm') as $format) {
+ $movieDb->insertMovieFormat($dbId, $format);
+ }
+
+ $token = Resque::enqueue(HV_MOVIE_QUEUE, 'Job_MovieBuilder', $args, true);
+
+ // Print response
+ $response = array(
+ 'id' => $publicId,
+ 'eta' => $eta,
+ 'queue' => max(0, $queueSize + 1 - sizeOf($movieWorkers)),
+ 'token' => $token
+ );
+
+ $this->_printJSON(json_encode($response));
+ }
+
+ /**
+ * Queues a request for a Helioviewer.org movie
+ */
+ public function queueMovie() {
+
+ include_once HV_ROOT_DIR.'/../lib/alphaID/alphaID.php';
+ include_once HV_ROOT_DIR.'/../lib/Resque.php';
+ include_once HV_ROOT_DIR.'/../lib/Redisent/Redisent.php';
+ include_once HV_ROOT_DIR.'/../src/Helper/HelioviewerLayers.php';
+ include_once HV_ROOT_DIR.'/../src/Database/MovieDatabase.php';
+ include_once HV_ROOT_DIR.'/../src/Database/ImgIndex.php';
+
// Connect to redis
$redis = new Redisent(HV_REDIS_HOST, HV_REDIS_PORT);
@@ -114,12 +292,26 @@ public function queueMovie() {
// Limit movies to three layers
$layers = new Helper_HelioviewerLayers($this->_params['layers']);
if ( $layers->length() < 1 || $layers->length() > 3 ) {
- throw new Exception(
- 'Invalid layer choices! You must specify 1-3 comma-separated '.
- 'layer names.', 22);
+ throw new Exception('Invalid layer choices! You must specify 1-3 comma-separated layer names.', 22);
+ }
+
+ // Event legacy string
+ $events_legacy_string = "";
+ if ( array_key_exists('events', $this->_params) ) {
+ $events_legacy_string = $this->_params['events'];
+ }
+
+ // Event legacy labels switch
+ $event_labels = false;
+ if ( array_key_exists('eventLabels', $this->_params) ) {
+ $event_labels = (bool)$this->_params['eventLabels'];
}
- $events = new Helper_HelioviewerEvents($this->_params['events']);
+ // ATTENTION! These two fields eventsLabels and eventSourceString needs to be kept in DB schema
+ // We are keeping them to support old takeScreenshot , queueMovie requests
+
+ // Events manager built from old logic
+ $events_manager = EventsStateManager::buildFromLegacyEventStrings($events_legacy_string, $event_labels);
// TODO 2012/04/11
// Discard any layers which do not share an overlap with the roi to
@@ -127,7 +319,7 @@ public function queueMovie() {
// on front-end, but should also be done before queuing a request.
// Determine the ROI
- $roi = $this->_getMovieROI($options);
+ $roi = $this->_getMovieROI($options, $this->_params);
$roiString = $roi->getPolygonString();
$numPixels = $roi->getPixelWidth() * $roi->getPixelHeight();
@@ -176,8 +368,7 @@ public function queueMovie() {
$options['watermark'],
$this->_params['layers'],
$bitmask,
- $this->_params['events'],
- $this->_params['eventsLabels'],
+ $events_manager->export(),
(isset($this->_params['movieIcons']) ? $this->_params['movieIcons'] : false),
(isset($this->_params['followViewport']) ? $this->_params['followViewport'] : false),
$options['scale'],
@@ -231,7 +422,6 @@ public function reQueueMovie($silent=false) {
include_once HV_ROOT_DIR.'/../lib/Resque.php';
include_once HV_ROOT_DIR.'/../lib/Redisent/Redisent.php';
include_once HV_ROOT_DIR.'/../src/Helper/HelioviewerLayers.php';
- include_once HV_ROOT_DIR.'/../src/Helper/HelioviewerEvents.php';
include_once HV_ROOT_DIR.'/../src/Database/MovieDatabase.php';
include_once HV_ROOT_DIR.'/../src/Database/ImgIndex.php';
include_once HV_ROOT_DIR.'/../src/Movie/HelioviewerMovie.php';
@@ -270,8 +460,8 @@ public function reQueueMovie($silent=false) {
// Check if movie exists on disk before re-queueing
if ( $options['force'] === false ) {
- $helioviewerMovie = new Movie_HelioviewerMovie(
- $this->_params['id'], $options['format']);
+ $helioviewerMovie = new Movie_HelioviewerMovie($this->_params['id'], $options['format']);
+
$filepath = $helioviewerMovie->getFilepath();
$path_parts = pathinfo($filepath);
@@ -290,6 +480,11 @@ public function reQueueMovie($silent=false) {
$movieDatabase = new Database_MovieDatabase();
$movie = $movieDatabase->getMovieMetadata($movieId);
+ // Movie not found
+ if(!$movie) {
+ return $this->_sendResponse(404, 'NOT FOUND', 'Movie could not be found');
+ }
+
// Check if movie is already in the queue (status=0)
// or is already being processed (status=1) before re-queueing.
@@ -304,11 +499,9 @@ public function reQueueMovie($silent=false) {
foreach ( $movieFormats as $movieFormat ) {
if ($movieFormat['status'] == 0) {
throw new Exception('Movie is already in queue', 47);
- return;
}
if ( $movieFormat['status'] == 1) {
throw new Exception('Movie is currently being processed', 47);
- return;
}
}
@@ -529,7 +722,8 @@ private function _getDefaultROIOptions($scale) {
* Returns the region of interest for the movie request or throws an error
* if one was not properly specified.
*/
- private function _getMovieROI($options) {
+ private function _getMovieROI($options, $params) {
+
include_once HV_ROOT_DIR.'/../src/Helper/RegionOfInterest.php';
// Region of interest (center in arcseconds and dimensions in pixels)
@@ -539,7 +733,7 @@ private function _getMovieROI($options) {
!isset($options['x2']) && !isset($options['y2']) &&
!isset($options['x0']) && !isset($options['y0']) &&
!isset($options['width']) && !isset($options['height']) ) {
- $defaultOptions = $this->_getDefaultROIOptions($this->_params['imageScale']);
+ $defaultOptions = $this->_getDefaultROIOptions($params['imageScale']);
$x1 = $defaultOptions['x1'];
$y1 = $defaultOptions['y1'];
$x2 = $defaultOptions['x2'];
@@ -559,14 +753,14 @@ private function _getMovieROI($options) {
// Region of interest (top-left and bottom-right coords in
// arcseconds)
$x1 = $options['x0'] - 0.5 * $options['width']
- * $this->_params['imageScale'];
+ * $params['imageScale'];
$y1 = $options['y0'] - 0.5 * $options['height']
- * $this->_params['imageScale'];
+ * $params['imageScale'];
$x2 = $options['x0'] + 0.5 * $options['width']
- * $this->_params['imageScale'];
+ * $params['imageScale'];
$y2 = $options['y0'] + 0.5 * $options['height']
- * $this->_params['imageScale'];
+ * $params['imageScale'];
}
else {
throw new Exception(
@@ -574,7 +768,7 @@ private function _getMovieROI($options) {
}
$roi = new Helper_RegionOfInterest($x1, $y1, $x2, $y2,
- $this->_params['imageScale']);
+ $params['imageScale']);
return $roi;
}
@@ -591,26 +785,21 @@ private function _getMovieROI($options) {
* use of prior actual movie generation times and will not require use of
* manually-selected system-dependent coefficients
*/
- private function _estimateNumFrames($db, $layers, $startTime, $endTime) {
+ private function _estimateNumFrames($db, $layers, $startTime, $endTime)
+ {
$numFrames = 0;
- $sql = 'SELECT COUNT(*) FROM images WHERE DATE BETWEEN "%s" ' .
- 'AND "%s" AND sourceId=%d;';
// Estimate number of movies frames for each layer
foreach ( $layers->toArray() as $layer ) {
- $numFrames += $db->getDataCount($startTime, $endTime,
- $layer['sourceId']);
+ $numFrames += $db->getDataCount($startTime, $endTime, $layer['sourceId']);
}
// Raise an error if few or no frames were found for the request range
// and data sources
if ($numFrames == 0) {
- throw new Exception('No images found for requested time range. '.
- 'Please try a different time.', 12);
- }
- else if ($numFrames <= 3) {
- throw new Exception('Insufficient data was found for the '.
- 'requested time range. Please try a different time.', 16);
+ throw new Exception('No images found for requested time range. Please try a different time.', 12);
+ } else if ($numFrames <= 3) {
+ throw new Exception('Insufficient data was found for the requested time range. Please try a different time.', 16);
}
return $numFrames;
@@ -1346,6 +1535,11 @@ public function validate() {
'ints' => array('maxFrames', 'width', 'height', 'size')
);
break;
+ case 'postMovie':
+ $expected = [
+ 'required' => ['json'],
+ ];
+ break;
case 'reQueueMovie':
$expected = array(
'required' => array('id'),
@@ -1405,5 +1599,25 @@ public function validate() {
return true;
}
+
+ /**
+ * Helper function to handle response code and response message with
+ * output result as either JSON or JSONP
+ *
+ * @param int $code HTTP response code to return
+ * @param string $message Message for the response code,
+ * @param mixed $data Data can be anything
+ *
+ * @return void
+ */
+ private function _sendResponse(int $code, string $message, mixed $data) : void
+ {
+ http_response_code($code);
+ $this->_printJSON(json_encode([
+ 'status_code' => $code,
+ 'status_txt' => $message,
+ 'data' => $data,
+ ]));
+ }
}
?>
diff --git a/src/Module/WebClient.php b/src/Module/WebClient.php
index c552e77c7..e3e541117 100644
--- a/src/Module/WebClient.php
+++ b/src/Module/WebClient.php
@@ -17,6 +17,8 @@
require_once HV_ROOT_DIR.'/../src/Validation/InputValidator.php';
require_once HV_ROOT_DIR.'/../src/Helper/ErrorHandler.php';
+use Helioviewer\Api\Event\EventsStateManager;
+
class Module_WebClient implements Module {
private $_params;
@@ -69,6 +71,10 @@ public function downloadScreenshot() {
$info = $imgIndex->getScreenshot($this->_params['id']);
+ if(!$info) {
+ return $this->_sendResponse(404, "NOT FOUND", "Screenshot not found");
+ }
+
$layers = new Helper_HelioviewerLayers($info['dataSourceString']);
$dir = sprintf('%s/screenshots/%s/%s/',
@@ -110,6 +116,31 @@ public function downloadScreenshot() {
echo file_get_contents($filepath);
}
+ /**
+ * Finds the closest image available for a given time and datasource
+ *
+ * @return JSON meta information for matching image
+ *
+ * TODO: Combine with getJP2Image? (e.g. "&display=true")
+ */
+ public function getClosestImageDatesForSources() {
+
+ include_once HV_ROOT_DIR.'/../src/Database/ImgIndex.php';
+
+ $imgIndex = new Database_ImgIndex();
+
+ $results = [];
+
+ foreach($this->_params['sources'] as $sid) {
+ $closestImages = $imgIndex->getClosestDataBeforeAndAfter($this->_params['date'], $sid);
+ $results[$sid]['prev_date'] = $closestImages['prev_date'];
+ $results[$sid]['next_date'] = $closestImages['next_date'];
+ }
+
+ // Print result
+ $this->_printJSON(json_encode($results));
+ }
+
/**
* Finds the closest image available for a given time and datasource
*
@@ -383,6 +414,88 @@ public function getTile() {
$tile->display();
}
+ /**
+ * Obtains layer information, ranges of pixels visible, and the date being
+ * looked at and creates a composite image (a Screenshot) of all the
+ * layers. Does it with HTTP POST request
+ *
+ * See the API webpage for example usage.
+ *
+ * @return image/jpeg or JSON
+ */
+ public function postScreenshot()
+ {
+ include_once HV_ROOT_DIR.'/../src/Image/Composite/HelioviewerScreenshot.php';
+ include_once HV_ROOT_DIR.'/../src/Helper/HelioviewerLayers.php';
+
+ $json_params = $this->_params['json'];
+
+ // Data Layers
+ $layers = new Helper_HelioviewerLayers($json_params['layers']);
+
+ // Event Labels
+ $movieIcons = false;
+ if ( array_key_exists('movieIcons', $json_params) ) {
+ $movieIcons = $json_params['movieIcons'];
+ }
+
+ // Scale
+ $scale = false;
+ $scaleType = 'earth';
+ $scaleX = 0;
+ $scaleY = 0;
+ if ( array_key_exists('scale', $json_params) ) {
+ $scale = (isset($json_params['scale']) ? $json_params['scale'] : $scale);
+ $scaleType = (isset($json_params['scaleType']) ? $json_params['scaleType'] : $scaleType);
+ $scaleX = (isset($json_params['scaleX']) ? $json_params['scaleX'] : $scaleX);
+ $scaleY = (isset($json_params['scaleY']) ? $json_params['scaleY'] : $scaleY);
+ }
+
+ // Region of interest
+ $roi = $this->_getRegionOfInterest($json_params, $json_params);
+
+ // Celestial Bodies
+ if( isset($json_params['celestialBodiesLabels']) && isset($json_params['celestialBodiesTrajectories']) ){
+
+ $celestialBodiesLabels = $json_params['celestialBodiesLabels'];
+ $celestialBodiesTrajectories = $json_params['celestialBodiesTrajectories'];
+ $celestialBodies = array(
+ 'labels' => $celestialBodiesLabels,
+ 'trajectories' => $celestialBodiesTrajectories
+ );
+
+ } else {
+
+ $celestialBodies = array( "labels" => "", "trajectories" => "");
+
+ }
+
+ $events_manager = EventsStateManager::buildFromEventsState($json_params['eventsState']);
+
+ // Create the screenshot
+ $screenshot = new Image_Composite_HelioviewerScreenshot(
+ $layers,
+ $events_manager,
+ $movieIcons,
+ $celestialBodies,
+ $scale,
+ $scaleType,
+ $scaleX,
+ $scaleY,
+ $json_params['date'],
+ $roi,
+ $json_params
+ );
+
+ // Display screenshot
+ if (isset($json_params['display']) && $json_params['display']) {
+ $screenshot->display();
+ } else {
+ // Print JSON
+ $this->_printJSON(json_encode(array('id' => $screenshot->id)));
+ }
+ }
+
/**
* Obtains layer information, ranges of pixels visible, and the date being
* looked at and creates a composite image (a Screenshot) of all the
@@ -398,24 +511,10 @@ public function getTile() {
public function takeScreenshot() {
include_once HV_ROOT_DIR.'/../src/Image/Composite/HelioviewerScreenshot.php';
include_once HV_ROOT_DIR.'/../src/Helper/HelioviewerLayers.php';
- include_once HV_ROOT_DIR.'/../src/Helper/HelioviewerEvents.php';
// Data Layers
$layers = new Helper_HelioviewerLayers($this->_params['layers']);
- // Event Layers
- $events = Array();
- if ( !array_key_exists('events', $this->_params) ) {
- $this->_params['events'] = '';
- }
- $events = new Helper_HelioviewerEvents($this->_params['events']);
-
- // Event Labels
- $eventLabels = false;
- if ( array_key_exists('eventLabels', $this->_params) ) {
- $eventLabels = $this->_params['eventLabels'];
- }
-
// Event Labels
$movieIcons = false;
if ( array_key_exists('movieIcons', $this->_params) ) {
@@ -435,7 +534,7 @@ public function takeScreenshot() {
}
// Region of interest
- $roi = $this->_getRegionOfInterest();
+ $roi = $this->_getRegionOfInterest($this->_options, $this->_params);
// Celestial Bodies
if( isset($this->_params['celestialBodiesLabels']) && isset($this->_params['celestialBodiesTrajectories']) ){
@@ -445,22 +544,51 @@ public function takeScreenshot() {
'labels' => $celestialBodiesLabels,
'trajectories' => $celestialBodiesTrajectories
);
- }else{
- $celestialBodies = array( "labels" => "",
- "trajectories" => "");
+ } else {
+ $celestialBodies = array(
+ "labels" => "",
+ "trajectories" => ""
+ );
}
+ // Event legacy string
+ $events_legacy_string = "";
+ if ( array_key_exists('events', $this->_params) ) {
+ $events_legacy_string = $this->_params['events'];
+ }
+
+ // Event legacy labels switch
+ $event_labels = false;
+ if ( array_key_exists('eventLabels', $this->_params) ) {
+ $event_labels = (bool)$this->_params['eventLabels'];
+ }
+
+
+ // ATTENTION! These two fields eventsLabels and eventSourceString needs to be kept in DB schema
+ // We are keeping them to support old takeScreenshot , queueMovie requests
+
+ // Events manager built from old logic
+ $events_manager = EventsStateManager::buildFromLegacyEventStrings($events_legacy_string, $event_labels);
+
// Create the screenshot
$screenshot = new Image_Composite_HelioviewerScreenshot(
- $layers, $events, $eventLabels, $movieIcons, $celestialBodies, $scale, $scaleType, $scaleX,
- $scaleY, $this->_params['date'], $roi, $this->_options
+ $layers,
+ $events_manager,
+ $movieIcons,
+ $celestialBodies,
+ $scale,
+ $scaleType,
+ $scaleX,
+ $scaleY,
+ $this->_params['date'],
+ $roi,
+ $this->_options
);
// Display screenshot
if (isset($this->_options['display']) && $this->_options['display']) {
$screenshot->display();
- }
- else {
+ } else {
// Print JSON
$this->_printJSON(json_encode(array('id' => $screenshot->id)));
}
@@ -476,7 +604,6 @@ public function reTakeScreenshot($screenshotId) {
include_once HV_ROOT_DIR.'/../src/Database/ImgIndex.php';
include_once HV_ROOT_DIR.'/../src/Image/Composite/HelioviewerScreenshot.php';
include_once HV_ROOT_DIR.'/../src/Helper/HelioviewerLayers.php';
- include_once HV_ROOT_DIR.'/../src/Helper/HelioviewerEvents.php';
include_once HV_ROOT_DIR.'/../src/Helper/RegionOfInterest.php';
// Default options
@@ -538,19 +665,35 @@ public function reTakeScreenshot($screenshotId) {
}
// Event Layers
- $events = new Helper_HelioviewerEvents(
- $metaData['eventSourceString']);
+ $events_state_from_metadata = json_decode($metaData['eventsState'], true);
+ $events_manager;
+
+ // ATTENTION! These two fields eventsLabels and eventSourceString needs to be kept in DB schema
+ // We are keeping them to support old takeScreenshot , queueMovie requests
+
+ if(!empty($events_state_from_metadata)) {
+ $events_manager = EventsStateManager::buildFromEventsState($events_state_from_metadata);
+ } else {
+ $events_manager = EventsStateManager::buildFromLegacyEventStrings($metaData['eventSourceString'], (bool)$metaData['eventsLabels']);
+ }
+
$celestialBodies = array( "labels" => $metaData['celestialBodiesLabels'],
"trajectories" => $metaData['celestialBodiesTrajectories']);
// Create the screenshot
$screenshot = new Image_Composite_HelioviewerScreenshot(
- $layers, $events, (bool)$metaData['eventsLabels'], (bool)$metaData['movieIcons'],
+ $layers,
+ $events_manager,
+ (bool)$metaData['movieIcons'],
$celestialBodies,
- (bool)$metaData['scale'], $metaData['scaleType'],
- $metaData['scaleX'], $metaData['scaleY'],
- $metaData['observationDate'], $roi, $options
+ (bool)$metaData['scale'],
+ $metaData['scaleType'],
+ $metaData['scaleX'],
+ $metaData['scaleY'],
+ $metaData['observationDate'],
+ $roi,
+ $options
);
}
@@ -623,7 +766,6 @@ public function saveWebClientState() {
$client_state = new ClientState();
try {
-
$state_key = $client_state->upsert($this->_params['json']);
return $this->_sendResponse(200, 'OK', $state_key);
@@ -743,17 +885,14 @@ public function getUsageStatistics() {
*/
public function getSciDataScript()
{
- if ( strtolower($this->_params['lang']) == 'sswidl' ) {
+ if (strtolower($this->_params['lang']) == 'sswidl') {
include_once HV_ROOT_DIR.'/../src/Helper/SSWIDL.php';
$script = new Helper_SSWIDL($this->_params);
- }
- else if ( strtolower($this->_params['lang']) == 'sunpy' ) {
+ } else if (strtolower($this->_params['lang']) == 'sunpy') {
include_once HV_ROOT_DIR.'/../src/Helper/SunPy.php';
$script = new Helper_SunPy($this->_params);
- }
- else {
- handleError(
- 'Invalid value specified for request parameter "lang".', 25);
+ } else {
+ handleError('Invalid value specified for request parameter "lang".', 25);
}
$script->buildScript();
@@ -781,7 +920,6 @@ public function getDataCoverage() {
}
-
$start = @$this->_options['startDate'];
if ($start && !preg_match('/^[0-9]+$/', $start)) {
die("Invalid start parameter: $start");
@@ -1197,9 +1335,11 @@ public function getEclipseImage() {
$range = 6000;
$roi = new Helper_RegionOfInterest(-$range, -$range, $range, $range, 15);
+ // ATTENTION! These two fields eventsLabels and eventSourceString needs to be kept in DB schema
+ // We are keeping them to support old takeScreenshot , queueMovie requests
+
// Create empty events object required for screenshots.
- include_once HV_ROOT_DIR.'/../src/Helper/HelioviewerEvents.php';
- $events = new Helper_HelioviewerEvents('');
+ $events_manager = EventStateManager::buildFromLegacyEventStrings('', false);
// Create empty celestial bodies list
$celestialBodies = array( "labels" => "",
@@ -1208,8 +1348,21 @@ public function getEclipseImage() {
// Create the base screenshot image
include_once HV_ROOT_DIR.'/../src/Image/Composite/HelioviewerScreenshot.php';
$screenshot = new Image_Composite_HelioviewerScreenshot(
- $layers, $events, false, false, $celestialBodies, false, 'earth', 0, 0, $now_str, $roi,
- ['grayscale' => true, 'eclipse' => true, 'moon' => $this->_options['moon']]
+ $layers,
+ $events_manager,
+ false,
+ $celestialBodies,
+ false,
+ 'earth',
+ 0,
+ 0,
+ $now_str,
+ $roi,
+ [
+ 'grayscale' => true,
+ 'eclipse' => true,
+ 'moon' => $this->_options['moon']
+ ]
);
$screenshot->display();
}
@@ -1281,42 +1434,50 @@ private function _computeStatusLevel($elapsed, $inst) {
* 1) x1, y1, x2, y2, OR
* 2) x0, y0, width, height
*/
- private function _getRegionOfInterest() {
+ private function _getRegionOfInterest($options, $params)
+ {
include_once HV_ROOT_DIR.'/../src/Helper/RegionOfInterest.php';
+ $isset_x0 = isset($options['x0']);
+ $isset_x1 = isset($options['x1']);
+ $isset_x2 = isset($options['x2']);
+ $isset_y0 = isset($options['y0']);
+ $isset_y1 = isset($options['y1']);
+ $isset_y2 = isset($options['y2']);
+ $isset_height = isset($options['height']);
+ $isset_width = isset($options['width']);
+
+
// Region of interest: x1, x2, y1, y2
- if (isset($this->_options['x1']) && isset($this->_options['y1']) &&
- isset($this->_options['x2']) && isset($this->_options['y2'])) {
+ if ($isset_x1 && $isset_y1 && $isset_x2 && $isset_y2) {
- $x1 = $this->_options['x1'];
- $y1 = $this->_options['y1'];
- $x2 = $this->_options['x2'];
- $y2 = $this->_options['y2'];
- }
- else if ( isset($this->_options['x0']) &&
- isset($this->_options['y0']) &&
- isset($this->_options['width']) &&
- isset($this->_options['height']) ) {
+ $x1 = $options['x1'];
+ $y1 = $options['y1'];
+ $x2 = $options['x2'];
+ $y2 = $options['y2'];
+
+ } else if ( $isset_x0 && $isset_y0 && $isset_width && $isset_height ) {
// Region of interest: x0, y0, width, height
- $x1 = $this->_options['x0'] - 0.5 * $this->_options['width'] * $this->_params['imageScale'];
- $y1 = $this->_options['y0'] - 0.5 * $this->_options['height'] * $this->_params['imageScale'];
+ $x1 = $options['x0'] - 0.5 * $options['width'] * $params['imageScale'];
+ $y1 = $options['y0'] - 0.5 * $options['height'] * $params['imageScale'];
+
+ $x2 = $options['x0'] + 0.5 * $options['width'] * $params['imageScale'];
+ $y2 = $options['y0'] + 0.5 * $options['height'] * $params['imageScale'];
+
+ } else {
- $x2 = $this->_options['x0'] + 0.5 * $this->_options['width'] * $this->_params['imageScale'];
- $y2 = $this->_options['y0'] + 0.5 * $this->_options['height'] * $this->_params['imageScale'];
- }
- else {
throw new Exception(
'Region of interest not specified: you must specify values ' .
'for imageScale and either x1, x2, y1, and y2 or x0, y0, ' .
'width and height.', 23
);
+
}
// Create RegionOfInterest helper object
- return new Helper_RegionOfInterest($x1, $y1, $x2, $y2,
- $this->_params['imageScale']);
+ return new Helper_RegionOfInterest($x1, $y1, $x2, $y2, $params['imageScale']);
}
@@ -1507,6 +1668,14 @@ public function validate() {
'ints' => array('sourceId')
);
break;
+
+ case 'getClosestImageDatesForSources':
+ $expected = array(
+ 'required' => array('date', 'sources'),
+ 'dates' => array('date'),
+ 'array_ints' => array('sources'),
+ );
+ break;
case 'getDataSources':
$expected = array(
'optional' => array('verbose', 'callback', 'enable'),
@@ -1594,6 +1763,12 @@ public function validate() {
'layer' => array('layers')
);
break;
+
+ case 'postScreenshot':
+ $expected = [
+ 'required' => ['json'],
+ ];
+ break;
case 'getStatus':
$expected = array(
'optional' => array('key'),
diff --git a/src/Movie/HelioviewerMovie.php b/src/Movie/HelioviewerMovie.php
index a831de3b5..d4e2dc5f5 100644
--- a/src/Movie/HelioviewerMovie.php
+++ b/src/Movie/HelioviewerMovie.php
@@ -31,11 +31,12 @@
require_once HV_ROOT_DIR . '/../lib/alphaID/alphaID.php';
require_once HV_ROOT_DIR . '/../src/Database/ImgIndex.php';
require_once HV_ROOT_DIR . '/../src/Helper/DateTimeConversions.php';
-require_once HV_ROOT_DIR . '/../src/Helper/HelioviewerEvents.php';
require_once HV_ROOT_DIR . '/../src/Helper/HelioviewerLayers.php';
require_once HV_ROOT_DIR . '/../src/Helper/RegionOfInterest.php';
require_once HV_ROOT_DIR . '/../src/Helper/Serialize.php';
+use Helioviewer\Api\Event\EventsStateManager;
+
class Movie_HelioviewerMovie {
const STATUS_QUEUED = 0;
const STATUS_PROCESSING = 1;
@@ -62,7 +63,6 @@ class Movie_HelioviewerMovie {
public $timestamp;
public $modified;
public $watermark;
- public $eventsLabels;
public $movieIcons;
public $celestialBodies;
public $followViewport;
@@ -77,7 +77,7 @@ class Movie_HelioviewerMovie {
private $_db;
private $_layers;
- private $_events;
+ private $_eventsManager;
private $_roi;
private $_timestamps = array();
private $_frames = array();
@@ -139,7 +139,6 @@ public function __construct($publicId, $format='mp4') {
$this->width = (int)$info['width'];
$this->height = (int)$info['height'];
$this->watermark = (bool)$info['watermark'];
- $this->eventsLabels = (bool)$info['eventsLabels'];
$this->movieIcons = (bool)$info['movieIcons'];
$this->celestialBodies = array(
'labels' => $info['celestialBodiesLabels'],
@@ -156,7 +155,18 @@ public function __construct($publicId, $format='mp4') {
// Data Layers
$this->_layers = new Helper_HelioviewerLayers($info['dataSourceString']);
- $this->_events = new Helper_HelioviewerEvents($info['eventSourceString']);
+
+ // ATTENTION! These two fields eventsLabels and eventSourceString needs to be kept in DB schema
+ // We are keeping them to support old takeScreenshot , queueMovie requests
+
+ // Events Manager
+ $events_state_from_info = json_decode($info['eventsState'], true);
+
+ if(!empty($events_state_from_info)) {
+ $this->_eventsManager = EventsStateManager::buildFromEventsState($events_state_from_info);
+ } else {
+ $this->_eventsManager = EventsStateManager::buildFromLegacyEventStrings($info['eventSourceString'], (bool)$info['eventsLabels']);
+ }
// Regon of interest
$this->_roi = Helper_RegionOfInterest::parsePolygonString($info['roi'], $info['imageScale']);
@@ -249,7 +259,7 @@ public function build() {
$statistics = new Database_Statistics();
$statistics->log('buildMovie');
- $statistics->logRedis('buildMovie');
+ $statistics->logRedis('buildMovie');
}
$this->_cleanUp();
@@ -281,7 +291,7 @@ public function getCompletedMovieInformation($verbose=false) {
'duration' => $this->getDuration(),
'imageScale' => $this->imageScale,
'layers' => $this->_layers->serialize(),
- 'events' => $this->_events->serialize(),
+ 'events' => $this->_eventsManager->getState(),
'x1' => $this->_roi->left(),
'y1' => $this->_roi->top(),
'x2' => $this->_roi->right(),
@@ -351,6 +361,12 @@ public function getCurrentFrame() {
}
}
}
+
+ // Do not call closedir boolean if we can not open directory
+ if (false === $handle) {
+ throw new \Exception("Could not find requested movie frames");
+ }
+
@closedir($handle);
return ($newest_frame>0) ? (int)$newest_frame : null;
@@ -421,8 +437,8 @@ private function _buildMovieFrames($watermark) {
'compress' => false,
'interlace' => false,
'watermark' => $watermark,
- 'movie' => true,
- 'size' => $this->size,
+ 'movie' => true,
+ 'size' => $this->size,
'followViewport' => $this->followViewport,
'startDate' => $this->startDate,
'reqStartDate' => $this->reqStartDate,
@@ -444,8 +460,8 @@ private function _buildMovieFrames($watermark) {
try {
$screenshot = new Image_Composite_HelioviewerMovieFrame(
- $filepath, $this->_layers, $this->_events,
- $this->eventsLabels, $this->movieIcons, $this->celestialBodies,
+ $filepath, $this->_layers, $this->_eventsManager,
+ $this->movieIcons, $this->celestialBodies,
$this->scale, $this->scaleType, $this->scaleX, $this->scaleY,
$time, $this->_roi, $options);
@@ -581,18 +597,18 @@ private function _encodeMovie() {
// https://bugs.launchpad.net/helioviewer.org/+bug/979231
$frameRate = round($this->frameRate, 1);
- if($this->size == 1){
- $this->width = 1280;
- $this->height = 720;
+ if($this->size == 1){
+ $this->width = 1280;
+ $this->height = 720;
}else if($this->size == 2){
- $this->width = 1920;
- $this->height = 1080;
+ $this->width = 1920;
+ $this->height = 1080;
}else if($this->size == 3){
- $this->width = 2560;
- $this->height = 1440;
+ $this->width = 2560;
+ $this->height = 1440;
}else if($this->size == 4){
- $this->width = 3840;
- $this->height = 2160;
+ $this->width = 3840;
+ $this->height = 2160;
}
// Create and FFmpeg encoder instance
@@ -761,21 +777,21 @@ private function _setMovieProperties() {
$this->_setMovieDimensions();
- if($this->size == 1){
- $width = 1280;
- $height = 720;
+ if($this->size == 1){
+ $width = 1280;
+ $height = 720;
}else if($this->size == 2){
- $width = 1920;
- $height = 1080;
+ $width = 1920;
+ $height = 1080;
}else if($this->size == 3){
- $width = 2560;
- $height = 1440;
+ $width = 2560;
+ $height = 1440;
}else if($this->size == 4){
- $width = 3840;
- $height = 2160;
+ $width = 3840;
+ $height = 2160;
}else{
- $width = $this->width;
- $height = $this->height;
+ $width = $this->width;
+ $height = $this->height;
}
// Update movie entry in database with new details
diff --git a/src/Validation/InputValidator.php b/src/Validation/InputValidator.php
index a0b9c1c08..bdb5643bb 100644
--- a/src/Validation/InputValidator.php
+++ b/src/Validation/InputValidator.php
@@ -34,18 +34,19 @@ public static function checkInput(&$expected, &$input, &$optional)
{
// Validation checks
$checks = array(
- "required" => "checkForMissingParams",
- "alphanum" => "checkAlphaNumericStrings",
- "ints" => "checkInts",
- "floats" => "checkFloats",
- "bools" => "checkBools",
- "dates" => "checkDates",
- "encoded" => "checkURLEncodedStrings",
- "urls" => "checkURLs",
- "files" => "checkFilePaths",
- "uuids" => "checkUUIDs",
- "layer" => "checkLayerValidity",
- "choices" => "checkChoices"
+ "required" => "checkForMissingParams",
+ "alphanum" => "checkAlphaNumericStrings",
+ "ints" => "checkInts",
+ "array_ints" => "checkOfArrayInts",
+ "floats" => "checkFloats",
+ "bools" => "checkBools",
+ "dates" => "checkDates",
+ "encoded" => "checkURLEncodedStrings",
+ "urls" => "checkURLs",
+ "files" => "checkFilePaths",
+ "uuids" => "checkUUIDs",
+ "layer" => "checkLayerValidity",
+ "choices" => "checkChoices"
);
// Run validation checks
@@ -211,6 +212,36 @@ public static function checkInts($ints, &$params)
}
}
+
+ /**
+ * Typecasts validates and fixes types for array integer parameters
+ *
+ * @param array $ints A list of integer array parameters which are used by an action.
+ * @param array &$params The parameters that were passed in
+ *
+ * @return void
+ */
+ public static function checkOfArrayInts($ints, &$params)
+ {
+ foreach ($ints as $int) {
+ if (isset($params[$int])) {
+
+
+ $integers_to_check = explode(',',$params[$int]);
+ $validated_ints = [];
+
+ foreach($integers_to_check as $itc) {
+ if (filter_var(trim($itc), FILTER_VALIDATE_INT) === false) {
+ throw new InvalidArgumentException("Invalid value for $int. Please specify an integer array value, as ex:1,2,3", 25);
+ }
+ $validated_ints[] = (int) trim($itc);
+ }
+
+ $params[$int] = $validated_ints;
+ }
+ }
+ }
+
/**
* Typecasts validates and fixes types for float parameters
*
diff --git a/tests/unit_tests/events/EventsStateManagerTest.php b/tests/unit_tests/events/EventsStateManagerTest.php
new file mode 100644
index 000000000..960abf59e
--- /dev/null
+++ b/tests/unit_tests/events/EventsStateManagerTest.php
@@ -0,0 +1,458 @@
+ [
+ 'id' => 'HEK',
+ 'markers_visible' => true,
+ 'labels_visible' => true,
+ 'layers' => [
+ [
+ 'event_type' => 'flare',
+ 'frms' => ['frm10', 'frm20'],
+ 'event_instances' => ['flare--frm1--event1', 'flare--frm2--event2'],
+ 'open' => true,
+ ]
+ ]
+ ],
+ 'tree_CCMC' => [
+ 'id' => 'CCMC',
+ 'markers_visible' => true,
+ 'labels_visible' => false,
+ 'layers' => [
+ [
+ 'event_type' => 'storm',
+ 'frms' => ['frm30', 'frm40'],
+ 'event_instances' => ['storm--frm3--event3', 'storm--frm4--event4'],
+ 'open' => true,
+ ]
+ ]
+ ]
+ ];
+
+
+ // Test building EventsStateManager from events state with correct tree
+ public function testItShouldBuildFromEventStateArrayAsExpectedWithAllFrms()
+ {
+ $events_state_with_all = [
+ 'tree_HEK' => [
+ 'id' => 'HEK',
+ 'markers_visible' => true,
+ 'labels_visible' => false,
+ 'layers' => [
+ [
+ 'event_type' => 'flare',
+ 'frms' => ['all'],
+ 'event_instances' => ['flare--frm1--event1', 'flare--frm2--event2'],
+ 'open' => true,
+ ]
+ ]
+ ],
+ ];
+ $manager = EventsStateManager::buildFromEventsState($events_state_with_all);
+ $this->assertInstanceOf(EventsStateManager::class, $manager);
+ $this->assertEquals($manager->getStateTree(), [
+ 'flare' => 'all_frms',
+ ]);
+ $this->assertEquals($manager->getStateTreeLabelVisibility(), [
+ 'flare' => false,
+ ]);
+
+ }
+
+ // Test building EventsStateManager from events state with correct tree
+ public function testItShouldBuildFromEventStateArrayAsExpected()
+ {
+ $manager = EventsStateManager::buildFromEventsState($this->eventsState);
+ $this->assertInstanceOf(EventsStateManager::class, $manager);
+ $this->assertEquals($manager->getStateTree(), [
+ 'flare' => [
+ 'frm10' => 'all_event_instances',
+ 'frm20' => 'all_event_instances',
+ 'frm1' => [
+ 'flare--frm1--event1'
+ ],
+ 'frm2' => [
+ 'flare--frm2--event2'
+ ],
+ ],
+ 'storm' => [
+ 'frm30' => 'all_event_instances',
+ 'frm40' => 'all_event_instances',
+ 'frm3' => [
+ 'storm--frm3--event3'
+ ],
+ 'frm4' => [
+ 'storm--frm4--event4'
+ ],
+ ],
+ ]);
+ $this->assertEquals($manager->getStateTreeLabelVisibility(), [
+ 'storm' => false,
+ 'flare' => true,
+ ]);
+
+ }
+
+ // Test building EventsStateManager from events state with correct tree
+ public function testItShouldCorrectlyIgnoreLayersWithNotVisibleMarkersAndBuildFromEventStateArrayAsExpected()
+ {
+ $event_state_markers_not_visible = [
+ 'tree_HEK' => [
+ 'id' => 'HEK',
+ 'markers_visible' => false,
+ 'labels_visible' => true,
+ 'layers' => [
+ [
+ 'event_type' => 'flare',
+ 'frms' => ['frm10', 'frm20'],
+ 'event_instances' => ['flare--frm1--event1', 'flare--frm2--event2'],
+ 'open' => true,
+ ]
+ ]
+ ],
+ 'tree_CCMC' => [
+ 'id' => 'CCMC',
+ 'markers_visible' => true,
+ 'labels_visible' => false,
+ 'layers' => [
+ [
+ 'event_type' => 'storm',
+ 'frms' => ['frm30', 'frm40'],
+ 'event_instances' => ['storm--frm3--event3', 'storm--frm4--event4'],
+ 'open' => true,
+ ]
+ ]
+ ]
+ ];
+ $manager = EventsStateManager::buildFromEventsState($event_state_markers_not_visible);
+ $this->assertInstanceOf(EventsStateManager::class, $manager);
+ $this->assertEquals($manager->getStateTree(), [
+ 'storm' => [
+ 'frm30' => 'all_event_instances',
+ 'frm40' => 'all_event_instances',
+ 'frm3' => [
+ 'storm--frm3--event3'
+ ],
+ 'frm4' => [
+ 'storm--frm4--event4'
+ ],
+ ],
+ ]);
+ $this->assertEquals($manager->getStateTreeLabelVisibility(), [
+ 'storm' => false,
+ ]);
+
+ }
+
+ public function testItShouldBuildFromInvalidEventStateArrayWithInvalidEventInstancesAsExpected()
+ {
+ $invalid_event_state = [
+ 'tree_HEK' => [
+ 'id' => 'HEK',
+ 'markers_visible' => true,
+ 'labels_visible' => true,
+ 'layers' => [
+ [
+ 'event_type' => 'flare',
+ 'frms' => ['frm10', 'frm20'],
+ 'event_instances' => ['flare--frm10--event1', 'flare--frm20--event2'],
+ 'open' => true,
+ ]
+ ]
+ ],
+ 'tree_CCMC' => [
+ 'id' => 'CCMC',
+ 'markers_visible' => true,
+ 'labels_visible' => false,
+ 'layers' => [
+ [
+ 'event_type' => 'storm',
+ 'frms' => ['frm30', 'frm40'],
+ 'event_instances' => ['storm--frm30--event3', 'storm--frm40--event4'],
+ 'open' => true,
+ ]
+ ]
+ ]
+ ];
+ $manager = EventsStateManager::buildFromEventsState($invalid_event_state);
+ $this->assertInstanceOf(EventsStateManager::class, $manager);
+ $this->assertEquals($manager->getStateTree(), [
+ 'flare' => [
+ 'frm10' => 'all_event_instances',
+ 'frm20' => 'all_event_instances',
+ ],
+ 'storm' => [
+ 'frm30' => 'all_event_instances',
+ 'frm40' => 'all_event_instances',
+ ],
+ ]);
+ $this->assertEquals($manager->getStateTreeLabelVisibility(), [
+ 'storm' => false,
+ 'flare' => true,
+ ]);
+ }
+
+ // Test building EventsStateManager from legacy event strings
+ public function testItShouldBuildFromLegacyEventStrings1()
+ {
+ $legacyString = '[flare,frm1;frm2,1],[storm,frm3;frm4,0]';
+ $manager = EventsStateManager::buildFromLegacyEventStrings($legacyString, true);
+ $this->assertInstanceOf(EventsStateManager::class, $manager);
+ $this->assertEquals($manager->getStateTree(), [
+ 'flare' => [
+ 'frm1' => 'all_event_instances',
+ 'frm2' => 'all_event_instances',
+ ],
+ 'storm' => [
+ 'frm3' => 'all_event_instances',
+ 'frm4' => 'all_event_instances',
+ ]
+ ]);
+ $this->assertEquals($manager->getStateTreeLabelVisibility(), [
+ 'storm' => true,
+ 'flare' => true,
+ ]);
+ }
+
+ public function testItShouldBuildFromLegacyEventStrings2()
+ {
+ $legacyString = '[flare,frm1,1],[storm,frm3;frm4,0]';
+ $manager = EventsStateManager::buildFromLegacyEventStrings($legacyString, false);
+ $this->assertInstanceOf(EventsStateManager::class, $manager);
+ $this->assertEquals($manager->getStateTree(), [
+ 'flare' => [
+ 'frm1' => 'all_event_instances',
+ ],
+ 'storm' => [
+ 'frm3' => 'all_event_instances',
+ 'frm4' => 'all_event_instances',
+ ]
+ ]);
+ $this->assertEquals($manager->getStateTreeLabelVisibility(), [
+ 'storm' => false,
+ 'flare' => false,
+ ]);
+ }
+
+ public function testItShouldBuildFromLegacyEventStrings3()
+ {
+ $legacyString = '[flare,frm1,1],[storm,,0]';
+ $manager = EventsStateManager::buildFromLegacyEventStrings($legacyString, false);
+ $this->assertInstanceOf(EventsStateManager::class, $manager);
+ $this->assertEquals($manager->getStateTree(), [
+ 'flare' => [
+ 'frm1' => 'all_event_instances',
+ ],
+ ]);
+ $this->assertEquals($manager->getStateTreeLabelVisibility(), [
+ 'flare' => false,
+ ]);
+ }
+
+ // Test if the manager correctly identifies no events
+ public function testItShouldCorrectlySayIfNoEvents()
+ {
+ $emptyState = [
+ 'tree_HEK' => [
+ 'id' => 'HEK',
+ 'markers_visible' => false,
+ 'labels_visible' => false,
+ 'layers' => []
+ ],
+ 'tree_CCMC' => [
+ 'id' => 'CCMC',
+ 'markers_visible' => false,
+ 'labels_visible' => false,
+ 'layers' => []
+ ]
+ ];
+ $manager = EventsStateManager::buildFromEventsState($emptyState);
+ $this->assertFalse($manager->hasEvents());
+ }
+
+ public function testItShouldCorrectlySayIfNoEventsWithLegacyStrings()
+ {
+ $legacyString = '[flare,,1],[storm,,0]';
+ $manager = EventsStateManager::buildFromLegacyEventStrings($legacyString, false);
+ $this->assertFalse($manager->hasEvents());
+ }
+
+ // Test checking for events of a specific type
+ public function testItShouldTellIfHasEventsForEventType()
+ {
+ $manager = EventsStateManager::buildFromEventsState($this->eventsState);
+ $this->assertTrue($manager->hasEventsForEventType('flare'));
+ $this->assertTrue($manager->hasEventsForEventType('storm'));
+ }
+
+ // Test checking for events of an unknown type
+ public function testItShouldTellIfHasNoEventsForEventType()
+ {
+ $manager = EventsStateManager::buildFromEventsState($this->eventsState);
+ $this->assertFalse($manager->hasEventsForEventType('unknown_event_type'));
+ }
+
+ // Test if the manager applies all events for a specific type
+ public function testItShouldTellIfStateAppliesAllEventsForEventType()
+ {
+ $events_state = [
+ 'tree_HEK' => [
+ 'id' => 'HEK',
+ 'markers_visible' => true,
+ 'labels_visible' => true,
+ 'layers' => [
+ [
+ 'event_type' => 'flare',
+ 'frms' => ['all'],
+ 'event_instances' => [],
+ 'open' => true,
+ ]
+ ]
+ ],
+ 'tree_CCMC' => [
+ 'id' => 'CCMC',
+ 'markers_visible' => true,
+ 'labels_visible' => true,
+ 'layers' => [
+ [
+ 'event_type' => 'foo',
+ 'frms' => ['hede'],
+ 'event_instances' => [],
+ 'open' => true,
+ ]
+ ]
+ ]
+ ];
+ $manager = EventsStateManager::buildFromEventsState($events_state);
+ $this->assertTrue($manager->appliesAllEventsForEventType('flare'));
+ $this->assertFalse($manager->appliesAllEventsForEventType('foo'));
+ }
+
+
+ // Test if a specific frm is applied for an event type
+ public function testItShouldAppliesFrmForEventType()
+ {
+ $manager = EventsStateManager::buildFromEventsState($this->eventsState);
+ $this->assertTrue($manager->appliesFrmForEventType('flare', 'frm1'));
+ $this->assertTrue($manager->appliesFrmForEventType('flare', 'frm10'));
+ $this->assertFalse($manager->appliesFrmForEventType('flare', 'frm3'));
+ $this->assertTrue($manager->appliesFrmForEventType('storm', 'frm3'));
+ $this->assertTrue($manager->appliesFrmForEventType('storm', 'frm30'));
+ $this->assertFalse($manager->appliesFrmForEventType('storm', 'frm1'));
+ }
+
+ // Test if a non-existent frm is applied for an event type
+ public function testItShouldNotApplyFrmForEventTypeNoFrm()
+ {
+ $manager = EventsStateManager::buildFromEventsState($this->eventsState);
+ $this->assertFalse($manager->appliesFrmForEventType('flare', 'unknown_frm'));
+ $this->assertFalse($manager->appliesFrmForEventType('storm', 'unknown_frm'));
+ }
+
+ // Test if all event instances for a frm are applied
+ public function testItShouldApplyAllEventInstancesForFrm()
+ {
+ $eventsState = [
+ 'tree_HEK' => [
+ 'id' => 'HEK',
+ 'markers_visible' => true,
+ 'labels_visible' => true,
+ 'layers' => [
+ [
+ 'event_type' => 'flare',
+ 'frms' => ['frm1'],
+ 'event_instances' => ['flare--frm2--all_event_instances'],
+ 'open' => true,
+ ]
+ ]
+ ]
+ ];
+ $manager = EventsStateManager::buildFromEventsState($eventsState);
+ $this->assertTrue($manager->appliesAllEventInstancesForFrm('flare', 'frm1'));
+ $this->assertFalse($manager->appliesAllEventInstancesForFrm('flare', 'frm2'));
+ }
+
+
+ // Test if a specific event instance is applied
+ public function testItShouldApplyEventInstanceWhenMatch()
+ {
+ $event_1 = ['id' => 'event1'];
+ $event_id_1 = EventsStateManager::makeEventId('flare', 'frm1', $event_1);
+ $event_2 = ['id' => 'event2'];
+ $event_id_2 = EventsStateManager::makeEventId('flare', 'frm2', $event_2);
+
+ $event_3 = ['id' => 'event3'];
+ $event_id_3 = EventsStateManager::makeEventId('storm', 'frm3', $event_3);
+ $event_4 = ['id' => 'event4'];
+ $event_id_4 = EventsStateManager::makeEventId('storm', 'frm4', $event_4);
+
+ $event_5 = ['id' => 'event5'];
+ $event_id_5 = EventsStateManager::makeEventId('storm', 'frm4', $event_5);
+
+ $event_state = [
+ 'tree_HEK' => [
+ 'id' => 'HEK',
+ 'markers_visible' => true,
+ 'labels_visible' => true,
+ 'layers' => [
+ [
+ 'event_type' => 'flare',
+ 'frms' => ['frm10', 'frm20'],
+ 'event_instances' => [$event_id_1, $event_id_2],
+ 'open' => true,
+ ]
+ ]
+ ],
+ 'tree_CCMC' => [
+ 'id' => 'CCMC',
+ 'markers_visible' => true,
+ 'labels_visible' => false,
+ 'layers' => [
+ [
+ 'event_type' => 'storm',
+ 'frms' => ['frm30', 'frm40'],
+ 'event_instances' => [$event_id_3, $event_id_4],
+ 'open' => true,
+ ]
+ ]
+ ]
+ ];
+
+ $manager = EventsStateManager::buildFromEventsState($event_state);
+ $this->assertTrue($manager->appliesEventInstance('flare', 'frm1', $event_1));
+ $this->assertTrue($manager->appliesEventInstance('flare', 'frm2', $event_2));
+ $this->assertTrue($manager->appliesEventInstance('storm', 'frm3', $event_3));
+ $this->assertTrue($manager->appliesEventInstance('storm', 'frm4', $event_4));
+ $this->assertFalse($manager->appliesEventInstance('storm', 'frm3', $event_5));
+ }
+
+ // Test if a non-existent event instance is applied
+ public function testItShouldNotApplyEventInstanceNoInstance()
+ {
+ $manager = EventsStateManager::buildFromEventsState($this->eventsState);
+ $event = ['id' => 'unknown_event'];
+ $this->assertFalse($manager->appliesEventInstance('flare', 'frm1', $event));
+ }
+
+ // Test if event type labels are visible
+ public function testItShouldCorrectlyKeepEventTypesLabelVisible()
+ {
+ $manager = EventsStateManager::buildFromEventsState($this->eventsState);
+ $this->assertTrue($manager->isEventTypeLabelVisible('flare'));
+ $this->assertFalse($manager->isEventTypeLabelVisible('storm'));
+ }
+
+ // Test if event type labels are visible for an unknown event type
+ public function testItShouldCorrectlyReportNonExistantEventTypesLabelVisible()
+ {
+ $manager = EventsStateManager::buildFromEventsState($this->eventsState);
+ $this->assertFalse($manager->isEventTypeLabelVisible('unknown_event_type'));
+ }
+}
+
diff --git a/tests/unit_tests/movies/reQueueMovieTest.php b/tests/unit_tests/movies/reQueueMovieTest.php
index e575448c8..d96472d3b 100644
--- a/tests/unit_tests/movies/reQueueMovieTest.php
+++ b/tests/unit_tests/movies/reQueueMovieTest.php
@@ -70,7 +70,6 @@ private function _markMovieAsProcessing($movie) {
* the movie is already there
*/
public function testRequeueMovie_MovieExists() {
- $this->markTestSkipped("Integration Tests to be handled later");
// Queue the test movie
$result = $this->_queueTestMovie();
// Build the test movie
@@ -93,7 +92,6 @@ public function testRequeueMovie_MovieExists() {
* @runInSeparateProcess
*/
public function testRequeueMovie_Force() {
- $this->markTestSkipped("Integration Tests to be handled later");
// Queue the test movie
$result = $this->_queueTestMovie();
// Build the test movie
@@ -115,7 +113,6 @@ public function testRequeueMovie_Force() {
* Helper function so the test can be run with both force = true & false
*/
public function _testRequeueMovie_MovieProcessing($force) {
- $this->markTestSkipped("Integration Tests to be handled later");
// Queue the test movie
$result = $this->_queueTestMovie();
// Build the test movie
@@ -146,7 +143,6 @@ public function _testRequeueMovie_MovieProcessing($force) {
* @runInSeparateProcess
*/
public function testRequeueMovie_MovieProcessing() {
- $this->markTestSkipped("Integration Tests to be handled later");
$this->_testRequeueMovie_MovieProcessing(false);
}
@@ -156,7 +152,6 @@ public function testRequeueMovie_MovieProcessing() {
* @runInSeparateProcess
*/
public function testRequeueMovie_MovieProcessing_Force() {
- $this->markTestSkipped("Integration Tests to be handled later");
$this->_testRequeueMovie_MovieProcessing(true);
}
@@ -164,7 +159,6 @@ public function testRequeueMovie_MovieProcessing_Force() {
* Issue #262 - When mp4 creation fails, movie can't be requeued
*/
public function testRequeueMovie_262() {
- $this->markTestSkipped("Integration Tests to be handled later");
// Queue the test movie
$result = $this->_queueTestMovie();
// Get the movie ID as an integer
diff --git a/tests/unit_tests/validation/ValidatorTest.php b/tests/unit_tests/validation/ValidatorTest.php
index 193899236..e7f3d2ff9 100644
--- a/tests/unit_tests/validation/ValidatorTest.php
+++ b/tests/unit_tests/validation/ValidatorTest.php
@@ -61,4 +61,60 @@ public function test_ValidateLayerArray_MultipleLayers(): void
// that no exception was raised.
$this->assertTrue(true);
}
+
+ public function test_ValidateArrayIntegersProblem1(): void
+ {
+ // The expected layer string to be given
+ $input = array(
+ 'sources' => ''
+ );
+
+ $rules = array(
+ 'array_ints' => array('sources')
+ );
+
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('Invalid value for sources. Please specify an integer array value, as ex:1,2,3');
+ Validation_InputValidator::checkInput($rules, $input, $input) ;
+ // checkInput will raise an exception if it fails, so assertTrue means
+ // that no exception was raised.
+ }
+
+ public function test_ValidateArrayIntegersProblem2(): void
+ {
+ // The expected layer string to be given
+ $input = array(
+ 'sources' => 'a,1,2'
+ );
+
+ $rules = array(
+ 'array_ints' => array('sources')
+ );
+
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('Invalid value for sources. Please specify an integer array value, as ex:1,2,3');
+ Validation_InputValidator::checkInput($rules, $input, $input) ;
+ // checkInput will raise an exception if it fails, so assertTrue means
+ // that no exception was raised.
+ }
+
+ public function test_ValidateArrayIntegersCorrectly(): void
+ {
+ // The expected layer string to be given
+ $input = array(
+ 'sources' => '4,1,2'
+ );
+
+ $rules = array(
+ 'array_ints' => array('sources')
+ );
+
+ Validation_InputValidator::checkInput($rules, $input, $input) ;
+
+ $this->assertEquals($input, [
+ 'sources' => [4,1,2]
+ ]);
+ // checkInput will raise an exception if it fails, so assertTrue means
+ // that no exception was raised.
+ }
}