diff --git a/components/results/download.tsx b/components/results/download.tsx index 6e36aa5..b212d77 100644 --- a/components/results/download.tsx +++ b/components/results/download.tsx @@ -4,7 +4,12 @@ import { Box, Flex, Spinner } from 'theme-ui' import { Button } from '@carbonplan/components' //@ts-expect-error - carbonplan icons types not available import { Down, X } from '@carbonplan/icons' -import { DATA_VERSION, DATA_URLS, LICENSE_INFO } from '@/lib/config' +import { + DATA_VERSION, + DATA_URLS, + LICENSE_INFO, + PARQUET_BUCKET_URL, +} from '@/lib/config' import { useStore } from '@/lib/store' import { getGeographyName, getGeoid } from '@/lib/risk-utils' import { GeographyKey } from '@/types/location' @@ -78,8 +83,6 @@ const REGION_TYPES: Partial> = { censusBlock: 'block', } -const S3_BUCKET = new URL(DATA_URLS.parquetBase).origin - const DURATION_BUCKETS: [number, string][] = [ [1000, '<1s'], [5000, '1-5s'], @@ -123,11 +126,11 @@ async function getPartitionUrls( ): Promise { const stateFips = geoid.slice(0, 2) const countyFips = geoid.slice(2, 5) - const prefix = DATA_URLS.parquetBase.replace(S3_BUCKET + '/', '') + const prefix = DATA_URLS.parquetBase.replace(PARQUET_BUCKET_URL + '/', '') const partitionPrefix = `${prefix}/state_fips=${stateFips}/county_fips=${countyFips}/` const res = await fetch( - `${S3_BUCKET}/?list-type=2&prefix=${partitionPrefix}`, + `${PARQUET_BUCKET_URL}/?list-type=2&prefix=${partitionPrefix}`, { signal }, ) const doc = new DOMParser().parseFromString( @@ -138,7 +141,7 @@ async function getPartitionUrls( const urls: string[] = [] for (const el of doc.querySelectorAll('Contents > Key')) { const key = el.textContent - if (key?.endsWith('.parquet')) urls.push(`${S3_BUCKET}/${key}`) + if (key?.endsWith('.parquet')) urls.push(`${PARQUET_BUCKET_URL}/${key}`) } if (urls.length === 0) { diff --git a/components/results/regional-risk.tsx b/components/results/regional-risk.tsx index 1b864f0..fa515eb 100644 --- a/components/results/regional-risk.tsx +++ b/components/results/regional-risk.tsx @@ -8,7 +8,7 @@ import { LngLatBounds } from 'maplibre-gl' import { getGeographyRisk, getBoundingBox } from '@/lib/risk-utils' import { useStore } from '@/lib/store' import { - DATA_VERSION, + DATA_URLS, GEOGRAPHY_ATTRIBUTE_KEYS, GEOGRAPHY_MIN_ZOOM, STATISTICS_PATHS, @@ -345,14 +345,14 @@ const RegionalRisk = () => { label='CSV' loading={false} disabled={false} - href={`https://s3.us-west-2.amazonaws.com/us-west-2.opendata.source.coop/carbonplan/carbonplan-ocr/output/fire-risk/vector/production/${DATA_VERSION}/region-analysis/${STATISTICS_PATHS[geographyLevel]}/stats.csv`} + href={`${DATA_URLS.regionAnalysisBase}/${STATISTICS_PATHS[geographyLevel]}/stats.csv`} ariaLabel={`Download summary data as CSV`} /> diff --git a/lib/config.ts b/lib/config.ts index 9dd03ab..4593494 100644 --- a/lib/config.ts +++ b/lib/config.ts @@ -2,26 +2,42 @@ export const BASE_PATH = '/research/climate-risk' export const DATA_VERSION = 'v1.1.0' +// Source Coop's bucket name contains dots, so only path-style URLs work — +// virtual-hosted style (bucket.s3.amazonaws.com) fails TLS validation. +const S3_BUCKET_URL = + 'https://s3.us-west-2.amazonaws.com/us-west-2.opendata.source.coop' + +const dataUrl = (kind: 'vector' | 'pyramid', path: string) => + `${S3_BUCKET_URL}/carbonplan/carbonplan-ocr/output/fire-risk/${kind}/production/${DATA_VERSION}/${path}` + export const DATA_URLS = { vector: { buildings: process.env.NEXT_PUBLIC_BUILDING_URL ?? - `https://carbonplan-ocr.s3.amazonaws.com/output/fire-risk/vector/production/${DATA_VERSION}/pmtiles/buildings.pmtiles`, + dataUrl('vector', 'pmtiles/buildings.pmtiles'), regions: process.env.NEXT_PUBLIC_REGIONS_URL ?? - `https://carbonplan-ocr.s3.amazonaws.com/output/fire-risk/vector/production/${DATA_VERSION}/pmtiles/regions.pmtiles`, + dataUrl('vector', 'pmtiles/regions.pmtiles'), buildingPoints: process.env.NEXT_PUBLIC_BUILDING_POINTS_URL ?? - `https://carbonplan-ocr.s3.amazonaws.com/output/fire-risk/vector/production/${DATA_VERSION}/pmtiles/building_centroids.pmtiles`, + dataUrl('vector', 'pmtiles/building_centroids.pmtiles'), }, raster: - process.env.NEXT_PUBLIC_RISK_ZARR_URL ?? - `https://carbonplan-ocr.s3.amazonaws.com/output/fire-risk/pyramid/production/${DATA_VERSION}/pyramid.zarr`, + process.env.NEXT_PUBLIC_RISK_ZARR_URL ?? dataUrl('pyramid', 'pyramid.zarr'), parquetBase: process.env.NEXT_PUBLIC_GEOPARQUET_URL ?? - `https://carbonplan-ocr.s3.us-west-2.amazonaws.com/output/fire-risk/vector/production/${DATA_VERSION}/geoparquet/buildings.parquet`, + dataUrl('vector', 'geoparquet/buildings.parquet'), + regionAnalysisBase: dataUrl('vector', 'region-analysis'), } +// Root for ListObjectsV2 requests against whichever bucket holds the parquet. +// Path-style URLs put the bucket after the host; anything else is virtual-hosted. +export const PARQUET_BUCKET_URL = DATA_URLS.parquetBase.startsWith( + S3_BUCKET_URL, +) + ? S3_BUCKET_URL + : new URL(DATA_URLS.parquetBase).origin + export const LICENSE_INFO = { provider: 'CarbonPlan', termsOfAccess: diff --git a/pages/_document.tsx b/pages/_document.tsx index bd5b92f..777631c 100644 --- a/pages/_document.tsx +++ b/pages/_document.tsx @@ -21,10 +21,7 @@ class MyDocument extends Document { rel='preconnect' href='https://carbonplan-maps.s3.us-west-2.amazonaws.com' /> - +