Compare commits

...

50 Commits

Author SHA1 Message Date
charlesh88
98a048062f Added minor ordinal ticks to rose
- Added NE, SE, SW and NW ticks to compass rose;
2021-02-01 22:29:51 -08:00
David Tsay
9b114c49df cleanup 2021-01-29 14:49:44 -08:00
David Tsay
2c0c998e29 cleanup 2021-01-29 14:16:51 -08:00
David Tsay
a001e07600 Merge branch 'imagery-enhancements' into compass-rose 2021-01-29 14:11:02 -08:00
David Tsay
4b40233bf3 remove unnused code 2021-01-29 11:34:28 -08:00
Jamie Vigliotta
f3f833a337 WIP adding debug code for viper env testing 2021-01-29 10:23:17 -08:00
David Tsay
e6e8b8e048 Merge branch 'imagery-enhancements' into compass-rose 2021-01-28 11:28:25 -08:00
David Tsay
6a2c079336 fix click on compass rose
fix hud pointer
2021-01-28 11:25:30 -08:00
David Tsay
334aeb42ae do not unskew lettering in hud 2021-01-28 10:30:07 -08:00
David Tsay
5900bb0d98 add rover roll skew to hud 2021-01-27 21:36:28 -08:00
David Tsay
a2c350b105 include camera pan in calculations 2021-01-27 20:32:34 -08:00
David Tsay
174f212328 make compass hud reactive 2021-01-27 18:40:06 -08:00
Jamie Vigliotta
a28ec45f71 liniting 2021-01-26 12:17:48 -08:00
Jamie Vigliotta
fcc6bb9873 Merge branch 'master' into imagery-enhancements
Merg'n master
2021-01-26 11:24:18 -08:00
David Tsay
45578b113f cleanup from refactor compass out of imagery-view component 2021-01-25 23:15:26 -08:00
David Tsay
5ef14b0975 move compass logic to component 2021-01-25 23:00:58 -08:00
David Tsay
2be429a04f compass rose and hud should be part of compass component 2021-01-25 22:25:20 -08:00
David Tsay
3804fe1a1e merge imagery-enhancements 2021-01-25 13:53:50 -08:00
David Tsay
7576673e77 Merge branch 'imagery-enhancements' into compass-rose 2021-01-25 13:51:31 -08:00
Jamie Vigliotta
63e04caab6 remove old cod 2021-01-25 13:41:54 -08:00
Jamie Vigliotta
956cfbd01f removed space, linting 2021-01-25 13:40:11 -08:00
Jamie Vigliotta
6c77be32c7 updates from imagery-freshness branch that were supposed to be in this branch 2021-01-25 13:35:55 -08:00
David Tsay
f732167e02 compass rose and hud listening to live metadata 2021-01-25 08:51:52 -08:00
David Tsay
1d56fd98dc Merge branch 'imagery-enhancements' into compass-rose 2021-01-22 10:10:28 -08:00
Jamie Vigliotta
b43fef6e21 added a check for on telemetry related data, handling accordingly by not requesting data and just returning it if it exists on the datum 2021-01-21 15:55:16 -08:00
Jamie Vigliotta
d04c29345b removed splice of image backinto image history, looks like we didnt need it since the changes were made on an object which kept its reference in the array 2021-01-21 13:18:18 -08:00
David Tsay
24b96cdb47 WIP get image container size to resize HUD element 2021-01-21 11:58:28 -08:00
David Tsay
14ce4a1aa0 Merge branch 'imagery-enhancements' into compass-rose 2021-01-21 09:46:26 -08:00
Jamie Vigliotta
28d97be60e adding keys to data object 2021-01-21 09:42:15 -08:00
David Tsay
1f6e91c6b5 add true sun heading from metadata 2021-01-21 09:34:26 -08:00
David Tsay
0b078497f1 Merge branch 'imagery-enhancements' into compass-rose 2021-01-21 09:33:50 -08:00
David Tsay
060a1b17db add sun heading to related telemetry 2021-01-21 09:33:29 -08:00
David Tsay
417f81b7fd connect compass rose to image metadata
add compass HUD
2021-01-20 20:43:00 -08:00
David Tsay
f219394abd Merge branch 'imagery-enhancements' into compass-rose 2021-01-20 16:48:35 -08:00
David Tsay
4e5c74ecef add metadata to this.focusedImage 2021-01-20 16:47:53 -08:00
David Tsay
218530e436 telemetry changes 2021-01-20 15:58:06 -08:00
David Tsay
0890499a2b Merge branch 'imagery-enhancements' into compass-rose 2021-01-20 15:00:35 -08:00
Jamie Vigliotta
d9dad09dfd lazy related telemetry data grab, added focused image related data key 2021-01-20 14:34:55 -08:00
David Tsay
9af5df0f20 Merge branch 'imagery-enhancements' into compass-rose 2021-01-20 10:44:47 -08:00
David Tsay
1580a61092 Merge branch 'master' into imagery-enhancements 2021-01-20 10:43:43 -08:00
Jamie Vigliotta
c236444a05 added options for request 2021-01-19 19:51:55 -08:00
Jamie Vigliotta
39c1eb1d5b added revised functionality for getting related telemetry data with examples and local testing settings as well 2021-01-19 19:49:22 -08:00
David Tsay
95caab944d Merge branch 'imagery-enhancements' into compass-rose 2021-01-15 08:39:43 -08:00
David Tsay
ac89e51d1b Merge branch 'master' into imagery-enhancements 2021-01-15 08:39:23 -08:00
David Tsay
85d9ed8287 unhardcode camera field of view
add toggle lock feature
2021-01-14 20:24:12 -08:00
David Tsay
aedc24a2da unhardcode headings and cardinal points 2021-01-14 16:14:38 -08:00
David Tsay
823eda4465 linting 2021-01-14 13:08:33 -08:00
David Tsay
7eaa1d3e2b add markup for compass rose component 2021-01-14 13:06:08 -08:00
Jamie Vigliotta
4633436cbd added get imageMetadataValue method for getting necessary values for new image enhancements, has temporary local testing code as well for dev 2021-01-13 15:16:03 -08:00
David Tsay
b68a7e27c9 add missing copyrights 2021-01-12 16:19:51 -08:00
10 changed files with 1181 additions and 15 deletions

