Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
ce2bb45
Fix sequence playback starving on long sequences
Aug 25, 2026
6912be1
Reorientation: honor navigation intent across sequences, reorient on …
Aug 25, 2026
0f3392e
Reorientation: adopt host views, keep carried views on arrows, fix co…
Aug 28, 2026
c8b1e6b
Add tooltips to sequence controls
Sep 2, 2026
a124e02
Reorientation: reject implausible computed tracks
Sep 2, 2026
a9f0f17
Remove viewer canvas focus outline
Sep 3, 2026
8de856a
Show sequence arrows before spatial graph loads
Sep 3, 2026
103c4e7
Keep sequence arrows after spatial graph loads
Sep 3, 2026
c4a2360
Sync reorientation source with shipped behavior
Sep 3, 2026
10e9002
Prepare mapillary-js fb6 package
Sep 3, 2026
356ec9d
Level image horizons from reconstruction
Sep 7, 2026
8fa116d
Handle unmerged panorama navigation fallbacks
Sep 8, 2026
02e8de0
Align fallback panorama arrows with capture heading
Sep 8, 2026
201cef0
Reorient unmerged panoramas from capture metadata
Sep 8, 2026
e01c784
Avoid box artifacts for default panorama meshes
Sep 8, 2026
2e100ca
Fix fallback panorama arrow alignment
Sep 8, 2026
3b8bf88
Avoid transitions across incompatible camera frames
Sep 8, 2026
d2ae749
Cross-fade fallback image transitions
Sep 8, 2026
179d336
Use fast dissolves for incompatible transitions
Sep 8, 2026
16282f5
Match fallback arrow spacing to spatial navigation
Sep 8, 2026
38b7769
Avoid spins from reversed computed tracks
Sep 8, 2026
8f27916
Preserve heading on reorientation fallback
Sep 8, 2026
4e22ea3
Preserve smooth transitions within reconstructions
Sep 8, 2026
77642e1
Freeze source frame during fallback fades
Sep 8, 2026
aa918e0
Validate horizon alignment at travel view
Sep 8, 2026
9aac82a
Prepare mapillary-js fb12 package
Sep 8, 2026
a37236d
Preserve shared view from sequence endpoints
Sep 9, 2026
361283d
Keep horizon leveling for steep capture pitch
Sep 9, 2026
fb99696
Place spherical sequence arrows front and back
Sep 10, 2026
4333a4c
Expose GPS travel bearing for map indicators
Sep 10, 2026
503d9da
Keep sequence arrows oriented while panning
Sep 10, 2026
933d22f
Reset orientation on direct image jumps
Sep 10, 2026
4851865
Preserve current view on sequence navigation
Sep 10, 2026
2d68e1a
Face travel on unmerged panorama loads
Sep 10, 2026
ea652b7
Preserve look direction while playback advances
Sep 10, 2026
3e65da7
Smooth initial orientation without reconstruction
Sep 10, 2026
63f589a
Stabilize navigation across placeholder panoramas
Sep 10, 2026
0c3796f
Bump internal package version to fb13
Sep 10, 2026
1c6f815
Report reorientation bearings in map coordinates
Sep 10, 2026
b176d0f
Allow valid tilted reconstructions
Sep 17, 2026
3530791
Reject inconsistent reconstructed headings
Sep 21, 2026
272326d
Reject sideways reconstructed tracks
Sep 21, 2026
0c8d5b9
Avoid bad-pose flashes on reconstructed fallbacks
Sep 21, 2026
8ad758d
Make reorientation configurable and preserve same-sequence view
Sep 21, 2026
793caef
Preorient rejected poses below turn threshold
Sep 21, 2026
9dbcc73
Reject coupled reconstruction heading and track jumps
Sep 21, 2026
aee23df
Cut transitions across reconstruction calibration jumps
Sep 21, 2026
f205e30
Prepare reorientation before sequence navigation
Sep 21, 2026
033f49c
Reject inconsistent reconstructed leveling poses
Sep 22, 2026
c4ef78e
Keep fallback pitch offsets relative to raw center
Sep 22, 2026
b54a60f
Prepare mapillary-js fb14 package
Sep 22, 2026
1a63ce3
Keep coherent reconstructed tracks for reorientation
Sep 23, 2026
e411457
Keep reorientation off the navigation critical path
Sep 23, 2026
9ba3153
Respect user input during pending reorientation
Sep 23, 2026
d5c1271
Batch reorientation metadata lookahead
Sep 23, 2026
528f278
Prioritize sequence images during playback
Sep 23, 2026
8571209
Honor Graph image query batch limits
Sep 24, 2026
f184055
Prepare mapillary-js fb15 package
Sep 25, 2026
f3162d5
Keep metadata batches within Graph limits
Sep 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "mapillary-js",
"version": "5.0.0-beta.5",
"version": "5.0.0-beta.5-fb15",
"description": "A WebGL interactive street imagery library",
"license": "MIT",
"main": "dist/mapillary.js",
Expand Down
68 changes: 56 additions & 12 deletions src/component/direction/DirectionComponent.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as vd from "virtual-dom";
import {
combineLatest as observableCombineLatest,
concat as observableConcat,
of as observableOf,
Observable,
Subject,
Expand All @@ -13,6 +14,7 @@ import {
share,
startWith,
switchMap,
takeUntil,
tap,
withLatestFrom,
} from "rxjs/operators";
Expand Down Expand Up @@ -151,18 +153,60 @@ export class DirectionComponent extends Component<DirectionConfiguration> {
withLatestFrom(this._configuration$),
switchMap(
([image, configuration]: [Image, DirectionConfiguration]): Observable<[NavigationEdgeStatus, Sequence]> => {
return observableCombineLatest(
image.spatialEdges$,
configuration.distinguishSequence ?
this._navigator.graphService
.cacheSequence$(image.sequenceId).pipe(
catchError(
(error: Error): Observable<Sequence> => {
console.error(`Failed to cache sequence (${image.sequenceId})`, error);

return observableOf<Sequence>(null);
})) :
observableOf<Sequence>(null));
if (!configuration.distinguishSequence) {
return image.spatialEdges$.pipe(
map(
(edgeStatus: NavigationEdgeStatus): [NavigationEdgeStatus, Sequence] => {
return [edgeStatus, null];
}));
}

return this._navigator.graphService
.cacheSequence$(image.sequenceId).pipe(
catchError(
(error: Error): Observable<Sequence> => {
console.error(`Failed to cache sequence (${image.sequenceId})`, error);

return observableOf<Sequence>(null);
}),
switchMap(
(sequence: Sequence): Observable<[NavigationEdgeStatus, Sequence]> => {
if (sequence == null) {
return image.spatialEdges$.pipe(
map(
(edgeStatus: NavigationEdgeStatus):
[NavigationEdgeStatus, Sequence] => {
return [edgeStatus, null];
}));
}

const spatialEdges$ = image.spatialEdges$.pipe(
filter(
(edgeStatus: NavigationEdgeStatus): boolean => {
return edgeStatus.cached;
}));
const sequenceEdges$ = this._navigator.graphService
.cacheSequenceSpatialEdges$(image.id).pipe(
takeUntil(spatialEdges$),
catchError(
(error: Error): Observable<NavigationEdgeStatus> => {
console.error(
`Failed to cache sequence spatial edges (${image.id})`,
error);

return observableOf<NavigationEdgeStatus>({
cached: false,
edges: [],
});
}));

return observableConcat(sequenceEdges$, spatialEdges$).pipe(
map(
(edgeStatus: NavigationEdgeStatus):
[NavigationEdgeStatus, Sequence] => {
return [edgeStatus, sequence];
}));
}));
}))
.subscribe(
([edgeStatus, sequence]: [NavigationEdgeStatus, Sequence]): void => {
Expand Down
52 changes: 39 additions & 13 deletions src/component/direction/DirectionDOMRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,13 @@ export class DirectionDOMRenderer {
private _distinguishSequence: boolean;

private _needsRender: boolean;
private _edgesCached: boolean;

private _stepEdges: NavigationEdge[];
private _turnEdges: NavigationEdge[];
private _sphericalEdges: NavigationEdge[];
private _sequenceEdgeKeys: string[];
private _sequenceAzimuthOffset: number;

private _stepDirections: NavigationDirection[];
private _turnDirections: NavigationDirection[];
Expand All @@ -57,11 +59,13 @@ export class DirectionDOMRenderer {
this._distinguishSequence = false;

this._needsRender = false;
this._edgesCached = false;

this._stepEdges = [];
this._turnEdges = [];
this._sphericalEdges = [];
this._sequenceEdgeKeys = [];
this._sequenceAzimuthOffset = 0;

this._stepDirections = [
NavigationDirection.StepForward,
Expand Down Expand Up @@ -133,6 +137,11 @@ export class DirectionDOMRenderer {
*/
public setImage(image: Image): void {
this._image = image;
this._sequenceAzimuthOffset = image != null &&
isSpherical(image.cameraType) &&
!Number.isFinite(image.computedCompassAngle) &&
Number.isFinite(image.compassAngle) ?
this._spatial.degToRad(90 - image.compassAngle) : 0;
this._clearEdges();

this._setNeedsRender();
Expand Down Expand Up @@ -200,13 +209,15 @@ export class DirectionDOMRenderer {
}

private _clearEdges(): void {
this._edgesCached = false;
this._stepEdges = [];
this._turnEdges = [];
this._sphericalEdges = [];
this._sequenceEdgeKeys = [];
}

private _setEdges(edgeStatus: NavigationEdgeStatus, sequence: Sequence): void {
this._edgesCached = edgeStatus.cached;
this._stepEdges = [];
this._turnEdges = [];
this._sphericalEdges = [];
Expand Down Expand Up @@ -237,13 +248,12 @@ export class DirectionDOMRenderer {

for (let edge of edges) {
let edgeKey: string = edge.target;

for (let sequenceKey of sequence.imageIds) {
if (sequenceKey === edgeKey) {
this._sequenceEdgeKeys.push(edgeKey);
break;
}
const edgeIndex: number = sequence.imageIds.indexOf(edgeKey);
if (edgeIndex < 0) {
continue;
}

this._sequenceEdgeKeys.push(edgeKey);
}
}
}
Expand All @@ -256,25 +266,34 @@ export class DirectionDOMRenderer {
this._createVNodeByKey(
navigator,
sphericalEdge.target,
sphericalEdge.data.worldMotionAzimuth,
this._getSphericalEdgeAzimuth(sphericalEdge),
rotation,
this._calculator.outerRadius,
"mapillary-direction-arrow-spherical"));
"mapillary-direction-arrow-spherical",
sphericalEdge.data.direction));
}

for (let stepEdge of this._stepEdges) {
arrows.push(
this._createSphericalToPerspectiveArrow(
navigator,
stepEdge.target,
stepEdge.data.worldMotionAzimuth,
this._getSphericalEdgeAzimuth(stepEdge),
rotation,
stepEdge.data.direction));
}

return arrows;
}

private _getSphericalEdgeAzimuth(edge: NavigationEdge): number {
const sequenceEdge = this._sequenceEdgeKeys.indexOf(edge.target) > -1;
// Unmerged equirectangular captures are world-aligned at their east
// axis even when their compass field describes vehicle travel.
return edge.data.worldMotionAzimuth +
(sequenceEdge ? this._sequenceAzimuthOffset : 0);
}

private _createSphericalToPerspectiveArrow(
navigator: Navigator,
key: string,
Expand Down Expand Up @@ -307,7 +326,8 @@ export class DirectionDOMRenderer {
azimuth,
rotation,
this._calculator.outerRadius,
"mapillary-direction-arrow-step");
"mapillary-direction-arrow-step",
direction);
}

return this._createVNodeInactive(key, azimuth, rotation);
Expand All @@ -325,6 +345,7 @@ export class DirectionDOMRenderer {
rotation,
this._calculator.innerRadius,
"mapillary-direction-arrow-spherical",
sphericalEdge.data.direction,
true));
}

Expand Down Expand Up @@ -372,11 +393,12 @@ export class DirectionDOMRenderer {
rotation: EulerRotation,
offset: number,
className: string,
direction: NavigationDirection,
shiftVertically?: boolean): vd.VNode {

let onClick: (e: Event) => void =
(e: Event): void => {
navigator.moveTo$(key)
navigator.moveTo$(key, direction)
.subscribe(
undefined,
(error: Error): void => {
Expand Down Expand Up @@ -406,7 +428,9 @@ export class DirectionDOMRenderer {

let onClick: (e: Event) => void =
(e: Event): void => {
navigator.moveDir$(direction)
(this._edgesCached ?
navigator.moveDir$(direction) :
navigator.moveTo$(key, direction))
.subscribe(
undefined,
(error: Error): void => {
Expand Down Expand Up @@ -434,7 +458,9 @@ export class DirectionDOMRenderer {

let onClick: (e: Event) => void =
(e: Event): void => {
navigator.moveDir$(direction)
(this._edgesCached ?
navigator.moveDir$(direction) :
navigator.moveTo$(key, direction))
.subscribe(
undefined,
(error: Error): void => {
Expand Down
49 changes: 46 additions & 3 deletions src/component/image/ImageGLRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Subscription } from "rxjs";
import { IAnimationState } from "../../state/interfaces/IAnimationState";
import { AnimationFrame } from "../../state/interfaces/AnimationFrame";
import { Image } from "../../graph/Image";
import { Camera } from "../../geo/Camera";
import { Transform } from "../../geo/Transform";
import { TextureProvider } from "../../tile/TextureProvider";
import { MeshFactory } from "../util/MeshFactory";
Expand All @@ -27,6 +28,10 @@ export class ImageGLRenderer {
private _frameId: number;
private _needsRender: boolean;

private _motionless: boolean;
private _previousCamera: Camera;
private _previousPerspective: THREE.PerspectiveCamera;

constructor() {
this._factory = new MeshFactory();
this._scene = new MeshScene();
Expand All @@ -41,6 +46,10 @@ export class ImageGLRenderer {

this._frameId = 0;
this._needsRender = false;

this._motionless = false;
this._previousCamera = null;
this._previousPerspective = null;
}

public get frameId(): number {
Expand Down Expand Up @@ -177,7 +186,9 @@ export class ImageGLRenderer {

renderer.render(this._scene.scenePeriphery, perspectiveCamera);
renderer.render(this._scene.scene, perspectiveCamera);
renderer.render(this._scene.sceneOld, perspectiveCamera);
renderer.render(
this._scene.sceneOld,
this._getPreviousPerspective(perspectiveCamera));

for (const plane of planes) {
(<ProjectorShaderMaterial>plane.mesh.material).uniforms.opacity.value = this._alpha;
Expand All @@ -197,9 +208,33 @@ export class ImageGLRenderer {
disposeProvider();
}

this._motionless = false;
this._previousCamera = null;
this._previousPerspective = null;
this._needsRender = true;
}

private _getPreviousPerspective(
perspectiveCamera: THREE.PerspectiveCamera)
: THREE.PerspectiveCamera {
if (!this._motionless || this._previousCamera == null) {
return perspectiveCamera;
}
if (this._previousPerspective == null) {
this._previousPerspective = perspectiveCamera.clone();
this._previousPerspective.position.copy(
this._previousCamera.position);
this._previousPerspective.up.copy(this._previousCamera.up);
this._previousPerspective.matrixAutoUpdate = true;
this._previousPerspective.lookAt(this._previousCamera.lookat);
this._previousPerspective.matrixAutoUpdate = false;
this._previousPerspective.updateMatrix();
this._previousPerspective.updateMatrixWorld(false);
}

return this._previousPerspective;
}

private _setShader(shader: GLShader, planes: MeshSceneItem[]): void {
for (const plane of planes) {
const material = <ProjectorShaderMaterial>plane.mesh.material;
Expand Down Expand Up @@ -257,12 +292,16 @@ export class ImageGLRenderer {
}

if (previousKey != null) {
if (previousKey !== this._currentKey && previousKey !== this._previousKey) {
const freezePrevious = state.motionless && state.alpha < 1;
if (freezePrevious ||
(previousKey !== this._currentKey &&
previousKey !== this._previousKey)) {
const previousMesh =
this._factory.createMesh(
state.previousImage,
state.previousTransform,
shader);
shader,
!freezePrevious);

const previousPlane: MeshSceneItem = {
mesh: previousMesh,
Expand All @@ -275,6 +314,10 @@ export class ImageGLRenderer {
this._previousKey = previousKey;
}

this._motionless = state.motionless && state.alpha < 1;
this._previousCamera = this._motionless ?
state.previousCamera.clone() : null;
this._previousPerspective = null;
this._currentKey = currentKey;
const currentMesh =
this._factory.createMesh(
Expand Down
Loading
Loading