Skip to content

Commit

Permalink
refactor lasso, centralize service requests
Browse files Browse the repository at this point in the history
  • Loading branch information
warm-coolguy committed Feb 20, 2025
1 parent 5bc9014 commit e5ba9ba
Show file tree
Hide file tree
Showing 7 changed files with 175 additions and 149 deletions.
4 changes: 4 additions & 0 deletions packages/lib/getFeatures/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# CHANGELOG

## unpublished

- Feature: Add new methods `getVectorFeaturesByBboxRequest` and `getVectorFeaturesByFeatureRequest` that support both WFS and OAF layers.

## 3.0.0

- Breaking: Upgrade peerDependency `ol` from `^9.2.4` to `^10.3.1`.
Expand Down
4 changes: 4 additions & 0 deletions packages/lib/getFeatures/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
export * from './types'
export { getWfsFeatures } from './wfs'
export {
getVectorFeaturesByBboxRequest,
getVectorFeaturesByFeatureRequest,
} from './vector'
66 changes: 66 additions & 0 deletions packages/lib/getFeatures/vector/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { rawLayerList } from '@masterportal/masterportalapi'
import { Feature } from 'ol'

const supportedFormats = ['OAF', 'WFS']

export const getVectorFeaturesByBboxRequest = ({
bbox,
fetchLayerId,
projectionCode,
}: {
bbox: number[]
fetchLayerId: string
projectionCode: string
}) => {
const serviceDefinition = rawLayerList.getLayerWhere({ id: fetchLayerId })
if (!supportedFormats.includes(serviceDefinition.typ)) {
throw new Error(
`@polar/lib-get-features#getVectorFeaturesByBboxRequest: Layer with ID "${fetchLayerId}" of type "${serviceDefinition.typ}" used to retrieve vector data, but is not within the range of supported types [${supportedFormats}].`
)
}

const [codeName, codeNumber] = projectionCode.split(':')

const url =
serviceDefinition.typ === 'OAF'
? [
serviceDefinition.url,
'collections',
serviceDefinition.collection,
`items?f=json&limit=100&bbox=${bbox}&bbox-crs=http://www.opengis.net/def/crs/${codeName}/0/${codeNumber}&crs=http://www.opengis.net/def/crs/${codeName}/0/${codeNumber}`,
].join('/')
: `${serviceDefinition.url}${[
`?service=${serviceDefinition.typ}`,
`version=${serviceDefinition.version}`,
`request=GetFeature`,
`srsName=${projectionCode}`,
`typeName=${serviceDefinition.featureType}`,
`bbox=${bbox},${projectionCode}`,
].join('&')}`

return fetch(url)
}

