Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 64 additions & 3 deletions @here/olp-sdk-dataservice-read/lib/client/VersionedLayerClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import {
} from "@here/olp-sdk-core";
import { BlobApi, MetadataApi, QueryApi } from "@here/olp-sdk-dataservice-api";
import { MetadataCacheRepository } from "../cache/MetadataCacheRepository";
import { getTile } from "../utils/getTile";
import { getTile, TileResponse } from "../utils/getTile";
import { DataRequest } from "./DataRequest";
import { PartitionsRequest } from "./PartitionsRequest";
import { QuadKeyPartitionsRequest } from "./QuadKeyPartitionsRequest";
Expand Down Expand Up @@ -102,7 +102,65 @@ export class VersionedLayerClient {
*
* @return Tile data (if it exists) or the nearest parent tile data
*/
async getAggregatedData(request: TileRequest, abortSignal?: AbortSignal) {
getAggregatedData(
request: TileRequest,
abortSignal?: AbortSignal
): Promise<Response>;

/**
* @brief Fetches data of a tile or its closest ancestor.
* Use this API for tile-tree structures where children tile data is aggregated and stored in parent tiles.
*
* @param request The `TileRequest` instance that contains a complete set
* of request parameters.
* @param abortSignal A signal object that allows you to communicate with a request (such as the `fetch` request)
* and, if required, abort it using the `AbortController` object.
* @param options An optional object that can include the `includeTileKey` flag to indicate whether the parent tile key should be included in the response.
*
* For more information, see the [`AbortController` documentation](https://developer.mozilla.org/en-US/docs/Web/API/AbortController).
*
* @return Tile data (if it exists) or the nearest parent tile data including the parent tile key
*/
getAggregatedData(
request: TileRequest,
abortSignal: AbortSignal | undefined,
options: { includeTileKey: true }
): Promise<TileResponse>;

/**
* @brief Fetches data of a tile or its closest ancestor.
*
* Same as the two-argument `getAggregatedData`, for callers that pass an
* options object with `includeTileKey` disabled or omitted.
*
* @return Tile data (if it exists) or the nearest parent tile data
*/
getAggregatedData(
request: TileRequest,
abortSignal: AbortSignal | undefined,
options: { includeTileKey?: false }
): Promise<Response>;

/**
* @brief Fetches data of a tile or its closest ancestor.
*
* Same as the two-argument `getAggregatedData`, for an `includeTileKey`
* flag whose value is only known at run time. Narrow the result before
* using it.
*
* @return A `TileResponse` if the flag was `true`, otherwise the tile data
*/
getAggregatedData(
request: TileRequest,
abortSignal: AbortSignal | undefined,
options: { includeTileKey?: boolean }
): Promise<Response | TileResponse>;

async getAggregatedData(
request: TileRequest,
abortSignal?: AbortSignal,
options?: { includeTileKey?: boolean }
): Promise<Response | TileResponse> {
let catalogVersion = this.version;

if (catalogVersion === undefined) {
Expand All @@ -119,7 +177,10 @@ export class VersionedLayerClient {
catalogVersion
};

return getTile(request, params, abortSignal);
const tileResponse = await getTile(request, params, abortSignal, {
includeTileKey: true
});
return options?.includeTileKey ? tileResponse : tileResponse.response;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,10 @@ export class VolatileLayerClient {
*
* @return Tile data (if it exists) or the nearest parent tile data
*/
async getAggregatedData(request: TileRequest, abortSignal?: AbortSignal) {
async getAggregatedData(
request: TileRequest,
abortSignal?: AbortSignal
): Promise<Response> {
const params: TileRequestParams = {
catalogHrn: HRN.fromString(this.hrn),
layerId: this.layerId,
Expand Down
207 changes: 153 additions & 54 deletions @here/olp-sdk-dataservice-read/lib/utils/getTile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@ import {
ApiName
} from "@here/olp-sdk-core";
import { BlobApi } from "@here/olp-sdk-dataservice-api";
import { Index as QuadTreeIndex } from "@here/olp-sdk-dataservice-api/lib/query-api";
import {
ParentQuad,
Index as QuadTreeIndex
} from "@here/olp-sdk-dataservice-api/lib/query-api";
import { QuadTreeIndexCacheRepository } from "../cache/QuadTreeIndexCacheRepository";
import {
QuadTreeIndexDepth,
Expand All @@ -44,6 +47,11 @@ export interface GetTileParams {
layerType: "versioned" | "volatile";
}

export interface TileResponse {
response: Response;
parentTileKey: TileKey | undefined;
}

/**
* Fetches asynchronously data from a tile or from its nearest ancestor if not found.
*
Expand Down Expand Up @@ -78,11 +86,73 @@ export interface GetTileParams {
*
* @returns The blob of the requested tile or the blob of the closest parent Tile.
*/
export function getTile(
request: TileRequest,
params: TileRequestParams,
abortSignal?: AbortSignal
): Promise<Response>;

/**
* Same as the three-argument [[getTile]], but also resolves the tile key of
* the parent whose data was actually returned.
*
* @param options Pass `{ includeTileKey: true }` to receive a [[TileResponse]]
* instead of a plain `Response`.
*
* @returns The blob of the requested tile or the blob of the closest parent
* tile, together with that tile's key.
*/
export function getTile(
request: TileRequest,
params: TileRequestParams,
abortSignal: AbortSignal | undefined,
options: { includeTileKey: true }
): Promise<TileResponse>;

/**
* Same as the three-argument [[getTile]], for callers that pass an options
* object with `includeTileKey` disabled or omitted.
*
* @returns The blob of the requested tile or the blob of the closest parent Tile.
*/
export function getTile(
request: TileRequest,
params: TileRequestParams,
abortSignal: AbortSignal | undefined,
options: { includeTileKey?: false }
): Promise<Response>;

/**
* Same as the three-argument [[getTile]], for an `includeTileKey` flag whose
* value is only known at run time. Narrow the result before using it.
*
* @returns A [[TileResponse]] if the flag was `true`, otherwise the plain blob.
*/
export function getTile(
request: TileRequest,
params: TileRequestParams,
abortSignal: AbortSignal | undefined,
options: { includeTileKey?: boolean }
): Promise<Response | TileResponse>;

export async function getTile(
request: TileRequest,
params: TileRequestParams,
abortSignal?: AbortSignal,
options?: { includeTileKey?: boolean }
): Promise<Response | TileResponse> {
const tileResponse = await fetchTile(request, params, abortSignal);
return options?.includeTileKey ? tileResponse : tileResponse.response;
}

/**
* @hidden
*/
async function fetchTile(
request: TileRequest,
params: TileRequestParams,
abortSignal?: AbortSignal
): Promise<Response> {
): Promise<TileResponse> {
let catalogVersion: number | undefined;

const quadKey = request.getTileKey();
Expand All @@ -105,91 +175,119 @@ export async function getTile(
abortSignal
);

const delta = 4;
const depth = 4;
const requestedTileKey = TileKey.fromRowColumnLevel(
quadKey.row,
quadKey.column,
quadKey.level
);
const quadTreeIndexRoot = requestedTileKey.changedLevelBy(-depth);

let quadTreeIndex = null;
let quadTreeIndex: QuadTreeIndex | undefined;

if (request.getFetchOption() !== FetchOptions.OnlineOnly) {
const cache = new QuadTreeIndexCacheRepository(params.settings.cache);

for (let i = 1; i <= delta; i++) {
const parentInCache = requestedTileKey.changedLevelBy(-i);
const cachedTree = cache.get({
hrn: params.catalogHrn.toString(),
layerId: params.layerId,
depth: delta,
root: parentInCache,
version: catalogVersion
});

if (cachedTree) {
quadTreeIndex = cachedTree;
}
}
quadTreeIndex = cache.get({
hrn: params.catalogHrn.toString(),
layerId: params.layerId,
depth,
root: quadTreeIndexRoot,
version: catalogVersion
});
}

const parentTileKey = requestedTileKey.changedLevelBy(-delta);

if (!quadTreeIndex) {
quadTreeIndex = await fetchQuadTreeIndex({
...params,
catalogVersion,
depth: delta,
depth,
fetchOptions: request.getFetchOption(),
tileKey: parentTileKey,
tileKey: quadTreeIndexRoot,
abortSignal,
billingTag: request.getBillingTag()
});
}

if (!quadTreeIndex.subQuads || !quadTreeIndex.subQuads.length) {
return Promise.resolve(
new Response(null, {
const subQuads = quadTreeIndex.subQuads ?? [];
const parentQuads = quadTreeIndex.parentQuads ?? [];

if (!subQuads.length && !parentQuads.length) {
return Promise.resolve({
response: new Response(null, {
status: 204,
statusText: "No Content"
})
}),
parentTileKey: undefined
});
}

const closestParent = findClosestParent(
requestedTileKey,
quadTreeIndex,
quadTreeIndexRoot
);
if (!closestParent) {
return Promise.reject(
new Error(
`Error getting blob for Tile: ${requestedTileKey.toHereTile()}`
)
);
}

// Return the data for the requested QuadKey or for the closest parent
const subQuads = quadTreeIndex.subQuads;
return {
response: await BlobApi.getBlob(blobRequestBuilder, {
dataHandle: closestParent.parentQuad.dataHandle,
layerId: params.layerId,
billingTag: request.getBillingTag()
}),
parentTileKey: closestParent.tileKey
};
}

let currentTileKey = requestedTileKey;
let currentDelta = delta;
for (
let level = currentTileKey.level;
level >= parentTileKey.level;
--level
) {
const metadata = subQuads.find(
(item) =>
item.subQuadKey === currentTileKey.getSubHereTile(currentDelta)
/**
* Helper function to get the closest parent quad for a given tile key.
* @hidden
*/
function findClosestParent(
tileKey: TileKey,
quadTreeIndex: QuadTreeIndex,
quadTreeIndexRoot: TileKey
) {
const subQuads = quadTreeIndex.subQuads ?? [];
const parentQuads = quadTreeIndex.parentQuads ?? [];

// First, iterate through the sub quad trees, starting from lowest level up to the quad tree index root
let currentTileKey = tileKey;
while (currentTileKey.level >= quadTreeIndexRoot.level) {
const currentDelta = currentTileKey.level - quadTreeIndexRoot.level;
const subHereTile = currentTileKey.getSubHereTile(currentDelta);
const subQuad = subQuads.find(
(item) => item.subQuadKey === subHereTile
);

if (metadata) {
return BlobApi.getBlob(blobRequestBuilder, {
dataHandle: metadata.dataHandle,
layerId: params.layerId,
billingTag: request.getBillingTag()
});
if (subQuad) {
return { parentQuad: subQuad, tileKey: currentTileKey };
}

try {
currentTileKey = currentTileKey.parent();
currentDelta = parentTileKey.level - currentTileKey.level;
} catch (error) {
continue;
// Stop before requesting the parent of the quad tree index root -
// that root can be the global root tile (level 0), whose parent()
// throws rather than returning another tile.
if (currentTileKey.level === quadTreeIndexRoot.level) {
break;
}

currentTileKey = currentTileKey.parent();
}

return Promise.reject(
new Error(`Error getting blob for Tile: ${JSON.stringify(quadKey)}`)
);
// not found? Find the closest parent quad
return parentQuads.reduce<
{ parentQuad: ParentQuad; tileKey: TileKey } | undefined
>((closest, parentQuad) => {
const tileKey = TileKey.fromHereTile(parentQuad.partition);
return !closest || tileKey.level > closest.tileKey.level
? { parentQuad, tileKey }
: closest;
}, undefined);
}

/**
Expand Down Expand Up @@ -229,7 +327,8 @@ export async function fetchQuadTreeIndex(

if (
params.fetchOptions !== FetchOptions.OnlineOnly &&
quadTreeIndex.subQuads
(quadTreeIndex.subQuads !== undefined ||
quadTreeIndex.parentQuads !== undefined)
) {
cache.put({
hrn: params.catalogHrn.toString(),
Expand Down
2 changes: 1 addition & 1 deletion @here/olp-sdk-dataservice-read/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@here/olp-sdk-dataservice-read",
"version": "3.0.0",
"version": "3.1.0",
"description": "Wrapper around a subset of the HERE Open Location Platform Data REST API related to reading data from OLP catalogs",
"main": "index.js",
"browser": "index.web.js",
Expand Down
Loading
Loading