Skip to content

Commit

Permalink
Merge pull request #176 from Dataport/refactor/shorten-methods-above-…
Browse files Browse the repository at this point in the history
…lint-treshold

#33 Refactor/shorten methods above lint treshold
  • Loading branch information
warm-coolguy authored Sep 26, 2024
2 parents 4e3b9c2 + 9e59ad5 commit d43bfa6
Show file tree
Hide file tree
Showing 7 changed files with 212 additions and 146 deletions.
166 changes: 166 additions & 0 deletions packages/plugins/Gfi/src/store/actions/debouncedGfiRequest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import debounce from 'lodash.debounce'
import { rawLayerList } from '@masterportal/masterportalapi'
import {
Feature as GeoJsonFeature,
GeoJsonProperties,
Geometry as GeoJsonGeometry,
} from 'geojson'
import {
GfiConfiguration,
MapConfig,
PolarActionContext,
} from '@polar/lib-custom-types'
import { Map, Feature } from 'ol'
import { Geometry } from 'ol/geom'
import VectorLayer from 'ol/layer/Vector'
import { addFeature } from '../../utils/displayFeatureLayer'
import { requestGfi } from '../../utils/requestGfi'
import sortFeatures from '../../utils/sortFeatures'
import { GfiGetters, GfiState } from '../../types'

const mapFeaturesToLayerIds = (
layerKeys: string[],
gfiConfiguration: GfiConfiguration,
features: (symbol | GeoJsonFeature<GeoJsonGeometry, GeoJsonProperties>[])[],
srsName: string
): Record<string, GeoJsonFeature[] | symbol> => {
const generalMaxFeatures =
gfiConfiguration.maxFeatures || Number.POSITIVE_INFINITY
const featuresByLayerId = layerKeys.reduce(
(accumulator, key, index) => ({
...accumulator,
[key]: Array.isArray(features[index])
? (features[index] as []).slice(
0,
gfiConfiguration.layers[key].maxFeatures || generalMaxFeatures
)
: features[index],
}),
{}
)
return Object.entries(featuresByLayerId).reduce(
(accumulator, [layerKey, layerValues]) => ({
...accumulator,
[layerKey]:
Array.isArray(layerValues) && layerValues.length >= 2
? layerValues.sort((a, b) => sortFeatures(a, b, srsName))
: layerValues,
}),
{}
)
}

const getPromisedFeatures = (
map: Map,
configuration: MapConfig,
layerKeys: string[],
coordinate: [number, number]
) =>
layerKeys.map((key) => {
const layer = map
.getLayers()
.getArray()
.find((layer) => layer.getProperties().id === key)

if (!layer) {
console.error(
`@polar/plugin-gfi: No layer with id "${key}" found during run-time. GFI skipped.`
)
return [] as GeoJsonFeature[]
}

const layerConfiguration = configuration.gfi?.layers[key] || {}
const layerSpecification = rawLayerList.getLayerWhere({ id: key })
const mainLayerConfiguration = configuration.layers.find(
(element) => element.id === key
)
const layerGfiMode =
mainLayerConfiguration?.gfiMode || configuration?.gfi?.mode || 'bboxDot'

return requestGfi({
map,
layer,
coordinate,
layerConfiguration,
layerSpecification,
mode: layerGfiMode,
})
})

const filterFeatures = (
featuresByLayerId: Record<
string,
symbol | GeoJsonFeature<GeoJsonGeometry, GeoJsonProperties>[]
>
): Record<string, GeoJsonFeature<GeoJsonGeometry, GeoJsonProperties>[]> => {
const entries = Object.entries(featuresByLayerId)
const filtered = entries.filter((keyValue) => Array.isArray(keyValue[1])) as [
string,
GeoJsonFeature<GeoJsonGeometry, GeoJsonProperties>[]
][]
return Object.fromEntries(filtered)
}

const errorSymbol = (err) => Symbol(err)

