UNPKG

photo-sphere-viewer

Version:

A JavaScript library to display Photo Sphere panoramas

354 lines (302 loc) 11.7 kB
import assert from 'assert'; import { cleanPosition, getXMPValue, parseAngle, parsePosition, parseSpeed } from './psv'; describe('utils:psv:parseAngle', () => { it('should normalize number', () => { assert.strictEqual(parseAngle(0), 0, '0'); assert.strictEqual(parseAngle(Math.PI), Math.PI, 'PI'); assert.strictEqual(parseAngle(3 * Math.PI), Math.PI, '3xPI'); assert.strictEqual(parseAngle(0, true), 0, '0 centered'); assert.strictEqual(parseAngle(Math.PI * 3 / 4, true), Math.PI / 2, '3/4xPI centered'); assert.strictEqual(parseAngle(-Math.PI * 3 / 4, true), -Math.PI / 2, '-3/4xPI centered'); }); it('should parse radians angles', () => { const values = { '0' : 0, '1.72' : 1.72, '-2.56' : Math.PI * 2 - 2.56, '3.14rad' : 3.14, '-3.14rad': Math.PI * 2 - 3.14 }; for (const pos in values) { assert.strictEqual(parseAngle(pos).toFixed(16), values[pos].toFixed(16), pos); } }); it('should parse degrees angles', () => { const values = { '0deg' : 0, '30deg' : 30 * Math.PI / 180, '-30deg' : Math.PI * 2 - 30 * Math.PI / 180, '85degs' : 85 * Math.PI / 180, '360degs': 0 }; for (const pos in values) { assert.strictEqual(parseAngle(pos).toFixed(16), values[pos].toFixed(16), pos); } }); it('should normalize angles between 0 and 2Pi', () => { const values = { '450deg' : Math.PI / 2, '1440deg': 0, '8.15' : 8.15 - Math.PI * 2, '-3.14' : Math.PI * 2 - 3.14, '-360deg': 0 }; for (const pos in values) { assert.strictEqual(parseAngle(pos).toFixed(16), values[pos].toFixed(16), pos); } }); it('should normalize angles between -Pi/2 and Pi/2', () => { const values = { '45deg': Math.PI / 4, '-4' : Math.PI / 2 }; for (const pos in values) { assert.strictEqual(parseAngle(pos, true).toFixed(16), values[pos].toFixed(16), pos); } }); it('should normalize angles between -Pi and Pi', function () { const values = { '45deg': Math.PI / 4, '4' : -2 * Math.PI + 4 }; for (const pos in values) { assert.strictEqual(parseAngle(pos, true, false).toFixed(16), values[pos].toFixed(16), pos); } }); it('should throw exception on invalid values', () => { assert.throws(() => { parseAngle('foobar'); }, /Unknown angle "foobar"/, 'foobar'); assert.throws(() => { parseAngle('200gr') }, /Unknown angle unit "gr"/, '200gr'); }); }); describe('utils:psv:parsePosition', () => { it('should parse 2 keywords', () => { const values = { 'top left' : { x: 0, y: 0 }, 'top center' : { x: 0.5, y: 0 }, 'top right' : { x: 1, y: 0 }, 'center left' : { x: 0, y: 0.5 }, 'center center': { x: 0.5, y: 0.5 }, 'center right' : { x: 1, y: 0.5 }, 'bottom left' : { x: 0, y: 1 }, 'bottom center': { x: 0.5, y: 1 }, 'bottom right' : { x: 1, y: 1 } }; for (const pos in values) { assert.deepStrictEqual(parsePosition(pos), values[pos], pos); const rev = pos.split(' ').reverse().join(' '); assert.deepStrictEqual(parsePosition(rev), values[pos], rev); } }); it('should parse 1 keyword', () => { const values = { 'top' : { x: 0.5, y: 0 }, 'center': { x: 0.5, y: 0.5 }, 'bottom': { x: 0.5, y: 1 }, 'left' : { x: 0, y: 0.5 }, 'right' : { x: 1, y: 0.5 }, }; for (const pos in values) { assert.deepStrictEqual(parsePosition(pos), values[pos], pos); } }); it('should parse 2 percentages', () => { const values = { '0% 0%' : { x: 0, y: 0 }, '50% 50%' : { x: 0.5, y: 0.5 }, '100% 100%': { x: 1, y: 1 }, '10% 80%' : { x: 0.1, y: 0.8 }, '80% 10%' : { x: 0.8, y: 0.1 } }; for (const pos in values) { assert.deepStrictEqual(parsePosition(pos), values[pos], pos); } }); it('should parse 1 percentage', () => { const values = { '0%' : { x: 0, y: 0 }, '50%' : { x: 0.5, y: 0.5 }, '100%': { x: 1, y: 1 }, '80%' : { x: 0.8, y: 0.8 } }; for (const pos in values) { assert.deepStrictEqual(parsePosition(pos), values[pos], pos); } }); it('should parse mixed keyword & percentage', () => { const values = { 'top 80%' : { x: 0.8, y: 0 }, '80% bottom': { x: 0.8, y: 1 }, 'left 40%' : { x: 0, y: 0.4 }, '40% right' : { x: 1, y: 0.4 }, 'center 10%': { x: 0.5, y: 0.1 }, '10% center': { x: 0.1, y: 0.5 } }; for (const pos in values) { assert.deepStrictEqual(parsePosition(pos), values[pos], pos); } }); it('should fallback on parse fail', () => { const values = { '' : { x: 0.5, y: 0.5 }, 'crap' : { x: 0.5, y: 0.5 }, 'foo bar': { x: 0.5, y: 0.5 }, 'foo 50%': { x: 0.5, y: 0.5 }, '%' : { x: 0.5, y: 0.5 } }; for (const pos in values) { assert.deepStrictEqual(parsePosition(pos), values[pos], pos); } }); it('should ignore extra tokens', () => { const values = { 'top center bottom' : { x: 0.5, y: 0 }, '50% left 20%' : { x: 0, y: 0.5 }, '0% 0% okay this time it goes ridiculous': { x: 0, y: 0 } }; for (const pos in values) { assert.deepStrictEqual(parsePosition(pos), values[pos], pos); } }); it('should ignore case', () => { const values = { 'TOP CENTER' : { x: 0.5, y: 0 }, 'cenTer LefT': { x: 0, y: 0.5 } }; for (const pos in values) { assert.deepStrictEqual(parsePosition(pos), values[pos], pos); } }); }); describe('utils:psv:parseSpeed', () => { it('should parse all units', () => { const values = { '360dpm' : 360 * Math.PI / 180 / 60, '360degrees per minute' : 360 * Math.PI / 180 / 60, '10dps' : 10 * Math.PI / 180, '10degrees per second' : 10 * Math.PI / 180, '2radians per minute' : 2 / 60, '0.1radians per second' : 0.1, '2rpm' : 2 * 2 * Math.PI / 60, '2revolutions per minute' : 2 * 2 * Math.PI / 60, '0.01rps' : 0.01 * 2 * Math.PI, '0.01revolutions per second': 0.01 * 2 * Math.PI }; for (const speed in values) { assert.strictEqual(parseSpeed(speed).toFixed(16), values[speed].toFixed(16), speed); } }); it('should allow various forms', () => { const values = { '2rpm' : 2 * 2 * Math.PI / 60, '2 rpm' : 2 * 2 * Math.PI / 60, '2revolutions per minute' : 2 * 2 * Math.PI / 60, '2 revolutions per minute' : 2 * 2 * Math.PI / 60, '-2rpm' : -2 * 2 * Math.PI / 60, '-2 rpm' : -2 * 2 * Math.PI / 60, '-2revolutions per minute' : -2 * 2 * Math.PI / 60, '-2 revolutions per minute': -2 * 2 * Math.PI / 60 }; for (const speed in values) { assert.strictEqual(parseSpeed(speed).toFixed(16), values[speed].toFixed(16), speed); } }); it('should throw exception on invalid unit', () => { assert.throws(() => { parseSpeed('10rpsec'); }, /Unknown speed unit "rpsec"/, '10rpsec'); }); it('should passthrough when number', () => { assert.strictEqual(parseSpeed(Math.PI), Math.PI); }); }); describe('utils:psv:getXMPValue', () => { it('should parse XMP data with children', () => { const data = '<rdf:Description rdf:about="" xmlns:GPano="http://ns.google.com/photos/1.0/panorama/">\ <GPano:ProjectionType>equirectangular</GPano:ProjectionType>\ <GPano:UsePanoramaViewer>True</GPano:UsePanoramaViewer>\ <GPano:CroppedAreaImageWidthPixels>5376</GPano:CroppedAreaImageWidthPixels>\ <GPano:CroppedAreaImageHeightPixels>2688</GPano:CroppedAreaImageHeightPixels>\ <GPano:FullPanoWidthPixels>5376</GPano:FullPanoWidthPixels>\ <GPano:FullPanoHeightPixels>2688</GPano:FullPanoHeightPixels>\ <GPano:CroppedAreaLeftPixels>0</GPano:CroppedAreaLeftPixels>\ <GPano:CroppedAreaTopPixels>0</GPano:CroppedAreaTopPixels>\ <GPano:PoseHeadingDegrees>270.0</GPano:PoseHeadingDegrees>\ <GPano:PosePitchDegrees>0</GPano:PosePitchDegrees>\ <GPano:PoseRollDegrees>0.2</GPano:PoseRollDegrees>\ </rdf:Description>'; assert.deepStrictEqual([ getXMPValue(data, 'FullPanoWidthPixels'), getXMPValue(data, 'FullPanoHeightPixels'), getXMPValue(data, 'CroppedAreaImageWidthPixels'), getXMPValue(data, 'CroppedAreaImageHeightPixels'), getXMPValue(data, 'CroppedAreaLeftPixels'), getXMPValue(data, 'CroppedAreaTopPixels'), getXMPValue(data, 'PoseHeadingDegrees'), getXMPValue(data, 'PosePitchDegrees'), getXMPValue(data, 'PoseRollDegrees'), ], [ 5376, 2688, 5376, 2688, 0, 0, 270, 0, 0 ]) }); it('should parse XMP data with attributes', () => { const data = '<rdf:Description rdf:about="" xmlns:GPano="http://ns.google.com/photos/1.0/panorama/"\ GPano:ProjectionType="equirectangular"\ GPano:UsePanoramaViewer="True"\ GPano:CroppedAreaImageWidthPixels="5376"\ GPano:CroppedAreaImageHeightPixels="2688"\ GPano:FullPanoWidthPixels="5376"\ GPano:FullPanoHeightPixels="2688"\ GPano:CroppedAreaLeftPixels="0"\ GPano:CroppedAreaTopPixels="0"\ GPano:PoseHeadingDegrees="270"\ GPano:PosePitchDegrees="0"\ GPano:PoseRollDegrees="0"/>'; assert.deepStrictEqual([ getXMPValue(data, 'FullPanoWidthPixels'), getXMPValue(data, 'FullPanoHeightPixels'), getXMPValue(data, 'CroppedAreaImageWidthPixels'), getXMPValue(data, 'CroppedAreaImageHeightPixels'), getXMPValue(data, 'CroppedAreaLeftPixels'), getXMPValue(data, 'CroppedAreaTopPixels'), getXMPValue(data, 'PoseHeadingDegrees'), getXMPValue(data, 'PosePitchDegrees'), getXMPValue(data, 'PoseRollDegrees'), ], [ 5376, 2688, 5376, 2688, 0, 0, 270, 0, 0 ]) }); }); describe('utils:psv:cleanPosition', () => { it('should clean various formats', () => { assert.deepStrictEqual(cleanPosition('top right'), ['top', 'right']); assert.deepStrictEqual(cleanPosition('right top'), ['top', 'right']); assert.deepStrictEqual(cleanPosition(['top', 'right']), ['top', 'right']); }); it('should add missing center', () => { assert.deepStrictEqual(cleanPosition('top'), ['top', 'center']); assert.deepStrictEqual(cleanPosition('left'), ['center', 'left']); assert.deepStrictEqual(cleanPosition('center'), ['center', 'center']); }); it('should dissallow all center', () => { assert.strictEqual(cleanPosition('center center', { allowCenter: false }), null); assert.strictEqual(cleanPosition('center', { allowCenter: false }), null); }); it('should return null on unparsable values', () => { assert.strictEqual(cleanPosition('foo bar'), null); assert.strictEqual(cleanPosition('TOP CENTER'), null); assert.strictEqual(cleanPosition(''), null); assert.strictEqual(cleanPosition(undefined), null); }); it('should allow XY order', () => { assert.deepStrictEqual(cleanPosition('right top', { cssOrder: false }), ['right', 'top']); assert.deepStrictEqual(cleanPosition(['top', 'right'], { cssOrder: false }), ['top', 'right']); }); it('should always order with center', () => { assert.deepStrictEqual(cleanPosition('center top'), ['top', 'center']); assert.deepStrictEqual(cleanPosition('left center'), ['center', 'left']); }); });