您喜歡視聽學習嗎?觀看視頻指南!
或者直接跳到代碼
Overture Maps Foundation是由亞馬遜、Meta、微軟和 tomtom 發起的聯合開發基金會項目,旨在創建可靠、易于使用、可互操作的開放地圖數據。
Overture Maps 允許我們以GeoJSON格式下載開放地圖數據(例如名勝古跡),我們可以將其轉換為 SQL 并導入 Supabase 上的 Postgres 數據庫。
使用 PostGIS,我們可以以編程方式生成矢量圖塊,并使用 supabase-js 將它們提供給我們的 MapLibre GL 客戶端。
矢量圖塊是地理數據包,被打包成預定義的大致正方形的“圖塊”,以便在網絡上傳輸。客戶端請求的地圖數據是一組“圖塊”,對應于預定義大小和位置的方形土地區域。
特別是對于大型數據集,這樣做的好處是數據傳輸大大減少,因為只需要傳輸當前視口內和當前縮放級別的數據。
在本教程中,您將學習
- 使用 Overture Maps 以 GeoJSON 格式下載開放地圖地點數據。
- 使用 GDAL ogr2ogr 將 GeoJSON 轉換為 SQL 語句。
- 使用 psql 將位置數據和 JSON 元數據導入您的 Supabase Postgres 數據庫。
- 使用 PostGIS
ST_AsMVT
將與圖塊層對應的一組行聚合為二進制矢量圖塊表示。 addProtocol
通過使用 supabase-js 進行遠程過程調用,使用 MapLibre可視化大型 PostGIS 表。- 使用 supabase-js 按需獲取其他 JSON 元數據
使用 Overture Maps 下載開放地圖數據
Overture Maps 提供了一個Python 命令行工具來下載感興趣區域內的數據并將其轉換為幾種常見的地理空間文件格式。
我們可以使用以下命令將新加坡的地點下載到 GeoJSON 文件中:
overturemaps download --bbox=103.570233,1.125077,104.115855,1.490957 -f geojson --type=place -o places.geojson
根據邊界框的大小,這可能需要相當長的時間!
將 GeoJSON 轉換為SQL
下一步,我們可以使用GDAL ogr2ogr將 GeoJSON 文件轉換為 PostGIS 兼容的 SQL 文件。
您可以GDAL
通過安裝homebrew brew install gdal
或按照下載說明進行操作。
PG_USE_COPY=true ogr2ogr -f pgdump places.sql places.geojson
將位置數據導入 Supabase
在專用的單獨架構上啟用 Supabase 數據庫上的 PostGIS 擴展gis
。為此,您可以導航到SQL 編輯器并運行以下 SQL,或者您可以從數據庫擴展設置中啟用擴展。(也可使用國內版supabase)
由于 PostGIS 的計算量可能很大,我們建議在專用的單獨模式上啟用它,例如名為gis
!
CREATE SCHEMA IF NOT EXISTS "gis";
CREATE EXTENSION IF NOT EXISTS "postgis" WITH SCHEMA "gis";
將打開的地圖數據導入到places
Supabase中的表中:
psql -h aws-0-us-west-1.pooler.supabase.com -p 5432 -d postgres -U postgres.project-ref < places.sql
您可以在Supabase 儀表板的數據庫設置中找到憑據。
啟用 RLS 并創建公共讀取策略
我們希望地點數據可以公開獲取,因此我們可以創建一個允許公開讀取的行級安全策略。
在您的 Supabase 儀表板中,導航到SQL 編輯器并運行以下命令:
ALTER TABLE "public"."places" ENABLE ROW LEVEL SECURITY;CREATE POLICY "Enable read access for all users" ON "public"."places" FOR SELECT USING (true);
使用PostGIS生成矢量圖塊
為了在客戶端請求時以編程方式生成矢量圖塊,我們需要創建一個 Postgres 函數,可以通過遠程過程調用來調用它。在 SQL 編輯器中,運行:
CREATE OR REPLACE FUNCTION mvt(z integer, x integer, y integer)
RETURNS text
LANGUAGE plpgsql
AS $$
DECLAREmvt_output text;
BEGINWITH-- Define the bounds of the tile using the provided Z, X, Y coordinatesbounds AS (SELECT ST_TileEnvelope(z, x, y) AS geom),-- Transform the geometries from EPSG:4326 to EPSG:3857 and clip them to the tile boundsmvtgeom AS (SELECT-- include the name and id only at zoom 13 to make low-zoom tiles smallerCASEWHEN z > 13 THEN idELSE NULLEND AS id,CASEWHEN z > 13 THEN names::json->>'primary'ELSE NULLEND AS primary_name,categories::json->>'main' as main_category,ST_AsMVTGeom(ST_Transform(wkb_geometry, 3857), -- Transform the geometry to Web Mercatorbounds.geom,4096, -- The extent of the tile in pixels (commonly 256 or 4096)0, -- Buffer around the tile in pixelstrue -- Clip geometries to the tile extent) AS geomFROMplaces, boundsWHEREST_Intersects(ST_Transform(wkb_geometry, 3857), bounds.geom))-- Generate the MVT from the clipped geometriesSELECT INTO mvt_output encode(ST_AsMVT(mvtgeom, 'places', 4096, 'geom'),'base64')FROM mvtgeom;RETURN mvt_output;
END;
$$;
為了限制通過網絡發送的數據量,我們限制了矢量圖塊中包含的元數據量。例如,我們為縮放級別添加了一個條件,并且只有當用戶放大到 13 級以上時才返回地名。
使用 supabase-js 從 MapLibre GL 客戶端獲取矢量瓦片
index.html
您可以在GitHub上找到完整的代碼。在這里,我們將重點介紹如何向 MapLibreGL 添加新協議,以通過 supabase-js 獲取 bas64 編碼的二進制矢量瓦片數據,以便 MapLibre GL 可以在用戶與地圖交互時獲取和呈現數據:
index.html
const client = supabase.createClient('your-supabase-api-url', 'your-supabase-anon-key')function base64ToArrayBuffer(base64) {var binaryString = atob(base64)var bytes = new Uint8Array(binaryString.length)for (var i = 0; i < binaryString.length; i++) {bytes[i] = binaryString.charCodeAt(i)}return bytes
}maplibregl.addProtocol('supabase', async (params, abortController) => {const re = new RegExp(/supabase:\/\/(.+)\/(\d+)\/(\d+)\/(\d+)/)const result = params.url.match(re)const { data, error } = await client.rpc('mvt', {z: result[2],x: result[3],y: result[4],})const encoded = base64ToArrayBuffer(data)if (!error) {return { data: encoded }} else {throw new Error(`Tile fetch error:`)}
})
注冊 supabase 協議后,我們現在可以將其添加到 MapLibre GL 源中,并放置在底圖(如Protomaps)之上,例如:
index.html
// ...
const map = new maplibregl.Map({hash: true,container: 'map',style: {version: 8,glyphs: 'https://cdn.protomaps.com/fonts/pbf/{fontstack}/{range}.pbf',sources: {supabase: {type: 'vector',tiles: ['supabase://boston/{z}/{x}/{y}'],attribution: '? <a href="https://overturemaps.org">Overture Maps Foundation</a>',},protomaps: {type: 'vector',url: 'https://api.protomaps.com/tiles/v3.json?key=your-protomaps-api-key',attribution: 'Basemap ? <a href="https://openstreetmap.org">OpenStreetMap</a>',},},},
})
// ...
按需獲取額外的 JSON 元數據
為了限制通過網絡發送的數據量,我們不會對矢量圖塊本身中的所有元數據進行編碼,而是設置一個 onclick 處理程序以在 MapLibre GL 彈出窗口中按需獲取其他元數據:
index.html
// ..
const popup = new maplibregl.Popup({closeButton: true,closeOnClick: false,maxWidth: 'none',
})function loadDetails(element, id) {element.innerHTML = 'loading...'client.from('places').select(`websites,socials,phones,addresses,source: sources->0->dataset`).eq('id', id).single().then(({ data, error }) => {if (error) return console.error(error)element.parentElement.innerHTML = `<pre>${JSON.stringify(data, null, 2)}</pre>`})
}map.on('click', 'overture-pois-text', async (e) => {if (e.features.length > 0) {const feature = e.features[0]console.log(feature)popup.setHTML(`<table style="font-size:12px"><tr><td>id:</td><td>${feature.properties.id}</td></tr><tr><td>name:</td><td>${feature.properties.primary_name}</td></tr><tr><td>main_category:</td><td>${feature.properties.main_category}</td></tr><tr><td>details:</td><td><span οnclick="loadDetails(this, '${feature.properties.id}')">load details</span></td></tr></table>`)popup.setLngLat(e.lngLat)popup.addTo(map)}
})
// ...
結論
PostGIS 功能強大,可讓您以編程方式從存儲在 Postgres 中的表行生成矢量圖塊。與 Supabase 自動生成的 REST API 和 supabase-js 客戶端庫配合使用,您可以輕松構建交互式地理空間應用程序!
更多 Supabase
- 觀看視頻指南
- 國內版suapbase
- 查找代碼
- 使用 Protomaps 在 Supabase 存儲上自托管地圖
- PostGIS 入門
- PostGIS 文檔指南
原文章:https://supabase.com/blog/postgis-generate-vector-tiles