Compare commits
	
		
			121 Commits
		
	
	
		
			fix-view-s
			...
			imagery-en
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					eb97e94cd6 | ||
| 
						 | 
					e7b8c42f02 | ||
| 
						 | 
					3810b6c441 | ||
| 
						 | 
					2c62b4c1bc | ||
| 
						 | 
					ca47fb6f2d | ||
| 
						 | 
					5d4d87cd89 | ||
| 
						 | 
					f32602343d | ||
| 
						 | 
					bf82abd464 | ||
| 
						 | 
					ab31581ea4 | ||
| 
						 | 
					35bad9cb82 | ||
| 
						 | 
					03a104c9f5 | ||
| 
						 | 
					99ace5ec9b | ||
| 
						 | 
					93785544f1 | ||
| 
						 | 
					ed0095fc00 | ||
| 
						 | 
					63cf6e8156 | ||
| 
						 | 
					e8600d23e1 | ||
| 
						 | 
					36e720ad85 | ||
| 
						 | 
					46e926aa08 | ||
| 
						 | 
					429a628c92 | ||
| 
						 | 
					03e1229576 | ||
| 
						 | 
					05c8a8a2f0 | ||
| 
						 | 
					7a7ec7c9b7 | ||
| 
						 | 
					b5fcda3107 | ||
| 
						 | 
					ab4e770b79 | ||
| 
						 | 
					3f140de03a | ||
| 
						 | 
					9ee6cca07d | ||
| 
						 | 
					54182e400a | ||
| 
						 | 
					863533910e | ||
| 
						 | 
					edbdf432d1 | ||
| 
						 | 
					35256b6e96 | ||
| 
						 | 
					375bbd244e | ||
| 
						 | 
					8090e27b7b | ||
| 
						 | 
					275410f99c | ||
| 
						 | 
					31ab08c9d3 | ||
| 
						 | 
					082a89440e | ||
| 
						 | 
					c729732541 | ||
| 
						 | 
					8d3737912b | ||
| 
						 | 
					d6a71adb7f | ||
| 
						 | 
					8397b13c57 | ||
| 
						 | 
					6a4ceb5219 | ||
| 
						 | 
					c25b196b8f | ||
| 
						 | 
					cd5cc4c76c | ||
| 
						 | 
					f8b818e78b | ||
| 
						 | 
					6cea1a77e5 | ||
| 
						 | 
					88ff09857b | ||
| 
						 | 
					b409d3cb1e | ||
| 
						 | 
					a64e3e5ca0 | ||
| 
						 | 
					d6ba2f8b4c | ||
| 
						 | 
					4f1642a8d6 | ||
| 
						 | 
					9b73b45ba9 | ||
| 
						 | 
					98a048062f | ||
| 
						 | 
					9b114c49df | ||
| 
						 | 
					2c0c998e29 | ||
| 
						 | 
					a001e07600 | ||
| 
						 | 
					157564487d | ||
| 
						 | 
					fcbd8c682a | ||
| 
						 | 
					4b40233bf3 | ||
| 
						 | 
					fa2197f9c1 | ||
| 
						 | 
					f3f833a337 | ||
| 
						 | 
					e6e8b8e048 | ||
| 
						 | 
					6a2c079336 | ||
| 
						 | 
					0d23fe3d14 | ||
| 
						 | 
					1d645a8472 | ||
| 
						 | 
					334aeb42ae | ||
| 
						 | 
					5900bb0d98 | ||
| 
						 | 
					a2c350b105 | ||
| 
						 | 
					174f212328 | ||
| 
						 | 
					a28ec45f71 | ||
| 
						 | 
					fcc6bb9873 | ||
| 
						 | 
					45578b113f | ||
| 
						 | 
					5ef14b0975 | ||
| 
						 | 
					2be429a04f | ||
| 
						 | 
					c4ce405b1e | ||
| 
						 | 
					b95f844a4e | ||
| 
						 | 
					3804fe1a1e | ||
| 
						 | 
					7576673e77 | ||
| 
						 | 
					63e04caab6 | ||
| 
						 | 
					956cfbd01f | ||
| 
						 | 
					6c77be32c7 | ||
| 
						 | 
					af4c7c9ca0 | ||
| 
						 | 
					1697362994 | ||
