Skip to content

Commit

Permalink
Support time window query (#6872)
Browse files Browse the repository at this point in the history
* Add time interval traits.

* refactor

* refactor

* Add unit tests.

* Refactor

* Refactor. Not to specify current time in model dimensions.

* Update doc.

* Update doc.

* Update based on review.

* Refactor based on review.

* Simplified based on review.

* Remove unwanted import.

* Update doc.

* Refactor

* Remove unwanted import.

* Rename trait.

* Minor refactoring.

* Refactor based on review.

* Refactor to make use of @compute.
  • Loading branch information
mwu2018 authored Sep 14, 2023
1 parent 6abb083 commit 881b779
Show file tree
Hide file tree
Showing 4 changed files with 256 additions and 13 deletions.
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#### next release (8.3.4)

- Add `timeWindowDuration`, `timeWindowUnit` and `isForwardTimeWindow` traits to esri-mapServer type to support time window query.
- Move map credits to map column so it don't get hidden by chart panel
- TSify `MapColumn` module and reorganize components directory structure.
- Add null check to `WebMapServiceCatalogItem` `rectangle` calculation - and now we ascend tree of WMS `Layers` until we find a rectangle.
Expand Down
101 changes: 89 additions & 12 deletions lib/Models/Catalog/Esri/ArcGisMapServerCatalogItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import getToken from "../../getToken";
import proxyCatalogItemUrl from "../proxyCatalogItemUrl";
import MinMaxLevelMixin from "./../../../ModelMixins/MinMaxLevelMixin";
import { Extent, Layer, MapServer } from "./ArcGisInterfaces";
import moment from "moment";

const proj4 = require("proj4").default;

Expand Down Expand Up @@ -328,6 +329,13 @@ class MapServerStratum extends LoadableStratum(

StratumOrder.addLoadStratum(MapServerStratum.stratumName);

interface TimeParams {
currentTime: number | undefined;
timeWindowDuration: number | undefined;
timeWindowUnit: string | undefined;
isForwardTimeWindow: boolean;
}

export default class ArcGisMapServerCatalogItem extends UrlMixin(
DiscretelyTimeVaryingMixin(
MinMaxLevelMixin(
Expand Down Expand Up @@ -393,14 +401,33 @@ export default class ArcGisMapServerCatalogItem extends UrlMixin(
return result;
}

@computed
private get _currentImageryParts(): ImageryParts | undefined {
const dateAsUnix: string | undefined =
private getCurrentTime() {
const dateAsUnix: number | undefined =
this.currentDiscreteTimeTag === undefined
? undefined
: new Date(this.currentDiscreteTimeTag).getTime().toString();
: new Date(this.currentDiscreteTimeTag).getTime();
return dateAsUnix;
}

@computed
private get timeParams(): TimeParams {
const currentTime = this.getCurrentTime();
const timeWindowDuration = this.timeWindowDuration;
const timeWindowUnit = this.timeWindowUnit;
const isForwardTimeWindow = this.isForwardTimeWindow;
const timeParams = {
currentTime,
timeWindowDuration,
timeWindowUnit,
isForwardTimeWindow
} as TimeParams;
return timeParams;
}

const imageryProvider = this._createImageryProvider(dateAsUnix);
@computed
private get _currentImageryParts(): ImageryParts | undefined {
const timeParams = this.timeParams;
const imageryProvider = this._createImageryProvider(timeParams);
if (imageryProvider === undefined) {
return undefined;
}
Expand All @@ -419,10 +446,8 @@ export default class ArcGisMapServerCatalogItem extends UrlMixin(
!this.isPaused &&
this.nextDiscreteTimeTag
) {
const dateAsUnix: number = new Date(this.nextDiscreteTimeTag).getTime();
const imageryProvider = this._createImageryProvider(
dateAsUnix.toString()
);
const timeParams = this.timeParams;
const imageryProvider = this._createImageryProvider(timeParams);
if (imageryProvider === undefined) {
return undefined;
}
Expand All @@ -442,8 +467,46 @@ export default class ArcGisMapServerCatalogItem extends UrlMixin(
}
}

private windowDurationInMs(
rawTimeWindowDuration: number | undefined,
timeWindowUnit: string | undefined
): number | undefined {
if (
rawTimeWindowDuration === undefined ||
rawTimeWindowDuration === 0 ||
timeWindowUnit === undefined
) {
return undefined;
}

const rawTimeWindowData: any = {};
rawTimeWindowData[timeWindowUnit] = rawTimeWindowDuration;
const duration = moment.duration(rawTimeWindowData).asMilliseconds();
if (duration === 0) {
return undefined;
} else {
return duration;
}
}

private getTimeWindowQueryString(
currentTime: number,
duration: number,
isForward: boolean = true
) {
if (isForward) {
const toTime = Number(currentTime) + duration;
return currentTime + "," + toTime;
} else {
const fromTime = Number(currentTime) - duration;
return "" + fromTime + "," + currentTime;
}
}

private _createImageryProvider = createTransformerAllowUndefined(
(time: string | undefined): ArcGisMapServerImageryProvider | undefined => {
(
timeParams: TimeParams | undefined
): ArcGisMapServerImageryProvider | undefined => {
const stratum = <MapServerStratum>(
this.strata.get(MapServerStratum.stratumName)
);
Expand All @@ -453,8 +516,22 @@ export default class ArcGisMapServerCatalogItem extends UrlMixin(
}

const params: any = Object.assign({}, this.parameters);
if (time !== undefined) {
params.time = time;
const currentTime = timeParams?.currentTime;
if (currentTime !== undefined) {
const windowDuration = this.windowDurationInMs(
timeParams?.timeWindowDuration,
timeParams?.timeWindowUnit
);

if (windowDuration !== undefined) {
params.time = this.getTimeWindowQueryString(
currentTime,
windowDuration,
timeParams?.isForwardTimeWindow
);
} else {
params.time = currentTime;
}
}

const maximumLevel = scaleDenominatorToLevel(
Expand Down
24 changes: 24 additions & 0 deletions lib/Traits/TraitsClasses/ArcGisMapServerCatalogItemTraits.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,28 @@ export default class ArcGisMapServerCatalogItemTraits extends mixTraits(
"date range when layer in time-enabled."
})
maxRefreshIntervals: number = 1000;

@primitiveTrait({
name: "Time Window Duration",
description:
"Specify a time window duration when querying a time-enabled layer. Will not query with time window for non-positive value",
type: "number"
})
timeWindowDuration?: number;

@primitiveTrait({
name: "Time Window Unit",
description:
"The time window unit for the `Time Window Duration`. Any units supported by `moment` module are valid, such as, `year`, `month`, `week`, `day`, `hour`, etc. Will not query time with window if the unit is invalid or undefined.",
type: "string"
})
timeWindowUnit?: string;

@primitiveTrait({
name: "Is Forward Time Window",
description:
"If true, the time window is forward from the current time. Otherwise backward. Default to forward window.",
type: "boolean"
})
isForwardTimeWindow: boolean = true;
}
143 changes: 142 additions & 1 deletion test/Models/Catalog/esri/ArcGisMapServerCatalogItemSpec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -451,7 +451,7 @@ describe("ArcGisMapServerCatalogItem", function () {
});

describe("time-enabled layer", function () {
it("can load a time-enabled layer", async function () {
it("can load a layer, querying time without window", async function () {
runInAction(() => {
item = new ArcGisMapServerCatalogItem("test", new Terria());
item.setTrait(
Expand All @@ -466,6 +466,147 @@ describe("ArcGisMapServerCatalogItem", function () {
}
expect(item.startTime).toBe("2004-11-26T09:43:22.000000000Z");
expect(item.stopTime).toBe("2019-11-03T14:00:00.000000000Z");
const expectedTimeQueryString = 1572789600000; // from json file
const imageryProvider = item.mapItems[0]
.imageryProvider as ArcGisMapServerImageryProvider;
expect(imageryProvider.parameters.time).toBe(expectedTimeQueryString);
});

it("can load a layer, querying time without window if timeWindowDuration is not defined", async function () {
runInAction(() => {
item = new ArcGisMapServerCatalogItem("test", new Terria());
item.setTrait(
CommonStrata.definition,
"url",
"http://example.com/cadastre_history/MapServer"
);
item.setTrait(CommonStrata.user, "timeWindowUnit", "year");
});
const defaultCurrentTime = 1572789600000; // from json file
await item.loadMapItems();
const expectedTimeQueryString = defaultCurrentTime;
const imageryProvider = item.mapItems[0]
.imageryProvider as ArcGisMapServerImageryProvider;
expect(imageryProvider.parameters.time).toBe(expectedTimeQueryString);
});

it("can load a layer, querying time without window if timeWindowUnit is not defined", async function () {
runInAction(() => {
item = new ArcGisMapServerCatalogItem("test", new Terria());
item.setTrait(
CommonStrata.definition,
"url",
"http://example.com/cadastre_history/MapServer"
);
item.setTrait(CommonStrata.user, "timeWindowDuration", 2);
});
const defaultCurrentTime = 1572789600000; // from json file
await item.loadMapItems();
const expectedTimeQueryString = defaultCurrentTime;
const imageryProvider = item.mapItems[0]
.imageryProvider as ArcGisMapServerImageryProvider;
expect(imageryProvider.parameters.time).toBe(expectedTimeQueryString);
});

it("can load a layer, querying time with default forward window", async function () {
runInAction(() => {
item = new ArcGisMapServerCatalogItem("test", new Terria());
item.setTrait(
CommonStrata.definition,
"url",
"http://example.com/cadastre_history/MapServer"
);
item.setTrait(CommonStrata.user, "timeWindowDuration", 2);
item.setTrait(CommonStrata.user, "timeWindowUnit", "week");
});
const defaultCurrentTime = 1572789600000; // from json file
const twoWeekTime = 14 * 24 * 3600 * 1000;
const toTime = defaultCurrentTime + twoWeekTime;
await item.loadMapItems();
const expectedTimeQueryString = `${defaultCurrentTime},${toTime}`;
const imageryProvider = item.mapItems[0]
.imageryProvider as ArcGisMapServerImageryProvider;
expect(imageryProvider.parameters.time).toBe(expectedTimeQueryString);
});

it("can load a layer, querying time with explicit forward window", async function () {
runInAction(() => {
item = new ArcGisMapServerCatalogItem("test", new Terria());
item.setTrait(
CommonStrata.definition,
"url",
"http://example.com/cadastre_history/MapServer"
);
item.setTrait(CommonStrata.user, "timeWindowDuration", 2);
item.setTrait(CommonStrata.user, "timeWindowUnit", "week");
item.setTrait(CommonStrata.user, "isForwardTimeWindow", true);
});
const defaultCurrentTime = 1572789600000; // from json file
const twoWeekTime = 14 * 24 * 3600 * 1000;
const toTime = defaultCurrentTime + twoWeekTime;
await item.loadMapItems();
const expectedTimeQueryString = `${defaultCurrentTime},${toTime}`;
const imageryProvider = item.mapItems[0]
.imageryProvider as ArcGisMapServerImageryProvider;
expect(imageryProvider.parameters.time).toBe(expectedTimeQueryString);
});

it("can load a layer, querying time with backward time window", async function () {
runInAction(() => {
item = new ArcGisMapServerCatalogItem("test", new Terria());
item.setTrait(
CommonStrata.definition,
"url",
"http://example.com/cadastre_history/MapServer"
);
item.setTrait(CommonStrata.user, "timeWindowDuration", 2);
item.setTrait(CommonStrata.user, "timeWindowUnit", "week");
item.setTrait(CommonStrata.user, "isForwardTimeWindow", false);
});
const defaultCurrentTime = 1572789600000; // from json file
const twoWeekTime = 14 * 24 * 3600 * 1000;
const fromTime = defaultCurrentTime - twoWeekTime;
await item.loadMapItems();
const expectedTimeQueryString = `${fromTime},${defaultCurrentTime}`;
const imageryProvider = item.mapItems[0]
.imageryProvider as ArcGisMapServerImageryProvider;
expect(imageryProvider.parameters.time).toBe(expectedTimeQueryString);
});

it("can load a layer, querying time without window if timeWindowDuration is 0", async function () {
runInAction(() => {
item = new ArcGisMapServerCatalogItem("test", new Terria());
item.setTrait(
CommonStrata.definition,
"url",
"http://example.com/cadastre_history/MapServer"
);
item.setTrait(CommonStrata.user, "timeWindowDuration", 0);
item.setTrait(CommonStrata.user, "timeWindowUnit", "year");
});
const defaultCurrentTime = 1572789600000; // from json file
await item.loadMapItems();
const imageryProvider = item.mapItems[0]
.imageryProvider as ArcGisMapServerImageryProvider;
expect(imageryProvider.parameters.time).toBe(defaultCurrentTime);
});

it("can load a layer, querying time without window if timeWindowUnit is invalid", async function () {
runInAction(() => {
item = new ArcGisMapServerCatalogItem("test", new Terria());
item.setTrait(
CommonStrata.definition,
"url",
"http://example.com/cadastre_history/MapServer"
);
item.setTrait(CommonStrata.user, "timeWindowDuration", 2);
item.setTrait(CommonStrata.user, "timeWindowUnit", "fortnight");
});
const defaultCurrentTime = 1572789600000; // from json file
await item.loadMapItems();
const imageryProvider = item.mapItems[0]
.imageryProvider as ArcGisMapServerImageryProvider;
expect(imageryProvider.parameters.time).toBe(defaultCurrentTime);
});
});

Expand Down

0 comments on commit 881b779

Please sign in to comment.