View File

@@ -1,3 +1,25 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import ImageryViewLayout from './components/ImageryViewLayout.vue';
import Vue from 'vue';

View File

@@ -0,0 +1,130 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
<template>
<div
class="c-compass"
:style="compassDimensionsStyle"
>
<CompassHUD
v-if="shouldDisplayCompassHUD"
:rover-heading="roverHeading"
:rover-roll="roverRoll"
:sun-heading="sunHeading"
:camera-field-of-view="cameraFieldOfView"
:camera-pan="cameraPan"
/>
<CompassRose
v-if="shouldDisplayCompassRose"
:rover-heading="roverHeading"
:sun-heading="sunHeading"
:camera-field-of-view="cameraFieldOfView"
:camera-pan="cameraPan"
/>
</div>
</template>
<script>
import CompassHUD from './CompassHUD.vue';
import CompassRose from './CompassRose.vue';
const CAM_FIELD_OF_VIEW = 70;
export default {
components: {
CompassHUD,
CompassRose
},
props: {
containerWidth: {
type: Number,
required: true
},
containerHeight: {
type: Number,
required: true
},
naturalAspectRatio: {
type: Number,
required: true
},
image: {
type: Object,
required: true
}
},
computed: {
shouldDisplayCompassRose() {
return this.roverHeading !== undefined;
},
shouldDisplayCompassHUD() {
return this.roverHeading !== undefined;
},
// degrees from north heading
roverHeading() {
return this.image['Rover Heading'];
},
roverRoll() {
return this.image['Rover Roll'];
},
roverYaw() {
return this.image['Rover Yaw'];
},
roverPitch() {
return this.image['Rover Pitch'];
},
// degrees from north heading
sunHeading() {
return this.image['Sun Orientation'];
},
// degrees from rover heading
cameraPan() {
return this.image['Camera Pan'];
},
cameraTilt() {
return this.image['Camera Tilt'];
},
cameraFieldOfView() {
return CAM_FIELD_OF_VIEW;
},
compassDimensionsStyle() {
const containerAspectRatio = this.containerWidth / this.containerHeight;
let width;
let height;
if (containerAspectRatio < this.naturalAspectRatio) {
width = '100%';
height = `${ this.containerWidth / this.naturalAspectRatio }px`;
} else {
width = `${ this.containerHeight * this.naturalAspectRatio }px`;
height = '100%';
}
return {
width: width,
height: height
};
}
}
};
</script>

View File

