Skip to content

Commit

Permalink
Merge pull request #211 from Dataport/feature/gfi-radius-select
Browse files Browse the repository at this point in the history
Extend multi selection by adding new mode "circle"
  • Loading branch information
dopenguin authored Jan 30, 2025
2 parents 1fa351e + dbb2cec commit 81cb8b1
Show file tree
Hide file tree
Showing 12 changed files with 118 additions and 61 deletions.
2 changes: 1 addition & 1 deletion packages/clients/snowbox/src/mapConfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ export const mapConfiguration = {
gfi: {
mode: 'bboxDot',
activeLayerPath: 'plugin/layerChooser/activeMaskIds',
boxSelect: true,
multiSelect: 'circle',
layers: {
[uBahn]: {
geometry: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ export default function (
type: 'Point',
style: textStyle,
})
// @ts-expect-error | internal hack to detect it in @polar/plugin-pins and @polar/plugin-gfi
draw._isDrawPlugin = true
draw.on('drawend', function (e) {
e.feature.setStyle(textStyle)
e.feature.set('text', textInput)
Expand Down
2 changes: 2 additions & 0 deletions packages/plugins/Draw/src/store/createInteractions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ export default function (
type: drawMode,
style,
})
// @ts-expect-error | internal hack to detect it in @polar/plugin-pins and @polar/plugin-gfi
draw._isDrawPlugin = true
draw.on('drawend', (e) => e.feature.setStyle(style))

return [draw, new Snap({ source: drawSource })]
Expand Down
1 change: 1 addition & 0 deletions packages/plugins/Gfi/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## unpublished

- Feature: Add new configuration parameter `multiSelect` to enable the possibility to choose between the selecting multiple features through a box or through a circle. The addition of this parameter deprecates the previously used parameter `boxSelect`.
- Fix: Correctly disable `directSelect` if the user is currently using functionality of `@polar/plugin-draw`.

## 2.1.0
Expand Down
3 changes: 2 additions & 1 deletion packages/plugins/Gfi/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@ The GFI plugin can be used to fetch and optionally display GFI (GetFeatureInfo)
| layers | Record<string, gfiLayerConfiguration> | Maps a string (must be a layer ID) to a behaviour configuration for that layer. |
| activeLayerPath | string? | Optional store path to array of active mask layer ids. If used with `LayerChooser`, setting this to `'plugin/layerChooser/activeMaskIds'` will result in an info text in the GFI box, should no layer be active. If used without `LayerChooser`, the active mask layers have to be provided by another plugin or the client. If not set, the GFI plugin may e.g. show an empty list, which may be confusing to some users. |
| afterLoadFunction | function (featuresByLayerId: Record<string, GeoJsonFeature[]>): Record<layerId, GeoJsonFeature[]>? | This method can be used to extend, filter, or otherwise modify a GFI result. |
| boxSelect | boolean? | If set to `true`, multiple features can be selected at once by using the modifier key (CTRL on Windows or Command on macOS) and dragging the mouse. Similar to `gfi.directSelect`, features can be added and removed by selection / unselecting them. The features need to be distinguishable by their properties for the functionality to properly work. Does not work together with `extendedMasterportalapiMarkers` of `@polar/core`. Defaults to `false`. |
| boxSelect | boolean? | **This parameter has been deprecated. Please use `multiSelect` to `'box'` instead.** If set to `true`, multiple features can be selected at once by using the modifier key (CTRL on Windows or Command on macOS) and dragging the mouse. Can only be used in Desktop environments. Similar to `gfi.directSelect`, features can be added and removed by selection / unselecting them. The features need to be distinguishable by their properties for the functionality to properly work. Does not work together with `extendedMasterportalapiMarkers` of `@polar/core`. Defaults to `false`. |
| customHighlightStyle | customHighlighStyle? | If required a user can change the stroke and fill of the highlighted feature. The default style as seen in the example will be used for each part that is not customized. An empty object will return the complete default style while e.g. for an object without a configured fill the default fill will be applied. |
| directSelect | boolean? | If set to `true`, a feature can be selected without defining a value in `gfi.coordinateSources`. It is also possible to add multiple features to the selection by using the modifier key (CTRL on Windows or Command on macOS). To delesect a feature, simply reclick it with the modifier key pressed. To create a new selection, click anywhere else without pressing the modifier key. Be careful when using this parameter together with some values set in `coordinateSources` as it may lead to unexpected results. The features need to be distinguishable by their properties for the functionality to properly work. Does not work together with `extendedMasterportalapiMarkers` of `@polar/core`. Defaults to `false`. |
| featureList | featureList? | If defined, a list of available vector layer features is visible when no feature is selected. Only usable if `renderType` is set to `iconMenu` and `window` is set to `true` for at least one configured layer. |
| gfiContentComponent | VueConstructor? | Allows overriding the GfiContent.vue component for custom design and functionality. Coding knowledge is required to use this feature, as any implementation will have to rely upon the VueX store model. Please refer to the implementation. |
| maxFeatures | number? | Limits the viewable GFIs per layer by this number. The first n elements are chosen arbitrarily. Useful if you e.g. just want one result, or to limit an endless stream of returns to e.g. 10. Infinite by default. |
| mode | enum["bboxDot", "intersects"]? | Method of calculating which feature has been chosen by the user. `bboxDot` utilizes the `bbox`-url parameter using the clicked coordinate while `intersects` uses a `Filter` to calculate the intersected features. Layers can have their own `gfiMode` parameter which would override this global mode. To apply this, add the desired value to the parameter in the `mapConfiguration`. Defaults to `'bboxDot'`. |
| multiSelect | enum["box", "circle"]? | If configured, multiple features can be selected at once by using the modifier key (CTRL on Windows or Command on macOS) and dragging the mouse. Can only be used in Desktop environments. If set to `'box'`, the selection will be done in a box. If set to `'circle'`, the selection will be done in a circle. Similar to `gfi.directSelect`, features can be added and removed by selection / unselecting them. The features need to be distinguishable by their properties for the functionality to properly work. Does not work together with `extendedMasterportalapiMarkers` of `@polar/core`. Is disabled by default. |
| renderType | ('iconMenu' \| 'independent')? | Only relevant if `window` is set to `true` for at least one layer. Whether the gfi plugin is rendered independently or as part of the IconMenu. Defaults to 'independent'. |

For details on the `displayComponent` attribute, refer to the [Global Plugin Parameters](../../core/README.md#global-plugin-parameters) section of `@polar/core`.
Expand Down
14 changes: 5 additions & 9 deletions packages/plugins/Gfi/src/store/actions/index.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,16 @@
import debounce from 'lodash.debounce'
import { Feature as GeoJsonFeature } from 'geojson'
import { Style, Fill, Stroke } from 'ol/style'
import { Fill, Stroke, Style } from 'ol/style'
import { GeoJSON } from 'ol/format'
import { Feature } from 'ol'
import { PolarActionTree } from '@polar/lib-custom-types'
import { getFeatureDisplayLayer, clear } from '../../utils/displayFeatureLayer'
import { FeaturesByLayerId, GfiGetters, GfiState } from '../../types'
import { filterFeatures } from '../../utils/filterFeatures'
import { renderFeatures } from '../../utils/renderFeatures'
import { clear, getFeatureDisplayLayer } from '../../utils/displayFeatureLayer'
import { FeaturesByLayerId, GfiGetters, GfiState } from '../../types'
import { debouncedGfiRequest } from './debouncedGfiRequest'
import {
setupCoreListener,
setupMultiSelection,
setupTooltip,
setupZoomListeners,
} from './setup'
import { setupCoreListener, setupTooltip, setupZoomListeners } from './setup'
import { setupMultiSelection } from './setupMultiSelection'

// OK for module action set creation
// eslint-disable-next-line max-lines-per-function
Expand Down
42 changes: 0 additions & 42 deletions packages/plugins/Gfi/src/store/actions/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import getCluster from '@polar/lib-get-cluster'
import { getTooltip, Tooltip } from '@polar/lib-tooltip'
import Overlay from 'ol/Overlay'
import { Feature } from 'ol'
import { DragBox, Draw, Modify } from 'ol/interaction'
import { platformModifierKeyOnly } from 'ol/events/condition'
import { GfiGetters, GfiState } from '../../types'
import { getOriginalFeature } from '../../utils/getOriginalFeature'

Expand All @@ -26,46 +24,6 @@ export function setupCoreListener(
}
}

export function setupMultiSelection({
dispatch,
getters,
rootGetters,
}: PolarActionContext<GfiState, GfiGetters>) {
if (getters.gfiConfiguration.boxSelect) {
const dragBox = new DragBox({ condition: platformModifierKeyOnly })
dragBox.on('boxend', () =>
dispatch('getFeatureInfo', {
coordinateOrExtent: dragBox.getGeometry().getExtent(),
modifierPressed: true,
})
)
rootGetters.map.addInteraction(dragBox)
}
if (getters.gfiConfiguration.directSelect) {
rootGetters.map.on('click', ({ coordinate, originalEvent }) => {
const isDrawing = rootGetters.map
.getInteractions()
.getArray()
.some(
(interaction) =>
// these indicate other interactions are expected now
interaction instanceof Draw ||
interaction instanceof Modify ||
// @ts-expect-error | internal hack to detect it from @polar/plugin-draw
interaction._isDeleteSelect
)
if (!isDrawing) {
dispatch('getFeatureInfo', {
coordinateOrExtent: coordinate,
modifierPressed: navigator.userAgent.includes('Mac')
? originalEvent.metaKey
: originalEvent.ctrlKey,
})
}
})
}
}

export function setupTooltip({
getters: { gfiConfiguration },
rootGetters: { map },
Expand Down
95 changes: 95 additions & 0 deletions packages/plugins/Gfi/src/store/actions/setupMultiSelection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { Map } from 'ol'
import { Modify } from 'ol/interaction'
import Draw, {
createBox,
type Options as DrawOptions,
} from 'ol/interaction/Draw'
import { platformModifierKeyOnly } from 'ol/events/condition'
import { Fill, Stroke, Style } from 'ol/style'
import { PolarActionContext } from '@polar/lib-custom-types'
import { GfiGetters, GfiState } from '../../types'

const isDrawing = (map: Map) =>
map
.getInteractions()
.getArray()
.some(
(interaction) =>
(interaction instanceof Draw &&
// @ts-expect-error | internal hack to detect it from @polar/plugin-gfi and @polar/plugin-draw
(interaction._isMultiSelect || interaction._isDrawPlugin)) ||
interaction instanceof Modify ||
// @ts-expect-error | internal hack to detect it from @polar/plugin-draw
interaction._isDeleteSelect ||
// @ts-expect-error | internal hack to detect it from @polar/plugin-measure
interaction._isMeasureSelect
)

const drawOptions: DrawOptions = {
stopClick: true,
type: 'Circle',
style: new Style({
stroke: new Stroke({ color: 'white', width: 1.5 }),
fill: new Fill({ color: [255, 255, 255, 0.75] }),
}),
freehandCondition: (event) => {
if (event.type === 'pointermove') {
return false
} else if (event.type === 'pointerup') {
return true
}
return platformModifierKeyOnly(event)
},
condition: () => false,
}

export function setupMultiSelection({
dispatch,
getters: {
gfiConfiguration: { boxSelect, directSelect, multiSelect },
},
rootGetters: { map },
}: PolarActionContext<GfiState, GfiGetters>) {
if (boxSelect || multiSelect === 'box' || multiSelect === 'circle') {
if (boxSelect) {
console.warn(
'@polar/plugin-gfi: Configuration parameter "boxSelect" has been deprecated. Please use the new parameter "multiSelect" set to "box" instead.'
)
}
if (multiSelect !== 'circle') {
drawOptions.geometryFunction = createBox()
} else {
delete drawOptions.geometryFunction
}
const draw = new Draw(drawOptions)
draw.on('drawstart', () => {
// @ts-expect-error | internal hack to detect it in @polar/plugin-pins
draw._isMultiSelect = true
})
draw.on('drawend', (e) =>
dispatch('getFeatureInfo', {
// @ts-expect-error | A feature that is drawn has a geometry.
coordinateOrExtent: e.feature.getGeometry().getExtent(),
modifierPressed: true,
}).finally(
() =>
// @ts-expect-error | internal hack to detect it in @polar/plugin-pins
(draw._isMultiSelect = false)
)
)
map.addInteraction(draw)
}
if (directSelect) {
map.on('click', ({ coordinate, originalEvent }) => {
if (!isDrawing(map)) {
dispatch('getFeatureInfo', {
coordinateOrExtent: coordinate,
modifierPressed:
navigator.userAgent.indexOf('Mac') !== -1
? originalEvent.metaKey
: originalEvent.ctrlKey,
})
}
})
}
}
4 changes: 4 additions & 0 deletions packages/plugins/Pins/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# CHANGELOG

# unpublished

- Feature: Recognize the multi-select-`Draw` of `@polar/plugin-gfi`.

## 2.0.0

- Breaking: Upgrade peerDependency `ol` from `^7.1.0` to `^9.2.4`.
Expand Down
5 changes: 3 additions & 2 deletions packages/plugins/Pins/src/store/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,9 @@ export const makeStoreModule = () => {
rootGetters.map.on('singleclick', async ({ coordinate }) => {
const isDrawing = interactions.getArray().some(
(interaction) =>
// these indicate other interactions are expected now
interaction instanceof Draw ||
(interaction instanceof Draw &&
// @ts-expect-error | internal hack to detect it from @polar/plugin-gfi and @polar/plugin-draw
(interaction._isMultiSelect || interaction._isDrawPlugin)) ||
interaction instanceof Modify ||
// @ts-expect-error | internal hack to detect it from @polar/plugin-draw
interaction._isDeleteSelect
Expand Down
1 change: 1 addition & 0 deletions packages/types/custom/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
- Feature: Add new type `MeasureOptions`.
- Feature: Add new type `MeasureMode`.
- Feature: Add optional property `measure` to `DrawStyle`.
- Feature: Add new property `multiSelect` to `GfiConfiguration`.
- Fix: Make `selectionStyle`, `hoverStyle`, `defaultStyle` and `unselectableStyle` optional and edit type for `dispatchOnMapSelect` in interface `ExtendedMasterportalapiMarkers`.

## 1.5.0
Expand Down
8 changes: 2 additions & 6 deletions packages/types/custom/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,7 @@ export interface GfiConfiguration extends PluginOptions {
layers: Record<string, GfiLayerConfiguration>
activeLayerPath?: string
afterLoadFunction?: GfiAfterLoadFunction
boxSelect?: boolean
/**
* If required the stroke and fill of the highlighted feature can be configured.
* Otherwise, a default style is applied.
Expand All @@ -390,7 +391,7 @@ export interface GfiConfiguration extends PluginOptions {
*/
maxFeatures?: number
mode?: 'bboxDot' | 'intersects'
boxSelect?: boolean
multiSelect?: 'box' | 'circle'
renderType?: RenderType
}

Expand All @@ -407,10 +408,6 @@ export interface IconMenuConfiguration extends PluginOptions {
initiallyOpen?: string
}

export interface MeasureConfiguration extends PluginOptions {
color?: Color | ColorLike
textColor?: Color | ColorLike
}
export interface AppearOnClick {
/** Whether the pin should be set with a click on a map. */
show: boolean
Expand Down Expand Up @@ -651,7 +648,6 @@ export interface MapConfig extends MasterportalApiConfig {
geoLocation?: GeoLocationConfiguration
gfi?: GfiConfiguration
iconMenu?: IconMenuConfiguration
measure?: MeasureConfiguration
pins?: PinsConfiguration
reverseGeocoder?: ReverseGeocoderConfiguration
scale?: ScaleConfiguration
Expand Down

0 comments on commit 81cb8b1

Please sign in to comment.