Skip to content

Commit

Permalink
Use of ogc-client to generate ogc-api custom url
Browse files Browse the repository at this point in the history
  • Loading branch information
ronitjadhav committed May 23, 2024
1 parent 9f0464a commit 1c28342
Show file tree
Hide file tree
Showing 2 changed files with 110 additions and 84 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const mockDatasetServiceDistribution: DatasetServiceDistribution = {
url: new URL('https://api.example.com/data'),
type: 'service',
accessServiceProtocol: 'ogcFeatures',
name: 'mockFeatureType',
}

jest.mock('@camptocamp/ogc-client', () => ({
Expand All @@ -28,6 +29,16 @@ jest.mock('@camptocamp/ogc-client', () => ({
],
})
}
getCollectionItemsUrl(collectionName, options) {
const queryParams = new URLSearchParams()
if (options.limit !== undefined) queryParams.set('limit', options.limit)
if (options.offset !== undefined)
queryParams.set('offset', options.offset)
queryParams.set('f', options.outputFormat)
return `${
this.url
}/collections/${collectionName}/items?${queryParams.toString()}`
}
},
WfsEndpoint: class {
constructor(private url) {}
Expand Down Expand Up @@ -81,7 +92,9 @@ describe('RecordApFormComponent', () => {
expect(component.limit$.getValue()).toBe('-1')
expect(component.format$.getValue()).toBe('json')
const url = await firstValueFrom(component.apiQueryUrl$)
expect(url).toBe('https://api.example.com/data?limit=-1&f=json')
expect(url).toBe(
'https://api.example.com/data/collections/mockFeatureType/items?f=json'
)
})
})
describe('When URL params are changed', () => {
Expand All @@ -94,19 +107,19 @@ describe('RecordApFormComponent', () => {
component.setFormat(mockFormat)
const url = await firstValueFrom(component.apiQueryUrl$)
expect(url).toBe(
`https://api.example.com/data?offset=${mockOffset}&limit=${mockLimit}&f=${mockFormat}`
`https://api.example.com/data/collections/mockFeatureType/items?limit=${mockLimit}&offset=${mockOffset}&f=${mockFormat}`
)
})
it('should remove the param in url if value is null', async () => {
const mockOffset = null
const mockOffset = '0'
const mockLimit = '20'
const mockFormat = 'json'
component.setOffset(mockOffset)
component.setLimit(mockLimit)
component.setFormat(mockFormat)
const url = await firstValueFrom(component.apiQueryUrl$)
expect(url).toBe(
`https://api.example.com/data?limit=${mockLimit}&f=${mockFormat}`
`https://api.example.com/data/collections/mockFeatureType/items?limit=${mockLimit}&offset=${mockOffset}&f=${mockFormat}`
)
})
it('should remove the param in url if value is zero', async () => {
Expand All @@ -118,7 +131,7 @@ describe('RecordApFormComponent', () => {
component.setFormat(mockFormat)
const url = await firstValueFrom(component.apiQueryUrl$)
expect(url).toBe(
`https://api.example.com/data?offset=${mockOffset}&f=${mockFormat}`
`https://api.example.com/data/collections/mockFeatureType/items?limit=${mockLimit}&offset=${mockOffset}&f=${mockFormat}`
)
})
})
Expand Down Expand Up @@ -164,7 +177,7 @@ describe('RecordApFormComponent', () => {
expect(component.format$.getValue()).toBe('json')
const url = await firstValueFrom(component.apiQueryUrl$)
expect(url).toBe(
'https://api.example.com/data?type=undefined&options={"outputFormat":"json","startIndex":0}'
`https://api.example.com/data?type=mockFeatureType&options={"outputFormat":"json"}`
)
})
})
Expand Down
169 changes: 91 additions & 78 deletions libs/ui/elements/src/lib/record-api-form/record-api-form.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ const DEFAULT_PARAMS = {
LIMIT: '-1',
FORMAT: 'json',
}

interface OutputFormats {
itemFormats?: any[]
outputFormats?: any[]
}