export const getVectorFeaturesByFeatureRequest = ({
feature,
fetchLayerId,
projectionCode,
}: {
feature: Feature
fetchLayerId: string
projectionCode: string
}) => {
const bbox = feature.getGeometry()?.getExtent?.()

if (typeof bbox === 'undefined') {
throw new Error(
'@polar/lib-get-features#getVectorFeaturesByFeatureRequest: Given feature had no extent.'
)
}

return getVectorFeaturesByBboxRequest({
bbox,
fetchLayerId,
projectionCode,
})
}
8 changes: 2 additions & 6 deletions packages/plugins/Draw/src/locales.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,7 @@ export const resourcesDe = {
lasso: {
layerRejected:
'Die Antwort des Layers "{{id}}" konnte nicht gelesen werden. Es wurden keine Geometrien aus diesem Layer bezogen.',
notInZoomRange:
'Der Layer "$t({{serviceName}})" wurde nicht für die Lasso-Funktion genutzt, da er auf der derzeitigen Zoomstufe nicht aktivierbar ist. Bitte passen Sie die Zoomstufe an, um die Lasso-Funktion zu nutzen.',
fatalError:
internalError:
'Ein unerwarteter Fehler ist in der Verarbeitung der Lasso-Daten aufgetreten.',
},
},
Expand Down Expand Up @@ -97,9 +95,7 @@ export const resourcesEn = {
lasso: {
layerRejected:
'The response of layer "{{id}}" could not be read. No geometries were fetched from that layer.',
notInZoomRange:
'The layer "$t({{serviceName}})" was not used for the lasso as it is not activatable on the current zoom level. Please change the zoom level to use the lasso.',
fatalError:
internalError:
'An unexpected error occured in the processing of lasso data.',
},
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import Draw from 'ol/interaction/Draw'
import Interaction from 'ol/interaction/Interaction'
import i18next from 'i18next'
import { GeoJSON } from 'ol/format'
import { booleanContains } from '@turf/boolean-contains'
Expand All @@ -9,186 +8,121 @@ import { Feature } from 'ol'
import { Polygon } from 'ol/geom'
import { parseWfsResponse } from '@polar/lib-get-features/wfs/parse'
import { FeatureCollection, Feature as GeoJsonFeature } from 'geojson'
import VectorLayer from 'ol/layer/Vector'
import VectorSource from 'ol/source/Vector'
import { getVectorFeaturesByFeatureRequest } from '@polar/lib-get-features'
import { DrawGetters, DrawState } from '../../types'

const loaderKey = 'drawLasso'
const supportedFormats = ['OAF', 'WFS']
let loaderKeyCounter = 0
const loaderKeyBase = 'draw-lasso-load'

// TODO un – refactor after done (actions/getters are mixed here)
/* eslint-disable max-lines-per-function */
const requestError = `@polar/plugin-draw: An error occurred on creating the lasso request: `
const rejectedError =
'@polar/plugin-draw: The response to a lasso request indicated an error.'
const parseError =
'@polar/plugin-draw: Client failure in reading responses in lasso action.'
const internalError = () => ({
type: 'error',
text: i18next.t('plugins.draw.lasso.internalError'),
})

const buildAddFeaturesPayload = (
featureCollections: FeatureCollection[],
drawnLasso: Feature
) => ({
geoJSON: {
type: 'FeatureCollection',
features: featureCollections
.reduce(
(accumulator, { features }) => accumulator.concat(features),
[] as GeoJsonFeature[]
)
.filter((feature) =>
booleanContains(
JSON.parse(new GeoJSON().writeFeature(drawnLasso)),
feature
)
),
},
})

// should be fine, surrounding method is only unpacking/packing, also see below
// eslint-disable-next-line max-lines-per-function
export default function ({
rootGetters,
getters,
commit,
dispatch,
}: PolarActionContext<DrawState, DrawGetters>): Interaction[] {
const draw = new Draw({
type: 'Polygon',
freehand: true,
})
const { addLoading, removeLoading } = rootGetters.configuration?.draw || {}
const toastAction = rootGetters.configuration.draw?.toastAction || ''
}: PolarActionContext<DrawState, DrawGetters>) {
const draw = new Draw({ type: 'Polygon', freehand: true })

// should be fine, line bloat is from error handling (only logging/toasting)
// eslint-disable-next-line max-lines-per-function
draw.on('drawend', (e) => {
dispatch('setMode', 'none')

const toast = (toastObject) =>
getters.toastAction &&
dispatch(getters.toastAction, toastObject, { root: true })
const drawnLasso = e.feature as Feature<Polygon> // due to Draw 'type' param
const drawnLassoAsGeoJson = JSON.parse(
new GeoJSON().writeFeature(drawnLasso)
)
const lassoIds = (rootGetters.configuration.draw?.lassos || []).reduce(
(accumulator, { id, minZoom = true }) => {
const layerConfig = rootGetters.configuration.layers?.find(
(layer) => id === layer.id
)
if (
minZoom &&
layerConfig &&
typeof layerConfig.minZoom !== 'undefined' &&
rootGetters.zoomLevel < layerConfig.minZoom
) {
if (toastAction) {
dispatch(
toastAction,
{
type: 'info',
text: i18next.t('plugins.draw.lasso.notInZoomRange', {
serviceName: layerConfig.name || id,
}),
timeout: 10000,
},
{ root: true }
)
} else {
console.warn(
`Lasso not used with layer with id "${id}". (minZoom not reached)`
)
}
return accumulator
}
accumulator.push(id)
return accumulator
},
[] as string[]
)
const requests = lassoIds.reduce((accumulator, id) => {
const serviceDefinition = rawLayerList.getLayerWhere({ id })
if (!supportedFormats.includes(serviceDefinition.typ)) {
console.warn(
`@polar/plugin-draw: Layer with ID "${id} configured for 'lasso, but it's not a supported format. Layer is of type "${serviceDefinition.typ}", but only [${supportedFormats}] are supported.`
)
return accumulator
}

const source = (
rootGetters.map
.getLayers()
.getArray()
.find((layer) => layer.get('id') === id) as VectorLayer
)?.getSource?.()
if (!(source instanceof VectorSource)) {
console.warn(
`@polar/plugin-draw: Layer with ID "${id}" configured for 'lasso', but it has no vector source. The layer does probably not hold any vector data and is skipped.`
)
return accumulator
const requests = getters.activeLassoIds.reduce((accumulator, id) => {
try {
const request = getVectorFeaturesByFeatureRequest({
feature: drawnLasso,
fetchLayerId: id,
projectionCode: rootGetters.map.getView().getProjection().getCode(),
})
accumulator.push(request)
} catch (e) {
console.error(requestError, e)
toast(internalError())
}

const code = rootGetters.map.getView().getProjection().getCode()
const [codeName, codeNumber] = code.split(':')

const url =
serviceDefinition.typ === 'OAF'
? [
serviceDefinition.url,
'collections',
serviceDefinition.collection,
`items?f=json&limit=100&bbox=${drawnLasso
.getGeometry()
?.getExtent()}&bbox-crs=http://www.opengis.net/def/crs/${codeName}/0/${codeNumber}&crs=http://www.opengis.net/def/crs/${codeName}/0/${codeNumber}`,
].join('/')
: `${serviceDefinition.url}${[
`?service=${serviceDefinition.typ}`,
`version=${serviceDefinition.version}`,
`request=GetFeature`,
`srsName=${code}`,
`typeName=${serviceDefinition.featureType}`,
`bbox=${drawnLasso.getGeometry()?.getExtent()},${code}`,
].join('&')}`

accumulator.push(fetch(url))
return accumulator
}, [] as Promise<Response>[])

if (addLoading) {
commit(addLoading, loaderKey, { root: true })
let loaderKey
if (getters.configuration.addLoading) {
loaderKey = `${loaderKeyBase}-${loaderKeyCounter++}`
commit(getters.configuration.addLoading, loaderKey, { root: true })
}

Promise.allSettled(requests)
.then((settledRequests) =>
Promise.all(
(
settledRequests.filter((promiseSettledResult, index) => {
if (promiseSettledResult.status === 'rejected') {
console.error(
'@polar/plugin-draw: Error during reading lasso response from layer.',
promiseSettledResult.reason
)
if (toastAction) {
dispatch(
toastAction,
{
type: 'error',
text: i18next.t('plugins.draw.lasso.layerRejected', {
id: lassoIds[index],
}),
},
{ root: true }
)
}
console.error(rejectedError, promiseSettledResult.reason)
toast({
type: 'error',
text: i18next.t('plugins.draw.lasso.layerRejected', {
id: getters.activeLassoIds[index],
}),
})
return false
}
return true
}) as PromiseFulfilledResult<Response>[]
).map(async (resolution, index) =>
rawLayerList.getLayerWhere({ id: lassoIds[index] }).typ === 'WFS'
rawLayerList.getLayerWhere({ id: getters.activeLassoIds[index] })
.typ === 'WFS'
? await parseWfsResponse(resolution.value, undefined, false)
: ((await resolution.value.json()) as FeatureCollection)
)
)
)
.then((featureCollections) =>
dispatch('addFeatures', {
geoJSON: {
type: 'FeatureCollection',
features: featureCollections
.reduce(
(accumulator, { features }) => accumulator.concat(features),
[] as GeoJsonFeature[]
)
.filter((feature) =>
booleanContains(drawnLassoAsGeoJson, feature)
),
},
})
dispatch(
'addFeatures',
buildAddFeaturesPayload(featureCollections, drawnLasso)
)
)
.catch((error) => {
console.error(
'@polar/plugin-draw: Client failure in reading responses in lasso action.',
error
)
if (toastAction) {
dispatch(
toastAction,
{
type: 'error',
text: i18next.t('plugins.draw.lasso.fatalError'),
},
{ root: true }
)
}
console.error(parseError, error)
toast(internalError())
})
.finally(
() => removeLoading && commit(removeLoading, loaderKey, { root: true })
() =>
getters.configuration.removeLoading &&
commit(getters.configuration.removeLoading, loaderKey, { root: true })
)
})

Expand Down
Loading

0 comments on commit e5ba9ba

Please sign in to comment.