/**
* Code from `getFeatureInfo`, pulled to avoid overly requesting feature
* information. Since sources in Pins plugin update right after each other
* (and such effects are to be expected across the system), we're debouncing
* this *after* resetting the module state, as something is bound to happen.
*/
// eslint-disable-next-line max-lines-per-function
const gfiRequest =
(featureDisplayLayer: VectorLayer<Feature<Geometry>>) =>
async (
{
commit,
getters: { layerKeys },
rootGetters: { map, configuration },
getters: { geometryLayerKeys, afterLoadFunction },
}: PolarActionContext<GfiState, GfiGetters>,
coordinate: [number, number]
): Promise<void> => {
// fetch new feature information for all configured layers
const promisedFeatures = getPromisedFeatures(
map,
configuration,
layerKeys,
coordinate
)
const features = (await Promise.allSettled(promisedFeatures)).map(
(result) =>
result.status === 'fulfilled'
? result.value
: errorSymbol(result.reason.message)
)
const srsName: string = map.getView().getProjection().getCode()
let featuresByLayerId = mapFeaturesToLayerIds(
layerKeys,
// NOTE if there was no configuration, we would not be here
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
configuration.gfi!,
features,
srsName
)
// store features in state, if configured via client after specific function
if (typeof afterLoadFunction === 'function') {
featuresByLayerId = await afterLoadFunction(
filterFeatures(featuresByLayerId),
srsName
)
}
commit('setFeatureInformation', featuresByLayerId)
// render feature geometries to help layer
geometryLayerKeys
.filter((key) => Array.isArray(featuresByLayerId[key]))
.forEach((key) =>
filterFeatures(featuresByLayerId)[key].forEach((feature) =>
addFeature(feature, featureDisplayLayer)
)
)
}

export const debouncedGfiRequest = (
featureDisplayLayer: VectorLayer<Feature<Geometry>>
) => debounce(gfiRequest(featureDisplayLayer), 50)
151 changes: 8 additions & 143 deletions packages/plugins/Gfi/src/store/actions/index.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,17 @@
import debounce from 'lodash.debounce'
import compare from 'just-compare'
import { Coordinate } from 'ol/coordinate'
import { Style, Fill, Stroke } from 'ol/style'
import Overlay from 'ol/Overlay'
import { GeoJSON } from 'ol/format'
import { Feature } from 'ol'
import { Feature as GeoJsonFeature, GeoJsonProperties } from 'geojson'
import { rawLayerList } from '@masterportal/masterportalapi'
import { PolarActionTree } from '@polar/lib-custom-types'
import getCluster from '@polar/lib-get-cluster'
import { isVisible } from '@polar/lib-invisible-style'
import { getTooltip, Tooltip } from '@polar/lib-tooltip'
import {
getFeatureDisplayLayer,
clear,
addFeature,
} from '../../utils/displayFeatureLayer'
import { requestGfi } from '../../utils/requestGfi'
import { getFeatureDisplayLayer, clear } from '../../utils/displayFeatureLayer'
import { GfiGetters, GfiState } from '../../types'
import sortFeatures from '../../utils/sortFeatures'
import { getOriginalFeature } from '../../utils/getOriginalFeature'
import { debouncedGfiRequest } from './debouncedGfiRequest'

