diff --git a/components/constants.js b/components/constants.js
index 50d79ec..3930cb4 100644
--- a/components/constants.js
+++ b/components/constants.js
@@ -25,6 +25,15 @@ export const LABELS = {
},
}
+export const BOUNDARY_PMTILES_URL =
+ 'https://carbonplan-offsets-db.s3.us-west-2.amazonaws.com/miscellaneous/project-boundaries.pmtiles'
+
+export const BOUNDARY_LAYER_ID = 'boundaries'
+export const CENTROIDS_LAYER_ID = 'centroids'
+
+export const BASEMAP_PMTILES_URL =
+ 'https://carbonplan-maps.s3.us-west-2.amazonaws.com/basemaps/pmtiles/global.pmtiles'
+
export const COLORS = {
agriculture: 'orange',
forest: 'green',
diff --git a/components/map.js b/components/map.js
new file mode 100644
index 0000000..4bdb719
--- /dev/null
+++ b/components/map.js
@@ -0,0 +1,447 @@
+import React, { useEffect, useMemo, useRef, useState } from 'react'
+import { createPortal } from 'react-dom'
+import { Box, get, useThemeUI } from 'theme-ui'
+import { useRouter } from 'next/router'
+import maplibregl from 'maplibre-gl'
+import 'maplibre-gl/dist/maplibre-gl.css'
+import { Protocol } from 'pmtiles'
+import { Arrow } from '@carbonplan/icons'
+import {
+ BOUNDARY_PMTILES_URL,
+ BASEMAP_PMTILES_URL,
+ BOUNDARY_LAYER_ID,
+ CENTROIDS_LAYER_ID,
+ COLORS,
+} from './constants'
+import { useMapTheme } from './use-map-theme'
+import { getProjectCategory } from './utils'
+
+const Map = ({ project }) => {
+ const mapContainer = useRef(null)
+ const map = useRef(null)
+ const hoveredFeatureId = useRef(null)
+ const hoveredFeature = useRef(null)
+ const markerRef = useRef(null)
+ const [markerEl, setMarkerEl] = useState(null)
+ const [markerVisible, setMarkerVisible] = useState(false)
+ const mapLayers = useMapTheme()
+ const { theme } = useThemeUI()
+ const router = useRouter()
+
+ const colorName = COLORS[getProjectCategory(project)] ?? COLORS.other
+ const color = get(theme, `rawColors.${colorName}`, colorName)
+ const secondary = get(theme, 'rawColors.secondary')
+ const background = get(theme, 'rawColors.background')
+ const hinted = get(theme, 'rawColors.hinted')
+ const primary = get(theme, 'rawColors.primary')
+
+ const bounds = useMemo(
+ () => [
+ [project.bbox.xmin, project.bbox.ymin],
+ [project.bbox.xmax, project.bbox.ymax],
+ ],
+ [project.bbox]
+ )
+
+ const projectLayers = useMemo(() => {
+ const isProject = ['==', ['get', 'project_id'], project.project_id]
+ return [
+ {
+ id: 'project-boundaries-fill',
+ type: 'fill',
+ source: 'project-boundaries',
+ 'source-layer': BOUNDARY_LAYER_ID,
+ paint: {
+ 'fill-color': ['case', isProject, color, secondary],
+ 'fill-opacity': 0.2,
+ },
+ },
+ {
+ id: 'project-boundaries-outline',
+ type: 'line',
+ source: 'project-boundaries',
+ 'source-layer': BOUNDARY_LAYER_ID,
+ paint: {
+ 'line-color': ['case', isProject, color, secondary],
+ 'line-width': [
+ 'case',
+ isProject,
+ 1,
+ ['case', ['==', ['feature-state', 'hover'], true], 1, 0.5],
+ ],
+ },
+ },
+ {
+ id: 'project-centroids-label',
+ type: 'symbol',
+ source: 'project-boundaries',
+ 'source-layer': CENTROIDS_LAYER_ID,
+ layout: {
+ 'text-field': ['get', 'project_id'],
+ 'text-size': 16,
+ 'text-font': ['Relative Mono Pro 11 Pitch'],
+ 'text-letter-spacing': 0.02,
+ 'text-anchor': 'center',
+ 'symbol-sort-key': ['case', isProject, 0, 1],
+ },
+ paint: {
+ 'text-color': [
+ 'case',
+ isProject,
+ color,
+ [
+ 'case',
+ ['==', ['feature-state', 'hover'], true],
+ primary,
+ secondary,
+ ],
+ ],
+ 'text-halo-color': [
+ 'case',
+ isProject,
+ background,
+ [
+ 'case',
+ ['==', ['feature-state', 'hover'], true],
+ hinted,
+ background,
+ ],
+ ],
+ 'text-halo-width': 2,
+ },
+ },
+ ]
+ }, [project.project_id, color, secondary, primary, background, hinted])
+
+ const mapControlStyles = useMemo(
+ () => ({
+ '& .maplibregl-control-container': {
+ fontSize: [0, 0, 0, 0],
+ fontFamily: 'faux',
+ letterSpacing: 'faux',
+ '& [class*="maplibregl-ctrl-"]': { zIndex: 0 },
+ '& .maplibregl-ctrl-attrib': {
+ bg: 'hinted',
+ alignItems: 'center',
+ border: `1px solid`,
+ borderColor: 'muted',
+ color: 'secondary',
+ display: 'flex',
+ '& a': { color: 'secondary' },
+ '& .maplibregl-ctrl-attrib-button': {
+ bg: 'hinted',
+ backgroundImage: `url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill-rule='evenodd' viewBox='0 0 20 20'%3E%3Cpath fill='${encodeURIComponent(
+ primary
+ )}' d='M4 10a6 6 0 1 0 12 0 6 6 0 1 0-12 0m5-3a1 1 0 1 0 2 0 1 1 0 1 0-2 0m0 3a1 1 0 1 1 2 0v3a1 1 0 1 1-2 0'/%3E%3C/svg%3E")`,
+ '&:hover, &:focus-visible': {
+ backgroundImage: `url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill-rule='evenodd' viewBox='0 0 20 20'%3E%3Cpath fill='${encodeURIComponent(
+ secondary
+ )}' d='M4 10a6 6 0 1 0 12 0 6 6 0 1 0-12 0m5-3a1 1 0 1 0 2 0 1 1 0 1 0-2 0m0 3a1 1 0 1 1 2 0v3a1 1 0 1 1-2 0'/%3E%3C/svg%3E")`,
+ },
+ },
+ },
+ '& .maplibregl-ctrl-group': {
+ marginBottom: 0,
+ bg: 'hinted',
+ border: `1px solid`,
+ borderColor: 'muted',
+ borderRadius: '20px',
+ boxShadow: 'none',
+ overflow: 'hidden',
+ '& button': {
+ bg: 'hinted',
+ border: 'none',
+ borderBottom: `1px solid`,
+ borderColor: 'muted',
+ width: '24px',
+ height: '24px',
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ cursor: 'pointer',
+ '&:last-child': {
+ borderBottom: 'none',
+ },
+ '& .maplibregl-ctrl-icon': {
+ backgroundSize: '20px',
+ backgroundRepeat: 'no-repeat',
+ backgroundPosition: 'center',
+ },
+ },
+ '& .maplibregl-ctrl-zoom-in .maplibregl-ctrl-icon': {
+ backgroundImage: `url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cpath stroke='${encodeURIComponent(
+ primary
+ )}' stroke-width='2' stroke-linecap='round' fill='none' d='M10 6v8M6 10h8'/%3E%3C/svg%3E")`,
+ },
+ '& .maplibregl-ctrl-zoom-in:hover .maplibregl-ctrl-icon, & .maplibregl-ctrl-zoom-in:focus-visible .maplibregl-ctrl-icon':
+ {
+ backgroundImage: `url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cpath stroke='${encodeURIComponent(
+ secondary
+ )}' stroke-width='2' stroke-linecap='round' fill='none' d='M10 6v8M6 10h8'/%3E%3C/svg%3E")`,
+ },
+ '& .maplibregl-ctrl-zoom-out .maplibregl-ctrl-icon': {
+ backgroundImage: `url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cpath stroke='${encodeURIComponent(
+ primary
+ )}' stroke-width='2' stroke-linecap='round' fill='none' d='M6 10h8'/%3E%3C/svg%3E")`,
+ },
+ '& .maplibregl-ctrl-zoom-out:hover .maplibregl-ctrl-icon, & .maplibregl-ctrl-zoom-out:focus-visible .maplibregl-ctrl-icon':
+ {
+ backgroundImage: `url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cpath stroke='${encodeURIComponent(
+ secondary
+ )}' stroke-width='2' stroke-linecap='round' fill='none' d='M6 10h8'/%3E%3C/svg%3E")`,
+ },
+ },
+ },
+ }),
+ [primary, secondary]
+ )
+
+ useEffect(() => {
+ if (!mapContainer.current || map.current) return
+
+ const setFeatureHover = (featureId, hover) => {
+ if (!featureId || !map.current) return
+ const layers = [BOUNDARY_LAYER_ID, CENTROIDS_LAYER_ID]
+ layers.forEach((sourceLayer) => {
+ map.current.setFeatureState(
+ { source: 'project-boundaries', sourceLayer, id: featureId },
+ { hover }
+ )
+ })
+ }
+
+ const clearHover = () => {
+ if (hoveredFeatureId.current) {
+ setFeatureHover(hoveredFeatureId.current, false)
+ hoveredFeatureId.current = null
+ }
+ hoveredFeature.current = null
+ if (map.current) {
+ map.current.getCanvas().style.cursor = ''
+ }
+ setMarkerVisible(false)
+ }
+
+ const updateHover = (feature) => {
+ const featureId = feature.id || feature.properties.project_id
+ const featureProjectId = feature.properties?.project_id
+ if (featureId !== hoveredFeatureId.current) {
+ clearHover()
+ hoveredFeatureId.current = featureId
+ setFeatureHover(featureId, true)
+ }
+ if (featureProjectId !== project.project_id) {
+ map.current.getCanvas().style.cursor = 'pointer'
+ }
+ }
+
+ const updateMarkerPosition = (feature) => {
+ if (!markerRef.current || !feature) {
+ hoveredFeature.current = null
+ return
+ }
+
+ const projectId = feature.properties?.project_id
+ if (projectId === project.project_id) {
+ setMarkerVisible(false)
+ hoveredFeature.current = null
+ return
+ }
+
+ hoveredFeature.current = feature
+ const coords = feature.geometry.coordinates
+ const point = map.current.project(coords)
+
+ const fontSize = 16
+ const letterSpacing = 0.02 // em
+ const charWidth = fontSize * (0.55 + letterSpacing)
+ const textWidth = projectId.length * charWidth
+ const offsetX = textWidth / 2 + 8
+
+ const offsetPoint = map.current.unproject([point.x + offsetX, point.y])
+ markerRef.current.setLngLat(offsetPoint)
+ setMarkerVisible(true)
+ }
+
+ const handleZoom = () => {
+ if (hoveredFeature.current) {
+ updateMarkerPosition(hoveredFeature.current)
+ }
+ }
+
+ const handleMouseMove = (event) => {
+ // Query both layers, prioritize labels over fills
+ const labels = map.current.queryRenderedFeatures(event.point, {
+ layers: ['project-centroids-label'],
+ })
+ if (labels[0]) {
+ updateHover(labels[0])
+ updateMarkerPosition(labels[0])
+ return
+ }
+
+ const tolerance = 3
+ const bbox = [
+ [event.point.x - tolerance, event.point.y - tolerance],
+ [event.point.x + tolerance, event.point.y + tolerance],
+ ]
+ const fills = map.current.queryRenderedFeatures(bbox, {
+ layers: ['project-boundaries-fill'],
+ })
+ if (fills[0]) {
+ updateHover(fills[0])
+ // Find the visible centroid label for this project to position the marker
+ const projectId = fills[0].properties?.project_id
+ const centroids = map.current.queryRenderedFeatures(undefined, {
+ layers: ['project-centroids-label'],
+ filter: ['==', ['get', 'project_id'], projectId],
+ })
+ if (centroids[0]) {
+ updateMarkerPosition(centroids[0])
+ } else {
+ setMarkerVisible(false)
+ }
+ } else {
+ clearHover()
+ }
+ }
+
+ const handleClick = (event) => {
+ const clickedProjectId = event.features?.[0]?.properties?.project_id
+ if (clickedProjectId && clickedProjectId !== project.project_id) {
+ router.push(`/projects/${clickedProjectId}`)
+ }
+ }
+
+ const protocol = new Protocol()
+ maplibregl.addProtocol('pmtiles', protocol.tile)
+
+ map.current = new maplibregl.Map({
+ container: mapContainer.current,
+ bounds,
+ fitBoundsOptions: { padding: 10 },
+ scrollZoom: false,
+ style: {
+ version: 8,
+ glyphs:
+ 'https://carbonplan-maps.s3.us-west-2.amazonaws.com/basemaps/fonts/{fontstack}/{range}.pbf',
+ sources: {
+ basemap: {
+ type: 'vector',
+ url: `pmtiles://${BASEMAP_PMTILES_URL}`,
+ attribution:
+ 'Protomaps © OpenStreetMap',
+ },
+ 'project-boundaries': {
+ type: 'vector',
+ url: `pmtiles://${BOUNDARY_PMTILES_URL}`,
+ promoteId: 'project_id',
+ attribution:
+ 'Karnik et al. (2025)',
+ },
+ },
+ layers: mapLayers,
+ },
+ attributionControl: false,
+ })
+
+ map.current.addControl(
+ new maplibregl.AttributionControl({ compact: true }),
+ 'bottom-right'
+ )
+
+ map.current.addControl(
+ new maplibregl.NavigationControl({ showCompass: false }),
+ 'bottom-right'
+ )
+
+ map.current.on('load', () => {
+ projectLayers
+ .filter((layer) => layer.id !== 'project-centroids-label')
+ .forEach((layer) => map.current.addLayer(layer, 'address_label'))
+
+ const labelLayer = projectLayers.find(
+ (layer) => layer.id === 'project-centroids-label'
+ )
+ if (labelLayer) map.current.addLayer(labelLayer)
+
+ const el = document.createElement('div')
+ markerRef.current = new maplibregl.Marker({
+ element: el,
+ anchor: 'center',
+ })
+ .setLngLat([0, 0])
+ .addTo(map.current)
+ setMarkerEl(el)
+
+ map.current.on('mousemove', handleMouseMove)
+ map.current.on('zoom', handleZoom)
+ map.current.on('click', 'project-boundaries-fill', handleClick)
+ map.current.on('click', 'project-centroids-label', handleClick)
+ })
+
+ const handleKeyDown = (e) => {
+ if ((e.metaKey || e.ctrlKey) && map.current) {
+ map.current.scrollZoom.enable()
+ }
+ }
+
+ const handleKeyUp = (e) => {
+ if (!e.metaKey && !e.ctrlKey && map.current) {
+ map.current.scrollZoom.disable()
+ }
+ }
+
+ window.addEventListener('keydown', handleKeyDown)
+ window.addEventListener('keyup', handleKeyUp)
+
+ return () => {
+ window.removeEventListener('keydown', handleKeyDown)
+ window.removeEventListener('keyup', handleKeyUp)
+ if (markerRef.current) {
+ markerRef.current.remove()
+ markerRef.current = null
+ }
+ setMarkerEl(null)
+ if (map.current) {
+ clearHover()
+ map.current.remove()
+ map.current = null
+ }
+ maplibregl.removeProtocol?.('pmtiles')
+ }
+ }, [bounds, projectLayers, mapLayers, project.project_id, router])
+
+ return (
+ <>
+
+ {markerEl &&
+ createPortal(
+ ,
+ markerEl
+ )}
+ >
+ )
+}
+
+export default Map
diff --git a/components/project.js b/components/project.js
index 6be73cd..aa0cd3b 100644
--- a/components/project.js
+++ b/components/project.js
@@ -8,6 +8,7 @@ import ProjectOverview from './project-overview'
import Timeline from './timeline'
import BackButton from './back-button'
import Quantity from './quantity'
+import Map from './map'
import { getProjectCategory } from './utils'
const Project = ({ project }) => {
@@ -115,6 +116,17 @@ const Project = ({ project }) => {
+ {project.bbox && (
+ <>
+
+ Boundary
+
+
+
+
+ >
+ )}
+
Transactions
diff --git a/components/queries.js b/components/queries.js
index 79c35cd..985c21c 100644
--- a/components/queries.js
+++ b/components/queries.js
@@ -1,4 +1,4 @@
-import { Column, Filter, Input, Link, Row } from '@carbonplan/components'
+import { Column, Filter, Input, Link, Row, Tag } from '@carbonplan/components'
import { useRouter } from 'next/router'
import { createContext, useContext, useEffect, useState } from 'react'
import { Box, Flex } from 'theme-ui'
@@ -30,6 +30,7 @@ export const QueryProvider = ({ children }) => {
)
const [projectType, setProjectType] = useState(null)
const [complianceOnly, setComplianceOnly] = useState(null)
+ const [hasGeography, setHasGeography] = useState(null)
const [search, setSearch] = useState('')
const [beneficiarySearch, setBeneficiarySearch] = useState('')
const [listingBounds, setListingBounds] = useState(null)
@@ -59,6 +60,8 @@ export const QueryProvider = ({ children }) => {
setRegistry,
category,
setCategory,
+ hasGeography,
+ setHasGeography,
projectType,
setProjectType,
complianceOnly,
@@ -118,6 +121,8 @@ const Queries = () => {
setRegistry,
complianceOnly,
setComplianceOnly,
+ hasGeography,
+ setHasGeography,
search,
setSearch,
beneficiarySearch,
@@ -141,7 +146,7 @@ const Queries = () => {
return (
{view === 'projects' && (
-
+
Project
@@ -175,7 +180,7 @@ const Queries = () => {
)}
{view === 'transactions' && (
-
+
User
@@ -208,7 +213,7 @@ const Queries = () => {
)}
-
+
Registry
@@ -227,7 +232,7 @@ const Queries = () => {
-
+
Category
@@ -240,7 +245,7 @@ const Queries = () => {
-
+
Type
@@ -253,7 +258,7 @@ const Queries = () => {
-
+
Country
@@ -269,7 +274,7 @@ const Queries = () => {
-
+
Protocol
@@ -285,7 +290,7 @@ const Queries = () => {
-
+
Program
@@ -316,6 +321,39 @@ const Queries = () => {
+
+
+ Geography
+
+
+
+ {
+ let value
+ if (obj.available && obj.missing) {
+ value = null
+ } else if (obj.available) {
+ value = true
+ } else if (obj.missing) {
+ value = false
+ } else {
+ return
+ }
+ setHasGeography(value)
+ }}
+ multiSelect
+ />
+
+
+
)
}
diff --git a/components/use-fetcher.js b/components/use-fetcher.js
index b3df724..ad3c5aa 100644
--- a/components/use-fetcher.js
+++ b/components/use-fetcher.js
@@ -22,6 +22,7 @@ const fetcher = ([
category,
projectType,
complianceOnly,
+ hasGeography,
search,
listingBounds,
transactionBounds,
@@ -129,6 +130,10 @@ const fetcher = ([
protocols.forEach((protocol) => params.append('protocol', protocol))
}
+ if (typeof hasGeography === 'boolean') {
+ params.append('geography', hasGeography ? 'true' : 'false')
+ }
+
const reqUrl = new URL(
'/research/offsets-db/api/query',
window.location.origin
@@ -162,6 +167,7 @@ const useFetcher = (
category,
projectType,
complianceOnly,
+ hasGeography,
search,
listingBounds,
transactionBounds,
@@ -176,6 +182,7 @@ const useFetcher = (
useDebounce(category),
useDebounce(projectType),
complianceOnly,
+ hasGeography,
useDebounce(search),
useDebounce(listingBounds),
useDebounce(transactionBounds),
diff --git a/components/use-map-theme.js b/components/use-map-theme.js
new file mode 100644
index 0000000..b1c8c4b
--- /dev/null
+++ b/components/use-map-theme.js
@@ -0,0 +1,174 @@
+import { useMemo } from 'react'
+import { useColorMode, useThemeUI, get } from 'theme-ui'
+import { layers, namedFlavor } from '@protomaps/basemaps'
+
+const language = 'en'
+
+export const useMapTheme = () => {
+ const [colorMode] = useColorMode()
+ const { theme } = useThemeUI()
+ const isDark = colorMode === 'dark'
+ const flavorName = isDark ? 'black' : 'white'
+ const transparent = 'transparent'
+ const hinted = get(theme, 'rawColors.hinted')
+ const primary = get(theme, 'rawColors.primary')
+ const muted = get(theme, 'rawColors.muted')
+ const background = get(theme, 'rawColors.background')
+ const secondary = get(theme, 'rawColors.secondary')
+
+ const mapTheme = useMemo(
+ () => ({
+ ...namedFlavor(flavorName),
+ buildings: muted,
+ background: transparent,
+ park_a: transparent,
+ park_b: transparent,
+ hospital: transparent,
+ industrial: transparent,
+ school: transparent,
+ wood_a: transparent,
+ wood_b: transparent,
+ pedestrian: transparent,
+ scrub_a: transparent,
+ scrub_b: transparent,
+ glacier: transparent,
+ sand: transparent,
+ beach: transparent,
+ aerodrome: transparent,
+ runway: transparent,
+ earth: transparent,
+ zoo: transparent,
+ military: transparent,
+
+ landcover: {
+ barren: transparent,
+ farmland: transparent,
+ forest: transparent,
+ glacier: transparent,
+ grassland: transparent,
+ scrub: transparent,
+ urban_area: transparent,
+ },
+
+ water: hinted,
+
+ bridges_other_casing: background,
+ bridges_minor_casing: background,
+ bridges_link_casing: background,
+ bridges_major_casing: background,
+ bridges_highway_casing: background,
+ bridges_other: muted,
+ bridges_minor: muted,
+ bridges_link: muted,
+ bridges_major: muted,
+ bridges_highway: muted,
+
+ minor_service_casing: background,
+ minor_casing: background,
+ link_casing: background,
+ major_casing_late: background,
+ highway_casing_late: background,
+ other: muted,
+ minor_service: muted,
+ minor_a: muted,
+ minor_b: muted,
+ link: muted,
+ major_casing_early: background,
+ major: muted,
+ highway_casing_early: background,
+ highway: muted,
+ pier: muted,
+
+ railway: muted,
+ boundaries: muted,
+
+ roads_label_minor: muted,
+ roads_label_minor_halo: background,
+ roads_label_major: muted,
+ roads_label_major_halo: background,
+ ocean_label: muted,
+ subplace_label: [
+ 'interpolate',
+ ['linear'],
+ ['zoom'],
+ 12,
+ muted,
+ 22,
+ primary,
+ ],
+ subplace_label_halo: background,
+ city_label: [
+ 'interpolate',
+ ['linear'],
+ ['zoom'],
+ 0,
+ muted,
+ 12,
+ secondary,
+ ],
+ city_label_halo: background,
+ state_label: [
+ 'interpolate',
+ ['linear'],
+ ['zoom'],
+ 0,
+ muted,
+ 12,
+ secondary,
+ ],
+ state_label_halo: background,
+ country_label: [
+ 'interpolate',
+ ['linear'],
+ ['zoom'],
+ 0,
+ muted,
+ 12,
+ secondary,
+ ],
+
+ address_label: muted,
+ address_label_halo: background,
+
+ regular: 'Relative Faux Pro Book',
+ bold: 'Relative Faux Pro Book',
+ italic: 'Relative Faux Pro Book',
+ }),
+ [flavorName, hinted, muted, background, primary]
+ )
+
+ const mapLayers = useMemo(() => {
+ const baseLayers = layers('basemap', mapTheme, {
+ lang: language,
+ })
+
+ return baseLayers.map((layer) => {
+ if (layer.id === 'places_locality' && layer.type === 'symbol') {
+ const { 'text-variable-anchor': _, ...restLayout } = layer.layout
+ return {
+ ...layer,
+ layout: {
+ ...restLayout,
+ 'text-anchor': 'center',
+ 'text-justify': 'center',
+ 'text-letter-spacing': 0.05,
+ },
+ }
+ }
+ if (layer.type === 'symbol' && layer.layout?.['text-field']) {
+ return {
+ ...layer,
+ layout: {
+ ...layer.layout,
+ 'text-letter-spacing': 0.05,
+ },
+ }
+ }
+ return layer
+ })
+ }, [mapTheme])
+
+ return mapLayers
+}
+
+export default useMapTheme
diff --git a/package-lock.json b/package-lock.json
index f0ba3b6..fc822a3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -20,12 +20,15 @@
"@mdx-js/loader": "^2.1.5",
"@mdx-js/react": "^2.1.5",
"@next/mdx": "^12.3.1",
+ "@protomaps/basemaps": "^5.7.0",
"@theme-ui/color": "^0.15.3",
"@theme-ui/match-media": "^0.15.3",
"d3-brush": "^3.0.0",
"d3-format": "^3.1.0",
"d3-selection": "^3.0.0",
+ "maplibre-gl": "^5.14.0",
"next": "^14.2.23",
+ "pmtiles": "^4.3.0",
"polished": "^4.2.2",
"react": "^18.2.0",
"react-animate-height": "^3.2.2",
@@ -400,6 +403,109 @@
"@jridgewell/sourcemap-codec": "1.4.14"
}
},
+ "node_modules/@mapbox/geojson-rewind": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/@mapbox/geojson-rewind/-/geojson-rewind-0.5.2.tgz",
+ "integrity": "sha512-tJaT+RbYGJYStt7wI3cq4Nl4SXxG8W7JDG5DMJu97V25RnbNg3QtQtf+KD+VLjNpWKYsRvXDNmNrBgEETr1ifA==",
+ "license": "ISC",
+ "dependencies": {
+ "get-stream": "^6.0.1",
+ "minimist": "^1.2.6"
+ },
+ "bin": {
+ "geojson-rewind": "geojson-rewind"
+ }
+ },
+ "node_modules/@mapbox/jsonlint-lines-primitives": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/@mapbox/jsonlint-lines-primitives/-/jsonlint-lines-primitives-2.0.2.tgz",
+ "integrity": "sha512-rY0o9A5ECsTQRVhv7tL/OyDpGAoUB4tTvLiW1DSzQGq4bvTPhNw1VpSNjDJc5GFZ2XuyOtSWSVN05qOtcD71qQ==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/@mapbox/point-geometry": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@mapbox/point-geometry/-/point-geometry-1.1.0.tgz",
+ "integrity": "sha512-YGcBz1cg4ATXDCM/71L9xveh4dynfGmcLDqufR+nQQy3fKwsAZsWd/x4621/6uJaeB9mwOHE6hPeDgXz9uViUQ==",
+ "license": "ISC"
+ },
+ "node_modules/@mapbox/tiny-sdf": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/@mapbox/tiny-sdf/-/tiny-sdf-2.0.7.tgz",
+ "integrity": "sha512-25gQLQMcpivjOSA40g3gO6qgiFPDpWRoMfd+G/GoppPIeP6JDaMMkMrEJnMZhKyyS6iKwVt5YKu02vCUyJM3Ug==",
+ "license": "BSD-2-Clause"
+ },
+ "node_modules/@mapbox/unitbezier": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/@mapbox/unitbezier/-/unitbezier-0.0.1.tgz",
+ "integrity": "sha512-nMkuDXFv60aBr9soUG5q+GvZYL+2KZHVvsqFCzqnkGEf46U2fvmytHaEVc1/YZbiLn8X+eR3QzX1+dwDO1lxlw==",
+ "license": "BSD-2-Clause"
+ },
+ "node_modules/@mapbox/vector-tile": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/@mapbox/vector-tile/-/vector-tile-2.0.4.tgz",
+ "integrity": "sha512-AkOLcbgGTdXScosBWwmmD7cDlvOjkg/DetGva26pIRiZPdeJYjYKarIlb4uxVzi6bwHO6EWH82eZ5Nuv4T5DUg==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@mapbox/point-geometry": "~1.1.0",
+ "@types/geojson": "^7946.0.16",
+ "pbf": "^4.0.1"
+ }
+ },
+ "node_modules/@mapbox/whoots-js": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@mapbox/whoots-js/-/whoots-js-3.1.0.tgz",
+ "integrity": "sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@maplibre/maplibre-gl-style-spec": {
+ "version": "24.3.1",
+ "resolved": "https://registry.npmjs.org/@maplibre/maplibre-gl-style-spec/-/maplibre-gl-style-spec-24.3.1.tgz",
+ "integrity": "sha512-TUM5JD40H2mgtVXl5IwWz03BuQabw8oZQLJTmPpJA0YTYF+B+oZppy5lNMO6bMvHzB+/5mxqW9VLG3wFdeqtOw==",
+ "license": "ISC",
+ "dependencies": {
+ "@mapbox/jsonlint-lines-primitives": "~2.0.2",
+ "@mapbox/unitbezier": "^0.0.1",
+ "json-stringify-pretty-compact": "^4.0.0",
+ "minimist": "^1.2.8",
+ "quickselect": "^3.0.0",
+ "rw": "^1.3.3",
+ "tinyqueue": "^3.0.0"
+ },
+ "bin": {
+ "gl-style-format": "dist/gl-style-format.mjs",
+ "gl-style-migrate": "dist/gl-style-migrate.mjs",
+ "gl-style-validate": "dist/gl-style-validate.mjs"
+ }
+ },
+ "node_modules/@maplibre/mlt": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@maplibre/mlt/-/mlt-1.1.2.tgz",
+ "integrity": "sha512-SQKdJ909VGROkA6ovJgtHNs9YXV4YXUPS+VaZ50I2Mt951SLlUm2Cv34x5Xwc1HiFlsd3h2Yrs5cn7xzqBmENw==",
+ "license": "(MIT OR Apache-2.0)",
+ "dependencies": {
+ "@mapbox/point-geometry": "^1.1.0"
+ }
+ },
+ "node_modules/@maplibre/vt-pbf": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/@maplibre/vt-pbf/-/vt-pbf-4.1.0.tgz",
+ "integrity": "sha512-9LjFAoWtxdGRns8RK9vG3Fcw/fb3eHMxvAn2jffwn3jnVO1k49VOv6+FEza70rK7WzF8GnBiKa0K39RyfevKUw==",
+ "license": "MIT",
+ "dependencies": {
+ "@mapbox/point-geometry": "^1.1.0",
+ "@mapbox/vector-tile": "^2.0.4",
+ "@types/geojson-vt": "3.2.5",
+ "@types/supercluster": "^7.1.3",
+ "geojson-vt": "^4.0.2",
+ "pbf": "^4.0.1",
+ "supercluster": "^8.0.1"
+ }
+ },
"node_modules/@mdx-js/loader": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@mdx-js/loader/-/loader-2.1.5.tgz",
@@ -627,6 +733,15 @@
"node": ">= 10"
}
},
+ "node_modules/@protomaps/basemaps": {
+ "version": "5.7.0",
+ "resolved": "https://registry.npmjs.org/@protomaps/basemaps/-/basemaps-5.7.0.tgz",
+ "integrity": "sha512-vIInnzVSxHuOcvj1BFGkCjlFxG/9a1GV23t98kGEVcPUM7aEqTnf6loUHTRJYX5eCz+WCO16N0aibr1SLg830Q==",
+ "license": "BSD-3-Clause",
+ "bin": {
+ "generate_style": "src/cli.ts"
+ }
+ },
"node_modules/@styled-system/background": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/@styled-system/background/-/background-5.1.2.tgz",
@@ -896,6 +1011,21 @@
"@types/estree": "*"
}
},
+ "node_modules/@types/geojson": {
+ "version": "7946.0.16",
+ "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz",
+ "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==",
+ "license": "MIT"
+ },
+ "node_modules/@types/geojson-vt": {
+ "version": "3.2.5",
+ "resolved": "https://registry.npmjs.org/@types/geojson-vt/-/geojson-vt-3.2.5.tgz",
+ "integrity": "sha512-qDO7wqtprzlpe8FfQ//ClPV9xiuoh2nkIgiouIptON9w5jvD/fA4szvP9GBlDVdJ5dldAl0kX/sy3URbWwLx0g==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/geojson": "*"
+ }
+ },
"node_modules/@types/hast": {
"version": "2.3.4",
"resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.4.tgz",
@@ -967,6 +1097,15 @@
"csstype": "^3.0.2"
}
},
+ "node_modules/@types/supercluster": {
+ "version": "7.1.3",
+ "resolved": "https://registry.npmjs.org/@types/supercluster/-/supercluster-7.1.3.tgz",
+ "integrity": "sha512-Z0pOY34GDFl3Q6hUFYf3HkTwKEE02e7QgtJppBt+beEAxnyOpJua+voGFvxINBHa06GwLFFym7gRPY2SiKIfIA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/geojson": "*"
+ }
+ },
"node_modules/@types/unist": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz",
@@ -1636,6 +1775,12 @@
"node": ">=0.3.1"
}
},
+ "node_modules/earcut": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/earcut/-/earcut-3.0.2.tgz",
+ "integrity": "sha512-X7hshQbLyMJ/3RPhyObLARM2sNxxmRALLKx1+NVFFnQ9gKzmCrxm9+uLIAdBcvc8FNLpctqlQ2V6AE92Ol9UDQ==",
+ "license": "ISC"
+ },
"node_modules/electron-to-chromium": {
"version": "1.4.284",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz",
@@ -1833,6 +1978,12 @@
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
"peer": true
},
+ "node_modules/fflate": {
+ "version": "0.8.2",
+ "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
+ "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==",
+ "license": "MIT"
+ },
"node_modules/find-root": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz",
@@ -1843,11 +1994,35 @@
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
},
+ "node_modules/geojson-vt": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/geojson-vt/-/geojson-vt-4.0.2.tgz",
+ "integrity": "sha512-AV9ROqlNqoZEIJGfm1ncNjEXfkz2hdFlZf0qkVfmkwdKa8vj7H16YUOT81rJw1rdFhyEDlN2Tds91p/glzbl5A==",
+ "license": "ISC"
+ },
+ "node_modules/get-stream": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
+ "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/github-slugger": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.5.0.tgz",
"integrity": "sha512-wIh+gKBI9Nshz2o46B0B3f5k/W+WI9ZAv6y5Dn5WJ5SK1t0TnDimB4WE5rmTD05ZAIn8HALCZVmCsvj0w0v0lw=="
},
+ "node_modules/gl-matrix": {
+ "version": "3.4.4",
+ "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.4.tgz",
+ "integrity": "sha512-latSnyDNt/8zYUB6VIJ6PCh2jBjJX6gnDsoCZ7LyW7GkqrD51EWwa9qCoGixj8YqBtETQK/xY7OmpTF8xz1DdQ==",
+ "license": "MIT"
+ },
"node_modules/glob-to-regexp": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz",
@@ -2125,6 +2300,18 @@
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"peer": true
},
+ "node_modules/json-stringify-pretty-compact": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/json-stringify-pretty-compact/-/json-stringify-pretty-compact-4.0.0.tgz",
+ "integrity": "sha512-3CNZ2DnrpByG9Nqj6Xo8vqbjT4F6N+tb4Gb28ESAZjYZ5yqvmc56J+/kuIwkaAMOyblTQhUW7PxMkUb8Q36N3Q==",
+ "license": "MIT"
+ },
+ "node_modules/kdbush": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-4.0.2.tgz",
+ "integrity": "sha512-WbCVYJ27Sz8zi9Q7Q0xHC+05iwkm3Znipc2XTlrnJbsHMYktW4hPhXUE8Ys1engBrvffoSCqbil1JQAa7clRpA==",
+ "license": "ISC"
+ },
"node_modules/kleur": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz",
@@ -2167,6 +2354,44 @@
"loose-envify": "cli.js"
}
},
+ "node_modules/maplibre-gl": {
+ "version": "5.14.0",
+ "resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-5.14.0.tgz",
+ "integrity": "sha512-O2ok6N/bQ9NA9nJ22r/PRQQYkUe9JwfDMjBPkQ+8OwsVH4TpA5skIAM2wc0k+rni5lVbAVONVyBvgi1rF2vEPA==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@mapbox/geojson-rewind": "^0.5.2",
+ "@mapbox/jsonlint-lines-primitives": "^2.0.2",
+ "@mapbox/point-geometry": "^1.1.0",
+ "@mapbox/tiny-sdf": "^2.0.7",
+ "@mapbox/unitbezier": "^0.0.1",
+ "@mapbox/vector-tile": "^2.0.4",
+ "@mapbox/whoots-js": "^3.1.0",
+ "@maplibre/maplibre-gl-style-spec": "^24.3.1",
+ "@maplibre/mlt": "^1.1.2",
+ "@maplibre/vt-pbf": "^4.1.0",
+ "@types/geojson": "^7946.0.16",
+ "@types/geojson-vt": "3.2.5",
+ "@types/supercluster": "^7.1.3",
+ "earcut": "^3.0.2",
+ "geojson-vt": "^4.0.2",
+ "gl-matrix": "^3.4.4",
+ "kdbush": "^4.0.2",
+ "murmurhash-js": "^1.0.0",
+ "pbf": "^4.0.1",
+ "potpack": "^2.1.0",
+ "quickselect": "^3.0.0",
+ "supercluster": "^8.0.1",
+ "tinyqueue": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=16.14.0",
+ "npm": ">=8.1.0"
+ },
+ "funding": {
+ "url": "https://github.com/maplibre/maplibre-gl-js?sponsor=1"
+ }
+ },
"node_modules/markdown-extensions": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-1.1.1.tgz",
@@ -2918,6 +3143,15 @@
"node": ">= 0.6"
}
},
+ "node_modules/minimist": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/mri": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
@@ -2931,6 +3165,12 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
+ "node_modules/murmurhash-js": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/murmurhash-js/-/murmurhash-js-1.0.0.tgz",
+ "integrity": "sha512-TvmkNhkv8yct0SVBSy+o8wYzXjE4Zz3PCesbfs8HiCXXdcTuocApFv11UWlNFWKYsP2okqrhb7JNlSm9InBhIw==",
+ "license": "MIT"
+ },
"node_modules/nanoid": {
"version": "3.3.8",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
@@ -3079,6 +3319,18 @@
"node": ">=8"
}
},
+ "node_modules/pbf": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/pbf/-/pbf-4.0.1.tgz",
+ "integrity": "sha512-SuLdBvS42z33m8ejRbInMapQe8n0D3vN/Xd5fmWM3tufNgRQFBpaW2YVJxQZV4iPNqb0vEFvssMEo5w9c6BTIA==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "resolve-protobuf-schema": "^2.1.0"
+ },
+ "bin": {
+ "pbf": "bin/pbf"
+ }
+ },
"node_modules/periscopic": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.0.4.tgz",
@@ -3093,6 +3345,15 @@
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
},
+ "node_modules/pmtiles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/pmtiles/-/pmtiles-4.3.0.tgz",
+ "integrity": "sha512-wnzQeSiYT/MyO63o7AVxwt7+uKqU0QUy2lHrivM7GvecNy0m1A4voVyGey7bujnEW5Hn+ZzLdvHPoFaqrOzbPA==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "fflate": "^0.8.2"
+ }
+ },
"node_modules/polished": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/polished/-/polished-4.2.2.tgz",
@@ -3132,6 +3393,12 @@
"node": "^10 || ^12 || >=14"
}
},
+ "node_modules/potpack": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/potpack/-/potpack-2.1.0.tgz",
+ "integrity": "sha512-pcaShQc1Shq0y+E7GqJqvZj8DTthWV1KeHGdi0Z6IAin2Oi3JnLCOfwnCo84qc+HAp52wT9nK9H7FAJp5a44GQ==",
+ "license": "ISC"
+ },
"node_modules/prettier": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.2.tgz",
@@ -3153,6 +3420,12 @@
"url": "https://github.com/sponsors/wooorm"
}
},
+ "node_modules/protocol-buffers-schema": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz",
+ "integrity": "sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw==",
+ "license": "MIT"
+ },
"node_modules/punycode": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
@@ -3162,6 +3435,12 @@
"node": ">=6"
}
},
+ "node_modules/quickselect": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-3.0.0.tgz",
+ "integrity": "sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g==",
+ "license": "ISC"
+ },
"node_modules/randombytes": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
@@ -3334,6 +3613,21 @@
"node": ">=4"
}
},
+ "node_modules/resolve-protobuf-schema": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/resolve-protobuf-schema/-/resolve-protobuf-schema-2.1.0.tgz",
+ "integrity": "sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==",
+ "license": "MIT",
+ "dependencies": {
+ "protocol-buffers-schema": "^3.3.1"
+ }
+ },
+ "node_modules/rw": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz",
+ "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==",
+ "license": "BSD-3-Clause"
+ },
"node_modules/sade": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz",
@@ -3522,6 +3816,15 @@
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.1.3.tgz",
"integrity": "sha512-GP6WDNWf+o403jrEp9c5jibKavrtLW+/qYGhFxFrG8maXhwTBI7gLLhiBb0o7uFccWN+EOS9aMO6cGHWAO07OA=="
},
+ "node_modules/supercluster": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/supercluster/-/supercluster-8.0.1.tgz",
+ "integrity": "sha512-IiOea5kJ9iqzD2t7QJq/cREyLHTtSmUT6gQsweojg9WH2sYJqZK9SswTu6jrscO6D1G5v5vYZ9ru/eq85lXeZQ==",
+ "license": "ISC",
+ "dependencies": {
+ "kdbush": "^4.0.2"
+ }
+ },
"node_modules/supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
@@ -3631,6 +3934,12 @@
"react": ">=18"
}
},
+ "node_modules/tinyqueue": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-3.0.0.tgz",
+ "integrity": "sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g==",
+ "license": "ISC"
+ },
"node_modules/to-fast-properties": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
diff --git a/package.json b/package.json
index cdc874d..a914727 100644
--- a/package.json
+++ b/package.json
@@ -32,12 +32,15 @@
"@mdx-js/loader": "^2.1.5",
"@mdx-js/react": "^2.1.5",
"@next/mdx": "^12.3.1",
+ "@protomaps/basemaps": "^5.7.0",
"@theme-ui/color": "^0.15.3",
"@theme-ui/match-media": "^0.15.3",
"d3-brush": "^3.0.0",
"d3-format": "^3.1.0",
"d3-selection": "^3.0.0",
+ "maplibre-gl": "^5.14.0",
"next": "^14.2.23",
+ "pmtiles": "^4.3.0",
"polished": "^4.2.2",
"react": "^18.2.0",
"react-animate-height": "^3.2.2",