Compass rose fix (#6318)

* won't mount if cameraAngleOfView undefined

* fix non-gimbling camera azimuth

correct pan to azimuth

* reorganize shared props passing

* enable hud for non-gimbling cameras

* fix unit tests

* rotate function needs to work with numbers

* avoid -0

* fix: don't delete imagery size, update related telemetry on focusedImage change

* refactor: remove unused prop

* fix: ensure thumbnail key is unique

* fix: watch `focusedImage`, not `focusedImageIndex`

- Corrects a false assumption that if the `focusedImageIndex` changes, the `focusedImage` has changed. This was causing us to mistakenly reset a lot of display props that control whether or not the Compass shows.

- For example, if an image falls out of bounds, the `focusedImageIndex` will change as the old image is removed from the array.

---------

Co-authored-by: Jesse Mazzella <jesse.d.mazzella@nasa.gov>
Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com>
Co-authored-by: Andrew Henry <akhenry@gmail.com>
This commit is contained in:
David Tsay 2023-02-13 11:28:00 -08:00 committed by GitHub
parent 4fa9a9697b
commit 5da1c9c0d7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 133 additions and 76 deletions

View File

@ -275,7 +275,7 @@ function pointForTimestamp(timestamp, name, imageSamples, delay) {
local: Math.floor(timestamp / delay) * delay, local: Math.floor(timestamp / delay) * delay,
url, url,
sunOrientation: getCompassValues(0, 360), sunOrientation: getCompassValues(0, 360),
cameraPan: getCompassValues(0, 360), cameraAzimuth: getCompassValues(0, 360),
heading: getCompassValues(0, 360), heading: getCompassValues(0, 360),
transformations: navCamTransformations, transformations: navCamTransformations,
imageDownloadName imageDownloadName

View File

@ -26,18 +26,23 @@
:style="`width: 100%; height: 100%`" :style="`width: 100%; height: 100%`"
> >
<CompassHUD <CompassHUD
v-if="showCompassHUD"
:sun-heading="sunHeading"
:camera-angle-of-view="cameraAngleOfView" :camera-angle-of-view="cameraAngleOfView"
:camera-pan="cameraPan" :heading="heading"
:camera-azimuth="cameraAzimuth"
:transformations="transformations"
:has-gimble="hasGimble"
:normalized-camera-azimuth="normalizedCameraAzimuth"
:sun-heading="sunHeading"
/> />
<CompassRose <CompassRose
v-if="showCompassRose" :camera-angle-of-view="cameraAngleOfView"
:camera-pan="cameraPan"
:heading="heading" :heading="heading"
:sized-image-dimensions="sizedImageDimensions" :camera-azimuth="cameraAzimuth"
:sun-heading="sunHeading"
:transformations="transformations" :transformations="transformations"
:has-gimble="hasGimble"
:normalized-camera-azimuth="normalizedCameraAzimuth"
:sun-heading="sunHeading"
:sized-image-dimensions="sizedImageDimensions"
/> />
</div> </div>
</template> </template>
@ -45,6 +50,7 @@
<script> <script>
import CompassHUD from './CompassHUD.vue'; import CompassHUD from './CompassHUD.vue';
import CompassRose from './CompassRose.vue'; import CompassRose from './CompassRose.vue';
import { rotate } from './utils';
export default { export default {
components: { components: {
@ -62,11 +68,14 @@ export default {
} }
}, },
computed: { computed: {
showCompassHUD() { hasGimble() {
return this.hasCameraPan && this.cameraAngleOfView > 0; return this.cameraAzimuth !== undefined;
}, },
showCompassRose() { // compass ordinal orientation of camera
return (this.hasCameraPan || this.hasHeading) && this.cameraAngleOfView > 0; normalizedCameraAzimuth() {
return this.hasGimble
? rotate(this.cameraAzimuth)
: rotate(this.heading, -this.transformations.rotation || 0);
}, },
// horizontal rotation from north in degrees // horizontal rotation from north in degrees
heading() { heading() {
@ -80,14 +89,11 @@ export default {
return this.image.sunOrientation; return this.image.sunOrientation;
}, },
// horizontal rotation from north in degrees // horizontal rotation from north in degrees
cameraPan() { cameraAzimuth() {
return this.image.cameraPan; return this.image.cameraPan;
}, },
hasCameraPan() {
return this.cameraPan !== undefined;
},
cameraAngleOfView() { cameraAngleOfView() {
return this.transformations?.cameraAngleOfView; return this.transformations.cameraAngleOfView;
}, },
transformations() { transformations() {
return this.image.transformations; return this.image.transformations;

View File

@ -94,17 +94,33 @@ const COMPASS_POINTS = [
export default { export default {
props: { props: {
cameraAngleOfView: {
type: Number,
required: true
},
heading: {
type: Number,
required: true
},
cameraAzimuth: {
type: Number,
default: undefined
},
transformations: {
type: Object,
required: true
},
hasGimble: {
type: Boolean,
required: true
},
normalizedCameraAzimuth: {
type: Number,
required: true
},
sunHeading: { sunHeading: {
type: Number, type: Number,
default: undefined default: undefined
},
cameraAngleOfView: {
type: Number,
default: undefined
},
cameraPan: {
type: Number,
required: true
} }
}, },
computed: { computed: {
@ -130,10 +146,13 @@ export default {
left: `${ percentage * 100 }%` left: `${ percentage * 100 }%`
}; };
}, },
cameraRotation() {
return this.transformations?.rotation;
},
visibleRange() { visibleRange() {
return [ return [
rotate(this.cameraPan, -this.cameraAngleOfView / 2), rotate(this.normalizedCameraAzimuth, -this.cameraAngleOfView / 2),
rotate(this.cameraPan, this.cameraAngleOfView / 2) rotate(this.normalizedCameraAzimuth, this.cameraAngleOfView / 2)
]; ];
} }
} }

View File

@ -75,7 +75,6 @@
:style="sunHeadingStyle" :style="sunHeadingStyle"
/> />
<!-- Camera FOV -->
<mask <mask
id="mask2" id="mask2"
class="c-cr__cam-fov-l-mask" class="c-cr__cam-fov-l-mask"
@ -117,10 +116,10 @@
class="cr-vrover" class="cr-vrover"
:style="camAngleAndPositionStyle" :style="camAngleAndPositionStyle"
> >
<!-- Equipment body. Rotates relative to the camera pan value for cams that gimbal. --> <!-- Equipment body. Rotates relative to the camera pan value for cameras that gimble. -->
<path <path
class="cr-vrover__body" class="cr-vrover__body"
:style="camGimbalAngleStyle" :style="gimbledCameraPanStyle"
x x
fill-rule="evenodd" fill-rule="evenodd"
clip-rule="evenodd" clip-rule="evenodd"
@ -128,6 +127,7 @@
/> />
</g> </g>
<!-- Camera FOV -->
<g <g
class="c-cr__cam-fov" class="c-cr__cam-fov"
> >
@ -160,7 +160,7 @@
<!-- NSEW and ticks --> <!-- NSEW and ticks -->
<g <g
class="c-cr__nsew" class="c-cr__nsew"
:style="compassRoseStyle" :style="compassDialStyle"
> >
<g class="c-cr__ticks-major"> <g class="c-cr__ticks-major">
<path d="M50 3L43 10H57L50 3Z" /> <path d="M50 3L43 10H57L50 3Z" />
@ -259,23 +259,32 @@ import { throttle } from 'lodash';
export default { export default {
props: { props: {
cameraAngleOfView: {
type: Number,
required: true
},
heading: { heading: {
type: Number, type: Number,
required: true, required: true
default() {
return 0;
}
}, },
sunHeading: { cameraAzimuth: {
type: Number,
default: undefined
},
cameraPan: {
type: Number, type: Number,
default: undefined default: undefined
}, },
transformations: { transformations: {
type: Object, type: Object,
required: true
},
hasGimble: {
type: Boolean,
required: true
},
normalizedCameraAzimuth: {
type: Number,
required: true
},
sunHeading: {
type: Number,
default: undefined default: undefined
}, },
sizedImageDimensions: { sizedImageDimensions: {
@ -289,18 +298,6 @@ export default {
}; };
}, },
computed: { computed: {
cameraHeading() {
return this.cameraPan ?? this.heading;
},
cameraAngleOfView() {
const cameraAngleOfView = this.transformations?.cameraAngleOfView;
if (!cameraAngleOfView) {
console.warn('No Camera Angle of View provided');
}
return cameraAngleOfView;
},
camAngleAndPositionStyle() { camAngleAndPositionStyle() {
const translateX = this.transformations?.translateX; const translateX = this.transformations?.translateX;
const translateY = this.transformations?.translateY; const translateY = this.transformations?.translateY;
@ -309,18 +306,22 @@ export default {
return { transform: `translate(${translateX}%, ${translateY}%) rotate(${rotation}deg) scale(${scale})` }; return { transform: `translate(${translateX}%, ${translateY}%) rotate(${rotation}deg) scale(${scale})` };
}, },
camGimbalAngleStyle() { gimbledCameraPanStyle() {
const rotation = rotate(this.heading); if (!this.hasGimble) {
return;
}
const gimbledCameraPan = rotate(this.normalizedCameraAzimuth, -this.heading);
return { return {
transform: `rotate(${ rotation }deg)` transform: `rotate(${ -gimbledCameraPan }deg)`
}; };
}, },
compassRoseStyle() { compassDialStyle() {
return { transform: `rotate(${ this.north }deg)` }; return { transform: `rotate(${ this.north }deg)` };
}, },
north() { north() {
return this.lockCompass ? rotate(-this.cameraHeading) : 0; return this.lockCompass ? rotate(-this.normalizedCameraAzimuth) : 0;
}, },
cardinalTextRotateN() { cardinalTextRotateN() {
return { transform: `translateY(-27%) rotate(${ -this.north }deg)` }; return { transform: `translateY(-27%) rotate(${ -this.north }deg)` };
@ -348,7 +349,7 @@ export default {
}; };
}, },
cameraHeadingStyle() { cameraHeadingStyle() {
const rotation = rotate(this.north, this.cameraHeading); const rotation = rotate(this.north, this.normalizedCameraAzimuth);
return { return {
transform: `rotate(${ rotation }deg)` transform: `rotate(${ rotation }deg)`

View File

@ -35,8 +35,15 @@ describe("The Compass component", () => {
roll: 90, roll: 90,
pitch: 90, pitch: 90,
cameraTilt: 100, cameraTilt: 100,
cameraPan: 90, cameraAzimuth: 90,
sunAngle: 30 sunAngle: 30,
transformations: {
translateX: 0,
translateY: 18,
rotation: 0,
scale: 0.3,
cameraAngleOfView: 70
}
}; };
let propsData = { let propsData = {
naturalAspectRatio: 0.9, naturalAspectRatio: 0.9,
@ -44,8 +51,7 @@ describe("The Compass component", () => {
sizedImageDimensions: { sizedImageDimensions: {
width: 100, width: 100,
height: 100 height: 100
}, }
compassRoseSizingClasses: '--rose-small --rose-min'
}; };
app = new Vue({ app = new Vue({
@ -54,7 +60,6 @@ describe("The Compass component", () => {
return propsData; return propsData;
}, },
template: `<Compass template: `<Compass
:compass-rose-sizing-classes="compassRoseSizingClasses"
:image="image" :image="image"
:natural-aspect-ratio="naturalAspectRatio" :natural-aspect-ratio="naturalAspectRatio"
:sized-image-dimensions="sizedImageDimensions" :sized-image-dimensions="sizedImageDimensions"
@ -67,7 +72,7 @@ describe("The Compass component", () => {
app.$destroy(); app.$destroy();
}); });
describe("when a heading value exists on the image", () => { describe("when a heading value and cameraAngleOfView exists on the image", () => {
it("should display a compass rose", () => { it("should display a compass rose", () => {
let compassRoseElement = instance.$el.querySelector(COMPASS_ROSE_CLASS let compassRoseElement = instance.$el.querySelector(COMPASS_ROSE_CLASS

View File

@ -94,7 +94,6 @@
<Compass <Compass
v-if="shouldDisplayCompass" v-if="shouldDisplayCompass"
:image="focusedImage" :image="focusedImage"
:natural-aspect-ratio="focusedImageNaturalAspectRatio"
:sized-image-dimensions="sizedImageDimensions" :sized-image-dimensions="sizedImageDimensions"
/> />
</div> </div>
@ -171,7 +170,7 @@
> >
<ImageThumbnail <ImageThumbnail
v-for="(image, index) in imageHistory" v-for="(image, index) in imageHistory"
:key="`${image.thumbnailUrl || image.url}${image.time}`" :key="`${image.thumbnailUrl || image.url}-${image.time}-${index}`"
:image="image" :image="image"
:active="focusedImageIndex === index" :active="focusedImageIndex === index"
:selected="focusedImageIndex === index && isPaused" :selected="focusedImageIndex === index && isPaused"
@ -430,9 +429,12 @@ export default {
&& imageHeightAndWidth && imageHeightAndWidth
&& this.zoomFactor === 1 && this.zoomFactor === 1
&& this.imagePanned !== true; && this.imagePanned !== true;
const hasCameraConfigurations = this.focusedImage?.transformations !== undefined; const hasHeading = this.focusedImage?.heading !== undefined;
const hasCameraAngleOfView = this.focusedImage?.transformations?.cameraAngleOfView > 0;
return display && hasCameraConfigurations; return display
&& hasCameraAngleOfView
&& hasHeading;
}, },
isSpacecraftPositionFresh() { isSpacecraftPositionFresh() {
let isFresh = undefined; let isFresh = undefined;
@ -582,11 +584,34 @@ export default {
}, },
deep: true deep: true
}, },
focusedImageIndex() { focusedImage: {
this.trackDuration(); handler(newImage, oldImage) {
this.resetAgeCSS(); const newTime = newImage?.time;
this.updateRelatedTelemetryForFocusedImage(); const oldTime = oldImage?.time;
this.getImageNaturalDimensions(); const newUrl = newImage?.url;
const oldUrl = oldImage?.url;
// Skip if it's all falsy
if (!newTime && !oldTime && !newUrl && !oldUrl) {
return;
}
// Skip if it's the same image
if (newTime === oldTime && newUrl === oldUrl) {
return;
}
// Update image duration and reset age CSS
this.trackDuration();
this.resetAgeCSS();
// Reset image dimensions and calculate new dimensions
// on new image load
this.getImageNaturalDimensions();
// Get the related telemetry for the new image
this.updateRelatedTelemetryForFocusedImage();
}
}, },
bounds() { bounds() {
this.scrollHandler(); this.scrollHandler();
@ -771,6 +796,10 @@ export default {
this.layers = layersMetadata; this.layers = layersMetadata;
if (this.domainObject.configuration) { if (this.domainObject.configuration) {
const persistedLayers = this.domainObject.configuration.layers; const persistedLayers = this.domainObject.configuration.layers;
if (!persistedLayers) {
return;
}
layersMetadata.forEach((layer) => { layersMetadata.forEach((layer) => {
const persistedLayer = persistedLayers.find(object => object.name === layer.name); const persistedLayer = persistedLayers.find(object => object.name === layer.name);
if (persistedLayer) { if (persistedLayer) {

View File

@ -153,9 +153,6 @@ export default {
return; return;
} }
// forcibly reset the imageContainer size to prevent an aspect ratio distortion
delete this.imageContainerWidth;
delete this.imageContainerHeight;
this.bounds = bounds; // setting bounds for ImageryView watcher this.bounds = bounds; // setting bounds for ImageryView watcher
}, },
timeSystemChange() { timeSystemChange() {