Skip to content

Commit 647af32

Browse files
committed
Demo app ready for prod
1 parent a0ef3ee commit 647af32

File tree

12 files changed

+57
-67
lines changed

12 files changed

+57
-67
lines changed

geopoly-demo/README.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Example App using SQLite's Geopoly Extension
2+
3+
<img width="721" alt="Screenshot 2024-08-06 at 3 33 47 PM" src="https://github.com/user-attachments/assets/113ebbdc-778e-4862-a4cb-080c20cb57fd">
4+
5+
## Description
6+
7+
This example demos using SQLite's built-in Geopoly extension with SQLite Cloud to create a local attractions finder map-plication.
8+
9+
To build the app from scratch and/or customize the attractions, read the [Tutorial](https://docs.sqlitecloud.io/docs/tutorial-geopoly) in the SQLite Cloud documentation.
10+
11+
## Tutorial TL;DR - Get up-and-exploring NYC attractions fast!
12+
13+
1. In your SQLite Cloud account dashboard's left nav, click Databases > Create Database. To minimize code changes, name your new database `geopoly-demo`.
14+
15+
2. Clone and open the `examples` repo in your local editor. Install the `geopoly-demo` app's dependencies.
16+
17+
```
18+
git clone https://github.com/sqlitecloud/examples.git
19+
cd geopoly-demo
20+
npm i
21+
```
22+
23+
3. [Sign up](https://account.mapbox.com/auth/signup/) for an Individual Mapbox account. (We'll stay on the free tier.)
24+
25+
4. Create a `.env` file. Add 2 env vars:
26+
27+
- `REACT_APP_CONNECTION_STRING`. Copy/ paste your connection string from your SQLite Cloud account dashboard.
28+
- `REACT_APP_MAPBOX_TOKEN`. In your Mapbox account dashboard's nav, click Tokens. Copy/ paste your default public token.
29+
30+
5. Run `npm run create-tables` to create 2 DB tables in the `geopoly-demo` database:
31+
32+
- a `polygons` table to store the polygons generated by Geopoly as you interact with the app map, and
33+
- an `attractions` table to store NYC attraction geodata. Since `data/geodata.json` contains ~2000 Point features, it will take a little time for this command to finish inserting the attractions as rows.
34+
35+
6. Run `npm start` to start your local dev server. Visit `http://localhost:3000/` (adjust the port as-needed) in your browser to view the app.
36+
37+
- The loaded map is centered at Central Park, NYC.
38+
- In the geocoder (i.e. search input) at the top right of the map, enter "Empire" and select the "Empire State Building" search result.
39+
- A polygon will be generated by Geopoly, added to your `polygons` table, and displayed on the map.
40+
- All attractions from your `attractions` table within the polygon area will be listed (nearest to furthest from the Empire State Building) in the left sidebar AND rendered as map markers.
41+
- The map will zoom in to the closest attraction to your searched location, in this case the "Empire State Building". However, you can click on any attraction listing or marker to center that attraction on the map.
File renamed without changes.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"scripts": {
88
"start": "react-scripts start",
99
"build": "react-scripts build",
10-
"create-db": "node src/helpers/createDatabase.js"
10+
"create-tables": "node src/helpers/createDatabase.js"
1111
},
1212
"eslintConfig": {
1313
"extends": [
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
name="description"
88
content="Create a React web app that uses Mapbox GL JS to render a map"
99
/>
10-
<title>Local Attraction Finder</title>
10+
<title>Local Attractions Finder</title>
1111
<link rel="icon" type="image/x-icon" href="../images/favicon.ico" />
1212
<link rel="icon" type="image/svg+xml" href="../images/favicon.svg" />
1313
</head>
Lines changed: 11 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,36 +6,34 @@ import { point, distance } from '@turf/turf';
66
import { Database } from '@sqlitecloud/drivers';
77
import { getBbox } from './helpers/getBbox.js';
88

9-
mapboxgl.accessToken =
10-
'pk.eyJ1IjoidW5hdGFyYWphbiIsImEiOiJjbDFpcW82MGYxeDE1M2RwNjU4MmZ1YndsIn0.HyxwEtZz-pQ_7R6e48l0-g';
9+
mapboxgl.accessToken = process.env.REACT_APP_MAPBOX_TOKEN;
1110

1211
function App() {
1312
const mapContainerRef = useRef();
1413
const mapRef = useRef();
1514

16-
// centers on Central Park, NYC
1715
const [lng, setLng] = useState(-73.9654897);
1816
const [lat, setLat] = useState(40.7824635);
1917
const [zoom, setZoom] = useState(12);
2018

2119
const [places, setPlaces] = useState([]);
2220
const [geometry, setGeometry] = useState([]);
2321

24-
const units = 'miles'; // default is 'kilometers'; can also be miles, degrees, or radians
22+
const units = 'miles';
2523

2624
async function queryGeopoly(searchedLng, searchedLat) {
2725
const db = new Database(process.env.REACT_APP_CONNECTION_STRING);
2826

29-
const db_name = 'geopoly-app';
27+
const db_name = 'geopoly-demo';
28+
29+
const radius = 0.05;
30+
const sides = 50;
3031

31-
const radius = 0.05; // must be non-negative
32-
const sides = 50; // caps at 1000
33-
// use coords to create a polygon to store in geopoly
3432
const polygonCoords =
3533
await db.sql`USE DATABASE ${db_name}; INSERT INTO polygons(_shape) VALUES(geopoly_regular(${searchedLng}, ${searchedLat}, ${radius}, ${sides})) RETURNING geopoly_json(_shape);`;
3634

3735
const attractionsInPolygon =
38-
await db.sql`USE DATABASE ${db_name}; SELECT name, coordinates FROM attractions WHERE geopoly_contains_point(${polygonCoords[0]['geopoly_json(_shape)']}, lng, lat);`; // here, lng and lat are the cols in the attractions table
36+
await db.sql`USE DATABASE ${db_name}; SELECT name, coordinates FROM attractions WHERE geopoly_contains_point(${polygonCoords[0]['geopoly_json(_shape)']}, lng, lat);`;
3937

4038
db.close();
4139

@@ -65,11 +63,10 @@ function App() {
6563
},
6664
};
6765

68-
// (re-)apply markers for newly searched location
6966
const marker = document.createElement('div');
7067
marker.key = `marker-${attractionFeature.properties.id}`;
7168
marker.id = `marker-${attractionFeature.properties.id}`;
72-
marker.className = 'marker'; // no css for this class; adding to enable removal later
69+
marker.className = 'marker';
7370

7471
marker.addEventListener('click', (e) => {
7572
handleClick(attractionFeature);
@@ -89,14 +86,12 @@ function App() {
8986
if (a.properties.distance < b.properties.distance) {
9087
return -1;
9188
}
92-
return 0; // a must be equal to b
89+
return 0;
9390
});
9491

9592
setPlaces(attractionFeatures);
9693

97-
// if there is at least one attraction in the polygon
9894
if (attractionFeatures[0]) {
99-
// create bounding box around searched location and closest attraction
10095
const bbox = getBbox(attractionFeatures, searchedLng, searchedLat);
10196
mapRef.current.fitBounds(bbox, {
10297
padding: 100,
@@ -134,7 +129,7 @@ function App() {
134129
type: 'geojson',
135130
data: {
136131
type: 'FeatureCollection',
137-
features: geometry, // array of feature objects (polygon + attraction points)
132+
features: geometry,
138133
},
139134
});
140135

@@ -162,7 +157,7 @@ function App() {
162157
} else {
163158
mapRef.current.getSource(sourceId).setData({
164159
type: 'FeatureCollection',
165-
features: geometry, // array of feature objects (polygon + attraction points)
160+
features: geometry,
166161
});
167162
}
168163
}
@@ -237,13 +232,11 @@ function App() {
237232
mapRef.current.on('move', updateCoordinates);
238233

239234
geocoder.on('result', (e) => {
240-
// remove existing map markers
241235
const existingMarkers = document.getElementsByClassName('marker');
242236
while (existingMarkers[0]) {
243237
existingMarkers[0].remove();
244238
}
245239

246-
// remove existing popups
247240
const popUps = document.getElementsByClassName('mapboxgl-popup');
248241
while (popUps[0]) {
249242
popUps[0].remove();
@@ -253,7 +246,6 @@ function App() {
253246
queryGeopoly(lng, lat);
254247
});
255248

256-
// return effect cleanup function
257249
return () => {
258250
mapRef.current.removeControl(geocoder);
259251
mapRef.current.removeControl(fullscreenCtrl);
@@ -286,7 +278,6 @@ function App() {
286278
<div
287279
key={index}
288280
id={`listing-${place.properties.id}`}
289-
// first item in the list is the closest, active by default
290281
className={`item ${index === 0 && 'active'}`}
291282
>
292283
<a href="#" className="title" onClick={() => handleClick(place)}>

geopoly/src/helpers/createDatabase.js renamed to geopoly-demo/src/helpers/createDatabase.js

Lines changed: 1 addition & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,13 @@ import geodata from '../../data/geodata.json' assert { type: 'json' };
55
async function createDatabase() {
66
const db = new Database(process.env.REACT_APP_CONNECTION_STRING);
77

8-
const db_name = 'geopoly-app';
8+
const db_name = 'geopoly-demo';
99
await db.sql`USE DATABASE ${db_name};`;
1010

1111
await db.sql`CREATE VIRTUAL TABLE polygons USING geopoly()`;
1212

1313
await db.sql`CREATE TABLE attractions (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, lng REAL NOT NULL, lat REAL NOT NULL, coordinates TEXT NOT NULL)`;
1414

15-
// stick to node (Point) data from Overpass API!
1615
for (const feature of geodata['features']) {
1716
const { name } = feature.properties;
1817
const { coordinates } = feature.geometry;
@@ -29,34 +28,3 @@ async function createDatabase() {
2928
}
3029

3130
createDatabase();
32-
33-
/*
34-
Overpass API query:
35-
36-
[out:json][timeout:25];
37-
38-
// Define areas for Paris, New York, and Tokyo
39-
area[name="New York"]->.newyork;
40-
41-
// Fetch nodes, ways, and relations tagged as tourist attractions in these areas
42-
(
43-
node["amenity"="events_venue"](area.newyork);
44-
node["amenity"="exhibition_centre"](area.newyork);
45-
node["amenity"="music_venue"](area.newyork);
46-
node["amenity"="social_centre"](area.newyork);
47-
node["amenity"="marketplace"](area.newyork);
48-
node["building"="museum"](area.newyork);
49-
node["historic"="building"](area.newyork);
50-
node["tourism"="attraction"](area.newyork);
51-
node["leisure"="park"](area.newyork);
52-
node["natural"="beach"](area.newyork);
53-
node["shop"="coffee"](area.newyork);
54-
node["sport"="yoga"](area.newyork);
55-
);
56-
57-
// Output the data
58-
out body;
59-
>;
60-
out skel qt;
61-
62-
*/
Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,6 @@
11
export function getBbox(sortedEvents, locationLng, locationLat) {
2-
const lons = [
3-
// closest event lng
4-
sortedEvents[0].geometry.coordinates[0],
5-
// searched location lng
6-
locationLng,
7-
];
8-
const lats = [
9-
// closest event lat
10-
sortedEvents[0].geometry.coordinates[1],
11-
// searched location lat
12-
locationLat,
13-
];
2+
const lons = [sortedEvents[0].geometry.coordinates[0], locationLng];
3+
const lats = [sortedEvents[0].geometry.coordinates[1], locationLat];
144
const sortedLons = lons.sort((a, b) => {
155
if (a > b) {
166
return 1;

0 commit comments

Comments
 (0)