Skip to content

Commit 063c7cc

Browse files
committed
Deploy preview for PR 56 🛫
1 parent 4b960cc commit 063c7cc

31 files changed

Lines changed: 14815 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
APIFY_TOKEN=apify_api_1234567890
2+
GOOGLE_TOKEN=your_google_maps_api_key_here
3+
SPOTIFY_CLIENT_ID=your_spotify_client_id_here
4+
SPOTIFY_CLIENT_SECRET=your_spotify_client_secret_here
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules
2+
.env

‎pr-preview/pr-56/pr-preview/pr-56/pr-preview/pr-56/pr-preview/pr-56/pr-preview/pr-56/pr-preview/pr-56/19hz/events_BayArea.csv‎

Lines changed: 458 additions & 0 deletions
Large diffs are not rendered by default.

‎pr-preview/pr-56/pr-preview/pr-56/pr-preview/pr-56/pr-preview/pr-56/pr-preview/pr-56/pr-preview/pr-56/19hz/events_DC.csv‎

Lines changed: 143 additions & 0 deletions
Large diffs are not rendered by default.

‎pr-preview/pr-56/pr-preview/pr-56/pr-preview/pr-56/pr-preview/pr-56/pr-preview/pr-56/pr-preview/pr-56/19hz/events_LosAngeles.csv‎

Lines changed: 453 additions & 0 deletions
Large diffs are not rendered by default.

‎pr-preview/pr-56/pr-preview/pr-56/pr-preview/pr-56/pr-preview/pr-56/pr-preview/pr-56/pr-preview/pr-56/19hz/events_Miami.csv‎

Lines changed: 165 additions & 0 deletions
Large diffs are not rendered by default.

‎pr-preview/pr-56/pr-preview/pr-56/pr-preview/pr-56/pr-preview/pr-56/pr-preview/pr-56/pr-preview/pr-56/19hz/events_Seattle.csv‎