@Component({
selector: 'gn-ui-record-api-form',
templateUrl: './record-api-form.component.html',
Expand All @@ -25,56 +31,38 @@ export class RecordApiFormComponent {
this.apiFeatureType = value ? value.name : undefined
if (value) {
this.apiBaseUrl = value.url.href
this.parseOutputFormats()
this.createEndpoint().then(() => this.parseOutputFormats())
}
this.resetUrl()
}

offset$ = new BehaviorSubject('')
limit$ = new BehaviorSubject('')
format$ = new BehaviorSubject('')
offset$ = new BehaviorSubject(DEFAULT_PARAMS.OFFSET)
limit$ = new BehaviorSubject(DEFAULT_PARAMS.LIMIT)
format$ = new BehaviorSubject(DEFAULT_PARAMS.FORMAT)
endpoint$ = new BehaviorSubject<WfsEndpoint | OgcApiEndpoint | undefined>(
undefined
)
apiBaseUrl: string
apiFeatureType: string
supportOffset = true
accessServiceProtocol: ServiceProtocol | undefined
outputFormats = [{ value: 'json', label: 'JSON' }]
endpoint: WfsEndpoint | OgcApiEndpoint | undefined

apiQueryUrl$ = combineLatest([this.offset$, this.limit$, this.format$]).pipe(
switchMap(async ([offset, limit, format]) => {
let outputUrl
if (this.apiBaseUrl) {
const url = new URL(this.apiBaseUrl)
const params = { offset: offset, limit: limit, f: format }
for (const [key, value] of Object.entries(params)) {
if (value && value !== '0') {
url.searchParams.set(key, value)
} else {
url.searchParams.delete(key)
}
}
outputUrl = url.toString()
}

if (this.accessServiceProtocol === 'wfs') {
const wfsEndpoint = new WfsEndpoint(this.apiBaseUrl)
if (await wfsEndpoint.isReady()) {
const options = {
outputFormat: format,
startIndex: Number(offset),
}
if (limit !== '-1') {
options['maxFeatures'] = Number(limit)
}
outputUrl = wfsEndpoint.getFeatureUrl(this.apiFeatureType, options)
}
}
return outputUrl
})
apiQueryUrl$ = combineLatest([
this.offset$,
this.limit$,
this.format$,
this.endpoint$,
]).pipe(
switchMap(([offset, limit, format, endpoint]) =>
this.generateApiQueryUrl(offset, limit, format)
)
)

noLimitChecked$ = this.limit$.pipe(
map((limit) => limit === '-1' || limit === '')
)

displayLimit$ = this.limit$.pipe(
map((limit) => (limit !== '-1' ? limit : ''))
)
Expand All @@ -84,8 +72,7 @@ export class RecordApiFormComponent {
}

setLimit(value: string) {
const newLimit = value === '' ? '-1' : value
this.limit$.next(newLimit)
this.limit$.next(value === '' ? '-1' : value)
}

setFormat(value: string | unknown) {
Expand All @@ -98,55 +85,81 @@ export class RecordApiFormComponent {
this.format$.next(DEFAULT_PARAMS.FORMAT)
}

parseOutputFormats() {
const apiUrl =
this.apiBaseUrl.slice(-1) === '?'
? this.apiBaseUrl.slice(0, -1)
: this.apiBaseUrl

this.getOutputFormats(apiUrl, this.accessServiceProtocol).then(
(outputFormats) => {
let formatsList = []
if ('itemFormats' in outputFormats) {
formatsList = this.mapFormats(outputFormats.itemFormats)
} else if ('outputFormats' in outputFormats) {
formatsList = this.mapFormats(outputFormats.outputFormats)
}
this.outputFormats = this.outputFormats.concat(
formatsList.filter(Boolean)
)
this.outputFormats = this.outputFormats
.filter(
(format, index, self) =>
index === self.findIndex((t) => t.value === format.value)
)
.sort((a, b) => a.label.localeCompare(b.label))
}
)
async parseOutputFormats() {
if (!this.endpoint) return
const apiUrl = this.apiBaseUrl.endsWith('?')
? this.apiBaseUrl.slice(0, -1)
: this.apiBaseUrl
const outputFormats = await this.getOutputFormats(apiUrl)

const formatsList = outputFormats.itemFormats
? this.mapFormats(outputFormats.itemFormats)
: this.mapFormats(outputFormats.outputFormats || [])

this.outputFormats = this.outputFormats
.concat(formatsList.filter(Boolean))
.filter(
(format, index, self) =>
index === self.findIndex((t) => t.value === format.value)
)
.sort((a, b) => a.label.localeCompare(b.label))
}

mapFormats(formats: any[]) {
return formats.map((format) => {
const normalizedFormat = mimeTypeToFormat(format)
if (normalizedFormat) {
return {
label: normalizedFormat.toUpperCase(),
value: normalizedFormat,
}
}
return null
return normalizedFormat
? { label: normalizedFormat.toUpperCase(), value: normalizedFormat }
: null
})
}

async getOutputFormats(url: string, accessServiceProtocol: string) {
if (accessServiceProtocol === 'wfs') {
const endpoint = await new WfsEndpoint(url).isReady()
this.supportOffset = endpoint.supportsStartIndex()
return endpoint.getServiceInfo()
async getOutputFormats(url: string): Promise<OutputFormats> {
if (!this.endpoint) return {}
if (this.endpoint instanceof WfsEndpoint) {
this.supportOffset = this.endpoint.supportsStartIndex()
return this.endpoint.getServiceInfo() as OutputFormats
} else {
{
const firstCollection = (await this.endpoint.featureCollections)[0]
return (await this.endpoint.getCollectionInfo(
firstCollection
)) as OutputFormats
}
}
}

async createEndpoint() {
if (!this.apiBaseUrl || !this.accessServiceProtocol) return
if (this.accessServiceProtocol === 'wfs') {
this.endpoint = new WfsEndpoint(this.apiBaseUrl)
await (this.endpoint as WfsEndpoint).isReady()
} else {
this.endpoint = new OgcApiEndpoint(this.apiBaseUrl)
}
this.endpoint$.next(this.endpoint)
}

async generateApiQueryUrl(
offset: string,
limit: string,
format: string
): Promise<string> {
if (!this.apiBaseUrl || !this.endpoint || !this.apiFeatureType) return ''

const options = {
outputFormat: format,
startIndex: offset ? Number(offset) : undefined,
maxFeatures: limit !== '-1' ? Number(limit) : undefined,
limit: limit !== '-1' ? Number(limit) : undefined,
offset: offset !== '' ? Number(offset) : undefined,
}

if (this.endpoint instanceof WfsEndpoint) {
return this.endpoint.getFeatureUrl(this.apiFeatureType, options)
} else {
const endpoint = await new OgcApiEndpoint(url)
const firstCollection = (await endpoint.featureCollections)[0]
return endpoint.getCollectionInfo(firstCollection)
const firstCollection = (await this.endpoint.featureCollections)[0]
return await this.endpoint.getCollectionItemsUrl(firstCollection, options)
}
}
}

0 comments on commit 1c28342

Please sign in to comment.