get places from google by location and search

This commit is contained in:
bartronx7 2018-07-08 10:59:43 -05:00
parent 12bc4898ff
commit 3a9d400f05
5 changed files with 118 additions and 64 deletions

View 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);
};

View file

@ -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}`;
};

View file

@ -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} />}>

View file

@ -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,

View file

@ -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$;