Leaflet + MapLibre 雙引擎,這個開源工具把任意城市變成裝飾級地圖海報

作者:三木前端筆記
日期:2026年5月2日 上午3:05
來源:WeChat 原文

整理版優先睇

速讀 5 個重點 高亮

map-to-poster:雙引擎地圖海報生成工具,將城市變成藝術海報

整理版摘要

呢篇文章介紹一個叫 map-to-poster 嘅開源工具,由開發者 Dimar Tarmizi 整出嚟,核心係將任意地理位置轉成可打印嘅裝飾海報。佢同普通地圖工具最大分別係主題系統偏向藝術風格,好似 Arctic FrostAurora GlowCyber Glitch 呢類,仲有雙引擎 Leaflet 同 MapLibre GL 同時行,畀用戶喺標準地圖同藝術風格之間切換。

作者想解決嘅問題係:點樣令地圖海報唔只係資訊展示,仲可以係有視覺個性嘅裝飾品。整體結論係呢個工具透過雙引擎架構同豐富主題,做到「任何地點都變到有設計感」,而且支援高達 50,000px 嘅 PNG 導出,全部喺瀏覽器本地搞掂。

呢個項目係 MIT 協議,可以商用同二次開發,對於想做地圖海報應用嘅開發者來講,佢嘅雙引擎同步機制、主題系統同狀態管理寫法都值得參考。

  • 雙引擎 Leaflet(光柵)同 MapLibre GL(矢量)同時運行,用 isSyncing 標誌避免視口同步死循環,縮放級別有 ±1 偏移補償。
  • 藝術主題系統包括 Arctic FrostAurora GlowCyber Glitch 等,亦可透過 artistic-themes.js 自定義 MapLibre 樣式配置。
  • 支援路線繪製同標記點拖拽,將靜態地圖變成「某段經歷嘅記錄」,適合旅行紀念或活動路線圖。
  • 高分辨率導出:多層 Canvas 合成(地圖層 + UI 層),最高 50,000px,並為 iOS 嘅 canvas 上限做等比縮小兜底。
  • 狀態管理採用輕量觀察者模式,無框架依賴,透過 localStorage 持久化設定,適合學習唔用 React/Vue 點樣構建複雜 Web 應用。
值得記低
連結 github.com

GitHub 倉庫

map-to-poster 源碼,MIT 協議,可自行下載安裝

連結 maptoposter.tarmizi.id

在線 Demo

即時體驗地圖海報生成功能

整理重點

雙引擎同步:Leaflet 同 MapLibre GL 點樣夾到實

呢個工具同時用咗兩套地圖引擎——Leaflet 負責光柵瓦片,標準地圖同衞星圖靠佢;MapLibre GL 行矢量瓦片,GPU 實時渲染,係藝術主題嘅技術基礎。兩者切換時要確保位置同縮放唔會跳,所以用 雙向事件監聽 加 isSyncing 標誌 防止死循環。

視口同步代碼節錄 javascript
map.on('moveend', () => {
 if (isSyncing) return;
 isSyncing = true;
 const center = map.getCenter();
 const zoom = map.getZoom();
 artisticMap.jumpTo({ center: [center.lng, center.lat], zoom: zoom - 1 });
 isSyncing = false;
});

artisticMap.on('moveend', () => {
 if (isSyncing) return;
 isSyncing = true;
 const center = artisticMap.getCenter();
 const zoom = artisticMap.getZoom();
 map.setView([center.lat, center.lng], zoom + 1);
 isSyncing = false;
});

另外,MapLibre 切換主題係非同步嘅,為咗避免快速切換嘅衝突,項目用咗個 隊列機制:加載期間先記低最新請求,等完成後再拎下一個。

整理重點

主題系統:由簡約到故障藝術,仲可以自己改