Lines changed: 322 additions & 0 deletions
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
const fs = require('fs');
2+
3+
const CSV_FILES = [
4+
https://19hz.info/events_BayArea.csv
5+
https://19hz.info/events_LosAngeles.csv
6+
https://19hz.info/events_Seattle.csv
7+
https://19hz.info/events_Atlanta.csv
8+
https://19hz.info/events_Miami.csv
9+
https://19hz.info/events_DC.csv
10+
https://19hz.info/events_Toronto.csv
11+
https://19hz.info/events_Iowa.csv
12+
https://19hz.info/events_Texas.csv
13+
https://19hz.info/events_PHL.csv
14+
https://19hz.info/events_Denver.csv
15+
https://19hz.info/events_CHI.csv
16+
https://19hz.info/events_Detroit.csv
17+
https://19hz.info/events_Massachusetts.csv
18+
https://19hz.info/events_LasVegas.csv
19+
https://19hz.info/events_Phoenix.csv
20+
https://19hz.info/events_ORE.csv
21+
https://19hz.info/events_BC.csv
22+
];
23+
24+
function parseCSV(csvText) {
25+
const rows = [];
26+
const lines = csvText.split('\n');
27+
28+
for (const line of lines) {
29+
if (!line.trim()) continue;
30+
31+
const row = [];
32+
let currentField = '';
33+
let inQuotes = false;
34+
35+
for (let i = 0; i < line.length; i++) {
36+
const char = line[i];
37+
38+
if (char === '"') {
39+
if (inQuotes && line[i + 1] === '"') {
40+
currentField += '"';
41+
i++;
42+
} else {
43+
inQuotes = !inQuotes;
44+
}
45+
} else if (char === ',' && !inQuotes) {
46+
row.push(currentField.trim());
47+
currentField = '';
48+
} else {
49+
currentField += char;
50+
}
51+
}
52+
53+
row.push(currentField.trim());
54+
rows.push(row);
55+
}
56+
57+
return rows;
58+
}
59+
60+
function extractVenuesFromCSV(filename) {
61+
console.log(`\nProcessing ${filename}...`);
62+
const csvText = fs.readFileSync(filename, 'utf8');
63+
const rows = parseCSV(csvText);
64+
65+
const venues = new Set();
66+
67+
rows.forEach((row, index) => {
68+
if (row.length >= 4) {
69+
const venueName = row[3];
70+
if (venueName && venueName.includes('(') && venueName.includes(')')) {
71+
venues.add(venueName);
72+
}
73+
}
74+
});
75+
76+
console.log(`Found ${venues.size} unique venues`);
77+
return Array.from(venues).sort();
78+
}
79+
80+
console.log('Extracting venues from CSV files...');
81+
82+
const allVenues = new Map();
83+
84+
CSV_FILES.forEach(file => {
85+
const venues = extractVenuesFromCSV(file);
86+
venues.forEach(venue => {
87+
const match = venue.match(/^(.+?)\s*\((.+)\)$/);
88+
if (match) {
89+
const name = match[1].trim();
90+
const address = match[2].trim();
91+
allVenues.set(venue, { name: venue, address: address });
92+
}
93+
});
94+
});
95+
96+
console.log(`\nTotal unique venues across all files: ${allVenues.size}`);
97+
console.log('\nWriting to new-venues.json...');
98+
99+
const venueArray = Array.from(allVenues.values());
100+
fs.writeFileSync('new-venues.json', JSON.stringify(venueArray, null, 2));
101+
102+
console.log(`Done! Wrote ${venueArray.length} venues to new-venues.json`);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
const fs = require('fs');
2+
const https = require('https');
3+
4+
const INPUT_FILE = 'venues.json';
5+
const OUTPUT_FILE = 'venues.json';
6+
const DELAY_MS = 1000; // Nominatim requires 1 request per second
7+
8+
function delay(ms) {
9+
return new Promise(resolve => setTimeout(resolve, ms));
10+
}
11+
12+
function geocodeAddress(address) {
13+
return new Promise((resolve, reject) => {
14+
const encodedAddress = encodeURIComponent(address);
15+
const url = `https://nominatim.openstreetmap.org/search?q=${encodedAddress}&format=json&limit=1`;
16+
17+
const options = {
18+
headers: {
19+
'User-Agent': 'FunCheapSFMap/1.0'
20+
}
21+
};
22+
23+
https.get(url, options, (response) => {
24+
let data = '';
25+
26+
response.on('data', (chunk) => {
27+
data += chunk;
28+
});
29+
30+
response.on('end', () => {
31+
try {
32+
const results = JSON.parse(data);
33+
if (results && results.length > 0) {
34+
resolve({
35+
lat: parseFloat(results[0].lat),
36+
lng: parseFloat(results[0].lon)
37+
});
38+
} else {
39+
resolve(null);
40+
}
41+
} catch (error) {
42+
reject(error);
43+
}
44+
});
45+
}).on('error', (error) => {
46+
reject(error);
47+
});
48+
});
49+
}
50+
51+
async function processVenues() {
52+
console.log('Reading data file...');
53+
const rawData = fs.readFileSync(INPUT_FILE, 'utf8');
54+
// Remove control characters except newlines, tabs, and carriage returns
55+
const data = JSON.parse(rawData);
56+
57+
const venuesWithoutGeometry = data.filter(venue => !venue.geometry);
58+
const venuesWithGeometry = data.filter(venue => venue.geometry);
59+
60+
console.log(`Total venues: ${data.length}`);
61+
console.log(`Venues with geometry: ${venuesWithGeometry.length}`);
62+
console.log(`Venues missing geometry: ${venuesWithoutGeometry.length}`);
63+
console.log('');
64+
65+
let successCount = 0;
66+
let failCount = 0;
67+
68+
for (let i = 0; i < venuesWithoutGeometry.length; i++) {
69+
const venue = venuesWithoutGeometry[i];
70+
71+
if (!venue.address) {
72+
console.log(`[${i + 1}/${venuesWithoutGeometry.length}] Skipping ${venue.name} - no address`);
73+
failCount++;
74+
continue;
75+
}
76+
77+
try {
78+
console.log(`[${i + 1}/${venuesWithoutGeometry.length}] Geocoding: ${venue.name} (${venue.address})`);
79+
80+
const geometry = await geocodeAddress(venue.address);
81+
82+
if (geometry) {
83+
venue.geometry = geometry;
84+
console.log(` ✓ Found: ${geometry.lat}, ${geometry.lng}`);
85+
successCount++;
86+
} else {
87+
console.log(` ✗ No results found`);
88+
failCount++;
89+
}
90+
91+
// Save progress after each successful geocoding
92+
const updatedVenues = [...venuesWithGeometry, ...venuesWithoutGeometry];
93+
fs.writeFileSync(OUTPUT_FILE, JSON.stringify(updatedVenues, null, 2));
94+
95+
// Rate limiting - wait before next request
96+
if (i < venuesWithoutGeometry.length - 1) {
97+
await delay(DELAY_MS);
98+
}
99+
} catch (error) {
100+
console.log(` ✗ Error: ${error.message}`);
101+
failCount++;
102+
}
103+
}
104+
105+
console.log('');
106+
console.log('='.repeat(50));
107+
console.log('Geocoding complete!');
108+
console.log(`Successfully geocoded: ${successCount}`);
109+
console.log(`Failed: ${failCount}`);
110+
console.log(`Output saved to: ${OUTPUT_FILE}`);
111+
}
112+
113+
processVenues().catch(error => {
114+
console.error('Fatal error:', error);
115+
process.exit(1);
116+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
<!doctype html>
2+
<html>
3+
4+
<head>
5+
<meta charset="utf-8">
6+
<title>19hz Mapper</title>
7+
<meta name="description"
8+
content="The Unofficial Map of 19hz Events Updated Daily! Pick a day and a category to plan activities or just clear the filters and browse through all the most popular events the Bay Area has to offer. Free. Open Source. No registration necessary. Works with Mobile and Tablets as well as Computers!" />
9+
<link rel="stylesheet" href="../style.css">
10+
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
11+
<link rel="icon" href="../favicon_fc.ico" />
12+
<link rel="shortcut icon" href="../favicon_fc.ico" />
13+
</head>
14+
15+
<body>
16+
<div id="map-canvas"></div>
17+
18+
<a id="feedback" class="ui-button" target="_blank" href="https://github.com/ProLoser/funcheapmap/issues">
19+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
20+
<path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H5.17L4 17.17V4h16v12z">
21+
</path>
22+
<path d="M11 5h2v6h-2zm0 8h2v2h-2z"></path>
23+
</svg>
24+
Feedback
25+
</a>
26+
27+
<form id="controls" onreset="filter({ date: '', category: '' })">
28+
<details open>
29+
<summary>
30+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="filter-icon">
31+
<path d="M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z" />
32+
</svg>
33+
Showing <output name="countEvents" for="date">0</output> Events
34+
</summary>
35+
<p>
36+
<label for="date">Date:</label>
37+
<input id="date" name="date" type="date" onchange="filter({ date: this.value })">
38+
<button type="reset">Clear</button>
39+
</p>
40+
<p>
41+
<label for="category">Categories: <output name="countCategories" for="category">All</output></label>
42+
</p>
43+
<p>
44+
<!-- <select id="category" onchange="filter({ category: this.value })"> -->
45+
<select id="category" name="category" multiple
46+
onchange="filter({ category: Array.from(this.selectedOptions).map(o => o.value).join('~') })">
47+
<!-- These are programmatically replaced after data loads -->
48+
<option value="**Annual Event**">Annual Event</option>
49+
<option value="*Top Pick*">Top Pick</option>
50+
<option value="420">420</option>
51+
<option value="4th of July">4th of July</option>
52+
<option value="Top Annual Event">Top Annual Event</option>
53+
<option value="Adults Only">Adults Only</option>
54+
<option value="Art & Museums">Art &amp; Museums</option>
55+
<option value="Charity & Volunteering">Charity &amp; Volunteering</option>
56+
<option value="Cheap Drinks">Cheap Drinks</option>
57+
<option value="Club / DJ">Club / DJ</option>
58+
<option value="Comedy">Comedy</option>
59+
<option value="Discount Tix / Promo Codes">Discount Tix / Promo Codes</option>
60+
<option value="Eating & Drinking">Eating &amp; Drinking</option>
61+
<option value="Fairs & Festivals">Fairs &amp; Festivals</option>
62+
<option value="Free Food">Free Food</option>
63+
<option value="Fun & Games">Fun &amp; Games</option>
64+
<option value="Geek Event">Geek Event</option>
65+
<option value="In Person">In Person</option>
66+
<option value="Kids & Families">Kids &amp; Families</option>
67+
<option value="Lectures & Workshops">Lectures &amp; Workshops</option>
68+
<option value="LGBTQ+">LGBTQ+</option>
69+
<option value="Literature">Literature</option>
70+
<option value="Live Music">Live Music</option>
71+
<option value="Movies">Movies</option>
72+
<option value="Outdoors">Outdoors</option>
73+
<option value="Other">Other</option>
74+
<option value="Rainy Day Fun">Rainy Day Fun</option>
75+
<option value="San FranFREEsco">San FranFREEsco</option>
76+
<option value="Shopping & Fashion">Shopping &amp; Fashion</option>
77+
<option value="Sponsored">Sponsored</option>
78+
<option value="Sports & Wellness">Sports &amp; Wellness</option>
79+
<option value="Theater & Performance">Theater &amp; Performance</option>
80+
<option value="Walks & Tours">Walks &amp; Tours</option>
81+
</select>
82+
</p>
83+
</details>
84+
</form>
85+
<script async defer src="https://cdn.jsdelivr.net/npm/add-to-calendar-button@2"></script>
86+
<script src="map.js"></script>
87+
<script async
88+
src="https://maps.googleapis.com/maps/api/js?v=3.exp&loading=async&key=AIzaSyDOtvUXkVqgvf1TgyCGS3le1WaevMUZjwI&libraries=marker&callback=initialize"></script>
89+
90+
<!-- Google tag (gtag.js) -->
91+
<script async src="https://www.googletagmanager.com/gtag/js?id=G-0NECRETGYD"></script>
92+
<script>
93+
window.dataLayer = window.dataLayer || [];
94+
function gtag() { dataLayer.push(arguments); }
95+
gtag('js', new Date());
96+
97+
gtag('config', 'G-0NECRETGYD');
98+
</script>
99+
</body>
100+
101+
</html>

0 commit comments

Comments
 (0)