// OK for module action set creation
// eslint-disable-next-line max-lines-per-function
Expand Down Expand Up @@ -107,28 +100,10 @@ export const makeActions = () => {
...featureInformation[layerId][visibleWindowFeatureIndex]
.properties,
}
const originalFeature = listableLayerSources
.map((source) =>
source
.getFeatures()
.filter(isVisible)
.map((feature) => {
// true = silent change (prevents cluster recomputation & rerender)
feature.set(
'_gfiLayerId',
source.get('_gfiLayerId'),
true
)
return feature
})
)
.flat(1)
.find((f) =>
compare(
JSON.parse(new GeoJSON().writeFeature(f)).properties,
selectedFeatureProperties
)
)
const originalFeature = getOriginalFeature(
listableLayerSources,
selectedFeatureProperties
)
if (originalFeature) {
dispatch('setOlFeatureInformation', {
feature: getCluster(
Expand Down Expand Up @@ -244,117 +219,7 @@ export const makeActions = () => {
// call further stepped in a debounced fashion to avoid a mess
return await dispatch('debouncedGfiRequest', coordinate)
},
/**
* Code from `getFeatureInfo`, pulled to avoid overly requesting feature
* information. Since sources in Pins plugin update right after each other
* (and such effects are to be expected across the system), we're debouncing
* this *after* resetting the module state, as something is bound to happen.
*/
debouncedGfiRequest: debounce(
// TODO: Types are not properly displayed here as it is wrapped through debounce
async (
{
commit,
rootGetters: { map, configuration },
getters: { layerKeys, geometryLayerKeys, afterLoadFunction },
},
coordinate: [number, number]
): Promise<void> => {
// fetch new feature information for all configured layers
const promisedFeatures: Promise<GeoJsonFeature[]>[] = layerKeys.map(
(key) => {
const layer = map
.getLayers()
.getArray()
.find((layer) => layer.getProperties().id === key)

if (!layer) {
console.error(
`@polar/plugin-gfi: No layer with id "${key}" found during run-time. GFI skipped.`
)
return [] as GeoJsonFeature[]
}

const layerConfiguration = configuration.gfi.layers[key] || {}
const layerSpecification = rawLayerList.getLayerWhere({ id: key })
const mainLayerConfiguration = configuration.layers.find(
(element) => element.id === key
)
const layerGfiMode =
mainLayerConfiguration.gfiMode ||
configuration.gfi.mode ||
'bboxDot'

return requestGfi({
map,
layer,
coordinate,
layerConfiguration,
layerSpecification,
mode: layerGfiMode,
})
}
)

const errorSymbol = (err) => Symbol(err)
const features = (await Promise.allSettled(promisedFeatures)).map(
(result) =>
result.status === 'fulfilled'
? result.value
: errorSymbol(result.reason.message)
)

const generalMaxFeatures: number =
configuration.gfi.maxFeatures || Number.POSITIVE_INFINITY

const srsName: string = map.getView().getProjection().getCode()
// map features back to their layer keys
let featuresByLayerId: Record<string, GeoJsonFeature[] | symbol> =
layerKeys.reduce(
(accumulator, key, index) => ({
...accumulator,
[key]: Array.isArray(features[index])
? (features[index] as []).slice(
0,
configuration.gfi.layers[key].maxFeatures ||
generalMaxFeatures
)
: features[index],
}),
{}
)
featuresByLayerId = Object.entries(featuresByLayerId).reduce(
(accumulator, [layerKey, layerValues]) => ({
...accumulator,
[layerKey]:
Array.isArray(layerValues) && layerValues.length >= 2
? layerValues.sort((a, b) => sortFeatures(a, b, srsName))
: layerValues,
}),
{}
)

// store features in state, if configured via client specific function
if (typeof afterLoadFunction === 'function') {
featuresByLayerId = await afterLoadFunction(
featuresByLayerId,
srsName
)
}
commit('setFeatureInformation', featuresByLayerId)

// render feature geometries to help layer
geometryLayerKeys
.filter((key) => Array.isArray(featuresByLayerId[key]))
.forEach((key) =>
// @ts-expect-error | Might be fixed through having all the types in the action. Otherwise: It works properly, as all the symbols are filtered before calling forEach
featuresByLayerId[key].forEach((feature) =>
addFeature(feature, featureDisplayLayer)
)
)
},
50
),
debouncedGfiRequest: debouncedGfiRequest(featureDisplayLayer),
setCoreSelection(
{ commit, dispatch, rootGetters },
{
Expand Down
27 changes: 27 additions & 0 deletions packages/plugins/Gfi/src/utils/getOriginalFeature.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { GeoJSON } from 'ol/format'
import { Geometry } from 'ol/geom'
import { Feature } from 'ol'
import compare from 'just-compare'
import { isVisible } from '@polar/lib-invisible-style'
import { GeoJsonProperties } from 'geojson'
import VectorSource from 'ol/source/Vector'

export const getOriginalFeature = (
sources: VectorSource<Feature<Geometry>>[],
properties: GeoJsonProperties
) =>
sources
.map((source) =>
source
.getFeatures()
.filter(isVisible)
.map((feature) => {
// true = silent change (prevents cluster recomputation & rerender)
feature.set('_gfiLayerId', source.get('_gfiLayerId'), true)
return feature
})
)
.flat(1)
.find((f) =>
compare(JSON.parse(new GeoJSON().writeFeature(f)).properties, properties)
)
2 changes: 2 additions & 0 deletions packages/plugins/Gfi/src/utils/requestGfiWfs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ const xml2GeoJson = (
* no new request should be started. Instead, wait for any running load to
* finish and use feature at layer source coordinate. (Not trivial.)
*/
// NOTE length comes from string build, not complexity – keep as it is
// eslint-disable-next-line max-lines-per-function
export default ({
map,
coordinate,
Expand Down
2 changes: 0 additions & 2 deletions packages/plugins/Gfi/src/utils/requestGfiWms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ function readTextFeatures(text: string): Feature<Geometry>[] {
const lines = text.split('\n')
const features: Feature<Geometry>[] = []
let feature: Feature<Geometry> | undefined

/* TODO: Format supposedly looks like this – is this a standard or arbitrary?
GetFeatureInfo results:
LayerName:
Expand Down Expand Up @@ -76,7 +75,6 @@ function readTextFeatures(text: string): Feature<Geometry>[] {
features.push(feature)
}
}

return features
}

Expand Down
Loading

0 comments on commit d43bfa6

Please sign in to comment.