├── .eslintrc.js
├── .gitignore
├── .storybook
└── main.js
├── .travis.yml
├── README.md
├── package.json
├── public
├── favicon.ico
├── index.html
├── logo192.png
├── logo512.png
├── manifest.json
├── places.json
└── robots.txt
├── src
├── App.css
├── App.js
├── App.test.js
├── components
│ ├── AutoComplete.js
│ ├── GoogleMap.js
│ ├── Marker.js
│ └── SearchBox.js
├── const
│ └── la_center.js
├── examples
│ ├── AutoComplete.js
│ ├── Heatmap.js
│ ├── Main.js
│ ├── MarkerInfoWindow.js
│ ├── MarkerInfoWindowGmapsObj.js
│ └── Searchbox.js
├── index.css
├── index.js
├── logo.svg
├── serviceWorker.js
├── setupTests.js
└── stories
│ ├── Autocomplete
│ └── Base.stories.js
│ ├── ClickableMarkers
│ └── Base.stories.js
│ ├── Heatmap
│ └── Base.stories.js
│ ├── Main
│ └── Base.stories.js
│ └── Searchbox
│ └── Base.stories.js
└── yarn.lock
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "parser": "babel-eslint",
3 | "extends": "airbnb",
4 | "rules": {
5 | "react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }],
6 | "import/no-extraneous-dependencies": [
7 | "error",
8 | {
9 | devDependencies: [
10 | "src/stories/**",
11 | ]
12 | }
13 | ]
14 | },
15 | "env": {
16 | "es6": true,
17 | "jest": true,
18 | "browser": true,
19 | }
20 | };
21 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 | /storybook-static
14 |
15 | # misc
16 | .DS_Store
17 | .env.local
18 | .env.development.local
19 | .env.test.local
20 | .env.production.local
21 |
22 | npm-debug.log*
23 | yarn-debug.log*
24 | yarn-error.log*
25 | build-storybook.log*
26 |
27 | .env.*
28 |
--------------------------------------------------------------------------------
/.storybook/main.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | stories: ['../src/**/*.stories.js'],
3 | addons: [
4 | '@storybook/preset-create-react-app',
5 | '@storybook/addon-actions',
6 | '@storybook/addon-links',
7 | ],
8 | };
9 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - stable
4 | cache:
5 | directories:
6 | - node_modules
7 | before_script:
8 | - git diff --exit-code # make sure that yarn.lock didn't change
9 | script:
10 | - yarn run lint
11 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Examples for [google-map-react library](https://github.com/google-map-react/google-map-react) · [](https://travis-ci.org/google-map-react/google-map-react-examples)
2 |
3 | This project was bootstrapped with [Create React App](https://github.com/facebookincubator/create-react-app).
4 |
5 | ## Stack
6 |
7 | - [React](https://facebook.github.io/react) rendering
8 | - [React-router](https://reacttraining.com/react-router/) for routing
9 | - [Styled components](https://www.styled-components.com/) for styling our components
10 |
11 | ## Examples
12 |
13 | - [Main](https://google-map-react.github.io/google-map-react-examples/?path=/story/main-examples--base) ([source](https://github.com/google-map-react/google-map-react-examples/blob/master/src/examples/Main.js))
14 | - [Heatmap](https://google-map-react.github.io/google-map-react-examples/?path=/story/heatmap-examples--base) ([source](https://github.com/google-map-react/google-map-react-examples/blob/master/src/examples/Heatmap.js))
15 | - [Searchbox](https://google-map-react.github.io/google-map-react-examples/?path=/story/searchbox-examples--base) ([source](https://github.com/google-map-react/google-map-react-examples/blob/master/src/examples/Searchbox.js))
16 | - [Autocomplete](https://google-map-react.github.io/google-map-react-examples/?path=/story/autocomplete-examples--base) ([source](https://github.com/google-map-react/google-map-react-examples/blob/master/src/examples/Searchbox.js))
17 | - [Marker and Info Window using React Component](https://google-map-react.github.io/google-map-react-examples/?path=/story/markerinfo-examples--custom-component) ([source](https://github.com/google-map-react/google-map-react-examples/blob/master/src/examples/MarkerInfoWindow.js))
18 | - [Marker and Info Window using Google Maps API Object](https://google-map-react.github.io/google-map-react-examples/?path=/story/markerinfo-examples--default-marker) ([source](https://github.com/google-map-react/google-map-react-examples/blob/master/src/examples/MarkerInfoWindowGmapsObj.js))
19 |
20 | ## Getting started
21 |
22 | ### Installation
23 |
24 | Although I prefer yarn, you can use either yarn or npm:
25 |
26 | - `npm install` or simply `yarn`
27 |
28 | ### Run development
29 |
30 | - `npm start` or `yarn start`
31 |
32 | Runs the app in development mode.
33 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
34 |
35 | The page will automatically reload if you make changes to the code.
36 | You will see the build errors and lint warnings in the console.
37 |
38 | ### Run production build
39 |
40 | - `npm run build` or `yarn build`
41 |
42 | Builds the app for production to the `build` folder.
43 | It correctly bundles React in production mode and optimizes the build for the best performance.
44 |
45 | The build is minified and the filenames include the hashes.
46 | By default, it also [includes a service worker](https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/template/README.md#making-a-progressive-web-app) so that your app loads from local cache on future visits.
47 |
48 | Your app is ready to be deployed.
49 |
50 | ### Deploy to GH pages
51 |
52 | - `npm run deploy` or `yarn deploy`
53 |
54 | This will push a branch named `gh-pages` to the repository, which will be used by GH to render the latest version of our site.
55 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "google-map-react-examples",
3 | "version": "0.1.0",
4 | "private": true,
5 | "homepage": "https://itsmichaeldiego.github.io/google-map-react-examples/",
6 | "dependencies": {
7 | "@testing-library/jest-dom": "^5.11.0",
8 | "@testing-library/react": "^10.4.5",
9 | "@testing-library/user-event": "^12.0.11",
10 | "google-map-react": "^2.0.1",
11 | "lodash.isempty": "^4.4.0",
12 | "prop-types": "^15.6.1",
13 | "react": "^16.13.1",
14 | "react-dom": "^16.13.1",
15 | "react-scripts": "3.4.1",
16 | "styled-components": "^5.1.1"
17 | },
18 | "scripts": {
19 | "lint": "eslint src",
20 | "start": "react-scripts start",
21 | "build": "react-scripts build",
22 | "test": "react-scripts test",
23 | "eject": "react-scripts eject",
24 | "predeploy": "npm run build-storybook",
25 | "deploy": "gh-pages -d storybook-static",
26 | "storybook": "start-storybook -p 9009 -s public",
27 | "build-storybook": "build-storybook -s public",
28 | "chromatic": "npx chromatic --project-token=2mchcgpjqg2"
29 | },
30 | "devDependencies": {
31 | "@storybook/addon-actions": "^5.3.19",
32 | "@storybook/addon-links": "^5.3.19",
33 | "@storybook/addons": "^5.3.19",
34 | "@storybook/preset-create-react-app": "^3.1.3",
35 | "@storybook/react": "^5.3.19",
36 | "babel-eslint": "^10.0.1",
37 | "chromatic": "^5.0.0",
38 | "eslint-config-airbnb": "^18.2.0",
39 | "eslint-plugin-import": "^2.9.0",
40 | "eslint-plugin-jsx-a11y": "^6.0.3",
41 | "eslint-plugin-react": "^7.7.0",
42 | "gh-pages": "^3.1.0"
43 | },
44 | "browserslist": {
45 | "production": [
46 | ">0.2%",
47 | "not dead",
48 | "not op_mini all"
49 | ],
50 | "development": [
51 | "last 1 chrome version",
52 | "last 1 firefox version",
53 | "last 1 safari version"
54 | ]
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/google-map-react/google-map-react-examples/73385d4434d8d70737613d61123cab63e197e5c7/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/google-map-react/google-map-react-examples/73385d4434d8d70737613d61123cab63e197e5c7/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/google-map-react/google-map-react-examples/73385d4434d8d70737613d61123cab63e197e5c7/public/logo512.png
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/public/places.json:
--------------------------------------------------------------------------------
1 | {
2 | "html_attributions": [],
3 | "next_page_token": "CpQCDwEAAJwdqLU_q4CMC9j-DOFIiwmwpP8ICAF_lHHKpWrNLfYsLS0hubKFTbau5sp7Tc_vC2bftXqZSRfq4uMk34-nE1f3weBRv77ZvsUhQn1MvJCfW6pNFA6zrDP_lPLBQZVrGVePIGhW8g485WnZdAHUyKpr4jjJ6hiOh2R6MxVBTYHyRuy5mDa-NGx4zQXDsHGSejOPH6BmPUoyfb-2MmRo1kcYABkBZRAaXpDmTtoXLmX8GxELbMOOAjEJ8Vzxdk-4GQkzoDYB1YwHBR1JXzxlawX0DdUyaJKjLWGjvt8GOwF8d9KVTF0r528jDCheFjvRgxOGMeYq1IrUx9lBofHH4sAX2plD6BGumN40mdBkDKVMEhC8pyLxL6t51mflBEc_WkF6GhTsH0kHwldrZYDnyUZ78TU9zeiiqg",
4 | "results": [{
5 | "formatted_address": "3818 Sunset Blvd, Los Angeles, CA 90026, USA",
6 | "geometry": {
7 | "location": {
8 | "lat": 34.091158,
9 | "lng": -118.2795188
10 | },
11 | "viewport": {
12 | "northeast": {
13 | "lat": 34.09258172989272,
14 | "lng": -118.2780556701073
15 | },
16 | "southwest": {
17 | "lat": 34.08988207010728,
18 | "lng": -118.2807553298927
19 | }
20 | }
21 | },
22 | "icon": "https://maps.gstatic.com/mapfiles/place_api/icons/restaurant-71.png",
23 | "id": "1113d3dd7339f965caae39387dd808a9e877bc2e",
24 | "name": "Flore Vegan",
25 | "opening_hours": {
26 | "open_now": false,
27 | "weekday_text": []
28 | },
29 | "photos": [{
30 | "height": 2336,
31 | "html_attributions": [
32 | "Roman Roze"
33 | ],
34 | "photo_reference": "CmRaAAAA69UVwaJnUQXQUSSX9IfB3b29opNIohkexsGAGoHTD5Lyg24lhpBtaiNlrgihstR-k7Su9Vgbc8-eE5qHEdeLVY1QTfiuyS9TPp3e2GMM_grW2FtrgrFQGtMJSeJ336cPEhCVHYfFzoOgrrKdXlk34rJiGhSXSv_XG1q1CtOrWJjWQrxJmLvIPg",
35 | "width": 3504
36 | }],
37 | "place_id": "ChIJj62e80jHwoARusJT4mjohWw",
38 | "price_level": 2,
39 | "rating": 4.6,
40 | "reference": "CmRbAAAAM9_YQ6Dt9T69zucczidzOd6HU2vmzaXvTG-lJ89KyBlJVBJ0aEfar2Exre4iDWKHjExUshYSpAXEzA-YqotVnOt4XznKY_vkD520XK5nzFz5v5IefUe6FDBqZPzYlxRDEhAgQvwzNjwC49WWlyoMKza5GhS6r-VIl1lXdMl_JEW67yL7fPkZdg",
41 | "types": [
42 | "restaurant",
43 | "food",
44 | "point_of_interest",
45 | "establishment"
46 | ]
47 | },
48 | {
49 | "formatted_address": "1700 Sunset Blvd, Los Angeles, CA 90026, USA",
50 | "geometry": {
51 | "location": {
52 | "lat": 34.0771192,
53 | "lng": -118.2587199
54 | },
55 | "viewport": {
56 | "northeast": {
57 | "lat": 34.07856197989273,
58 | "lng": -118.2573112201073
59 | },
60 | "southwest": {
61 | "lat": 34.07586232010728,
62 | "lng": -118.2600108798928
63 | }
64 | }
65 | },
66 | "icon": "https://maps.gstatic.com/mapfiles/place_api/icons/restaurant-71.png",
67 | "id": "d8bc8d867ccf72cbc552e100238a0820628963ee",
68 | "name": "Sage Plant Based Bistro and Brewery Echo Park",
69 | "opening_hours": {
70 | "open_now": false,
71 | "weekday_text": []
72 | },
73 | "photos": [{
74 | "height": 3024,
75 | "html_attributions": [
76 | "Anurag Singhai"
77 | ],
78 | "photo_reference": "CmRaAAAAW6v0EyZa77vrG5Aq8zbnU1sR7pljqfoDpSxXhISFgWoYWLkFY5hh7YCFbzYLj1XzflTJOrCXJa-q5jPT4L0vMY8cjXrhCzB5y7Z--qJTWOO_NxaRUBbB5QhpxyUT-R6tEhBru_ZZ_xcCxFueYrRxI6pFGhRgiExnWWOhU2Ii_NnW6M8R4xs4IQ",
79 | "width": 4032
80 | }],
81 | "place_id": "ChIJ6T9ggBvHwoARc3aegK3PBe0",
82 | "price_level": 2,
83 | "rating": 4.6,
84 | "reference": "CmRbAAAAy5CT3sE8bRlADeIeMYtC7NRdE58vKOCjOvhZNUs0QwBD7kSS6WIfo3wmxvt4EGZm5TJ6WdqOCRSnnnFoXAVpz9F3EyZPosUDCN2LJIvtYxwS3BYkwh6uVQRSj-OPLak6EhBRf8xOpcuc5WWWBgGNbGGbGhSYiomy4iNV-g_KzzqNrpymm3MxQw",
85 | "types": [
86 | "restaurant",
87 | "food",
88 | "point_of_interest",
89 | "establishment"
90 | ]
91 | },
92 | {
93 | "formatted_address": "8284 Melrose Ave, Los Angeles, CA 90046, USA",
94 | "geometry": {
95 | "location": {
96 | "lat": 34.083527,
97 | "lng": -118.370157
98 | },
99 | "viewport": {
100 | "northeast": {
101 | "lat": 34.08495787989272,
102 | "lng": -118.3688093201073
103 | },
104 | "southwest": {
105 | "lat": 34.08225822010727,
106 | "lng": -118.3715089798927
107 | }
108 | }
109 | },
110 | "icon": "https://maps.gstatic.com/mapfiles/place_api/icons/restaurant-71.png",
111 | "id": "8529e7504ec45e2f380310882a3bd05fe050d701",
112 | "name": "Crossroads Kitchen",
113 | "opening_hours": {
114 | "open_now": false,
115 | "weekday_text": []
116 | },
117 | "photos": [{
118 | "height": 446,
119 | "html_attributions": [
120 | "Crossroads Vegan Restaurant"
121 | ],
122 | "photo_reference": "CmRaAAAAeNvmtrTLNo95XHaHxur4Lc3CSCbtl8zUk1cbjlFdDSpMupFJ0LXRP7iaqHQeP0N8srb3P9NzwI0jrASIvj7dn-8b0KkmjvgeAmETi9sDAEwQVOui2yIJd5yzzJN3LD_bEhCCaBWZ_g1fJHyzVzz9c18eGhTSCsqJIwmOF_xRWfhPBoFUO86y1A",
123 | "width": 720
124 | }],
125 | "place_id": "ChIJhZy9Gba-woARQLEEpEN46uw",
126 | "price_level": 3,
127 | "rating": 4.5,
128 | "reference": "CmRbAAAAU-AZsWP9B3I6f7bIB2lho68meak73rt_fxdoJug-tXoqRil2guPjWxQmycp2dPFKXZNG5azOwT-rEy9vJtXQf44uVj45j_ohS6ietOXClyyAppqZt_ngGVISdtcNvrFaEhDYSDnUEsrd7GxTol1wHfJ4GhRsQAnur588F9jnXy0S4IYaiaEAQA",
129 | "types": [
130 | "bar",
131 | "restaurant",
132 | "food",
133 | "point_of_interest",
134 | "establishment"
135 | ]
136 | },
137 | {
138 | "formatted_address": "4319 Sunset Blvd, Los Angeles, CA 90029, USA",
139 | "geometry": {
140 | "location": {
141 | "lat": 34.0951843,
142 | "lng": -118.283107
143 | },
144 | "viewport": {
145 | "northeast": {
146 | "lat": 34.09646662989272,
147 | "lng": -118.2818451701072
148 | },
149 | "southwest": {
150 | "lat": 34.09376697010727,
151 | "lng": -118.2845448298927
152 | }
153 | }
154 | },
155 | "icon": "https://maps.gstatic.com/mapfiles/place_api/icons/restaurant-71.png",
156 | "id": "287049135d7d1ab1bfd2de8d7654afcc97cfee77",
157 | "name": "My Vegan Gold",
158 | "opening_hours": {
159 | "open_now": false,
160 | "weekday_text": []
161 | },
162 | "photos": [{
163 | "height": 667,
164 | "html_attributions": [
165 | "Sayamon Riddang"
166 | ],
167 | "photo_reference": "CmRaAAAABpfiLbCymEfhpZDl0vsV5ZjC7UDVT-GQAryHJkQ1rZHakstWk4Cugb0HrxU3dsdqbjxvKU9gxVHsZjxToVuMwRw_dHWdwIPFNl-VGYtipa8Z6Uzf6RzmS2tDje45jzTREhD1E_zkXr3Z6v8wWLzDDxwlGhS8VB8VsaT96ycvEwdkjhzmNKSfDg",
168 | "width": 1000
169 | }],
170 | "place_id": "ChIJh6ttFUzHwoAR9p8Wi_B8AbI",
171 | "price_level": 2,
172 | "rating": 4.6,
173 | "reference": "CmRbAAAAUR_MLHOu0gPb-77cvoygt4blzXX2PtoI7PRfh8mqqOgx8MPgpclf8PcfCXkEvAfDxu4wafz5l1jBDTPTntDz3Epf0dJxilKvshezCkwMXdlIoZjBZwW8n6BMU5WKGCjXEhBd8OVC5aJ7Qc_S3skTq1KnGhQNTUFaUZAnY5r37iUSLCT59zfhrw",
174 | "types": [
175 | "restaurant",
176 | "food",
177 | "point_of_interest",
178 | "establishment"
179 | ]
180 | },
181 | {
182 | "formatted_address": "4442, 1769 Hillhurst Ave, Los Angeles, CA 90027, United States",
183 | "geometry": {
184 | "location": {
185 | "lat": 34.1033401,
186 | "lng": -118.2875469
187 | },
188 | "viewport": {
189 | "northeast": {
190 | "lat": 34.10468952989272,
191 | "lng": -118.2861405701073
192 | },
193 | "southwest": {
194 | "lat": 34.10198987010727,
195 | "lng": -118.2888402298927
196 | }
197 | }
198 | },
199 | "icon": "https://maps.gstatic.com/mapfiles/place_api/icons/restaurant-71.png",
200 | "id": "1401d50571af0bd7fc0267e6a3635bfb3d3dbda2",
201 | "name": "Green Leaves Vegan",
202 | "opening_hours": {
203 | "open_now": false,
204 | "weekday_text": []
205 | },
206 | "photos": [{
207 | "height": 3168,
208 | "html_attributions": [
209 | "Carl Afable"
210 | ],
211 | "photo_reference": "CmRaAAAAz4NOaoeJczV-BFF7HYE-unBeYl8bud0vmkVtYxCv-lKLYo1YnAgcf-Cisr4BQypE9WbaUwm157PYyxLkWkQ1CwVcP7bXtV6QAdNegOXB8QlWAEPR95Q6lm4NQ71B3Vm2EhD2nTXqDhitEm0BrjyRh3JxGhRYCJXLJCF4AWJiBXk5fcdcjylX_g",
212 | "width": 4752
213 | }],
214 | "place_id": "ChIJ2RKN_rHAwoARIGWYxAgzzyQ",
215 | "price_level": 1,
216 | "rating": 4.5,
217 | "reference": "CmRbAAAAtM6n50gnrmJYk2qR_1uslm5ILIFgla8_RVtkm5pLzg9pMV-UcNQ3LzYSc6iM2vm6rbIzSvmjYMvWZ0Qy0AnVyIhb0kYHi1HpNFbYF3aD5cxH2hyHuB9qbn_OY_7vTpWEEhDTEJ002c2Pt-QTBNerb9iOGhRnv28Ti7KlI3gYVtBKG5XKOju9Zg",
218 | "types": [
219 | "restaurant",
220 | "food",
221 | "point_of_interest",
222 | "establishment"
223 | ]
224 | },
225 | {
226 | "formatted_address": "707 E 10th St, Los Angeles, CA 90021, USA",
227 | "geometry": {
228 | "location": {
229 | "lat": 34.035798,
230 | "lng": -118.251288
231 | },
232 | "viewport": {
233 | "northeast": {
234 | "lat": 34.03709272989273,
235 | "lng": -118.2499997201073
236 | },
237 | "southwest": {
238 | "lat": 34.03439307010728,
239 | "lng": -118.2526993798928
240 | }
241 | }
242 | },
243 | "icon": "https://maps.gstatic.com/mapfiles/place_api/icons/restaurant-71.png",
244 | "id": "01c75569ea2c5ee7ae29e91990ff4d872bc1ca5b",
245 | "name": "The Vegan Joint",
246 | "opening_hours": {
247 | "open_now": false,
248 | "weekday_text": []
249 | },
250 | "photos": [{
251 | "height": 680,
252 | "html_attributions": [
253 | "The Vegan Joint (Downtown)"
254 | ],
255 | "photo_reference": "CmRaAAAAGdHRzCGTwQ0_tc-fWo9wfezr51jbxPQ7IH1Gsc9OCRxoIijxg110YoNAaCTQSefXtCVdiH2xYcifKokVtnwxp01tE6J888NxT_MA1zpzxiM5ZM3MgJ9OxeJERDyE8jT0EhBSS5kv0YX7tmAxiUFHPOEXGhT2ZyiB6uRAfzpKchXvfOxyvA7S2Q",
256 | "width": 1906
257 | }],
258 | "place_id": "ChIJL8eKojLGwoAR-inFlT4uF8Q",
259 | "rating": 4.5,
260 | "reference": "CmRbAAAAPj4xP0HMJoDhx-P7C2JZTRSxu44XKoALQqupq2WaiqDjqiqi7g1ua7imgENWEH_fXu8XS5XxAmpkYV4XcDWfbLeZFuOKbgNGIvxQhXBhL-215hJQvoHFodeO9_lCGliNEhC6QZwCTKktRp33eibHgQJyGhRZf9Z_PHxTO-dxhjCFmIY8KQUYlQ",
261 | "types": [
262 | "restaurant",
263 | "food",
264 | "point_of_interest",
265 | "establishment"
266 | ]
267 | },
268 | {
269 | "formatted_address": "2135 Sunset Blvd, Los Angeles, CA 90026, USA",
270 | "geometry": {
271 | "location": {
272 | "lat": 34.0776068,
273 | "lng": -118.2646526
274 | },
275 | "viewport": {
276 | "northeast": {
277 | "lat": 34.07886997989272,
278 | "lng": -118.2632818701073
279 | },
280 | "southwest": {
281 | "lat": 34.07617032010727,
282 | "lng": -118.2659815298927
283 | }
284 | }
285 | },
286 | "icon": "https://maps.gstatic.com/mapfiles/place_api/icons/restaurant-71.png",
287 | "id": "946525c8b38a73842b88f67b1786746dce63fc27",
288 | "name": "Elf Cafe",
289 | "opening_hours": {
290 | "open_now": false,
291 | "weekday_text": []
292 | },
293 | "photos": [{
294 | "height": 1365,
295 | "html_attributions": [
296 | "ZAGAT"
297 | ],
298 | "photo_reference": "CmRaAAAAXeyh-4q2GinyRWob20cRuMTPB9uwOMr4jceLbiGh1sUI3I3dU2tqPBg0fIALpeBD4h5cfCTyULBTj6vSqNEcDUcPGFnjOaDO3BYQALO94_WCtbjkY83fQkdM_BPFlm2oEhDshmrB_41OPbyEVGJ4fPUVGhSYvebsAZyOFjSgUoJm_QQAhDFhzA",
299 | "width": 2048
300 | }],
301 | "place_id": "ChIJEy4mphDHwoARejZ9zUN_d4U",
302 | "price_level": 2,
303 | "rating": 4.7,
304 | "reference": "CmRbAAAAwxF-jK7VtlDfSJigYrgWtGrgNXRvw-4pU62YULylvTajhxW9lwzvmYKmKKx4ZO0SBMR1TOqVE-rPlhlh7aZI5NfcL8RItn_IX1BxS7X9X2ode26MDbVwICFFC_qziC-MEhA4i-3OikQ9eHbTeu-L27G1GhRLgk421tk5hnWDbhrEbtK-S3Ap4A",
305 | "types": [
306 | "restaurant",
307 | "food",
308 | "point_of_interest",
309 | "establishment"
310 | ]
311 | },
312 | {
313 | "formatted_address": "4114 Santa Monica Blvd, Los Angeles, CA 90029, USA",
314 | "geometry": {
315 | "location": {
316 | "lat": 34.0919263,
317 | "lng": -118.2820544
318 | },
319 | "viewport": {
320 | "northeast": {
321 | "lat": 34.09333132989273,
322 | "lng": -118.2807559701073
323 | },
324 | "southwest": {
325 | "lat": 34.09063167010728,
326 | "lng": -118.2834556298927
327 | }
328 | }
329 | },
330 | "icon": "https://maps.gstatic.com/mapfiles/place_api/icons/restaurant-71.png",
331 | "id": "d358acb483e9bacaf445de016f78473908253696",
332 | "name": "Bulan Thai Vegetarian Kitchen",
333 | "opening_hours": {
334 | "open_now": false,
335 | "weekday_text": []
336 | },
337 | "photos": [{
338 | "height": 3024,
339 | "html_attributions": [
340 | "jeromy robert"
341 | ],
342 | "photo_reference": "CmRaAAAAbjH5vQk8Ese5ppONFAIwbunnNnhcEEkKtRrn5yttTYvxHxjbgbGDXIv7cNSfQMU7wICcox5NT__xnaKwJi-e3zr5230Z4t8TcReOcVoV8Ewjfn5g7AL4AK0gksctR-KqEhD6iw1PbCByU1zD7iD3lxPXGhT5AoSLQ7BHIzymU94-0xSvRMo9yA",
343 | "width": 4032
344 | }],
345 | "place_id": "ChIJAaqI1E7HwoARRCxBLPw048Y",
346 | "price_level": 2,
347 | "rating": 4.6,
348 | "reference": "CmRbAAAAbLDZZu-oOjRmRi7hsQpZELuusRQZEk6lE1YJL5r7pnP3RtWaSLlA_p9uYp0QIalfzXhS0605iRnNsEr_WDhos1HxubW4h0sPKz96GPUKeULOux319b2pYHqQCYwYUmOMEhCEXh-YmfKFafd1EtkAYkB2GhSbxBpYLCWz45RTP0YaWm6eeJecwQ",
349 | "types": [
350 | "restaurant",
351 | "food",
352 | "point_of_interest",
353 | "establishment"
354 | ]
355 | },
356 | {
357 | "formatted_address": "1047 S Fairfax Ave, Los Angeles, CA 90019, USA",
358 | "geometry": {
359 | "location": {
360 | "lat": 34.0568525,
361 | "lng": -118.3646369
362 | },
363 | "viewport": {
364 | "northeast": {
365 | "lat": 34.05817752989272,
366 | "lng": -118.3632100701073
367 | },
368 | "southwest": {
369 | "lat": 34.05547787010728,
370 | "lng": -118.3659097298927
371 | }
372 | }
373 | },
374 | "icon": "https://maps.gstatic.com/mapfiles/place_api/icons/restaurant-71.png",
375 | "id": "6f73d8dfd2f3dc8b08d127ea3d0691b33030973b",
376 | "name": "Rahel Ethiopian Vegan Cuisine",
377 | "opening_hours": {
378 | "open_now": false,
379 | "weekday_text": []
380 | },
381 | "photos": [{
382 | "height": 2988,
383 | "html_attributions": [
384 | "Edna Banayat"
385 | ],
386 | "photo_reference": "CmRaAAAAsltyy0_FJzHaxUaRfy3ks0qgLs8N0x2Y7dZqKDw21NUi7D9STyFscfcUKiL9z9E2LXC3jrIqq6vikR6j0hb8_L7kCDr1WjgVUamyzOw3GdXSmaDVEQrOWDkBSGWGCvSAEhA9zXJDgrZrtdC90Jah1VWnGhRr8wcVDTal8O1R7S6WOQ0482phCw",
387 | "width": 5312
388 | }],
389 | "place_id": "ChIJqeeNwhW5woARdmN2K2Q107A",
390 | "price_level": 2,
391 | "rating": 4.6,
392 | "reference": "CmRbAAAAoNTyxGrtvDexCku_OpmL47Akpq5T587ncWV1Rp8xWIc7YQlqk75_0h2_nB0bduMRadfyHsW6i6SXFUNicU0BWvaGGy3Mtta6Omd-9lePm-ZN2EQBbuT05kFBNHkWbs9zEhCfXjvnfKzALRwRyDKEeQr-GhRIXuOSJHoM7g6L-V83WDXl3mxUgQ",
393 | "types": [
394 | "restaurant",
395 | "food",
396 | "point_of_interest",
397 | "establishment"
398 | ]
399 | },
400 | {
401 | "formatted_address": "4664, 10438 National Blvd, Los Angeles, CA 90034, United States",
402 | "geometry": {
403 | "location": {
404 | "lat": 34.0285781,
405 | "lng": -118.4115541
406 | },
407 | "viewport": {
408 | "northeast": {
409 | "lat": 34.02999047989272,
410 | "lng": -118.4102540701073
411 | },
412 | "southwest": {
413 | "lat": 34.02729082010728,
414 | "lng": -118.4129537298927
415 | }
416 | }
417 | },
418 | "icon": "https://maps.gstatic.com/mapfiles/place_api/icons/restaurant-71.png",
419 | "id": "92cc1f89e314ba9e518f1d7aa345a57441affe39",
420 | "name": "The Vegan Joint (West LA)",
421 | "opening_hours": {
422 | "open_now": false,
423 | "weekday_text": []
424 | },
425 | "photos": [{
426 | "height": 421,
427 | "html_attributions": [
428 | "The Vegan Joint (West LA)"
429 | ],
430 | "photo_reference": "CmRaAAAAN3v7CUQbGdsnPNSZ26qvp11iqa78bJjwe_P9pr0rA0meuKU916gHo0OktysWHWB-ZhFRtiVGYQeTm6mQUVChAxglotv9Tgv4VTRAIFdy_-WeLSvkQz-KhOf-mtvmfObfEhCOC0m29jY1Dvm0Xz_l2s1sGhT-9vWMge8CoQrb5wpFAtLrUB8phg",
431 | "width": 750
432 | }],
433 | "place_id": "ChIJ74X8ncu7woARzxiCmkqeeXc",
434 | "price_level": 2,
435 | "rating": 4.4,
436 | "reference": "CmRbAAAAh4e7HwMd83IQ1TW182b2n_qdhUDULWHH33HQjJOT_DGzaFV2HyLLEnN4z07DGn_-S0GsA2mwy3hvI-GFIKW1hM-VO8MOaJkiFMfWW347qlVy6iYmltY0DzU_-eA02rHPEhC0AGDxyCgGEH-2PrMsG0_ZGhTFDKatE5VBRZkTdWAaYfzSrs3QDg",
437 | "types": [
438 | "restaurant",
439 | "food",
440 | "point_of_interest",
441 | "establishment"
442 | ]
443 | },
444 | {
445 | "formatted_address": "3655 S Grand Ave, Los Angeles, CA 90007, USA",
446 | "geometry": {
447 | "location": {
448 | "lat": 34.017339,
449 | "lng": -118.278469
450 | },
451 | "viewport": {
452 | "northeast": {
453 | "lat": 34.01859817989273,
454 | "lng": -118.2769120701073
455 | },
456 | "southwest": {
457 | "lat": 34.01589852010728,
458 | "lng": -118.2796117298927
459 | }
460 | }
461 | },
462 | "icon": "https://maps.gstatic.com/mapfiles/place_api/icons/restaurant-71.png",
463 | "id": "da772e983e3e6dbc0cd042042311ab79839d1f28",
464 | "name": "Azla Ethiopian Vegan Cuisine",
465 | "opening_hours": {
466 | "open_now": false,
467 | "weekday_text": []
468 | },
469 | "photos": [{
470 | "height": 3024,
471 | "html_attributions": [
472 | "Benjamin Rubin"
473 | ],
474 | "photo_reference": "CmRaAAAARwfVFSLVUmk_dVkBaY43M8sPng1XdXX7Is882vzG7KcFewp2dMMlYqwKQSGyfAOSWZ3vJrgBUEypUwJESuqoxRneTLjp9W-XcQ4pQByDVabpYbT5r5y006esU3aTeHvIEhDXtTfa4Kh1z_-u4D_BFJT2GhSQDwL9zFfvKbwRuGmmcui08SdiLg",
475 | "width": 4032
476 | }],
477 | "place_id": "ChIJmb4BkgrIwoAR-PS33wrjk_M",
478 | "rating": 4.8,
479 | "reference": "CmRbAAAA5lMrGEVyK5VhjDMMJZ2tUR0Poo9zQt4wZZBcW46e3CfsYUN0INPZKeeKERZJwkhcogLK-FsKAZI92jncCYN4RZQYqtyiJONx3I6VWIrTXGI9rOt01dSTtBCZEKtag0AcEhBlaMFWX2bnU8gMjCv3Fs1AGhSs8gJhrAa-jQNLRX7pxzIqBJ_HQg",
480 | "types": [
481 | "restaurant",
482 | "food",
483 | "point_of_interest",
484 | "establishment"
485 | ]
486 | },
487 | {
488 | "formatted_address": "8101½ Beverly Blvd, Los Angeles, CA 90048, USA",
489 | "geometry": {
490 | "location": {
491 | "lat": 34.0764288,
492 | "lng": -118.3661624
493 | },
494 | "viewport": {
495 | "northeast": {
496 | "lat": 34.07760962989272,
497 | "lng": -118.3648135201073
498 | },
499 | "southwest": {
500 | "lat": 34.07490997010728,
501 | "lng": -118.3675131798927
502 | }
503 | }
504 | },
505 | "icon": "https://maps.gstatic.com/mapfiles/place_api/icons/restaurant-71.png",
506 | "id": "1924e9b97b3101ea1d18ac33191e8c2d8fffcbc2",
507 | "name": "Araya's Vegetarian Place",
508 | "opening_hours": {
509 | "open_now": false,
510 | "weekday_text": []
511 | },
512 | "photos": [{
513 | "height": 3088,
514 | "html_attributions": [
515 | "Simon Catford"
516 | ],
517 | "photo_reference": "CmRaAAAAyNe-nZSiUqUrfU3RKs7KqCG2-yoYpRixjGySI1LQgkDKkmRycyaz8RNyaVTYD8bqIf8FpmH7MOvC84sF6gVTsOBKI3IUGeyx54Uxntf8IkZOx4MjDr-Gdh_e40hXYKePEhApDNZUIBhEwnQHrWJqcBA1GhSN3DbanyozQB0pl8wVQucBZbsg1g",
518 | "width": 4160
519 | }],
520 | "place_id": "ChIJV-aT-jS5woAROYkaRCGf2s8",
521 | "price_level": 2,
522 | "rating": 4.6,
523 | "reference": "CmRbAAAAeSSwdHRB2eK9ro7pBWjJZeiDbH12E8AhhMyUsosVZeCMtzmNgPNfRgbS96NRPgdbWrudWjLJrKcomMqCyH8PWpA8rhSKHf7Kj7u7rgBv0r3b4kowdzJ4VUe8WyNoGQ_VEhBVcix6m4C5QlEf-G3y7AveGhTqHZ55gdeHglCukJFsCIpkzMR9Cw",
524 | "types": [
525 | "restaurant",
526 | "food",
527 | "point_of_interest",
528 | "establishment"
529 | ]
530 | },
531 | {
532 | "formatted_address": "4507 S Centinela Ave, Los Angeles, CA 90066, USA",
533 | "geometry": {
534 | "location": {
535 | "lat": 33.9925942,
536 | "lng": -118.4232475
537 | },
538 | "viewport": {
539 | "northeast": {
540 | "lat": 33.99401072989273,
541 | "lng": -118.4218230701073
542 | },
543 | "southwest": {
544 | "lat": 33.99131107010729,
545 | "lng": -118.4245227298927
546 | }
547 | }
548 | },
549 | "icon": "https://maps.gstatic.com/mapfiles/place_api/icons/restaurant-71.png",
550 | "id": "8f5ac5552a0e53f7ae627916c3d1d48b45030f54",
551 | "name": "LA Vegan",
552 | "opening_hours": {
553 | "open_now": false,
554 | "weekday_text": []
555 | },
556 | "photos": [{
557 | "height": 3644,
558 | "html_attributions": [
559 | "Eric Estenzo"
560 | ],
561 | "photo_reference": "CmRaAAAAdcatw_mFed_48beFNIsyCrjLRenujvkj0YkhN5PX8oJQAr013xQEGat49VQ1GmrvEMRTV8CIeWtBOYSwzIWUrD7kyY1c3sgmx-0y_ecKUIuSlxhlbDtjQeCa3U8SV5yGEhAGwkRlZ2kuFsZ5uBKLh79CGhRKQDVIo1Z_ijdaHPLZqjuRU8mcew",
562 | "width": 7012
563 | }],
564 | "place_id": "ChIJZUzGG266woARXsRdJc2silw",
565 | "price_level": 2,
566 | "rating": 4.6,
567 | "reference": "CmRbAAAAXBx1MbFAy5X5HJqg1yMWtv10pIpp5Qeq2lCD7y7dhJM5B8iHXgysRGXgjsD5eTeKnxuaM26srTPC3Ib7g1TfBAnaIcVVw7K0-JEiuRzs-dBnuj_1ey5A0r3zvXOQpcN0EhB18uIS3_Rj_T61b2Sfo0ecGhSmROJ3Of8T44ckuMzOqKUJsZng8Q",
568 | "types": [
569 | "restaurant",
570 | "food",
571 | "point_of_interest",
572 | "establishment"
573 | ]
574 | },
575 | {
576 | "formatted_address": "8393 Beverly Blvd, Los Angeles, CA 90048, USA",
577 | "geometry": {
578 | "location": {
579 | "lat": 34.0764345,
580 | "lng": -118.3730332
581 | },
582 | "viewport": {
583 | "northeast": {
584 | "lat": 34.07760002989272,
585 | "lng": -118.3716821701073
586 | },
587 | "southwest": {
588 | "lat": 34.07490037010728,
589 | "lng": -118.3743818298927
590 | }
591 | }
592 | },
593 | "icon": "https://maps.gstatic.com/mapfiles/place_api/icons/restaurant-71.png",
594 | "id": "1d6756ade69dac8cf5ce42aeeeb793535b4504e8",
595 | "name": "Vegan Glory",
596 | "opening_hours": {
597 | "open_now": false,
598 | "weekday_text": []
599 | },
600 | "photos": [{
601 | "height": 570,
602 | "html_attributions": [
603 | "Thom Uber"
604 | ],
605 | "photo_reference": "CmRaAAAA1YSeQpoJrDP6swpc1vw2b364pPMsjw2s2DVZYV4AWfENCwY02zHen8VpfI27Vnh8762bVFs4kda28R2sH354xKhju0vBIEM4yRlQr_G1-NvcywFtMQE7mBVtgegTo6ADEhBBLf_S0InHdmZ-SVC1KX8zGhQS9hpDvsrqVNDPNuWKLSPHy34XoA",
606 | "width": 760
607 | }],
608 | "place_id": "ChIJyYMRL0u5woARId-DVapUFNk",
609 | "rating": 4.4,
610 | "reference": "CmRbAAAActbey6zjHroEmUk5ZVCyD6LR0-RHIuWbMAJW5v7PaA-V-pijZFT9MgDjuoCOYAy7asUfIXfLiPxli2mdJtUGTCsUW6qw91uHaZR6XQv62bUFwmM9qM6IB6gOcv4T5BKbEhD9Jo87T4pNpyFSgCZWcyMOGhT2Au7pVgKuofn6_B7s4vSFUNhXPA",
611 | "types": [
612 | "restaurant",
613 | "food",
614 | "point_of_interest",
615 | "establishment"
616 | ]
617 | },
618 | {
619 | "formatted_address": "1253 Vine St # 9, Los Angeles, CA 90038, USA",
620 | "geometry": {
621 | "location": {
622 | "lat": 34.093981,
623 | "lng": -118.327638
624 | },
625 | "viewport": {
626 | "northeast": {
627 | "lat": 34.09533472989273,
628 | "lng": -118.3257989701073
629 | },
630 | "southwest": {
631 | "lat": 34.09263507010728,
632 | "lng": -118.3284986298927
633 | }
634 | }
635 | },
636 | "icon": "https://maps.gstatic.com/mapfiles/place_api/icons/restaurant-71.png",
637 | "id": "a869aba570e8103a6ae09ae71335a38e99b11e01",
638 | "name": "Doomie's Home Cookin'",
639 | "opening_hours": {
640 | "open_now": false,
641 | "weekday_text": []
642 | },
643 | "photos": [{
644 | "height": 2400,
645 | "html_attributions": [
646 | "Joe Gebis"
647 | ],
648 | "photo_reference": "CmRaAAAA20MBConiFPlO2-6BuoTBSGSAUBJaYoX0_m0xj_MNE2BPaqhDDSNtUXMTcmiKKZw0umwGkTaBt-Y3kNfKwKfRAdNnW9byHMuvElqBWXZTPSPuTIN_4xJKt1tKsosLuIfBEhB_ZCYjBhFWDue7-Z-UcfFjGhRyD45yAH2035Ifu15OklnylBIfhA",
649 | "width": 3200
650 | }],
651 | "place_id": "ChIJ56wYlja_woAR7f2m0YbS8hk",
652 | "price_level": 2,
653 | "rating": 4.4,
654 | "reference": "CmRbAAAAObVl81DX-3CYhyQv0itdfPojjR7bwG0eAv2JpZb9ZYoVBNg9F0znMXC72bBsDctx7ogiPKPvagqMNtEFkHYG7aLMk5VO-xtR8sfDAgeEiHHZV24oStUWs4aY-xqrU8W4EhAok2aSJPg0eRFD7nXeJpzzGhRPGtkJYZeCZHvy9UctDGItmFdbKg",
655 | "types": [
656 | "restaurant",
657 | "food",
658 | "point_of_interest",
659 | "establishment"
660 | ]
661 | },
662 | {
663 | "formatted_address": "710 W 1st St, Los Angeles, CA 90012, USA",
664 | "geometry": {
665 | "location": {
666 | "lat": 34.056385,
667 | "lng": -118.2508724
668 | },
669 | "viewport": {
670 | "northeast": {
671 | "lat": 34.05794337989273,
672 | "lng": -118.2493326201073
673 | },
674 | "southwest": {
675 | "lat": 34.05524372010728,
676 | "lng": -118.2520322798927
677 | }
678 | }
679 | },
680 | "icon": "https://maps.gstatic.com/mapfiles/place_api/icons/restaurant-71.png",
681 | "id": "53b4341b80e3596c05431abaa9b42b3fb0adf66d",
682 | "name": "Âu Lạc LA",
683 | "opening_hours": {
684 | "open_now": false,
685 | "weekday_text": []
686 | },
687 | "photos": [{
688 | "height": 3036,
689 | "html_attributions": [
690 | "Patrick Donnelly"
691 | ],
692 | "photo_reference": "CmRaAAAAmofVPSjKMklO3J2mpcRl_kHAwl710EqByuQjaP8rjOtmj08yJKKr1xcJICtaZ-quKiiG2aYUhFru5JF1eYeDHx-pILIPOmKspUN07oGjhMwKnUa73wikXvM-FVJuJcRMEhDLVub-Rx6D4N3iiN0JAtHhGhQQ2Un8dIsylPeLN9IcRcerUif-1w",
693 | "width": 4048
694 | }],
695 | "place_id": "ChIJe0sPnFLGwoARxFDUYFEjhuQ",
696 | "price_level": 2,
697 | "rating": 4.5,
698 | "reference": "CmRbAAAAHb0M5CcLv12TZJiEq0QRs5nexZ-fBdKvzQmTE8w3j_-YHX15hSy7WYb3EuweeyxhuIBirsVr4uBMtSUP4KoClwz1l6uE0KgO5jHySb6a9Qi5_Hoju3iN55xLA38dCGPTEhBqVAmmstFHL-wzx6LKFLkWGhTdLVJpc_s2vuSY-OL3rJzZStmuFg",
699 | "types": [
700 | "bar",
701 | "restaurant",
702 | "food",
703 | "point_of_interest",
704 | "establishment"
705 | ]
706 | },
707 | {
708 | "formatted_address": "2870 Rowena Ave, Los Angeles, CA 90039, USA",
709 | "geometry": {
710 | "location": {
711 | "lat": 34.107701,
712 | "lng": -118.2667943
713 | },
714 | "viewport": {
715 | "northeast": {
716 | "lat": 34.10916667989272,
717 | "lng": -118.2653993701072
718 | },
719 | "southwest": {
720 | "lat": 34.10646702010727,
721 | "lng": -118.2680990298927
722 | }
723 | }
724 | },
725 | "icon": "https://maps.gstatic.com/mapfiles/place_api/icons/restaurant-71.png",
726 | "id": "199ee04e9dbcb33b16d3aed792b6c0cc0e441d8d",
727 | "name": "Little Pine",
728 | "opening_hours": {
729 | "open_now": false,
730 | "weekday_text": []
731 | },
732 | "photos": [{
733 | "height": 563,
734 | "html_attributions": [
735 | "Karen TenEyck"
736 | ],
737 | "photo_reference": "CmRaAAAAoYVVT_rhyzFXzPVGgWSkRNXU5Fqm7AC5BcX7obilkGQX_cqVfu74Jb4Dm77gWDtvF3uov5Oeu06JutufKGQwbWlBJmdH0O3xCDoNA-t5uPOHjvZu26A2WYA3JtaMR4TzEhAbM1Fsz-8UUaZOh37fbEs3GhRdnpshI2u0QbwbL0KbAa_IiNPPdg",
738 | "width": 1000
739 | }],
740 | "place_id": "ChIJjc_P0c3AwoARBWVMPN_YVRU",
741 | "price_level": 2,
742 | "rating": 4.7,
743 | "reference": "CmRbAAAAugyZhEPl2BK5sW7_AnhEZv-d-21pR6GzooTVD3RJFsDF9hecZMusog36IO34t7wuFczZWyD8kEiVKilj4Sp9buBdtO077raxcOkVC6--BXKuhGrh2Lkq5dhR2pLXNLXZEhBKcQzGE58Xr82Fz7Oy--MaGhTT7qBjkenQ3jVinJpgqASlYR5c0g",
744 | "types": [
745 | "restaurant",
746 | "food",
747 | "point_of_interest",
748 | "establishment"
749 | ]
750 | },
751 | {
752 | "formatted_address": "Little Tokyo Shopping Center, 333 South Alameda St #310, Los Angeles, CA 90013, USA",
753 | "geometry": {
754 | "location": {
755 | "lat": 34.0450139,
756 | "lng": -118.2388682
757 | },
758 | "viewport": {
759 | "northeast": {
760 | "lat": 34.04636372989273,
761 | "lng": -118.2375183701073
762 | },
763 | "southwest": {
764 | "lat": 34.04366407010728,
765 | "lng": -118.2402180298928
766 | }
767 | }
768 | },
769 | "icon": "https://maps.gstatic.com/mapfiles/place_api/icons/restaurant-71.png",
770 | "id": "bdc0c9353ceb91395214f3f84f0016059271acd7",
771 | "name": "Shojin",
772 | "opening_hours": {
773 | "open_now": false,
774 | "weekday_text": []
775 | },
776 | "photos": [{
777 | "height": 2336,
778 | "html_attributions": [
779 | "Roman Roze"
780 | ],
781 | "photo_reference": "CmRaAAAALLVT3JKv4U1gRmbItfHhzOIdYRIibKmLMWThQa0gYU-umKbevliJUIogQL_Hm1_6oWGlutmR4kuzd6ikrpM4_eIeLSprpxAlHiEmi3FVNN5XvnBxfV8xWj6ggQLNM8DxEhDscXYyU5r1aKlYTCVLG61tGhQsd9XLF9OR7UL9XZo_eOm1Y98hCw",
782 | "width": 3504
783 | }],
784 | "place_id": "ChIJyy_0pjnGwoARi47oqNbdedU",
785 | "price_level": 3,
786 | "rating": 4.6,
787 | "reference": "CmRbAAAAORx-wW_TP5eWz0P93UBThqK-FdOLxos-ptjGRGlmiFaOf6ySc43rGF1ENCNilSFQL0Q-7RtFawCJMdvApBCTEyHZnRIIx4Jp4cRIcvSxRBJzB-S3aWfAglqv4L_sO7zsEhAjm-hxEpM5a-8fqVVcqJAbGhQDjg_IeMxUnePapODPTZPxOis_Lw",
788 | "types": [
789 | "restaurant",
790 | "food",
791 | "point_of_interest",
792 | "establishment"
793 | ]
794 | },
795 | {
796 | "formatted_address": "2520 Glendale Blvd, Los Angeles, CA 90039, USA",
797 | "geometry": {
798 | "location": {
799 | "lat": 34.1031997,
800 | "lng": -118.2586152
801 | },
802 | "viewport": {
803 | "northeast": {
804 | "lat": 34.10455672989272,
805 | "lng": -118.2575728201073
806 | },
807 | "southwest": {
808 | "lat": 34.10185707010727,
809 | "lng": -118.2602724798928
810 | }
811 | }
812 | },
813 | "icon": "https://maps.gstatic.com/mapfiles/place_api/icons/restaurant-71.png",
814 | "id": "52be338abc98361f3c8664a7d4e853a11332afec",
815 | "name": "by CHLOE. Silver Lake",
816 | "opening_hours": {
817 | "open_now": false,
818 | "weekday_text": []
819 | },
820 | "photos": [{
821 | "height": 2988,
822 | "html_attributions": [
823 | "Jeannette G."
824 | ],
825 | "photo_reference": "CmRaAAAA4Yb-teP-cLR0wHYMwtNvoo-wPlgVsj444v-VTgdwi63Gshd-PV5ZMhVPQSN2v5yPM-lxvZQW7Redm4wNKPLmGku5MNs0yhMuBysvXO6gpJ6kF4PGCepMIcZQ409LpBD_EhD8ZeAMspzsx_RnDHSQkuDlGhQ3-VOGj2K85VfvxsOCAyIS4HdVnw",
826 | "width": 5312
827 | }],
828 | "place_id": "ChIJ4fYRfivHwoAR_2DlpZxO5_8",
829 | "price_level": 2,
830 | "rating": 4.5,
831 | "reference": "CmRbAAAALsJqmhwH-aLZPy6SNAbLNzQRQ3qfT92yX3k6t13Ynq9K5hQatkTEBPhTr-pqcF3RznWRRYDNUoyPSoT8irbse6j2VZUHsgvo7wvPPVSTeYaeB3gZYXNaFnJmcqyUUU4CEhAhjByzVT-NNxUGH3pcyuS-GhTSUc8pvZUzjDie_3LXHrkObrtDzA",
832 | "types": [
833 | "restaurant",
834 | "food",
835 | "point_of_interest",
836 | "establishment"
837 | ]
838 | },
839 | {
840 | "formatted_address": "639 N Larchmont Blvd, Los Angeles, CA 90004, USA",
841 | "geometry": {
842 | "location": {
843 | "lat": 34.0828183,
844 | "lng": -118.3241586
845 | },
846 | "viewport": {
847 | "northeast": {
848 | "lat": 34.08422687989272,
849 | "lng": -118.3225729201073
850 | },
851 | "southwest": {
852 | "lat": 34.08152722010728,
853 | "lng": -118.3252725798928
854 | }
855 | }
856 | },
857 | "icon": "https://maps.gstatic.com/mapfiles/place_api/icons/restaurant-71.png",
858 | "id": "21f5bb524cea1c8698187b817298300df290ad1b",
859 | "name": "Cafe Gratitude",
860 | "opening_hours": {
861 | "open_now": false,
862 | "weekday_text": []
863 | },
864 | "photos": [{
865 | "height": 1365,
866 | "html_attributions": [
867 | "Cafe Gratitude"
868 | ],
869 | "photo_reference": "CmRaAAAAX_eBNo4lUv8qGy5th3upd5Qp_Gx9NEQ55wfy0Yb7l1L_0M_gIwQqPb55RiJivmK_Wu3_CiyjBwEVGeeQCGRPHanVPYIG7_fQGV3_o2QgPSgQOAEfeKvuvdeUFGLX2TMzEhANtSJNyyzGbezwvsEmgvklGhSNKWwiEgOHaw28SqiWmqm1aAWDJQ",
870 | "width": 2048
871 | }],
872 | "place_id": "ChIJjRPL7sm4woARFcIwlfejAGA",
873 | "price_level": 2,
874 | "rating": 4.5,
875 | "reference": "CmRbAAAANXEcxEtfEYz2V5OIwdFEuyw35xZZ7SpF9AVuYFUqha3tf8CiZbuYrXLCG_y-twfXpcRiYyN0kXDfwmjA-uPZQaPbfpriBY397myHuiFF1A1ESI-5Hcb1y6UiRger5g3tEhCRq3Ml_vdNqA8AGua9iLXrGhTz1KC9902YirqAu7KLW4Bq7FYOEQ",
876 | "types": [
877 | "cafe",
878 | "restaurant",
879 | "food",
880 | "point_of_interest",
881 | "establishment"
882 | ]
883 | }
884 | ],
885 | "status": "OK"
886 | }
887 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | height: 40vmin;
7 | pointer-events: none;
8 | }
9 |
10 | @media (prefers-reduced-motion: no-preference) {
11 | .App-logo {
12 | animation: App-logo-spin infinite 20s linear;
13 | }
14 | }
15 |
16 | .App-header {
17 | background-color: #282c34;
18 | min-height: 100vh;
19 | display: flex;
20 | flex-direction: column;
21 | align-items: center;
22 | justify-content: center;
23 | font-size: calc(10px + 2vmin);
24 | color: white;
25 | }
26 |
27 | .App-link {
28 | color: #61dafb;
29 | }
30 |
31 | @keyframes App-logo-spin {
32 | from {
33 | transform: rotate(0deg);
34 | }
35 | to {
36 | transform: rotate(360deg);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import logo from './logo.svg';
3 | import './App.css';
4 |
5 | function App() {
6 | return (
7 |
27 | );
28 | }
29 |
30 | export default App;
31 |
--------------------------------------------------------------------------------
/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 | import App from './App';
4 |
5 | test('renders learn react link', () => {
6 | const { getByText } = render();
7 | const linkElement = getByText(/learn react/i);
8 | expect(linkElement).toBeInTheDocument();
9 | });
10 |
--------------------------------------------------------------------------------
/src/components/AutoComplete.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import styled from 'styled-components';
3 |
4 | const Wrapper = styled.div`
5 | position: relative;
6 | align-items: center;
7 | justify-content: center;
8 | width: 100%;
9 | padding: 20px;
10 | `;
11 |
12 | class AutoComplete extends Component {
13 | constructor(props) {
14 | super(props);
15 | this.clearSearchBox = this.clearSearchBox.bind(this);
16 | }
17 |
18 | componentDidMount({ map, mapApi } = this.props) {
19 | const options = {
20 | // restrict your search to a specific type of result
21 | // types: ['geocode', 'address', 'establishment', '(regions)', '(cities)'],
22 | // restrict your search to a specific country, or an array of countries
23 | // componentRestrictions: { country: ['gb', 'us'] },
24 | };
25 | this.autoComplete = new mapApi.places.Autocomplete(
26 | this.searchInput,
27 | options,
28 | );
29 | this.autoComplete.addListener('place_changed', this.onPlaceChanged);
30 | this.autoComplete.bindTo('bounds', map);
31 | }
32 |
33 | componentWillUnmount({ mapApi } = this.props) {
34 | mapApi.event.clearInstanceListeners(this.searchInput);
35 | }
36 |
37 | onPlaceChanged = ({ map, addplace } = this.props) => {
38 | const place = this.autoComplete.getPlace();
39 |
40 | if (!place.geometry) return;
41 | if (place.geometry.viewport) {
42 | map.fitBounds(place.geometry.viewport);
43 | } else {
44 | map.setCenter(place.geometry.location);
45 | map.setZoom(17);
46 | }
47 |
48 | addplace(place);
49 | this.searchInput.blur();
50 | };
51 |
52 | clearSearchBox() {
53 | this.searchInput.value = '';
54 | }
55 |
56 | render() {
57 | return (
58 |
59 | {
61 | this.searchInput = ref;
62 | }}
63 | type="text"
64 | onFocus={this.clearSearchBox}
65 | placeholder="Enter a location"
66 | />
67 |
68 | );
69 | }
70 | }
71 |
72 | export default AutoComplete;
73 |
--------------------------------------------------------------------------------
/src/components/GoogleMap.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import styled from 'styled-components';
4 | import GoogleMapReact from 'google-map-react';
5 |
6 | const Wrapper = styled.main`
7 | width: 100%;
8 | height: 100%;
9 | `;
10 |
11 | const GoogleMap = ({ children, ...props }) => (
12 |
13 |
19 | {children}
20 |
21 |
22 | );
23 |
24 | GoogleMap.propTypes = {
25 | children: PropTypes.oneOfType([
26 | PropTypes.node,
27 | PropTypes.arrayOf(PropTypes.node),
28 | ]),
29 | };
30 |
31 | GoogleMap.defaultProps = {
32 | children: null,
33 | };
34 |
35 | export default GoogleMap;
36 |
--------------------------------------------------------------------------------
/src/components/Marker.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import styled from 'styled-components';
4 |
5 | const Wrapper = styled.div`
6 | position: absolute;
7 | top: 50%;
8 | left: 50%;
9 | width: 18px;
10 | height: 18px;
11 | background-color: #000;
12 | border: 2px solid #fff;
13 | border-radius: 100%;
14 | user-select: none;
15 | transform: translate(-50%, -50%);
16 | cursor: ${(props) => (props.onClick ? 'pointer' : 'default')};
17 | &:hover {
18 | z-index: 1;
19 | }
20 | `;
21 |
22 | const Marker = ({ text, onClick }) => (
23 |
27 | );
28 |
29 | Marker.defaultProps = {
30 | onClick: null,
31 | };
32 |
33 | Marker.propTypes = {
34 | onClick: PropTypes.func,
35 | text: PropTypes.string.isRequired,
36 | };
37 |
38 | export default Marker;
39 |
--------------------------------------------------------------------------------
/src/components/SearchBox.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import styled from 'styled-components';
3 |
4 | const Wrapper = styled.div`
5 | position: relative;
6 | align-items: center;
7 | justify-content: center;
8 | width: 100%;
9 | padding: 20px;
10 | `;
11 |
12 | class SearchBox extends Component {
13 | constructor(props) {
14 | super(props);
15 | this.clearSearchBox = this.clearSearchBox.bind(this);
16 | }
17 |
18 | componentDidMount({ map, mapApi } = this.props) {
19 | this.searchBox = new mapApi.places.SearchBox(this.searchInput);
20 | this.searchBox.addListener('places_changed', this.onPlacesChanged);
21 | this.searchBox.bindTo('bounds', map);
22 | }
23 |
24 | componentWillUnmount({ mapApi } = this.props) {
25 | mapApi.event.clearInstanceListeners(this.searchInput);
26 | }
27 |
28 | onPlacesChanged = ({ map, addplace } = this.props) => {
29 | const selected = this.searchBox.getPlaces();
30 | const { 0: place } = selected;
31 | if (!place.geometry) return;
32 | if (place.geometry.viewport) {
33 | map.fitBounds(place.geometry.viewport);
34 | } else {
35 | map.setCenter(place.geometry.location);
36 | map.setZoom(17);
37 | }
38 |
39 | addplace(selected);
40 | this.searchInput.blur();
41 | };
42 |
43 | clearSearchBox() {
44 | this.searchInput.value = '';
45 | }
46 |
47 | render() {
48 | return (
49 |
50 | {
52 | this.searchInput = ref;
53 | }}
54 | type="text"
55 | onFocus={this.clearSearchBox}
56 | placeholder="Enter a location"
57 | />
58 |
59 | );
60 | }
61 | }
62 |
63 | export default SearchBox;
64 |
--------------------------------------------------------------------------------
/src/const/la_center.js:
--------------------------------------------------------------------------------
1 | export default [34.0522, -118.2437];
2 |
--------------------------------------------------------------------------------
/src/examples/AutoComplete.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import isEmpty from 'lodash.isempty';
3 |
4 | // components:
5 | import Marker from '../components/Marker';
6 |
7 | // examples:
8 | import GoogleMap from '../components/GoogleMap';
9 | import AutoComplete from '../components/AutoComplete';
10 |
11 | // consts
12 | import LOS_ANGELES_CENTER from '../const/la_center';
13 |
14 | class Autocomplete extends Component {
15 | constructor(props) {
16 | super(props);
17 |
18 | this.state = {
19 | mapApiLoaded: false,
20 | mapInstance: null,
21 | mapApi: null,
22 | places: [],
23 | };
24 | }
25 |
26 | apiHasLoaded = (map, maps) => {
27 | this.setState({
28 | mapApiLoaded: true,
29 | mapInstance: map,
30 | mapApi: maps,
31 | });
32 | };
33 |
34 | addPlace = (place) => {
35 | this.setState({ places: [place] });
36 | };
37 |
38 | render() {
39 | const {
40 | places, mapApiLoaded, mapInstance, mapApi,
41 | } = this.state;
42 | return (
43 | <>
44 | {mapApiLoaded && (
45 |
46 | )}
47 | this.apiHasLoaded(map, maps)}
56 | >
57 | {!isEmpty(places)
58 | && places.map((place) => (
59 |
65 | ))}
66 |
67 | >
68 | );
69 | }
70 | }
71 |
72 | export default Autocomplete;
73 |
--------------------------------------------------------------------------------
/src/examples/Heatmap.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import isEmpty from 'lodash.isempty';
3 |
4 | // examples:
5 | import GoogleMap from '../components/GoogleMap';
6 |
7 | // consts
8 | import LOS_ANGELES_CENTER from '../const/la_center';
9 |
10 | class Heatmap extends Component {
11 | constructor(props) {
12 | super(props);
13 |
14 | this.state = {
15 | places: [],
16 | };
17 | }
18 |
19 | componentDidMount() {
20 | fetch('places.json')
21 | .then((response) => response.json())
22 | .then((data) => this.setState({ places: data.results }));
23 | }
24 |
25 | render() {
26 | const { places } = this.state;
27 | const data = places.map((place) => ({
28 | lat: place.geometry.location.lat,
29 | lng: place.geometry.location.lng,
30 | weight: Math.floor(Math.random() * Math.floor(5)),
31 | }));
32 | const heatmapData = {
33 | positions: data,
34 | options: {
35 | radius: 20,
36 | opacity: 1,
37 | },
38 | };
39 |
40 | return (
41 | <>
42 | {!isEmpty(places) && (
43 |
52 | )}
53 | >
54 | );
55 | }
56 | }
57 |
58 | export default Heatmap;
59 |
--------------------------------------------------------------------------------
/src/examples/Main.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import isEmpty from 'lodash.isempty';
3 |
4 | // components:
5 | import Marker from '../components/Marker';
6 |
7 | // examples:
8 | import GoogleMap from '../components/GoogleMap';
9 |
10 | // consts
11 | import LOS_ANGELES_CENTER from '../const/la_center';
12 |
13 | // Return map bounds based on list of places
14 | const getMapBounds = (map, maps, places) => {
15 | const bounds = new maps.LatLngBounds();
16 |
17 | places.forEach((place) => {
18 | bounds.extend(new maps.LatLng(
19 | place.geometry.location.lat,
20 | place.geometry.location.lng,
21 | ));
22 | });
23 | return bounds;
24 | };
25 |
26 | // Re-center map when resizing the window
27 | const bindResizeListener = (map, maps, bounds) => {
28 | maps.event.addDomListenerOnce(map, 'idle', () => {
29 | maps.event.addDomListener(window, 'resize', () => {
30 | map.fitBounds(bounds);
31 | });
32 | });
33 | };
34 |
35 | // Fit map to its bounds after the api is loaded
36 | const apiIsLoaded = (map, maps, places) => {
37 | // Get bounds by our places
38 | const bounds = getMapBounds(map, maps, places);
39 | // Fit map to bounds
40 | map.fitBounds(bounds);
41 | // Bind the resize listener
42 | bindResizeListener(map, maps, bounds);
43 | };
44 |
45 | class Main extends Component {
46 | constructor(props) {
47 | super(props);
48 |
49 | this.state = {
50 | places: [],
51 | };
52 | }
53 |
54 | componentDidMount() {
55 | fetch('places.json')
56 | .then((response) => response.json())
57 | .then((data) => this.setState({ places: data.results }));
58 | }
59 |
60 | render() {
61 | const { places } = this.state;
62 | return (
63 | <>
64 | {!isEmpty(places) && (
65 | apiIsLoaded(map, maps, places)}
70 | >
71 | {places.map((place) => (
72 |
78 | ))}
79 |
80 | )}
81 | >
82 | );
83 | }
84 | }
85 |
86 | export default Main;
87 |
--------------------------------------------------------------------------------
/src/examples/MarkerInfoWindow.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 | import isEmpty from 'lodash.isempty';
4 |
5 | // examples:
6 | import GoogleMap from '../components/GoogleMap';
7 |
8 | // consts: [34.0522, -118.2437]
9 | import LOS_ANGELES_CENTER from '../const/la_center';
10 |
11 | // InfoWindow component
12 | const InfoWindow = (props) => {
13 | const { place } = props;
14 | const infoWindowStyle = {
15 | position: 'relative',
16 | bottom: 150,
17 | left: '-45px',
18 | width: 220,
19 | backgroundColor: 'white',
20 | boxShadow: '0 2px 7px 1px rgba(0, 0, 0, 0.3)',
21 | padding: 10,
22 | fontSize: 14,
23 | zIndex: 100,
24 | };
25 |
26 | return (
27 |
28 |
29 | {place.name}
30 |
31 |
32 |
33 | {place.rating}
34 | {' '}
35 |
36 |
37 | {String.fromCharCode(9733).repeat(Math.floor(place.rating))}
38 |
39 |
40 | {String.fromCharCode(9733).repeat(5 - Math.floor(place.rating))}
41 |
42 |
43 |
44 | {place.types[0]}
45 |
46 |
47 | {'$'.repeat(place.price_level)}
48 |
49 |
50 | {place.opening_hours.open_now ? 'Open' : 'Closed'}
51 |
52 |
53 | );
54 | };
55 |
56 | // Marker component
57 | const Marker = ({ show, place }) => {
58 | const markerStyle = {
59 | border: '1px solid white',
60 | borderRadius: '50%',
61 | height: 10,
62 | width: 10,
63 | backgroundColor: show ? 'red' : 'blue',
64 | cursor: 'pointer',
65 | zIndex: 10,
66 | };
67 |
68 | return (
69 | <>
70 |
71 | {show && }
72 | >
73 | );
74 | };
75 |
76 | class MarkerInfoWindow extends Component {
77 | constructor(props) {
78 | super(props);
79 |
80 | this.state = {
81 | places: [],
82 | };
83 | }
84 |
85 | componentDidMount() {
86 | fetch('places.json')
87 | .then((response) => response.json())
88 | .then((data) => {
89 | data.results.forEach((result) => {
90 | result.show = false; // eslint-disable-line no-param-reassign
91 | });
92 | this.setState({ places: data.results });
93 | });
94 | }
95 |
96 | // onChildClick callback can take two arguments: key and childProps
97 | onChildClickCallback = (key) => {
98 | this.setState((state) => {
99 | const index = state.places.findIndex((e) => e.id === key);
100 | state.places[index].show = !state.places[index].show; // eslint-disable-line no-param-reassign
101 | return { places: state.places };
102 | });
103 | };
104 |
105 | render() {
106 | const { places } = this.state;
107 |
108 | return (
109 | <>
110 | {!isEmpty(places) && (
111 |
117 | {places.map((place) => (
118 |
125 | ))}
126 |
127 | )}
128 | >
129 | );
130 | }
131 | }
132 |
133 | InfoWindow.propTypes = {
134 | place: PropTypes.shape({
135 | name: PropTypes.string,
136 | formatted_address: PropTypes.string,
137 | rating: PropTypes.number,
138 | types: PropTypes.arrayOf(PropTypes.string),
139 | price_level: PropTypes.number,
140 | opening_hours: PropTypes.shape({
141 | open_now: PropTypes.bool,
142 | }),
143 | }).isRequired,
144 | };
145 |
146 | Marker.propTypes = {
147 | show: PropTypes.bool.isRequired,
148 | place: PropTypes.shape({
149 | name: PropTypes.string,
150 | formatted_address: PropTypes.string,
151 | rating: PropTypes.number,
152 | types: PropTypes.arrayOf(PropTypes.string),
153 | price_level: PropTypes.number,
154 | opening_hours: PropTypes.shape({
155 | open_now: PropTypes.bool,
156 | }),
157 | }).isRequired,
158 | };
159 |
160 | export default MarkerInfoWindow;
161 |
--------------------------------------------------------------------------------
/src/examples/MarkerInfoWindowGmapsObj.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import isEmpty from 'lodash.isempty';
3 |
4 | // examples:
5 | import GoogleMap from '../components/GoogleMap';
6 |
7 | // consts: [34.0522, -118.2437]
8 | import LOS_ANGELES_CENTER from '../const/la_center';
9 |
10 | const getInfoWindowString = (place) => `
11 |
12 |
13 | ${place.name}
14 |
15 |
16 |
17 | ${place.rating}
18 |
19 | ${String.fromCharCode(9733).repeat(Math.floor(place.rating))}${String.fromCharCode(9733).repeat(5 - Math.floor(place.rating))}
20 |
21 |
22 | ${place.types[0]}
23 |
24 |
25 | ${'$'.repeat(place.price_level)}
26 |
27 |
28 | ${place.opening_hours.open_now ? 'Open' : 'Closed'}
29 |
30 |
`;
31 |
32 | // Refer to https://github.com/google-map-react/google-map-react#use-google-maps-api
33 | const handleApiLoaded = (map, maps, places) => {
34 | const markers = [];
35 | const infowindows = [];
36 |
37 | places.forEach((place) => {
38 | markers.push(new maps.Marker({
39 | position: {
40 | lat: place.geometry.location.lat,
41 | lng: place.geometry.location.lng,
42 | },
43 | map,
44 | }));
45 |
46 | infowindows.push(new maps.InfoWindow({
47 | content: getInfoWindowString(place),
48 | }));
49 | });
50 |
51 | markers.forEach((marker, i) => {
52 | marker.addListener('click', () => {
53 | infowindows[i].open(map, marker);
54 | });
55 | });
56 | };
57 |
58 | class MarkerInfoWindowGmapsObj extends Component {
59 | constructor(props) {
60 | super(props);
61 |
62 | this.state = {
63 | places: [],
64 | };
65 | }
66 |
67 | componentDidMount() {
68 | fetch('places.json')
69 | .then((response) => response.json())
70 | .then((data) => {
71 | data.results.forEach((result) => {
72 | result.show = false; // eslint-disable-line no-param-reassign
73 | });
74 | this.setState({ places: data.results });
75 | });
76 | }
77 |
78 | render() {
79 | const { places } = this.state;
80 |
81 | return (
82 | <>
83 | {!isEmpty(places) && (
84 | handleApiLoaded(map, maps, places)}
90 | />
91 | )}
92 | >
93 | );
94 | }
95 | }
96 |
97 | export default MarkerInfoWindowGmapsObj;
98 |
--------------------------------------------------------------------------------
/src/examples/Searchbox.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import isEmpty from 'lodash.isempty';
3 |
4 | // components:
5 | import Marker from '../components/Marker';
6 |
7 | // examples:
8 | import GoogleMap from '../components/GoogleMap';
9 | import SearchBox from '../components/SearchBox';
10 |
11 | // consts
12 | import LOS_ANGELES_CENTER from '../const/la_center';
13 |
14 | class Searchbox extends Component {
15 | constructor(props) {
16 | super(props);
17 |
18 | this.state = {
19 | mapApiLoaded: false,
20 | mapInstance: null,
21 | mapApi: null,
22 | places: [],
23 | };
24 | }
25 |
26 | apiHasLoaded = (map, maps) => {
27 | this.setState({
28 | mapApiLoaded: true,
29 | mapInstance: map,
30 | mapApi: maps,
31 | });
32 | };
33 |
34 | addPlace = (place) => {
35 | this.setState({ places: place });
36 | };
37 |
38 | render() {
39 | const {
40 | places, mapApiLoaded, mapInstance, mapApi,
41 | } = this.state;
42 | return (
43 | <>
44 | {mapApiLoaded && }
45 | this.apiHasLoaded(map, maps)}
54 | >
55 | {!isEmpty(places)
56 | && places.map((place) => (
57 |
63 | ))}
64 |
65 | >
66 | );
67 | }
68 | }
69 |
70 | export default Searchbox;
71 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 | import * as serviceWorker from './serviceWorker';
6 |
7 | ReactDOM.render(
8 |
9 |
10 | ,
11 | document.getElementById('root'),
12 | );
13 |
14 | // If you want your app to work offline and load faster, you can change
15 | // unregister() to register() below. Note this comes with some pitfalls.
16 | // Learn more about service workers: https://bit.ly/CRA-PWA
17 | serviceWorker.unregister();
18 |
--------------------------------------------------------------------------------
/src/logo.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/src/serviceWorker.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | // This optional code is used to register a service worker.
3 | // register() is not called by default.
4 |
5 | // This lets the app load faster on subsequent visits in production, and gives
6 | // it offline capabilities. However, it also means that developers (and users)
7 | // will only see deployed updates on subsequent visits to a page, after all the
8 | // existing tabs open on the page have been closed, since previously cached
9 | // resources are updated in the background.
10 |
11 | // To learn more about the benefits of this model and instructions on how to
12 | // opt-in, read https://bit.ly/CRA-PWA
13 |
14 | const isLocalhost = Boolean(window.location.hostname === 'localhost' ||
15 | // [::1] is the IPv6 localhost address.
16 | window.location.hostname === '[::1]' ||
17 | // 127.0.0.0/8 are considered localhost for IPv4.
18 | window.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/));
19 |
20 | export function register(config) {
21 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
22 | // The URL constructor is available in all browsers that support SW.
23 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
24 | if (publicUrl.origin !== window.location.origin) {
25 | // Our service worker won't work if PUBLIC_URL is on a different origin
26 | // from what our page is served on. This might happen if a CDN is used to
27 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
28 | return;
29 | }
30 |
31 | window.addEventListener('load', () => {
32 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
33 |
34 | if (isLocalhost) {
35 | // This is running on localhost. Let's check if a service worker still exists or not.
36 | checkValidServiceWorker(swUrl, config);
37 |
38 | // Add some additional logging to localhost, pointing developers to the
39 | // service worker/PWA documentation.
40 | navigator.serviceWorker.ready.then(() => {
41 | console.log('This web app is being served cache-first by a service ' +
42 | 'worker. To learn more, visit https://bit.ly/CRA-PWA');
43 | });
44 | } else {
45 | // Is not localhost. Just register service worker
46 | registerValidSW(swUrl, config);
47 | }
48 | });
49 | }
50 | }
51 |
52 | function registerValidSW(swUrl, config) {
53 | navigator.serviceWorker
54 | .register(swUrl)
55 | .then((registration) => {
56 | registration.onupdatefound = () => {
57 | const installingWorker = registration.installing;
58 | if (installingWorker == null) {
59 | return;
60 | }
61 | installingWorker.onstatechange = () => {
62 | if (installingWorker.state === 'installed') {
63 | if (navigator.serviceWorker.controller) {
64 | // At this point, the updated precached content has been fetched,
65 | // but the previous service worker will still serve the older
66 | // content until all client tabs are closed.
67 | console.log('New content is available and will be used when all ' +
68 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.');
69 |
70 | // Execute callback
71 | if (config && config.onUpdate) {
72 | config.onUpdate(registration);
73 | }
74 | } else {
75 | // At this point, everything has been precached.
76 | // It's the perfect time to display a
77 | // "Content is cached for offline use." message.
78 | console.log('Content is cached for offline use.');
79 |
80 | // Execute callback
81 | if (config && config.onSuccess) {
82 | config.onSuccess(registration);
83 | }
84 | }
85 | }
86 | };
87 | };
88 | })
89 | .catch((error) => {
90 | console.error('Error during service worker registration:', error);
91 | });
92 | }
93 |
94 | function checkValidServiceWorker(swUrl, config) {
95 | // Check if the service worker can be found. If it can't reload the page.
96 | fetch(swUrl, {
97 | headers: { 'Service-Worker': 'script' },
98 | })
99 | .then((response) => {
100 | // Ensure service worker exists, and that we really are getting a JS file.
101 | const contentType = response.headers.get('content-type');
102 | if (
103 | response.status === 404 ||
104 | (contentType != null && contentType.indexOf('javascript') === -1)
105 | ) {
106 | // No service worker found. Probably a different app. Reload the page.
107 | navigator.serviceWorker.ready.then((registration) => {
108 | registration.unregister().then(() => {
109 | window.location.reload();
110 | });
111 | });
112 | } else {
113 | // Service worker found. Proceed as normal.
114 | registerValidSW(swUrl, config);
115 | }
116 | })
117 | .catch(() => {
118 | console.log('No internet connection found. App is running in offline mode.');
119 | });
120 | }
121 |
122 | export function unregister() {
123 | if ('serviceWorker' in navigator) {
124 | navigator.serviceWorker.ready
125 | .then((registration) => {
126 | registration.unregister();
127 | })
128 | .catch((error) => {
129 | console.error(error.message);
130 | });
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom/extend-expect';
6 |
--------------------------------------------------------------------------------
/src/stories/Autocomplete/Base.stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 |
4 | import AutoComplete from '../../examples/AutoComplete';
5 |
6 | export default {
7 | title: 'AutoComplete Examples',
8 | };
9 |
10 | export const Base = () => (
11 |
12 |
13 |
14 | );
15 |
16 | const Wrapper = styled.section`
17 | width: 100vw;
18 | height: 100vh;
19 | `;
20 |
--------------------------------------------------------------------------------
/src/stories/ClickableMarkers/Base.stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 |
4 | import MarkerInfoWindow from '../../examples/MarkerInfoWindow';
5 | import MarkerInfoWindowGmapsObj from '../../examples/MarkerInfoWindowGmapsObj';
6 |
7 | export default {
8 | title: 'MarkerInfo Examples',
9 | };
10 |
11 | export const DefaultMarker = () => (
12 |
13 |
14 |
15 | );
16 |
17 | export const CustomComponent = () => (
18 |
19 |
20 |
21 | );
22 |
23 | const Wrapper = styled.section`
24 | width: 100vw;
25 | height: 100vh;
26 | `;
27 |
--------------------------------------------------------------------------------
/src/stories/Heatmap/Base.stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 |
4 | import Heatmap from '../../examples/Heatmap';
5 |
6 | export default {
7 | title: 'Heatmap Examples',
8 | };
9 |
10 | export const Base = () => (
11 |
12 |
13 |
14 | );
15 |
16 | const Wrapper = styled.section`
17 | width: 100vw;
18 | height: 100vh;
19 | `;
20 |
--------------------------------------------------------------------------------
/src/stories/Main/Base.stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 |
4 | import Main from '../../examples/Main';
5 |
6 | export default {
7 | title: 'Main Examples',
8 | };
9 |
10 | export const Base = () => (
11 |
12 |
13 |
14 | );
15 |
16 | const Wrapper = styled.section`
17 | width: 100vw;
18 | height: 100vh;
19 | `;
20 |
--------------------------------------------------------------------------------
/src/stories/Searchbox/Base.stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 |
4 | import Searchbox from '../../examples/Searchbox';
5 |
6 | export default {
7 | title: 'Searchbox Examples',
8 | };
9 |
10 | export const Base = () => (
11 |
12 |
13 |
14 | );
15 |
16 | const Wrapper = styled.section`
17 | width: 100vw;
18 | height: 100vh;
19 | `;
20 |
--------------------------------------------------------------------------------