| 
						 | 
					e69911385f | ||
| 
						 | 
					6478267cbe | ||
| 
						 | 
					22d53c1ccd | ||
| 
						 | 
					633a95dd27 | ||
| 
						 | 
					f732167e02 | ||
| 
						 | 
					50ff26ad5d | ||
| 
						 | 
					f056e8e57b | ||
| 
						 | 
					1d56fd98dc | ||
| 
						 | 
					320217f8c4 | ||
| 
						 | 
					b43fef6e21 | ||
| 
						 | 
					d04c29345b | ||
| 
						 | 
					49afec5cdd | ||
| 
						 | 
					24b96cdb47 | ||
| 
						 | 
					14ce4a1aa0 | ||
| 
						 | 
					43a8901c34 | ||
| 
						 | 
					28d97be60e | ||
| 
						 | 
					4bb2b35124 | ||
| 
						 | 
					1f6e91c6b5 | ||
| 
						 | 
					0b078497f1 | ||
| 
						 | 
					060a1b17db | ||
| 
						 | 
					db50b8b732 | ||
| 
						 | 
					62de05808e | ||
| 
						 | 
					417f81b7fd | ||
| 
						 | 
					f219394abd | ||
| 
						 | 
					4e5c74ecef | ||
| 
						 | 
					218530e436 | ||
| 
						 | 
					0890499a2b | ||
| 
						 | 
					d9dad09dfd | ||
| 
						 | 
					9af5df0f20 | ||
| 
						 | 
					1580a61092 | ||
| 
						 | 
					c236444a05 | ||
| 
						 | 
					39c1eb1d5b | ||
| 
						 | 
					95caab944d | ||
| 
						 | 
					ac89e51d1b | ||
| 
						 | 
					85d9ed8287 | ||
| 
						 | 
					aedc24a2da | ||
| 
						 | 
					823eda4465 | ||
| 
						 | 
					7eaa1d3e2b | ||
| 
						 | 
					4633436cbd | ||
| 
						 | 
					b68a7e27c9 | 
@@ -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';
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										127
									
								
								src/plugins/imagery/components/Compass/Compass.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										127
									
								
								src/plugins/imagery/components/Compass/Compass.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,127 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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"
 | 
			
		||||
        :heading="heading"
 | 
			
		||||
        :roll="roll"
 | 
			
		||||
        :sun-heading="sunHeading"
 | 
			
		||||
        :camera-field-of-view="cameraFieldOfView"
 | 
			
		||||
        :camera-pan="cameraPan"
 | 
			
		||||
    />
 | 
			
		||||
    <CompassRose
 | 
			
		||||
        v-if="shouldDisplayCompassRose"
 | 
			
		||||
        :heading="heading"
 | 
			
		||||
        :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.heading !== undefined;
 | 
			
		||||
        },
 | 
			
		||||
        shouldDisplayCompassHUD() {
 | 
			
		||||
            return this.heading !== undefined;
 | 
			
		||||
        },
 | 
			
		||||
        // degrees from north heading
 | 
			
		||||
        heading() {
 | 
			
		||||
            return this.image.heading;
 | 
			
		||||
        },
 | 
			
		||||
        roll() {
 | 
			
		||||
            return this.image.roll;
 | 
			
		||||
        },
 | 
			
		||||
        pitch() {
 | 
			
		||||
            return this.image.pitch;
 | 
			
		||||
        },
 | 
			
		||||
        // degrees from north heading
 | 
			
		||||
        sunHeading() {
 | 
			
		||||
            return this.image.sunOrientation;
 | 
			
		||||
        },
 | 
			
		||||
        // degrees from spacecraft heading
 | 
			
		||||
        cameraPan() {
 | 
			
		||||
            return this.image.cameraPan;
 | 
			
		||||
        },
 | 
			
		||||
        cameraTilt() {
 | 
			
		||||
            return this.image.cameraTilt;
 | 
			
		||||
        },
 | 
			
		||||
        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>
 | 
			
		||||
							
								
								
									
										171
									
								
								src/plugins/imagery/components/Compass/CompassHUD.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										171
									
								
								src/plugins/imagery/components/Compass/CompassHUD.vue
									
									
									
									
									
										Normal 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: {
 | 
			
		||||
        heading: {
 | 
			
		||||
            type: Number,
 | 
			
		||||
            required: true
 | 
			
		||||
        },
 | 
			
		||||
        roll: {
 | 
			
		||||
            type: Number,
 | 
			
		||||
            default: undefined
 | 
			
		||||
        },
 | 
			
		||||
        sunHeading: {
 | 
			
		||||
            type: Number,
 | 
			
		||||
            default: undefined
 | 
			
		||||
        },
 | 
			
		||||
        cameraFieldOfView: {
 | 
			
		||||
            type: Number,
 | 
			
		||||
            default: undefined
 | 
			
		||||
        },
 | 
			
		||||
        cameraPan: {
 | 
			
		||||
            type: Number,
 | 
			
		||||
            default: undefined
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    computed: {
 | 
			
		||||
        skewCompassHUDStyle() {
 | 
			
		||||
            if (this.roll === undefined || this.roll === 0) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            const origin = this.roll > 0 ? 'left bottom' : 'right top';
 | 
			
		||||
 | 
			
		||||
            return {
 | 
			
		||||
                'transform-origin': origin,
 | 
			
		||||
                transform: `skew(0, ${ this.roll }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);
 | 
			
		||||
        },
 | 
			
		||||
        normalizedHeading() {
 | 
			
		||||
            return normalizeDegrees(this.heading);
 | 
			
		||||
        },
 | 
			
		||||
        visibleRange() {
 | 
			
		||||
            const min = normalizeDegrees(this.normalizedHeading + this.cameraPan - this.cameraFieldOfView / 2);
 | 
			
		||||
            const max = normalizeDegrees(this.normalizedHeading + this.cameraPan + this.cameraFieldOfView / 2);
 | 
			
		||||
 | 
			
		||||
            return [
 | 
			
		||||
                min,
 | 
			
		||||
                max
 | 
			
		||||
            ];
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
							
								
								
									
										262
									
								
								src/plugins/imagery/components/Compass/CompassRose.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										262
									
								
								src/plugins/imagery/components/Compass/CompassRose.vue
									
									
									
									
									
										Normal 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-spacecraft-body"
 | 
			
		||||
        :style="headingStyle"
 | 
			
		||||
    >
 | 
			
		||||
    </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: {
 | 
			
		||||
        heading: {
 | 
			
		||||
            type: Number,
 | 
			
		||||
            required: true
 | 
			
		||||
        },
 | 
			
		||||
        sunHeading: {
 | 
			
		||||
            type: Number,
 | 
			
		||||
            default: undefined
 | 
			
		||||
        },
 | 
			
		||||
        cameraFieldOfView: {
 | 
			
		||||
            type: Number,
 | 
			
		||||
            default: undefined
 | 
			
		||||
        },
 | 
			
		||||
        cameraPan: {
 | 
			
		||||
            type: Number,
 | 
			
		||||
            default: undefined
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    data() {
 | 
			
		||||
        return {
 | 
			
		||||
            lockBezel: true
 | 
			
		||||
        };
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    computed: {
 | 
			
		||||
        compassHeading() {
 | 
			
		||||
            return this.lockBezel ? normalizeDegrees(this.heading) : 0;
 | 
			
		||||
        },
 | 
			
		||||
        north() {
 | 
			
		||||
            return normalizeDegrees(this.compassHeading - this.heading);
 | 
			
		||||
        },
 | 
			
		||||
        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 }`
 | 
			
		||||
            };
 | 
			
		||||
        },
 | 
			
		||||
        headingStyle() {
 | 
			
		||||
            return {
 | 
			
		||||
                transform: `translateX(-50%) rotate(${ this.compassHeading }deg)`
 | 
			
		||||
            };
 | 
			
		||||
        },
 | 
			
		||||
        cameraFOVHeading() {
 | 
			
		||||
            return this.compassHeading + 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>
 | 
			
		||||
							
								
								
									
										216
									
								
								src/plugins/imagery/components/Compass/compass.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										216
									
								
								src/plugins/imagery/components/Compass/compass.scss
									
									
									
									
									
										Normal 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;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/***************************** SPACECRAFT BODY */
 | 
			
		||||
.c-spacecraft-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%);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										44
									
								
								src/plugins/imagery/components/Compass/utils.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								src/plugins/imagery/components/Compass/utils.js
									
									
									
									
									
										Normal 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;
 | 
			
		||||
}
 | 
			
		||||
@@ -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"
 | 
			
		||||
@@ -61,11 +92,25 @@
 | 
			
		||||
        <div class="c-imagery__control-bar">
 | 
			
		||||
            <div class="c-imagery__time">
 | 
			
		||||
                <div class="c-imagery__timestamp u-style-receiver js-style-receiver">{{ time }}</div>
 | 
			
		||||
 | 
			
		||||
                <!-- image fresh -->
 | 
			
		||||
                <div
 | 
			
		||||
                    v-if="canTrackDuration"
 | 
			
		||||
                    :class="{'c-imagery--new': isImageNew && !refreshCSS}"
 | 
			
		||||
                    class="c-imagery__age icon-timer"
 | 
			
		||||
                >{{ formattedDuration }}</div>
 | 
			
		||||
 | 
			
		||||
                <!-- spacecraft position fresh -->
 | 
			
		||||
                <div
 | 
			
		||||
                    v-if="relatedTelemetry.hasRelatedTelemetry && isSpacecraftPositionFresh"
 | 
			
		||||
                    class="c-imagery__age icon-check c-imagery--new"
 | 
			
		||||
                >POS</div>
 | 
			
		||||
 | 
			
		||||
                <!-- camera position fresh -->
 | 
			
		||||
                <div
 | 
			
		||||
                    v-if="relatedTelemetry.hasRelatedTelemetry && isCameraPositionFresh"
 | 
			
		||||
                    class="c-imagery__age icon-check c-imagery--new"
 | 
			
		||||
                >CAM</div>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="h-local-controls">
 | 
			
		||||
                <button
 | 
			
		||||
@@ -76,13 +121,14 @@
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div ref="thumbsWrapper"
 | 
			
		||||
         class="c-imagery__thumbs-wrapper"
 | 
			
		||||
         :class="{'is-paused': isPaused}"
 | 
			
		||||
         @scroll="handleScroll"
 | 
			
		||||
    <div
 | 
			
		||||
        ref="thumbsWrapper"
 | 
			
		||||
        class="c-imagery__thumbs-wrapper"
 | 
			
		||||
        :class="{'is-paused': isPaused}"
 | 
			
		||||
        @scroll="handleScroll"
 | 
			
		||||
    >
 | 
			
		||||
        <div v-for="(datum, index) in imageHistory"
 | 
			
		||||
             :key="datum.url"
 | 
			
		||||
             :key="datum.url + datum[timeKey]"
 | 
			
		||||
             class="c-imagery__thumb c-thumb"
 | 
			
		||||
             :class="{ selected: focusedImageIndex === index && isPaused }"
 | 
			
		||||
             @click="setFocusedImage(index, thumbnailClick)"
 | 
			
		||||
@@ -97,7 +143,10 @@
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import _ from 'lodash';
 | 
			
		||||
import moment from 'moment';
 | 
			
		||||
import Compass from './Compass/Compass.vue';
 | 
			
		||||
import RelatedTelemetry from './RelatedTelemetry/RelatedTelemetry';
 | 
			
		||||
 | 
			
		||||
const DEFAULT_DURATION_FORMATTER = 'duration';
 | 
			
		||||
const REFRESH_CSS_MS = 500;
 | 
			
		||||
@@ -116,6 +165,9 @@ const ARROW_RIGHT = 39;
 | 
			
		||||
const ARROW_LEFT = 37;
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    components: {
 | 
			
		||||
        Compass
 | 
			
		||||
    },
 | 
			
		||||
    inject: ['openmct', 'domainObject'],
 | 
			
		||||
    data() {
 | 
			
		||||
        let timeSystem = this.openmct.time.timeSystem();
 | 
			
		||||
@@ -137,7 +189,14 @@ export default {
 | 
			
		||||
            refreshCSS: false,
 | 
			
		||||
            keyString: undefined,
 | 
			
		||||
            focusedImageIndex: undefined,
 | 
			
		||||
            numericDuration: undefined
 | 
			
		||||
            focusedImageRelatedTelemetry: {},
 | 
			
		||||
            numericDuration: undefined,
 | 
			
		||||
            metadataEndpoints: {},
 | 
			
		||||
            relatedTelemetry: {},
 | 
			
		||||
            latestRelatedTelemetry: {},
 | 
			
		||||
            focusedImageNaturalAspectRatio: undefined,
 | 
			
		||||
            imageContainerWidth: undefined,
 | 
			
		||||
            imageContainerHeight: undefined
 | 
			
		||||
        };
 | 
			
		||||
    },
 | 
			
		||||
    computed: {
 | 
			
		||||
@@ -195,15 +254,69 @@ export default {
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return result;
 | 
			
		||||
        },
 | 
			
		||||
        shouldDisplayCompass() {
 | 
			
		||||
            return this.focusedImage !== undefined
 | 
			
		||||
                && this.focusedImageNaturalAspectRatio !== undefined
 | 
			
		||||
                && this.imageContainerWidth !== undefined
 | 
			
		||||
                && this.imageContainerHeight !== undefined;
 | 
			
		||||
        },
 | 
			
		||||
        isSpacecraftPositionFresh() {
 | 
			
		||||
            let isFresh = undefined;
 | 
			
		||||
            let latest = this.latestRelatedTelemetry;
 | 
			
		||||
            let focused = this.focusedImageRelatedTelemetry;
 | 
			
		||||
 | 
			
		||||
            if (this.relatedTelemetry.hasRelatedTelemetry) {
 | 
			
		||||
                isFresh = true;
 | 
			
		||||
                for (let key of this.spacecraftKeys) {
 | 
			
		||||
                    if (this.relatedTelemetry[key] && latest[key] && focused[key]) {
 | 
			
		||||
                        if (!this.relatedTelemetry[key].comparisonFunction(latest[key], focused[key])) {
 | 
			
		||||
                            isFresh = false;
 | 
			
		||||
                        }
 | 
			
		||||
                    } else {
 | 
			
		||||
                        isFresh = false;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return isFresh;
 | 
			
		||||
        },
 | 
			
		||||
        isCameraPositionFresh() {
 | 
			
		||||
            let isFresh = undefined;
 | 
			
		||||
            let latest = this.latestRelatedTelemetry;
 | 
			
		||||
            let focused = this.focusedImageRelatedTelemetry;
 | 
			
		||||
 | 
			
		||||
            if (this.relatedTelemetry.hasRelatedTelemetry) {
 | 
			
		||||
                isFresh = true;
 | 
			
		||||
 | 
			
		||||
                // camera freshness relies on spacecraft position freshness
 | 
			
		||||
                if (this.isSpacecraftPositionFresh) {
 | 
			
		||||
                    for (let key of this.cameraKeys) {
 | 
			
		||||
                        if (this.relatedTelemetry[key] && latest[key] && focused[key]) {
 | 
			
		||||
                            if (!this.relatedTelemetry[key].comparisonFunction(latest[key], focused[key])) {
 | 
			
		||||
                                isFresh = false;
 | 
			
		||||
                            }
 | 
			
		||||
                        } else {
 | 
			
		||||
                            isFresh = false;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                } else {
 | 
			
		||||
                    isFresh = false;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return isFresh;
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    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 +325,14 @@ 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);
 | 
			
		||||
 | 
			
		||||
        // related telemetry keys
 | 
			
		||||
        this.spacecraftKeys = ['heading', 'roll', 'pitch'];
 | 
			
		||||
        this.cameraKeys = ['cameraPan', 'cameraTilt'];
 | 
			
		||||
        this.sunKeys = ['sunOrientation'];
 | 
			
		||||
 | 
			
		||||
        // initialize
 | 
			
		||||
        this.timeKey = this.timeSystem.key;
 | 
			
		||||
@@ -222,6 +341,18 @@ export default {
 | 
			
		||||
        // kickoff
 | 
			
		||||
        this.subscribe();
 | 
			
		||||
        this.requestHistory();
 | 
			
		||||
 | 
			
		||||
        // related telemetry
 | 
			
		||||
        await this.initializeRelatedTelemetry();
 | 
			
		||||
        this.updateRelatedTelemetryForFocusedImage();
 | 
			
		||||
        this.trackLatestRelatedTelemetry();
 | 
			
		||||
 | 
			
		||||
        // for scrolling through images quickly and resizing the object view
 | 
			
		||||
        _.debounce(this.updateRelatedTelemetryForFocusedImage, 400);
 | 
			
		||||
        _.debounce(this.resizeImageContainer, 400);
 | 
			
		||||
 | 
			
		||||
        this.imageContainerResizeObserver = new ResizeObserver(this.resizeImageContainer);
 | 
			
		||||
        this.imageContainerResizeObserver.observe(this.$refs.focusedImage);
 | 
			
		||||
    },
 | 
			
		||||
    updated() {
 | 
			
		||||
        this.scrollToRight();
 | 
			
		||||
@@ -232,12 +363,115 @@ export default {
 | 
			
		||||
            delete this.unsubscribe;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.imageContainerResizeObserver.disconnect();
 | 
			
		||||
 | 
			
		||||
        if (this.relatedTelemetry.hasRelatedTelemetry) {
 | 
			
		||||
            this.relatedTelemetry.destroy();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.stopDurationTracking();
 | 
			
		||||
        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.relatedTelemetry.hasRelatedTelemetry) {
 | 
			
		||||
            for (let key of this.relatedTelemetry.keys) {
 | 
			
		||||
                if (this.relatedTelemetry[key].unsubscribe) {
 | 
			
		||||
                    this.relatedTelemetry[key].unsubscribe();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    methods: {
 | 
			
		||||
        async initializeRelatedTelemetry() {
 | 
			
		||||
            this.relatedTelemetry = new RelatedTelemetry(
 | 
			
		||||
                this.openmct,
 | 
			
		||||
                this.domainObject,
 | 
			
		||||
                [...this.spacecraftKeys, ...this.cameraKeys, ...this.sunKeys]
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            if (this.relatedTelemetry.hasRelatedTelemetry) {
 | 
			
		||||
                await this.relatedTelemetry.load();
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        async getMostRecentRelatedTelemetry(key, targetDatum) {
 | 
			
		||||
            if (!this.relatedTelemetry.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].hasTelemetryOnDatum;
 | 
			
		||||
 | 
			
		||||
            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.relatedTelemetry.hasRelatedTelemetry || !this.focusedImage) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // set data ON image telemetry as well as in focusedImageRelatedTelemetry
 | 
			
		||||
            for (let key of this.relatedTelemetry.keys) {
 | 
			
		||||
                if (this.relatedTelemetry[key] && this.relatedTelemetry[key].historical) {
 | 
			
		||||
                    let valuesOnTelemetry = this.relatedTelemetry[key].hasTelemetryOnDatum;
 | 
			
		||||
                    let value = await this.getMostRecentRelatedTelemetry(key, this.focusedImage);
 | 
			
		||||
 | 
			
		||||
                    if (!valuesOnTelemetry) {
 | 
			
		||||
                        this.$set(this.imageHistory[this.focusedImageIndex], key, value); // manually add to telemetry
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    this.$set(this.focusedImageRelatedTelemetry, key, value);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        trackLatestRelatedTelemetry() {
 | 
			
		||||
            [...this.spacecraftKeys, ...this.cameraKeys, ...this.sunKeys].forEach(key => {
 | 
			
		||||
                if (this.relatedTelemetry[key] && this.relatedTelemetry[key].subscribe) {
 | 
			
		||||
                    this.relatedTelemetry[key].subscribe((datum) => {
 | 
			
		||||
                        let valueKey = this.relatedTelemetry[key].realtime.valueKey;
 | 
			
		||||
                        this.$set(this.latestRelatedTelemetry, key, datum[valueKey]);
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        focusElement() {
 | 
			
		||||
            this.$el.focus();
 | 
			
		||||
        },
 | 
			
		||||
@@ -358,6 +592,7 @@ export default {
 | 
			
		||||
            this.requestCount++;
 | 
			
		||||
            const requestId = this.requestCount;
 | 
			
		||||
            this.imageHistory = [];
 | 
			
		||||
 | 
			
		||||
            let data = await this.openmct.telemetry
 | 
			
		||||
                .request(this.domainObject, bounds) || [];
 | 
			
		||||
 | 
			
		||||
@@ -509,6 +744,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 });
 | 
			
		||||
        },
 | 
			
		||||
        resizeImageContainer() {
 | 
			
		||||
            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;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,162 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
 | 
			
		||||
function copyRelatedMetadata(metadata) {
 | 
			
		||||
    let compare = metadata.comparisonFunction;
 | 
			
		||||
    let copiedMetadata = JSON.parse(JSON.stringify(metadata));
 | 
			
		||||
    copiedMetadata.comparisonFunction = compare;
 | 
			
		||||
 | 
			
		||||
    return copiedMetadata;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default class RelatedTelemetry {
 | 
			
		||||
 | 
			
		||||
    constructor(openmct, domainObject, telemetryKeys) {
 | 
			
		||||
        this._openmct = openmct;
 | 
			
		||||
        this._domainObject = domainObject;
 | 
			
		||||
 | 
			
		||||
        let metadata = this._openmct.telemetry.getMetadata(this._domainObject);
 | 
			
		||||
        let imageHints = metadata.valuesForHints(['image'])[0];
 | 
			
		||||
 | 
			
		||||
        this.hasRelatedTelemetry = imageHints.relatedTelemetry !== undefined;
 | 
			
		||||
 | 
			
		||||
        if (this.hasRelatedTelemetry) {
 | 
			
		||||
            this.keys = telemetryKeys;
 | 
			
		||||
 | 
			
		||||
            this._timeFormatter = undefined;
 | 
			
		||||
            this._timeSystemChange(this._openmct.time.timeSystem());
 | 
			
		||||
 | 
			
		||||
            // grab related telemetry metadata
 | 
			
		||||
            for (let key of this.keys) {
 | 
			
		||||
                if (imageHints.relatedTelemetry[key]) {
 | 
			
		||||
                    this[key] = copyRelatedMetadata(imageHints.relatedTelemetry[key]);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            this.load = this.load.bind(this);
 | 
			
		||||
            this._parseTime = this._parseTime.bind(this);
 | 
			
		||||
            this._timeSystemChange = this._timeSystemChange.bind(this);
 | 
			
		||||
            this.destroy = this.destroy.bind(this);
 | 
			
		||||
 | 
			
		||||
            this._openmct.time.on('timeSystem', this._timeSystemChange);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async load() {
 | 
			
		||||
        if (!this.hasRelatedTelemetry) {
 | 
			
		||||
            throw new Error('This domain object does not have related telemetry, use "hasRelatedTelemetry" to check before loading.');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await Promise.all(
 | 
			
		||||
            this.keys.map(async (key) => {
 | 
			
		||||
                if (this[key].historical) {
 | 
			
		||||
                    await this._initializeHistorical(key);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (this[key].realtime && this[key].realtime.telemetryObjectId) {
 | 
			
		||||
                    await this._intializeRealtime(key);
 | 
			
		||||
                }
 | 
			
		||||
            })
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async _initializeHistorical(key) {
 | 
			
		||||
        if (this[key].historical.telemetryObjectId) {
 | 
			
		||||
            this[key].historicalDomainObject = await this._openmct.objects.get(this[key].historical.telemetryObjectId);
 | 
			
		||||
 | 
			
		||||
            this[key].requestLatestFor = async (datum) => {
 | 
			
		||||
                const options = {
 | 
			
		||||
                    start: this._openmct.time.bounds().start,
 | 
			
		||||
                    end: this._parseTime(datum),
 | 
			
		||||
                    strategy: 'latest'
 | 
			
		||||
                };
 | 
			
		||||
                let results = await this._openmct.telemetry
 | 
			
		||||
                    .request(this[key].historicalDomainObject, options);
 | 
			
		||||
 | 
			
		||||
                return results[results.length - 1];
 | 
			
		||||
            };
 | 
			
		||||
        } else {
 | 
			
		||||
            this[key].historical.hasTelemetryOnDatum = true;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async _intializeRealtime(key) {
 | 
			
		||||
        this[key].realtimeDomainObject = await this._openmct.objects.get(this[key].realtime.telemetryObjectId);
 | 
			
		||||
        this[key].listeners = [];
 | 
			
		||||
        this[key].subscribe = (callback) => {
 | 
			
		||||
 | 
			
		||||
            if (!this[key].isSubscribed) {
 | 
			
		||||
                this._subscribeToDataForKey(key);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (!this[key].listeners.includes(callback)) {
 | 
			
		||||
                this[key].listeners.push(callback);
 | 
			
		||||
 | 
			
		||||
                return () => {
 | 
			
		||||
                    this[key].listeners.remove(callback);
 | 
			
		||||
                };
 | 
			
		||||
            } else {
 | 
			
		||||
                return () => {};
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _subscribeToDataForKey(key) {
 | 
			
		||||
        if (this[key].isSubscribed) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (this[key].realtimeDomainObject) {
 | 
			
		||||
            this[key].unsubscribe = this._openmct.telemetry.subscribe(
 | 
			
		||||
                this[key].realtimeDomainObject, datum => {
 | 
			
		||||
                    this[key].listeners.forEach(callback => {
 | 
			
		||||
                        callback(datum);
 | 
			
		||||
                    });
 | 
			
		||||
 | 
			
		||||
                }
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            this[key].isSubscribed = true;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _parseTime(datum) {
 | 
			
		||||
        return this._timeFormatter.parse(datum);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _timeSystemChange(system) {
 | 
			
		||||
        let key = system.key;
 | 
			
		||||
        let metadata = this._openmct.telemetry.getMetadata(this._domainObject);
 | 
			
		||||
        let metadataValue = metadata.value(key) || { format: key };
 | 
			
		||||
        this._timeFormatter = this._openmct.telemetry.getValueFormatter(metadataValue);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    destroy() {
 | 
			
		||||
        this._openmct.time.off('timeSystem', this._timeSystemChange);
 | 
			
		||||
        for (let key of this.keys) {
 | 
			
		||||
            if (this[key].unsubscribe) {
 | 
			
		||||
                this[key].unsubscribe();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -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;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -71,13 +71,14 @@
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__age {
 | 
			
		||||
        border-radius: $controlCr;
 | 
			
		||||
        border-radius: $smallCr;
 | 
			
		||||
        display: flex;
 | 
			
		||||
        flex-shrink: 0;
 | 
			
		||||
        align-items: baseline;
 | 
			
		||||
        padding: 1px $interiorMarginSm;
 | 
			
		||||
        align-items: center;
 | 
			
		||||
        padding: 2px $interiorMarginSm;
 | 
			
		||||
 | 
			
		||||
        &:before {
 | 
			
		||||
            font-size: 0.9em;
 | 
			
		||||
            opacity: 0.5;
 | 
			
		||||
            margin-right: $interiorMarginSm;
 | 
			
		||||
        }
 | 
			
		||||
@@ -86,8 +87,9 @@
 | 
			
		||||
    &--new {
 | 
			
		||||
        // New imagery
 | 
			
		||||
        $bgColor: $colorOk;
 | 
			
		||||
        color: $colorOkFg;
 | 
			
		||||
        background: rgba($bgColor, 0.5);
 | 
			
		||||
        @include flash($animName: flashImageAge, $dur: 250ms, $valStart: rgba($colorOk, 0.7), $valEnd: rgba($colorOk, 0));
 | 
			
		||||
        @include flash($animName: flashImageAge, $iter: 2, $dur: 250ms, $valStart: rgba($colorOk, 0.7), $valEnd: rgba($colorOk, 0));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__thumbs-wrapper {
 | 
			
		||||
 
 | 
			
		||||
@@ -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 () {
 | 
			
		||||
 
 | 
			
		||||
@@ -37,7 +37,7 @@ function getImageInfo(doc) {
 | 
			
		||||
    let imageElement = doc.querySelectorAll(MAIN_IMAGE_CLASS)[0];
 | 
			
		||||
    let timestamp = imageElement.dataset.openmctImageTimestamp;
 | 
			
		||||
    let identifier = imageElement.dataset.openmctObjectKeystring;
 | 
			
		||||
    let url = imageElement.style.backgroundImage;
 | 
			
		||||
    let url = imageElement.src;
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
        timestamp,
 | 
			
		||||
@@ -70,7 +70,7 @@ function generateTelemetry(start, count) {
 | 
			
		||||
    return telemetry;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
describe("The Imagery View Layout", () => {
 | 
			
		||||
fdescribe("The Imagery View Layout", () => {
 | 
			
		||||
    const imageryKey = 'example.imagery';
 | 
			
		||||
    const START = Date.now();
 | 
			
		||||
    const COUNT = 10;
 | 
			
		||||
 
 | 
			
		||||
@@ -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";
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user