0
0
Lập trình
Sơn Tùng Lê
Sơn Tùng Lê103931498422911686980

Cách Tính Kích Thước Pixel của Bounding Box tại Các Mức Phóng Đại Khác Nhau

Đăng vào 1 tháng trước

• 9 phút đọc

Giới thiệu

Khi tạo bản đồ in hoặc hình ảnh bản đồ tĩnh, một trong những thách thức lớn nhất là kiểm soát tỷ lệ: diện tích mà tôi quan tâm sẽ rộng và cao bao nhiêu pixel?

Hãy tưởng tượng rằng bạn có một bounding box được định nghĩa bởi hai góc tọa độ latitude/longitude. Tùy thuộc vào mức phóng đại, chiều rộng và chiều cao của nó sẽ thay đổi theo pixel. Đôi khi bạn biết mức phóng đại và muốn đo kích thước pixel; những lúc khác, bạn biết chiều rộng (hoặc chiều cao) mong muốn của hình ảnh và muốn tìm mức phóng đại cho phép box vừa vặn.

Điều này đặc biệt hữu ích khi làm việc với Geoapify Static Maps API — ví dụ:

  • in bản đồ với kích thước cụ thể,
  • tạo hình ảnh tĩnh mà các nhãn vẫn dễ đọc,
  • đảm bảo khu vực yêu cầu vừa vặn với kích thước hình ảnh.

Trong bài viết này, chúng ta sẽ đề cập đến ba kịch bản với các hàm hỗ trợ JavaScript đơn giản và một bản demo trực tiếp:

  1. Tính toán chiều rộng và chiều cao của bbox tại một mức phóng đại cho trước.
  2. Tính toán chiều rộng khi chiều cao cố định.
  3. Tính toán chiều cao khi chiều rộng cố định.

Hãy thử nghiệm với bản demo: Demo Geoapify


Bối cảnh: Mức Phóng Đại, Tọa Độ Latitude/Longitude và Pixel

Hầu hết các thư viện bản đồ hiện đại — Leaflet, MapLibre GL, Google Maps, OpenLayers — đều dựa vào Web Mercator projection (EPSG:3857). Phép chiếu này chuyển đổi tọa độ địa lý (latitude/longitude) thành một bản đồ phẳng, hình vuông có thể được gạch và hiển thị trên màn hình.

Cách hoạt động:

  • Kinh độ ánh xạ tuyến tính với trục X. Tại mức phóng đại 0, toàn bộ dải −180° … +180° vừa vặn vào một ô 256 px.
  • Vĩ độ được chiếu bằng công thức Mercator, điều này kéo dài khoảng cách gần các cực. Để tránh các giá trị vô hạn, bản đồ bị cắt đến khoảng ±85.05113° vĩ độ.

Kết quả là một lưới các ô (tiles), mỗi ô thường có kích thước 256×256 px:

  • Tại phóng đại 0, thế giới là một ô (256 px rộng).
  • Tại phóng đại 1, thế giới rộng 512 px (2 ô × 256).
  • Tại phóng đại 2, rộng 1024 px.
  • Nói chung:
Copy
worldSizePx = 256 × 2^z

Mỗi bước phóng đại nhân đôi số pixel có sẵn cho cả kinh độ và vĩ độ.

👉 Xem trực tiếp: Demo zoom collage.
👉 Đọc thêm: Hiểu về mức phóng đại và tọa độ ô XYZ.

Sự chuyển đổi từ lat/lon sang pixel giải thích tại sao chiều rộng/chiều cao pixel của một bounding box phụ thuộc vào mức phóng đại. Một khu vực 1° × 1° gần xích đạo có thể chỉ trải dài vài pixel tại phóng đại 2, nhưng hàng ngàn pixel tại phóng đại 12 — và tại các vĩ độ cao, phép chiếu Mercator làm tăng thêm hiệu ứng này.

Các kịch bản thực tiễn:

  1. Tính toán chiều rộng/chiều cao của bounding box tại một mức phóng đại cho trước.
  2. Với chiều cao hình ảnh cố định, tìm chiều rộng tương ứng.
  3. Với chiều rộng hình ảnh cố định, tìm chiều cao tương ứng.

Kịch bản 1: Chiều rộng và chiều cao tại một Mức Phóng Đại Cho Trước

Trường hợp đơn giản nhất: bạn đã biết mức phóng đại và muốn đo xem một bounding box địa lý sẽ rộng và cao bao nhiêu pixel.

Điều này yêu cầu chuyển đổi latitude/longitude sang tọa độ pixel thế giới trong Web Mercator. Phép toán trông như sau:

  • Kinh độ → X (tuyến tính):
Copy
  x = (lon + 180) / 360 × worldSize
  • Vĩ độ → Y (phép chiếu Mercator):
Copy
  y = (0.5 - log((1 + sinφ) / (1 - sinφ)) / (4π)) × worldSize

Trong đó φ là vĩ độ được biểu diễn bằng radian (tức là, φ = lat × π / 180).

Dưới đây là một hàm tiện ích nhỏ trong JavaScript:

javascript Copy
const TILE_SIZE = 256;

// Chuyển đổi lon/lat sang tọa độ pixel thế giới tại mức phóng đại
function lonLatToWorldPixels(lon, lat, zoom, tileSize = TILE_SIZE) {
  const scale = tileSize * Math.pow(2, zoom);
  const x = (lon + 180) / 360 * scale;

  const sin = Math.sin((lat * Math.PI) / 180);
  const y = (0.5 - Math.log((1 + sin) / (1 - sin)) / (4 * Math.PI)) * scale;

  return { x, y };
}

// Tính toán kích thước bbox trong pixel tại một mức phóng đại cho trước
function bboxSizePx(bbox, zoom, tileSize = TILE_SIZE) {
  const [minLon, minLat, maxLon, maxLat] = bbox;
  const p1 = lonLatToWorldPixels(minLon, minLat, zoom, tileSize);
  const p2 = lonLatToWorldPixels(maxLon, maxLat, zoom, tileSize);
  return {
    width: Math.abs(p2.x - p1.x),
    height: Math.abs(p2.y - p1.y)
  };
}

Ví dụ

Hãy đo một bounding box quanh Paris:

javascript Copy
const bboxParis = [2.1, 48.7, 2.5, 49.0]; // [minLon, minLat, maxLon, maxLat]

console.log(bboxSizePx(bboxParis, 5));
// → { width: 9.10 px, height: 10.37 px }

console.log(bboxSizePx(bboxParis, 10));
// → { width: 291.27 px, height: 331.98 px }

Tại mức phóng đại 5, bounding box của Paris chỉ rộng vài pixel. Tại mức phóng đại 10, nó đã rộng hàng trăm pixel.

Điều này giải thích tại sao, khi in hoặc tạo hình ảnh tĩnh, việc chọn mức phóng đại đúng là rất quan trọng: quá thấp và khu vực của bạn trông sẽ rất nhỏ, quá cao và nó sẽ không vừa vặn với kích thước hình ảnh.

Kịch bản 2: Chiều rộng từ một Chiều cao Cố định

Đôi khi bạn không cố định mức phóng đại trực tiếp — thay vào đó, bạn biết chiều cao mà hình ảnh nên có bằng pixel. Từ chiều cao đó, bạn có thể suy ra chiều rộng tương ứng của bounding box (hoặc mức phóng đại cho phép bạn có chiều cao đó).

Phép toán hoạt động vì trong Web Mercator, cả chiều rộng và chiều cao đều tỷ lệ thuận với 2^z. Nếu bạn biết một kích thước, bạn có thể tính toán mức phóng đại và sau đó suy ra kích thước còn lại.

Hàm hỗ trợ JavaScript

javascript Copy
// Tính toán trước các delta bbox tại mức phóng đại 0
function bboxDeltaAtZoom0(bbox, tileSize = TILE_SIZE) {
  const [minLon, minLat, maxLon, maxLat] = bbox;

  const x0a = tileSize * (minLon + 180) / 360;
  const x0b = tileSize * (maxLon + 180) / 360;
  const dx0 = Math.abs(x0b - x0a);

  const mercY = (lat) => {
    const s = Math.sin((lat * Math.PI) / 180);
    return (0.5 - Math.log((1 + s) / (1 - s)) / (4 * Math.PI)) * tileSize;
  };
  const dy0 = Math.abs(mercY(maxLat) - mercY(minLat));

  return { dx0, dy0 };
}

// Với một bbox và chiều cao mục tiêu, tìm mức phóng đại
function zoomForHeight(bbox, targetHeightPx, tileSize = TILE_SIZE) {
  const { dy0 } = bboxDeltaAtZoom0(bbox, tileSize);
  return Math.log2(targetHeightPx / dy0);
}

// Từ mức phóng đại đó, tính chiều rộng
function widthFromHeight(bbox, targetHeightPx, tileSize = TILE_SIZE) {
  const z = zoomForHeight(bbox, targetHeightPx, tileSize);
  const { width } = bboxSizePx(bbox, z, tileSize);
  return { zoom: z, width };
}

Ví dụ

javascript Copy
const bboxParis = [2.1, 48.7, 2.5, 49.0]; // Paris bbox

const result = widthFromHeight(bboxParis, 600);
console.log(result);
// → { zoom:  10.854, width: 526.42 px }

Điều này cho chúng ta biết:

  • Nếu chúng ta muốn bounding box của Paris có chiều cao 600 px, chúng ta cần khoảng mức phóng đại 10.9.
  • Tại mức phóng đại đó, chiều rộng sẽ là ~526 px.

Tại sao điều này hữu ích

Kịch bản này rất tiện lợi khi bạn biết chiều cao hình ảnh (ví dụ, kích thước giấy cố định, hoặc một bản đồ tĩnh theo chiều dọc) và muốn chiều rộng tỷ lệ tương ứng.

Kịch bản 3: Chiều cao từ một Chiều rộng Cố định

Trong nhiều bố cục, chiều rộng của hình ảnh được cố định (container trang web, cột in). Từ chiều rộng đó, bạn có thể giải quyết mức phóng đại trước và sau đó suy ra chiều cao của bounding box.

Hàm hỗ trợ JavaScript

javascript Copy
// Tính toán trước các delta bbox tại mức phóng đại 0 (tái sử dụng nếu đã định nghĩa)
function bboxDeltaAtZoom0(bbox, tileSize = 256) {
  const [minLon, minLat, maxLon, maxLat] = bbox;

  const x0a = tileSize * (minLon + 180) / 360;
  const x0b = tileSize * (maxLon + 180) / 360;
  const dx0 = Math.abs(x0b - x0a);

  const mercY = (lat) => {
    const s = Math.sin((lat * Math.PI) / 180);
    return (0.5 - Math.log((1 + s) / (1 - s)) / (4 * Math.PI)) * tileSize;
  };
  const dy0 = Math.abs(mercY(maxLat) - mercY(minLat));

  return { dx0, dy0 };
}

// Giải quyết mức phóng đại cho một chiều rộng pixel mục tiêu
function zoomForWidth(bbox, targetWidthPx, tileSize = 256) {
  const { dx0 } = bboxDeltaAtZoom0(bbox, tileSize);
  return Math.log2(targetWidthPx / dx0);
}

// Từ mức phóng đại đó, tính chiều cao
function heightFromWidth(bbox, targetWidthPx, tileSize = 256) {
  const z = zoomForWidth(bbox, targetWidthPx, tileSize);
  const { height } = bboxSizePx(bbox, z, tileSize); // tái sử dụng bboxSizePx từ Kịch bản 1
  return { zoom: z, height };
}

Ví dụ

javascript Copy
const bboxParis = [2.1, 48.7, 2.5, 49.0]; // [minLon, minLat, maxLon, maxLat]

const result = heightFromWidth(bboxParis, 800);
console.log(result);
// → { zoom: 11.458, height: 911.81 px }

Giải thích:

  • Để làm cho bounding box của Paris rộng 800 px, bạn cần mức phóng đại khoảng 11.5.
  • Tại mức phóng đại đó, bounding box sẽ cao khoảng 912 px.

Khi điều này hữu ích

  • Các container web có chiều rộng cố định (các trang responsive mà chiều cao có thể tăng).
  • Hình ảnh bản đồ tĩnh theo chiều ngang phải khớp với chiều rộng mục tiêu.
  • Bố cục in mà chiều rộng cột bị hạn chế và chiều cao có thể mở rộng.

Hướng dẫn Demo

Để làm cho toán học trở nên trực quan hơn, chúng tôi đã chuẩn bị một bản demo trực tiếp mà bạn có thể khám phá trong trình duyệt của mình:

Bản demo này cho bạn thấy:

  • Kịch bản 1: tính toán chiều rộng và chiều cao của bounding box tại một mức phóng đại cho trước.
  • Kịch bản 2: tính toán chiều rộng khi chiều cao mục tiêu được chỉ định.
  • Kịch bản 3: tính toán chiều cao khi chiều rộng mục tiêu được chỉ định.

Cách sử dụng:

  1. Nhập hoặc điều chỉnh tọa độ bounding box.
  2. Chọn một mức phóng đại hoặc cung cấp chiều rộng/chiều cao mục tiêu.
  3. Xem cách kích thước bbox trong pixel cập nhật ngay lập tức.

Công cụ tương tác này giúp hình dung mối liên hệ giữa các tọa độ địa lý, mức phóng đại và kích thước pixel — cùng logic mà bạn có thể tái sử dụng trong mã khi chuẩn bị hình ảnh tĩnh hoặc bản đồ in.

Kết luận

Làm việc với bounding box trong Web Mercator không chỉ là tọa độ — nó còn liên quan đến pixel.
Trong bài viết này, chúng ta đã đề cập đến ba kịch bản thực tiễn:

  1. Chiều rộng/chiều cao tại một mức phóng đại cho trước → hữu ích khi bạn đã biết mức phóng đại và muốn đo xem khu vực của bạn sẽ xuất hiện lớn thế nào.
  2. Chiều rộng từ một chiều cao cho trước → tiện dụng khi bố cục hình ảnh hoặc in có chiều cao cố định.
  3. Chiều cao từ một chiều rộng cho trước → hoàn hảo cho các container web có chiều rộng cố định hoặc hình ảnh theo chiều ngang.

Những tính toán này đặc biệt quan trọng khi in bản đồ hoặc tạo hình ảnh tĩnh với Geoapify Static Maps API. Chúng cho phép bạn:

  • chọn mức phóng đại đúng để làm cho khu vực của bạn vừa vặn,
  • tránh cắt bỏ các đặc điểm quan trọng,
  • đảm bảo các nhãn và biểu tượng vẫn dễ đọc.

Và với bản demo, bạn có thể thử nghiệm theo thời gian thực để xem cách mà mức phóng đại, chiều rộng và chiều cao tương tác với nhau.

👉 Khám phá thêm các công cụ và API bản đồ tại Geoapify.

Gợi ý câu hỏi phỏng vấn
Không có dữ liệu

Không có dữ liệu

Bài viết được đề xuất
Bài viết cùng tác giả

Bình luận

Chưa có bình luận nào

Chưa có bình luận nào