easy-google-places 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +101 -0
- package/dist/api.d.ts +3 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/core.d.ts +75 -0
- package/dist/core.d.ts.map +1 -0
- package/dist/geometry.d.ts +12 -0
- package/dist/geometry.d.ts.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +229 -0
- package/dist/index.js.map +12 -0
- package/dist/types.d.ts +35 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +39 -0
package/README.md
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Easy Google Restaurants
|
|
2
|
+
|
|
3
|
+
A TypeScript library to query Google Places API with automatic area subdivision used to bypass the 60 results limit.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Automatic Subdivision**: Splits large search areas into smaller 500m radius sub-circles to maximize results, overcoming the 60-result limit per query.
|
|
8
|
+
- **Fluent API**: Easy-to-use chainable methods for configuration.
|
|
9
|
+
- **Rate Limiting**: Handles API pagination and rate limiting automatically.
|
|
10
|
+
- **Filtering**: By default, filters out temporarily closed places (configurable).
|
|
11
|
+
- **Flexible Output**: Export to JSON, CSV, or handle results with a custom callback.
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install easy-google-places
|
|
17
|
+
# or
|
|
18
|
+
bun add easy-google-places
|
|
19
|
+
# or
|
|
20
|
+
yarn add easy-google-places
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Basic Usage
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import { getGooglePlaces } from "easy-google-places";
|
|
27
|
+
|
|
28
|
+
// Coordinates for the center of your search (e.g., Paris)
|
|
29
|
+
const location = { latitude: 48.8566, longitude: 2.3522 };
|
|
30
|
+
|
|
31
|
+
await getGooglePlaces(location)
|
|
32
|
+
.radius(2000) // Search within 2km radius
|
|
33
|
+
.placesType("restaurant") // Default is "restaurant"
|
|
34
|
+
.apiKey("YOUR_GOOGLE_MAPS_API_KEY") // Or set via NEXT_PUBLIC_GOOGLE_MAPS_API_KEY env var
|
|
35
|
+
.showLogs() // Print logs to console
|
|
36
|
+
.showProgress() // Show progress per batch
|
|
37
|
+
.onFinished("json") // Save results to places_output.json
|
|
38
|
+
.run();
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Including Temporarily Closed Stores
|
|
42
|
+
By default, the library filters out places with `business_status: "CLOSED_TEMPORARILY"`. To include them:
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
await getGooglePlaces(location)
|
|
46
|
+
.allowClosedStores() // Include temporarily closed places
|
|
47
|
+
.run();
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## detailed Usage & Output Formats
|
|
51
|
+
|
|
52
|
+
### Using a Callback (`onFinished`)
|
|
53
|
+
|
|
54
|
+
You can process the results directly in your code instead of saving to a file:
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
await getGooglePlaces(location)
|
|
58
|
+
.apiKey("YOUR_API_KEY")
|
|
59
|
+
.onFinished((places) => {
|
|
60
|
+
console.log(`Found ${places.length} places!`);
|
|
61
|
+
places.forEach(place => {
|
|
62
|
+
console.log(`Name: ${place.name}, Rating: ${place.rating}`);
|
|
63
|
+
});
|
|
64
|
+
})
|
|
65
|
+
.run();
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Output Formats
|
|
69
|
+
|
|
70
|
+
**CSV Output (`.onFinished("csv")`)**
|
|
71
|
+
Creates `places_output.csv` with columns:
|
|
72
|
+
`name, address, rating, user_ratings_total, place_id, lat, lng`
|
|
73
|
+
|
|
74
|
+
**JSON Output (`.onFinished("json")`)**
|
|
75
|
+
Creates `places_output.json` containing an array of place objects:
|
|
76
|
+
```json
|
|
77
|
+
[
|
|
78
|
+
{
|
|
79
|
+
"place_id": "ChIJ...",
|
|
80
|
+
"name": "Le Example Bistro",
|
|
81
|
+
"formatted_address": "123 Rue de Example, Paris",
|
|
82
|
+
"geometry": {
|
|
83
|
+
"location": {
|
|
84
|
+
"lat": 48.85,
|
|
85
|
+
"lng": 2.35
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
"types": ["restaurant", "food", "point_of_interest"],
|
|
89
|
+
"rating": 4.5,
|
|
90
|
+
"user_ratings_total": 120,
|
|
91
|
+
"business_status": "OPERATIONAL"
|
|
92
|
+
}
|
|
93
|
+
]
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Requirements
|
|
97
|
+
- Node.js or Bun environment.
|
|
98
|
+
- A valid Google Cloud API Key with **Places API (New)** or **Places API** enabled.
|
|
99
|
+
|
|
100
|
+
## License
|
|
101
|
+
MIT
|
package/dist/api.d.ts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { Coordinate, PlaceResult } from "./types";
|
|
2
|
+
export declare function fetchPlaceFromApi(location: Coordinate, radius: number, type: string, apiKey: string, onProgress?: (count: number) => void, allowClosedStores?: boolean): Promise<PlaceResult[]>;
|
|
3
|
+
//# sourceMappingURL=api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAElD,wBAAsB,iBAAiB,CACrC,QAAQ,EAAE,UAAU,EACpB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,EACpC,iBAAiB,GAAE,OAAe,GACjC,OAAO,CAAC,WAAW,EAAE,CAAC,CA4DxB"}
|
package/dist/core.d.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { Coordinate, PlaceResult } from "./types";
|
|
2
|
+
type OutputFormat = "csv" | "json" | ((places: PlaceResult[]) => void);
|
|
3
|
+
export declare class PlaceQueryBuilder {
|
|
4
|
+
private _location;
|
|
5
|
+
private _radius;
|
|
6
|
+
private _subRadius;
|
|
7
|
+
private _minRate;
|
|
8
|
+
private _limitCount;
|
|
9
|
+
private _type;
|
|
10
|
+
private _onFinished?;
|
|
11
|
+
private _showLogs;
|
|
12
|
+
private _showProgress;
|
|
13
|
+
private _apiKey;
|
|
14
|
+
private _forceQueryClosedStores;
|
|
15
|
+
constructor(location: Coordinate);
|
|
16
|
+
/**
|
|
17
|
+
* Sets the type of place to search for (e.g., 'restaurant', 'cafe').
|
|
18
|
+
* @param type The place type.
|
|
19
|
+
*/
|
|
20
|
+
placesType(type: string): this;
|
|
21
|
+
/**
|
|
22
|
+
* Sets the search radius in meters.
|
|
23
|
+
* @param radius Radius in meters (default 4000).
|
|
24
|
+
*/
|
|
25
|
+
radius(radius: number): this;
|
|
26
|
+
/**
|
|
27
|
+
* Sets the minimum rating to filter results (default 4.1).
|
|
28
|
+
* @param rate Minimum rating.
|
|
29
|
+
*/
|
|
30
|
+
minRate(rate: number): this;
|
|
31
|
+
/**
|
|
32
|
+
* Limits the number of results and optimizes the query by reducing the number of sub-circles searched.
|
|
33
|
+
* Optimization assumption: ~60 results per sub-circle.
|
|
34
|
+
* @param max Max number of places to return.
|
|
35
|
+
*/
|
|
36
|
+
limit(max: number): this;
|
|
37
|
+
/**
|
|
38
|
+
* Sets the API Key. If not set here, checks NEXT_PUBLIC_GOOGLE_MAPS_API_KEY env var.
|
|
39
|
+
* @param key Google Maps API Key.
|
|
40
|
+
*/
|
|
41
|
+
apiKey(key: string): this;
|
|
42
|
+
/**
|
|
43
|
+
* Defines what to do when the process finishes.
|
|
44
|
+
* @param format 'csv', 'json', or a callback function.
|
|
45
|
+
*/
|
|
46
|
+
onFinished(format: OutputFormat): this;
|
|
47
|
+
/**
|
|
48
|
+
* Enables logging of the process.
|
|
49
|
+
*/
|
|
50
|
+
showLogs(): this;
|
|
51
|
+
/**
|
|
52
|
+
* Enables progress tracking logging.
|
|
53
|
+
*/
|
|
54
|
+
showProgress(): this;
|
|
55
|
+
/**
|
|
56
|
+
* By default, this library filters out the TEMPORARILY_CLOSED stores.
|
|
57
|
+
* Use this method to allow them to be included in the results.
|
|
58
|
+
*/
|
|
59
|
+
allowClosedStores(): this;
|
|
60
|
+
/**
|
|
61
|
+
* Executes the query process.
|
|
62
|
+
*/
|
|
63
|
+
run(): Promise<void>;
|
|
64
|
+
private handleOutput;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
*
|
|
68
|
+
* @param location {latitude: number, longitude: number}
|
|
69
|
+
* @returns PlaceQueryBuilder
|
|
70
|
+
*
|
|
71
|
+
* @version 1.0.8
|
|
72
|
+
*/
|
|
73
|
+
export declare function getGooglePlaces(location: Coordinate): PlaceQueryBuilder;
|
|
74
|
+
export {};
|
|
75
|
+
//# sourceMappingURL=core.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAElD,KAAK,YAAY,GAAG,KAAK,GAAG,MAAM,GAAG,CAAC,CAAC,MAAM,EAAE,WAAW,EAAE,KAAK,IAAI,CAAC,CAAC;AAEvE,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,SAAS,CAAa;IAC9B,OAAO,CAAC,OAAO,CAAgB;IAC/B,OAAO,CAAC,UAAU,CAAe;IACjC,OAAO,CAAC,QAAQ,CAAe;IAC/B,OAAO,CAAC,WAAW,CAAqB;IACxC,OAAO,CAAC,KAAK,CAAwB;IACrC,OAAO,CAAC,WAAW,CAAC,CAAe;IACnC,OAAO,CAAC,SAAS,CAAkB;IACnC,OAAO,CAAC,aAAa,CAAkB;IACvC,OAAO,CAAC,OAAO,CAAqB;IACpC,OAAO,CAAC,uBAAuB,CAAkB;gBAErC,QAAQ,EAAE,UAAU;IAMhC;;;OAGG;IACI,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAKrC;;;OAGG;IACI,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAKnC;;;OAGG;IACI,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAKlC;;;;OAIG;IACI,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAK/B;;;OAGG;IACI,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAKhC;;;OAGG;IACI,UAAU,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI;IAK7C;;OAEG;IACI,QAAQ,IAAI,IAAI;IAKvB;;OAEG;IACI,YAAY,IAAI,IAAI;IAK3B;;;OAGG;IACI,iBAAiB,IAAI,IAAI;IAKhC;;OAEG;IACU,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;IA0FjC,OAAO,CAAC,YAAY;CA2BrB;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,UAAU,qBAEnD"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Coordinate } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* Generates a list of coordinates for sub-circles that cover a larger circle.
|
|
4
|
+
* Uses a hexagonal packing strategy to ensure coverage.
|
|
5
|
+
*
|
|
6
|
+
* @param center The center of the main search area.
|
|
7
|
+
* @param radius The radius of the main search area in meters.
|
|
8
|
+
* @param subRadius The radius of each sub-circle in meters (default 500m).
|
|
9
|
+
* @returns Array of coordinates for the centers of the sub-circles.
|
|
10
|
+
*/
|
|
11
|
+
export declare function generateSubCircles(center: Coordinate, radius: number, subRadius?: number): Coordinate[];
|
|
12
|
+
//# sourceMappingURL=geometry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"geometry.d.ts","sourceRoot":"","sources":["../src/geometry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAGrC;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,UAAU,EAClB,MAAM,EAAE,MAAM,EACd,SAAS,GAAE,MAAY,GACtB,UAAU,EAAE,CAyDd"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,EAAE,iBAAiB,EAAE,MAAM,OAAO,CAAC;AAC1C,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,QAAQ,CAAC;AAC5D,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
// src/geometry.ts
|
|
2
|
+
function generateSubCircles(center, radius, subRadius = 500) {
|
|
3
|
+
if (radius <= subRadius) {
|
|
4
|
+
return [center];
|
|
5
|
+
}
|
|
6
|
+
const coordinates = [];
|
|
7
|
+
const R = 6371000;
|
|
8
|
+
const latRad = center.latitude * Math.PI / 180;
|
|
9
|
+
const step = subRadius * 1.5;
|
|
10
|
+
const latDegPerMeter = 1 / 111320;
|
|
11
|
+
const lonDegPerMeter = 1 / (111320 * Math.cos(latRad));
|
|
12
|
+
const latStep = step * latDegPerMeter;
|
|
13
|
+
const lonStep = step * lonDegPerMeter;
|
|
14
|
+
const numSteps = Math.ceil(radius / step);
|
|
15
|
+
for (let i = -numSteps;i <= numSteps; i++) {
|
|
16
|
+
for (let j = -numSteps;j <= numSteps; j++) {
|
|
17
|
+
const xOffset = j * lonStep + (i % 2 === 0 ? 0 : lonStep / 2);
|
|
18
|
+
const yOffset = i * latStep * (Math.sqrt(3) / 2);
|
|
19
|
+
const distX = xOffset / lonDegPerMeter;
|
|
20
|
+
const distY = yOffset / latDegPerMeter;
|
|
21
|
+
const distance = Math.sqrt(distX * distX + distY * distY);
|
|
22
|
+
if (distance - subRadius < radius) {
|
|
23
|
+
coordinates.push({
|
|
24
|
+
latitude: center.latitude + yOffset,
|
|
25
|
+
longitude: center.longitude + xOffset
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return coordinates;
|
|
31
|
+
}
|
|
32
|
+
// src/api.ts
|
|
33
|
+
async function fetchPlaceFromApi(location, radius, type, apiKey, onProgress, allowClosedStores = false) {
|
|
34
|
+
const allPlaces = [];
|
|
35
|
+
const maxPages = 3;
|
|
36
|
+
const url = new URL("https://maps.googleapis.com/maps/api/place/nearbysearch/json");
|
|
37
|
+
url.searchParams.append("location", `${location.latitude},${location.longitude}`);
|
|
38
|
+
url.searchParams.append("radius", radius.toString());
|
|
39
|
+
url.searchParams.append("type", type);
|
|
40
|
+
url.searchParams.append("key", apiKey);
|
|
41
|
+
let nextPageToken = undefined;
|
|
42
|
+
let pageCount = 0;
|
|
43
|
+
do {
|
|
44
|
+
const currentUrl = new URL(url.toString());
|
|
45
|
+
if (nextPageToken) {
|
|
46
|
+
currentUrl.searchParams.append("pagetoken", nextPageToken);
|
|
47
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
const response = await fetch(currentUrl.toString());
|
|
51
|
+
const data = await response.json();
|
|
52
|
+
if (data.status !== "OK" && data.status !== "ZERO_RESULTS") {
|
|
53
|
+
console.error("Google Places API error:", data.status, data.error_message);
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
if (data.results) {
|
|
57
|
+
let activePlaces = [];
|
|
58
|
+
if (!allowClosedStores) {
|
|
59
|
+
activePlaces = data.results.filter((place) => place.business_status !== "CLOSED_TEMPORARILY");
|
|
60
|
+
allPlaces.push(...activePlaces);
|
|
61
|
+
} else {
|
|
62
|
+
allPlaces.push(...data.results);
|
|
63
|
+
}
|
|
64
|
+
if (onProgress) {
|
|
65
|
+
onProgress(activePlaces.length);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
nextPageToken = data.next_page_token;
|
|
69
|
+
pageCount++;
|
|
70
|
+
} catch (error) {
|
|
71
|
+
console.error("Network error fetching places:", error);
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
} while (nextPageToken && pageCount < maxPages);
|
|
75
|
+
return allPlaces;
|
|
76
|
+
}
|
|
77
|
+
// src/core.ts
|
|
78
|
+
import { writeFileSync } from "fs";
|
|
79
|
+
|
|
80
|
+
class PlaceQueryBuilder {
|
|
81
|
+
_location;
|
|
82
|
+
_radius = 4000;
|
|
83
|
+
_subRadius = 500;
|
|
84
|
+
_minRate = 4.1;
|
|
85
|
+
_limitCount;
|
|
86
|
+
_type = "restaurant";
|
|
87
|
+
_onFinished;
|
|
88
|
+
_showLogs = false;
|
|
89
|
+
_showProgress = false;
|
|
90
|
+
_apiKey;
|
|
91
|
+
_forceQueryClosedStores = false;
|
|
92
|
+
constructor(location) {
|
|
93
|
+
this._location = location;
|
|
94
|
+
this._apiKey = process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY;
|
|
95
|
+
}
|
|
96
|
+
placesType(type) {
|
|
97
|
+
this._type = type;
|
|
98
|
+
return this;
|
|
99
|
+
}
|
|
100
|
+
radius(radius) {
|
|
101
|
+
this._radius = radius;
|
|
102
|
+
return this;
|
|
103
|
+
}
|
|
104
|
+
minRate(rate) {
|
|
105
|
+
this._minRate = rate;
|
|
106
|
+
return this;
|
|
107
|
+
}
|
|
108
|
+
limit(max) {
|
|
109
|
+
this._limitCount = max;
|
|
110
|
+
return this;
|
|
111
|
+
}
|
|
112
|
+
apiKey(key) {
|
|
113
|
+
this._apiKey = key;
|
|
114
|
+
return this;
|
|
115
|
+
}
|
|
116
|
+
onFinished(format) {
|
|
117
|
+
this._onFinished = format;
|
|
118
|
+
return this;
|
|
119
|
+
}
|
|
120
|
+
showLogs() {
|
|
121
|
+
this._showLogs = true;
|
|
122
|
+
return this;
|
|
123
|
+
}
|
|
124
|
+
showProgress() {
|
|
125
|
+
this._showProgress = true;
|
|
126
|
+
return this;
|
|
127
|
+
}
|
|
128
|
+
allowClosedStores() {
|
|
129
|
+
this._forceQueryClosedStores = true;
|
|
130
|
+
return this;
|
|
131
|
+
}
|
|
132
|
+
async run() {
|
|
133
|
+
if (!this._apiKey) {
|
|
134
|
+
throw new Error("API Key is required. Set it via .apiKey() or NEXT_PUBLIC_GOOGLE_MAPS_API_KEY env var.");
|
|
135
|
+
}
|
|
136
|
+
if (!this._onFinished) {
|
|
137
|
+
throw new Error("onFinished callback or format is required.");
|
|
138
|
+
}
|
|
139
|
+
if (this._showLogs)
|
|
140
|
+
console.log("Starting Google Places query...");
|
|
141
|
+
if (this._showLogs)
|
|
142
|
+
console.log(`Location: ${this._location.latitude}, ${this._location.longitude}`);
|
|
143
|
+
if (this._showLogs)
|
|
144
|
+
console.log(`Radius: ${this._radius}m, Sub-radius: ${this._subRadius}m`);
|
|
145
|
+
let subCircles = generateSubCircles(this._location, this._radius, this._subRadius);
|
|
146
|
+
if (this._limitCount) {
|
|
147
|
+
const maxBatches = Math.ceil(this._limitCount / 60);
|
|
148
|
+
if (subCircles.length > maxBatches) {
|
|
149
|
+
for (let i = subCircles.length - 1;i > 0; i--) {
|
|
150
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
151
|
+
[subCircles[i], subCircles[j]] = [subCircles[j], subCircles[i]];
|
|
152
|
+
}
|
|
153
|
+
subCircles = subCircles.slice(0, maxBatches);
|
|
154
|
+
if (this._showLogs)
|
|
155
|
+
console.log(`Optimization: Limiting to ${maxBatches} batches based on limit of ${this._limitCount}`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
const totalBatches = subCircles.length;
|
|
159
|
+
if (this._showLogs)
|
|
160
|
+
console.log(`Generated ${totalBatches} sub-circles for searching.`);
|
|
161
|
+
const allPlacesMap = new Map;
|
|
162
|
+
let processedBatches = 0;
|
|
163
|
+
for (const circle of subCircles) {
|
|
164
|
+
if (this._showLogs)
|
|
165
|
+
console.log(`Querying batch ${processedBatches + 1}/${totalBatches} at ${circle.latitude}, ${circle.longitude}...`);
|
|
166
|
+
const places = await fetchPlaceFromApi(circle, this._subRadius, this._type, this._apiKey, (count) => {}, this._forceQueryClosedStores);
|
|
167
|
+
for (const place of places) {
|
|
168
|
+
if (place.place_id) {
|
|
169
|
+
allPlacesMap.set(place.place_id, place);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
processedBatches++;
|
|
173
|
+
if (this._showProgress) {
|
|
174
|
+
const percentage = (processedBatches / totalBatches * 100).toFixed(1);
|
|
175
|
+
console.log(`Progress: ${percentage}% (${processedBatches}/${totalBatches} batches)`);
|
|
176
|
+
}
|
|
177
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
178
|
+
}
|
|
179
|
+
let uniquePlaces = Array.from(allPlacesMap.values());
|
|
180
|
+
uniquePlaces = uniquePlaces.filter((p) => (p.rating || 0) >= this._minRate);
|
|
181
|
+
if (this._limitCount && uniquePlaces.length > this._limitCount) {
|
|
182
|
+
uniquePlaces = uniquePlaces.slice(0, this._limitCount);
|
|
183
|
+
}
|
|
184
|
+
if (this._showLogs)
|
|
185
|
+
console.log(`Finished! Found ${uniquePlaces.length} unique places.`);
|
|
186
|
+
this.handleOutput(uniquePlaces);
|
|
187
|
+
}
|
|
188
|
+
handleOutput(places) {
|
|
189
|
+
if (typeof this._onFinished === "function") {
|
|
190
|
+
this._onFinished(places);
|
|
191
|
+
} else if (this._onFinished === "json") {
|
|
192
|
+
const jsonContent = JSON.stringify(places, null, 2);
|
|
193
|
+
writeFileSync("places_output.json", jsonContent);
|
|
194
|
+
if (this._showLogs)
|
|
195
|
+
console.log("Saved results to places_output.json");
|
|
196
|
+
} else if (this._onFinished === "csv") {
|
|
197
|
+
const headers = ["name", "address", "rating", "user_ratings_total", "place_id", "lat", "lng"];
|
|
198
|
+
const csvContent = [
|
|
199
|
+
headers.join(","),
|
|
200
|
+
...places.map((p) => {
|
|
201
|
+
return [
|
|
202
|
+
`"${(p.name || "").replace(/"/g, '""')}"`,
|
|
203
|
+
`"${(p.formatted_address || "").replace(/"/g, '""')}"`,
|
|
204
|
+
p.rating || "",
|
|
205
|
+
p.user_ratings_total || "",
|
|
206
|
+
p.place_id,
|
|
207
|
+
p.geometry?.location?.lat || "",
|
|
208
|
+
p.geometry?.location?.lng || ""
|
|
209
|
+
].join(",");
|
|
210
|
+
})
|
|
211
|
+
].join(`
|
|
212
|
+
`);
|
|
213
|
+
writeFileSync("places_output.csv", csvContent);
|
|
214
|
+
if (this._showLogs)
|
|
215
|
+
console.log("Saved results to places_output.csv");
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
function getGooglePlaces(location) {
|
|
220
|
+
return new PlaceQueryBuilder(location);
|
|
221
|
+
}
|
|
222
|
+
export {
|
|
223
|
+
getGooglePlaces,
|
|
224
|
+
generateSubCircles,
|
|
225
|
+
fetchPlaceFromApi,
|
|
226
|
+
PlaceQueryBuilder
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
//# debugId=98EB28B615C3ED4C64756E2164756E21
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/geometry.ts", "../src/api.ts", "../src/core.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"import { Coordinate } from \"./types\";\n\n\n/**\n * Generates a list of coordinates for sub-circles that cover a larger circle.\n * Uses a hexagonal packing strategy to ensure coverage.\n * \n * @param center The center of the main search area.\n * @param radius The radius of the main search area in meters.\n * @param subRadius The radius of each sub-circle in meters (default 500m).\n * @returns Array of coordinates for the centers of the sub-circles.\n */\nexport function generateSubCircles(\n center: Coordinate,\n radius: number,\n subRadius: number = 500\n): Coordinate[] {\n // If the main radius is smaller than the subRadius, just return the center.\n if (radius <= subRadius) {\n return [center];\n }\n\n const coordinates: Coordinate[] = [];\n \n // Earth's radius in meters\n const R = 6371000;\n \n // Convert latitude to radians to calculate longitude offsets correctly\n const latRad = (center.latitude * Math.PI) / 180;\n \n // Distance between centers of hexagonally packed circles to ensure coverage.\n // Ideally, for r=subRadius, the distance should be approx r * sqrt(3) for tight packing without gaps,\n // but to be safe and ensure overlap (so we don't miss spots), we can use a slightly tighter spacing.\n // Using subRadius * 1.5 is a reasonable approximation for coverage with overlap.\n const step = subRadius * 1.5; \n\n // Simple grid or spiral generation could work, but let's do a bounding box scan\n // We scan a square area around the center and keep points within the radius.\n \n // Degrees per meter (approximate)\n const latDegPerMeter = 1 / 111320; \n const lonDegPerMeter = 1 / (111320 * Math.cos(latRad));\n\n const latStep = step * latDegPerMeter;\n const lonStep = step * lonDegPerMeter;\n\n // Calculate bounds\n const numSteps = Math.ceil(radius / step);\n \n for (let i = -numSteps; i <= numSteps; i++) {\n for (let j = -numSteps; j <= numSteps; j++) {\n // Offset based on hexagonal-like grid (shift every other row)\n const xOffset = j * lonStep + (i % 2 === 0 ? 0 : lonStep / 2);\n const yOffset = i * latStep * (Math.sqrt(3) / 2); // Hexagonal height factor\n\n // Calculate distance from center in meters (approx)\n const distX = xOffset / lonDegPerMeter;\n const distY = yOffset / latDegPerMeter;\n const distance = Math.sqrt(distX * distX + distY * distY);\n\n // We include the circle if its center is within (ParentRadius + epsilon) \n // or if it covers any part of the parent circle. \n // Being generous: if distance - subRadius < radius\n if (distance - subRadius < radius) {\n coordinates.push({\n latitude: center.latitude + yOffset,\n longitude: center.longitude + xOffset\n });\n }\n }\n }\n\n return coordinates;\n}\n",
|
|
6
|
+
"import { Coordinate, PlaceResult } from \"./types\";\n\nexport async function fetchPlaceFromApi(\n location: Coordinate,\n radius: number,\n type: string,\n apiKey: string,\n onProgress?: (count: number) => void,\n allowClosedStores: boolean = false,\n): Promise<PlaceResult[]> {\n const allPlaces: PlaceResult[] = [];\n const maxPages = 3;\n \n const url = new URL(\n \"https://maps.googleapis.com/maps/api/place/nearbysearch/json\"\n );\n url.searchParams.append(\"location\", `${location.latitude},${location.longitude}`);\n url.searchParams.append(\"radius\", radius.toString());\n url.searchParams.append(\"type\", type);\n // Important: Google Places API does not support explicit pagination by page number,\n // only by next_page_token.\n url.searchParams.append(\"key\", apiKey);\n\n let nextPageToken: string | undefined = undefined;\n let pageCount = 0;\n\n do {\n const currentUrl = new URL(url.toString());\n if (nextPageToken) {\n currentUrl.searchParams.append(\"pagetoken\", nextPageToken);\n // Determine if we need to wait.\n // Although the user snippet has a wait, let's keep it robust.\n // Google requires a short delay before the next_page_token becomes valid.\n await new Promise((resolve) => setTimeout(resolve, 2000));\n }\n\n try {\n const response = await fetch(currentUrl.toString());\n const data = await response.json();\n\n if (data.status !== \"OK\" && data.status !== \"ZERO_RESULTS\") {\n console.error(\"Google Places API error:\", data.status, data.error_message);\n break;\n }\n\n if (data.results) {\n let activePlaces: PlaceResult[] = [];\n if (!allowClosedStores) {\n activePlaces = (data.results as PlaceResult[]).filter(\n (place) => place.business_status !== \"CLOSED_TEMPORARILY\"\n );\n allPlaces.push(...activePlaces);\n } else {\n allPlaces.push(...data.results as PlaceResult[]);\n }\n if (onProgress) {\n onProgress(activePlaces.length);\n }\n }\n\n nextPageToken = data.next_page_token;\n pageCount++;\n } catch (error) {\n console.error(\"Network error fetching places:\", error);\n break;\n }\n } while (nextPageToken && pageCount < maxPages);\n\n return allPlaces;\n}\n",
|
|
7
|
+
"import { generateSubCircles } from \"./geometry\";\nimport { fetchPlaceFromApi } from \"./api\";\nimport { writeFileSync } from \"fs\";\nimport { Coordinate, PlaceResult } from \"./types\";\n\ntype OutputFormat = \"csv\" | \"json\" | ((places: PlaceResult[]) => void);\n\nexport class PlaceQueryBuilder {\n private _location: Coordinate;\n private _radius: number = 4000; // Default 4km\n private _subRadius: number = 500; // Default 500m\n private _minRate: number = 4.1;\n private _limitCount: number | undefined;\n private _type: string = \"restaurant\";\n private _onFinished?: OutputFormat;\n private _showLogs: boolean = false;\n private _showProgress: boolean = false;\n private _apiKey: string | undefined;\n private _forceQueryClosedStores: boolean = false;\n\n constructor(location: Coordinate) {\n this._location = location;\n // Try to load API Key from env if available (Bun loads .env automatically)\n this._apiKey = process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY;\n }\n\n /**\n * Sets the type of place to search for (e.g., 'restaurant', 'cafe').\n * @param type The place type.\n */\n public placesType(type: string): this {\n this._type = type;\n return this;\n }\n\n /**\n * Sets the search radius in meters.\n * @param radius Radius in meters (default 4000).\n */\n public radius(radius: number): this {\n this._radius = radius;\n return this;\n }\n\n /**\n * Sets the minimum rating to filter results (default 4.1).\n * @param rate Minimum rating.\n */\n public minRate(rate: number): this {\n this._minRate = rate;\n return this;\n }\n\n /**\n * Limits the number of results and optimizes the query by reducing the number of sub-circles searched.\n * Optimization assumption: ~60 results per sub-circle.\n * @param max Max number of places to return.\n */\n public limit(max: number): this {\n this._limitCount = max;\n return this;\n }\n\n /**\n * Sets the API Key. If not set here, checks NEXT_PUBLIC_GOOGLE_MAPS_API_KEY env var.\n * @param key Google Maps API Key.\n */\n public apiKey(key: string): this {\n this._apiKey = key;\n return this;\n }\n\n /**\n * Defines what to do when the process finishes.\n * @param format 'csv', 'json', or a callback function.\n */\n public onFinished(format: OutputFormat): this {\n this._onFinished = format;\n return this;\n }\n\n /**\n * Enables logging of the process.\n */\n public showLogs(): this {\n this._showLogs = true;\n return this;\n }\n\n /**\n * Enables progress tracking logging.\n */\n public showProgress(): this {\n this._showProgress = true;\n return this;\n }\n\n /**\n * By default, this library filters out the TEMPORARILY_CLOSED stores.\n * Use this method to allow them to be included in the results.\n */\n public allowClosedStores(): this {\n this._forceQueryClosedStores = true;\n return this;\n }\n\n /**\n * Executes the query process.\n */\n public async run(): Promise<void> {\n if (!this._apiKey) {\n throw new Error(\"API Key is required. Set it via .apiKey() or NEXT_PUBLIC_GOOGLE_MAPS_API_KEY env var.\");\n }\n if (!this._onFinished) {\n throw new Error(\"onFinished callback or format is required.\");\n }\n\n if (this._showLogs) console.log(\"Starting Google Places query...\");\n if (this._showLogs) console.log(`Location: ${this._location.latitude}, ${this._location.longitude}`);\n if (this._showLogs) console.log(`Radius: ${this._radius}m, Sub-radius: ${this._subRadius}m`);\n\n // 1. Generate sub-circles\n let subCircles = generateSubCircles(this._location, this._radius, this._subRadius);\n \n // Optimization: If limit is set, reduce the number of batches\n if (this._limitCount) {\n const maxBatches = Math.ceil(this._limitCount / 60);\n if (subCircles.length > maxBatches) {\n // Shuffle array to get random circles\n for (let i = subCircles.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1));\n [subCircles[i], subCircles[j]] = [subCircles[j], subCircles[i]];\n }\n // Slice to the max needed matches\n subCircles = subCircles.slice(0, maxBatches);\n if (this._showLogs) console.log(`Optimization: Limiting to ${maxBatches} batches based on limit of ${this._limitCount}`);\n }\n }\n\n const totalBatches = subCircles.length;\n\n if (this._showLogs) console.log(`Generated ${totalBatches} sub-circles for searching.`);\n\n const allPlacesMap = new Map<string, PlaceResult>();\n let processedBatches = 0;\n \n // 2. Iterate batches\n // We implement a queue/concurrency limit? The user mentioned \"20 every 2 seconds\". \n // Since each fetchPlaceFromApi call takes at least ~4s (2s wait between pages, 3 pages max),\n // and we want to respect rate limits.\n // Let's run them sequentially to be safe and simple, or with very low concurrency.\n // Sequential is safest for a \"library\" that shouldn't blast the API.\n\n for (const circle of subCircles) {\n if (this._showLogs) console.log(`Querying batch ${processedBatches + 1}/${totalBatches} at ${circle.latitude}, ${circle.longitude}...`);\n \n const places = await fetchPlaceFromApi(\n circle, \n this._subRadius, \n this._type, \n this._apiKey,\n (count) => {\n // Optional: finer progress update\n },\n this._forceQueryClosedStores\n );\n\n for (const place of places) {\n if (place.place_id) {\n allPlacesMap.set(place.place_id, place);\n }\n }\n\n processedBatches++;\n \n if (this._showProgress) {\n const percentage = ((processedBatches / totalBatches) * 100).toFixed(1);\n console.log(`Progress: ${percentage}% (${processedBatches}/${totalBatches} batches)`);\n }\n\n // Small delay between batches to be polite to the API?\n await new Promise(r => setTimeout(r, 200)); \n }\n\n let uniquePlaces = Array.from(allPlacesMap.values());\n \n // Filter by minRate\n uniquePlaces = uniquePlaces.filter(p => (p.rating || 0) >= this._minRate);\n\n // Apply limit\n if (this._limitCount && uniquePlaces.length > this._limitCount) {\n uniquePlaces = uniquePlaces.slice(0, this._limitCount);\n }\n if (this._showLogs) console.log(`Finished! Found ${uniquePlaces.length} unique places.`);\n\n // 3. Handle output\n this.handleOutput(uniquePlaces);\n }\n\n private handleOutput(places: PlaceResult[]) {\n if (typeof this._onFinished === \"function\") {\n this._onFinished(places);\n } else if (this._onFinished === \"json\") {\n const jsonContent = JSON.stringify(places, null, 2);\n writeFileSync(\"places_output.json\", jsonContent);\n if (this._showLogs) console.log(\"Saved results to places_output.json\");\n } else if (this._onFinished === \"csv\") {\n const headers = [\"name\", \"address\", \"rating\", \"user_ratings_total\", \"place_id\", \"lat\", \"lng\"];\n const csvContent = [\n headers.join(\",\"),\n ...places.map(p => {\n return [\n `\"${(p.name || \"\").replace(/\"/g, '\"\"')}\"`,\n `\"${(p.formatted_address || \"\").replace(/\"/g, '\"\"')}\"`,\n p.rating || \"\",\n p.user_ratings_total || \"\",\n p.place_id,\n p.geometry?.location?.lat || \"\",\n p.geometry?.location?.lng || \"\"\n ].join(\",\")\n })\n ].join(\"\\n\");\n writeFileSync(\"places_output.csv\", csvContent);\n if (this._showLogs) console.log(\"Saved results to places_output.csv\");\n }\n }\n}\n\n/**\n * \n * @param location {latitude: number, longitude: number}\n * @returns PlaceQueryBuilder\n * \n * @version 1.0.8\n */\nexport function getGooglePlaces(location: Coordinate) {\n return new PlaceQueryBuilder(location);\n}\n"
|
|
8
|
+
],
|
|
9
|
+
"mappings": "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",
|
|
10
|
+
"debugId": "98EB28B615C3ED4C64756E2164756E21",
|
|
11
|
+
"names": []
|
|
12
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Represents a geographic coordinate.
|
|
3
|
+
*/
|
|
4
|
+
export interface Coordinate {
|
|
5
|
+
latitude: number;
|
|
6
|
+
longitude: number;
|
|
7
|
+
}
|
|
8
|
+
export interface PlaceResult {
|
|
9
|
+
place_id: string;
|
|
10
|
+
name?: string;
|
|
11
|
+
formatted_address?: string;
|
|
12
|
+
geometry?: {
|
|
13
|
+
location?: {
|
|
14
|
+
lat(): number;
|
|
15
|
+
lng(): number;
|
|
16
|
+
} | {
|
|
17
|
+
lat: number;
|
|
18
|
+
lng: number;
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
address_components?: Array<{
|
|
22
|
+
long_name: string;
|
|
23
|
+
short_name: string;
|
|
24
|
+
types: string[];
|
|
25
|
+
}>;
|
|
26
|
+
formatted_phone_number?: string;
|
|
27
|
+
website?: string;
|
|
28
|
+
price_level?: number;
|
|
29
|
+
rating?: number;
|
|
30
|
+
types?: string[];
|
|
31
|
+
timezone?: string;
|
|
32
|
+
business_status?: string;
|
|
33
|
+
user_ratings_total?: number;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB;AAGD,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,EAAE;QACT,QAAQ,CAAC,EACL;YACE,GAAG,IAAI,MAAM,CAAC;YACd,GAAG,IAAI,MAAM,CAAC;SACf,GACD;YACE,GAAG,EAAE,MAAM,CAAC;YACZ,GAAG,EAAE,MAAM,CAAC;SACb,CAAC;KACP,CAAC;IACF,kBAAkB,CAAC,EAAE,KAAK,CAAC;QACzB,SAAS,EAAE,MAAM,CAAC;QAClB,UAAU,EAAE,MAAM,CAAC;QACnB,KAAK,EAAE,MAAM,EAAE,CAAC;KACjB,CAAC,CAAC;IACH,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B"}
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "easy-google-places",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Query Google Places API with automatic area subdivision to maximize results.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"require": "./dist/index.js",
|
|
13
|
+
"types": "./dist/index.d.ts"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "bun build ./src/index.ts --outdir ./dist --target node --sourcemap=external && tsc --emitDeclarationOnly --outDir dist",
|
|
21
|
+
"test": "bun test",
|
|
22
|
+
"prepublishOnly": "bun run build"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"google-places",
|
|
26
|
+
"google-maps",
|
|
27
|
+
"places-api",
|
|
28
|
+
"restaurants"
|
|
29
|
+
],
|
|
30
|
+
"author": "Eduardo Fazolo",
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/bun": "^1.3.6"
|
|
34
|
+
},
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"typescript": "^5.0.0"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {}
|
|
39
|
+
}
|