UNPKG

mapillary-js

Version:

WebGL JavaScript library for displaying street level imagery from mapillary.com

1,255 lines (928 loc) 144 kB
import {from as observableFrom, of as observableOf, merge as observableMerge, Observable, Subject} from "rxjs"; import {first, mergeAll} from "rxjs/operators"; import {NodeHelper} from "../helper/NodeHelper.spec"; import { APIv3, ICoreNode, IFillNode, IFullNode, ISequence, } from "../../src/API"; import {EdgeCalculator} from "../../src/Edge"; import {GraphMapillaryError} from "../../src/Error"; import {GeoRBush} from "../../src/Geo"; import { GraphCalculator, Graph, IGraphConfiguration, Node, Sequence, } from "../../src/Graph"; describe("Graph.ctor", () => { it("should create a graph", () => { let apiV3: APIv3 = new APIv3("clientId"); let graph: Graph = new Graph(apiV3); expect(graph).toBeDefined(); }); it("should create a graph with all ctor params", () => { let apiV3: APIv3 = new APIv3("clientId"); let index: GeoRBush<any> = new GeoRBush(16); let calculator: GraphCalculator = new GraphCalculator(null); let graph: Graph = new Graph(apiV3, index, calculator); expect(graph).toBeDefined(); }); }); describe("Graph.cacheBoundingBox$", () => { let helper: NodeHelper; beforeEach(() => { helper = new NodeHelper(); }); it("should cache one node in the bounding box", (done: Function) => { let apiV3: APIv3 = new APIv3("clientId"); let index: GeoRBush<any> = new GeoRBush(16); let calculator: GraphCalculator = new GraphCalculator(null); let h: string = "h"; spyOn(calculator, "encodeHsFromBoundingBox").and.returnValue([h]); let imagesByH: Subject<{ [key: string]: { [index: string]: ICoreNode } }> = new Subject<{ [key: string]: { [index: string]: ICoreNode } }>(); spyOn(apiV3, "imagesByH$").and.returnValue(imagesByH); let key: string = "key"; let imageByKeyFill: Subject<{ [key: string]: IFillNode }> = new Subject<{ [key: string]: IFillNode }>(); spyOn(apiV3, "imageByKeyFill$").and.returnValue(imageByKeyFill); let fullNode: IFullNode = helper.createFullNode(); fullNode.key = key; fullNode.l.lat = 0.5; fullNode.l.lon = 0.5; let graph: Graph = new Graph(apiV3, index, calculator); graph.cacheBoundingBox$({ lat: 0, lon: 0 }, { lat: 1, lon: 1 }) .subscribe( (nodes: Node[]): void => { expect(nodes.length).toBe(1); expect(nodes[0].key).toBe(fullNode.key); expect(nodes[0].full).toBe(true); expect(graph.hasNode(key)).toBe(true); done(); }); let tileResult: { [key: string]: { [index: string]: ICoreNode } } = {}; tileResult[h] = {}; tileResult[h]["0"] = fullNode; imagesByH.next(tileResult); imagesByH.complete(); let fillResult: { [key: string]: IFillNode } = {}; fillResult[key] = fullNode; imageByKeyFill.next(fillResult); imageByKeyFill.complete(); }); it("should not cache tile of fill node if already cached", (done: Function) => { let apiV3: APIv3 = new APIv3("clientId"); let index: GeoRBush<any> = new GeoRBush(16); let calculator: GraphCalculator = new GraphCalculator(null); let h: string = "h"; spyOn(calculator, "encodeHsFromBoundingBox").and.returnValue([h]); spyOn(calculator, "encodeHs").and.returnValue([h]); spyOn(calculator, "encodeH").and.returnValue(h); let imagesByH: Subject<{ [key: string]: { [index: string]: ICoreNode } }> = new Subject<{ [key: string]: { [index: string]: ICoreNode } }>(); const imagesByHSpy: jasmine.Spy = spyOn(apiV3, "imagesByH$"); imagesByHSpy.and.returnValue(imagesByH); let key: string = "key"; let imageByKeyFill: Subject<{ [key: string]: IFillNode }> = new Subject<{ [key: string]: IFillNode }>(); const imageByKeyFillSpy: jasmine.Spy = spyOn(apiV3, "imageByKeyFill$"); imageByKeyFillSpy.and.returnValue(imageByKeyFill); let imageByKeyFull: Subject<{ [key: string]: IFillNode }> = new Subject<{ [key: string]: IFillNode }>(); const imageByKeyFullSpy: jasmine.Spy = spyOn(apiV3, "imageByKeyFull$"); imageByKeyFullSpy.and.returnValue(imageByKeyFull); let fullNode: IFullNode = helper.createFullNode(); fullNode.key = key; fullNode.l.lat = 0.5; fullNode.l.lon = 0.5; let graph: Graph = new Graph(apiV3, index, calculator); graph.cacheFull$(fullNode.key).subscribe(() => { /*noop*/ }); let fullResult: { [key: string]: IFullNode } = {}; fullResult[fullNode.key] = fullNode; imageByKeyFull.next(fullResult); imageByKeyFull.complete(); graph.hasTiles(fullNode.key); observableFrom(graph.cacheTiles$(fullNode.key)).pipe( mergeAll()) .subscribe(() => { /*noop*/ }); let tileResult: { [key: string]: { [index: string]: ICoreNode } } = {}; tileResult[h] = {}; tileResult[h]["0"] = fullNode; imagesByH.next(tileResult); imagesByH.complete(); expect(graph.hasNode(fullNode.key)).toBe(true); expect(graph.hasTiles(fullNode.key)).toBe(true); expect(imagesByHSpy.calls.count()).toBe(1); expect(imageByKeyFillSpy.calls.count()).toBe(0); expect(imageByKeyFullSpy.calls.count()).toBe(1); graph.cacheBoundingBox$({ lat: 0, lon: 0 }, { lat: 1, lon: 1 }) .subscribe( (nodes: Node[]): void => { expect(nodes.length).toBe(1); expect(nodes[0].key).toBe(fullNode.key); expect(nodes[0].full).toBe(true); expect(graph.hasNode(key)).toBe(true); expect(imagesByHSpy.calls.count()).toBe(1); expect(imageByKeyFillSpy.calls.count()).toBe(0); expect(imageByKeyFullSpy.calls.count()).toBe(1); done(); }); }); it("should only cache tile once for two similar calls", (done: Function) => { let apiV3: APIv3 = new APIv3("clientId"); let index: GeoRBush<any> = new GeoRBush(16); let calculator: GraphCalculator = new GraphCalculator(null); let h: string = "h"; spyOn(calculator, "encodeHsFromBoundingBox").and.returnValue([h]); let imagesByH: Subject<{ [key: string]: { [index: string]: ICoreNode } }> = new Subject<{ [key: string]: { [index: string]: ICoreNode } }>(); const imagesByHSpy: jasmine.Spy = spyOn(apiV3, "imagesByH$"); imagesByHSpy.and.returnValue(imagesByH); let key: string = "key"; let imageByKeyFill: Subject<{ [key: string]: IFillNode }> = new Subject<{ [key: string]: IFillNode }>(); spyOn(apiV3, "imageByKeyFill$").and.returnValue(imageByKeyFill); let fullNode: IFullNode = helper.createFullNode(); fullNode.key = key; fullNode.l.lat = 0.5; fullNode.l.lon = 0.5; let graph: Graph = new Graph(apiV3, index, calculator); let count: number = 0; observableMerge( graph.cacheBoundingBox$({ lat: 0, lon: 0 }, { lat: 1, lon: 1 }), graph.cacheBoundingBox$({ lat: 0, lon: 0 }, { lat: 1, lon: 1 })) .subscribe( (nodes: Node[]): void => { expect(nodes.length).toBe(1); expect(nodes[0].key).toBe(fullNode.key); expect(nodes[0].full).toBe(true); expect(graph.hasNode(key)).toBe(true); count++; }, undefined, (): void => { expect(count).toBe(2); expect(imagesByHSpy.calls.count()).toBe(1); done(); }); let tileResult: { [key: string]: { [index: string]: ICoreNode } } = {}; tileResult[h] = {}; tileResult[h]["0"] = fullNode; imagesByH.next(tileResult); imagesByH.complete(); let fillResult: { [key: string]: IFillNode } = {}; fillResult[key] = fullNode; imageByKeyFill.next(fillResult); imageByKeyFill.complete(); }); }); describe("Graph.cacheFull$", () => { let helper: NodeHelper; beforeEach(() => { helper = new NodeHelper(); }); it("should be fetching", () => { let apiV3: APIv3 = new APIv3("clientId"); let index: GeoRBush<any> = new GeoRBush(16); let calculator: GraphCalculator = new GraphCalculator(null); let fullNode: IFullNode = helper.createFullNode(); let imageByKeyFull: Subject<{ [key: string]: IFullNode }> = new Subject<{ [key: string]: IFullNode }>(); spyOn(apiV3, "imageByKeyFull$").and.returnValue(imageByKeyFull); let graph: Graph = new Graph(apiV3, index, calculator); graph.cacheFull$(fullNode.key); expect(graph.isCachingFull(fullNode.key)).toBe(true); expect(graph.hasNode(fullNode.key)).toBe(false); expect(graph.getNode(fullNode.key)).toBeUndefined(); }); it("should fetch", (done: Function) => { let apiV3: APIv3 = new APIv3("clientId"); let index: GeoRBush<any> = new GeoRBush(16); let calculator: GraphCalculator = new GraphCalculator(null); let imageByKeyFull: Subject<{ [key: string]: IFullNode }> = new Subject<{ [key: string]: IFullNode }>(); spyOn(apiV3, "imageByKeyFull$").and.returnValue(imageByKeyFull); let graph: Graph = new Graph(apiV3, index, calculator); let fullNode: IFullNode = helper.createFullNode(); let result: { [key: string]: IFullNode } = {}; result[fullNode.key] = fullNode; graph.cacheFull$(fullNode.key) .subscribe( (g: Graph): void => { expect(g.isCachingFull(fullNode.key)).toBe(false); expect(g.hasNode(fullNode.key)).toBe(true); expect(g.getNode(fullNode.key)).toBeDefined(); expect(g.getNode(fullNode.key).key).toBe(fullNode.key); done(); }); imageByKeyFull.next(result); imageByKeyFull.complete(); expect(graph.isCachingFull(fullNode.key)).toBe(false); expect(graph.hasNode(fullNode.key)).toBe(true); expect(graph.getNode(fullNode.key)).toBeDefined(); expect(graph.getNode(fullNode.key).key).toBe(fullNode.key); }); it("should not make additional calls when fetching same node twice", () => { let apiV3: APIv3 = new APIv3("clientId"); let index: GeoRBush<any> = new GeoRBush(16); let calculator: GraphCalculator = new GraphCalculator(null); let fullNode: IFullNode = helper.createFullNode(); let imageByKeyFull: Subject<{ [key: string]: IFullNode }> = new Subject<{ [key: string]: IFullNode }>(); let imageByKeyFullSpy: jasmine.Spy = spyOn(apiV3, "imageByKeyFull$"); imageByKeyFullSpy.and.returnValue(imageByKeyFull); let graph: Graph = new Graph(apiV3, index, calculator); graph.cacheFull$(fullNode.key).subscribe(() => { /*noop*/ }); graph.cacheFull$(fullNode.key).subscribe(() => { /*noop*/ }); expect(imageByKeyFullSpy.calls.count()).toBe(1); }); it("should throw when fetching node already in graph", () => { let apiV3: APIv3 = new APIv3("clientId"); let index: GeoRBush<any> = new GeoRBush(16); let calculator: GraphCalculator = new GraphCalculator(null); let imageByKeyFull: Subject<{ [key: string]: IFullNode }> = new Subject<{ [key: string]: IFullNode }>(); spyOn(apiV3, "imageByKeyFull$").and.returnValue(imageByKeyFull); let graph: Graph = new Graph(apiV3, index, calculator); let fullNode: IFullNode = helper.createFullNode(); graph.cacheFull$(fullNode.key).subscribe(() => { /*noop*/ }); let result: { [key: string]: IFullNode } = {}; result[fullNode.key] = fullNode; imageByKeyFull.next(result); imageByKeyFull.complete(); expect(graph.isCachingFull(fullNode.key)).toBe(false); expect(() => { graph.cacheFull$(fullNode.key); }).toThrowError(Error); }); it("should throw if sequence key is missing", (done: Function) => { let apiV3: APIv3 = new APIv3("clientId"); let index: GeoRBush<any> = new GeoRBush(16); let calculator: GraphCalculator = new GraphCalculator(null); let imageByKeyFull: Subject<{ [key: string]: IFullNode }> = new Subject<{ [key: string]: IFullNode }>(); spyOn(apiV3, "imageByKeyFull$").and.returnValue(imageByKeyFull); let graph: Graph = new Graph(apiV3, index, calculator); let fullNode: IFullNode = helper.createFullNode(); fullNode.sequence_key = undefined; graph.cacheFull$(fullNode.key) .subscribe( (g: Graph): void => { return; }, (e: GraphMapillaryError): void => { done(); }); let result: { [key: string]: IFullNode } = {}; result[fullNode.key] = fullNode; imageByKeyFull.next(result); imageByKeyFull.complete(); }); it("should make full when fetched node has been retrieved in tile in parallell", () => { let apiV3: APIv3 = new APIv3("clientId"); let index: GeoRBush<any> = new GeoRBush(16); let calculator: GraphCalculator = new GraphCalculator(null); let key: string = "key"; let otherKey: string = "otherKey"; let imageByKeyFull: Subject<{ [key: string]: IFullNode }> = new Subject<{ [key: string]: IFullNode }>(); let imageByKeyFullOther: Subject<{ [key: string]: IFullNode }> = new Subject<{ [key: string]: IFullNode }>(); spyOn(apiV3, "imageByKeyFull$").and.callFake( (keys: string[]): Observable<{ [key: string]: IFullNode }> => { if (keys[0] === key) { return imageByKeyFull; } else if (keys[0] === otherKey) { return imageByKeyFullOther; } throw new GraphMapillaryError("Wrong key."); }); let h: string = "h"; spyOn(calculator, "encodeH").and.returnValue(h); spyOn(calculator, "encodeHs").and.returnValue([h]); let imagesByH: Subject<{ [key: string]: { [index: string]: ICoreNode } }> = new Subject<{ [key: string]: { [index: string]: ICoreNode } }>(); spyOn(apiV3, "imagesByH$").and.returnValue(imagesByH); let graph: Graph = new Graph(apiV3, index, calculator); let otherNode: IFullNode = helper.createFullNode(); otherNode.key = otherKey; graph.cacheFull$(otherNode.key).subscribe(() => { /*noop*/ }); let otherFullResult: { [key: string]: IFullNode } = {}; otherFullResult[otherNode.key] = otherNode; imageByKeyFullOther.next(otherFullResult); imageByKeyFullOther.complete(); graph.hasTiles(otherNode.key); observableFrom(graph.cacheTiles$(otherNode.key)).pipe( mergeAll()) .subscribe(() => { /*noop*/ }); let fullNode: IFullNode = helper.createFullNode(); fullNode.key = key; graph.cacheFull$(fullNode.key).subscribe(() => { /*noop*/ }); expect(graph.hasNode(fullNode.key)).toBe(false); expect(graph.isCachingFull(fullNode.key)).toBe(true); let tileResult: { [key: string]: { [index: string]: ICoreNode } } = {}; tileResult[h] = {}; tileResult[h]["0"] = otherNode; tileResult[h]["1"] = fullNode; imagesByH.next(tileResult); imagesByH.complete(); expect(graph.hasNode(fullNode.key)).toBe(true); expect(graph.getNode(fullNode.key).full).toBe(false); let fullResult: { [key: string]: IFullNode } = {}; fullResult[fullNode.key] = fullNode; imageByKeyFull.next(fullResult); imageByKeyFull.complete(); expect(graph.getNode(fullNode.key).full).toBe(true); }); }); describe("Graph.cacheFill$", () => { let helper: NodeHelper; beforeEach(() => { helper = new NodeHelper(); }); it("should be filling", () => { let apiV3: APIv3 = new APIv3("clientId"); let index: GeoRBush<any> = new GeoRBush(16); let calculator: GraphCalculator = new GraphCalculator(null); let imageByKeyFull: Subject<{ [key: string]: IFullNode }> = new Subject<{ [key: string]: IFullNode }>(); spyOn(apiV3, "imageByKeyFull$").and.returnValue(imageByKeyFull); let h: string = "h"; spyOn(calculator, "encodeH").and.returnValue(h); spyOn(calculator, "encodeHs").and.returnValue([h]); let imagesByH: Subject<{ [key: string]: { [index: string]: ICoreNode } }> = new Subject<{ [key: string]: { [index: string]: ICoreNode } }>(); spyOn(apiV3, "imagesByH$").and.returnValue(imagesByH); let imageByKeyFill: Subject<{ [key: string]: IFillNode }> = new Subject<{ [key: string]: IFillNode }>(); spyOn(apiV3, "imageByKeyFill$").and.returnValue(imageByKeyFill); let graph: Graph = new Graph(apiV3, index, calculator); let fullNode: IFullNode = helper.createFullNode(); graph.cacheFull$(fullNode.key).subscribe(() => { /*noop*/ }); let fetchResult: { [key: string]: IFullNode } = {}; fetchResult[fullNode.key] = fullNode; imageByKeyFull.next(fetchResult); graph.hasTiles(fullNode.key); observableFrom(graph.cacheTiles$(fullNode.key)).pipe( mergeAll()) .subscribe(() => { /*noop*/ }); let tileNode: ICoreNode = helper.createCoreNode(); tileNode.key = "tileNodeKey"; let result: { [key: string]: { [index: string]: ICoreNode } } = {}; result[h] = {}; result[h]["0"] = tileNode; imagesByH.next(result); expect(graph.getNode(tileNode.key).full).toBe(false); expect(graph.isCachingFill(tileNode.key)).toBe(false); graph.cacheFill$(tileNode.key).subscribe(() => { /*noop*/ }); expect(graph.getNode(tileNode.key).full).toBe(false); expect(graph.isCachingFill(tileNode.key)).toBe(true); }); it("should fill", () => { let apiV3: APIv3 = new APIv3("clientId"); let index: GeoRBush<any> = new GeoRBush(16); let calculator: GraphCalculator = new GraphCalculator(null); let imageByKeyFull: Subject<{ [key: string]: IFullNode }> = new Subject<{ [key: string]: IFullNode }>(); spyOn(apiV3, "imageByKeyFull$").and.returnValue(imageByKeyFull); let h: string = "h"; spyOn(calculator, "encodeH").and.returnValue(h); spyOn(calculator, "encodeHs").and.returnValue([h]); let imagesByH: Subject<{ [key: string]: { [index: string]: ICoreNode } }> = new Subject<{ [key: string]: { [index: string]: ICoreNode } }>(); spyOn(apiV3, "imagesByH$").and.returnValue(imagesByH); let imageByKeyFill: Subject<{ [key: string]: IFillNode }> = new Subject<{ [key: string]: IFillNode }>(); spyOn(apiV3, "imageByKeyFill$").and.returnValue(imageByKeyFill); let graph: Graph = new Graph(apiV3, index, calculator); let fullNode: IFullNode = helper.createFullNode(); graph.cacheFull$(fullNode.key).subscribe(() => { /*noop*/ }); let fetchResult: { [key: string]: IFullNode } = {}; fetchResult[fullNode.key] = fullNode; imageByKeyFull.next(fetchResult); graph.hasTiles(fullNode.key); observableFrom(graph.cacheTiles$(fullNode.key)).pipe( mergeAll()) .subscribe(() => { /*noop*/ }); let tileNode: ICoreNode = helper.createCoreNode(); tileNode.key = "tileNodeKey"; let result: { [key: string]: { [index: string]: ICoreNode } } = {}; result[h] = {}; result[h]["0"] = tileNode; imagesByH.next(result); expect(graph.getNode(tileNode.key).full).toBe(false); expect(graph.isCachingFill(tileNode.key)).toBe(false); graph.cacheFill$(tileNode.key).subscribe(() => { /*noop*/ }); let fillTileNode: IFillNode = helper.createFullNode(); let fillResult: { [key: string]: IFillNode } = {}; fillResult[tileNode.key] = fillTileNode; imageByKeyFill.next(fillResult); expect(graph.getNode(tileNode.key).full).toBe(true); expect(graph.isCachingFill(tileNode.key)).toBe(false); }); it("should not make additional calls when filling same node twice", () => { let apiV3: APIv3 = new APIv3("clientId"); let index: GeoRBush<any> = new GeoRBush(16); let calculator: GraphCalculator = new GraphCalculator(null); let imageByKeyFull: Subject<{ [key: string]: IFullNode }> = new Subject<{ [key: string]: IFullNode }>(); spyOn(apiV3, "imageByKeyFull$").and.returnValue(imageByKeyFull); let h: string = "h"; spyOn(calculator, "encodeH").and.returnValue(h); spyOn(calculator, "encodeHs").and.returnValue([h]); let imagesByH: Subject<{ [key: string]: { [index: string]: ICoreNode } }> = new Subject<{ [key: string]: { [index: string]: ICoreNode } }>(); spyOn(apiV3, "imagesByH$").and.returnValue(imagesByH); let imageByKeyFill: Subject<{ [key: string]: IFillNode }> = new Subject<{ [key: string]: IFillNode }>(); let imageByKeyFillSpy: jasmine.Spy = spyOn(apiV3, "imageByKeyFill$"); imageByKeyFillSpy.and.returnValue(imageByKeyFill); let graph: Graph = new Graph(apiV3, index, calculator); let fullNode: IFullNode = helper.createFullNode(); graph.cacheFull$(fullNode.key).subscribe(() => { /*noop*/ }); let fetchResult: { [key: string]: IFullNode } = {}; fetchResult[fullNode.key] = fullNode; imageByKeyFull.next(fetchResult); graph.hasTiles(fullNode.key); observableFrom(graph.cacheTiles$(fullNode.key)).pipe( mergeAll()) .subscribe(() => { /*noop*/ }); let tileNode: ICoreNode = helper.createCoreNode(); tileNode.key = "tileNodeKey"; let result: { [key: string]: { [index: string]: ICoreNode } } = {}; result[h] = {}; result[h]["0"] = tileNode; imagesByH.next(result); expect(graph.getNode(tileNode.key).full).toBe(false); expect(graph.isCachingFill(tileNode.key)).toBe(false); graph.cacheFill$(tileNode.key).subscribe(() => { /*noop*/ }); graph.cacheFill$(tileNode.key).subscribe(() => { /*noop*/ }); expect(imageByKeyFillSpy.calls.count()).toBe(1); }); it("should throw if already fetching", () => { let apiV3: APIv3 = new APIv3("clientId"); let index: GeoRBush<any> = new GeoRBush(16); let calculator: GraphCalculator = new GraphCalculator(null); let imageByKeyFull: Subject<{ [key: string]: IFullNode }> = new Subject<{ [key: string]: IFullNode }>(); spyOn(apiV3, "imageByKeyFull$").and.returnValue(imageByKeyFull); let h: string = "h"; spyOn(calculator, "encodeH").and.returnValue(h); spyOn(calculator, "encodeHs").and.returnValue([h]); let imagesByH: Subject<{ [key: string]: { [index: string]: ICoreNode } }> = new Subject<{ [key: string]: { [index: string]: ICoreNode } }>(); spyOn(apiV3, "imagesByH$").and.returnValue(imagesByH); let graph: Graph = new Graph(apiV3, index, calculator); let fullNode: IFullNode = helper.createFullNode(); graph.cacheFull$(fullNode.key); expect(graph.isCachingFull(fullNode.key)).toBe(true); expect(() => { graph.cacheFill$(fullNode.key); }).toThrowError(Error); }); it("should throw if node does not exist", () => { let apiV3: APIv3 = new APIv3("clientId"); let index: GeoRBush<any> = new GeoRBush(16); let calculator: GraphCalculator = new GraphCalculator(null); let imageByKeyFill: Subject<{ [key: string]: IFillNode }> = new Subject<{ [key: string]: IFillNode }>(); spyOn(apiV3, "imageByKeyFill$").and.returnValue(imageByKeyFill); let graph: Graph = new Graph(apiV3, index, calculator); expect(() => { graph.cacheFill$("key"); }).toThrowError(Error); }); it("should throw if already full", () => { let apiV3: APIv3 = new APIv3("clientId"); let index: GeoRBush<any> = new GeoRBush(16); let calculator: GraphCalculator = new GraphCalculator(null); let imageByKeyFull: Subject<{ [key: string]: IFullNode }> = new Subject<{ [key: string]: IFullNode }>(); spyOn(apiV3, "imageByKeyFull$").and.returnValue(imageByKeyFull); let imageByKeyFill: Subject<{ [key: string]: IFillNode }> = new Subject<{ [key: string]: IFillNode }>(); spyOn(apiV3, "imageByKeyFill$").and.returnValue(imageByKeyFill); let graph: Graph = new Graph(apiV3, index, calculator); let fullNode: IFullNode = helper.createFullNode(); graph.cacheFull$(fullNode.key); let fetchResult: { [key: string]: IFullNode } = {}; fetchResult[fullNode.key] = fullNode; imageByKeyFull.next(fetchResult); expect(() => { graph.cacheFill$(fullNode.key); }).toThrowError(Error); }); }); describe("Graph.cacheTiles$", () => { let helper: NodeHelper; beforeEach(() => { helper = new NodeHelper(); }); it("should be caching tiles", () => { let apiV3: APIv3 = new APIv3("clientId"); let index: GeoRBush<any> = new GeoRBush(16); let calculator: GraphCalculator = new GraphCalculator(null); let node: Node = helper.createNode(); spyOn(calculator, "encodeHs").and.returnValue(["h"]); let imagesByH: Subject<{ [key: string]: { [index: string]: ICoreNode } }> = new Subject<{ [key: string]: { [index: string]: ICoreNode } }>(); spyOn(apiV3, "imagesByH$").and.returnValue(imagesByH); let graph: Graph = new Graph(apiV3, index, calculator); spyOn(graph, "hasNode").and.returnValue(true); spyOn(graph, "getNode").and.returnValue(node); expect(graph.hasTiles(node.key)).toBe(false); expect(graph.isCachingTiles(node.key)).toBe(false); graph.cacheTiles$(node.key); expect(graph.hasTiles(node.key)).toBe(false); expect(graph.isCachingTiles(node.key)).toBe(true); }); it("should cache tiles", () => { let apiV3: APIv3 = new APIv3("clientId"); let index: GeoRBush<any> = new GeoRBush(16); let calculator: GraphCalculator = new GraphCalculator(null); let fullNode: IFullNode = helper.createFullNode(); let h: string = "h"; spyOn(calculator, "encodeH").and.returnValue(h); spyOn(calculator, "encodeHs").and.returnValue([h]); let imageByKeyResult: { [key: string]: IFullNode } = {}; imageByKeyResult[fullNode.key] = fullNode; let imageByKeyFull: Observable<{ [key: string]: IFullNode }> = observableOf<{ [key: string]: IFullNode }>(imageByKeyResult); spyOn(apiV3, "imageByKeyFull$").and.returnValue(imageByKeyFull); let imagesByH: Subject<{ [key: string]: { [index: string]: ICoreNode } }> = new Subject<{ [key: string]: { [index: string]: ICoreNode } }>(); spyOn(apiV3, "imagesByH$").and.returnValue(imagesByH); let graph: Graph = new Graph(apiV3, index, calculator); graph.cacheFull$(fullNode.key).subscribe(() => { /*noop*/ }); expect(graph.hasTiles(fullNode.key)).toBe(false); expect(graph.isCachingTiles(fullNode.key)).toBe(false); observableFrom(graph.cacheTiles$(fullNode.key)).pipe( mergeAll()) .subscribe(() => { /*noop*/ }); let result: { [key: string]: { [index: string]: ICoreNode } } = {}; result[h] = {}; result[h]["0"] = fullNode; imagesByH.next(result); expect(graph.hasTiles(fullNode.key)).toBe(true); expect(graph.isCachingTiles(fullNode.key)).toBe(false); }); it("should encode hs only once when checking tiles cache", () => { let apiV3: APIv3 = new APIv3("clientId"); let index: GeoRBush<any> = new GeoRBush(16); let calculator: GraphCalculator = new GraphCalculator(null); let node: Node = helper.createNode(); let h: string = "h"; let encodeHsSpy: jasmine.Spy = spyOn(calculator, "encodeHs"); encodeHsSpy.and.returnValue([h]); let imagesByH: Subject<{ [key: string]: { [index: string]: ICoreNode } }> = new Subject<{ [key: string]: { [index: string]: ICoreNode } }>(); spyOn(apiV3, "imagesByH$").and.returnValue(imagesByH); let graph: Graph = new Graph(apiV3, index, calculator); spyOn(graph, "hasNode").and.returnValue(true); spyOn(graph, "getNode").and.returnValue(node); expect(graph.hasTiles(node.key)).toBe(false); expect(graph.hasTiles(node.key)).toBe(false); expect(encodeHsSpy.calls.count()).toBe(1); }); it("should encode hs only once when caching tiles", () => { let apiV3: APIv3 = new APIv3("clientId"); let index: GeoRBush<any> = new GeoRBush(16); let calculator: GraphCalculator = new GraphCalculator(null); let fullNode: IFullNode = helper.createFullNode(); let h: string = "h"; spyOn(calculator, "encodeH").and.returnValue(h); let encodeHsSpy: jasmine.Spy = spyOn(calculator, "encodeHs"); encodeHsSpy.and.returnValue([h]); let imageByKeyResult: { [key: string]: IFullNode } = {}; imageByKeyResult[fullNode.key] = fullNode; let imageByKeyFull: Observable<{ [key: string]: IFullNode }> = observableOf<{ [key: string]: IFullNode }>(imageByKeyResult); spyOn(apiV3, "imageByKeyFull$").and.returnValue(imageByKeyFull); let imagesByH: Subject<{ [key: string]: { [index: string]: ICoreNode } }> = new Subject<{ [key: string]: { [index: string]: ICoreNode } }>(); spyOn(apiV3, "imagesByH$").and.returnValue(imagesByH); let graph: Graph = new Graph(apiV3, index, calculator); graph.cacheFull$(fullNode.key).subscribe(() => { /*noop*/ }); expect(graph.hasTiles(fullNode.key)).toBe(false); observableFrom(graph.cacheTiles$(fullNode.key)).pipe( mergeAll()) .subscribe(() => { /*noop*/ }); let result: { [key: string]: { [index: string]: ICoreNode } } = {}; result[h] = {}; result[h]["0"] = fullNode; imagesByH.next(result); expect(graph.hasTiles(fullNode.key)).toBe(true); expect(encodeHsSpy.calls.count()).toBe(1); }); }); describe("Graph.cacheSequenceNodes$", () => { let helper: NodeHelper; beforeEach(() => { helper = new NodeHelper(); }); it("should throw when sequence does not exist", () => { const apiV3: APIv3 = new APIv3("clientId"); const index: GeoRBush<any> = new GeoRBush(16); const graphCalculator: GraphCalculator = new GraphCalculator(null); const edgeCalculator: EdgeCalculator = new EdgeCalculator(); const graph: Graph = new Graph(apiV3, index, graphCalculator, edgeCalculator); expect(() => { graph.cacheSequenceNodes$("sequenceKey"); }).toThrowError(Error); }); it("should not be cached", () => { const apiV3: APIv3 = new APIv3("clientId"); const index: GeoRBush<any> = new GeoRBush(16); const graphCalculator: GraphCalculator = new GraphCalculator(null); const edgeCalculator: EdgeCalculator = new EdgeCalculator(); const sequenceByKey: Subject<{ [key: string]: ISequence }> = new Subject<{ [key: string]: ISequence }>(); spyOn(apiV3, "sequenceByKey$").and.returnValue(sequenceByKey); const imageByKey: Subject<{ [key: string]: IFullNode }> = new Subject<{ [key: string]: IFullNode }>(); spyOn(apiV3, "imageByKeyFull$").and.returnValue(imageByKey); const graph: Graph = new Graph(apiV3, index, graphCalculator, edgeCalculator); const sequenceKey: string = "sequenceKey"; const key: string = "key"; graph.cacheSequence$(sequenceKey).subscribe(); const result: { [sequenceKey: string]: ISequence } = {}; result[sequenceKey] = { key: sequenceKey, keys: [key] }; sequenceByKey.next(result); sequenceByKey.complete(); expect(graph.hasSequenceNodes(sequenceKey)).toBe(false); }); it("should start caching", () => { const apiV3: APIv3 = new APIv3("clientId"); const index: GeoRBush<any> = new GeoRBush(16); const graphCalculator: GraphCalculator = new GraphCalculator(null); const edgeCalculator: EdgeCalculator = new EdgeCalculator(); const sequenceByKey: Subject<{ [key: string]: ISequence }> = new Subject<{ [key: string]: ISequence }>(); spyOn(apiV3, "sequenceByKey$").and.returnValue(sequenceByKey); const imageByKey: Subject<{ [key: string]: IFullNode }> = new Subject<{ [key: string]: IFullNode }>(); spyOn(apiV3, "imageByKeyFull$").and.returnValue(imageByKey); const graph: Graph = new Graph(apiV3, index, graphCalculator, edgeCalculator); const sequenceKey: string = "sequenceKey"; const key: string = "key"; graph.cacheSequence$(sequenceKey).subscribe(); const result: { [sequenceKey: string]: ISequence } = {}; result[sequenceKey] = { key: sequenceKey, keys: [key] }; sequenceByKey.next(result); sequenceByKey.complete(); graph.cacheSequenceNodes$(sequenceKey).subscribe(); expect(graph.isCachingSequenceNodes(sequenceKey)).toBe(true); }); it("should be cached and not caching", () => { const apiV3: APIv3 = new APIv3("clientId"); const index: GeoRBush<any> = new GeoRBush(16); const graphCalculator: GraphCalculator = new GraphCalculator(null); const edgeCalculator: EdgeCalculator = new EdgeCalculator(); const sequenceByKey: Subject<{ [key: string]: ISequence }> = new Subject<{ [key: string]: ISequence }>(); spyOn(apiV3, "sequenceByKey$").and.returnValue(sequenceByKey); const imageByKey: Subject<{ [key: string]: IFullNode }> = new Subject<{ [key: string]: IFullNode }>(); spyOn(apiV3, "imageByKeyFull$").and.returnValue(imageByKey); const graph: Graph = new Graph(apiV3, index, graphCalculator, edgeCalculator); const sequenceKey: string = "sequenceKey"; const nodeKey: string = "nodeKey"; graph.cacheSequence$(sequenceKey).subscribe(); const result: { [sequenceKey: string]: ISequence } = {}; result[sequenceKey] = { key: sequenceKey, keys: [nodeKey] }; sequenceByKey.next(result); sequenceByKey.complete(); graph.cacheSequenceNodes$(sequenceKey).subscribe(); const imageResult: { [key: string]: IFullNode } = {}; imageResult[nodeKey] = helper.createFullNode(); imageResult[nodeKey].key = nodeKey; imageByKey.next(imageResult); imageByKey.complete(); expect(graph.hasSequenceNodes(sequenceKey)).toBe(true); expect(graph.isCachingSequenceNodes(sequenceKey)).toBe(false); expect(graph.hasNode(nodeKey)).toBe(true); expect(graph.getNode(nodeKey).key).toBe(nodeKey); }); it("should not be cached after uncaching sequence node", () => { const apiV3: APIv3 = new APIv3("clientId"); const index: GeoRBush<any> = new GeoRBush(16); const graphCalculator: GraphCalculator = new GraphCalculator(null); spyOn(graphCalculator, "encodeH").and.returnValue("h"); const edgeCalculator: EdgeCalculator = new EdgeCalculator(); const configuration: IGraphConfiguration = { maxSequences: 1, maxUnusedNodes: 0, maxUnusedPreStoredNodes: 0, maxUnusedTiles: 0, }; const sequenceByKey: Subject<{ [key: string]: ISequence }> = new Subject<{ [key: string]: ISequence }>(); spyOn(apiV3, "sequenceByKey$").and.returnValue(sequenceByKey); const imageByKey: Subject<{ [key: string]: IFullNode }> = new Subject<{ [key: string]: IFullNode }>(); spyOn(apiV3, "imageByKeyFull$").and.returnValue(imageByKey); const graph: Graph = new Graph(apiV3, index, graphCalculator, edgeCalculator, undefined, configuration); const sequenceKey: string = "sequenceKey"; const nodeKey: string = "nodeKey"; graph.cacheSequence$(sequenceKey).subscribe(); const result: { [sequenceKey: string]: ISequence } = {}; result[sequenceKey] = { key: sequenceKey, keys: [nodeKey] }; sequenceByKey.next(result); sequenceByKey.complete(); graph.cacheSequenceNodes$(sequenceKey).subscribe(); const imageResult: { [key: string]: IFullNode } = {}; const fullNode: IFullNode = helper.createFullNode(); fullNode.key = nodeKey; fullNode.sequence_key = sequenceKey; imageResult[fullNode.key] = fullNode; imageByKey.next(imageResult); imageByKey.complete(); expect(graph.hasSequenceNodes(sequenceKey)).toBe(true); graph.uncache([]); expect(graph.hasSequenceNodes(sequenceKey)).toBe(false); }); it("should not be cached after uncaching sequence", () => { const apiV3: APIv3 = new APIv3("clientId"); const index: GeoRBush<any> = new GeoRBush(16); const graphCalculator: GraphCalculator = new GraphCalculator(null); spyOn(graphCalculator, "encodeH").and.returnValue("h"); const edgeCalculator: EdgeCalculator = new EdgeCalculator(); const configuration: IGraphConfiguration = { maxSequences: 0, maxUnusedNodes: 1, maxUnusedPreStoredNodes: 0, maxUnusedTiles: 1, }; const sequenceByKey: Subject<{ [key: string]: ISequence }> = new Subject<{ [key: string]: ISequence }>(); spyOn(apiV3, "sequenceByKey$").and.returnValue(sequenceByKey); const imageByKey: Subject<{ [key: string]: IFullNode }> = new Subject<{ [key: string]: IFullNode }>(); spyOn(apiV3, "imageByKeyFull$").and.returnValue(imageByKey); const graph: Graph = new Graph(apiV3, index, graphCalculator, edgeCalculator, undefined, configuration); const sequenceKey: string = "sequenceKey"; const nodeKey: string = "nodeKey"; graph.cacheSequence$(sequenceKey).subscribe(); const result: { [sequenceKey: string]: ISequence } = {}; result[sequenceKey] = { key: sequenceKey, keys: [nodeKey] }; sequenceByKey.next(result); sequenceByKey.complete(); graph.cacheSequenceNodes$(sequenceKey).subscribe(); const imageResult: { [key: string]: IFullNode } = {}; const fullNode: IFullNode = helper.createFullNode(); fullNode.key = nodeKey; fullNode.sequence_key = sequenceKey; imageResult[fullNode.key] = fullNode; imageByKey.next(imageResult); imageByKey.complete(); graph.initializeCache(fullNode.key); expect(graph.hasSequenceNodes(sequenceKey)).toBe(true); graph.uncache([]); expect(graph.hasSequenceNodes(sequenceKey)).toBe(false); }); it("should be cached after uncaching if sequence is kept", () => { const apiV3: APIv3 = new APIv3("clientId"); const index: GeoRBush<any> = new GeoRBush(16); const graphCalculator: GraphCalculator = new GraphCalculator(null); spyOn(graphCalculator, "encodeH").and.returnValue("h"); const edgeCalculator: EdgeCalculator = new EdgeCalculator(); const configuration: IGraphConfiguration = { maxSequences: 1, maxUnusedNodes: 0, maxUnusedPreStoredNodes: 0, maxUnusedTiles: 0, }; const sequenceByKey: Subject<{ [key: string]: ISequence }> = new Subject<{ [key: string]: ISequence }>(); spyOn(apiV3, "sequenceByKey$").and.returnValue(sequenceByKey); const imageByKey: Subject<{ [key: string]: IFullNode }> = new Subject<{ [key: string]: IFullNode }>(); spyOn(apiV3, "imageByKeyFull$").and.returnValue(imageByKey); const graph: Graph = new Graph(apiV3, index, graphCalculator, edgeCalculator, undefined, configuration); const sequenceKey: string = "sequenceKey"; const nodeKey: string = "nodeKey"; graph.cacheSequence$(sequenceKey).subscribe(); const result: { [sequenceKey: string]: ISequence } = {}; result[sequenceKey] = { key: sequenceKey, keys: [nodeKey] }; sequenceByKey.next(result); sequenceByKey.complete(); graph.cacheSequenceNodes$(sequenceKey).subscribe(); const imageResult: { [key: string]: IFullNode } = {}; const fullNode: IFullNode = helper.createFullNode(); fullNode.key = nodeKey; fullNode.sequence_key = sequenceKey; imageResult[fullNode.key] = fullNode; imageByKey.next(imageResult); imageByKey.complete(); expect(graph.hasSequenceNodes(sequenceKey)).toBe(true); graph.uncache([], sequenceKey); expect(graph.hasSequenceNodes(sequenceKey)).toBe(true); }); it("should be cached after uncaching if all nodes are kept", () => { const apiV3: APIv3 = new APIv3("clientId"); const index: GeoRBush<any> = new GeoRBush(16); const graphCalculator: GraphCalculator = new GraphCalculator(null); spyOn(graphCalculator, "encodeH").and.returnValue("h"); const edgeCalculator: EdgeCalculator = new EdgeCalculator(); const configuration: IGraphConfiguration = { maxSequences: 1, maxUnusedNodes: 0, maxUnusedPreStoredNodes: 0, maxUnusedTiles: 0, }; const sequenceByKey: Subject<{ [key: string]: ISequence }> = new Subject<{ [key: string]: ISequence }>(); spyOn(apiV3, "sequenceByKey$").and.returnValue(sequenceByKey); const imageByKey: Subject<{ [key: string]: IFullNode }> = new Subject<{ [key: string]: IFullNode }>(); spyOn(apiV3, "imageByKeyFull$").and.returnValue(imageByKey); const graph: Graph = new Graph(apiV3, index, graphCalculator, edgeCalculator, undefined, configuration); const sequenceKey: string = "sequenceKey"; const nodeKey: string = "nodeKey"; graph.cacheSequence$(sequenceKey).subscribe(); const result: { [sequenceKey: string]: ISequence } = {}; result[sequenceKey] = { key: sequenceKey, keys: [nodeKey] }; sequenceByKey.next(result); sequenceByKey.complete(); graph.cacheSequenceNodes$(sequenceKey).subscribe(); const imageResult: { [key: string]: IFullNode } = {}; const fullNode: IFullNode = helper.createFullNode(); fullNode.key = nodeKey; fullNode.sequence_key = sequenceKey; imageResult[fullNode.key] = fullNode; imageByKey.next(imageResult); imageByKey.complete(); expect(graph.hasSequenceNodes(sequenceKey)).toBe(true); graph.uncache([fullNode.key]); expect(graph.hasSequenceNodes(sequenceKey)).toBe(true); }); it("should not be cached after uncaching tile", () => { const apiV3: APIv3 = new APIv3("clientId"); const index: GeoRBush<any> = new GeoRBush(16); const graphCalculator: GraphCalculator = new GraphCalculator(null); let h: string = "h"; spyOn(graphCalculator, "encodeH").and.returnValue(h); spyOn(graphCalculator, "encodeHs").and.returnValue([h]); const edgeCalculator: EdgeCalculator = new EdgeCalculator(); const configuration: IGraphConfiguration = { maxSequences: 1, maxUnusedNodes: 1, maxUnusedPreStoredNodes: 0, maxUnusedTiles: 0, }; const sequenceByKey: Subject<{ [key: string]: ISequence }> = new Subject<{ [key: string]: ISequence }>(); spyOn(apiV3, "sequenceByKey$").and.returnValue(sequenceByKey); const imageByKey: Subject<{ [key: string]: IFullNode }> = new Subject<{ [key: string]: IFullNode }>(); spyOn(apiV3, "imageByKeyFull$").and.returnValue(imageByKey); const graph: Graph = new Graph(apiV3, index, graphCalculator, edgeCalculator, undefined, configuration); const sequenceKey: string = "sequenceKey"; const nodeKey: string = "nodeKey"; graph.cacheSequence$(sequenceKey).subscribe(); const result: { [sequenceKey: string]: ISequence } = {}; result[sequenceKey] = { key: sequenceKey, keys: [nodeKey] }; sequenceByKey.next(result); sequenceByKey.complete(); graph.cacheSequenceNodes$(sequenceKey).subscribe(); const imageResult: { [key: string]: IFullNode } = {}; const fullNode: IFullNode = helper.createFullNode(); fullNode.key = nodeKey; fullNode.sequence_key = sequenceKey; imageResult[fullNode.key] = fullNode; imageByKey.next(imageResult); imageByKey.complete(); graph.initializeCache(fullNode.key); let imagesByH: Subject<{ [key: string]: { [index: string]: ICoreNode } }> = new Subject<{ [key: string]: { [index: string]: ICoreNode } }>(); spyOn(apiV3, "imagesByH$").and.returnValue(imagesByH); graph.hasTiles(fullNode.key); observableFrom(graph.cacheTiles$(fullNode.key)).pipe( mergeAll()) .subscribe(() => { /*noop*/ }); let imagesByHResult: { [key: string]: { [index: string]: ICoreNode } } = {}; imagesByHResult[h] = {}; imagesByHResult[h]["0"] = fullNode; imagesByH.next(imagesByHResult); expect(graph.hasTiles(fullNode.key)).toBe(true); expect(graph.hasSequenceNodes(sequenceKey)).toBe(true); let node: Node = graph.getNode(fullNode.key); let nodeUncacheSpy: jasmine.Spy = spyOn(node, "uncache").and.stub(); let nodeDisposeSpy: jasmine.Spy = spyOn(node, "dispose").and.stub(); graph.uncache([]); expect(nodeUncacheSpy.calls.count()).toBe(0); expect(nodeDisposeSpy.calls.count()).toBe(1); expect(graph.hasSequenceNodes(sequenceKey)).toBe(false); }); it("should be cached after uncaching tile if sequence is kept", () => { const apiV3: APIv3 = new APIv3("clientId"); const index: GeoRBush<any> = new GeoRBush(16); const graphCalculator: GraphCalculator = new GraphCalculator(null); let h: string = "h"; spyOn(graphCalculator, "encodeH").and.returnValue(h); spyOn(graphCalculator, "encodeHs").and.returnValue([h]); const edgeCalculator: EdgeCalculator = new EdgeCalculator(); const configuration: IGraphConfiguration = { maxSequences: 1, maxUnusedNodes: 1, maxUnusedPreStoredNodes: 0, maxUnusedTiles: 0, }; const sequenceByKey: Subject<{ [key: string]: ISequence }> = new Subject<{ [key: string]: ISequence }>(); spyOn(apiV3, "sequenceByKey$").and.returnValue(sequenceByKey); const imageByKey: Subject<{ [key: string]: IFullNode }> = new Subject<{ [key: string]: IFullNode }>(); spyOn(apiV3, "imageByKeyFull$").and.returnValue(imageByKey); const graph: Graph = new Graph(apiV3, index, graphCalculator, edgeCalculator, undefined, configuration); const sequenceKey: string = "sequenceKey"; const nodeKey: string = "nodeKey"; graph.cacheSequence$(sequenceKey).subscribe(); const result: { [sequenceKey: string]: ISequence } = {}; result[sequenceKey] = { key: sequenceKey, keys: [nodeKey] }; sequenceByKey.next(result); sequenceByKey.complete(); graph.cacheSequenceNodes$(sequenceKey).subscribe(); const imageResult: { [key: string]: IFullNode } = {}; const fullNode: IFullNode = helper.createFullNode(); fullNode.key = nodeKey; fullNode.sequence_key = sequenceKey; imageResult[fullNode.key] = fullNode; imageByKey.next(imageResult); imageByKey.complete(); graph.initializeCache(fullNode.key); let imagesByH: Subject<{ [key: string]: { [index: string]: ICoreNode } }> = new Subject<{ [key: string]: { [index: string]: ICoreNode } }>(); spyOn(apiV3, "imagesByH$").and.returnValue(imagesByH); graph.hasTiles(fullNode.key); observableFrom(graph.cacheTiles$(fullNode.key)).pipe( mergeAll()) .subscribe(() => { /*noop*/ }); let imagesByHResult: { [key: string]: { [index: string]: ICoreNode } } = {}; imagesByHResult[h] = {}; imagesByHResult[h]["0"] = fullNode; imagesByH.next(imagesByHResult); expect(graph.hasTiles(fullNode.key)).toBe(true); expect(graph.hasSequenceNodes(sequenceKey)).toBe(true); let node: Node = graph.getNode(fullNode.key); let nodeUncacheSpy: jasmine.Spy = spyOn(node, "uncache").and.stub(); let nodeDisposeSpy: jasmine.Spy = spyOn(node, "dispose").and.stub(); graph.uncache([], sequenceKey); expect(nodeUncacheSpy.calls.count()).toBe(1); expect(nodeDisposeSpy.calls.count()).toBe(0); expect(graph.hasSequenceNodes(sequenceKey)).toBe(true); }); it("should throw if caching already cached sequence nodes", () => { const apiV3: APIv3 = new APIv3("clientId"); const index: GeoRBush<any> = new GeoRBush(16);