內置主題分兩種:標準主題 有 Minimal WhiteMidnight DarkSatellite View,適合常規海報;藝術主題 就玩到 Arctic Frost、Aurora Glow、Cyber Glitch 呢類強烈風格。如果嫌唔夠,可以直接改 artistic-themes.js 檔案加自訂主題,每個主題本質上係一套 MapLibre 樣式配置。

  • 排版方面:支援多種相框樣式、留白選項、字體同文字內容,做到畫廊級效果。
  • 路線同標記點:可以畫旅行軌跡、放標記,令張海報變成「一段經歷嘅記錄」。
整理重點

高分辨率導出:多層 Canvas 合成,本地搞掂 50,000px

導出做 PNG,最高 50,000px,全部喺瀏覽器完成。佢唔係簡單 cap 圖,而係將地圖層同 UI 層分開處理:地圖層直接從引擎 canvas 讀像素,UI 層用 html2canvas 單獨渲染,最後合成。

導出流程核心代碼 javascript
// 忽略地圖元素,只捕獲 UI 覆蓋層
const overlayCanvas = await html2canvas(element, {
 useCORS: true,
 scale: scale,
 ignoreElements: (el) => el.id === 'map-preview' || el.id === 'artistic-map'
});

// MapLibre 地圖層:等待渲染完成再讀取 canvas
await new Promise(resolve => {
 const timer = setTimeout(() => {
 mapDataURL = artisticMap.getCanvas().toDataURL();
 resolve();
 }, 1500);
 artisticMap.once('idle', () => {
 clearTimeout(timer);
 mapDataURL = artisticMap.getCanvas().toDataURL();
 resolve();
 });
});

高分辨率靠 scale 參數 控制,scale 越大 canvas 越大。同時針對 iOS 限制有兜底,確保兼容。

整理重點

狀態管理:自己寫觀察者模式,唔靠 React/Vue

成個項目用 Vanilla JavaScript 整,狀態管理係自訂嘅輕量觀察者模式。用 subscribe 訂閲變化,updateState 通知所有訂閲者,同時自動寫入 localStorage。地圖、主題、導出三個模塊各自訂閲自己關心嘅狀態,互不耦合。

狀態管理核心代碼 javascript
export function subscribe(callback) {
 observers.push(callback);
 callback(state); // 立即執行一次,同步初始狀態
}

export function updateState(partialState) {
 Object.assign(state, partialState);
 saveSettings(); // 持久化到 localStorage
 notifyObservers(); // 通知所有訂閲者
}
整理重點

技術棧同緣故

技術棧包括 Vanilla JavaScriptVite 5、LeafletMapLibre GL、Tailwind CSS 3、html2canvas、Nominatim。幾個值得留意嘅選型:用 Vanilla JS 而非框架,狀態管理自行實現;導出用 html2canvas 將 DOM 轉 canvas,優點簡單,缺點受瀏覽器渲染影響。

總結來講,map-to-poster 同類工具嘅最大差異係 雙引擎架構 同 藝術主題系統,Cyber Glitch 呢類風格好少見。項目用 MIT 協議,可以商用,如果想整有特定視覺風格嘅地圖海報應用,呢個係好嘅起點。

map-to-poster 係開發者 Dimar Tarmizi 開源嘅一個地圖海報生成工具,有670 Star。核心功能係將任何地理位置轉成可以打印嘅海報,但佢嘅主題系統走嘅係偏藝術嘅方向——除咗常規嘅 Minimal White 同 Midnight Dark,仲有 Arctic Frost、Aurora Glow、Cyber Glitch 呢類有明確視覺風格嘅主題。

在線 demo:https://maptoposter.tarmizi.id

圖片

雙引擎渲染同視口同步

項目同時集成咗兩套地圖渲染引擎:Leaflet 同 MapLibre GL。

Leaflet 係基於圖片瓦片渲染,處理標準地圖同衞星圖——數據已經喺服務器端渲染好,以圖片形式分塊傳輸。MapLibre GL 係基於矢量瓦片,將原始地理數據傳到瀏覽器裏面,由 GPU 實時渲染,樣式完全由代碼控制,係藝術主題嘅技術基礎。