@@ -0,0 +1,171 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
<template>
<div
class="c-compass__hud c-hud"
:style="skewCompassHUDStyle"
>
<div
v-for="point in visibleCompassPoints"
:key="point.direction"
:class="point.class"
:style="point.style"
>
{{ point.direction }}
</div>
<div
v-if="isSunInRange"
ref="sun"
class="c-hud__sun"
:style="sunPositionStyle"
></div>
<div class="c-hud__range"></div>
</div>
</template>
<script>
import {
normalizeDegrees,
inRange,
percentOfRange
} from './utils';
const COMPASS_POINTS = [
{
direction: 'N',
class: 'c-hud__dir',
degrees: 0
},
{
direction: 'NE',
class: 'c-hud__dir--sub',
degrees: 45
},
{
direction: 'E',
class: 'c-hud__dir',
degrees: 90
},
{
direction: 'SE',
class: 'c-hud__dir--sub',
degrees: 135
},
{
direction: 'S',
class: 'c-hud__dir',
degrees: 180
},
{
direction: 'SW',
class: 'c-hud__dir--sub',
degrees: 225
},
{
direction: 'W',
class: 'c-hud__dir',
degrees: 270
},
{
direction: 'NW',
class: 'c-hud__dir--sub',
degrees: 315
}
];
export default {
props: {
roverHeading: {
type: Number,
required: true
},
roverRoll: {
type: Number,
default: undefined
},
sunHeading: {
type: Number,
default: undefined
},
cameraFieldOfView: {
type: Number,
default: undefined
},
cameraPan: {
type: Number,
default: undefined
}
},
computed: {
skewCompassHUDStyle() {
if (this.roverRoll === undefined || this.roverRoll === 0) {
return;
}
const origin = this.roverRoll > 0 ? 'left bottom' : 'right top';
return {
'transform-origin': origin,
transform: `skew(0, ${ this.roverRoll }deg`
};
},
visibleCompassPoints() {
return COMPASS_POINTS
.filter(point => inRange(point.degrees, this.visibleRange))
.map(point => {
const percentage = percentOfRange(point.degrees, this.visibleRange);
point.style = Object.assign(
{ left: `${ percentage * 100 }%` }
);
return point;
});
},
isSunInRange() {
return inRange(this.normalizedSunHeading, this.visibleRange);
},
sunPositionStyle() {
const percentage = percentOfRange(this.normalizedSunHeading, this.visibleRange);
return {
left: `${ percentage * 100 }%`
};
},
normalizedSunHeading() {
return normalizeDegrees(this.sunHeading);
},
normalizedRoverHeading() {
return normalizeDegrees(this.roverHeading);
},
visibleRange() {
const min = normalizeDegrees(this.normalizedRoverHeading + this.cameraPan - this.cameraFieldOfView / 2);
const max = normalizeDegrees(this.normalizedRoverHeading + this.cameraPan + this.cameraFieldOfView / 2);
return [
min,
max
];
}
}
};
</script>

View File

