Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for the textSearch API #36

Merged
merged 1 commit into from
May 3, 2024
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
47 changes: 45 additions & 2 deletions examples/autoComplete/example.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
let map;
let placesService;
let predictionItems = [];
let markers = [];

function initMap() {
const austinCoords = { lat: 30.268193, lng: -97.7457518 }; // Austin, TX :)
Expand All @@ -30,7 +31,7 @@ function initMap() {
for (let i = 0; i < predictionItems.length; i++) {
let prediction = predictionItems[i];
if (prediction.description === ui.item.label) {
getPlaceDetails(prediction.place_id);
getPredictionInfo(prediction);

// Clear our cached prediction items after making selection,
// otherwise when the input widget loses focus it will trigger
Expand Down Expand Up @@ -77,11 +78,28 @@ function initMap() {
searchInput.val(prediction.description);
searchInput.autocomplete("close");

getPlaceDetails(prediction.place_id);
getPredictionInfo(prediction);
}
});
}

function getPredictionInfo(prediction) {
// Clear out any previous markers before we place new ones for the new prediction
markers.map((marker) => {
marker.setMap(null);
});
markers = [];

// If the prediction has a place_id, then we can do a getDetails
// Otherwise, the prediction is a query string (e.g. whataburgers in austin)
// so instead we need to do a textSearch to gather the place results
if (prediction.place_id) {
getPlaceDetails(prediction.place_id);
} else {
getTextSearch(prediction.description);
}
}

function getPlaceDetails(placeId) {
var request = {
placeId: placeId,
Expand All @@ -96,6 +114,29 @@ function getPlaceDetails(placeId) {
});
}

function getTextSearch(query) {
var request = {
query: query,
location: map.getCenter(),
};

const resultsBounds = new google.maps.LatLngBounds();

placesService.textSearch(request, function (results, status) {
if (status === google.maps.places.PlacesServiceStatus.OK) {
results.map((result) => {
createMarker(result);

resultsBounds.extend(result.geometry.location);
});

// Adjust the map to fit all the new markers we added
const paddingInPixels = 50;
map.fitBounds(resultsBounds, paddingInPixels);
}
});
}

function createMarker(place) {
if (!place.geometry || !place.geometry.location) return;

Expand All @@ -104,6 +145,8 @@ function createMarker(place) {
position: place.geometry.location,
});

markers.push(marker);

// TODO: Add support for re-routing these event listeners to our MapLibre markers
google.maps.event.addListener(marker, "click", () => {
console.log("MARKER CLICKED", place.name);
Expand Down
70 changes: 70 additions & 0 deletions src/places.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,76 @@ class MigrationPlacesService {
callback(null, PlacesServiceStatus.UNKNOWN_ERROR);
});
}

textSearch(request, callback) {
const query = request.query; // optional
const locationBias = request.location; // optional
const bounds = request.bounds; // optional
const language = request.language; // optional
const region = request.region; // optional

const input: SearchPlaceIndexForTextRequest = {
IndexName: this._placeIndexName,
Text: query, // required
};

// If bounds is specified, then location bias is ignored
if (bounds) {
// TODO: Change this to use GoogleLatLngBounds once MigrationLatLngBounds has
// been updated to handle all the constructor variants, which will handle converting
// either bounds from both LatLngBounds|LatLngBoundsLiteral for us
let southWest;
let northEast;
if (bounds.getSouthWest !== undefined) {
southWest = bounds.getSouthWest();
northEast = bounds.getNorthEast();
} else {
southWest = GoogleLatLng(bounds.south, bounds.west);
northEast = GoogleLatLng(bounds.north, bounds.east);
}

input.FilterBBox = [southWest.lng(), southWest.lat(), northEast.lng(), northEast.lat()];
} else if (locationBias) {
const lngLat = LatLngToLngLat(locationBias);
if (lngLat) {
input.BiasPosition = lngLat;
}
}

if (language) {
input.Language = language;
}

if (region) {
input.FilterCountries = [region];
}

const command = new SearchPlaceIndexForTextCommand(input);

this._client
.send(command)
.then((response) => {
const googleResults = [];

const results = response.Results;
if (results.length !== 0) {
results.forEach(function (place) {
// Include all supported fields as in findPlaceFromQuery,
// but not the additional fields for getDetails
const placeResponse = convertAmazonPlaceToGoogle(place, ["ALL"], false);

googleResults.push(placeResponse);
});
}

callback(googleResults, PlacesServiceStatus.OK);
})
.catch((error) => {
console.error(error);

callback([], PlacesServiceStatus.UNKNOWN_ERROR);
});
}
}

class MigrationAutocompleteService {
Expand Down
154 changes: 153 additions & 1 deletion test/places.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0

import { MigrationPlacesService } from "../src/places";
import { PlacesServiceStatus } from "../src/googleCommon";
import { GoogleLatLng, GoogleLatLngBounds, PlacesServiceStatus } from "../src/googleCommon";

// Spy on console.error so we can verify it gets called in error cases
jest.spyOn(console, "error").mockImplementation(() => {});
Expand Down Expand Up @@ -249,3 +249,155 @@ test("getDetails should handle client error", (done) => {
done();
});
});

