mapillary-js
Version:
WebGL JavaScript library for displaying street level imagery from mapillary.com
1,206 lines (912 loc) • 156 kB
text/typescript
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 {
ICoreNode,
IFillNode,
IFullNode,
ISequence,
IDataProvider,
} 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";
import API from "../../src/api/API";
import FalcorDataProvider from "../../src/api/FalcorDataProvider";
import IGeometryProvider from "../../src/api/interfaces/IGeometryProvider";
import GeohashGeometryProvider from "../../src/api/GeohashGeometryProvider";
describe("Graph.ctor", () => {
it("should create a graph", () => {
const api: API = new API(new FalcorDataProvider({ clientToken: "cid" }));
const graph: Graph = new Graph(api);
expect(graph).toBeDefined();
});
it("should create a graph with all ctor params", () => {
const api: API = new API(new FalcorDataProvider({ clientToken: "cid" }));
const index: GeoRBush<any> = new GeoRBush(16);
const calculator: GraphCalculator = new GraphCalculator(null);
const graph: Graph = new Graph(api, 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) => {
const geometryProvider: IGeometryProvider = new GeohashGeometryProvider();
const dataProvider: IDataProvider = new FalcorDataProvider(
{ clientToken: "cid" },
geometryProvider);
const api: API = new API(dataProvider);
const index: GeoRBush<any> = new GeoRBush(16);
const calculator: GraphCalculator = new GraphCalculator(null);
const h: string = "h";
spyOn(geometryProvider, "bboxToCellIds").and.returnValue([h]);
const imagesByH: Subject<{ [key: string]: { [index: string]: ICoreNode } }> =
new Subject<{ [key: string]: { [index: string]: ICoreNode } }>();
spyOn(api, "imagesByH$").and.returnValue(imagesByH);
const key: string = "key";
const imageByKeyFill: Subject<{ [key: string]: IFillNode }> = new Subject<{ [key: string]: IFillNode }>();
spyOn(api, "imageByKeyFill$").and.returnValue(imageByKeyFill);
const fullNode: IFullNode = helper.createFullNode();
fullNode.key = key;
fullNode.l.lat = 0.5;
fullNode.l.lon = 0.5;
const graph: Graph = new Graph(api, 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();
});
const tileResult: { [key: string]: { [index: string]: ICoreNode } } = {};
tileResult[h] = {};
tileResult[h]["0"] = fullNode;
imagesByH.next(tileResult);
imagesByH.complete();
const 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) => {
const geometryProvider: IGeometryProvider = new GeohashGeometryProvider();
const dataProvider: IDataProvider = new FalcorDataProvider(
{ clientToken: "cid" },
geometryProvider);
const api: API = new API(dataProvider);
const index: GeoRBush<any> = new GeoRBush(16);
const calculator: GraphCalculator = new GraphCalculator(null);
const h: string = "h";
spyOn(geometryProvider, "bboxToCellIds").and.returnValue([h]);
spyOn(geometryProvider, "latLonToCellIds").and.returnValue([h]);
spyOn(geometryProvider, "latLonToCellId").and.returnValue(h);
const imagesByH: Subject<{ [key: string]: { [index: string]: ICoreNode } }> =
new Subject<{ [key: string]: { [index: string]: ICoreNode } }>();
const imagesByHSpy: jasmine.Spy = spyOn(api, "imagesByH$");
imagesByHSpy.and.returnValue(imagesByH);
const key: string = "key";
const imageByKeyFill: Subject<{ [key: string]: IFillNode }> = new Subject<{ [key: string]: IFillNode }>();
const imageByKeyFillSpy: jasmine.Spy = spyOn(api, "imageByKeyFill$");
imageByKeyFillSpy.and.returnValue(imageByKeyFill);
const imageByKeyFull: Subject<{ [key: string]: IFillNode }> = new Subject<{ [key: string]: IFillNode }>();
const imageByKeyFullSpy: jasmine.Spy = spyOn(api, "imageByKeyFull$");
imageByKeyFullSpy.and.returnValue(imageByKeyFull);
const fullNode: IFullNode = helper.createFullNode();
fullNode.key = key;
fullNode.l.lat = 0.5;
fullNode.l.lon = 0.5;
const graph: Graph = new Graph(api, index, calculator);
graph.cacheFull$(fullNode.key).subscribe(() => { /*noop*/ });
const 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*/ });
const 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) => {
const geometryProvider: IGeometryProvider = new GeohashGeometryProvider();
const dataProvider: IDataProvider = new FalcorDataProvider(
{ clientToken: "cid" },
geometryProvider);
const api: API = new API(dataProvider);
const index: GeoRBush<any> = new GeoRBush(16);
const calculator: GraphCalculator = new GraphCalculator(null);
const h: string = "h";
spyOn(geometryProvider, "bboxToCellIds").and.returnValue([h]);
const imagesByH: Subject<{ [key: string]: { [index: string]: ICoreNode } }> =
new Subject<{ [key: string]: { [index: string]: ICoreNode } }>();
const imagesByHSpy: jasmine.Spy = spyOn(api, "imagesByH$");
imagesByHSpy.and.returnValue(imagesByH);
const key: string = "key";
const imageByKeyFill: Subject<{ [key: string]: IFillNode }> = new Subject<{ [key: string]: IFillNode }>();
spyOn(api, "imageByKeyFill$").and.returnValue(imageByKeyFill);
const fullNode: IFullNode = helper.createFullNode();
fullNode.key = key;
fullNode.l.lat = 0.5;
fullNode.l.lon = 0.5;
const graph: Graph = new Graph(api, 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();
});
const tileResult: { [key: string]: { [index: string]: ICoreNode } } = {};
tileResult[h] = {};
tileResult[h]["0"] = fullNode;
imagesByH.next(tileResult);
imagesByH.complete();
const 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", () => {
const api: API = new API(new FalcorDataProvider({ clientToken: "cid" }));
const index: GeoRBush<any> = new GeoRBush(16);
const calculator: GraphCalculator = new GraphCalculator(null);
const fullNode: IFullNode = helper.createFullNode();
const imageByKeyFull: Subject<{ [key: string]: IFullNode }> = new Subject<{ [key: string]: IFullNode }>();
spyOn(api, "imageByKeyFull$").and.returnValue(imageByKeyFull);
const graph: Graph = new Graph(api, 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) => {
const api: API = new API(new FalcorDataProvider({ clientToken: "cid" }));
const index: GeoRBush<any> = new GeoRBush(16);
const calculator: GraphCalculator = new GraphCalculator(null);
const imageByKeyFull: Subject<{ [key: string]: IFullNode }> = new Subject<{ [key: string]: IFullNode }>();
spyOn(api, "imageByKeyFull$").and.returnValue(imageByKeyFull);
const graph: Graph = new Graph(api, index, calculator);
const fullNode: IFullNode = helper.createFullNode();
const 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", () => {
const api: API = new API(new FalcorDataProvider({ clientToken: "cid" }));
const index: GeoRBush<any> = new GeoRBush(16);
const calculator: GraphCalculator = new GraphCalculator(null);
const fullNode: IFullNode = helper.createFullNode();
const imageByKeyFull: Subject<{ [key: string]: IFullNode }> = new Subject<{ [key: string]: IFullNode }>();
const imageByKeyFullSpy: jasmine.Spy = spyOn(api, "imageByKeyFull$");
imageByKeyFullSpy.and.returnValue(imageByKeyFull);
const graph: Graph = new Graph(api, 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", () => {
const api: API = new API(new FalcorDataProvider({ clientToken: "cid" }));
const index: GeoRBush<any> = new GeoRBush(16);
const calculator: GraphCalculator = new GraphCalculator(null);
const imageByKeyFull: Subject<{ [key: string]: IFullNode }> = new Subject<{ [key: string]: IFullNode }>();
spyOn(api, "imageByKeyFull$").and.returnValue(imageByKeyFull);
const graph: Graph = new Graph(api, index, calculator);
const fullNode: IFullNode = helper.createFullNode();
graph.cacheFull$(fullNode.key).subscribe(() => { /*noop*/ });
const 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) => {
const api: API = new API(new FalcorDataProvider({ clientToken: "cid" }));
const index: GeoRBush<any> = new GeoRBush(16);
const calculator: GraphCalculator = new GraphCalculator(null);
const imageByKeyFull: Subject<{ [key: string]: IFullNode }> = new Subject<{ [key: string]: IFullNode }>();
spyOn(api, "imageByKeyFull$").and.returnValue(imageByKeyFull);
const graph: Graph = new Graph(api, index, calculator);
const fullNode: IFullNode = helper.createFullNode();
fullNode.sequence_key = undefined;
graph.cacheFull$(fullNode.key)
.subscribe(
(g: Graph): void => { return; },
(e: GraphMapillaryError): void => {
done();
});
const 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", () => {
const geometryProvider: IGeometryProvider = new GeohashGeometryProvider();
const dataProvider: IDataProvider = new FalcorDataProvider(
{ clientToken: "cid" },
geometryProvider);
const api: API = new API(dataProvider);
const index: GeoRBush<any> = new GeoRBush(16);
const calculator: GraphCalculator = new GraphCalculator(null);
const key: string = "key";
const otherKey: string = "otherKey";
const imageByKeyFull: Subject<{ [key: string]: IFullNode }> = new Subject<{ [key: string]: IFullNode }>();
const imageByKeyFullOther: Subject<{ [key: string]: IFullNode }> = new Subject<{ [key: string]: IFullNode }>();
spyOn(api, "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.");
});
const h: string = "h";
spyOn(geometryProvider, "latLonToCellId").and.returnValue(h);
spyOn(geometryProvider, "latLonToCellIds").and.returnValue([h]);
const imagesByH: Subject<{ [key: string]: { [index: string]: ICoreNode } }> =
new Subject<{ [key: string]: { [index: string]: ICoreNode } }>();
spyOn(api, "imagesByH$").and.returnValue(imagesByH);
const graph: Graph = new Graph(api, index, calculator);
const otherNode: IFullNode = helper.createFullNode();
otherNode.key = otherKey;
graph.cacheFull$(otherNode.key).subscribe(() => { /*noop*/ });
const 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*/ });
const 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);
const 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);
const 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", () => {
const geometryProvider: IGeometryProvider = new GeohashGeometryProvider();
const dataProvider: IDataProvider = new FalcorDataProvider(
{ clientToken: "cid" },
geometryProvider);
const api: API = new API(dataProvider);
const index: GeoRBush<any> = new GeoRBush(16);
const calculator: GraphCalculator = new GraphCalculator(null);
const imageByKeyFull: Subject<{ [key: string]: IFullNode }> = new Subject<{ [key: string]: IFullNode }>();
spyOn(api, "imageByKeyFull$").and.returnValue(imageByKeyFull);
const h: string = "h";
spyOn(geometryProvider, "latLonToCellId").and.returnValue(h);
spyOn(geometryProvider, "latLonToCellIds").and.returnValue([h]);
const imagesByH: Subject<{ [key: string]: { [index: string]: ICoreNode } }> =
new Subject<{ [key: string]: { [index: string]: ICoreNode } }>();
spyOn(api, "imagesByH$").and.returnValue(imagesByH);
const imageByKeyFill: Subject<{ [key: string]: IFillNode }> = new Subject<{ [key: string]: IFillNode }>();
spyOn(api, "imageByKeyFill$").and.returnValue(imageByKeyFill);
const graph: Graph = new Graph(api, index, calculator);
const fullNode: IFullNode = helper.createFullNode();
graph.cacheFull$(fullNode.key).subscribe(() => { /*noop*/ });
const fetchResult: { [key: string]: IFullNode } = {};
fetchResult[fullNode.key] = fullNode;
imageByKeyFull.next(fetchResult);
graph.hasTiles(fullNode.key);
observableFrom(graph.cacheTiles$(fullNode.key)).pipe(
mergeAll())
.subscribe(() => { /*noop*/ });
const tileNode: ICoreNode = helper.createCoreNode();
tileNode.key = "tileNodeKey";
const 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", () => {
const geometryProvider: IGeometryProvider = new GeohashGeometryProvider();
const dataProvider: IDataProvider = new FalcorDataProvider(
{ clientToken: "cid" },
geometryProvider);
const api: API = new API(dataProvider);
const index: GeoRBush<any> = new GeoRBush(16);
const calculator: GraphCalculator = new GraphCalculator(null);
const imageByKeyFull: Subject<{ [key: string]: IFullNode }> = new Subject<{ [key: string]: IFullNode }>();
spyOn(api, "imageByKeyFull$").and.returnValue(imageByKeyFull);
const h: string = "h";
spyOn(geometryProvider, "latLonToCellId").and.returnValue(h);
spyOn(geometryProvider, "latLonToCellIds").and.returnValue([h]);
const imagesByH: Subject<{ [key: string]: { [index: string]: ICoreNode } }> =
new Subject<{ [key: string]: { [index: string]: ICoreNode } }>();
spyOn(api, "imagesByH$").and.returnValue(imagesByH);
const imageByKeyFill: Subject<{ [key: string]: IFillNode }> = new Subject<{ [key: string]: IFillNode }>();
spyOn(api, "imageByKeyFill$").and.returnValue(imageByKeyFill);
const graph: Graph = new Graph(api, index, calculator);
const fullNode: IFullNode = helper.createFullNode();
graph.cacheFull$(fullNode.key).subscribe(() => { /*noop*/ });
const fetchResult: { [key: string]: IFullNode } = {};
fetchResult[fullNode.key] = fullNode;
imageByKeyFull.next(fetchResult);
graph.hasTiles(fullNode.key);
observableFrom(graph.cacheTiles$(fullNode.key)).pipe(
mergeAll())
.subscribe(() => { /*noop*/ });
const tileNode: ICoreNode = helper.createCoreNode();
tileNode.key = "tileNodeKey";
const 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*/ });
const fillTileNode: IFillNode = helper.createFullNode();
const 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", () => {
const geometryProvider: IGeometryProvider = new GeohashGeometryProvider();
const dataProvider: IDataProvider = new FalcorDataProvider(
{ clientToken: "cid" },
geometryProvider);
const api: API = new API(dataProvider);
const index: GeoRBush<any> = new GeoRBush(16);
const calculator: GraphCalculator = new GraphCalculator(null);
const imageByKeyFull: Subject<{ [key: string]: IFullNode }> = new Subject<{ [key: string]: IFullNode }>();
spyOn(api, "imageByKeyFull$").and.returnValue(imageByKeyFull);
const h: string = "h";
spyOn(geometryProvider, "latLonToCellId").and.returnValue(h);
spyOn(geometryProvider, "latLonToCellIds").and.returnValue([h]);
const imagesByH: Subject<{ [key: string]: { [index: string]: ICoreNode } }> =
new Subject<{ [key: string]: { [index: string]: ICoreNode } }>();
spyOn(api, "imagesByH$").and.returnValue(imagesByH);
const imageByKeyFill: Subject<{ [key: string]: IFillNode }> = new Subject<{ [key: string]: IFillNode }>();
const imageByKeyFillSpy: jasmine.Spy = spyOn(api, "imageByKeyFill$");
imageByKeyFillSpy.and.returnValue(imageByKeyFill);
const graph: Graph = new Graph(api, index, calculator);
const fullNode: IFullNode = helper.createFullNode();
graph.cacheFull$(fullNode.key).subscribe(() => { /*noop*/ });
const fetchResult: { [key: string]: IFullNode } = {};
fetchResult[fullNode.key] = fullNode;
imageByKeyFull.next(fetchResult);
graph.hasTiles(fullNode.key);
observableFrom(graph.cacheTiles$(fullNode.key)).pipe(
mergeAll())
.subscribe(() => { /*noop*/ });
const tileNode: ICoreNode = helper.createCoreNode();
tileNode.key = "tileNodeKey";
const 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", () => {
const geometryProvider: IGeometryProvider = new GeohashGeometryProvider();
const dataProvider: IDataProvider = new FalcorDataProvider(
{ clientToken: "cid" },
geometryProvider);
const api: API = new API(dataProvider);
const index: GeoRBush<any> = new GeoRBush(16);
const calculator: GraphCalculator = new GraphCalculator(null);
const imageByKeyFull: Subject<{ [key: string]: IFullNode }> = new Subject<{ [key: string]: IFullNode }>();
spyOn(api, "imageByKeyFull$").and.returnValue(imageByKeyFull);
const h: string = "h";
spyOn(geometryProvider, "latLonToCellId").and.returnValue(h);
spyOn(geometryProvider, "latLonToCellIds").and.returnValue([h]);
const imagesByH: Subject<{ [key: string]: { [index: string]: ICoreNode } }> =
new Subject<{ [key: string]: { [index: string]: ICoreNode } }>();
spyOn(api, "imagesByH$").and.returnValue(imagesByH);
const graph: Graph = new Graph(api, index, calculator);
const 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", () => {
const api: API = new API(new FalcorDataProvider({ clientToken: "cid" }));
const index: GeoRBush<any> = new GeoRBush(16);
const calculator: GraphCalculator = new GraphCalculator(null);
const imageByKeyFill: Subject<{ [key: string]: IFillNode }> = new Subject<{ [key: string]: IFillNode }>();
spyOn(api, "imageByKeyFill$").and.returnValue(imageByKeyFill);
const graph: Graph = new Graph(api, index, calculator);
expect(() => { graph.cacheFill$("key"); }).toThrowError(Error);
});
it("should throw if already full", () => {
const api: API = new API(new FalcorDataProvider({ clientToken: "cid" }));
const index: GeoRBush<any> = new GeoRBush(16);
const calculator: GraphCalculator = new GraphCalculator(null);
const imageByKeyFull: Subject<{ [key: string]: IFullNode }> = new Subject<{ [key: string]: IFullNode }>();
spyOn(api, "imageByKeyFull$").and.returnValue(imageByKeyFull);
const imageByKeyFill: Subject<{ [key: string]: IFillNode }> = new Subject<{ [key: string]: IFillNode }>();
spyOn(api, "imageByKeyFill$").and.returnValue(imageByKeyFill);
const graph: Graph = new Graph(api, index, calculator);
const fullNode: IFullNode = helper.createFullNode();
graph.cacheFull$(fullNode.key);
const 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", () => {
const geometryProvider: IGeometryProvider = new GeohashGeometryProvider();
const dataProvider: IDataProvider = new FalcorDataProvider(
{ clientToken: "cid" },
geometryProvider);
const api: API = new API(dataProvider);
const index: GeoRBush<any> = new GeoRBush(16);
const calculator: GraphCalculator = new GraphCalculator(null);
const node: Node = helper.createNode();
spyOn(geometryProvider, "latLonToCellIds").and.returnValue(["h"]);
const imagesByH: Subject<{ [key: string]: { [index: string]: ICoreNode } }> =
new Subject<{ [key: string]: { [index: string]: ICoreNode } }>();
spyOn(api, "imagesByH$").and.returnValue(imagesByH);
const graph: Graph = new Graph(api, 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", () => {
const geometryProvider: IGeometryProvider = new GeohashGeometryProvider();
const dataProvider: IDataProvider = new FalcorDataProvider(
{ clientToken: "cid" },
geometryProvider);
const api: API = new API(dataProvider);
const index: GeoRBush<any> = new GeoRBush(16);
const calculator: GraphCalculator = new GraphCalculator(null);
const fullNode: IFullNode = helper.createFullNode();
const h: string = "h";
spyOn(geometryProvider, "latLonToCellId").and.returnValue(h);
spyOn(geometryProvider, "latLonToCellIds").and.returnValue([h]);
const imageByKeyResult: { [key: string]: IFullNode } = {};
imageByKeyResult[fullNode.key] = fullNode;
const imageByKeyFull: Observable<{ [key: string]: IFullNode }> = observableOf<{ [key: string]: IFullNode }>(imageByKeyResult);
spyOn(api, "imageByKeyFull$").and.returnValue(imageByKeyFull);
const imagesByH: Subject<{ [key: string]: { [index: string]: ICoreNode } }> =
new Subject<{ [key: string]: { [index: string]: ICoreNode } }>();
spyOn(api, "imagesByH$").and.returnValue(imagesByH);
const graph: Graph = new Graph(api, 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*/ });
const 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", () => {
const geometryProvider: IGeometryProvider = new GeohashGeometryProvider();
const dataProvider: IDataProvider = new FalcorDataProvider(
{ clientToken: "cid" },
geometryProvider);
const api: API = new API(dataProvider);
const index: GeoRBush<any> = new GeoRBush(16);
const calculator: GraphCalculator = new GraphCalculator(null);
const node: Node = helper.createNode();
const h: string = "h";
const encodeHsSpy: jasmine.Spy = spyOn(geometryProvider, "latLonToCellIds");
encodeHsSpy.and.returnValue([h]);
const imagesByH: Subject<{ [key: string]: { [index: string]: ICoreNode } }> =
new Subject<{ [key: string]: { [index: string]: ICoreNode } }>();
spyOn(api, "imagesByH$").and.returnValue(imagesByH);
const graph: Graph = new Graph(api, 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", () => {
const geometryProvider: IGeometryProvider = new GeohashGeometryProvider();
const dataProvider: IDataProvider = new FalcorDataProvider(
{ clientToken: "cid" },
geometryProvider);
const api: API = new API(dataProvider);
const index: GeoRBush<any> = new GeoRBush(16);
const calculator: GraphCalculator = new GraphCalculator(null);
const fullNode: IFullNode = helper.createFullNode();
const h: string = "h";
spyOn(geometryProvider, "latLonToCellId").and.returnValue(h);
const encodeHsSpy: jasmine.Spy = spyOn(geometryProvider, "latLonToCellIds");
encodeHsSpy.and.returnValue([h]);
const imageByKeyResult: { [key: string]: IFullNode } = {};
imageByKeyResult[fullNode.key] = fullNode;
const imageByKeyFull: Observable<{ [key: string]: IFullNode }> = observableOf<{ [key: string]: IFullNode }>(imageByKeyResult);
spyOn(api, "imageByKeyFull$").and.returnValue(imageByKeyFull);
const imagesByH: Subject<{ [key: string]: { [index: string]: ICoreNode } }> =
new Subject<{ [key: string]: { [index: string]: ICoreNode } }>();
spyOn(api, "imagesByH$").and.returnValue(imagesByH);
const graph: Graph = new Graph(api, index, calculator);
graph.cacheFull$(fullNode.key).subscribe(() => { /*noop*/ });
expect(graph.hasTiles(fullNode.key)).toBe(false);
observableFrom(graph.cacheTiles$(fullNode.key)).pipe(
mergeAll())
.subscribe(() => { /*noop*/ });
const 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 api: API = new API(new FalcorDataProvider({ clientToken: "cid" }));
const index: GeoRBush<any> = new GeoRBush(16);
const graphCalculator: GraphCalculator = new GraphCalculator(null);
const edgeCalculator: EdgeCalculator = new EdgeCalculator();
const graph: Graph = new Graph(api, index, graphCalculator, edgeCalculator);
expect(() => { graph.cacheSequenceNodes$("sequenceKey"); }).toThrowError(Error);
});
it("should not be cached", () => {
const api: API = new API(new FalcorDataProvider({ clientToken: "cid" }));
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(api, "sequenceByKey$").and.returnValue(sequenceByKey);
const imageByKey: Subject<{ [key: string]: IFullNode }> = new Subject<{ [key: string]: IFullNode }>();
spyOn(api, "imageByKeyFull$").and.returnValue(imageByKey);
const graph: Graph = new Graph(api, 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 api: API = new API(new FalcorDataProvider({ clientToken: "cid" }));
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(api, "sequenceByKey$").and.returnValue(sequenceByKey);
const imageByKey: Subject<{ [key: string]: IFullNode }> = new Subject<{ [key: string]: IFullNode }>();
spyOn(api, "imageByKeyFull$").and.returnValue(imageByKey);
const graph: Graph = new Graph(api, 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 api: API = new API(new FalcorDataProvider({ clientToken: "cid" }));
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(api, "sequenceByKey$").and.returnValue(sequenceByKey);
const imageByKey: Subject<{ [key: string]: IFullNode }> = new Subject<{ [key: string]: IFullNode }>();
spyOn(api, "imageByKeyFull$").and.returnValue(imageByKey);
const graph: Graph = new Graph(api, 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 geometryProvider: IGeometryProvider = new GeohashGeometryProvider();
const dataProvider: IDataProvider = new FalcorDataProvider(
{ clientToken: "cid" },
geometryProvider);
const api: API = new API(dataProvider);
const index: GeoRBush<any> = new GeoRBush(16);
const graphCalculator: GraphCalculator = new GraphCalculator(null);
spyOn(geometryProvider, "latLonToCellId").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(api, "sequenceByKey$").and.returnValue(sequenceByKey);
const imageByKey: Subject<{ [key: string]: IFullNode }> = new Subject<{ [key: string]: IFullNode }>();
spyOn(api, "imageByKeyFull$").and.returnValue(imageByKey);
const graph: Graph = new Graph(api, 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 geometryProvider: IGeometryProvider = new GeohashGeometryProvider();
const dataProvider: IDataProvider = new FalcorDataProvider(
{ clientToken: "cid" },
geometryProvider);
const api: API = new API(dataProvider);
const index: GeoRBush<any> = new GeoRBush(16);
const graphCalculator: GraphCalculator = new GraphCalculator(null);
spyOn(geometryProvider, "latLonToCellId").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(api, "sequenceByKey$").and.returnValue(sequenceByKey);
const imageByKey: Subject<{ [key: string]: IFullNode }> = new Subject<{ [key: string]: IFullNode }>();
spyOn(api, "imageByKeyFull$").and.returnValue(imageByKey);
const graph: Graph = new Graph(api, 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 geometryProvider: IGeometryProvider = new GeohashGeometryProvider();
const dataProvider: IDataProvider = new FalcorDataProvider(
{ clientToken: "cid" },
geometryProvider);
const api: API = new API(dataProvider);
const index: GeoRBush<any> = new GeoRBush(16);
const graphCalculator: GraphCalculator = new GraphCalculator(null);
spyOn(geometryProvider, "latLonToCellId").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(api, "sequenceByKey$").and.returnValue(sequenceByKey);
const imageByKey: Subject<{ [key: string]: IFullNode }> = new Subject<{ [key: string]: IFullNode }>();
spyOn(api, "imageByKeyFull$").and.returnValue(imageByKey);
const graph: Graph = new Graph(api, 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 geometryProvider: IGeometryProvider = new GeohashGeometryProvider();
const dataProvider: IDataProvider = new FalcorDataProvider(
{ clientToken: "cid" },
geometryProvider);
const api: API = new API(dataProvider);
const index: GeoRBush<any> = new GeoRBush(16);
const graphCalculator: GraphCalculator = new GraphCalculator(null);
spyOn(geometryProvider, "latLonToCellId").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(api, "sequenceByKey$").and.returnValue(sequenceByKey);
const imageByKey: Subject<{ [key: string]: IFullNode }> = new Subject<{ [key: string]: IFullNode }>();
spyOn(api, "imageByKeyFull$").and.returnValue(imageByKey);
const graph: Graph = new Graph(api, 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 geometryProvider: IGeometryProvider = new GeohashGeometryProvider();
const dataProvider: IDataProvider = new FalcorDataProvider(
{ clientToken: "cid" },
geometryProvider);
const api: API = new API(dataProvider);
const index: GeoRBush<any> = new GeoRBush(16);
const graphCalculator: GraphCalculator = new GraphCalculator(null);
const h: string = "h";
spyOn(geometryProvider, "latLonToCellId").and.returnValue(h);
spyOn(geometryProvider, "latLonToCellIds").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(api, "sequenceByKey$").and.returnValue(sequenceByKey);
const imageByKey: Subject<{ [key: string]: IFullNode }> = new Subject<{ [key: string]: IFullNode }>();
spyOn(api, "imageByKeyFull$").and.returnValue(imageByKey);
const graph: Graph = new Graph(api, 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$(sequ