@@ -0,0 +1,262 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
<template>
<div
class="c-direction-rose"
@click="toggleBezelLock"
>
<div
class="c-nsew"
:style="rotateFrameStyle"
>
<svg
class="c-nsew__minor-ticks"
viewBox="0 0 100 100"
>
<rect
class="c-nsew__tick c-tick-ne"
x="49"
y="0"
width="2"
height="5"
/>
<rect
class="c-nsew__tick c-tick-se"
x="95"
y="49"
width="5"
height="2"
/>
<rect
class="c-nsew__tick c-tick-sw"
x="49"
y="95"
width="2"
height="5"
/>
<rect
class="c-nsew__tick c-tick-nw"
x="0"
y="49"
width="5"
height="2"
/>
</svg>
<svg
class="c-nsew__ticks"
viewBox="0 0 100 100"
>
<polygon
class="c-nsew__tick c-tick-n"
points="50,0 57,5 43,5"
/>
<rect
class="c-nsew__tick c-tick-e"
x="95"
y="49"
width="5"
height="2"
/>
<rect
class="c-nsew__tick c-tick-w"
x="0"
y="49"
width="5"
height="2"
/>
<rect
class="c-nsew__tick c-tick-s"
x="49"
y="95"
width="2"
height="5"
/>
<text
class="c-nsew__label c-label-n"
text-anchor="middle"
:transform="northTextTransform"
>N</text>
<text
class="c-nsew__label c-label-e"
text-anchor="middle"
:transform="eastTextTransform"
>E</text>
<text
class="c-nsew__label c-label-w"
text-anchor="middle"
:transform="southTextTransform"
>W</text>
<text
class="c-nsew__label c-label-s"
text-anchor="middle"
:transform="westTextTransform"
>S</text>
</svg>
</div>
<div
class="c-rover-body"
:style="roverHeadingStyle"
>
</div>
<div
class="c-sun"
:style="sunHeadingStyle"
></div>
<div
v-if="showCameraFOV"
class="c-cam-field"
:style="cameraFOVHeadingStyle"
>
<div class="cam-field-half cam-field-half-l">
<div
class="cam-field-area"
:style="cameraFOVStyleLeftHalf"
></div>
</div>
<div class="cam-field-half cam-field-half-r">
<div
class="cam-field-area"
:style="cameraFOVStyleRightHalf"
></div>
</div>
</div>
</div>
</template>
<script>
import { normalizeDegrees } from './utils';
export default {
props: {
roverHeading: {
type: Number,
required: true
},
sunHeading: {
type: Number,
default: undefined
},
cameraFieldOfView: {
type: Number,
default: undefined
},
cameraPan: {
type: Number,
default: undefined
}
},
data() {
return {
lockBezel: true
};
},
computed: {
compassRoverHeading() {
return this.lockBezel ? normalizeDegrees(this.roverHeading) : 0;
},
north() {
return normalizeDegrees(this.compassRoverHeading - this.roverHeading);
},
rotateFrameStyle() {
return { transform: `rotate(${ this.north }deg)` };
},
northTextTransform() {
return this.cardinalPointsTextTransform.north;
},
eastTextTransform() {
return this.cardinalPointsTextTransform.east;
},
southTextTransform() {
return this.cardinalPointsTextTransform.south;
},
westTextTransform() {
return this.cardinalPointsTextTransform.west;
},
cardinalPointsTextTransform() {
/**
* cardinal points text must be rotated
* in the opposite direction that north is rotated
* to keep text vertically oriented
*/
const rotation = `rotate(${ -this.north })`;
return {
north: `translate(50,15) ${ rotation }`,
east: `translate(87,50) ${ rotation }`,
south: `translate(13,50) ${ rotation }`,
west: `translate(50,87) ${ rotation }`
};
},
roverHeadingStyle() {
return {
transform: `translateX(-50%) rotate(${ this.compassRoverHeading }deg)`
};
},
cameraFOVHeading() {
return this.compassRoverHeading + this.cameraPan;
},
cameraFOVHeadingStyle() {
return {
transform: `rotate(${ this.cameraFOVHeading }deg)`
};
},
sunHeadingStyle() {
const rotation = normalizeDegrees(this.north + this.sunHeading);
return {
transform: `rotate(${ rotation }deg)`
};
},
showCameraFOV() {
return this.cameraPan !== undefined && this.cameraFieldOfView > 0;
},
// left half of camera field of view
// rotated counter-clockwise from camera field of view heading
cameraFOVStyleLeftHalf() {
return {
transform: `translateX(50%) rotate(${ -this.cameraFieldOfView / 2 }deg)`
};
},
// right half of camera field of view
// rotated clockwise from camera field of view heading
cameraFOVStyleRightHalf() {
return {
transform: `translateX(-50%) rotate(${ this.cameraFieldOfView / 2 }deg)`
};
}
},
methods: {
toggleBezelLock() {
this.lockBezel = !this.lockBezel;
}
}
};
</script>

View File

