Skip to content

Commit

Permalink
- Handle errors
Browse files Browse the repository at this point in the history
- log to analytics
- handle empty result
- add unit test
  • Loading branch information
ljowen committed Nov 14, 2023
1 parent c305744 commit 9a6af32
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 24 deletions.
2 changes: 1 addition & 1 deletion CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
- See [ASGS 2021](https://www.abs.gov.au/statistics/standards/australian-statistical-geography-standard-asgs-edition-3/jul2021-jun2026/access-and-downloads/digital-boundary-files)
- Added [Melbourne CLUE blocks](https://data.melbourne.vic.gov.au/pages/clue/) to region mapping.
- Fix WMS `GetMap`/`GetFeatureInfo` requests not having `styles` parameter (will use empty string instead of `undefined`)
- [The next improvement]
- Add CesiumIon geocoder

#### 8.3.7 - 2023-10-26

Expand Down
3 changes: 2 additions & 1 deletion lib/Core/AnalyticEvents/analyticEvents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ export enum SearchAction {
bing = "Bing",
catalog = "Catalog",
gazetteer = "Gazetteer",
nominatim = "nominatim"
nominatim = "nominatim",
cesium = "Cesium"
}

export enum LaunchAction {
Expand Down
70 changes: 48 additions & 22 deletions lib/Models/SearchProviders/CesiumIonSearchProvider.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
import SearchProvider from "./SearchProvider";
import { observable, makeObservable } from "mobx";
import Terria from "../Terria";
import Rectangle from "terriajs-cesium/Source/Core/Rectangle";
import defaultValue from "terriajs-cesium/Source/Core/defaultValue";
import i18next from "i18next";
import Terria from "../Terria";
import SearchProviderResults from "./SearchProviderResults";
import SearchResult from "./SearchResult";
import Rectangle from "terriajs-cesium/Source/Core/Rectangle";
import loadJson from "../../Core/loadJson";
import {
Category,
SearchAction
} from "../../Core/AnalyticEvents/analyticEvents";
interface CesiumIonSearchProviderOptions {
terria: Terria;
url?: string;
key?: string;
key: string;
flightDurationSeconds?: number;
primaryCountry?: string;
culture?: string;
}

interface CesiumIonGeocodeResultFeature {
Expand All @@ -36,6 +39,7 @@ export default class CesiumIonSearchProvider extends SearchProvider {
makeObservable(this);

this.terria = options.terria;
this.name = i18next.t("viewModels.searchLocations");
this.url = defaultValue(
options.url,
"https://api.cesium.com/v1/geocode/search"
Expand All @@ -45,39 +49,61 @@ export default class CesiumIonSearchProvider extends SearchProvider {
options.flightDurationSeconds,
1.5
);

if (!this.key) {
console.warn(
"The " +
this.name +
" geocoder will always return no results because a CesiumIon key has not been provided. Please get a CesiumIon key from ion.cesium.com, ensure it has geocoding permission and add it to parameters.cesiumIonAccessToken in config.json."
);
}
}

protected async doSearch(
searchText: string,
results: SearchProviderResults
searchResults: SearchProviderResults
): Promise<void> {
if (searchText === undefined || /^\s*$/.test(searchText)) {
return Promise.resolve();
}

const response = await loadJson<CesiumIonGeocodeResult>(
`${this.url}?text=${searchText}&access_token=${this.key}`
this.terria.analytics?.logEvent(
Category.search,
SearchAction.cesium,
searchText
);
let response;
try {
response = await loadJson<CesiumIonGeocodeResult>(
`${this.url}?text=${searchText}&access_token=${this.key}`
);
} catch (e) {
searchResults.message = i18next.t("viewModels.searchErrorOccurred");
return;
}

if (!response.features) {
searchResults.message = i18next.t("viewModels.searchNoLocations");
return;
}

results.results.push(
...response.features.map<SearchResult>((feature) => {
const [w, s, e, n] = feature.bbox;
const rectangle = Rectangle.fromDegrees(w, s, e, n);
if (response.features.length === 0) {
searchResults.message = i18next.t("viewModels.searchNoLocations");
}

return new SearchResult({
name: feature.properties.label,
clickAction: createZoomToFunction(this, rectangle),
location: {
latitude: (s + n) / 2,
longitude: (e + w) / 2
}
});
})
);
searchResults.results = response.features.map<SearchResult>((feature) => {
const [w, s, e, n] = feature.bbox;
const rectangle = Rectangle.fromDegrees(w, s, e, n);

return new SearchResult({
name: feature.properties.label,
clickAction: createZoomToFunction(this, rectangle),
location: {
latitude: (s + n) / 2,
longitude: (e + w) / 2
}
});
});
}
}

Expand Down
59 changes: 59 additions & 0 deletions test/Models/SearchProviders/CesiumIonSearchProviderSpec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import CesiumIonSearchProvider from "../../../lib/Models/SearchProviders/CesiumIonSearchProvider";
import Terria from "../../../lib/Models//Terria";
import * as loadJson from "../../../lib/Core/loadJson";

const fixture = {
features: [
{
properties: {
label: "West End, Australia"
},
bbox: [
152.99620056152344, -27.490509033203125, 153.0145721435547,
-27.474090576171875
]
}
]
};

describe("CesiumIonSearchProvider", () => {
const searchProvider = new CesiumIonSearchProvider({
key: "testkey",
url: "api.test.com",
terria: {
currentViewer: {
zoomTo: () => {}
}
} as unknown as Terria
});
it("Handles valid results", async () => {
spyOn(loadJson, "default").and.returnValue(
new Promise((resolve) => resolve(fixture))
);

const result = await searchProvider.search("test");
expect(loadJson.default).toHaveBeenCalledWith(
"api.test.com?text=test&access_token=testkey"
);
expect(result.results.length).toBe(1);
expect(result.results[0].name).toBe("West End, Australia");
expect(result.results[0].location?.latitude).toBe(-27.4822998046875);
});

it("Handles empty result", async () => {
spyOn(loadJson, "default").and.returnValue(
new Promise((resolve) => resolve([]))
);
const result = await searchProvider.search("test");
console.log(result);
expect(result.results.length).toBe(0);
expect(result.message).toBe("viewModels.searchNoLocations");
});

it("Handles error", async () => {
spyOn(loadJson, "default").and.throwError("error");
const result = await searchProvider.search("test");
expect(result.results.length).toBe(0);
expect(result.message).toBe("viewModels.searchErrorOccurred");
});
});

0 comments on commit 9a6af32

Please sign in to comment.