Leaflet + MapLibre 雙引擎,這個開源工具把任意城市變成裝飾級地圖海報
整理版優先睇
map-to-poster:雙引擎地圖海報生成工具,將城市變成藝術海報
呢篇文章介紹一個叫 map-to-poster 嘅開源工具,由開發者 Dimar Tarmizi 整出嚟,核心係將任意地理位置轉成可打印嘅裝飾海報。佢同普通地圖工具最大分別係主題系統偏向藝術風格,好似 Arctic Frost、Aurora Glow、Cyber Glitch 呢類,仲有雙引擎 Leaflet 同 MapLibre GL 同時行,畀用戶喺標準地圖同藝術風格之間切換。
作者想解決嘅問題係:點樣令地圖海報唔只係資訊展示,仲可以係有視覺個性嘅裝飾品。整體結論係呢個工具透過雙引擎架構同豐富主題,做到「任何地點都變到有設計感」,而且支援高達 50,000px 嘅 PNG 導出,全部喺瀏覽器本地搞掂。
呢個項目係 MIT 協議,可以商用同二次開發,對於想做地圖海報應用嘅開發者來講,佢嘅雙引擎同步機制、主題系統同狀態管理寫法都值得參考。
- 雙引擎 Leaflet(光柵)同 MapLibre GL(矢量)同時運行,用 isSyncing 標誌避免視口同步死循環,縮放級別有 ±1 偏移補償。
- 藝術主題系統包括 Arctic Frost、Aurora Glow、Cyber Glitch 等,亦可透過 artistic-themes.js 自定義 MapLibre 樣式配置。
- 支援路線繪製同標記點拖拽,將靜態地圖變成「某段經歷嘅記錄」,適合旅行紀念或活動路線圖。
- 高分辨率導出:多層 Canvas 合成(地圖層 + UI 層),最高 50,000px,並為 iOS 嘅 canvas 上限做等比縮小兜底。
- 狀態管理採用輕量觀察者模式,無框架依賴,透過 localStorage 持久化設定,適合學習唔用 React/Vue 點樣構建複雜 Web 應用。
GitHub 倉庫
map-to-poster 源碼,MIT 協議,可自行下載安裝
在線 Demo
即時體驗地圖海報生成功能
雙引擎同步:Leaflet 同 MapLibre GL 點樣夾到實
呢個工具同時用咗兩套地圖引擎——Leaflet 負責光柵瓦片,標準地圖同衞星圖靠佢;MapLibre GL 行矢量瓦片,GPU 實時渲染,係藝術主題嘅技術基礎。兩者切換時要確保位置同縮放唔會跳,所以用 雙向事件監聽 加 isSyncing 標誌 防止死循環。
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 White、Midnight Dark、Satellite View,適合常規海報;藝術主題 就玩到 Arctic Frost、Aurora Glow、Cyber Glitch 呢類強烈風格。如果嫌唔夠,可以直接改 artistic-themes.js 檔案加自訂主題,每個主題本質上係一套 MapLibre 樣式配置。
- 排版方面:支援多種相框樣式、留白選項、字體同文字內容,做到畫廊級效果。
- 路線同標記點:可以畫旅行軌跡、放標記,令張海報變成「一段經歷嘅記錄」。
高分辨率導出:多層 Canvas 合成,本地搞掂 50,000px
導出做 PNG,最高 50,000px,全部喺瀏覽器完成。佢唔係簡單 cap 圖,而係將地圖層同 UI 層分開處理:地圖層直接從引擎 canvas 讀像素,UI 層用 html2canvas 單獨渲染,最後合成。
// 忽略地圖元素,只捕獲 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。地圖、主題、導出三個模塊各自訂閲自己關心嘅狀態,互不耦合。
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、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 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

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

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 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

掃碼直達 ⤵️ 或加微信 sanmu1598
