mirror of
https://gitlab.com/wheres-the-tp/ui-mobile.git
synced 2026-01-25 07:54:57 -06:00
get places from google by location and search
This commit is contained in:
parent
12bc4898ff
commit
3a9d400f05
5 changed files with 118 additions and 64 deletions
79
js/apis/GooglePlacesApi.js
Normal file
79
js/apis/GooglePlacesApi.js
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
// @flow
|
||||||
|
import { type GooglePlaceObj } from '../records/PlaceRecord';
|
||||||
|
import { GoogleAPIKey } from '../constants/AppConstants';
|
||||||
|
import { pipe, filter, path, contains } from 'ramda';
|
||||||
|
|
||||||
|
const placesDetailUrl = `https://maps.googleapis.com/maps/api/place/details/json?key=${GoogleAPIKey}`;
|
||||||
|
const photosUrl = `https://maps.googleapis.com/maps/api/place/photo?key=${GoogleAPIKey}`;
|
||||||
|
const placesFromTextUrl = `https://maps.googleapis.com/maps/api/place/findplacefromtext/json?key=${GoogleAPIKey}`;
|
||||||
|
|
||||||
|
type GooglePlaceDetailsResponse = { error_message: ?string, result: GooglePlaceObj };
|
||||||
|
|
||||||
|
const milesToMeters = miles => Math.ceil(miles > 0 ? miles / 0.00062137 : 0);
|
||||||
|
|
||||||
|
const isEstablishment = pipe(
|
||||||
|
path(['types']),
|
||||||
|
contains('establishment')
|
||||||
|
);
|
||||||
|
|
||||||
|
export const getPlaceDetails = async (placeid: ?string): Promise<GooglePlaceObj> => {
|
||||||
|
if (!placeid || typeof placeid !== 'string') {
|
||||||
|
throw new Error('placeid looks wrong');
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await fetch(`${placesDetailUrl}&placeid=${placeid}`);
|
||||||
|
|
||||||
|
const { error_message, result }: GooglePlaceDetailsResponse = await res.json();
|
||||||
|
|
||||||
|
if (error_message) {
|
||||||
|
throw new Error(error_message);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getURLForPhotoReference = (opts?: { maxheight?: number, maxwidth?: number } = {}) => (
|
||||||
|
photoReference: string
|
||||||
|
) => {
|
||||||
|
if (!photoReference) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const { maxheight, maxwidth } = opts;
|
||||||
|
|
||||||
|
const maxHeight = `&maxheight=${maxheight || 600}`;
|
||||||
|
const maxWidth = maxwidth ? `&maxwidth=${maxwidth}` : '';
|
||||||
|
const photoref = `&photoreference=${photoReference}`;
|
||||||
|
|
||||||
|
return `${photosUrl}${photoref}${maxHeight}${maxWidth}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const findNearbyPlaces = async ({
|
||||||
|
location,
|
||||||
|
search,
|
||||||
|
radius,
|
||||||
|
}: {
|
||||||
|
location?: Position,
|
||||||
|
search?: string,
|
||||||
|
radius: number,
|
||||||
|
}): Promise<any> => {
|
||||||
|
if (!location) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
coords: { latitude, longitude },
|
||||||
|
} = location;
|
||||||
|
|
||||||
|
const input = `input=${encodeURIComponent(search || '*')}&inputtype=textquery`;
|
||||||
|
const loc = `locationbias=circle:${milesToMeters(radius)}@${latitude},${longitude}`;
|
||||||
|
const fields = 'fields=id,name,geometry,photos,types';
|
||||||
|
const reqUrl = `${placesFromTextUrl}&${input}&${loc}&${fields}`;
|
||||||
|
|
||||||
|
const { candidates, status } = await (await fetch(reqUrl)).json();
|
||||||
|
|
||||||
|
if (status !== 'OK') {
|
||||||
|
throw new Error('google find places request failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
return filter(isEstablishment, candidates);
|
||||||
|
};
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
// @flow
|
|
||||||
import { type GooglePlaceObj } from '../records/PlaceRecord';
|
|
||||||
import { GoogleAPIKey } from '../constants/AppConstants';
|
|
||||||
|
|
||||||
const placesUrl = `https://maps.googleapis.com/maps/api/place/details/json?key=${GoogleAPIKey}`;
|
|
||||||
const photosUrl = `https://maps.googleapis.com/maps/api/place/photo?key=${GoogleAPIKey}`;
|
|
||||||
|
|
||||||
type GooglePlaceDetailsResponse = { error_message: ?string, result: GooglePlaceObj };
|
|
||||||
|
|
||||||
export const getPlaceDetails = async (placeid: ?string): Promise<GooglePlaceObj> => {
|
|
||||||
if (!placeid || typeof placeid !== 'string') {
|
|
||||||
throw new Error('placeid looks wrong');
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = await fetch(`${placesUrl}&placeid=${placeid}`);
|
|
||||||
|
|
||||||
const { error_message, result }: GooglePlaceDetailsResponse = await res.json();
|
|
||||||
|
|
||||||
if (error_message) {
|
|
||||||
throw new Error(error_message);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getURLForPhotoReference = (opts?: { maxheight?: number, maxwidth?: number } = {}) => (
|
|
||||||
photoReference: string
|
|
||||||
) => {
|
|
||||||
if (!photoReference) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
const { maxheight, maxwidth } = opts;
|
|
||||||
|
|
||||||
const maxHeight = `&maxheight=${maxheight || 600}`;
|
|
||||||
const maxWidth = maxwidth ? `&maxwidth=${maxwidth}` : '';
|
|
||||||
const photoref = `&photoreference=${photoReference}`;
|
|
||||||
|
|
||||||
return `${photosUrl}${photoref}${maxHeight}${maxWidth}`;
|
|
||||||
};
|
|
||||||
|
|
@ -5,7 +5,7 @@ import PlaceTile from '../components/PlaceTile';
|
||||||
import { compose, pure, withState, withHandlers } from 'recompose';
|
import { compose, pure, withState, withHandlers } from 'recompose';
|
||||||
import { withFoodItemsGroupedByPlace } from '../enhancers/foodItemEnhancers';
|
import { withFoodItemsGroupedByPlace } from '../enhancers/foodItemEnhancers';
|
||||||
import { withPlaces } from '../enhancers/placeEnhancers';
|
import { withPlaces } from '../enhancers/placeEnhancers';
|
||||||
import { type Map, List } from 'immutable';
|
import { Map, List } from 'immutable';
|
||||||
import typeof FoodItemRecord from '../records/FoodItemRecord';
|
import typeof FoodItemRecord from '../records/FoodItemRecord';
|
||||||
import typeof PlaceRecord from '../records/PlaceRecord';
|
import typeof PlaceRecord from '../records/PlaceRecord';
|
||||||
|
|
||||||
|
|
@ -17,7 +17,7 @@ type Props = {
|
||||||
isRefreshing: boolean,
|
isRefreshing: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
const PlacesList = ({ foodItemsByPlace, places, isRefreshing, onPulldown }: Props) => {
|
const PlacesList = ({ foodItemsByPlace, places = Map(), isRefreshing, onPulldown }: Props) => {
|
||||||
return (
|
return (
|
||||||
<ScrollView
|
<ScrollView
|
||||||
refreshControl={<RefreshControl refreshing={isRefreshing} onRefresh={onPulldown} />}>
|
refreshControl={<RefreshControl refreshing={isRefreshing} onRefresh={onPulldown} />}>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
// @flow
|
// @flow
|
||||||
import { List, Map, Record, fromJS } from 'immutable';
|
import { List, Map, Record, fromJS } from 'immutable';
|
||||||
import { pipe, pathOr, map, head, memoizeWith } from 'ramda';
|
import { pipe, pathOr, map, head, memoizeWith } from 'ramda';
|
||||||
import { getURLForPhotoReference } from '../apis/PlaceDetailsApi';
|
import { getURLForPhotoReference } from '../apis/GooglePlacesApi';
|
||||||
|
|
||||||
export type GooglePlaceObj = {
|
export type GooglePlaceObj = {
|
||||||
place_id: string,
|
place_id: string,
|
||||||
|
|
@ -80,9 +80,18 @@ const getPhotos = pathOr([{}], ['photos']);
|
||||||
|
|
||||||
const getPhotoRef = photo => photo.photo_reference || '';
|
const getPhotoRef = photo => photo.photo_reference || '';
|
||||||
|
|
||||||
const getThumb = pipe(getPhotos, head, getPhotoRef, getURLForPhotoReference({ maxheight: 200 }));
|
const getThumb = pipe(
|
||||||
|
getPhotos,
|
||||||
|
head,
|
||||||
|
getPhotoRef,
|
||||||
|
getURLForPhotoReference({ maxheight: 200 })
|
||||||
|
);
|
||||||
|
|
||||||
const getPhotoUrls = pipe(getPhotos, map(getPhotoRef), map(getURLForPhotoReference({ maxheight: 600 })));
|
const getPhotoUrls = pipe(
|
||||||
|
getPhotos,
|
||||||
|
map(getPhotoRef),
|
||||||
|
map(getURLForPhotoReference({ maxheight: 600 }))
|
||||||
|
);
|
||||||
|
|
||||||
export const buildPlaceRecord = memoizeWith(
|
export const buildPlaceRecord = memoizeWith(
|
||||||
(place: ?GooglePlaceObj) => place && place.place_id,
|
(place: ?GooglePlaceObj) => place && place.place_id,
|
||||||
|
|
|
||||||
|
|
@ -1,33 +1,38 @@
|
||||||
// @flow
|
// @flow
|
||||||
import { buildPlaceRecord } from '../records/PlaceRecord';
|
import { buildPlaceRecord } from '../records/PlaceRecord';
|
||||||
import { Map } from 'immutable';
|
import { Map } from 'immutable';
|
||||||
import foodItems$ from './FoodItemsStream';
|
import { findNearbyPlaces } from '../apis/GooglePlacesApi';
|
||||||
import { getPlaceDetails } from '../apis/PlaceDetailsApi';
|
|
||||||
import { memoize } from 'ramda';
|
import { memoize } from 'ramda';
|
||||||
import { Observable } from 'rxjs';
|
|
||||||
import typeof FoodItemRecord from '../records/FoodItemRecord';
|
|
||||||
import { type GooglePlaceObj } from '../records/PlaceRecord';
|
import { type GooglePlaceObj } from '../records/PlaceRecord';
|
||||||
import { setById } from '../helpers/ImmutableHelpers';
|
import { setById } from '../helpers/ImmutableHelpers';
|
||||||
|
import location$ from './LocationStream';
|
||||||
|
import filter$ from './FilterStream';
|
||||||
|
import FilterRecord from '../records/FilterRecord';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* return a promise of a place details object
|
* always return a promise and swallow exceptions so as not to break the stream
|
||||||
* if already requested return existing promise
|
|
||||||
* swallow exceptions so as not to break the stream
|
|
||||||
*/
|
*/
|
||||||
const safeGetPlaceDetails = memoize((placeId: string): Promise<?GooglePlaceObj> => {
|
const safeWrapAjax = ajaxFn =>
|
||||||
return getPlaceDetails(placeId).catch(error => {
|
memoize((...args) => {
|
||||||
console.log('ERROR getPlaceDetails: ', error); // eslint-disable-line no-console
|
return ajaxFn(...args).catch(error => {
|
||||||
return null;
|
console.log('ERROR: ', error); //eslint-disable-line no-console
|
||||||
|
return null;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
const Places$ = foodItems$
|
const safeFindNearbyPlaces = safeWrapAjax(findNearbyPlaces);
|
||||||
.mergeMap((foodItems: Map<string, FoodItemRecord> = Map()): Observable<string> => {
|
|
||||||
return Observable.from(foodItems.toArray().map(foodItem => foodItem.placeId));
|
const Places$ = location$
|
||||||
})
|
.combineLatest(filter$)
|
||||||
.distinct()
|
.mergeMap(([location, filter]: [?Position, FilterRecord]) =>
|
||||||
.mergeMap(safeGetPlaceDetails)
|
safeFindNearbyPlaces({
|
||||||
.map(buildPlaceRecord)
|
location,
|
||||||
.scan(setById, new Map());
|
radius: filter.radius,
|
||||||
|
search: filter.search,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.map((places: Array<GooglePlaceObj>) => {
|
||||||
|
return (places || []).map(buildPlaceRecord).reduce(setById, new Map());
|
||||||
|
});
|
||||||
|
|
||||||
export default Places$;
|
export default Places$;
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue