Ô bản đồ nền Protomaps Basemap

Toàn bộ bản đồ nền Protomaps Basemap, mức thu phóng từ 0 đến 15, được phân phối dưới dạng một tệp lưu trữ PMTiles duy nhất qua giao thức HTTP thông thường. Trỏ bất kỳ trình khách PMTiles nào vào URL này là hoạt động ngay.

https://tiles.jasontally.com/basemap.pmtiles

Ô bản đồ OpenStreetMap bị chặn, hoặc gặp lỗi 403 từ tile.openstreetmap.org? Bắt đầu tại đây.

Dùng thử

Cùng một tệp lưu trữ với ba kiểu hiển thị. Khi đổi kiểu, các ô bản đồ đã tải vẫn được giữ nguyên nên việc chuyển đổi diễn ra tức thì.

z0

Tự tạo kiểu riêng

Đổi màu hoặc tắt một lớp, bản đồ sẽ cập nhật ngay khi bạn thao tác. Sau đó sao chép JSON kiểu — đây là một kiểu MapLibre tiêu chuẩn mà bạn có thể tự lưu trữ. Kiểu này trỏ tới các ô bản đồ tại đây nên sẽ hoạt động ở bất cứ đâu bạn đặt nó.

Cách sử dụng

Tệp lưu trữ hỗ trợ yêu cầu phạm vi HTTP (HTTP range request), vì vậy mọi trình khách PMTiles đều đọc được.

Một. Đưa bản đồ lên trang web

Sao chép tệp này. Đây là toàn bộ ví dụ: ba thẻ, một URL kiểu, một bản đồ. Cả hai thư viện đều được cố định phiên bản và theo giấy phép BSD-3-Clause; các tệp tương tự cũng được phục vụ từ máy chủ này nếu bạn không muốn dùng CDN.

<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Basemap</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css">
  <script src="https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/pmtiles@3.2.1/dist/pmtiles.js"></script>
  <style>html, body, #map { margin: 0; height: 100%; }</style>
</head>
<body>
  <div id="map"></div>
  <script>
    // Teach MapLibre how to read a PMTiles archive.
    const protocol = new pmtiles.Protocol();
    maplibregl.addProtocol("pmtiles", protocol.tile);

    new maplibregl.Map({
      container: "map",
      style: "https://tiles.jasontally.com/styles/light.json",
      center: [2.35, 48.85],   // Paris
      zoom: 4
    });
  </script>
</body>
</html>

Hai. Thay thế ô bản đồ trong kiểu bạn đã có

Mọi kiểu MapLibre đều đọc ô bản đồ từ một nguồn dữ liệu. Để dùng các ô bản đồ này, chỉ cần đổi trường url đó. Mọi phần khác trong kiểu đều giữ nguyên.

  "sources": {
    "protomaps": {
      "type": "vector",
-     "url": "pmtiles://https://demo-bucket.protomaps.com/v4.pmtiles",
+     "url": "pmtiles://https://tiles.jasontally.com/basemap.pmtiles",
      "attribution": "<a href=\"https://www.openstreetmap.org/copyright\">&copy; OpenStreetMap contributors</a>"
    }
  }

Hoặc thực hiện bằng mã lệnh, hữu ích khi kiểu được tải từ một máy chủ mà bạn không kiểm soát:

// Rewrite every PMTiles source in a style to point at these tiles.
const style = await (await fetch("https://your-server/style.json")).json();

for (const source of Object.values(style.sources)) {
  if (source.url?.startsWith("pmtiles://")) {
    source.url = "pmtiles://https://tiles.jasontally.com/basemap.pmtiles";
  }
}

new maplibregl.Map({ container: "map", style, center: [2.35, 48.85], zoom: 4 });

Ba. Đặt kiểu bạn đã tạo ở đâu

Nút JSON kiểu trong trình tạo ở trên mở ra kiểu mà bản đồ này đang vẽ ngay lúc này, bao gồm cả các chỉnh sửa của bạn, và hộp thoại có sẵn nút sao chép. Có hai cách sử dụng kết quả nhận được, cả hai đều là trang web thông thường.

Lưu nó thành một tệp nằm cạnh trang của bạn và trỏ style vào đó, giống cách ba kiểu trên máy chủ này được sử dụng. Hãy chọn cách này nếu kiểu đó là của riêng bạn và thay đổi cùng với trang web:

// Save the copied JSON as my-basemap.json beside this file.
new maplibregl.Map({
  container: "map",
  style: "my-basemap.json",           // relative, same origin
  center: [2.35, 48.85],
  zoom: 4
});

Hoặc giữ nó ngay trong tệp HTML, chỉ cần di chuyển một tệp duy nhất và không phải lưu trữ thêm gì. Cùng một kiểu, chỉ khác nơi đặt:

// The copied JSON, pasted straight into the page.
const myStyle = {
  "version": 8,
  "name": "Protomaps Basemap — custom",
  "glyphs": "https://tiles.jasontally.com/font/{fontstack}/{range}.pbf",
  "sprite": "https://tiles.jasontally.com/sprites/v4/light",
  "sources": { /* ... as copied ... */ },
  "layers":  [ /* ... as copied ... */ ]
};

new maplibregl.Map({ container: "map", style: myStyle, zoom: 4 });

Giữ nguyên các dòng glyphs và sprite. Chúng dùng để vẽ nhãn chữ và biểu tượng, đồng thời trỏ về máy chủ này, nên kiểu bạn dán vào trang riêng vẫn hoạt động dù bạn phục vụ trang từ đâu. Hãy trỏ chúng tới protomaps.github.io/basemaps-assets nếu bạn không muốn phụ thuộc vào máy chủ này.

Một điều cần kiểm tra trước khi thay thế. Tệp lưu trữ này dùng lược đồ Protomaps v3. Kiểu được viết cho lược đồ khác sẽ tải xuống nhưng không vẽ gì cả, vì tên các lớp nguồn không khớp. Chín tên lớp được liệt kê bên dưới, cùng với một kiểu v3 dựng sẵn để bạn sao chép.

Bốn. Phông chữ, sprite và thư viện

Một bản đồ gồm có các ô bản đồ, một URL glyphs cho văn bản, tùy chọn một URL sprite cho biểu tượng, và các thư viện để vẽ chúng. Tất cả đều có tại đây theo đúng đường dẫn mà từng thành phần yêu cầu, nên toàn bộ bản đồ có thể được tải từ một tên miền duy nhất nếu bạn muốn.

Thành phầnURL tại đâyGiấy phép

Các tệp MapLibre và PMTiles đều có trên jsDelivr và tại đây. Dùng URL nào cũng được.

Hàng cuối cùng không được trang này sử dụng. Đó là trình kết xuất vector cho Leaflet, được cung cấp tại đây để việc chuyển đổi chỉ cần một thẻ script. Bản thân Leaflet vẫn tải từ CDN riêng của nó.

Hai bộ phông chữ, Noto Sans Regular và Noto Sans Medium, được chia thành 256 dải mà MapLibre yêu cầu, mỗi dải 256 điểm mã. Toàn bộ 256 dải đều được xuất bản chứ không riêng chữ Latinh: Protomaps gắn nhãn đối tượng bằng tên bản địa, nên Tokyo hay Cairo cần các dải ở hàng chục nghìn. Nếu chỉ xuất bản những gì bản đồ tiếng Anh dùng, các nhãn đó sẽ biến mất mà không báo lỗi.

Năm bảng sprite, mỗi bảng ứng với một biến thể giao diện gốc, ở tỷ lệ 1x và 2x. Các kiểu bên dưới lấy biểu tượng từ những bảng này. Nếu bạn muốn có chấm đô thị, biển hiệu đường cao tốc hoặc điểm địa danh trong kiểu riêng của mình, hãy trỏ sprite tới bảng tương ứng. Tên các biểu tượng trong bảng được liệt kê trong tệp light.json của nó.

Bản đồ vẽ những gì và nguồn của từng thành phần

Mọi lớp đều thuộc một trong chín lớp nguồn trong tệp lưu trữ này, nên không cần đến tệp lưu trữ thứ hai. Có hai mục đáng lưu ý vì trông giống như bị thiếu nhưng thực ra không phải:

LớpNguồn từCó trong bản dựng này?
Hình dạng, đường giao thông, tòa nhà, mặt nước, đất liềnearth, landcover, landuse, water, roads, buildingscó
Tên địa danh, đường giao thông và mặt nướctrường name trên chính các lớp đócó
Biểu tượng địa điểm, chấm đô thịpois.kind, places, cộng với spritecó
Biển hiệu tuyến đườngroads.shield_textkhông. Bản dựng đang phục vụ không có trường đó
Số nhàbuildings.kind=address và buildings.addr_housenumberkhông, cùng lý do trên

Không có tệp lưu trữ địa chỉ riêng, cũng không có lớp địa chỉ riêng để tải. Dự án gốc đặt các điểm địa chỉ bên trong lớp buildings dưới dạng các đối tượng có kind=address và addr_housenumber. Trường đó mới xuất hiện trong bản dựng Protomaps muộn hơn bản mà bản đồ này đang phục vụ, nên lớp số nhà đã được thiết kế sẵn và sẽ tự bật khi đợt làm mới hàng tháng tiếp theo bổ sung dữ liệu. Văn bản biển hiệu tuyến đường cũng vậy: bản dựng hiện tại chỉ có shield_text_length, cho biết số ký tự của mã tuyến đường chứ không phải nội dung cụ thể, nên nếu vẽ biển hiệu từ đó thì trên mọi đường cao tốc sẽ chỉ là khung trống.

Tự viết một lớp và tô màu cho nó

Một lớp bao gồm bộ lọc, mức thu phóng, hình dạng và màu sắc. Có hai điểm trong bộ lọc mà nhiều người hay gặp phải, và cả hai đều khiến lớp không hiển thị mà không báo lỗi:

Dưới đây là một con đường, tên của nó, và nút bật/tắt để ẩn đường kẻ:

{
  "id": "road-major",
  "type": "line",
  "source": "protomaps",
  "source-layer": "roads",            // one of the nine
  "minzoom": 4,                                  // not drawn below this
  "filter": ["==", ["get", "kind"], "major_road"],
  "layout": {
    "line-cap": "round",
    "visibility": "visible"          // "none" to hide it
  },
  "paint": {
    "line-color": "#f7c44d",          // any CSS colour
    "line-width": ["interpolate", ["linear"], ["zoom"], 4, 1.7, 12, 6]
  }
}

Tên là một lớp ký hiệu đọc trường name, có viền sáng (halo) hoặc không có gì phía sau. Nếu thiếu glyph thì chữ sẽ lặng lẽ biến mất, nên bộ phông bạn yêu cầu phải là một trong hai bộ có trên máy chủ này:

{
  "id": "road-label-major",
  "type": "symbol",
  "source": "protomaps",
  "source-layer": "roads",
  "minzoom": 11,
  "layout": {
    "text-field": ["coalesce", ["get", "name:en"], ["get", "name"]],
    "text-font": ["Noto Sans Medium"],  // or Noto Sans Regular
    "text-size": 10,
    "symbol-placement": "line"       // follow the road
  },
  "paint": {
    "text-color": "#2b2a26",
    "text-halo-color": "#ffffff",
    "text-halo-width": 1.1
  }
}

Một lớp không vẽ ra gì không được coi là lỗi, và MapLibre sẽ không cho biết phép kiểm tra nào bị trượt. Nếu lớp của bạn trống trơn, hãy kiểm tra theo thứ tự sau: tên trường, minzoom của lớp, bộ phông chữ, rồi đến tên biểu tượng đối chiếu với tệp light.json của bảng sprite.

Nếu bạn có một kiểu đang trỏ glyph tới demotiles.maplibre.org, chỉ cần thay tên máy chủ đó là xong. Đường dẫn được giữ giống hệt một cách có chủ đích.

"glyphs": "https://tiles.jasontally.com/font/{fontstack}/{range}.pbf",
"sprite": "https://tiles.jasontally.com/sprites/v4/light"

// before, in a style you already have
- "glyphs": "https://demotiles.maplibre.org/font/{fontstack}/{range}.pbf",
+ "glyphs": "https://tiles.jasontally.com/font/{fontstack}/{range}.pbf",

Nếu một kiểu yêu cầu bộ phông chữ không nằm trong hai bộ kể trên, sẽ không có chữ nào được vẽ. Các kiểu của chính Protomaps dùng Noto Sans Medium cho chữ đậm; trong bộ này không có Noto Sans Bold, đó là lý do một kiểu yêu cầu phông đậm đó sẽ lặng lẽ mất toàn bộ nhãn.

Máy chủ nào nhanh hơn và quyết định đã được đưa ra. Được đo đạc thực tế thay vì phỏng đoán: các yêu cầu xen kẽ, từng cái một trên cùng một kết nối, bỏ qua ba mẫu đầu tiên, tách biệt thời gian nhận byte đầu tiên với thời gian truyền tải. Trong bài kiểm tra đó, CDN phản hồi nhanh hơn từ 10 đến 45 ms cho từng tệp, vượt rõ ngưỡng quy tắc 15%. Vì lý do đó, các thư viện được tải từ jsDelivr.

Phông chữ và sprite được phục vụ từ đây dù cùng phép đo cho thấy CDN sẽ phản hồi nhanh hơn khoảng 10 ms. Đó là một quyết định lựa chọn, không phải phép đo. Bản đồ tải từ một tên miền duy nhất sẽ không bị hỏng khi dịch vụ của bên khác gặp sự cố, còn máy chủ glyph bị sập đồng nghĩa với bản đồ mất sạch nhãn chữ. Nếu bạn muốn nhanh hơn 10 ms đó, hãy trỏ glyphs tới protomaps.github.io/basemaps-assets/fonts với cùng đường dẫn. Đây là số liệu đo từ một thành phố, và bạn có thể chạy lại phép so sánh chỉ bằng một lệnh.

Năm. Về Leaflet

L.tileLayer của Leaflet vẽ các bức ảnh, còn đây là các ô bản đồ vector. Bản đồ Leaflet vẫn có thể dùng chúng, và cách nào cũng giữ nguyên được điểm đánh dấu (marker), hộp bật lên (popup) và bộ điều khiển của bạn. Cả hai cách chuyển đổi đều có trong mục Chuyển từ ô bản đồ OpenStreetMap.

Sáu. Đọc tệp lưu trữ bằng thư viện PMTiles

Không có bản đồ, chỉ có dữ liệu. Hữu ích cho tập lệnh, kiểm thử và bất kỳ tác vụ nào không cần hiển thị hình ảnh.

import { PMTiles, FetchSource } from "pmtiles";

const archive = new PMTiles(new FetchSource("https://tiles.jasontally.com/basemap.pmtiles"));

const header = await archive.getHeader();
console.log(header.minZoom, header.maxZoom);        // 0 15
console.log(header.numAddressedTiles);              // 1431655765

const metadata = await archive.getMetadata();
console.log(metadata.name);                          // "Protomaps Basemap"

const tile = await archive.getZxy(3, 4, 2);          // z, x, y
console.log(tile.data.byteLength);

Lưu ý hai lớp đối tượng. FetchSource là bộ đọc byte qua HTTP, còn PMTiles là bộ đọc nằm phía trên nó và hiểu định dạng tệp. FileSource là bản tương đương dành cho tệp cục bộ của FetchSource; nếu truyền vào một chuỗi URL thì nó sẽ không hoạt động.

Tệp lưu trữ là một bản chụp tĩnh, không phải luồng dữ liệu trực tiếp. Xem lịch trình cụ thể tại mục Dữ liệu mới đến mức nào?.

Chuyển từ ô bản đồ OpenStreetMap

Lệnh chặn theo chính sách sử dụng ô bản đồ, lỗi 403, và cách khắc phục. Không cần khóa, không hạn ngạch.

Vì sao bản đồ hiển thị thông báo chặn

App is not following the tile usage policy of OpenStreetMap’s volunteer-run servers

General block: app is not following the tile usage policy of openstreetmap volunteer-run servers

403 Forbidden — tile.openstreetmap.org

Các máy chủ ô bản đồ đã chủ đích gửi hình ảnh đó thay cho bản đồ theo chính sách sử dụng ô bản đồ. Đây không phải lỗi kỹ thuật, và việc sửa lại URL không làm hết bị chặn.

Những cách mọi người thường thử trước tiên:

Văn phòng, trường học, VPN và mạng di động dùng chung địa chỉ IP, mà lệnh chặn áp dụng cho cả địa chỉ lẫn ứng dụng. Từ phía bạn sẽ không thể phân biệt mình đang rơi vào trường hợp nào.

Vì vậy, hãy ngừng gửi yêu cầu tới các máy chủ đó. Có ba cách để chuyển sang dùng nguồn tại đây.

Giữ nguyên Leaflet

protomaps-leaflet vẽ các ô bản đồ vector trong Leaflet. Thư viện này tự đọc tệp lưu trữ và thêm chính nó như một lớp thông thường, nhờ vậy các điểm đánh dấu, hộp bật lên, GeoJSON và bộ điều khiển đều giữ nguyên. Một bản sao đã được kiểm thử với tệp lưu trữ này có sẵn tại đây: https://tiles.jasontally.com/vendor/protomaps-leaflet.js.

Trước:

<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css">
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>

<script>
  const map = L.map("map").setView([48.85, 2.35], 12);

  L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", {
    maxZoom: 19,
    attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
  }).addTo(map);
</script>

Sau:

<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css">
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
+ <script src="https://tiles.jasontally.com/vendor/protomaps-leaflet.js"></script>

<script>
  const map = L.map("map").setView([48.85, 2.35], 12);

-  L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", { ... }).addTo(map);
+  protomapsL.leafletLayer({
+    url: "https://tiles.jasontally.com/basemap.pmtiles",
+    flavor: "light",
+    lang: "en"
+  }).addTo(map);
</script>

Dự án gốc của protomaps-leaflet đang ở chế độ bảo trì: chỉ sửa lỗi, không thêm tính năng mới. Nó vẫn phù hợp cho ứng dụng Leaflet sẵn có. Các dự án mới nên bắt đầu với MapLibre GL.

Giữ nguyên Leaflet, kết xuất bằng MapLibre

@maplibre/maplibre-gl-leaflet đặt một bản đồ MapLibre vào khung chứa ô bản đồ của Leaflet. Hãy dùng cách này khi bạn muốn áp dụng một trong các kiểu ở trên, hoặc địa hình và bóng đồi (hillshade), trong một ứng dụng vẫn giữ nền tảng Leaflet.

<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css">
<link rel="stylesheet" href="https://tiles.jasontally.com/vendor/maplibre-gl.css">
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script src="https://tiles.jasontally.com/vendor/maplibre-gl.js"></script>
<script src="https://tiles.jasontally.com/vendor/pmtiles.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@maplibre/maplibre-gl-leaflet@0.1.4/dist/leaflet-maplibre-gl.js"></script>

<script>
  // The style's source is pmtiles://, so MapLibre needs the protocol first.
  maplibregl.addProtocol("pmtiles", new pmtiles.Protocol().tile);

  const map = L.map("map", { minZoom: 1 }).setView([48.85, 2.35], 12);

  const gl = L.maplibreGL({
    style: "https://tiles.jasontally.com/styles/light.json"
  }).addTo(map);

  // gl.getMaplibreMap() is the MapLibre map, for sources and layers.
</script>

Chuyển sang MapLibre GL

Một thư viện duy nhất, không cần bộ chuyển đổi, và các kiểu ở trên hoạt động nguyên bản. Toàn bộ mã trang mẫu nằm ở mục Một. Đưa bản đồ lên trang web. Việc chuyển đổi chủ yếu là đảo thứ tự tọa độ:

LeafletMapLibre GL
L.map("map").setView([lat, lng], z)new maplibregl.Map({ container: "map", center: [lng, lat], zoom: z })
L.tileLayer(url).addTo(map)một kiểu có nguồn dữ liệu vector; xem ở trên
L.marker([lat, lng]).bindPopup(html)new maplibregl.Marker().setLngLat([lng, lat]).setPopup(new maplibregl.Popup().setHTML(html))
L.geoJSON(data)addSource(id, { type: "geojson", data }), sau đó gọi addLayer
L.control.scale()new maplibregl.ScaleControl()
map.invalidateSize()map.resize()

setView([lat, lng]) nhận vĩ độ trước; mọi lệnh gọi của MapLibre đều nhận [lng, lat] (kinh độ trước). Nếu bản đồ hiện ra ở sai bán cầu thì nguyên nhân là do thứ tự này chứ không phải do các ô bản đồ. Các nguồn và lớp được thêm trước khi kiểu tải xong sẽ bị bỏ qua mà không báo lỗi, vì vậy hãy thêm chúng bên trong map.on("load", …).

“archive contains MVT vector tiles, but leafletRasterLayer is for displaying raster tiles”

Đó là thông báo của pmtiles.leafletRasterLayer, hàm dùng để vẽ PMTiles dạng ảnh raster. Các ô tại đây là dữ liệu vector nên hàm đó từ chối. Không có tùy chọn nào thay đổi được điều này và tại đây cũng không có bản sao dạng raster. Hãy dùng một trong các cách ở trên.

“download.openstreetmap.fr returned 403”

Đó là máy chủ sao lưu và trích xuất dữ liệu chứ không phải máy chủ ô bản đồ. Vào tháng 10 năm 2026, máy chủ này đã chặn nhiều địa chỉ sau các đợt tải xuống lạm dụng, và đơn vị vận hành cho biết họ sẽ khôi phục quyền truy cập theo từng trường hợp. Không có gì tại đây cần tới máy chủ đó: tệp lưu trữ này lấy từ các bản phát hành của Protomaps, nên bạn không cần tự ghép tệp .osm.pbf. Nếu bạn có nhu cầu lấy dữ liệu OSM, hãy tải bản trích xuất định kỳ thay vì luồng cập nhật từng phút.

Các kiểu

Cùng bộ ô bản đồ, màu sắc khác nhau. Chọn một kiểu, sao chép, chỉnh sửa — nó là của bạn.

KiểuGiao diệnTệp

Có gì bên trong các ô bản đồ

Được đọc trực tiếp từ siêu dữ liệu của tệp lưu trữ khi tải trang. Đây là tên các lớp mà một kiểu bắt buộc phải sử dụng.

Lớp nguồnThu phóngCác trường
đang tải…

Những điều dịch vụ này không cung cấp

Câu trả lời thẳng thắn để bạn không gặp bất ngờ về sau.

Dữ liệu mới đến mức nào?

Các ô bản đồ là bản sao của Protomaps Basemap, được dựng lại mỗi tháng một lần.

JSON kiểu

open raw