@@ -0,0 +1,216 @@
/***************************** THEME/UI CONSTANTS AND MIXINS */
$interfaceKeyColor: #00B9C5;
$elemBg: rgba(black, 0.7);
@mixin sun($position: 'circle closest-side') {
$color: #ff9900;
$gradEdgePerc: 60%;
background: radial-gradient(#{$position}, $color, $color $gradEdgePerc, rgba($color, 0.4) $gradEdgePerc + 5%, transparent);
}
.c-compass {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
z-index: 1;
@include userSelectNone;
}
/***************************** COMPASS HUD */
.c-hud {
// To be placed within a imagery view, in the bounding box of the image
$m: 1px;
$padTB: 2px;
$padLR: $padTB;
background: $elemBg;
border-radius: 3px;
color: $interfaceKeyColor;
font-size: 0.8em;
position: absolute;
top: $m; right: $m; left: $m;
height: 18px;
svg, div {
position: absolute;
}
&__display {
height: 30px;
pointer-events: all;
position: absolute;
top: 0;
right: 0;
left: 0;
}
&__range {
border: 1px solid $interfaceKeyColor;
border-top-color: transparent;
position: absolute;
top: 50%; right: $padLR; bottom: $padTB; left: $padLR;
}
[class*="__dir"] {
// NSEW
display: inline-block;
font-weight: bold;
text-shadow: black 0 0 3px;
top: 50%;
transform: translate(-50%,-50%);
z-index: 2;
}
[class*="__dir--sub"] {
font-weight: normal;
opacity: 0.5;
}
&__sun {
$s: 10px;
@include sun('circle farthest-side at bottom');
bottom: $padTB + 2px;
height: $s; width: $s*2;
opacity: 0.8;
transform: translateX(-50%);
z-index: 1;
}
}
/***************************** COMPASS DIRECTIONS */
.c-nsew {
$color: $interfaceKeyColor;
$inset: 7%;
$tickHeightPerc: 15%;
text-shadow: black 0 0 10px;
top: $inset; right: $inset; bottom: $inset; left: $inset;
z-index: 3;
&__tick,
&__label {
fill: $color;
}
&__minor-ticks {
opacity: 0.5;
transform-origin: center;
transform: rotate(45deg);
}
&__label {
dominant-baseline: central;
font-size: 0.8em;
font-weight: bold;
}
.c-label-n {
font-size: 1.1em;
}
}
/***************************** CAMERA FIELD ANGLE */
.c-cam-field {
$color: white;
opacity: 0.2;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 2;
.cam-field-half {
top: 0;
right: 0;
bottom: 0;
left: 0;
.cam-field-area {
background: $color;
top: -30%;
right: 0;
bottom: -30%;
left: 0;
}
// clip-paths overlap a bit to avoid a gap between halves
&-l {
clip-path: polygon(0 0, 50.5% 0, 50.5% 100%, 0 100%);
.cam-field-area {
transform-origin: left center;
}
}
&-r {
clip-path: polygon(49.5% 0, 100% 0, 100% 100%, 49.5% 100%);
.cam-field-area {
transform-origin: right center;
}
}
}
}
/***************************** ROVER BODY */
.c-rover-body {
$color: $interfaceKeyColor;
$s: 30%;
background: $color;
border-radius: 3px;
height: $s; width: $s;
left: 50%; top: 50%;
opacity: 0.4;
transform-origin: center top;
&:before {
// Direction arrow
$color: rgba(black, 0.5);
$arwPointerY: 60%;
$arwBodyOffset: 25%;
background: $color;
content: '';
display: block;
position: absolute;
top: 10%; right: 20%; bottom: 50%; left: 20%;
clip-path: polygon(50% 0, 100% $arwPointerY, 100%-$arwBodyOffset $arwPointerY, 100%-$arwBodyOffset 100%, $arwBodyOffset 100%, $arwBodyOffset $arwPointerY, 0 $arwPointerY);
}
}
/***************************** DIRECTION ROSE */
.c-direction-rose {
$d: 100px;
$c2: rgba(white, 0.1);
background: $elemBg;
background-image: radial-gradient(circle closest-side, transparent, transparent 80%, $c2);
width: $d;
height: $d;
transform-origin: 0 0;
position: absolute;
bottom: 10px; left: 10px;
clip-path: circle(50% at 50% 50%);
border-radius: 100%;
svg, div {
position: absolute;
}
// Sun
.c-sun {
top: 0;
right: 0;
bottom: 0;
left: 0;
&:before {
$s: 35%;
@include sun();
content: '';
display: block;
position: absolute;
opacity: 0.7;
top: 0; left: 50%;
height:$s; width: $s;
transform: translate(-50%, -60%);
}
}
}

View File

@@ -0,0 +1,44 @@
export function normalizeDegrees(degrees) {
const base = degrees % 360;
return base >= 0 ? base : 360 + base;
}
export function inRange(degrees, [min, max]) {
return min > max
? (degrees >= min && degrees < 360) || (degrees <= max && degrees >= 0)
: degrees >= min && degrees <= max;
}
export function percentOfRange(degrees, [min, max]) {
let distance = degrees;
let minRange = min;
let maxRange = max;
if (min > max) {
if (distance < max) {
distance += 360;
}
maxRange += 360;
}
return (distance - minRange) / (maxRange - minRange);
}
export function normalizeSemiCircleDegrees(rawDegrees) {
// in case tony hawk is providing us degrees
let degrees = rawDegrees % 360;
// westward degrees are between 0 and -180 exclusively
if (degrees > 180) {
degrees = degrees - 360;
}
// eastward degrees are between 0 and 180 inclusively
if (degrees <= -180) {
degrees = 360 - degrees;
}
return degrees;
}

View File

@@ -1,3 +1,25 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
<template>
<div
tabindex="0"
@@ -36,14 +58,23 @@
<div class="c-imagery__main-image__bg"
:class="{'paused unnsynced': isPaused,'stale':false }"
>
<div class="c-imagery__main-image__image js-imageryView-image"
:style="{
'background-image': imageUrl ? `url(${imageUrl})` : 'none',
'filter': `brightness(${filters.brightness}%) contrast(${filters.contrast}%)`
}"
:data-openmct-image-timestamp="time"
:data-openmct-object-keystring="keyString"
></div>
<img
ref="focusedImage"
class="c-imagery__main-image__image js-imageryView-image"
:src="imageUrl"
:style="{
'filter': `brightness(${filters.brightness}%) contrast(${filters.contrast}%)`
}"
:data-openmct-image-timestamp="time"
:data-openmct-object-keystring="keyString"
>
<Compass
v-if="shouldDisplayCompass"
:container-width="imageContainerWidth"
:container-height="imageContainerHeight"
:natural-aspect-ratio="focusedImageNaturalAspectRatio"
:image="focusedImage"
/>
</div>
<div class="c-local-controls c-local-controls--show-on-hover c-imagery__prev-next-buttons">
<button class="c-nav c-nav--prev"
@@ -97,7 +128,9 @@
</template>
<script>
import _ from 'lodash';
import moment from 'moment';
import Compass from './Compass/Compass.vue';
const DEFAULT_DURATION_FORMATTER = 'duration';
const REFRESH_CSS_MS = 500;
@@ -117,6 +150,9 @@ const ARROW_LEFT = 37;
export default {
inject: ['openmct', 'domainObject'],
components: {
Compass
},
data() {
let timeSystem = this.openmct.time.timeSystem();
@@ -137,7 +173,13 @@ export default {
refreshCSS: false,
keyString: undefined,
focusedImageIndex: undefined,
numericDuration: undefined
focusedImageRelatedData: {},
numericDuration: undefined,
metadataEndpoints: {},
relatedTelemetry: {},
focusedImageNaturalAspectRatio: undefined,
imageContainerWidth: undefined,
imageContainerHeight: undefined
};
},
computed: {
@@ -195,15 +237,23 @@ export default {
}
return result;
},
shouldDisplayCompass() {
return this.focusedImage !== undefined
&& this.focusedImageNaturalAspectRatio !== undefined
&& this.imageContainerWidth !== undefined
&& this.imageContainerHeight !== undefined;
}
},
watch: {
focusedImageIndex() {
this.trackDuration();
this.resetAgeCSS();
this.updateRelatedTelemetryForFocusedImage();
this.getImageNaturalDimensions();
}
},
mounted() {
async mounted() {
// listen
this.openmct.time.on('bounds', this.boundsChange);
this.openmct.time.on('timeSystem', this.timeSystemChange);
@@ -212,8 +262,17 @@ export default {
// set
this.keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
this.metadata = this.openmct.telemetry.getMetadata(this.domainObject);
this.imageHints = this.metadata.valuesForHints(['image'])[0];
this.durationFormatter = this.getFormatter(this.timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
this.imageFormatter = this.openmct.telemetry.getValueFormatter(this.metadata.valuesForHints(['image'])[0]);
this.imageFormatter = this.openmct.telemetry.getValueFormatter(this.imageHints);
this.roverKeys = ['Rover Heading', 'Rover Roll', 'Rover Yaw', 'Rover Pitch'];
this.cameraKeys = ['Camera Pan', 'Camera Tilt'];
this.sunKeys = ['Sun Orientation'];
// DELETE WHEN DONE
if (!this.imageHints.relatedTelemetry) {
this.temporaryForImageEnhancements();
}
// initialize
this.timeKey = this.timeSystem.key;
@@ -222,6 +281,12 @@ export default {
// kickoff
this.subscribe();
this.requestHistory();
await this.initializeRelatedTelemetry();
// for when people are scrolling through images quickly
_.debounce(this.updateRelatedTelemetryForFocusedImage, 400);
this.pollResizeImageContainerID = setInterval(this.pollResizeImageContainer, 300);
},
updated() {
this.scrollToRight();
@@ -236,8 +301,222 @@ export default {
this.openmct.time.off('bounds', this.boundsChange);
this.openmct.time.off('timeSystem', this.timeSystemChange);
this.openmct.time.off('clock', this.clockChange);
// unsubscribe from related telemetry
if (this.hasRelatedTelemetry) {
for (let key of this.relatedTelemetry.keys) {
if (this.relatedTelemetry[key].unsubscribe) {
this.relatedTelemetry[key].unsubscribe();
}
}
}
clearInterval(this.pollResizeImageContainerID);
},
methods: {
// for local dev, to be DELETED
temporaryForImageEnhancements() {
this.searchService = this.openmct.$injector.get('searchService');
this.temporaryDev = true;
// mock related telemetry metadata
this.imageHints.relatedTelemetry = {};
// populate temp keys in imageHints for local testing
[...this.roverKeys, ...this.cameraKeys, ...this.sunKeys].forEach(key => {
this.imageHints.relatedTelemetry[key] = {
dev: true,
areEqual: function (valueOne, valueTwo) {
const DECIMAL_COMPARISON_TOLERANCE = 1;
const WHOLE = Math.pow(10, DECIMAL_COMPARISON_TOLERANCE);
return Math.floor(valueOne.toFixed(DECIMAL_COMPARISON_TOLERANCE) * WHOLE) === Math.floor(valueTwo.toFixed(DECIMAL_COMPARISON_TOLERANCE) * WHOLE);
},
realtime: {
telemetryObjectId: key,
valueKey: 'sin'
},
historical: {
telemetryObjectId: key,
valueKey: 'sin'
},
devInit: async () => {
const searchResults = await this.searchService.query(key);
const endpoint = searchResults.hits[0].id;
const domainObject = await this.openmct.objects.get(endpoint);
return domainObject;
}
};
});
},
async initializeRelatedTelemetry() {
if (this.imageHints.relatedTelemetry === undefined) {
this.hasRelatedTelemetry = false;
return;
}
// DELETE
if (this.temporaryDev) {
let searchIndexBuildDelay = new Promise((resolve, reject) => {
setTimeout(resolve, 3000);
});
await searchIndexBuildDelay;
}
let keys = Object.keys(this.imageHints.relatedTelemetry);
this.hasRelatedTelemetry = true;
this.relatedTelemetry = {
keys,
...this.imageHints.relatedTelemetry
};
// grab historical and subscribe to realtime
for (let key of keys) {
let historicalId;
let realtimeId;
if (this.relatedTelemetry[key].historical) {
if (this.relatedTelemetry[key].historical.telemetryObjectId) {
historicalId = this.relatedTelemetry[key].historical.telemetryObjectId;
} else {
this.relatedTelemetry[key].historicalValuesOnTelemetry = true;
}
}
if (this.relatedTelemetry[key].realtime && this.relatedTelemetry[key].realtime.telemetryObjectId) {
realtimeId = this.relatedTelemetry[key].realtime.telemetryObjectId;
}
// if we have a historical object id, then values will NOT be on the imagery datum
if (historicalId) {
// DELETE temp
if (this.relatedTelemetry[key].dev) {
this.relatedTelemetry[key].historicalDomainObject = await this.relatedTelemetry[key].devInit();
} else {
this.relatedTelemetry[key].historicalDomainObject = await this.openmct.objects.get(historicalId);
}
this.relatedTelemetry[key].requestLatestFor = async (datum) => {
const options = {
start: this.openmct.time.bounds().start,
end: datum[this.timeKey],
strategy: 'latest'
};
let results = await this.openmct.telemetry
.request(this.relatedTelemetry[key].historicalDomainObject, options);
return results[results.length - 1];
};
}
if (realtimeId) {
if (this.relatedTelemetry[key].dev) {
this.relatedTelemetry[key].realtimeDomainObject = await this.relatedTelemetry[key].devInit();
} else {
this.relatedTelemetry[key].realtimeDomainObject = await this.openmct.objects.get(realtimeId);
}
// set up listeners
this.relatedTelemetry[key].listeners = [];
this.relatedTelemetry[key].subscribe = (callback) => {
if (!this.relatedTelemetry[key].isSubscribed) {
this.subscribeToDataForKey(key);
}
if (!this.relatedTelemetry[key].listeners.includes(callback)) {
this.relatedTelemetry[key].listeners.push(callback);
return () => {
this.relatedTelemetry[key].listeners.remove(callback);
};
} else {
return () => {};
}
};
}
}
},
async getMostRecentRelatedTelemetry(key, targetDatum) {
if (!this.hasRelatedTelemetry) {
throw new Error(`${this.domainObject.name} does not have any related telemetry`);
}
if (!this.relatedTelemetry[key]) {
throw new Error(`${key} does not exist on related telemetry`);
}
let mostRecent;
let valueKey = this.relatedTelemetry[key].historical.valueKey;
let valuesOnTelemetry = this.relatedTelemetry[key].historicalValuesOnTelemetry;
if (valuesOnTelemetry) {
mostRecent = targetDatum[valueKey];
if (mostRecent) {
return mostRecent;
} else {
console.warn(`Related Telemetry for ${key} does NOT exist on this telemetry datum as configuration implied.`);
return;
}
}
mostRecent = await this.relatedTelemetry[key].requestLatestFor(targetDatum);
return mostRecent[valueKey];
},
// will subscribe to data for this key if not already done
subscribeToDataForKey(key) {
if (this.relatedTelemetry[key].isSubscribed) {
return;
}
if (this.relatedTelemetry[key].realtimeDomainObject) {
this.relatedTelemetry[key].unsubscribe = this.openmct.telemetry.subscribe(
this.relatedTelemetry[key].realtimeDomainObject, datum => {
this.relatedTelemetry[key].listeners.forEach(callback => {
callback(datum);
});
}
);
this.relatedTelemetry[key].isSubscribed = true;
}
},
async updateRelatedTelemetryForFocusedImage() {
if (!this.hasRelatedTelemetry) {
return;
}
const image = this.imageHistory[this.focusedImageIndex];
// set data ON image telemetry as well as in focusedImageRelatedData
for (let key of this.relatedTelemetry.keys) {
if (this.relatedTelemetry[key] && this.relatedTelemetry[key].historical) {
let valuesOnTelemetry = this.relatedTelemetry[key].historicalValuesOnTelemetry;
let value = await this.getMostRecentRelatedTelemetry(key, this.focusedImage);
if (!valuesOnTelemetry) {
image[key] = value; // manually add to telemetry
}
this.$set(this.focusedImageRelatedData, key, value);
}
}
},
focusElement() {
this.$el.focus();
},
@@ -509,6 +788,25 @@ export default {
},
isLeftOrRightArrowKey(keyCode) {
return [ARROW_RIGHT, ARROW_LEFT].includes(keyCode);
},
getImageNaturalDimensions() {
this.focusedImageNaturalAspectRatio = undefined;
const img = this.$refs.focusedImage;
// TODO - should probably cache this
img.addEventListener('load', () => {
this.focusedImageNaturalAspectRatio = img.naturalWidth / img.naturalHeight;
}, { once: true });
},
pollResizeImageContainer() {
if (this.$refs.focusedImage.clientWidth !== this.imageContainerWidth) {
this.imageContainerWidth = this.$refs.focusedImage.clientWidth;
}
if (this.$refs.focusedImage.clientHeight !== this.imageContainerHeight) {
this.imageContainerHeight = this.$refs.focusedImage.clientHeight;
}
}
}
};

View File

@@ -23,6 +23,7 @@
background-color: $colorPlotBg;
border: 1px solid transparent;
flex: 1 1 auto;
height: 0;
&.unnsynced{
@include sUnsynced();
@@ -30,10 +31,9 @@
}
&__image {
@include abs(); // Safari fix
background-position: center;
background-repeat: no-repeat;
background-size: contain;
height: 100%;
width: 100%;
object-fit: contain;
}
}

View File

@@ -1,3 +1,25 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import ImageryViewProvider from './ImageryViewProvider';
export default function () {

View File

@@ -17,6 +17,7 @@
@import "../plugins/folderView/components/list-item.scss";
@import "../plugins/folderView/components/list-view.scss";
@import "../plugins/imagery/components/imagery-view-layout.scss";
@import "../plugins/imagery/components/Compass/compass.scss";
@import "../plugins/telemetryTable/components/table-row.scss";
@import "../plugins/telemetryTable/components/table-footer-indicator.scss";
@import "../plugins/tabs/components/tabs.scss";