test("textSearch should only use bounds if location was also specified", (done) => {
const east = 0;
const north = 1;
const south = 2;
const west = 3;
const request = {
query: "cool places in austin",
bounds: GoogleLatLngBounds(GoogleLatLng(south, west), GoogleLatLng(east, north)),
location: GoogleLatLng(4, 5),
};

placesService.textSearch(request, (results, status) => {
expect(results.length).toStrictEqual(1);
const firstResult = results[0];

expect(mockedClientSend).toHaveBeenCalledTimes(1);
expect(mockedClientSend).toHaveBeenCalledWith(expect.any(SearchPlaceIndexForTextCommand));
const clientInput = mockedClientSend.mock.calls[0][0].input;

expect(clientInput.FilterBBox).toStrictEqual([west, south, north, east]);
expect(clientInput.BiasPosition).toBeUndefined();

expect(firstResult.name).toStrictEqual("Austin");
expect(status).toStrictEqual(PlacesServiceStatus.OK);

// Signal the unit test is complete
done();
});
});

test("textSearch should accept bounds as a literal", (done) => {
const east = 0;
const north = 1;
const south = 2;
const west = 3;
const request = {
query: "cool places in austin",
bounds: { east: east, north: north, south: south, west: west },
};

placesService.textSearch(request, (results, status) => {
expect(results.length).toStrictEqual(1);
const firstResult = results[0];

expect(mockedClientSend).toHaveBeenCalledTimes(1);
expect(mockedClientSend).toHaveBeenCalledWith(expect.any(SearchPlaceIndexForTextCommand));
const clientInput = mockedClientSend.mock.calls[0][0].input;

expect(clientInput.FilterBBox).toStrictEqual([west, south, east, north]);
expect(clientInput.BiasPosition).toBeUndefined();

expect(firstResult.name).toStrictEqual("Austin");
expect(status).toStrictEqual(PlacesServiceStatus.OK);

// Signal the unit test is complete
done();
});
});

test("textSearch should accept location bias if there is no bounds specified", (done) => {
const request = {
query: "cool places in austin",
location: GoogleLatLng(testLat, testLng),
};

placesService.textSearch(request, (results, status) => {
expect(results.length).toStrictEqual(1);
const firstResult = results[0];

expect(mockedClientSend).toHaveBeenCalledTimes(1);
expect(mockedClientSend).toHaveBeenCalledWith(expect.any(SearchPlaceIndexForTextCommand));
const clientInput = mockedClientSend.mock.calls[0][0].input;

expect(clientInput.BiasPosition).toStrictEqual([testLng, testLat]);
expect(clientInput.FilterBBox).toBeUndefined();

expect(firstResult.name).toStrictEqual("Austin");
expect(status).toStrictEqual(PlacesServiceStatus.OK);

// Signal the unit test is complete
done();
});
});

test("textSearch should accept language", (done) => {
const request = {
query: "cool places in austin",
location: GoogleLatLng(testLat, testLng),
language: "en",
};

placesService.textSearch(request, (results, status) => {
expect(results.length).toStrictEqual(1);
const firstResult = results[0];

expect(mockedClientSend).toHaveBeenCalledTimes(1);
expect(mockedClientSend).toHaveBeenCalledWith(expect.any(SearchPlaceIndexForTextCommand));
const clientInput = mockedClientSend.mock.calls[0][0].input;

expect(clientInput.BiasPosition).toStrictEqual([testLng, testLat]);
expect(clientInput.Language).toStrictEqual("en");

expect(firstResult.name).toStrictEqual("Austin");
expect(status).toStrictEqual(PlacesServiceStatus.OK);

// Signal the unit test is complete
done();
});
});

test("textSearch should convert region to countries filter", (done) => {
const request = {
query: "cool places in austin",
location: GoogleLatLng(testLat, testLng),
region: "us",
};

placesService.textSearch(request, (results, status) => {
expect(results.length).toStrictEqual(1);
const firstResult = results[0];

expect(mockedClientSend).toHaveBeenCalledTimes(1);
expect(mockedClientSend).toHaveBeenCalledWith(expect.any(SearchPlaceIndexForTextCommand));
const clientInput = mockedClientSend.mock.calls[0][0].input;

expect(clientInput.BiasPosition).toStrictEqual([testLng, testLat]);
expect(clientInput.FilterCountries).toStrictEqual(["us"]);

expect(firstResult.name).toStrictEqual("Austin");
expect(status).toStrictEqual(PlacesServiceStatus.OK);

// Signal the unit test is complete
done();
});
});

test("textSearch should handle client error", (done) => {
const request = {
query: clientErrorQuery,
};

placesService.textSearch(request, (results, status) => {
expect(results).toHaveLength(0);
expect(status).toStrictEqual(PlacesServiceStatus.UNKNOWN_ERROR);

expect(console.error).toHaveBeenCalledTimes(1);

// Signal the unit test is complete
done();
});
});
Loading