兩套引擎同時運行,要保持視口同步——切換主題嗰陣,地圖嘅位置同縮放級別唔可以變。項目用雙向事件監聽嚟實現呢點,並用 isSyncing 標誌防止兩個引擎互相觸發對方嘅 moveend 事件造成死循環:


map.on('moveend', () => {
if (isSyncing) return;
  isSyncing = true;
const center = map.getCenter();
const zoom = map.getZoom();
// Leaflet 座標係 [lat, lng],MapLibre 係 [lng, lat],需要轉換
  artisticMap.jumpTo({ center: [center.lng, center.lat], zoom: zoom - 1 });
  isSyncing = false;
});

artisticMap.on('moveend', () => {
if (isSyncing) return;
  isSyncing = true;
const center = artisticMap.getCenter();
const zoom = artisticMap.getZoom();
  map.setView([center.lat, center.lng], zoom + 1);
  isSyncing = false;
});

注意縮放級別嘅 ±1 差值——兩個引擎對同一區域嘅默認縮放比例唔同,透過呢個偏移量補償對齊。

樣式切換隊列

MapLibre 切換主題係異步嘅,加載新樣式需要時間。如果用戶快速連續切換主題,前一個樣式仲未加載完就嚟咗新請求,會產生衝突。項目用一個隊列機制處理呢個問題:


if (styleChangeInProgress) {
// 緩存最新嘅待切換樣式,丟棄中間狀態
  pendingArtisticStyle = style;
  pendingArtisticThemeName = theme.name;
return;
}
// 樣式加載完成後,檢查隊列裏面係咪仲有待處理嘅樣式
artisticMap.on('style.load', () => {
if (pendingArtisticStyle) {
    const next = pendingArtisticStyle;
    pendingArtisticStyle = null;
    artisticMap.setStyle(next);
  }
});

主題與自定義

內置主題分兩類:

標準主題:Minimal White(簡潔白底)、Midnight Dark(深色)、Satellite View(衞星圖),適合常規城市地圖海報。

藝術主題:Arctic Frost(冷色系冰雪感)、Aurora Glow(漸變發光)、Cyber Glitch(故障藝術風格)等,視覺風格更強烈,適合做裝飾性海報或者創意設計素材。

如果內置主題唔夠用,可以直接編輯項目入面嘅 artistic-themes.js 文件添加自定義主題,每個主題本質上係一套 MapLibre 樣式配置。

排版方面,提供咗多種相框樣式同留白(Mat)選項,可以選擇唔同字體同文字內容,組合出畫廊風格嘅海報版式。

路線與標記點

除咗靜態地圖,項目支援喺地圖上面添加可視化路線同拖拽標記點——例如標出一段旅行嘅軌跡,或者喺特定地點放置標記,導出帶有呢啲元素嘅海報。

令到海報由「某個地方嘅地圖」變成「某段經歷嘅記錄」,適合做旅行紀念、活動路線圖等個性化內容。

高解像度導出:多層 Canvas 合成

導出格式係 PNG,解像度最高 50,000px,所有處理喺瀏覽器本地完成。

導出唔係簡單咁截一張屏幕截圖,而係將地圖層同 UI 層分開處理再合成。地圖層(Leaflet 或 MapLibre)直接從引擎嘅 canvas 讀取像素,UI 層(相框、標題、座標文字)用 html2canvas 單獨渲染。兩層合成到最終 canvas 上輸出:


// 忽略地圖元素,淨係捕獲 UI 覆蓋層
const overlayCanvas = await html2canvas(element, {
  useCORS: true,
  scale: scale,
  ignoreElements: (el) => el.id === 'map-preview' || el.id === 'artistic-map'
});

// MapLibre 地圖層:等待渲染完成再讀取 canvas
awaitnew Promise(resolve => {
const timer = setTimeout(() => {
    mapDataURL = artisticMap.getCanvas().toDataURL();
    resolve();
  }, 1500);
// 優先監聽 idle 事件,地圖空閒嗰陣即時讀取
  artisticMap.once('idle', () => {
    clearTimeout(timer);
    mapDataURL = artisticMap.getCanvas().toDataURL();
    resolve();
  });
});

