使用 Protomaps 和 Supabase 存储自托管地图
Protomaps是一个开源的世界地图,可作为单个静态文件部署在Supabase Storage上。
在本教程中,您将学习
- 使用 Protomaps 将某个区域提取到静态 PMTiles 文件中。
- 将 PMTiles 文件上传到 Supabase 存储。
- 使用 MapLibre 将地图呈现到网页上。
- 使用 Supabase Edge Functions 来限制文件访问。
将某个区域提取到静态 PMTiles 文件中#
Protomaps 提供了一个pmtiles
CLI,可用于从世界地图中剪切出某些区域并将其压缩为单个静态文件。
例如,我们可以像这样提取荷兰乌得勒支周围的一小块区域:
pmtiles extract https://build.protomaps.com/20240618.pmtiles my_area.pmtiles --bbox=5.068050,52.112086,5.158424,52.064140
注意:请确保将日期更新为最新的每日构建!
这将创建一个my_area.pmtiles
您可以上传到 Supabase Storage 的文件。
将 PMTiles 文件上传到 Supabase 存储#
在您的Supabase 仪表板中导航到Storage
并单击“新存储桶”并创建一个名为 的新公共存储桶public-maps
。
将之前创建的文件上传my_area.pmtiles
到您的公共存储桶。上传后,单击文件并点击“获取 URL”。
Supabase Storage 开箱即用地支持所需的HTTP 范围请求,允许您直接从地图客户端使用公共存储 URL。
使用 MapLibre 渲染地图
PMTiles 可轻松与MapLibre GL和Leaflet配合使用。在我们的示例中,我们将使用MapLibre GL,这是一个 TypeScript 库,它使用 WebGL 在浏览器中从矢量图块渲染交互式地图。
这是一个使用库的 CDN 版本的原生 JS 示例。您可以非常轻松地将其调整为与 React 配合使用,例如使用react-map-gl库。
index.html
<html>
<head>
<title>Overture Places</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link
rel="stylesheet"
href="https://unpkg.com/maplibre-gl@4.1.2/dist/maplibre-gl.css"
crossorigin="anonymous"
/>
<script
src="https://unpkg.com/maplibre-gl@4.1.2/dist/maplibre-gl.js"
crossorigin="anonymous"
></script>
<script src="https://unpkg.com/protomaps-themes-base@2.0.0-alpha.5/dist/index.js"></script>
<script src="https://unpkg.com/pmtiles@3.0.6/dist/pmtiles.js"></script>
<style>
body {
margin: 0;
}
#map {
height: 100%;
width: 100%;
}
</style>
</head>
<body>
<div id="map"></div>
<script type="text/javascript">
// Add the PMTiles Protocol:
let protocol = new pmtiles.Protocol()
maplibregl.addProtocol('pmtiles', protocol.tile)
// Load the Map tiles directly from Supabase Storage:
const map = new maplibregl.Map({
hash: true,
container: 'map',
style: {
version: 8,
glyphs: 'https://cdn.protomaps.com/fonts/pbf/{fontstack}/{range}.pbf',
sources: {
protomaps: {
attribution:
'<a href="https://github.com/protomaps/basemaps">Protomaps</a> © <a href="https://openstreetmap.org">OpenStreetMap</a>',
type: 'vector',
url: 'pmtiles://https://<your-project-ref>.supabase.co/storage/v1/object/public/public-maps/my_area.pmtiles',
},
},
layers: protomaps_themes_base.default('protomaps', 'dark'),
},
})
</script>
</body>
</html>
使用 Supabase Edge Functions 限制访问#
公共 Supabase Storage 存储桶允许从任何来源进行访问,这可能并不适合您的使用情况。在撰写本文时,您无法修改 Supabase Storage 存储桶的 CORS 设置,但是您可以使用Supabase Edge Functions来限制对 PMTiles 文件的访问,例如,您甚至可以将其与Supabase Auth配对以限制对某些用户的访问。
在 Supabase 仪表板中,创建一个名为 的新私有存储桶,maps-private
并将my_area.pmtiles
文件上传到其中。私有存储桶中的文件只能通过短期签名 URL 或通过将秘密服务角色密钥作为授权标头传递来访问。由于我们的 Edge Function 是一个安全的服务器端环境,因此我们可以在此处使用后一种方法。
使用Supabase CLI,通过运行创建一个新的 Edge Function supabase functions new maps-private
,然后将以下代码添加到新创建的函数中:
supabase/functions/maps-private/ index.ts
const ALLOWED_ORIGINS = ['http://localhost:8000']
const corsHeaders = {
'Access-Control-Allow-Origin': ALLOWED_ORIGINS.join(','),
'Access-Control-Allow-Headers':
'authorization, x-client-info, apikey, content-type, range, if-match',
'Access-Control-Expose-Headers': 'range, accept-ranges, etag',
'Access-Control-Max-Age': '300',
}
Deno.serve((req) => {
// This is needed if you're planning to invoke your function from a browser.
if (req.method === 'OPTIONS') {
return new Response('ok', { headers: corsHeaders })
}
// Check origin
const origin = req.headers.get('Origin')
if (!origin || !ALLOWED_ORIGINS.includes(origin)) {
return new Response('Not Allowed', { status: 405 })
}
const reqUrl = new URL(req.url)
const url = `${Deno.env.get('SUPABASE_URL')}/storage/v1/object/authenticated${reqUrl.pathname}`
const { method, headers } = req
// Add Auth header
const modHeaders = new Headers(headers)
modHeaders.append('authorization', `Bearer ${Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!}`)
return fetch(url, { method, headers: modHeaders })
})
如果您想根据经过身份验证的用户进一步限制访问,您可以将 Edge Function 与 Supabase Auth 配对,如本例所示。
最后,我们需要通过运行将我们的 Edge Function 部署到 Supabase 。请注意,如果您想允许从您的网站进行公共访问而无需任何 Supabase Auth 用户,则需要supabase functions deploy maps-private --no-verify-jwt
该--no-verify-jwt
标志。
现在,我们可以简单地用我们的 Edge Functions URL 替换公共存储 URL,以将范围请求代理到我们的私有存储桶:
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: {
protomaps: {
attribution:
'<a href="https://github.com/protomaps/basemaps">Protomaps</a> © <a href="https://openstreetmap.org">OpenStreetMap</a>',
type: 'vector',
url: 'pmtiles://https://<project_ref>.supabase.co/functions/v1/maps-private/my_area.pmtiles',
},
},
layers: protomaps_themes_base.default('protomaps', 'dark'),
},
})
// ...
现在继续提供你的index.html
文件,例如通过 Python SimpleHTTPServer:并在localhost:8000python3 -m http.server
上欣赏你美丽的地图!
结论#
Protomaps 是一个很棒的开源项目,它允许您在 Supabase Storage 上托管您自己的 Google Maps 替代方案。您可以使用强大的 PostGIS 功能进一步扩展它,以编程方式生成矢量图块,我们将在本系列的下一篇文章中探讨这一点。所以请务必订阅我们的Twitter和YouTube频道,不要错过!到时候见!
更多 Supabase #
转载自:https://juejin.cn/post/7383658529791787048