From 322190a403a4c94c07d0b5983812e9e0f4ef7d39 Mon Sep 17 00:00:00 2001 From: Jeremy Sampson Date: Mon, 12 Dec 2022 12:17:45 -0700 Subject: [PATCH] integrate sensor position schema detection and parsing (#59) --- .../TimeSeriesViewerContext.js | 27 +- .../TimeSeriesViewerSites.d.ts | 28 +- .../TimeSeriesViewer/TimeSeriesViewerSites.js | 124 ++++----- lib/parser/DataPackageParser.d.ts | 13 + lib/parser/DataPackageParser.js | 83 ++++++ lib/types/internal.d.ts | 24 ++ lib/types/neonDataPackage.d.ts | 144 +++++++++++ lib/types/neonDataPackage.js | 62 +++++ package-lock.json | 11 +- package.json | 3 +- .../TimeSeriesViewerContext.jsx | 20 +- .../TimeSeriesViewerSites.jsx | 120 +++++---- .../__tests__/TimeSeriesViewerContext.jsx | 244 +++++++++++++----- .../__tests__/TimeSeriesViewerSites.jsx | 44 ++-- .../parser/DataPackageParser.ts | 96 +++++++ src/lib_components/types/internal.ts | 25 ++ src/lib_components/types/neonDataPackage.ts | 120 +++++++++ 17 files changed, 962 insertions(+), 226 deletions(-) create mode 100644 lib/parser/DataPackageParser.d.ts create mode 100644 lib/parser/DataPackageParser.js create mode 100644 lib/types/neonDataPackage.d.ts create mode 100644 lib/types/neonDataPackage.js create mode 100644 src/lib_components/parser/DataPackageParser.ts create mode 100644 src/lib_components/types/neonDataPackage.ts diff --git a/lib/components/TimeSeriesViewer/TimeSeriesViewerContext.js b/lib/components/TimeSeriesViewer/TimeSeriesViewerContext.js index 4243e1a7..c4bbf711 100644 --- a/lib/components/TimeSeriesViewer/TimeSeriesViewerContext.js +++ b/lib/components/TimeSeriesViewer/TimeSeriesViewerContext.js @@ -18,7 +18,9 @@ var _NeonApi = _interopRequireDefault(require("../NeonApi/NeonApi")); var _NeonGraphQL = _interopRequireDefault(require("../NeonGraphQL/NeonGraphQL")); var _NeonEnvironment = _interopRequireDefault(require("../NeonEnvironment/NeonEnvironment")); var _rxUtil = require("../../util/rxUtil"); +var _typeUtil = require("../../util/typeUtil"); var _parseTimeSeriesData = _interopRequireDefault(require("../../workers/parseTimeSeriesData")); +var _DataPackageParser = _interopRequireDefault(require("../../parser/DataPackageParser")); var _NeonSignInButtonState = _interopRequireDefault(require("../NeonSignInButton/NeonSignInButtonState")); var _StateStorageService = _interopRequireDefault(require("../../service/StateStorageService")); var _StateStorageConverter = require("./StateStorageConverter"); @@ -614,6 +616,10 @@ var parseSiteVariables = function parseSiteVariables(previousVariables, siteCode variablesObject: newStateVariables }; }; +var parsePosition = function parsePosition(position) { + if (!(0, _typeUtil.exists)(position)) return position; + return _DataPackageParser.default.parseSensorPosition(position); +}; /** * Build an object for state.product.sites[{site}] from a product/site positions fetch response @@ -627,22 +633,25 @@ var parseSitePositions = function parseSitePositions(site, csv) { var newSite = _extends({}, site); var positions = parseCSV(csv, true); // Duplicated lines have been unintentionally seen here! positions.data.forEach(function (position) { - var posId = position['HOR.VER']; - if (!newSite.positions[posId]) { - newSite.positions[posId] = { - data: {}, - history: [] - }; + var parsedPosition = parsePosition(position); + if ((0, _typeUtil.exists)(parsedPosition)) { + var posId = parsedPosition.horVer; + if (!newSite.positions[posId]) { + newSite.positions[posId] = { + data: {}, + history: [] + }; + } + newSite.positions[posId].history.push(parsedPosition); } - newSite.positions[posId].history.push(position); }); // Sort position history by start/end time descending Object.keys(newSite.positions).forEach(function (posId) { newSite.positions[posId].history.sort(function (a, b) { - if (!a.end) { + if (!a.sensorEndDateTime) { return 1; } - return a.end < b.start ? -1 : 1; + return a.sensorEndDateTime < b.sensorStartDateTime ? -1 : 1; }); }); return newSite; diff --git a/lib/components/TimeSeriesViewer/TimeSeriesViewerSites.d.ts b/lib/components/TimeSeriesViewer/TimeSeriesViewerSites.d.ts index 5a071c20..428fe0a4 100644 --- a/lib/components/TimeSeriesViewer/TimeSeriesViewerSites.d.ts +++ b/lib/components/TimeSeriesViewer/TimeSeriesViewerSites.d.ts @@ -36,20 +36,20 @@ declare namespace PositionHistoryButton { const position: PropTypes.Validator; const fullWidth: PropTypes.Requireable; const history: PropTypes.Validator<(PropTypes.InferProps<{ - 'HOR.VER': PropTypes.Validator; - azimuth: PropTypes.Validator; - pitch: PropTypes.Validator; - roll: PropTypes.Validator; - start: PropTypes.Requireable; - end: PropTypes.Requireable; - xOffset: PropTypes.Validator; - yOffset: PropTypes.Validator; - zOffset: PropTypes.Validator; - referenceStart: PropTypes.Requireable; - referenceEnd: PropTypes.Requireable; - referenceLatitude: PropTypes.Validator; - referenceLongitude: PropTypes.Validator; - referenceElevation: PropTypes.Validator; + horVer: PropTypes.Validator; + azimuth: PropTypes.Requireable; + pitch: PropTypes.Requireable; + roll: PropTypes.Requireable; + sensorStartDateTime: PropTypes.Requireable; + sensorEndDateTime: PropTypes.Requireable; + xOffset: PropTypes.Requireable; + yOffset: PropTypes.Requireable; + zOffset: PropTypes.Requireable; + referenceLocationStartDateTime: PropTypes.Requireable; + referenceLocationEndDateTime: PropTypes.Requireable; + referenceLocationLatitude: PropTypes.Requireable; + referenceLocationLongitude: PropTypes.Requireable; + referenceLocationElevation: PropTypes.Requireable; }> | null | undefined)[]>; } namespace defaultProps { diff --git a/lib/components/TimeSeriesViewer/TimeSeriesViewerSites.js b/lib/components/TimeSeriesViewer/TimeSeriesViewerSites.js index 44854f57..9e14b0d2 100644 --- a/lib/components/TimeSeriesViewer/TimeSeriesViewerSites.js +++ b/lib/components/TimeSeriesViewer/TimeSeriesViewerSites.js @@ -386,52 +386,59 @@ function PositionHistoryButton(props) { }, "z"), /*#__PURE__*/_react.default.createElement(_TableCell.default, { align: "right" }, "Elevation"))), /*#__PURE__*/_react.default.createElement(_TableBody.default, null, history.map(function (row, idx) { - var _row$start = row.start, - start = _row$start === void 0 ? '' : _row$start, - _row$end = row.end, - rawEnd = _row$end === void 0 ? '' : _row$end, + var _row$sensorStartDateT = row.sensorStartDateTime, + sensorStartDateTime = _row$sensorStartDateT === void 0 ? '' : _row$sensorStartDateT, + _row$sensorEndDateTim = row.sensorEndDateTime, + rawEnd = _row$sensorEndDateTim === void 0 ? '' : _row$sensorEndDateTim, xOffset = row.xOffset, yOffset = row.yOffset, zOffset = row.zOffset, - referenceElevation = row.referenceElevation; - var hasReferenceElevation = (0, _typeUtil.exists)(referenceElevation) && referenceElevation !== ''; - var hasZOffset = (0, _typeUtil.exists)(zOffset) && zOffset !== ''; - var parsedReferenceElevation = hasReferenceElevation ? parseFloat(referenceElevation, 10) : NaN; - var parsedZOffset = hasZOffset ? parseFloat(zOffset, 10) : NaN; + referenceLocationElevation = row.referenceLocationElevation; + var hasReferenceElevation = (0, _typeUtil.exists)(referenceLocationElevation) && !isNaN(referenceLocationElevation); + var hasXOffset = (0, _typeUtil.exists)(xOffset) && !isNaN(xOffset); + var hasYOffset = (0, _typeUtil.exists)(yOffset) && !isNaN(yOffset); + var hasZOffset = (0, _typeUtil.exists)(zOffset) && !isNaN(zOffset); + var parsedReferenceElevation = hasReferenceElevation ? referenceLocationElevation : NaN; + var parsedXOffset = hasXOffset ? xOffset : NaN; + var parsedYOffset = hasYOffset ? yOffset : NaN; + var parsedZOffset = hasZOffset ? zOffset : NaN; var elevation = 'unknown'; if (!isNaN(parsedReferenceElevation)) { - if (!isNaN(hasZOffset)) { + if (!isNaN(parsedZOffset)) { elevation = "".concat((parsedReferenceElevation + parsedZOffset).toFixed(2).toString(), "m"); } else { elevation = "".concat(parsedReferenceElevation, "m"); } } + var displayXOffset = hasXOffset ? "".concat(xOffset, "m") : '--'; + var displayYOffset = hasYOffset ? "".concat(yOffset, "m") : '--'; + var displayZOffset = hasZOffset ? "".concat(zOffset, "m") : '--'; var end = rawEnd === '' ? 'Current' : rawEnd; var cellStyle = idx !== history.length - 1 ? {} : { fontWeight: '600', borderBottom: 'none' }; - var key = "".concat(start).concat(end).concat(xOffset).concat(yOffset).concat(zOffset); + var key = "".concat(sensorStartDateTime).concat(end).concat(parsedXOffset).concat(parsedYOffset).concat(parsedZOffset); return /*#__PURE__*/_react.default.createElement(_TableRow.default, { key: key }, /*#__PURE__*/_react.default.createElement(_TableCell.default, { component: "th", scope: "row", style: cellStyle - }, start), /*#__PURE__*/_react.default.createElement(_TableCell.default, { + }, sensorStartDateTime), /*#__PURE__*/_react.default.createElement(_TableCell.default, { component: "th", scope: "row", style: cellStyle }, end), /*#__PURE__*/_react.default.createElement(_TableCell.default, { align: "right", style: cellStyle - }, "".concat(xOffset, "m")), /*#__PURE__*/_react.default.createElement(_TableCell.default, { + }, displayXOffset), /*#__PURE__*/_react.default.createElement(_TableCell.default, { align: "right", style: cellStyle - }, "".concat(yOffset, "m")), /*#__PURE__*/_react.default.createElement(_TableCell.default, { + }, displayYOffset), /*#__PURE__*/_react.default.createElement(_TableCell.default, { align: "right", style: cellStyle - }, "".concat(zOffset, "m")), /*#__PURE__*/_react.default.createElement(_TableCell.default, { + }, displayZOffset), /*#__PURE__*/_react.default.createElement(_TableCell.default, { align: "right", style: cellStyle }, elevation)); @@ -448,20 +455,20 @@ PositionHistoryButton.propTypes = { position: _propTypes.default.string.isRequired, fullWidth: _propTypes.default.bool, history: _propTypes.default.arrayOf(_propTypes.default.shape({ - 'HOR.VER': _propTypes.default.string.isRequired, - azimuth: _propTypes.default.string.isRequired, - pitch: _propTypes.default.string.isRequired, - roll: _propTypes.default.string.isRequired, - start: _propTypes.default.string, - end: _propTypes.default.string, - xOffset: _propTypes.default.string.isRequired, - yOffset: _propTypes.default.string.isRequired, - zOffset: _propTypes.default.string.isRequired, - referenceStart: _propTypes.default.string, - referenceEnd: _propTypes.default.string, - referenceLatitude: _propTypes.default.string.isRequired, - referenceLongitude: _propTypes.default.string.isRequired, - referenceElevation: _propTypes.default.string.isRequired + horVer: _propTypes.default.string.isRequired, + azimuth: _propTypes.default.number, + pitch: _propTypes.default.number, + roll: _propTypes.default.number, + sensorStartDateTime: _propTypes.default.string, + sensorEndDateTime: _propTypes.default.string, + xOffset: _propTypes.default.number, + yOffset: _propTypes.default.number, + zOffset: _propTypes.default.number, + referenceLocationStartDateTime: _propTypes.default.string, + referenceLocationEndDateTime: _propTypes.default.string, + referenceLocationLatitude: _propTypes.default.number, + referenceLocationLongitude: _propTypes.default.number, + referenceLocationElevation: _propTypes.default.number })).isRequired }; PositionHistoryButton.defaultProps = { @@ -532,25 +539,26 @@ function PositionDetail(props) { var history = state.product.sites[siteCode].positions[position].history; var current = history.length - 1 || 0; var _ref2 = history[current] || {}, - name = _ref2.name, - description = _ref2.description, - referenceName = _ref2.referenceName, - referenceDescription = _ref2.referenceDescription, - _ref2$referenceElevat = _ref2.referenceElevation, - referenceElevation = _ref2$referenceElevat === void 0 ? '--' : _ref2$referenceElevat, - _ref2$xOffset = _ref2.xOffset, - xOffset = _ref2$xOffset === void 0 ? '--' : _ref2$xOffset, - _ref2$yOffset = _ref2.yOffset, - yOffset = _ref2$yOffset === void 0 ? '--' : _ref2$yOffset, - _ref2$zOffset = _ref2.zOffset, - zOffset = _ref2$zOffset === void 0 ? '--' : _ref2$zOffset; - var hasReferenceElevation = (0, _typeUtil.exists)(referenceElevation) && referenceElevation !== ''; - var hasZOffset = (0, _typeUtil.exists)(zOffset) && zOffset !== '' && zOffset !== '--'; - var parsedReferenceElevation = hasReferenceElevation ? parseFloat(referenceElevation, 10) : NaN; - var parsedZOffset = hasZOffset ? parseFloat(zOffset, 10) : NaN; + sensorName = _ref2.sensorName, + sensorDescription = _ref2.sensorDescription, + referenceLocationName = _ref2.referenceLocationName, + referenceLocationDescription = _ref2.referenceLocationDescription, + referenceLocationElevation = _ref2.referenceLocationElevation, + xOffset = _ref2.xOffset, + yOffset = _ref2.yOffset, + zOffset = _ref2.zOffset; + var hasReferenceElevation = (0, _typeUtil.exists)(referenceLocationElevation) && !isNaN(referenceLocationElevation); + var hasXOffset = (0, _typeUtil.exists)(xOffset) && !isNaN(xOffset); + var hasYOffset = (0, _typeUtil.exists)(yOffset) && !isNaN(yOffset); + var hasZOffset = (0, _typeUtil.exists)(zOffset) && !isNaN(zOffset); + var parsedReferenceElevation = hasReferenceElevation ? referenceLocationElevation : NaN; + var parsedZOffset = hasZOffset ? zOffset : NaN; + var displayXOffset = hasXOffset ? "".concat(xOffset, "m") : '--'; + var displayYOffset = hasYOffset ? "".concat(yOffset, "m") : '--'; + var displayZOffset = hasZOffset ? "".concat(zOffset, "m") : '--'; var elevation = '--'; if (!isNaN(parsedReferenceElevation)) { - if (!isNaN(hasZOffset)) { + if (!isNaN(parsedZOffset)) { elevation = "".concat((parsedReferenceElevation + parsedZOffset).toFixed(2).toString(), "m"); } else { elevation = "".concat(parsedReferenceElevation, "m"); @@ -564,15 +572,15 @@ function PositionDetail(props) { fontWeight: 600 }; var renderDescription = function renderDescription() { - var hasName = (0, _typeUtil.isStringNonEmpty)(name); - var hasDescription = (0, _typeUtil.isStringNonEmpty)(description); - var hasReferenceName = (0, _typeUtil.isStringNonEmpty)(referenceName); - var hasReferenceDescription = (0, _typeUtil.isStringNonEmpty)(referenceDescription); - var appliedName = hasName ? name : 'N/A'; - var appliedDescription = hasDescription ? description : 'N/A'; + var hasName = (0, _typeUtil.isStringNonEmpty)(sensorName); + var hasDescription = (0, _typeUtil.isStringNonEmpty)(sensorDescription); + var hasReferenceName = (0, _typeUtil.isStringNonEmpty)(referenceLocationName); + var hasReferenceDescription = (0, _typeUtil.isStringNonEmpty)(referenceLocationDescription); + var appliedName = hasName ? sensorName : 'N/A'; + var appliedDescription = hasDescription ? sensorDescription : 'N/A'; var includeReference = hasReferenceName || hasReferenceDescription; - var appliedReferenceName = hasReferenceName ? referenceName : 'N/A'; - var appliedReferenceDescription = hasReferenceDescription ? referenceDescription : 'N/A'; + var appliedReferenceName = hasReferenceName ? referenceLocationName : 'N/A'; + var appliedReferenceDescription = hasReferenceDescription ? referenceLocationDescription : 'N/A'; return wide ? /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement("div", { className: classes.startFlex, style: _extends({}, fadeStyle, { @@ -681,7 +689,7 @@ function PositionDetail(props) { variant: "body2" }, /*#__PURE__*/_react.default.createElement("span", { style: _extends({}, axisStyle) - }, "x / y / z:"), "".concat(xOffset, "m / ").concat(yOffset, "m / ").concat(zOffset, "m"))), renderDescription()) : /*#__PURE__*/_react.default.createElement("div", { + }, "x / y / z:"), "".concat(displayXOffset, " / ").concat(displayYOffset, " / ").concat(displayZOffset))), renderDescription()) : /*#__PURE__*/_react.default.createElement("div", { ref: containerRef, className: classes.startFlex, style: positionContainerStyle @@ -725,11 +733,11 @@ function PositionDetail(props) { variant: "body2" }, /*#__PURE__*/_react.default.createElement("span", { style: _extends({}, axisStyle) - }, "x:"), "".concat(xOffset, "m"), /*#__PURE__*/_react.default.createElement("br", null), /*#__PURE__*/_react.default.createElement("span", { + }, "x:"), "".concat(displayXOffset), /*#__PURE__*/_react.default.createElement("br", null), /*#__PURE__*/_react.default.createElement("span", { style: _extends({}, axisStyle) - }, "y:"), "".concat(yOffset, "m"), /*#__PURE__*/_react.default.createElement("br", null), /*#__PURE__*/_react.default.createElement("span", { + }, "y:"), "".concat(displayYOffset), /*#__PURE__*/_react.default.createElement("br", null), /*#__PURE__*/_react.default.createElement("span", { style: _extends({}, axisStyle) - }, "z:"), "".concat(zOffset, "m")))), renderDescription()), /*#__PURE__*/_react.default.createElement("div", { + }, "z:"), "".concat(displayZOffset)))), renderDescription()), /*#__PURE__*/_react.default.createElement("div", { style: historyButtonContainerStyle }, /*#__PURE__*/_react.default.createElement(PositionHistoryButton, { siteCode: siteCode, diff --git a/lib/parser/DataPackageParser.d.ts b/lib/parser/DataPackageParser.d.ts new file mode 100644 index 00000000..5841ddf0 --- /dev/null +++ b/lib/parser/DataPackageParser.d.ts @@ -0,0 +1,13 @@ +import { Nullable, UnknownRecord } from '../types/core'; +import { SensorPosition } from '../types/internal'; +export interface IDataPackageParser { + /** + * Parse a data package sensor position record into an + * internal shape based on detected schema version. + * @param position The position to parse + * @returns The parsed position to an internal, normalized shape. + */ + parseSensorPosition: (position: Nullable) => Nullable; +} +declare const DataPackageParser: IDataPackageParser; +export default DataPackageParser; diff --git a/lib/parser/DataPackageParser.js b/lib/parser/DataPackageParser.js new file mode 100644 index 00000000..3eb9acb9 --- /dev/null +++ b/lib/parser/DataPackageParser.js @@ -0,0 +1,83 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = void 0; +var _typeUtil = require("../util/typeUtil"); +var _neonDataPackage = require("../types/neonDataPackage"); +var DataPackageParser = { + parseSensorPosition: function parseSensorPosition(position) { + if (!(0, _typeUtil.exists)(position)) { + return null; + } + var result = null; + var resultV2 = _neonDataPackage.sensorPositionV2Schema.safeParse(position); + if (!resultV2.success) { + // Check for v1 schema + var resultV1 = _neonDataPackage.sensorPositionV1Schema.safeParse(position); + if (!resultV1.success) { + var v2ErrorFormat = resultV2.error.format(); + var v1ErrorFormat = resultV1.error.format(); + // eslint-disable-next-line no-console + console.error('Failed to detect sensor position schema', v2ErrorFormat, v1ErrorFormat); + } else { + var parsedV1 = resultV1.data; + result = { + horVer: parsedV1['HOR.VER'], + sensorName: (0, _typeUtil.isStringNonEmpty)(parsedV1.name) ? parsedV1.name : 'N/A', + sensorDescription: parsedV1.description, + sensorStartDateTime: parsedV1.start, + sensorEndDateTime: parsedV1.end, + referenceLocationName: parsedV1.referenceName, + referenceLocationDescription: parsedV1.referenceDescription, + referenceLocationStartDateTime: parsedV1.referenceStart, + referenceLocationEndDateTime: parsedV1.referenceEnd, + xOffset: parsedV1.xOffset, + yOffset: parsedV1.yOffset, + zOffset: parsedV1.zOffset, + pitch: parsedV1.pitch, + roll: parsedV1.roll, + azimuth: parsedV1.azimuth, + referenceLocationLatitude: parsedV1.referenceLatitude, + referenceLocationLongitude: parsedV1.referenceLongitude, + referenceLocationElevation: parsedV1.referenceElevation, + eastOffset: parsedV1.eastOffset, + northOffset: parsedV1.northOffset, + xAzimuth: parsedV1.xAzimuth, + yAzimuth: parsedV1.yAzimuth + }; + } + } else { + var parsedV2 = resultV2.data; + result = { + horVer: parsedV2['HOR.VER'], + sensorName: parsedV2.sensorLocationID, + sensorDescription: parsedV2.sensorLocationDescription, + sensorStartDateTime: parsedV2.positionStartDateTime, + sensorEndDateTime: parsedV2.positionEndDateTime, + referenceLocationName: parsedV2.referenceLocationID, + referenceLocationDescription: parsedV2.referenceLocationIDDescription, + referenceLocationStartDateTime: parsedV2.referenceLocationIDStartDateTime, + referenceLocationEndDateTime: parsedV2.referenceLocationIDEndDateTime, + xOffset: parsedV2.xOffset, + yOffset: parsedV2.yOffset, + zOffset: parsedV2.zOffset, + pitch: parsedV2.pitch, + roll: parsedV2.roll, + azimuth: parsedV2.azimuth, + referenceLocationLatitude: parsedV2.locationReferenceLatitude, + referenceLocationLongitude: parsedV2.locationReferenceLongitude, + referenceLocationElevation: parsedV2.locationReferenceElevation, + eastOffset: parsedV2.eastOffset, + northOffset: parsedV2.northOffset, + xAzimuth: parsedV2.xAzimuth, + yAzimuth: parsedV2.yAzimuth + }; + } + return result; + } +}; +Object.freeze(DataPackageParser); +var _default = DataPackageParser; +exports.default = _default; \ No newline at end of file diff --git a/lib/types/internal.d.ts b/lib/types/internal.d.ts index d079512b..bc24c633 100644 --- a/lib/types/internal.d.ts +++ b/lib/types/internal.d.ts @@ -19,3 +19,27 @@ export interface CitationBundleState { parentCodes: string[]; doiProductCode: Nullable; } +export interface SensorPosition { + horVer: string; + sensorName: string; + sensorDescription: Nullable; + sensorStartDateTime: Nullable; + sensorEndDateTime: Nullable; + referenceLocationName: Nullable; + referenceLocationDescription: Nullable; + referenceLocationStartDateTime: Nullable; + referenceLocationEndDateTime: Nullable; + xOffset: Nullable; + yOffset: Nullable; + zOffset: Nullable; + pitch: Nullable; + roll: Nullable; + azimuth: Nullable; + referenceLocationLatitude: Nullable; + referenceLocationLongitude: Nullable; + referenceLocationElevation: Nullable; + eastOffset: Nullable; + northOffset: Nullable; + xAzimuth: Nullable; + yAzimuth: Nullable; +} diff --git a/lib/types/neonDataPackage.d.ts b/lib/types/neonDataPackage.d.ts new file mode 100644 index 00000000..1f01b77a --- /dev/null +++ b/lib/types/neonDataPackage.d.ts @@ -0,0 +1,144 @@ +import { z } from 'zod'; +declare const sensorPositionV1Schema: z.ZodObject<{ + 'HOR.VER': z.ZodString; + name: z.ZodNullable>; + description: z.ZodNullable>; + start: z.ZodNullable>; + end: z.ZodNullable>; + referenceName: z.ZodNullable>; + referenceDescription: z.ZodNullable>; + referenceStart: z.ZodNullable>; + referenceEnd: z.ZodNullable>; + xOffset: z.ZodNullable>, number | null | undefined, string | null | undefined>>; + yOffset: z.ZodNullable>, number | null | undefined, string | null | undefined>>; + zOffset: z.ZodNullable>, number | null | undefined, string | null | undefined>>; + pitch: z.ZodNullable>, number | null | undefined, string | null | undefined>>; + roll: z.ZodNullable>, number | null | undefined, string | null | undefined>>; + azimuth: z.ZodNullable>, number | null | undefined, string | null | undefined>>; + referenceLatitude: z.ZodNullable>, number | null | undefined, string | null | undefined>>; + referenceLongitude: z.ZodNullable>, number | null | undefined, string | null | undefined>>; + referenceElevation: z.ZodNullable>, number | null | undefined, string | null | undefined>>; + eastOffset: z.ZodNullable>, number | null | undefined, string | null | undefined>>; + northOffset: z.ZodNullable>, number | null | undefined, string | null | undefined>>; + xAzimuth: z.ZodNullable>, number | null | undefined, string | null | undefined>>; + yAzimuth: z.ZodNullable>, number | null | undefined, string | null | undefined>>; +}, "strip", z.ZodTypeAny, { + name?: string | null | undefined; + azimuth?: number | null | undefined; + end?: string | null | undefined; + start?: string | null | undefined; + description?: string | null | undefined; + referenceName?: string | null | undefined; + referenceDescription?: string | null | undefined; + referenceStart?: string | null | undefined; + referenceEnd?: string | null | undefined; + xOffset?: number | null | undefined; + yOffset?: number | null | undefined; + zOffset?: number | null | undefined; + pitch?: number | null | undefined; + roll?: number | null | undefined; + referenceLatitude?: number | null | undefined; + referenceLongitude?: number | null | undefined; + referenceElevation?: number | null | undefined; + eastOffset?: number | null | undefined; + northOffset?: number | null | undefined; + xAzimuth?: number | null | undefined; + yAzimuth?: number | null | undefined; + 'HOR.VER': string; +}, { + name?: string | null | undefined; + azimuth?: string | null | undefined; + end?: string | null | undefined; + start?: string | null | undefined; + description?: string | null | undefined; + referenceName?: string | null | undefined; + referenceDescription?: string | null | undefined; + referenceStart?: string | null | undefined; + referenceEnd?: string | null | undefined; + xOffset?: string | null | undefined; + yOffset?: string | null | undefined; + zOffset?: string | null | undefined; + pitch?: string | null | undefined; + roll?: string | null | undefined; + referenceLatitude?: string | null | undefined; + referenceLongitude?: string | null | undefined; + referenceElevation?: string | null | undefined; + eastOffset?: string | null | undefined; + northOffset?: string | null | undefined; + xAzimuth?: string | null | undefined; + yAzimuth?: string | null | undefined; + 'HOR.VER': string; +}>; +declare const sensorPositionV2Schema: z.ZodObject<{ + 'HOR.VER': z.ZodString; + sensorLocationID: z.ZodString; + sensorLocationDescription: z.ZodNullable; + positionStartDateTime: z.ZodNullable; + positionEndDateTime: z.ZodNullable; + referenceLocationID: z.ZodNullable>; + referenceLocationIDDescription: z.ZodNullable>; + referenceLocationIDStartDateTime: z.ZodNullable>; + referenceLocationIDEndDateTime: z.ZodNullable>; + xOffset: z.ZodNullable, number | null | undefined, string | null>>; + yOffset: z.ZodNullable, number | null | undefined, string | null>>; + zOffset: z.ZodNullable, number | null | undefined, string | null>>; + pitch: z.ZodNullable, number | null | undefined, string | null>>; + roll: z.ZodNullable, number | null | undefined, string | null>>; + azimuth: z.ZodNullable, number | null | undefined, string | null>>; + locationReferenceLatitude: z.ZodNullable>, number | null | undefined, string | null | undefined>>; + locationReferenceLongitude: z.ZodNullable>, number | null | undefined, string | null | undefined>>; + locationReferenceElevation: z.ZodNullable>, number | null | undefined, string | null | undefined>>; + eastOffset: z.ZodNullable>, number | null | undefined, string | null | undefined>>; + northOffset: z.ZodNullable>, number | null | undefined, string | null | undefined>>; + xAzimuth: z.ZodNullable>, number | null | undefined, string | null | undefined>>; + yAzimuth: z.ZodNullable>, number | null | undefined, string | null | undefined>>; +}, "strip", z.ZodTypeAny, { + azimuth?: number | null | undefined; + xOffset?: number | null | undefined; + yOffset?: number | null | undefined; + zOffset?: number | null | undefined; + pitch?: number | null | undefined; + roll?: number | null | undefined; + eastOffset?: number | null | undefined; + northOffset?: number | null | undefined; + xAzimuth?: number | null | undefined; + yAzimuth?: number | null | undefined; + referenceLocationID?: string | null | undefined; + referenceLocationIDDescription?: string | null | undefined; + referenceLocationIDStartDateTime?: string | null | undefined; + referenceLocationIDEndDateTime?: string | null | undefined; + locationReferenceLatitude?: number | null | undefined; + locationReferenceLongitude?: number | null | undefined; + locationReferenceElevation?: number | null | undefined; + 'HOR.VER': string; + sensorLocationID: string; + sensorLocationDescription: string | null; + positionStartDateTime: string | null; + positionEndDateTime: string | null; +}, { + eastOffset?: string | null | undefined; + northOffset?: string | null | undefined; + xAzimuth?: string | null | undefined; + yAzimuth?: string | null | undefined; + referenceLocationID?: string | null | undefined; + referenceLocationIDDescription?: string | null | undefined; + referenceLocationIDStartDateTime?: string | null | undefined; + referenceLocationIDEndDateTime?: string | null | undefined; + locationReferenceLatitude?: string | null | undefined; + locationReferenceLongitude?: string | null | undefined; + locationReferenceElevation?: string | null | undefined; + azimuth: string | null; + 'HOR.VER': string; + xOffset: string | null; + yOffset: string | null; + zOffset: string | null; + pitch: string | null; + roll: string | null; + sensorLocationID: string; + sensorLocationDescription: string | null; + positionStartDateTime: string | null; + positionEndDateTime: string | null; +}>; +export type SensorPositionV1Type = z.infer; +export type SensorPositionV2Type = z.infer; +export { sensorPositionV1Schema, sensorPositionV2Schema, }; diff --git a/lib/types/neonDataPackage.js b/lib/types/neonDataPackage.js new file mode 100644 index 00000000..8564c468 --- /dev/null +++ b/lib/types/neonDataPackage.js @@ -0,0 +1,62 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.sensorPositionV2Schema = exports.sensorPositionV1Schema = void 0; +var _zod = require("zod"); +var _typeUtil = require("../util/typeUtil"); +var transformStringToFloat = function transformStringToFloat(val, ctx) { + if (!(0, _typeUtil.isStringNonEmpty)(val)) return null; + return parseFloat(val); +}; +var sensorPositionV1Schema = _zod.z.object({ + 'HOR.VER': _zod.z.string(), + name: _zod.z.string().optional().nullable(), + description: _zod.z.string().optional().nullable(), + start: _zod.z.string().optional().nullable(), + end: _zod.z.string().optional().nullable(), + referenceName: _zod.z.string().optional().nullable(), + referenceDescription: _zod.z.string().optional().nullable(), + referenceStart: _zod.z.string().optional().nullable(), + referenceEnd: _zod.z.string().optional().nullable(), + xOffset: _zod.z.string().optional().nullable().transform(transformStringToFloat).nullable(), + yOffset: _zod.z.string().optional().nullable().transform(transformStringToFloat).nullable(), + zOffset: _zod.z.string().optional().nullable().transform(transformStringToFloat).nullable(), + pitch: _zod.z.string().optional().nullable().transform(transformStringToFloat).nullable(), + roll: _zod.z.string().optional().nullable().transform(transformStringToFloat).nullable(), + azimuth: _zod.z.string().optional().nullable().transform(transformStringToFloat).nullable(), + referenceLatitude: _zod.z.string().optional().nullable().transform(transformStringToFloat).nullable(), + referenceLongitude: _zod.z.string().optional().nullable().transform(transformStringToFloat).nullable(), + referenceElevation: _zod.z.string().optional().nullable().transform(transformStringToFloat).nullable(), + eastOffset: _zod.z.string().optional().nullable().transform(transformStringToFloat).nullable(), + northOffset: _zod.z.string().optional().nullable().transform(transformStringToFloat).nullable(), + xAzimuth: _zod.z.string().optional().nullable().transform(transformStringToFloat).nullable(), + yAzimuth: _zod.z.string().optional().nullable().transform(transformStringToFloat).nullable() +}); +exports.sensorPositionV1Schema = sensorPositionV1Schema; +var sensorPositionV2Schema = _zod.z.object({ + 'HOR.VER': _zod.z.string(), + sensorLocationID: _zod.z.string(), + sensorLocationDescription: _zod.z.string().nullable(), + positionStartDateTime: _zod.z.string().nullable(), + positionEndDateTime: _zod.z.string().nullable(), + referenceLocationID: _zod.z.string().optional().nullable(), + referenceLocationIDDescription: _zod.z.string().optional().nullable(), + referenceLocationIDStartDateTime: _zod.z.string().optional().nullable(), + referenceLocationIDEndDateTime: _zod.z.string().optional().nullable(), + xOffset: _zod.z.string().nullable().transform(transformStringToFloat).nullable(), + yOffset: _zod.z.string().nullable().transform(transformStringToFloat).nullable(), + zOffset: _zod.z.string().nullable().transform(transformStringToFloat).nullable(), + pitch: _zod.z.string().nullable().transform(transformStringToFloat).nullable(), + roll: _zod.z.string().nullable().transform(transformStringToFloat).nullable(), + azimuth: _zod.z.string().nullable().transform(transformStringToFloat).nullable(), + locationReferenceLatitude: _zod.z.string().optional().nullable().transform(transformStringToFloat).nullable(), + locationReferenceLongitude: _zod.z.string().optional().nullable().transform(transformStringToFloat).nullable(), + locationReferenceElevation: _zod.z.string().optional().nullable().transform(transformStringToFloat).nullable(), + eastOffset: _zod.z.string().optional().nullable().transform(transformStringToFloat).nullable(), + northOffset: _zod.z.string().optional().nullable().transform(transformStringToFloat).nullable(), + xAzimuth: _zod.z.string().optional().nullable().transform(transformStringToFloat).nullable(), + yAzimuth: _zod.z.string().optional().nullable().transform(transformStringToFloat).nullable() +}); +exports.sensorPositionV2Schema = sensorPositionV2Schema; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 4f7c43a9..f2f07f1b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -63,7 +63,8 @@ "sockjs-client": "^1.6.1", "tinycolor2": "^1.4.2", "ua-parser-js": "^1.0.32", - "universal-cookie": "^4.0.4" + "universal-cookie": "^4.0.4", + "zod": "^3.20.0" }, "devDependencies": { "@babel/cli": "^7.19.3", @@ -23871,6 +23872,14 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zod": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.20.0.tgz", + "integrity": "sha512-ZWxs7oM5ixoo1BMoxTNeDMYSih/F/FUnExsnRtHT04rG6q0Bd74TKS45RGXw07TOalOZyyzdKaYH38k8yTEv9A==", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/package.json b/package.json index 21a72d9d..b2d0280c 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,8 @@ "sockjs-client": "^1.6.1", "tinycolor2": "^1.4.2", "ua-parser-js": "^1.0.32", - "universal-cookie": "^4.0.4" + "universal-cookie": "^4.0.4", + "zod": "^3.20.0" }, "peerDependencies": { "worker-loader": "^3.0.8" diff --git a/src/lib_components/components/TimeSeriesViewer/TimeSeriesViewerContext.jsx b/src/lib_components/components/TimeSeriesViewer/TimeSeriesViewerContext.jsx index 7487a0ee..fab36bc1 100644 --- a/src/lib_components/components/TimeSeriesViewer/TimeSeriesViewerContext.jsx +++ b/src/lib_components/components/TimeSeriesViewer/TimeSeriesViewerContext.jsx @@ -29,9 +29,11 @@ import NeonApi from '../NeonApi/NeonApi'; import NeonGraphQL from '../NeonGraphQL/NeonGraphQL'; import NeonEnvironment from '../NeonEnvironment/NeonEnvironment'; import { forkJoinWithProgress } from '../../util/rxUtil'; +import { exists } from '../../util/typeUtil'; import parseTimeSeriesData from '../../workers/parseTimeSeriesData'; +import DataPackageParser from '../../parser/DataPackageParser'; import NeonSignInButtonState from '../NeonSignInButton/NeonSignInButtonState'; import makeStateStorage from '../../service/StateStorageService'; import { convertStateForStorage, convertStateFromStorage } from './StateStorageConverter'; @@ -497,6 +499,11 @@ const parseSiteVariables = (previousVariables, siteCode, csv) => { return { variablesSet, variablesObject: newStateVariables }; }; +const parsePosition = (position) => { + if (!exists(position)) return position; + return DataPackageParser.parseSensorPosition(position); +}; + /** * Build an object for state.product.sites[{site}] from a product/site positions fetch response * The site object should already have site position ids from the product/site/month fetch, so the @@ -509,15 +516,18 @@ const parseSitePositions = (site, csv) => { const newSite = { ...site }; const positions = parseCSV(csv, true); // Duplicated lines have been unintentionally seen here! positions.data.forEach((position) => { - const posId = position['HOR.VER']; - if (!newSite.positions[posId]) { newSite.positions[posId] = { data: {}, history: [] }; } - newSite.positions[posId].history.push(position); + const parsedPosition = parsePosition(position); + if (exists(parsedPosition)) { + const posId = parsedPosition.horVer; + if (!newSite.positions[posId]) { newSite.positions[posId] = { data: {}, history: [] }; } + newSite.positions[posId].history.push(parsedPosition); + } }); // Sort position history by start/end time descending Object.keys(newSite.positions).forEach((posId) => { newSite.positions[posId].history.sort((a, b) => { - if (!a.end) { return 1; } - return (a.end < b.start) ? -1 : 1; + if (!a.sensorEndDateTime) { return 1; } + return (a.sensorEndDateTime < b.sensorStartDateTime) ? -1 : 1; }); }); return newSite; diff --git a/src/lib_components/components/TimeSeriesViewer/TimeSeriesViewerSites.jsx b/src/lib_components/components/TimeSeriesViewer/TimeSeriesViewerSites.jsx index 570354dd..b8f7ef61 100644 --- a/src/lib_components/components/TimeSeriesViewer/TimeSeriesViewerSites.jsx +++ b/src/lib_components/components/TimeSeriesViewer/TimeSeriesViewerSites.jsx @@ -381,38 +381,46 @@ function PositionHistoryButton(props) { {history.map((row, idx) => { const { - start = '', - end: rawEnd = '', + sensorStartDateTime = '', + sensorEndDateTime: rawEnd = '', xOffset, yOffset, zOffset, - referenceElevation, + referenceLocationElevation, } = row; - const hasReferenceElevation = exists(referenceElevation) && (referenceElevation !== ''); - const hasZOffset = exists(zOffset) && (zOffset !== ''); + const hasReferenceElevation = exists(referenceLocationElevation) + && !isNaN(referenceLocationElevation); + const hasXOffset = exists(xOffset) && !isNaN(xOffset); + const hasYOffset = exists(yOffset) && !isNaN(yOffset); + const hasZOffset = exists(zOffset) && !isNaN(zOffset); const parsedReferenceElevation = hasReferenceElevation - ? parseFloat(referenceElevation, 10) + ? referenceLocationElevation : NaN; - const parsedZOffset = hasZOffset ? parseFloat(zOffset, 10) : NaN; + const parsedXOffset = hasXOffset ? xOffset : NaN; + const parsedYOffset = hasYOffset ? yOffset : NaN; + const parsedZOffset = hasZOffset ? zOffset : NaN; let elevation = 'unknown'; if (!isNaN(parsedReferenceElevation)) { - if (!isNaN(hasZOffset)) { + if (!isNaN(parsedZOffset)) { elevation = `${(parsedReferenceElevation + parsedZOffset).toFixed(2).toString()}m`; } else { elevation = `${parsedReferenceElevation}m`; } } + const displayXOffset = hasXOffset ? `${xOffset}m` : '--'; + const displayYOffset = hasYOffset ? `${yOffset}m` : '--'; + const displayZOffset = hasZOffset ? `${zOffset}m` : '--'; const end = rawEnd === '' ? 'Current' : rawEnd; const cellStyle = idx !== history.length - 1 ? {} : { fontWeight: '600', borderBottom: 'none' }; - const key = `${start}${end}${xOffset}${yOffset}${zOffset}`; + const key = `${sensorStartDateTime}${end}${parsedXOffset}${parsedYOffset}${parsedZOffset}`; return ( - {start} + {sensorStartDateTime} {end} - {`${xOffset}m`} - {`${yOffset}m`} - {`${zOffset}m`} + {displayXOffset} + {displayYOffset} + {displayZOffset} {elevation} ); @@ -436,20 +444,20 @@ PositionHistoryButton.propTypes = { position: PropTypes.string.isRequired, fullWidth: PropTypes.bool, history: PropTypes.arrayOf(PropTypes.shape({ - 'HOR.VER': PropTypes.string.isRequired, - azimuth: PropTypes.string.isRequired, - pitch: PropTypes.string.isRequired, - roll: PropTypes.string.isRequired, - start: PropTypes.string, - end: PropTypes.string, - xOffset: PropTypes.string.isRequired, - yOffset: PropTypes.string.isRequired, - zOffset: PropTypes.string.isRequired, - referenceStart: PropTypes.string, - referenceEnd: PropTypes.string, - referenceLatitude: PropTypes.string.isRequired, - referenceLongitude: PropTypes.string.isRequired, - referenceElevation: PropTypes.string.isRequired, + horVer: PropTypes.string.isRequired, + azimuth: PropTypes.number, + pitch: PropTypes.number, + roll: PropTypes.number, + sensorStartDateTime: PropTypes.string, + sensorEndDateTime: PropTypes.string, + xOffset: PropTypes.number, + yOffset: PropTypes.number, + zOffset: PropTypes.number, + referenceLocationStartDateTime: PropTypes.string, + referenceLocationEndDateTime: PropTypes.string, + referenceLocationLatitude: PropTypes.number, + referenceLocationLongitude: PropTypes.number, + referenceLocationElevation: PropTypes.number, })).isRequired, }; @@ -505,24 +513,30 @@ function PositionDetail(props) { const { history } = state.product.sites[siteCode].positions[position]; const current = history.length - 1 || 0; const { - name, - description, - referenceName, - referenceDescription, - referenceElevation = '--', - xOffset = '--', - yOffset = '--', - zOffset = '--', + sensorName, + sensorDescription, + referenceLocationName, + referenceLocationDescription, + referenceLocationElevation, + xOffset, + yOffset, + zOffset, } = history[current] || {}; - const hasReferenceElevation = exists(referenceElevation) && (referenceElevation !== ''); - const hasZOffset = exists(zOffset) && (zOffset !== '') && (zOffset !== '--'); + const hasReferenceElevation = exists(referenceLocationElevation) + && !isNaN(referenceLocationElevation); + const hasXOffset = exists(xOffset) && !isNaN(xOffset); + const hasYOffset = exists(yOffset) && !isNaN(yOffset); + const hasZOffset = exists(zOffset) && !isNaN(zOffset); const parsedReferenceElevation = hasReferenceElevation - ? parseFloat(referenceElevation, 10) + ? referenceLocationElevation : NaN; - const parsedZOffset = hasZOffset ? parseFloat(zOffset, 10) : NaN; + const parsedZOffset = hasZOffset ? zOffset : NaN; + const displayXOffset = hasXOffset ? `${xOffset}m` : '--'; + const displayYOffset = hasYOffset ? `${yOffset}m` : '--'; + const displayZOffset = hasZOffset ? `${zOffset}m` : '--'; let elevation = '--'; if (!isNaN(parsedReferenceElevation)) { - if (!isNaN(hasZOffset)) { + if (!isNaN(parsedZOffset)) { elevation = `${(parsedReferenceElevation + parsedZOffset).toFixed(2).toString()}m`; } else { elevation = `${parsedReferenceElevation}m`; @@ -531,15 +545,17 @@ function PositionDetail(props) { const fadeStyle = { color: Theme.palette.grey[500] }; const axisStyle = { marginRight: Theme.spacing(1), fontWeight: 600 }; const renderDescription = () => { - const hasName = isStringNonEmpty(name); - const hasDescription = isStringNonEmpty(description); - const hasReferenceName = isStringNonEmpty(referenceName); - const hasReferenceDescription = isStringNonEmpty(referenceDescription); - const appliedName = hasName ? name : 'N/A'; - const appliedDescription = hasDescription ? description : 'N/A'; + const hasName = isStringNonEmpty(sensorName); + const hasDescription = isStringNonEmpty(sensorDescription); + const hasReferenceName = isStringNonEmpty(referenceLocationName); + const hasReferenceDescription = isStringNonEmpty(referenceLocationDescription); + const appliedName = hasName ? sensorName : 'N/A'; + const appliedDescription = hasDescription ? sensorDescription : 'N/A'; const includeReference = hasReferenceName || hasReferenceDescription; - const appliedReferenceName = hasReferenceName ? referenceName : 'N/A'; - const appliedReferenceDescription = hasReferenceDescription ? referenceDescription : 'N/A'; + const appliedReferenceName = hasReferenceName ? referenceLocationName : 'N/A'; + const appliedReferenceDescription = hasReferenceDescription + ? referenceLocationDescription + : 'N/A'; return wide ? ( <>
x / y / z: - {`${xOffset}m / ${yOffset}m / ${zOffset}m`} + {`${displayXOffset} / ${displayYOffset} / ${displayZOffset}`}
{renderDescription()} @@ -670,13 +686,13 @@ function PositionDetail(props) {
x: - {`${xOffset}m`} + {`${displayXOffset}`}
y: - {`${yOffset}m`} + {`${displayYOffset}`}
z: - {`${zOffset}m`} + {`${displayZOffset}`}
diff --git a/src/lib_components/components/TimeSeriesViewer/__tests__/TimeSeriesViewerContext.jsx b/src/lib_components/components/TimeSeriesViewer/__tests__/TimeSeriesViewerContext.jsx index 0400fbe8..c46f76c6 100644 --- a/src/lib_components/components/TimeSeriesViewer/__tests__/TimeSeriesViewerContext.jsx +++ b/src/lib_components/components/TimeSeriesViewer/__tests__/TimeSeriesViewerContext.jsx @@ -467,14 +467,28 @@ table,fieldName,description,dataType,units,downloadPkg,pubFormat '000.010': { history: [ { - 'HOR.VER': '000.010', - name: 'CFGLOC102010', - description: 'Abby Road 2D Wind L1', - start: '2001-01-01T00:00:00Z', - end: '2009-01-01T00:00:00Z', - xOffset: '2.36', - yOffset: '6.25', - zOffset: '0.22', + horVer: '000.010', + sensorName: 'CFGLOC102010', + sensorDescription: 'Abby Road 2D Wind L1', + sensorStartDateTime: '2001-01-01T00:00:00Z', + sensorEndDateTime: '2009-01-01T00:00:00Z', + xOffset: 2.36, + yOffset: 6.25, + zOffset: 0.22, + referenceLocationName: undefined, + referenceLocationDescription: undefined, + referenceLocationStartDateTime: undefined, + referenceLocationEndDateTime: undefined, + pitch: null, + roll: null, + azimuth: null, + referenceLocationLatitude: null, + referenceLocationLongitude: null, + referenceLocationElevation: null, + eastOffset: null, + northOffset: null, + xAzimuth: null, + yAzimuth: null, }, ], data: {}, @@ -494,34 +508,76 @@ HOR.VER,name,description,start,end,xOffset,yOffset,zOffset '000.010': { history: [ { - 'HOR.VER': '000.010', - name: 'CFGLOC102010', - description: 'Abby Road 2D Wind L1', - start: '2001-01-01T00:00:00Z', - end: '2009-01-01T00:00:00Z', - xOffset: '2.36', - yOffset: '6.25', - zOffset: '0.22', + horVer: '000.010', + sensorName: 'CFGLOC102010', + sensorDescription: 'Abby Road 2D Wind L1', + sensorStartDateTime: '2001-01-01T00:00:00Z', + sensorEndDateTime: '2009-01-01T00:00:00Z', + xOffset: 2.36, + yOffset: 6.25, + zOffset: 0.22, + referenceLocationName: undefined, + referenceLocationDescription: undefined, + referenceLocationStartDateTime: undefined, + referenceLocationEndDateTime: undefined, + pitch: null, + roll: null, + azimuth: null, + referenceLocationLatitude: null, + referenceLocationLongitude: null, + referenceLocationElevation: null, + eastOffset: null, + northOffset: null, + xAzimuth: null, + yAzimuth: null, }, { - 'HOR.VER': '000.010', - name: 'CFGLOC102010', - description: 'Abby Road 2D Wind L1', - start: '2010-01-01T00:00:00Z', - end: '2020-01-01T00:00:00Z', - xOffset: '2.36', - yOffset: '6.25', - zOffset: '0.22', + horVer: '000.010', + sensorName: 'CFGLOC102010', + sensorDescription: 'Abby Road 2D Wind L1', + sensorStartDateTime: '2010-01-01T00:00:00Z', + sensorEndDateTime: '2020-01-01T00:00:00Z', + xOffset: 2.36, + yOffset: 6.25, + zOffset: 0.22, + referenceLocationName: undefined, + referenceLocationDescription: undefined, + referenceLocationStartDateTime: undefined, + referenceLocationEndDateTime: undefined, + pitch: null, + roll: null, + azimuth: null, + referenceLocationLatitude: null, + referenceLocationLongitude: null, + referenceLocationElevation: null, + eastOffset: null, + northOffset: null, + xAzimuth: null, + yAzimuth: null, }, { - 'HOR.VER': '000.010', - name: 'CFGLOC102010', - description: 'Abby Road 2D Wind L1', - start: '2020-01-01T00:00:01Z', - end: '', - xOffset: '2.45', - yOffset: '6.25', - zOffset: '0.22', + horVer: '000.010', + sensorName: 'CFGLOC102010', + sensorDescription: 'Abby Road 2D Wind L1', + sensorStartDateTime: '2020-01-01T00:00:01Z', + sensorEndDateTime: '', + xOffset: 2.45, + yOffset: 6.25, + zOffset: 0.22, + referenceLocationName: undefined, + referenceLocationDescription: undefined, + referenceLocationStartDateTime: undefined, + referenceLocationEndDateTime: undefined, + pitch: null, + roll: null, + azimuth: null, + referenceLocationLatitude: null, + referenceLocationLongitude: null, + referenceLocationElevation: null, + eastOffset: null, + northOffset: null, + xAzimuth: null, + yAzimuth: null, }, ], data: {}, @@ -529,14 +585,28 @@ HOR.VER,name,description,start,end,xOffset,yOffset,zOffset '000.030': { history: [ { - 'HOR.VER': '000.030', - name: 'CFGLOC102020', - description: 'Abby Road 2D Wind L3', - start: '2010-01-01T00:00:00Z', - end: '', - xOffset: '2.36', - yOffset: '6.22', - zOffset: '9.43', + horVer: '000.030', + sensorName: 'CFGLOC102020', + sensorDescription: 'Abby Road 2D Wind L3', + sensorStartDateTime: '2010-01-01T00:00:00Z', + sensorEndDateTime: '', + xOffset: 2.36, + yOffset: 6.22, + zOffset: 9.43, + referenceLocationName: undefined, + referenceLocationDescription: undefined, + referenceLocationStartDateTime: undefined, + referenceLocationEndDateTime: undefined, + pitch: null, + roll: null, + azimuth: null, + referenceLocationLatitude: null, + referenceLocationLongitude: null, + referenceLocationElevation: null, + eastOffset: null, + northOffset: null, + xAzimuth: null, + yAzimuth: null, }, ], data: {}, @@ -1387,11 +1457,57 @@ t1_2min,v3QM,v3QMdesc,real,percent,basic,* expect(newState.product.sites.JERC.positions).toStrictEqual({ '000.010': { data: {}, - history: [{ 'HOR.VER': '000.010', name: 'CFGLOC102010' }], + history: [{ + horVer: '000.010', + sensorName: 'CFGLOC102010', + sensorDescription: undefined, + sensorStartDateTime: undefined, + sensorEndDateTime: undefined, + referenceLocationName: undefined, + referenceLocationDescription: undefined, + referenceLocationStartDateTime: undefined, + referenceLocationEndDateTime: undefined, + xOffset: null, + yOffset: null, + zOffset: null, + pitch: null, + roll: null, + azimuth: null, + referenceLocationLatitude: null, + referenceLocationLongitude: null, + referenceLocationElevation: null, + eastOffset: null, + northOffset: null, + xAzimuth: null, + yAzimuth: null, + }], }, '000.020': { data: {}, - history: [{ 'HOR.VER': '000.020', name: 'CFGLOC102015' }], + history: [{ + horVer: '000.020', + sensorName: 'CFGLOC102015', + sensorDescription: undefined, + sensorStartDateTime: undefined, + sensorEndDateTime: undefined, + referenceLocationName: undefined, + referenceLocationDescription: undefined, + referenceLocationStartDateTime: undefined, + referenceLocationEndDateTime: undefined, + xOffset: null, + yOffset: null, + zOffset: null, + pitch: null, + roll: null, + azimuth: null, + referenceLocationLatitude: null, + referenceLocationLongitude: null, + referenceLocationElevation: null, + eastOffset: null, + northOffset: null, + xAzimuth: null, + yAzimuth: null, + }], }, }); expect(newState.status).toBe(TIME_SERIES_VIEWER_STATUS.READY_FOR_DATA); @@ -1515,28 +1631,28 @@ t1_2min,v3QM,v3QMdesc,real,percent,basic,* }, history: [ { - 'HOR.VER': '000.010', - name: 'CFGLOC102010', - description: 'Abby Road 2D Wind L1', - start: '2010-01-01T00:00:00Z', - end: '', - referenceName: 'TOWER102005', - referenceDescription: 'Abby Road Tower', - referenceStart: '2010-01-01T00:00:00Z', - referenceEnd: '', - xOffset: '2.36', - yOffset: '6.25', - zOffset: '0.22', - pitch: '0.00', - roll: '0.00', - azimuth: '180.00', - referenceLatitude: '45.762439', - referenceLongitude: '-122.330317', - referenceElevation: '364.55', - eastOffset: '-2.36', - northOffset: '-6.25', - xAzimuth: '270.00', - yAzimuth: '180.00', + horVer: '000.010', + sensorName: 'CFGLOC102010', + sensorDescription: 'Abby Road 2D Wind L1', + sensorStartDateTime: '2010-01-01T00:00:00Z', + sensorEndDateTime: '', + referenceLocationName: 'TOWER102005', + referenceLocationDescription: 'Abby Road Tower', + referenceLocationStartDateTime: '2010-01-01T00:00:00Z', + referenceLocationEndDateTime: '', + xOffset: 2.36, + yOffset: 6.25, + zOffset: 0.22, + pitch: 0.00, + roll: 0.00, + azimuth: 180.00, + referenceLocationLatitude: 45.762439, + referenceLocationLongitude: -122.330317, + referenceLocationElevation: 364.55, + eastOffset: -2.36, + northOffset: -6.25, + xAzimuth: 270.00, + yAzimuth: 180.00, }, ], }, diff --git a/src/lib_components/components/TimeSeriesViewer/__tests__/TimeSeriesViewerSites.jsx b/src/lib_components/components/TimeSeriesViewer/__tests__/TimeSeriesViewerSites.jsx index c92414bc..f5de2310 100644 --- a/src/lib_components/components/TimeSeriesViewer/__tests__/TimeSeriesViewerSites.jsx +++ b/src/lib_components/components/TimeSeriesViewer/__tests__/TimeSeriesViewerSites.jsx @@ -31,30 +31,30 @@ const { const history010 = [ { - 'HOR.VER': '000.010', - start: '2019-02-02', - end: '2020-01-09', - azimuth: '0', - pitch: '0', - roll: '0', - xOffset: '2', - yOffset: '1', - zOffset: '0', - referenceLatitude: '67.82', - referenceLongitude: '120.03', - referenceElevation: '980', + horVer: '000.010', + sensorStartDateTime: '2019-02-02', + sensorEndDateTime: '2020-01-09', + azimuth: 0, + pitch: 0, + roll: 0, + xOffset: 2, + yOffset: 1, + zOffset: 0, + referenceLocationLatitude: 67.82, + referenceLocationLongitude: 120.03, + referenceLocationElevation: 980, }, { - 'HOR.VER': '000.010', - azimuth: '0', - pitch: '0', - roll: '0', - xOffset: '5', - yOffset: '6', - zOffset: '1', - referenceLatitude: '67.82', - referenceLongitude: '120.03', - referenceElevation: '990', + horVer: '000.010', + azimuth: 0, + pitch: 0, + roll: 0, + xOffset: 5, + yOffset: 6, + zOffset: 1, + referenceLocationLatitude: 67.82, + referenceLocationLongitude: 120.03, + referenceLocationElevation: 990, }, ]; diff --git a/src/lib_components/parser/DataPackageParser.ts b/src/lib_components/parser/DataPackageParser.ts new file mode 100644 index 00000000..23abee4d --- /dev/null +++ b/src/lib_components/parser/DataPackageParser.ts @@ -0,0 +1,96 @@ +import { exists, isStringNonEmpty } from '../util/typeUtil'; +import { Nullable, UnknownRecord } from '../types/core'; +import { SensorPosition } from '../types/internal'; +import { + sensorPositionV2Schema, + sensorPositionV1Schema, + SensorPositionV2Type, + SensorPositionV1Type, +} from '../types/neonDataPackage'; + +export interface IDataPackageParser { + /** + * Parse a data package sensor position record into an + * internal shape based on detected schema version. + * @param position The position to parse + * @returns The parsed position to an internal, normalized shape. + */ + parseSensorPosition: (position: Nullable) => Nullable; +} + +const DataPackageParser: IDataPackageParser = { + parseSensorPosition: (position: Nullable): Nullable => { + if (!exists(position)) { + return null; + } + let result: Nullable = null; + const resultV2 = sensorPositionV2Schema.safeParse(position); + if (!resultV2.success) { + // Check for v1 schema + const resultV1 = sensorPositionV1Schema.safeParse(position); + if (!resultV1.success) { + const v2ErrorFormat = resultV2.error.format(); + const v1ErrorFormat = resultV1.error.format(); + // eslint-disable-next-line no-console + console.error('Failed to detect sensor position schema', v2ErrorFormat, v1ErrorFormat); + } else { + const parsedV1: SensorPositionV1Type = resultV1.data; + result = { + horVer: parsedV1['HOR.VER'], + sensorName: isStringNonEmpty(parsedV1.name) ? parsedV1.name as string : 'N/A', + sensorDescription: parsedV1.description, + sensorStartDateTime: parsedV1.start, + sensorEndDateTime: parsedV1.end, + referenceLocationName: parsedV1.referenceName, + referenceLocationDescription: parsedV1.referenceDescription, + referenceLocationStartDateTime: parsedV1.referenceStart, + referenceLocationEndDateTime: parsedV1.referenceEnd, + xOffset: parsedV1.xOffset, + yOffset: parsedV1.yOffset, + zOffset: parsedV1.zOffset, + pitch: parsedV1.pitch, + roll: parsedV1.roll, + azimuth: parsedV1.azimuth, + referenceLocationLatitude: parsedV1.referenceLatitude, + referenceLocationLongitude: parsedV1.referenceLongitude, + referenceLocationElevation: parsedV1.referenceElevation, + eastOffset: parsedV1.eastOffset, + northOffset: parsedV1.northOffset, + xAzimuth: parsedV1.xAzimuth, + yAzimuth: parsedV1.yAzimuth, + }; + } + } else { + const parsedV2: SensorPositionV2Type = resultV2.data; + result = { + horVer: parsedV2['HOR.VER'], + sensorName: parsedV2.sensorLocationID, + sensorDescription: parsedV2.sensorLocationDescription, + sensorStartDateTime: parsedV2.positionStartDateTime, + sensorEndDateTime: parsedV2.positionEndDateTime, + referenceLocationName: parsedV2.referenceLocationID, + referenceLocationDescription: parsedV2.referenceLocationIDDescription, + referenceLocationStartDateTime: parsedV2.referenceLocationIDStartDateTime, + referenceLocationEndDateTime: parsedV2.referenceLocationIDEndDateTime, + xOffset: parsedV2.xOffset, + yOffset: parsedV2.yOffset, + zOffset: parsedV2.zOffset, + pitch: parsedV2.pitch, + roll: parsedV2.roll, + azimuth: parsedV2.azimuth, + referenceLocationLatitude: parsedV2.locationReferenceLatitude, + referenceLocationLongitude: parsedV2.locationReferenceLongitude, + referenceLocationElevation: parsedV2.locationReferenceElevation, + eastOffset: parsedV2.eastOffset, + northOffset: parsedV2.northOffset, + xAzimuth: parsedV2.xAzimuth, + yAzimuth: parsedV2.yAzimuth, + }; + } + return result; + }, +}; + +Object.freeze(DataPackageParser); + +export default DataPackageParser; diff --git a/src/lib_components/types/internal.ts b/src/lib_components/types/internal.ts index 498d4a3f..f13bc3d0 100644 --- a/src/lib_components/types/internal.ts +++ b/src/lib_components/types/internal.ts @@ -23,3 +23,28 @@ export interface CitationBundleState { parentCodes: string[]; doiProductCode: Nullable; } + +export interface SensorPosition { + horVer: string; + sensorName: string; + sensorDescription: Nullable; + sensorStartDateTime: Nullable; + sensorEndDateTime: Nullable; + referenceLocationName: Nullable; + referenceLocationDescription: Nullable; + referenceLocationStartDateTime: Nullable; + referenceLocationEndDateTime: Nullable; + xOffset: Nullable; + yOffset: Nullable; + zOffset: Nullable; + pitch: Nullable; + roll: Nullable; + azimuth: Nullable; + referenceLocationLatitude: Nullable; + referenceLocationLongitude: Nullable; + referenceLocationElevation: Nullable; + eastOffset: Nullable; + northOffset: Nullable; + xAzimuth: Nullable; + yAzimuth: Nullable; +} diff --git a/src/lib_components/types/neonDataPackage.ts b/src/lib_components/types/neonDataPackage.ts new file mode 100644 index 00000000..7b031917 --- /dev/null +++ b/src/lib_components/types/neonDataPackage.ts @@ -0,0 +1,120 @@ +import { z } from 'zod'; +import { isStringNonEmpty } from '../util/typeUtil'; +import { Nullable } from './core'; + +const transformStringToFloat = ( + val: string|null|undefined, + ctx: z.RefinementCtx, +): Nullable => { + if (!isStringNonEmpty(val)) return null; + return parseFloat(val as string); +}; + +const sensorPositionV1Schema = z.object({ + 'HOR.VER': z.string(), + name: z.string().optional().nullable(), + description: z.string().optional().nullable(), + start: z.string().optional().nullable(), + end: z.string().optional().nullable(), + referenceName: z.string().optional().nullable(), + referenceDescription: z.string().optional().nullable(), + referenceStart: z.string().optional().nullable(), + referenceEnd: z.string().optional().nullable(), + xOffset: z.string().optional().nullable() + .transform(transformStringToFloat) + .nullable(), + yOffset: z.string().optional().nullable() + .transform(transformStringToFloat) + .nullable(), + zOffset: z.string().optional().nullable() + .transform(transformStringToFloat) + .nullable(), + pitch: z.string().optional().nullable() + .transform(transformStringToFloat) + .nullable(), + roll: z.string().optional().nullable() + .transform(transformStringToFloat) + .nullable(), + azimuth: z.string().optional().nullable() + .transform(transformStringToFloat) + .nullable(), + referenceLatitude: z.string().optional().nullable() + .transform(transformStringToFloat) + .nullable(), + referenceLongitude: z.string().optional().nullable() + .transform(transformStringToFloat) + .nullable(), + referenceElevation: z.string().optional().nullable() + .transform(transformStringToFloat) + .nullable(), + eastOffset: z.string().optional().nullable() + .transform(transformStringToFloat) + .nullable(), + northOffset: z.string().optional().nullable() + .transform(transformStringToFloat) + .nullable(), + xAzimuth: z.string().optional().nullable() + .transform(transformStringToFloat) + .nullable(), + yAzimuth: z.string().optional().nullable() + .transform(transformStringToFloat) + .nullable(), +}); +const sensorPositionV2Schema = z.object({ + 'HOR.VER': z.string(), + sensorLocationID: z.string(), + sensorLocationDescription: z.string().nullable(), + positionStartDateTime: z.string().nullable(), + positionEndDateTime: z.string().nullable(), + referenceLocationID: z.string().optional().nullable(), + referenceLocationIDDescription: z.string().optional().nullable(), + referenceLocationIDStartDateTime: z.string().optional().nullable(), + referenceLocationIDEndDateTime: z.string().optional().nullable(), + xOffset: z.string().nullable() + .transform(transformStringToFloat) + .nullable(), + yOffset: z.string().nullable() + .transform(transformStringToFloat) + .nullable(), + zOffset: z.string().nullable() + .transform(transformStringToFloat) + .nullable(), + pitch: z.string().nullable() + .transform(transformStringToFloat) + .nullable(), + roll: z.string().nullable() + .transform(transformStringToFloat) + .nullable(), + azimuth: z.string().nullable() + .transform(transformStringToFloat) + .nullable(), + locationReferenceLatitude: z.string().optional().nullable() + .transform(transformStringToFloat) + .nullable(), + locationReferenceLongitude: z.string().optional().nullable() + .transform(transformStringToFloat) + .nullable(), + locationReferenceElevation: z.string().optional().nullable() + .transform(transformStringToFloat) + .nullable(), + eastOffset: z.string().optional().nullable() + .transform(transformStringToFloat) + .nullable(), + northOffset: z.string().optional().nullable() + .transform(transformStringToFloat) + .nullable(), + xAzimuth: z.string().optional().nullable() + .transform(transformStringToFloat) + .nullable(), + yAzimuth: z.string().optional().nullable() + .transform(transformStringToFloat) + .nullable(), +}); + +export type SensorPositionV1Type = z.infer; +export type SensorPositionV2Type = z.infer; + +export { + sensorPositionV1Schema, + sensorPositionV2Schema, +};