Skip to content

Commit 140bac0

Browse files
committed
init
1 parent 835c15e commit 140bac0

File tree

7 files changed

+4719
-0
lines changed

7 files changed

+4719
-0
lines changed

.github/.DS_Store

6 KB
Binary file not shown.

.github/workflows/deploy.yml

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
name: Deploy to GitHub Pages
2+
3+
on:
4+
push:
5+
branches: ["main"]
6+
workflow_dispatch:
7+
8+
permissions:
9+
contents: read
10+
pages: write
11+
id-token: write
12+
13+
concurrency:
14+
group: "pages"
15+
cancel-in-progress: true
16+
17+
jobs:
18+
build:
19+
runs-on: ubuntu-latest
20+
steps:
21+
- name: Checkout
22+
uses: actions/checkout@v4
23+
- name: Set up Node
24+
uses: actions/setup-node@v4
25+
with:
26+
node-version: 18
27+
cache: 'npm'
28+
- name: Install dependencies
29+
run: npm install
30+
- name: Build
31+
run: npm run build
32+
- name: Setup Pages
33+
uses: actions/configure-pages@v4
34+
- name: Upload artifact
35+
uses: actions/upload-pages-artifact@v3
36+
with:
37+
path: './dist'
38+
39+
deploy:
40+
environment:
41+
name: github-pages
42+
url: ${{ steps.deployment.outputs.page_url }}
43+
runs-on: ubuntu-latest
44+
needs: build
45+
steps:
46+
- name: Deploy to GitHub Pages
47+
id: deployment
48+
uses: actions/deploy-pages@v4

