Initial version of RouteComponent

This commit is contained in:
Johan Gyllenspetz 2016-02-09 17:36:44 -08:00
parent b96f613a57
commit b27de56cce
15 changed files with 348 additions and 46 deletions

View File

@ -21,14 +21,99 @@
var viewer = new Mapillary.Viewer(
"viewer",
"MkJKbDA0bnZuZlcxeTJHTmFqN3g1dzo5NWEzOTg3OWUxZDI3MjM4",
"zarcRdNFZwg3FkXNcsFeGw",
{ "debug": true, "cover": false }
"2yFbES2-ogx4Cz8IOsaThQ",
{ "debug": false, "cover": true, "direction": false }
);
viewer.activateComponent("player");
var player = viewer.getComponent("player");
var route = viewer.getComponent("route");
route.configure({paths: [
{sequenceKey: "euUElwr9jYeUW1R1OHj04g", startKey: "2yFbES2-ogx4Cz8IOsaThQ", stopKey: "0cPOfPT2ZgqNIiU063CI4Q",
infoKeys: [
{key: "CSZGtPDQZqQxqb96lsTzpw", description: "This is mine and Millenbops Califronia roadtrip."},
{key: "DdTvQAKHEVhAbNlW3bCpvw", description: "It all began in West Hollywood."}
]
},
{sequenceKey: "euUElwr9jYeUW1R1OHj04g", startKey: "ERs17ne1Ln1FCWmrRAtWuw", stopKey: "rwfHD5iywHIDmzjsJvf8pw",
infoKeys: [
{key: "ERs17ne1Ln1FCWmrRAtWuw", description: "Every epic road trip should start at Sunset Blvd."},
]
},
{sequenceKey: "euUElwr9jYeUW1R1OHj04g", startKey: "EQCY7shEimCCW27J1cIl6g", stopKey: "Z5rHmOAWCtJFz6OJzFt20A",
infoKeys: [
{key: "EQCY7shEimCCW27J1cIl6g", description: "Sunset Blvd is one of the busiest streets in Los Angeles."},
]
},
{sequenceKey: "TJzxIvqxxxfJ4POUxNjAEA", startKey: "ajColPp_so8p0G6NXiMc4Q", stopKey: "x4BbhDOZhKJ1t96vDU2n-w",
infoKeys: [
{key: "ajColPp_so8p0G6NXiMc4Q", description: "After heading east for a while we ended up in the desert."}
]
},
{sequenceKey: "sK2j7wI6ZEkj2vsDy4jpDQ", startKey: "qKTcv58vTApeVwwZfsZL9w", stopKey: "f9viY8M7JYMxL0KVCaIN_g",
infoKeys: [
{key: "qKTcv58vTApeVwwZfsZL9w", description: "Our lodging the first two nights was deep into Yucca Valley."},
{key: "patDqTIis_qL2i72MFZaog", description: "Where Joshua Trees grows everywhere."}
]
},
{sequenceKey: "j8ROntKX9iv-9gb1HBuSHQ", startKey: "yZU9lUaTYExFrAtIUlo_uw", stopKey: "VEVbQEAZrtvz7JEinkFp2A",
infoKeys: [
{key: "yZU9lUaTYExFrAtIUlo_uw", description: "Our closets neighbours house was completly burnt to the ground."}
]
},
{sequenceKey: "sK2j7wI6ZEkj2vsDy4jpDQ", startKey: "vJtyfbUtct7rXbkSKnfMtQ", stopKey: "8XMDhO9w54oosT3ex7pVig",
infoKeys: [
{key: "vJtyfbUtct7rXbkSKnfMtQ", description: "The main yard was behind this fence."}
]
},
{sequenceKey: "TJzxIvqxxxfJ4POUxNjAEA", startKey: "joM8RlVPy356_sazcblQMA", stopKey: "ZZDciNIDLfB57I3s2cJF_w",
infoKeys: [
{key: "joM8RlVPy356_sazcblQMA", description: "The first day we headed straight out in the desert."},
{key: "b2GTrci-JeJvzPheAcL-1g", description: "With our destination set for Giant Rock."},
]
},
{sequenceKey: "DgoZjqjsHGH_zz5966dDBg", startKey: "LBPEtJMmbrGh0oLnQ9E8lg", stopKey: "wgXSKLtIR0GoFiVxe-INoQ",
infoKeys: [
{key: "LBPEtJMmbrGh0oLnQ9E8lg", description: "This is the Giant Rock. Some people belive it was placed there by aliens"},
{key: "Np7XYqKsXhxi3OmxEKBHSQ", description: "However the rock ended up there its an impressive beast!"}
]
},
{sequenceKey: "1rjqpVbg2kgTrKpkneyGIQ", startKey: "2uIn4edT9tlkzzxqD78Y1Q", stopKey: "HB4XNwjCmpR1k_VPPfhn1Q",
infoKeys: [
{key: "2uIn4edT9tlkzzxqD78Y1Q", description: "Next stop, the peaceful integraton."}
]
},
{sequenceKey: "VQZ6FJbwb7m-kak85Lk15g", startKey: "JAEaa5H6MdcgJGC8rWPEFQ", stopKey: "I3b1TBdOaP1lIeQOQ7SSSg",
infoKeys: [
{key: "JAEaa5H6MdcgJGC8rWPEFQ", description: "The integraton is close by the Giant Rock."},
{key: "l8oTDuLuVD_Q_mHd9_WKsQ", description: "Here you can get a soundbath to heal your soul."},
{key: "27FI9bQrMTwFk9FvG9xtnQ", description: "The integraton itself is the white building."},
{key: "79TCHVkj3qpOKBePzC6R8g", description: "A perfect sphere built in wood."}
]
},
{sequenceKey: "hV0PWG7L377V8pHG3CE-3w", startKey: "xOwoXDadn-G51JNJn67TRQ", stopKey: "13DeXe5fWJELCiXUOUqdrQ",
infoKeys: [
{key: "xOwoXDadn-G51JNJn67TRQ", description: "Our evening was spent on the veranda."}
]
},
{sequenceKey: "gD_UgTtm3Txuy907EePIYg", startKey: "af0_LV-Jwe981WM35yqIjQ", stopKey: "00elTbzW2vSoMo2JB-jHzw",
infoKeys: [
{key: "af0_LV-Jwe981WM35yqIjQ", description: "Next day we visited Pioneer Town."},
{key: "DTeBlU5HCqUXdM_LFChFwQ", description: "A western movie set."}
]
},
{sequenceKey: "FqH1Elh2497hGqntmHHy5A", startKey: "clvsHTDMqGAFz4CCE8ndDg", stopKey: "ZimS6e9sNXvT30y9b1sZeQ",
infoKeys: [
{key: "clvsHTDMqGAFz4CCE8ndDg", description: "Next up was King of the Hammer."},
{key: "Qqhp4_I2lPmH0TvUJnh86w", description: "This was my journey."},
{key: "B7_vLuIFsLIXLeBCRxja6Q", description: "Now you build yours."},
{key: "ZimS6e9sNXvT30y9b1sZeQ", description: "With mapillary-js."}
]
}
]});
// viewer.activateComponent("route");
function play() {
viewer.deactivateComponent("cache");
player.play();

15
src/api/APIS.ts Normal file
View File

@ -0,0 +1,15 @@
import * as when from "when";
import {APIv2Call, IAPISGet} from "../API";
export class APIS extends APIv2Call {
public callS(path: string): when.Promise<any> {
return this.callApi("s/" + path);
}
public get(sequenceKey: string): when.Promise<IAPISGet> {
return this.callS(sequenceKey);
}
}
export default APIS

View File

@ -1,10 +1,12 @@
import APIIm from "./APIIm";
import APINav from "./APINav";
import APISearch from "./APISearch";
import APIS from "./APIS";
export class APIv2 {
public im: APIIm;
public nav: APINav;
public s: APIS;
public search: APISearch;
private clientId: string;
@ -21,6 +23,7 @@ export class APIv2 {
this.im = new APIIm(clientId);
this.nav = new APINav(clientId);
this.s = new APIS(clientId);
this.search = new APISearch(clientId);
};
}

View File

@ -0,0 +1,6 @@
export interface IAPISGet {
key: string;
keys: string[];
}
export default IAPISGet;

View File

@ -2,6 +2,7 @@ export {IAPIImOr} from "./IAPIImOr";
export {IAPINavIm} from "./IAPINavIm";
export {IAPINavImIm} from "./IAPINavImIm";
export {IAPINavImS} from "./IAPINavImS";
export {IAPISGet} from "./IAPISGet";
export {IAPISearchImClose2} from "./IAPISearchImClose2";
export {IGPano} from "./IGPano";

View File

@ -1,31 +1,26 @@
/// <reference path="../../typings/virtual-dom/virtual-dom.d.ts" />
/// <reference path="../../node_modules/rx/ts/rx.all.d.ts" />
import * as rx from "rx";
import * as vd from "virtual-dom";
import {Container, Navigator} from "../Viewer";
import {Node} from "../Graph";
import {ComponentService, Component} from "../Component";
import {IVNodeHash} from "../Render";
export class BackgroundComponent extends Component {
public static componentName: string = "background";
private _disposable: rx.IDisposable;
constructor(name: string, container: Container, navigator: Navigator) {
super(name, container, navigator);
}
protected _activate(): void {
this._disposable = this._navigator.stateService.currentNode$.map((node: Node): IVNodeHash => {
return {name: this._name, vnode: this.getBackgroundNode("The viewer can't display the given photo.")};
}).subscribe(this._container.domRenderer.render$);
this._container.domRenderer.render$
.onNext({name: this._name, vnode: this.getBackgroundNode("The viewer can't display the given photo.")});
}
protected _deactivate(): void {
this._disposable.dispose();
return;
}
private getBackgroundNode(notice: string): vd.VNode {

View File

@ -21,8 +21,6 @@ export class PlayerComponent extends Component {
public static componentName: string = "player";
private _configurationOperation$: rx.Subject<IConfigurationOperation> = new rx.Subject<IConfigurationOperation>();
private _stop$: rx.Subject<void> = new rx.Subject<void>();
private _configurationSubscription: rx.IDisposable;
private _playingSubscription: rx.IDisposable;
@ -65,21 +63,6 @@ export class PlayerComponent extends Component {
configuration.playing = newConfiguration.playing;
return configuration;
};
})
.subscribe(this._configurationOperation$);
this._stop$
.map<IConfigurationOperation>(
() => {
return (configuration: IPlayerConfiguration): IPlayerConfiguration => {
if (configuration.playing) {
this._stop();
}
configuration.playing = false;
return configuration;
};
})
@ -148,7 +131,7 @@ export class PlayerComponent extends Component {
this._navigator.stateService.appendNodes([node]);
},
(error: Error): void => {
this._stop$.onNext(null);
this.configure({ playing: false });
}
);
}