高解像度透過 scale 參數控制——scale 越大,canvas 尺寸越大,圖片越清晰。同時針對 iOS 嘅 canvas 像素上限(16,777,216px)做咗兜底處理,超出限制時等比縮小:


if (canvasWidth * canvasHeight > IOS_MAX_CANVAS_PIXELS) {
  const ratio = Math.sqrt(IOS_MAX_CANVAS_PIXELS / (canvasWidth * canvasHeight));
  canvasWidth = Math.floor(canvasWidth * ratio);
  canvasHeight = Math.floor(canvasHeight * ratio);
}

狀態管理:觀察者模式

項目冇用 Redux 或 Pinia,而係自己實現咗一個輕量嘅觀察者模式。subscribe 訂閲狀態變化,updateState 觸發所有訂閲者更新,同時將需要持久化嘅字段寫入 localStorage:


export function subscribe(callback) {
  observers.push(callback);
  callback(state); // 即時執行一次,同步初始狀態
}

export function updateState(partialState) {
  Object.assign(state, partialState);
  saveSettings();       // 持久化到 localStorage
  notifyObservers();    // 通知所有訂閲者
}

呢套機制令地圖、主題、導出三個模塊各自訂閲自己關心嘅狀態變化,互不耦合。

技術棧

技術
版本
用途
Vanilla JavaScript
-
核心邏輯,無框架依賴
Vite
5
構建工具
Leaflet
-
光柵瓦片地圖渲染(標準/衞星圖)
MapLibre GL
-
矢量瓦片地圖渲染(藝術主題)
Tailwind CSS
3
樣式
html2canvas
-
將頁面渲染結果導出為 PNG
Nominatim
-
OpenStreetMap 地理編碼,支援地名搜索

幾個值得注意嘅選型:

Vanilla JS 而非框架:成個項目冇用 React 或 Vue,狀態管理用觀察者模式自行實現,設置透過 localStorage 持久化。對於想學唔依賴框架構建複雜 Web 應用嘅開發者,呢份代碼值得參考。

html2canvas 導出:導出 PNG 嘅方式係將當前 DOM 渲染結果轉成 canvas,再導出圖片。呢種方案嘅優點係實現簡單,缺點係導出結果受瀏覽器渲染影響,同 PDF 矢量導出嘅思路唔同。50,000px 嘅超高解像度透過縮放 canvas 尺寸實現。

雙引擎同步:Leaflet 同 MapLibre GL 分別維護自己嘅渲染上下文,項目喺兩者之間同步視口狀態(中心座標、縮放級別),確保切換引擎時畫面位置唔會跳變。

安裝與運行

需要預先安裝 Node.js 18 或以上版本。


git clone https://github.com/dimartarmizi/map-to-poster.git
cd map-to-poster
npm install
npm run dev

生產構建:


npm run build

寫喺最後

map-to-poster 同同類工具嘅主要差異在於兩點:第一係雙引擎架構令衞星圖同矢量藝術主題喺同一套界面入面共存;第二係主題系統覆蓋範圍更廣,Cyber Glitch 呢類風格喺同類工具裏面比較少見。

項目係 MIT 協議,允許商業使用同二次開發。如果你想整一個有特定視覺風格嘅地圖海報應用,佢嘅雙引擎架構同主題系統係值得參考嘅起點。

GitHub 地址:https://github.com/dimartarmizi/map-to-poster

關注公眾號,加好友領取1000+Three.js+Cesium項目案例合集

圖片

掃碼直達 ⤵️ 或者加微信 sanmu1598

image.jpg




map-to-poster 是開發者 Dimar Tarmizi 開源的一個地圖海報生成工具,670 Star。核心功能是把任意地理位置轉成可打印的海報,但它的主題系統走的是偏藝術的方向——除了常規的 Minimal White 和 Midnight Dark,還有 Arctic Frost、Aurora Glow、Cyber Glitch 這類有明確視覺風格的主題。

在線 demo:https://maptoposter.tarmizi.id

圖片

雙引擎渲染與視口同步

項目同時集成了兩套地圖渲染引擎:Leaflet 和 MapLibre GL。

Leaflet 基於圖片瓦片渲染,處理標準地圖和衞星圖——數據已經在服務端渲染好,以圖片形式分塊傳輸。MapLibre GL 基於矢量瓦片,把原始地理數據傳到瀏覽器裏,由 GPU 實時渲染,樣式完全由代碼控制,是藝術主題的技術基礎。

兩套引擎同時運行,需要保持視口同步——切換主題時,地圖的位置和縮放級別不能變。項目用雙向事件監聽實現這一點,並用 isSyncing 標誌防止兩個引擎互相觸發對方的 moveend 事件造成死循環:


map.on('moveend', () => {
if (isSyncing) return;
  isSyncing = true;
const center = map.getCenter();
const zoom = map.getZoom();
// Leaflet 座標是 [lat, lng],MapLibre 是 [lng, lat],需要轉換
  artisticMap.jumpTo({ center: [center.lng, center.lat], zoom: zoom - 1 });
  isSyncing = false;
});

artisticMap.on('moveend', () => {
if (isSyncing) return;
  isSyncing = true;
const center = artisticMap.getCenter();
const zoom = artisticMap.getZoom();
  map.setView([center.lat, center.lng], zoom + 1);
  isSyncing = false;
});

注意縮放級別的 ±1 差值——兩個引擎對同一區域的默認縮放比例不同,通過這個偏移量補償對齊。

樣式切換隊列

MapLibre 切換主題是異步的,加載新樣式需要時間。如果用戶快速連續切換主題,前一個樣式還沒加載完就來了新請求,會產生衝突。項目用一個隊列機制處理這個問題:


if (styleChangeInProgress) {
// 緩存最新的待切換樣式,丟棄中間狀態
  pendingArtisticStyle = style;
  pendingArtisticThemeName = theme.name;
return;
}
// 樣式加載完成後,檢查隊列裏是否還有待處理的樣式
artisticMap.on('style.load', () => {
if (pendingArtisticStyle) {
    const next = pendingArtisticStyle;
    pendingArtisticStyle = null;
    artisticMap.setStyle(next);
  }
});

主題與自定義

內置主題分兩類:

標準主題:Minimal White(簡潔白底)、Midnight Dark(深色)、Satellite View(衞星圖),適合常規城市地圖海報。

藝術主題:Arctic Frost(冷色系冰雪感)、Aurora Glow(漸變發光)、Cyber Glitch(故障藝術風格)等,視覺風格更強烈,適合做裝飾性海報或創意設計素材。

如果內置主題不夠用,可以直接編輯項目裏的 artistic-themes.js 文件添加自定義主題,每個主題本質上是一套 MapLibre 樣式配置。

排版方面,提供了多種相框樣式和留白(Mat)選項,可以選擇不同字體和文字內容,組合出畫廊風格的海報版式。

路線與標記點

除了靜態地圖,項目支持在地圖上添加可視化路線和拖拽標記點——比如標出一段旅行的軌跡,或者在特定地點放置標記,導出帶有這些元素的海報。

這讓海報從"某個地方的地圖"變成"某段經歷的記錄",適合做旅行紀念、活動路線圖等個性化內容。

高分辨率導出:多層 Canvas 合成

導出格式是 PNG,分辨率最高 50,000px,所有處理在瀏覽器本地完成。

導出不是簡單地截一張屏幕圖,而是把地圖層和 UI 層分開處理再合成。地圖層(Leaflet 或 MapLibre)直接從引擎的 canvas 讀取像素,UI 層(相框、標題、座標文字)用 html2canvas 單獨渲染。兩層合成到最終 canvas 上輸出:


// 忽略地圖元素,只捕獲 UI 覆蓋層
const overlayCanvas = await html2canvas(element, {
  useCORS: true,
  scale: scale,
  ignoreElements: (el) => el.id === 'map-preview' || el.id === 'artistic-map'
});

// MapLibre 地圖層:等待渲染完成再讀取 canvas
awaitnew Promise(resolve => {
const timer = setTimeout(() => {
    mapDataURL = artisticMap.getCanvas().toDataURL();
    resolve();
  }, 1500);
// 優先監聽 idle 事件,地圖空閒時立即讀取
  artisticMap.once('idle', () => {
    clearTimeout(timer);
    mapDataURL = artisticMap.getCanvas().toDataURL();
    resolve();
  });
});

高分辨率通過 scale 參數控制——scale 越大,canvas 尺寸越大,圖片越清晰。同時針對 iOS 的 canvas 像素上限(16,777,216px)做了兜底處理,超出限制時等比縮小:


if (canvasWidth * canvasHeight > IOS_MAX_CANVAS_PIXELS) {
  const ratio = Math.sqrt(IOS_MAX_CANVAS_PIXELS / (canvasWidth * canvasHeight));
  canvasWidth = Math.floor(canvasWidth * ratio);
  canvasHeight = Math.floor(canvasHeight * ratio);
}

狀態管理:觀察者模式

項目沒有用 Redux 或 Pinia,而是自己實現了一個輕量的觀察者模式。subscribe 訂閲狀態變化,updateState 觸發所有訂閲者更新,同時把需要持久化的字段寫入 localStorage:


export function subscribe(callback) {
  observers.push(callback);
  callback(state); // 立即執行一次,同步初始狀態
}

export function updateState(partialState) {
  Object.assign(state, partialState);
  saveSettings();       // 持久化到 localStorage
  notifyObservers();    // 通知所有訂閲者
}

這套機制讓地圖、主題、導出三個模塊各自訂閲自己關心的狀態變化,互不耦合。

技術棧

技術
版本
用途
Vanilla JavaScript
-
核心邏輯,無框架依賴
Vite
5
構建工具
Leaflet
-
光柵瓦片地圖渲染(標準/衞星圖)
MapLibre GL
-
矢量瓦片地圖渲染(藝術主題)
Tailwind CSS
3
樣式
html2canvas
-
將頁面渲染結果導出為 PNG
Nominatim
-
OpenStreetMap 地理編碼,支持地名搜索

幾個值得注意的選型:

Vanilla JS 而非框架:整個項目沒有使用 React 或 Vue,狀態管理用觀察者模式自行實現,設置通過 localStorage 持久化。對於想學習不依賴框架構建複雜 Web 應用的開發者,這份代碼值得參考。

html2canvas 導出:導出 PNG 的方式是把當前 DOM 渲染結果轉成 canvas,再導出圖片。這種方案的優點是實現簡單,缺點是導出結果受瀏覽器渲染影響,與 PDF 矢量導出的思路不同。50,000px 的超高分辨率通過縮放 canvas 尺寸實現。

雙引擎同步:Leaflet 和 MapLibre GL 分別維護自己的渲染上下文,項目在兩者之間同步視口狀態(中心座標、縮放級別),確保切換引擎時畫面位置不跳變。

安裝與運行

需要提前安裝 Node.js 18 及以上版本。


git clone https://github.com/dimartarmizi/map-to-poster.git
cd map-to-poster
npm install
npm run dev

生產構建:


npm run build

寫在最後

map-to-poster 和同類工具的主要差異在於兩點:一是雙引擎架構讓衞星圖和矢量藝術主題在同一套界面裏共存;二是主題系統覆蓋範圍更廣,Cyber Glitch 這類風格在同類工具裏比較少見。

項目是 MIT 協議,允許商業使用和二次開發。如果你想做一個有特定視覺風格的地圖海報應用,它的雙引擎架構和主題系統是值得參考的起點。

GitHub 地址:https://github.com/dimartarmizi/map-to-poster

關注公眾號,添加好友領1000+Three.js+Cesium項目案例合集

圖片

掃碼直達 ⤵️ 或加微信 sanmu1598

image.jpg