app.js

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
import 'maplibre-gl/dist/maplibre-gl.css';
2+
import { MapboxOverlay as DeckOverlay } from '@deck.gl/mapbox';
3+
import { ScatterplotLayer } from '@deck.gl/layers';
4+
import maplibregl from 'maplibre-gl';
5+
import { webgpuAdapter } from '@luma.gl/webgpu';
6+
7+
// --- Configuration ---
8+
const INITIAL_VIEW_STATE = {
9+
longitude: 0,
10+
latitude: 0,
11+
zoom: 1,
12+
maxZoom: 16,
13+
pitch: 0,
14+
bearing: 0
15+
};
16+
17+
// --- State Management ---
18+
let currentData = null;
19+
let pointCount = 0;
20+
21+
// --- Initialize Map ---
22+
const map = new maplibregl.Map({
23+
container: 'map',
24+
style: 'https://tiles.openfreemap.org/styles/liberty',
25+
center: [INITIAL_VIEW_STATE.longitude, INITIAL_VIEW_STATE.latitude],
26+
zoom: INITIAL_VIEW_STATE.zoom,
27+
maxZoom: INITIAL_VIEW_STATE.maxZoom,
28+
pitch: INITIAL_VIEW_STATE.pitch,
29+
bearing: INITIAL_VIEW_STATE.bearing
30+
});
31+
32+
// --- Initialize Deck Context ---
33+
const deckOverlay = new DeckOverlay({
34+
deviceProps: {
35+
adapters: [webgpuAdapter]
36+
},
37+
layers: []
38+
});
39+
40+
map.addControl(deckOverlay);
41+
map.addControl(new maplibregl.NavigationControl());
42+
43+
// --- Core Logic ---
44+
45+
function updateLayer(data) {
46+
const start = performance.now();
47+
48+
let layer;
49+
if (data.attributes) {
50+
// Binary data
51+
layer = new ScatterplotLayer({
52+
id: 'scatterplot',
53+
data: {
54+
length: data.length,
55+
attributes: data.attributes
56+
},
57+
getPosition: { strategy: 'attribute' }, // Tell deck.gl to use binary attributes
58+
getFillColor: { strategy: 'attribute' },
59+
getRadius: 30,
60+
radiusMinPixels: 0.25,
61+
pickable: true
62+
});
63+
} else {
64+
// Standard JSON data
65+
layer = new ScatterplotLayer({
66+
id: 'scatterplot',
67+
data: data,
68+
getPosition: d => [d[0], d[1]],
69+
getFillColor: d => (d[2] === 1 ? [0, 128, 255] : [255, 0, 128]),
70+
getRadius: 30,
71+
radiusMinPixels: 0.25,
72+
pickable: true
73+
});
74+
}
75+
76+
deckOverlay.setProps({ layers: [layer] });
77+
78+
const end = performance.now();
79+
document.getElementById('count-display').innerText = (data.length || data.length === 0 ? data.length : data.length).toLocaleString();
80+
document.getElementById('time-display').innerText = `${Math.round(end - start)}ms (render update)`;
81+
}
82+
83+
// Highly efficient generation using typed arrays
84+
function generatePoints(count) {
85+
const startTime = performance.now();
86+
const positions = new Float32Array(count * 2);
87+
const colors = new Uint8Array(count * 3);
88+
89+
// Use a simpler distribution for speed: full world
90+
for (let i = 0; i < count; i++) {
91+
const i2 = i * 2;
92+
const i3 = i * 3;
93+
94+
// Random worldwide coordinates
95+
positions[i2] = (Math.random() - 0.5) * 360;
96+
positions[i2 + 1] = (Math.random() - 0.5) * 170; // Stay within map range
97+
98+
// Random colors
99+
colors[i3] = Math.random() * 255;
100+
colors[i3 + 1] = Math.random() * 255;
101+
colors[i3 + 2] = Math.random() * 255;
102+
}
103+
104+
const endTime = performance.now();
105+
console.log(`Generated ${count} points in ${endTime - startTime}ms`);
106+
107+
return {
108+
length: count,
109+
attributes: {
110+
getPosition: { value: positions, size: 2 },
111+
getFillColor: { value: colors, size: 3 }
112+
}
113+
};
114+
}
115+
116+
// --- UI Interaction ---
117+
118+
const pointInput = document.getElementById('point-count');
119+
const generateBtn = document.getElementById('generate-btn');
120+
const dropZone = document.getElementById('drop-zone');
121+
122+
generateBtn.addEventListener('click', () => {
123+
// Parse number, removing dots/commas
124+
const rawValue = pointInput.value.replace(/[.,]/g, '');
125+
const count = parseInt(rawValue, 10);
126+
127+
if (isNaN(count)) {
128+
alert('Please enter a valid number');
129+
return;
130+
}
131+
132+
generateBtn.innerText = 'Generating...';
133+
generateBtn.disabled = true;
134+
135+
// Small timeout to allow UI to update
136+
setTimeout(() => {
137+
const data = generatePoints(count);
138+
currentData = data;
139+
updateLayer(data);
140+
generateBtn.innerText = 'Generate';
141+
generateBtn.disabled = false;
142+
}, 10);
143+
});
144+
145+
const removeBtn = document.getElementById('remove-btn');
146+
removeBtn.addEventListener('click', () => {
147+
currentData = null;
148+
updateLayer([]);
149+
});
150+
151+
// --- Drag and Drop ---
152+
153+
window.addEventListener('dragover', (e) => {
154+
e.preventDefault();
155+
dropZone.classList.add('active');
156+
});
157+
158+
window.addEventListener('dragleave', (e) => {
159+
e.preventDefault();
160+
if (e.relatedTarget === null) {
161+
dropZone.classList.remove('active');
162+
}
163+
});
164+
165+
window.addEventListener('drop', (e) => {
166+
e.preventDefault();
167+
dropZone.classList.remove('active');
168+
169+
const file = e.dataTransfer.files[0];
170+
if (file && file.type === 'application/json' || file.name.endsWith('.json')) {
171+
const reader = new FileReader();
172+
reader.onload = (event) => {
173+
try {
174+
const json = JSON.parse(event.target.result);
175+
currentData = json;
176+
updateLayer(json);
177+
} catch (err) {
178+
alert('Failed to parse JSON file');
179+
console.error(err);
180+
}
181+
};
182+
reader.readAsText(file);
183+
} else {
184+
alert('Please drop a valid JSON file');
185+
}
186+
});
187+
188+
// Load initial data if present (optional)
189+
// fetch('world_points.json').then(res => res.json()).then(updateLayer).catch(() => {});

0 commit comments

Comments
 (0)