您喜欢视听学习吗?观看视频指南!
或者直接跳到代码
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