View File

@ -1,36 +1,203 @@
/// <reference path="../../typings/virtual-dom/virtual-dom.d.ts" />
/// <reference path="../../node_modules/rx/ts/rx.all.d.ts" />
import * as _ from "underscore";
import * as rx from "rx";
import * as vd from "virtual-dom";
import {IAPISGet} from "../API";
import {Container, Navigator} from "../Viewer";
import {Node} from "../Graph";
import {ComponentService, Component} from "../Component";
import {IRouteConfiguration, IRoutePath, ComponentService, Component} from "../Component";
import {IVNodeHash} from "../Render";
import {IFrame} from "../State";
// return {name: this._name, vnode: this.getRouteAnnotationNode("test")};
interface IRtAndFrame {
routeTrack: RouteTrack;
frame: IFrame;
}
interface INodeInstruction {
key: string;
description: string;
}
interface IInstructionPlace {
place: number;
nodeInstructions: INodeInstruction[];
}
class DescriptionState {
public description: string;
public showsLeft: number;
}
class RouteState {
public routeTrack: RouteTrack;
public currentNode: Node;
public lastNode: Node;
}
class RouteTrack {
public nodeInstructions: INodeInstruction[] = [];
public nodeInstructionsOrdered: INodeInstruction[][] = [];
}
export class RouteComponent extends Component {
public static componentName: string = "route";
private _disposable: rx.IDisposable;
private _disposableDescription: rx.IDisposable;
constructor(name: string, container: Container, navigator: Navigator) {
super(name, container, navigator);
}
protected _activate(): void {
this._disposable = this._navigator.stateService.currentNode$.map((node: Node): IVNodeHash => {
return {name: this._name, vnode: this.getRouteAnnotationNode("test")};
}).subscribe(this._container.domRenderer.render$);
let _slowedStream$: rx.Observable<IFrame>;
_slowedStream$ = this._navigator.stateService.currentState$.filter((frame: IFrame) => {
return (frame.id % 20) === 0;
}).filter((frame: IFrame) => {
return frame.state.nodesAhead < 15;
}).distinctUntilChanged((frame: IFrame): string => {
return frame.state.lastNode.key;
});
let _routeTrack$: rx.Observable<RouteTrack>;
_routeTrack$ = this.configuration$.selectMany((conf: IRouteConfiguration): rx.Observable<IRoutePath> => {
return rx.Observable.from(conf.paths);
}).distinct((path: IRoutePath): string => {
return path.sequenceKey;
}).flatMap<IAPISGet>((path: IRoutePath): rx.Observable<IAPISGet> => {
return rx.Observable.fromPromise(this._navigator.apiV2.s.get(path.sequenceKey));
}).combineLatest(this.configuration$, (apiSGet: IAPISGet, conf: IRouteConfiguration): IInstructionPlace[] => {
let i: number = 0;
let instructionPlaces: IInstructionPlace[] = [];
for (let path of conf.paths) {
if (path.sequenceKey === apiSGet.key) {
let nodeInstructions: INodeInstruction[] = [];
let saveKey: boolean = false;
for (let key of apiSGet.keys) {
if (path.startKey === key) {
saveKey = true;
}
if (saveKey) {
let description: string = null;
for (let infoKey of path.infoKeys) {
if (infoKey.key === key) {
description = infoKey.description;
}
}
nodeInstructions.push({description: description, key: key});
}
if (path.stopKey === key) {
saveKey = false;
}
}
instructionPlaces.push({nodeInstructions: nodeInstructions, place: i});
}
i++;
}
return instructionPlaces;
}).scan<RouteTrack>(
(routeTrack: RouteTrack, instructionPlaces: IInstructionPlace[]): RouteTrack => {
for (let instructionPlace of instructionPlaces) {
routeTrack.nodeInstructionsOrdered[instructionPlace.place] = instructionPlace.nodeInstructions;
}
routeTrack.nodeInstructions = _.flatten(routeTrack.nodeInstructionsOrdered);
return routeTrack;
},
new RouteTrack());
this._disposable = _slowedStream$
.combineLatest(_routeTrack$, (frame: IFrame, routeTrack: RouteTrack): IRtAndFrame => {
return {frame: frame, routeTrack: routeTrack};
}).scan<RouteState>(
(routeState: RouteState, rtAndFrame: IRtAndFrame): RouteState => {
routeState.routeTrack = rtAndFrame.routeTrack;
routeState.currentNode = rtAndFrame.frame.state.currentNode;
routeState.lastNode = rtAndFrame.frame.state.lastNode;
return routeState;
},
new RouteState())
.filter((routeState: RouteState): boolean => {
for (let nodeInstruction of routeState.routeTrack.nodeInstructions) {
if (!nodeInstruction) {
continue;
}
if (nodeInstruction.key === routeState.lastNode.key) {
return true;
}
}
return false;
}).distinctUntilChanged((routeState: RouteState): string => {
return routeState.lastNode.key;
}).selectMany<Node>((routeState: RouteState): rx.Observable<Node> => {
let i: number = 0;
for (let nodeInstruction of routeState.routeTrack.nodeInstructions) {
if (nodeInstruction.key === routeState.lastNode.key) {
break;
}
i++;
}
let nextInstruction: INodeInstruction = routeState.routeTrack.nodeInstructions[i + 1];
if (!nextInstruction) {
return rx.Observable.just<Node>(null);
}
return this._navigator.graphService.node$(nextInstruction.key);
}).filter((node: Node) => {
return node !== null;
}).subscribe(this._navigator.stateService.appendNode$);
this._disposableDescription = this._navigator.stateService.currentNode$
.combineLatest(_routeTrack$, (node: Node, routeTrack: RouteTrack): string => {
let description: string = null;
for (let nodeInstruction of routeTrack.nodeInstructions) {
if (nodeInstruction.key === node.key) {
description = nodeInstruction.description;
break;
}
}
return description;
}).scan<DescriptionState>(
(descriptionState: DescriptionState, description: string): DescriptionState => {
if (description !== descriptionState.description && description !== null) {
descriptionState.description = description;
descriptionState.showsLeft = 6;
} else {
descriptionState.showsLeft--;
}
return descriptionState;
},
new DescriptionState()
).map((descriptionState: DescriptionState): IVNodeHash => {
if (descriptionState.showsLeft > 0 && descriptionState.description) {
return {name: this._name, vnode: this.getRouteAnnotationNode(descriptionState.description)};
} else {
return {name: this._name, vnode: vd.h("div", [])};
}
}).subscribe(this._container.domRenderer.render$);
}
protected _deactivate(): void {
this._disposable.dispose();
this._disposableDescription.dispose();
}
private getRouteAnnotationNode(text: string): vd.VNode {
private getRouteAnnotationNode(description: string): vd.VNode {
return vd.h("div.RouteFrame", {}, [
vd.h("p", {textContent: text}, [])
vd.h("p", {textContent: description}, [])
]);
}
}

View File

@ -0,0 +1,19 @@
import {IComponentConfiguration} from "../../Component";
export interface IRouteInfoKey {
key: string;
description: string;
}
export interface IRoutePath {
sequenceKey: string;
startKey: string;
stopKey: string;
infoKeys: IRouteInfoKey[];
}
export interface IRouteConfiguration extends IComponentConfiguration {
paths: IRoutePath[];
}
export default IRouteConfiguration;

View File

@ -1,4 +1,5 @@
export {ICoverConfiguration} from "./ICoverConfiguration"
export {IPlayerConfiguration} from "./IPlayerConfiguration"
export {IRouteConfiguration, IRoutePath, IRouteInfoKey} from "./IRouteConfiguration"
export {IComponentConfiguration} from "./IComponentConfiguration"
export {IShader} from "./IShader";

View File

@ -453,6 +453,15 @@ export class Graph {
};
}
// fixme this will fix referance on long jumps, but will keep bad cache
if (Math.abs((latLon.lon - this.referenceLatLonAlt.lon)) > 0.1) {
this.referenceLatLonAlt = {
alt: alt,
lat: latLon.lat,
lon: latLon.lon,
};
}
let C: number[] = this.geoCoords.topocentric_from_lla(
latLon.lat,
latLon.lon,

View File

@ -12,6 +12,7 @@ import {
} from "../State";
export class StateService {
private _appendNode$: rx.Subject<Node> = new rx.Subject<Node>();
private _currentState$: rx.Subject<IFrame>;
private _currentNode$: rx.Observable<Node>;
@ -33,6 +34,11 @@ export class StateService {
this._frameGenerator = new FrameGenerator();
this._frameId = null;
// fixme we should probably implement all functions in a more reactive way
this._appendNode$.subscribe((node: Node) => {
this.appendNodes([node]);
});
}
public get currentState$(): rx.Observable<IFrame> {
@ -80,6 +86,10 @@ export class StateService {
this._context.rotate(delta);
}
public get appendNode$(): rx.Subject<Node> {
return this._appendNode$;
}
private frame(time: number): void {
this._frameId = this._frameGenerator.requestAnimationFrame(this.frame.bind(this));

View File

@ -54,19 +54,19 @@ export class ComponentController {
private initializeComponents(): void {
let options: IViewerOptions = this._options;
this.uFalse(options.background, "background");
this.uFalse(options.debug, "debug");
this.uFalse(options.player, "player");
this.uFalse(options.navigation, "navigation");
this.uFalse(options.image, "image");
this.uFalse(options.navigation, "navigation");
this.uFalse(options.player, "player");
this.uFalse(options.route, "route");
this.uTrue(options.attribution, "attribution");
this.uTrue(options.background, "background");
this.uTrue(options.cache, "cache");
this.uTrue(options.direction, "direction");
this.uTrue(options.imageplane, "imageplane");
this.uTrue(options.keyboard, "keyboard");
this.uTrue(options.loading, "loading");
this.uTrue(options.mouse, "mouse");
this.uTrue(options.imageplane, "imageplane");
this._coverComponent = <CoverComponent> this._componentService.getCover();

View File

@ -7,6 +7,12 @@ export interface IViewerOptions {
*/
attribution?: boolean;
/**
* Display a background if no key is set.
* @default true
*/
background?: boolean;
/**
* Cache images ahead.
* @default true
@ -73,6 +79,12 @@ export interface IViewerOptions {
*/
player?: boolean;
/**
* Create a route with a story inside mapillary js
* @default false
*/
route?: boolean;
/**
* Default size of the thumbnail used in the viewer
* @default {ImageSize}
@ -85,10 +97,6 @@ export interface IViewerOptions {
* @default {ImageSize}
*/
maxImageSize?: ImageSize;
background?: boolean;
route?: boolean;
}
export default IViewerOptions;

View File

@ -2,7 +2,7 @@
background-color: rgba(0, 0, 0, 0.5);
outline: 1px solid rgba(255, 255, 255, 0.5);
width: 80%;
height: 30px;
height: 40px;
text-align: center;
color: white;
margin: 0 auto;
@ -14,8 +14,8 @@
.RouteFrame p {
margin: 0;
padding: 5px;
padding: 10px;
font-family: Helvetica, Arial, sans-serif;
font-size: 12px;
font-size: 14px;
text-shadow: 0 0 5px rgba(0, 0, 0, 0.75);
}