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,
url,
sunOrientation: getCompassValues(0, 360),
cameraPan: getCompassValues(0, 360),
cameraAzimuth: getCompassValues(0, 360),
heading: getCompassValues(0, 360),
transformations: navCamTransformations,
imageDownloadName

View File

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

View File

@ -94,17 +94,33 @@ const COMPASS_POINTS = [
export default {
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: {
type: Number,
default: undefined
},
cameraAngleOfView: {
type: Number,
default: undefined
},
cameraPan: {
type: Number,
required: true
}
},
computed: {
@ -130,10 +146,13 @@ export default {
left: `${ percentage * 100 }%`
};
},
cameraRotation() {
return this.transformations?.rotation;
},
visibleRange() {
return [
rotate(this.cameraPan, -this.cameraAngleOfView / 2),
rotate(this.cameraPan, this.cameraAngleOfView / 2)
rotate(this.normalizedCameraAzimuth, -this.cameraAngleOfView / 2),
rotate(this.normalizedCameraAzimuth, this.cameraAngleOfView / 2)
];
}
}

View File

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

View File

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

View File

@ -94,7 +94,6 @@
<Compass
v-if="shouldDisplayCompass"
:image="focusedImage"
:natural-aspect-ratio="focusedImageNaturalAspectRatio"
:sized-image-dimensions="sizedImageDimensions"
/>
</div>
@ -171,7 +170,7 @@
>
<ImageThumbnail
v-for="(image, index) in imageHistory"
:key="`${image.thumbnailUrl || image.url}${image.time}`"
:key="`${image.thumbnailUrl || image.url}-${image.time}-${index}`"
:image="image"
:active="focusedImageIndex === index"
:selected="focusedImageIndex === index && isPaused"
@ -430,9 +429,12 @@ export default {
&& imageHeightAndWidth
&& this.zoomFactor === 1
&& 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() {
let isFresh = undefined;
@ -582,11 +584,34 @@ export default {
},
deep: true
},
focusedImageIndex() {
this.trackDuration();
this.resetAgeCSS();
this.updateRelatedTelemetryForFocusedImage();
this.getImageNaturalDimensions();
focusedImage: {
handler(newImage, oldImage) {
const newTime = newImage?.time;
const oldTime = oldImage?.time;
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() {
this.scrollHandler();
@ -771,6 +796,10 @@ export default {
this.layers = layersMetadata;
if (this.domainObject.configuration) {
const persistedLayers = this.domainObject.configuration.layers;
if (!persistedLayers) {
return;
}
layersMetadata.forEach((layer) => {
const persistedLayer = persistedLayers.find(object => object.name === layer.name);
if (persistedLayer) {

View File

@ -153,9 +153,6 @@ export default {
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
},
timeSystemChange() {