0

Vẽ 3 triệu điểm trên trình duyệt chỉ với 90 KB tải ban đầu: bản đồ ngữ nghĩa các kênh Telegram

Bài viết này chia sẻ cách chúng tôi xây dựng một bản đồ tương tác hiển thị 3 triệu kênh Telegram công khai ngay trong trình duyệt, và vẫn mở được trong chưa đầy một giây, kể cả trên điện thoại. Bản đồ là một phần của TGScope, bạn có thể mở thử tại https://tgscope.io/map.

Toàn bộ bản đồ

1. Mỗi kênh được đặt vào đâu?

  • Chúng tôi chỉ đọc hồ sơ công khai của kênh: tên, chủ đề, danh mục và phần mô tả. Chủ đề chiếm 60% trọng số, mô tả chiếm 40%, vì mô tả thường chứa nhiều thông tin liên hệ và bảng giá quảng cáo.
  • Một mô hình embedding đa ngôn ngữ biến hồ sơ thành vector. Hai kênh cùng chủ đề sẽ có vector gần nhau, kể cả khi viết bằng hai ngôn ngữ khác nhau.
  • UMAP giảm hàng trăm chiều xuống còn 2 chiều và giữ được quan hệ hàng xóm gần nhất. Chúng tôi cắt bỏ 0,1% điểm cực trị ở mỗi cạnh để vài hồ sơ bất thường không ép phần còn lại vào một góc.
  • Nhãn của từng khu vực không do LLM viết: mỗi vùng được đặt tên theo một hoặc hai chủ đề đặc trưng nhất so với phần còn lại của bản đồ, chia thành 3 cấp: 25 vùng, 250 chủ đề và 2.500 chủ đề con.

2. Không bao giờ tải toàn bộ bản đồ

Cách đơn giản nhất là gửi tất cả các điểm xuống trình duyệt, nhưng với 3 triệu kênh thì dữ liệu lên tới hàng chục MB. Chúng tôi làm ngược lại:

  • Các điểm được chia thành tile theo cây quadtree, giống bản đồ đường phố trực tuyến. Mỗi tile chứa tối đa 4.096 kênh.
  • Tile gốc chứa các kênh lớn nhất, đủ để vẽ hình dạng của cả bản đồ. Kênh nhỏ chỉ được tải khi người dùng phóng to, và chỉ cho vùng đang hiển thị.
  • Màn hình đầu tiên chỉ cần khoảng 90 KB dữ liệu: manifest (1,3 KB), nhãn khu vực (59 KB) và tile gốc (32 KB).

3. 8 byte cho mỗi kênh

Tile là một buffer nhị phân, không có JSON hay tên kênh. Mỗi kênh chiếm 8 byte: tọa độ trong tile (hai số uint16), danh mục, ngôn ngữ và nhóm số người theo dõi (mỗi thứ một byte). Một tile đầy khoảng 32 KB, và việc giải mã chỉ là đọc typed array:

const u16 = new Uint16Array(buf);
const u8 = new Uint8Array(buf);
for (let i = 0; i < n; i++) {
  x[i] = (tx + u16[i * 4] / 65535) / k;
  y[i] = (ty + u16[i * 4 + 1] / 65535) / k;
  cat[i] = u8[i * 8 + 4];
  lang[i] = u8[i * 8 + 5];
  tier[i] = u8[i * 8 + 6];
}

Tên kênh nằm trên server. Trình duyệt chỉ hỏi khi người dùng di chuột qua hoặc nhấp vào một điểm, còn khi phóng to sâu thì hỏi theo từng vùng.

4. WebGL 2: mỗi tile một lần vẽ

Mỗi tile được đưa lên GPU một lần và vẽ bằng một lệnh gl.drawArrays(gl.POINTS, ...). Màu, kích thước và bộ lọc ngôn ngữ được tính trong vertex shader:

bool on = u_lang < 0 || lang == u_lang;
float a = (on ? 0.88 : 0.07) * u_alpha;
gl_PointSize = u_sizes[tier] * u_dpr * (on ? 1.0 : 0.8);

Nhờ vậy, đổi bộ lọc ngôn ngữ chỉ là đổi một uniform, không phải tải lại dữ liệu nào. 05.jpg

Kết luận

Bí quyết không nằm ở một thư viện đặc biệt mà ở việc tải đúng phần cần thiết: tile theo quadtree, định dạng nhị phân gọn, dữ liệu nặng để lại trên server và để GPU làm phần việc của nó. Nếu bạn từng nén point cloud xuống dưới 8 byte mỗi điểm mà vẫn giữ đủ độ chính xác, hãy chia sẻ trong phần bình luận nhé.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí