Skip to content

Commit

Permalink
feat: esri
Browse files Browse the repository at this point in the history
  • Loading branch information
8lane committed Aug 7, 2024
1 parent aa6f0ef commit 0ac7806
Show file tree
Hide file tree
Showing 6 changed files with 70 additions and 22 deletions.
54 changes: 43 additions & 11 deletions src/app/api/proxy/map/route.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,70 @@
import assert from 'assert'
import { NextResponse } from 'next/server'
import { NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'

export const dynamic = 'force-dynamic'
import { MapTileProvider } from '@/app/types'

// TODO: Update infra env vars to use generic naming.
const clientUrl = process.env.ESRI_CLIENT_URL
const clientId = process.env.ESRI_CLIENT_KEY
const clientSecret = process.env.ESRI_CLIENT_SECRET
export const dynamic = 'force-dynamic'

const clientResponseSchema = z.object({
const osmClientResponseSchema = z.object({
access_token: z.string(),
expires_in: z.string(),
issued_at: z.string(),
token_type: z.string(),
})

export async function POST() {
const esriClientResponseSchema = z.object({
access_token: z.string(),
expires_in: z.coerce.string(),
})

export async function POST(request: NextRequest) {
const body = await request.json()

let clientUrl
let clientId
let clientSecret

const provider: MapTileProvider = body.provider

if (provider === 'ArcGISEsri') {
clientUrl = process.env.ESRI_CLIENT_URL
clientId = process.env.ESRI_CLIENT_KEY
clientSecret = process.env.ESRI_CLIENT_SECRET
}

if (provider === 'OrdinanceSurveyMaps') {
clientUrl = process.env.OSM_CLIENT_URL
clientId = process.env.OSM_CLIENT_KEY
clientSecret = process.env.OSM_CLIENT_SECRET
}

assert(clientUrl)
assert(clientId)
assert(clientSecret)

const basicAuth = btoa(`${clientId}:${clientSecret}`)
const osmBasicAuth = { Authorization: `Basic ${btoa(`${clientId}:${clientSecret}`)}` }
const esriAuth = { client_id: clientId, client_secret: clientSecret }

const req = await fetch(clientUrl, {
method: 'POST',
headers: { Authorization: `Basic ${basicAuth}`, 'Content-Type': 'application/x-www-form-urlencoded' },
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
...(provider === 'OrdinanceSurveyMaps' && osmBasicAuth),
},
body: new URLSearchParams({
...(provider === 'ArcGISEsri' && esriAuth),
grant_type: 'client_credentials',
expiration: '5',
}),
})

const json = await req.json()
const { access_token: token, expires_in: expiresIn } = clientResponseSchema.parse(json)

console.log('json', json)
const parser = provider === 'ArcGISEsri' ? esriClientResponseSchema : osmClientResponseSchema

const { access_token: token, expires_in: expiresIn } = parser.parse(json)

return NextResponse.json({
token,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import useWeatherHealthAlertList from '@/app/hooks/queries/useWeatherHealthAlert
import { useTranslation } from '@/app/i18n/client'
import { MapTileProvider } from '@/app/types'

const { Map, BaseLayer, BaseLayerOSM, ChoroplethLayer, HealthAlertControl } = {
const { Map, BaseLayer, BaseLayerOSM, BaseLayerEsri, ChoroplethLayer, HealthAlertControl } = {
Map: dynamic(() => import('@/app/components/ui/ukhsa/Map/Map'), {
ssr: false,
loading: () => <Skeleton className="h-screen" />,
Expand All @@ -33,6 +33,9 @@ const { Map, BaseLayer, BaseLayerOSM, ChoroplethLayer, HealthAlertControl } = {
BaseLayerOSM: dynamic(() => import('@/app/components/ui/ukhsa/Map/shared/layers/BaseLayerOSM'), {
ssr: false,
}),
BaseLayerEsri: dynamic(() => import('@/app/components/ui/ukhsa/Map/shared/layers/BaseLayerEsri'), {
ssr: false,
}),
ChoroplethLayer: dynamic(() => import('@/app/components/ui/ukhsa/Map/shared/layers/ChoroplethLayer'), {
ssr: false,
}),
Expand Down Expand Up @@ -62,8 +65,12 @@ export default function HealthAlertsMapDialog({ featureCollection, mapTileProvid
const alertsQuery = useWeatherHealthAlertList({ type })

const baseLayer = useMemo(() => {
console.log('mapTileProvider', mapTileProvider)
if (mapTileProvider === 'OrdinanceSurveyMaps') {
return <BaseLayerOSM />
return <BaseLayerOSM provider={mapTileProvider} />
}
if (mapTileProvider === 'ArcGISEsri') {
return <BaseLayerEsri provider={mapTileProvider} />
}
return <BaseLayer />
}, [mapTileProvider])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ export async function HealthAlertsMapWrapper() {
<Suspense fallback={null}>
{mapTileProvider === 'OrdinanceSurveyMaps' ? (
<HealthAlertsMapDialog mapTileProvider="OrdinanceSurveyMaps" featureCollection={featureCollection} />
) : mapTileProvider === 'ArcGISEsri' ? (
<HealthAlertsMapDialog mapTileProvider="ArcGISEsri" featureCollection={featureCollection} />
) : (
<HealthAlertsMapDialog mapTileProvider="OpenStreetMaps" featureCollection={featureCollection} />
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,22 @@ import { ComponentProps } from 'react'
import VectorBasemapLayer from 'react-esri-leaflet/plugins/VectorBasemapLayer'

import useMapAuthToken from '@/app/hooks/queries/useMapAuthToken'
import { MapTileProvider } from '@/app/types'

import { useBaseLayerEsri } from '../hooks/useBaseLayerEsri'

type EsriProps = ComponentProps<typeof VectorBasemapLayer>

interface BaseLayerEsriProps extends Omit<EsriProps, 'name'> {
name?: EsriProps['name']
provider: MapTileProvider
}

const BaseLayerEsri = ({ name = 'ArcGIS:Navigation', ...rest }: BaseLayerEsriProps) => {
const BaseLayerEsri = ({ name = 'ArcGIS:Navigation', provider, ...rest }: BaseLayerEsriProps) => {
useBaseLayerEsri()
const {
data: { token },
} = useMapAuthToken()
} = useMapAuthToken(provider)
if (!token) return null
return (
<VectorBasemapLayer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import VectorTileLayer from 'react-leaflet-vector-tile-layer'

import { ordinanceSurveyMapsLicense } from '@/app/constants/map.constants'
import useMapAuthToken from '@/app/hooks/queries/useMapAuthToken'
import { MapTileProvider } from '@/app/types'

// The `transformRequest` method must be augmented as the `TileLayer` component is missing a type for this prop
declare module 'react-leaflet-vector-tile-layer' {
Expand All @@ -24,17 +25,19 @@ declare module 'react-leaflet-vector-tile-layer' {
}
}

interface BaseLayerProps extends Partial<TileLayerProps> {}
interface BaseLayerProps extends Partial<TileLayerProps> {
provider: MapTileProvider
}

const attribution = `\n\n© Crown copyright ${new Date().getFullYear()} OS ${ordinanceSurveyMapsLicense}. Use of this data is subject to terms and conditions.\n
You are granted a non-exclusive, royalty free revocable licence solely to view the licensed data for non-commercial purposes for the period during which UKHSA makes it available; You are not permitted to copy, sub-license, distribute, sell or otherwise make available the licensed data to third parties in any form; and Third party rights to enforce the terms of this licence shall be reserved to OS.\n
Ⓗ Hawlfraint y Goron ${new Date().getFullYear()} Arolwg Ordnans ${ordinanceSurveyMapsLicense}. Defnyddio data hwn yn amodol ar delerau ac amodau.\nRhoddir trwydded ddirymiadwy, anghyfyngedig a heb freindal i chi i weld y data trwyddedig at ddibenion anfasnachol am y cyfnod y mae ar gael gan UKHSA. Ni chewch gopïo, is-drwyddedu, dosbarthu na gwerthu unrhyw ran o’r data hwn i drydydd partïon mewn unrhyw ffurf; ac. Yr Arolwg Ordnans fydd yn cadw’r hawlio trydydd parti i orfodi amodau’r drwydded hon.
`

const BaseLayerOSM = ({ ...rest }: BaseLayerProps) => {
const BaseLayerOSM = ({ provider, ...rest }: BaseLayerProps) => {
const {
data: { token },
} = useMapAuthToken()
} = useMapAuthToken(provider)
return (
<VectorTileLayer
{...rest}
Expand Down
10 changes: 6 additions & 4 deletions src/app/hooks/queries/useMapAuthToken.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
import { useQueryClient, useSuspenseQuery } from '@tanstack/react-query'
import { z } from 'zod'

import { MapTileProvider } from '@/app/types'

const PROXY_ENDPOINT = 'api/proxy/map'
const RETRY_ATTEMPTS = 1

const proxyResponseSchema = z.object({ token: z.string(), expiresIn: z.string() })

async function getToken() {
const req = await fetch(PROXY_ENDPOINT, { method: 'POST' })
async function getToken(provider: MapTileProvider) {
const req = await fetch(PROXY_ENDPOINT, { method: 'POST', body: JSON.stringify({ provider }) })
const res = await req.json()
return proxyResponseSchema.parse(res)
}

export default function useMapAuthToken() {
export default function useMapAuthToken(provider: MapTileProvider) {
const queryClient = useQueryClient()

return useSuspenseQuery({
queryKey: ['weather-alert', 'map-auth-token'],
queryFn: getToken,
queryFn: async () => getToken(provider),
retry: RETRY_ATTEMPTS,
refetchInterval: (query) => {
// Extract the token expiration time (in seconds) and set the query refetch interval
Expand Down

0 comments on commit 0ac7806

Please sign in to comment.