From a25d2317f6baae2287bcae1fc0d5251dd045a935 Mon Sep 17 00:00:00 2001 From: FinnDarge Date: Mon, 30 Sep 2024 07:52:00 +0200 Subject: [PATCH 1/9] add comprehensive getting started guide --- README.md | 14 +- gettingStarted.md | 354 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 355 insertions(+), 13 deletions(-) create mode 100644 gettingStarted.md diff --git a/README.md b/README.md index 22736b3bf..fd33e68e7 100644 --- a/README.md +++ b/README.md @@ -104,19 +104,7 @@ To see our plugins in action, please visit our [documentation page](https://data ## Getting started (for developers) -### Using POLAR - -First check if the generic map client fulfills your need. For this, see the section "Quick start" above. Should your project not use NPM, you may also select a client from our [releases](https://github.com/Dataport/polar/releases). On all client releases, the zipped client is downloadable and attached as an asset to the release. - -Client packages contain example files that illustrate how the client can be used. The `@polar/client-generic` package's use is furthermore illustrated on our [documentation page](https://dataport.github.io/polar/). - -### Developing POLAR - -Clone the repository and run `npm install && npm run snowbox`. This winds up our development client in a minimal test environment as a starting point. - -If you aim to create an additional plugin, you may now create and install it to the snowbox during development. To create a new client, it is advised to create a copy of e.g. `@polar/client-generic` as a base project and adjust it to your needs. - -Clients run anywhere, but their development requires further setup. Right now, clients should be developed in this repository or in a fork of it to make use of its setup. +For a more detailed step-by-step guide, please refer to our comprehensive [Getting Started guide](https://github.com/Dataport/polar/tree/main/gettingStarted.md). ## Stay In Touch diff --git a/gettingStarted.md b/gettingStarted.md new file mode 100644 index 000000000..b77db8846 --- /dev/null +++ b/gettingStarted.md @@ -0,0 +1,354 @@ +# Getting Started (for Developers) + +Welcome to POLAR! This guide will walk you through the initial steps of installation and development with POLAR, with a particular focus on using plugins. + +## 1. Overview + +POLAR is a versatile package that allows you to develop your own map projects using clients and plugins. This guide will show you how to use the Snowbox client and how to integrate plugins into your project. + +## 2. Quick Start with the POLAR Client + +### Step 1: Check Your Requirements +- First check if the generic map client fulfills your need. The @polar/client-generic package's use is furthermore illustrated on our [documentation page](https://dataport.github.io/polar/). +- Should your project not use NPM, you may also select a client from our [releases](https://github.com/Dataport/polar/releases). On all client releases, the zipped client is downloadable and attached as an asset to the release. + +### Step 2: Download and Installation +- Download the desired client from the releases and unzip it. +- The unzipped files contain example projects and documentation that will help you get started. + +## 3. Developing with POLAR + +### Step 1: Clone the Repository and Set Up the Environment +1. Clone the POLAR repository: + ```bash + git clone https://github.com/Dataport/polar.git + ``` +2. Install the dependencies and start the development environment: + ```bash + npm install && npm run snowbox + ``` + > This winds up our development client in a minimal test environment as a starting point. + +## 4. Using Plugins in the Snowbox Client + +### 4.1 Overview of the Plugins + +The Snowbox client supports various plugins that are defined in the `package.json`. The available plugins include: +- `@polar/plugin-address-search` +- `@polar/plugin-draw` +- `@polar/plugin-fullscreen` +- `@polar/plugin-geo-location` +- `@polar/plugin-legend` +- ... and many more. + +These plugins extend the functionality of the Snowbox client and provide features like address search, drawing on the map, fullscreen support, geolocation, etc. + +### 4.2 Adding Plugins + +The `package.json` file of the Snowbox client already lists all available plugins as `devDependencies`. To use a plugin in your project, ensure that it is installed in your development environment. + +To add new plugins or activate existing ones, edit the `package.json` and add the desired plugin if it is not already present. When developing an unpublished plugin, use the path to your local plugin to ensure it is recognized by the client. + +Clients run anywhere, but their development requires further setup. Right now, clients should be developed in this repository or in a fork of it to make use of its setup. + +Then, reinstall the dependencies: +```bash +npm install +``` + +### 4.3 Using Plugins in Code – Detailed Explanation + +The provided `addPlugins.ts` file defines the `addPlugins` function, which integrates multiple plugins into the map application. Let’s break down the code and explain how each plugin is integrated and what configuration options are available. + +#### Overview of the `addPlugins` Function + +The `addPlugins` function expects a `core` object as a parameter. This `core` object represents the main instance of the POLAR application and provides methods to add plugins. + +```typescript +export const addPlugins = (core) => { + setLayout(NineLayout); + // Adding the iconMenu plugin and other plugins + core.addPlugins([ /* List of Plugins */ ]); +} +``` + +#### Configuration of the `iconMenu` Plugin + +The `iconMenu` plugin serves as a central menu for displaying and accessing other plugins. It is configured with a list of menu entries, each embedding another plugin. + +```typescript +const iconMenu = IconMenu({ + menus: [ + { + plugin: LayerChooser({}), // Layer chooser plugin + icon: 'fa-layer-group', // Icon for the plugin in the menu + id: 'layerChooser', + }, + { + plugin: Draw({}), // Drawing plugin + icon: 'fa-pencil', // Icon for the plugin + id: 'draw', + }, + { + plugin: Zoom({ renderType: 'iconMenu' }), // Zoom plugin + id: 'zoom', + }, + { + plugin: Fullscreen({ renderType: 'iconMenu' }), // Fullscreen plugin + id: 'fullscreen', + }, + { + plugin: GeoLocation({ renderType: 'iconMenu' }), // Geolocation plugin + id: 'geoLocation', + }, + { + plugin: Attributions({ + renderType: 'iconMenu', + listenToChanges: [ + 'plugin/zoom/zoomLevel', + 'plugin/layerChooser/activeBackgroundId', + 'plugin/layerChooser/activeMaskIds', + ], + }), + icon: 'fa-regular fa-copyright', + id: 'attributions', + }, + ], + displayComponent: true, // The IconMenu will be displayed on the map + initiallyOpen: 'layerChooser', // Specifies which menu is open by default + layoutTag: NineLayoutTag.TOP_RIGHT, // Position of the iconMenu on the map +}); +``` + +#### Adding Plugins to the Map + +The `core.addPlugins` method is used to add the list of plugins to the map application. Here’s a detailed look at some of these plugins: + +```typescript +core.addPlugins([ + AddressSearch( + merge({}, defaultOptions, { + layoutTag: NineLayoutTag.TOP_LEFT, // Position of the plugin on the map + addLoading: 'plugin/loadingIndicator/addLoadingKey', + removeLoading: 'plugin/loadingIndicator/removeLoadingKey', + }) + ), + iconMenu, + Export( + merge({}, defaultOptions, { + layoutTag: NineLayoutTag.BOTTOM_LEFT, // Position of the export plugin + }) + ), + LoadingIndicator( + merge({}, defaultOptions, { + layoutTag: NineLayoutTag.MIDDLE_MIDDLE, // Position of the loading indicator + }) + ), + Legend({ + displayComponent: true, + layoutTag: NineLayoutTag.BOTTOM_RIGHT, // Position of the legend + }), + Scale( + merge({}, defaultOptions, { + layoutTag: NineLayoutTag.BOTTOM_RIGHT, // Position of the scale + }) + ), + Toast( + merge({}, defaultOptions, { + layoutTag: NineLayoutTag.BOTTOM_MIDDLE, // Position of the toast messages + }) + ), + Pins({ + appearOnClick: { show: true, atZoomLevel: 6 }, // Configuration for the pins on the map + coordinateSource: 'plugin/addressSearch/chosenAddress', + toastAction: 'plugin/toast/addToast', + }), + Gfi( + merge({}, defaultOptions, { + layoutTag: NineLayoutTag.MIDDLE_LEFT, // Position of the GFI plugin + }) + ), + ReverseGeocoder({ + url: 'https://geodienste.hamburg.de/HH_WPS', + addLoading: 'plugin/loadingIndicator/addLoadingKey', + removeLoading: 'plugin/loadingIndicator/removeLoadingKey', + coordinateSource: 'plugin/pins/transformedCoordinate', + addressTarget: 'plugin/addressSearch/selectResult', + zoomTo: 7, + }), +]); +``` + +## 5. Integrating Plugins into mapConfiguration.ts + +The `mapConfiguration.ts` file serves as the central configuration point for your map application, providing a comprehensive structure for defining how your map should look and behave. This file, in combination with `addPlugins.ts` and `polar-client.ts`, integrates and manages various plugins within the POLAR framework. + +### 5.1 Basic Structure of mapConfiguration.ts + +Initially, the `mapConfiguration.ts` file starts with foundational settings for the map, such as projection systems, language settings, and the theme configuration using Vuetify. It also sets up layers, address search, drawing options, and other components that influence how the map and its plugins function. Here's an excerpt of the core configuration: + +``` Typescript +export const mapConfiguration = { + language: 'en', + epsg: 'EPSG:25832', + locales: language, + vuetify: { + theme: { + themes: { + light: { + primary: somewhatBlue, + primaryContrast: notQuiteWhite, + secondary: eigengrau, + secondaryContrast: notQuiteWhite, + }, + }, + }, + }, + // Other map configurations like namedProjections, addressSearch, layers, etc. +}; +``` + +### 5.2 Adding Plugins Using addPlugins.ts +The `addPlugins.ts` file is used to define and manage the integration of plugins into your map instance. The addPlugins function accepts the `core` instance and sets up the layout for the map using `setLayout(NineLayout)`. Plugins such as `AddressSearch`, `GeoLocation`, `Legend`, and `Scale` are imported and added to the map with specific configurations. +Structure of `addPlugins.ts`: + +```typescript +import merge from 'lodash.merge'; +import { setLayout, NineLayout, NineLayoutTag } from '@polar/core'; +// Import all the necessary plugins +import AddressSearch from '@polar/plugin-address-search'; +import Attributions from '@polar/plugin-attributions'; +import Draw from '@polar/plugin-draw'; +import Export from '@polar/plugin-export'; +import Fullscreen from '@polar/plugin-fullscreen'; +import GeoLocation from '@polar/plugin-geo-location'; +import IconMenu from '@polar/plugin-icon-menu'; +import LayerChooser from '@polar/plugin-layer-chooser'; +import Legend from '@polar/plugin-legend'; +import Scale from '@polar/plugin-scale'; +import Toast from '@polar/plugin-toast'; +import Zoom from '@polar/plugin-zoom'; + +// Default configuration for plugins +const defaultOptions = { + displayComponent: true, + layoutTag: NineLayoutTag.TOP_LEFT, +}; + +export const addPlugins = (core) => { + setLayout(NineLayout); + + // Define the iconMenu that groups several plugins + const iconMenu = IconMenu({ + menus: [ + { plugin: LayerChooser({}), icon: 'fa-layer-group', id: 'layerChooser' }, + { plugin: Draw({}), icon: 'fa-pencil', id: 'draw' }, + { plugin: Zoom({ renderType: 'iconMenu' }), id: 'zoom' }, + { plugin: Fullscreen({ renderType: 'iconMenu' }), id: 'fullscreen' }, + { plugin: GeoLocation({ renderType: 'iconMenu' }), id: 'geoLocation' }, + { + plugin: Attributions({ renderType: 'iconMenu', listenToChanges: ['plugin/zoom/zoomLevel', 'plugin/layerChooser/activeBackgroundId'] }), + icon: 'fa-regular fa-copyright', + id: 'attributions', + }, + ], + layoutTag: NineLayoutTag.TOP_RIGHT, + displayComponent: true, + initiallyOpen: 'layerChooser', + }); + + // Adding all plugins to the map instance + core.addPlugins([ + AddressSearch(merge({}, defaultOptions, { layoutTag: NineLayoutTag.TOP_LEFT })), + iconMenu, + Export(merge({}, defaultOptions, { layoutTag: NineLayoutTag.BOTTOM_LEFT })), + Legend({ displayComponent: true, layoutTag: NineLayoutTag.BOTTOM_RIGHT }), + Scale(merge({}, defaultOptions, { layoutTag: NineLayoutTag.BOTTOM_RIGHT })), + Toast(merge({}, defaultOptions, { layoutTag: NineLayoutTag.BOTTOM_MIDDLE })), + ]); +}; +``` + +### 5.3 Integrating the Plugins into the Application via polar-client.ts +The `polar-client.ts` file ties everything together. It initializes the POLAR `core` instance, invokes the `addPlugins` function, and sets up the map with the defined configuration from `mapConfiguration.ts`. + +```Typescript +import polarCore from '@polar/core'; +import { addPlugins } from './addPlugins'; +import { mapConfiguration } from './mapConfiguration'; + +addPlugins(polarCore); + +const createMap = (layerConf) => { + polarCore.createMap({ + containerId: 'polarstern', + mapConfiguration: { + ...mapConfiguration, + layerConf, + }, + }).then(addStoreSubscriptions(/* subscription logic */)); +}; + +// Initializes the layer list and creates the map instance +polarCore.rawLayerList.initializeLayerList('https://geodienste.hamburg.de/services-internet.json', createMap); +``` + +### 5.4 Explanation of the Plugin Integration Process +The process is as follows: + + - `mapConfiguration.ts` defines the `core` settings and appearance of the map. + - `addPlugins.ts` handles the instantiation and configuration of individual plugins, utilizing `addPlugins(core)` to add them to the `core` instance. + - `polar-client.ts` combines these configurations to initialize and render the map, ensuring the plugins are integrated and functioning within the defined layout. + +### 5.5 Best Practices for Integrating Plugins +- Centralized Configuration: Manage the primary map settings in mapConfiguration.ts for easy maintenance. +- Modular Plugin Management: Use addPlugins.ts to separate plugin configurations, allowing for easier updates and modifications. +- Testing: Regularly test your integrated plugins in the Snowbox environment to ensure compatibility and functionality. + +## 6. Using the Layout – The `NineLayout` System + +The `NineLayout` is a flexible layout system that allows plugins to be placed in a 3x3 grid (nine sections) on the map. + +### 6.1 How `NineLayout.vue` Works + +`NineLayout.vue` is the Vue component that represents the layout of the map and places plugins according to their configured positions. + +```vue + +``` + +- The `wrapper` div is the container element for the entire layout. +- Each plugin is positioned within the `plugin-vessel` component based on its `layoutTag`. + +### 6.2 Summary of the Layout System + +The `NineLayout` system provides a structured and flexible way to display plugins in one of nine defined positions within your map application. By using the `NineLayoutTag` enum and configuring it in `NineLayout.vue`, you can precisely control the placement of your plugins and customize them to fit the needs of your map application. + +## Conclusion + +Congratulations! You have now completed the Getting Started guide for POLAR. You should now have a solid understanding of how to set up and work with the POLAR package, integrate and configure plugins, and customize your map application to fit your project's needs. + +### Next Steps + +- Explore More Plugins: Experiment with different configurations. +- Develop Custom Plugins: Extend POLAR's capabilities. +- Contribute: Help improve the POLAR project. + +Enjoy Building with POLAR! 🎉 \ No newline at end of file From 6ff71add19e4f8a61284b6f97e4fd6b35f932382 Mon Sep 17 00:00:00 2001 From: Dennis Sen Date: Tue, 8 Oct 2024 16:29:26 +0200 Subject: [PATCH 2/9] implement unselectable features --- packages/clients/snowbox/src/language.ts | 10 ++ .../clients/snowbox/src/mapConfiguration.ts | 92 +++++++++++++++++++ packages/clients/snowbox/src/polar-client.ts | 6 +- packages/core/README.md | 8 +- packages/core/src/utils/markers/index.ts | 5 + .../index.ts | 33 ++++++- packages/core/src/vuePlugins/vuex.ts | 6 ++ packages/plugins/Gfi/CHANGELOG.md | 1 + packages/plugins/Gfi/README.md | 4 +- .../src/store/actions/debouncedGfiRequest.ts | 14 +-- packages/types/custom/CHANGELOG.md | 3 + packages/types/custom/core.ts | 11 +++ 12 files changed, 180 insertions(+), 13 deletions(-) diff --git a/packages/clients/snowbox/src/language.ts b/packages/clients/snowbox/src/language.ts index 4a73f9327..eb71670fc 100644 --- a/packages/clients/snowbox/src/language.ts +++ b/packages/clients/snowbox/src/language.ts @@ -12,6 +12,9 @@ const language: LanguageOption[] = [ 'Strecken U-Bahn © Freie und Hansestadt Hamburg, Behörde für Wirtschaft, Verkehr und Innovation', rapid: 'Strecken S-Bahn © Freie und Hansestadt Hamburg, Behörde für Wirtschaft, Verkehr und Innovation', + reports: 'Meldungen durch Bürger', + ausgleichsflaechen: + 'Ausgleichsflächen © Freie und Hansestadt Hamburg, Behörde für Umwelt und Energie', hamburgBorder: 'Landesgrenze Hamburg © Freie und Hansestadt Hamburg', }, layers: { @@ -19,6 +22,8 @@ const language: LanguageOption[] = [ basemapGrey: 'Basemap.de (Grau)', underground: 'U-Bahn', rapid: 'S-Bahn', + reports: 'Anliegen (MML)', + ausgleichsflaechen: 'Ausgleichsflächen', hamburgBorder: 'Landesgrenze Hamburg', }, }, @@ -43,6 +48,9 @@ const language: LanguageOption[] = [ 'Railway Lines U-Bahn © Freie und Hansestadt Hamburg, Behörde für Wirtschaft, Verkehr und Innovation', rapid: 'Railway Lines S-Bahn © Freie und Hansestadt Hamburg, Behörde für Wirtschaft, Verkehr und Innovation', + reports: 'Reports by citizens', + ausgleichsflaechen: + 'Compensation area © Freie und Hansestadt Hamburg, Behörde für Umwelt und Energie', hamburgBorder: 'City border Hamburg © Freie und Hansestadt Hamburg', }, layers: { @@ -50,6 +58,8 @@ const language: LanguageOption[] = [ basemapGrey: 'Basemap.de (Grey)', underground: 'Underground railway (U-Bahn)', rapid: 'City rapid railway (S-Bahn)', + reports: 'Reports (MML)', + ausgleichsflaechen: 'Compensation area', hamburgBorder: 'City border Hamburg', }, }, diff --git a/packages/clients/snowbox/src/mapConfiguration.ts b/packages/clients/snowbox/src/mapConfiguration.ts index 39e411cf9..012491f45 100644 --- a/packages/clients/snowbox/src/mapConfiguration.ts +++ b/packages/clients/snowbox/src/mapConfiguration.ts @@ -1,3 +1,9 @@ +import { Feature as GeoJsonFeature } from 'geojson' +import { Feature as OlFeature } from 'ol' +import { + ExtendedMasterportalapiMarkersIsSelectableFunction, + GfiIsSelectableFunction, +} from '@polar/lib-custom-types' import language from './language' const eigengrau = '#16161d' @@ -8,9 +14,40 @@ const basemapId = '23420' const basemapGreyId = '23421' const sBahn = '23050' const uBahn = '23053' +export const reports = '6059' +const ausgleichsflaechen = '1454' const hamburgBorder = '6074' +const isAusgleichsflaecheActive = (feature: GeoJsonFeature) => + new Date( + Date.parse(feature.properties?.vorhaben_zulassung_am.split('.')[2]) + ).getFullYear() >= 2000 + +// arbitrary condition for testing +const isEvenId = (mmlid: string) => Number(mmlid.slice(-1)) % 2 === 0 + +const isReportActive: GfiIsSelectableFunction = (feature) => + feature.properties?.features + ? // client is in cluster mode + feature.properties?.features.reduce( + (accumulator, current) => + // NOTE: that's how ol/GeoJSON packs clustered features as GeoJSON + isEvenId(current.values_.mmlid) || accumulator, + false + ) + : isEvenId(feature.properties?.mmlid) + +const isReportSelectable: ExtendedMasterportalapiMarkersIsSelectableFunction = ( + feature +) => + feature + .get('features') + .reduce( + (accumulator, current) => isEvenId(current.get('mmlid')) || accumulator, + false + ) + export const mapConfiguration = { language: 'en', locales: language, @@ -26,6 +63,27 @@ export const mapConfiguration = { }, }, }, + extendedMasterportalapiMarkers: { + layers: [reports], + defaultStyle: { + stroke: '#FFFFFF', + fill: '#005CA9', + }, + hoverStyle: { + stroke: '#46688E', + fill: '#8BA1B8', + }, + selectionStyle: { + stroke: '#FFFFFF', + fill: '#E10019', + }, + unselectableStyle: { + stroke: '#FFFFFF', + fill: '#333333', + }, + isSelectable: isReportSelectable, + clusterClickZoom: true, + }, addressSearch: { searchMethods: [ { @@ -61,6 +119,14 @@ export const mapConfiguration = { id: sBahn, title: 'snowbox.attributions.rapid', }, + { + id: reports, + title: 'snowbox.attributions.reports', + }, + { + id: ausgleichsflaechen, + title: 'snowbox.attributions.ausgleichsflaechen', + }, ], }, draw: { @@ -93,6 +159,8 @@ export const mapConfiguration = { zoomLevel: 9, }, gfi: { + mode: 'bboxDot', + activeLayerPath: 'plugin/layerChooser/activeMaskIds', layers: { [uBahn]: { geometry: true, @@ -110,10 +178,24 @@ export const mapConfiguration = { art: 'Art', }, }, + [reports]: { + geometry: false, + window: true, + // only one of these will be displayed, depending on whether (extended markers && clusters) are on + properties: ['_gfiLayerId', 'mmlid'], + isSelectable: isReportActive, + }, + [ausgleichsflaechen]: { + geometry: true, + window: true, + properties: ['vorhaben', 'vorhaben_zulassung_am'], + isSelectable: isAusgleichsflaecheActive, + }, }, coordinateSources: [ 'plugin/pins/transformedCoordinate', 'plugin/pins/coordinatesAfterDrag', + 'selectedCoordinates', ], customHighlightStyle: { stroke: { @@ -148,6 +230,16 @@ export const mapConfiguration = { type: 'mask', name: 'snowbox.layers.rapid', }, + { + id: reports, + type: 'mask', + name: 'snowbox.layers.reports', + }, + { + id: ausgleichsflaechen, + type: 'mask', + name: 'snowbox.layers.ausgleichsflaechen', + }, { id: hamburgBorder, visibility: true, diff --git a/packages/clients/snowbox/src/polar-client.ts b/packages/clients/snowbox/src/polar-client.ts index 4f55d6c1a..4a539ec87 100644 --- a/packages/clients/snowbox/src/polar-client.ts +++ b/packages/clients/snowbox/src/polar-client.ts @@ -1,8 +1,10 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ import polarCore from '@polar/core' import { changeLanguage } from 'i18next' +// NOTE bad pattern, but probably fine for a test client +import { enableClustering } from '../../meldemichel/src/utils/enableClustering' import { addPlugins } from './addPlugins' -import { mapConfiguration } from './mapConfiguration' +import { mapConfiguration, reports } from './mapConfiguration' addPlugins(polarCore) @@ -12,7 +14,7 @@ const createMap = (layerConf) => { containerId: 'polarstern', mapConfiguration: { ...mapConfiguration, - layerConf, + layerConf: (enableClustering(layerConf, reports), layerConf), }, }) .then((map) => { diff --git a/packages/core/README.md b/packages/core/README.md index 33b0c14c4..454c7e086 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -178,8 +178,9 @@ To figure out the name of the locales to override, inspect the matching plugin i | defaultStyle | MarkerStyle? | Used as the default marker style. The default fill color for these markers is `'#005CA9'`. | | dispatchOnMapSelect | string[]? | If set, the parameters will be spread to dispatchment on map selection. `['target', 'value']` will `dispatch(...['target', 'value'])`. This can be used to open the iconMenu's GFI with `['plugin/iconMenu/openMenuById', 'gfi']`, should the IconMenu exist and the gfi plugin be in it with this id. | | hoverStyle | MarkerStyle? | Used as map marker style for hovered features. The default fill color for these markers is `'#7B1045'`. | +| isSelectable | ((feature: GeoJsonFeature) => boolean)? | If undefined, all features are selectable. If defined, this can be used to sort out features to be unselectable, and such features will be styled different and won't react on click. | | selectionStyle | MarkerStyle? | Used as map marker style for selected features. The default fill color for these markers is `'#679100'`. | - +| unselectableStyle | MarkerStyle? | Used as a map marker style for unselectable features. Features are unselectable if a given `isSelectable` method returns falsy for a feature. The default fill color for these markers is `'#333333'`. | Example configuration: ```js @@ -197,6 +198,11 @@ extendedMasterportalapiMarkers: { stroke: '#FFFFFF', fill: '#E10019', }, + unselectableStyle: { + stroke: '#FFFFFF', + fill: '#333333' + }, + isSelectable: (feature: Feature) => feature.get('indicator') clusterClickZoom: true, dispatchOnMapSelect: ['plugin/iconMenu/openMenuById', 'gfi'], }, diff --git a/packages/core/src/utils/markers/index.ts b/packages/core/src/utils/markers/index.ts index c40908824..79ff0cf54 100644 --- a/packages/core/src/utils/markers/index.ts +++ b/packages/core/src/utils/markers/index.ts @@ -15,6 +15,7 @@ const defaultStrokeWidth = '2' const defaultFill = '#005CA9' const defaultHoverFill = '#7B1045' const defaultSelectionFill = '#679100' +const defaultUnselectableFill = '#333333' const prefix = 'data:image/svg+xml,' @@ -124,3 +125,7 @@ export const getHoveredStyle = memoizeStyle(getStyleFunction(defaultHoverFill)) export const getSelectedStyle = memoizeStyle( getStyleFunction(defaultSelectionFill) ) + +export const getUnselectableStyle = memoizeStyle( + getStyleFunction(defaultUnselectableFill) +) diff --git a/packages/core/src/vuePlugins/actions/useExtendedMasterportalapiMarkers/index.ts b/packages/core/src/vuePlugins/actions/useExtendedMasterportalapiMarkers/index.ts index 5249c58ea..12bf015bd 100644 --- a/packages/core/src/vuePlugins/actions/useExtendedMasterportalapiMarkers/index.ts +++ b/packages/core/src/vuePlugins/actions/useExtendedMasterportalapiMarkers/index.ts @@ -2,6 +2,7 @@ import { Feature, MapBrowserEvent } from 'ol' import { CoreGetters, CoreState, + ExtendedMasterportalapiMarkersIsSelectableFunction, MarkerStyle, PolarActionContext, PolarStore, @@ -11,7 +12,11 @@ import { isVisible } from '@polar/lib-invisible-style' import VectorLayer from 'ol/layer/Vector' import BaseLayer from 'ol/layer/Base' import getCluster from '@polar/lib-get-cluster' -import { getHoveredStyle, getSelectedStyle } from '../../../utils/markers' +import { + getHoveredStyle, + getSelectedStyle, + getUnselectableStyle, +} from '../../../utils/markers' import { resolveClusterClick } from '../../../utils/resolveClusterClick' import { setLayerId } from './setLayerId' @@ -70,12 +75,16 @@ export function useExtendedMasterportalapiMarkers( { hoverStyle = {}, selectionStyle = {}, + unselectableStyle = {}, + isSelectable = () => true, layers, clusterClickZoom = false, dispatchOnMapSelect, }: { hoverStyle?: MarkerStyle selectionStyle?: MarkerStyle + unselectableStyle?: MarkerStyle + isSelectable?: ExtendedMasterportalapiMarkersIsSelectableFunction layers: string[] clusterClickZoom: boolean dispatchOnMapSelect?: string[] @@ -101,6 +110,20 @@ export function useExtendedMasterportalapiMarkers( (feature: Feature) => isVisible(feature) ? feature.getGeometry() : null } + const originalStyleFunction = (layer as VectorLayer).getStyle() + ;(layer as VectorLayer).setStyle((feature) => { + if ( + typeof isSelectable === 'undefined' || + isSelectable(feature as Feature) + ) { + // @ts-expect-error | always is a function due to masterportalapi design + return originalStyleFunction(feature) + } + return getUnselectableStyle( + unselectableStyle, + feature.get('features').length > 1 + ) + }) }) // // // STORE EVENT HANDLING @@ -146,7 +169,7 @@ export function useExtendedMasterportalapiMarkers( hovered = null commit('setHovered', hovered) } - if (!feature) { + if (!feature || !isSelectable(feature)) { return } const isMultiFeature = feature.get('features')?.length > 1 @@ -164,7 +187,11 @@ export function useExtendedMasterportalapiMarkers( dispatch('updateSelection', { feature: selected }) } const feature = map.getFeaturesAtPixel(event.pixel, { layerFilter })[0] - if (!feature || feature instanceof RenderFeature) { + if ( + !feature || + feature instanceof RenderFeature || + !isSelectable(feature) + ) { return } const isMultiFeature = feature.get('features')?.length > 1 diff --git a/packages/core/src/vuePlugins/vuex.ts b/packages/core/src/vuePlugins/vuex.ts index d16305861..15b9eec24 100644 --- a/packages/core/src/vuePlugins/vuex.ts +++ b/packages/core/src/vuePlugins/vuex.ts @@ -141,6 +141,12 @@ export const makeStore = () => { noop(state.selected) return selected }, + selectedCoordinates: (state) => { + noop(state.selected) + return selected === null + ? null + : (selected.getGeometry() as Point).getCoordinates() + }, // hack: deliver components (outside vuex) based on counter; see NOTE above components: (state) => { noop(state.components) diff --git a/packages/plugins/Gfi/CHANGELOG.md b/packages/plugins/Gfi/CHANGELOG.md index 352411eff..125360e91 100644 --- a/packages/plugins/Gfi/CHANGELOG.md +++ b/packages/plugins/Gfi/CHANGELOG.md @@ -3,6 +3,7 @@ ## unpublished - Breaking: Upgrade `@masterportal/masterportalapi` from `2.8.0` to `2.40.0` and subsequently `ol` from `^7.1.0` to `^9.2.4`. +- Feature: Add new configuration parameter `isSelectable` to that can be used to filter features to be unselectable. - Fix: Adjust documentation to properly describe optionality of configuration parameters. - Fix: Add missing configuration parameters `featureList` and `maxFeatures` to the general documentation and `filterBy` and `format` to `gfi.gfiLayerConfiguration` - Refactor: Replace redundant prop-forwarding with `getters`. diff --git a/packages/plugins/Gfi/README.md b/packages/plugins/Gfi/README.md index ad492540e..ee3bb4405 100644 --- a/packages/plugins/Gfi/README.md +++ b/packages/plugins/Gfi/README.md @@ -83,6 +83,7 @@ function afterLoadFunction(featuresByLayerId: Record): | exportProperty | string? | Property of the features of a service having an url usable to trigger a download of features as a document. | | geometry | boolean? | If true, feature geometry will be highlighted within the map. Defaults to `false`. | | geometryName | string? | Name of the geometry property if not the default field. | +| isSelectable | ((feature: GeoJsonFeature) => boolean)? | A function can be defined to allow filtering features to be either selectable (return `true`) or not. Unselectable features will be filtered out by the GFI plugin and have neither GFI display nor store presence, but may be visible in the map nonetheless, depending on your other configuration. Please also mind that usage in combination with `extendedMasterportalapiMarkers` requires further configuration of that feature for smooth UX; see the respective documentation of `@polar/core`. | | properties | Record/string[]? | In case `window` is `true`, this will be used to determine which contents to show. In case of an array, keys are used to select properties. In case of an object, keys are used to select properties, but will be titles as their respective values. Displays all properties by default. | | showTooltip | ((feature: Feature) => [string, string][])? | If given, a tooltip will be shown with the values calculated for the feature. The first string is the HTML tag to render, the second its contents; contants may be locale keys. For more information regarding the strings, see the documentation of the `@polar/lib-tooltip` package. Defaults to `undefined`. Please mind that tooltips will only be shown if a mouse is used or the hovering device could not be detected. Touch and pen interactions do not open tooltips since they will open the GFI window, rendering the gatherable information redundant. | | window | boolean? | If true, properties will be shown in the map client. Defaults to `false`. | @@ -106,7 +107,8 @@ layers: { ['div', `Feature ID: ${feature.properties.id}`], ['span', `Coordinates: ${feature.geometry.coordinates.join(', ')}`] ]; - }; + }, + isSelectable: (feature: Feature): boolean => Boolean(Math.random() < 0.5) }, } ``` diff --git a/packages/plugins/Gfi/src/store/actions/debouncedGfiRequest.ts b/packages/plugins/Gfi/src/store/actions/debouncedGfiRequest.ts index 06af3d1b0..10e125a83 100644 --- a/packages/plugins/Gfi/src/store/actions/debouncedGfiRequest.ts +++ b/packages/plugins/Gfi/src/store/actions/debouncedGfiRequest.ts @@ -18,7 +18,7 @@ import { requestGfi } from '../../utils/requestGfi' import sortFeatures from '../../utils/sortFeatures' import { GfiGetters, GfiState } from '../../types' -const mapFeaturesToLayerIds = ( +const filterAndMapFeaturesToLayerIds = ( layerKeys: string[], gfiConfiguration: GfiConfiguration, features: (symbol | GeoJsonFeature[])[], @@ -30,10 +30,12 @@ const mapFeaturesToLayerIds = ( (accumulator, key, index) => ({ ...accumulator, [key]: Array.isArray(features[index]) - ? (features[index] as []).slice( - 0, - gfiConfiguration.layers[key].maxFeatures || generalMaxFeatures - ) + ? (features[index] as []) + .filter(gfiConfiguration.layers[key].isSelectable || (() => true)) + .slice( + 0, + gfiConfiguration.layers[key].maxFeatures || generalMaxFeatures + ) : features[index], }), {} @@ -135,7 +137,7 @@ const gfiRequest = : errorSymbol(result.reason.message) ) const srsName: string = map.getView().getProjection().getCode() - let featuresByLayerId = mapFeaturesToLayerIds( + let featuresByLayerId = filterAndMapFeaturesToLayerIds( layerKeys, // NOTE if there was no configuration, we would not be here // eslint-disable-next-line @typescript-eslint/no-non-null-assertion diff --git a/packages/types/custom/CHANGELOG.md b/packages/types/custom/CHANGELOG.md index 3df669eb2..85d6bccfd 100644 --- a/packages/types/custom/CHANGELOG.md +++ b/packages/types/custom/CHANGELOG.md @@ -2,6 +2,9 @@ ## unpublished +- Feature: Add `selectedCoordinate` to core store getters; it returns `null` or the `selected` feature's point coordinates. +- Feature: Add new parameters `unselectableStyle` and `isSelectable` with new type `ExtendedMasterportalapiMarkersIsSelectableFunction` to interface `ExtendedMasterportalapiMarkers`. +- Feature: Add new parameter `isSelectable` with new type `GfiIsSelectableFunction` to interface `GfiLayerConfiguration`. - Feature: Add new parameter `enableOptions` to interface `DrawConfiguration`. - Feature: Add new interface `ScaleConfiguration` and new property `scale` to `mapConfiguration`. - Feature: Add `afterResultComponent` to `AddressSearchConfiguration` for custom search result suffixes. diff --git a/packages/types/custom/core.ts b/packages/types/custom/core.ts index 1b6e05888..748be4365 100644 --- a/packages/types/custom/core.ts +++ b/packages/types/custom/core.ts @@ -20,6 +20,7 @@ import { } from 'vuex' import { Feature as GeoJsonFeature, FeatureCollection } from 'geojson' import { VueConstructor, WatchOptions } from 'vue' +import { Coordinate } from 'ol/coordinate' /** * @@ -250,6 +251,7 @@ export interface GfiLayerConfiguration { geometry?: boolean // name of field to use for geometry, if not default field geometryName?: string + isSelectable?: GfiIsSelectableFunction maxFeatures?: number /** * If window is true, the properties are either @@ -321,6 +323,12 @@ export interface FullscreenConfiguration extends PluginOptions { targetContainerId?: string } +export type ExtendedMasterportalapiMarkersIsSelectableFunction = ( + feature: Feature +) => boolean + +export type GfiIsSelectableFunction = (feature: GeoJsonFeature) => boolean + /** configurable function to gather additional info */ export type GfiAfterLoadFunction = ( featureInformation: Record, @@ -579,8 +587,10 @@ export interface ExtendedMasterportalapiMarkers { defaultStyle: MarkerStyle hoverStyle: MarkerStyle selectionStyle: MarkerStyle + unselectableStyle: MarkerStyle clusterClickZoom?: boolean dispatchOnMapSelect?: string + isSelectable?: ExtendedMasterportalapiMarkersIsSelectableFunction } export interface MasterportalApiConfig { @@ -705,6 +715,7 @@ export interface CoreGetters moveHandle: MoveHandleProperties moveHandleActionButton: MoveHandleActionButton selected: Feature | null + selectedCoordinate: Coordinate | null // regular getters deviceIsHorizontal: boolean From df58a963066fb810d7c3b142459039db4b02101c Mon Sep 17 00:00:00 2001 From: Dennis Sen Date: Tue, 8 Oct 2024 16:48:25 +0200 Subject: [PATCH 3/9] remove unused import --- packages/clients/snowbox/src/mapConfiguration.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/clients/snowbox/src/mapConfiguration.ts b/packages/clients/snowbox/src/mapConfiguration.ts index 012491f45..4fe993a71 100644 --- a/packages/clients/snowbox/src/mapConfiguration.ts +++ b/packages/clients/snowbox/src/mapConfiguration.ts @@ -1,5 +1,4 @@ import { Feature as GeoJsonFeature } from 'geojson' -import { Feature as OlFeature } from 'ol' import { ExtendedMasterportalapiMarkersIsSelectableFunction, GfiIsSelectableFunction, From c02f666c07a32093ddd8d840e7a4f8b7f403760d Mon Sep 17 00:00:00 2001 From: Dennis Sen <108349707+warm-coolguy@users.noreply.github.com> Date: Fri, 11 Oct 2024 06:35:00 +0200 Subject: [PATCH 4/9] Update packages/plugins/Gfi/CHANGELOG.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit apply suggestion "remove misplaced word" Co-authored-by: Pascal Röhling <73653210+dopenguin@users.noreply.github.com> --- packages/plugins/Gfi/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/plugins/Gfi/CHANGELOG.md b/packages/plugins/Gfi/CHANGELOG.md index 125360e91..8feec2fb0 100644 --- a/packages/plugins/Gfi/CHANGELOG.md +++ b/packages/plugins/Gfi/CHANGELOG.md @@ -3,7 +3,7 @@ ## unpublished - Breaking: Upgrade `@masterportal/masterportalapi` from `2.8.0` to `2.40.0` and subsequently `ol` from `^7.1.0` to `^9.2.4`. -- Feature: Add new configuration parameter `isSelectable` to that can be used to filter features to be unselectable. +- Feature: Add new configuration parameter `isSelectable` that can be used to filter features to be unselectable. - Fix: Adjust documentation to properly describe optionality of configuration parameters. - Fix: Add missing configuration parameters `featureList` and `maxFeatures` to the general documentation and `filterBy` and `format` to `gfi.gfiLayerConfiguration` - Refactor: Replace redundant prop-forwarding with `getters`. From 041ab1827791be04535b0aec498e5ab7969a89dd Mon Sep 17 00:00:00 2001 From: Dennis Sen Date: Fri, 11 Oct 2024 06:43:43 +0200 Subject: [PATCH 5/9] add missing changelog --- packages/core/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index 3da4ffb10..4efa28544 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -4,6 +4,7 @@ - Breaking: Upgrade `@masterportal/masterportalapi` from `2.8.0` to `2.40.0` and subsequently `ol` from `^7.1.0` to `^9.2.4`. - Breaking: Remove support for marking client CSS via `data-polar="true"`. Please use the configuration parameter `stylePath` instead. +- Feature: The `extendedMasterportalapiFeatures` feature has been extended by a `isSelectable` function and `unselectableStyle` to style markers accordingly. - Feature: Add new state parameter `mapHasDimensions` to let plugins have a "hook" to react on when the map is ready. - Feature: Add `deviceIsHorizontal` as a getter to have a more central place to check if the device is in landscape mode. - Feature: Add clearer documentation regarding `@masterportal/masterportalapi` related configuration parameters including examples. From 1c73c4c7e34f047d58166b32e78760e063ce4354 Mon Sep 17 00:00:00 2001 From: FinnDarge Date: Fri, 11 Oct 2024 10:23:17 +0200 Subject: [PATCH 6/9] edit getting started sections --- README.md | 2 +- gettingStarted.md | 421 ++++++++++++++++++++-------------------------- 2 files changed, 185 insertions(+), 238 deletions(-) diff --git a/README.md b/README.md index 3ccdb9169..5ba9bfac3 100644 --- a/README.md +++ b/README.md @@ -104,7 +104,7 @@ To see our plugins in action, please visit our [documentation page](https://data ## Getting started (for developers) -For a more detailed step-by-step guide, please refer to our comprehensive [Getting Started guide](https://github.com/Dataport/polar/tree/main/gettingStarted.md). +For a detailed step-by-step guide, please refer to our comprehensive [Getting Started guide](https://github.com/Dataport/polar/tree/main/gettingStarted.md). ## Stay In Touch diff --git a/gettingStarted.md b/gettingStarted.md index b77db8846..329323d2b 100644 --- a/gettingStarted.md +++ b/gettingStarted.md @@ -4,21 +4,41 @@ Welcome to POLAR! This guide will walk you through the initial steps of installa ## 1. Overview -POLAR is a versatile package that allows you to develop your own map projects using clients and plugins. This guide will show you how to use the Snowbox client and how to integrate plugins into your project. +POLAR is a versatile package library that allows you to develop your own map projects using clients and plugins. This guide will show you how to use the Snowbox client and how to integrate plugins into your project. ## 2. Quick Start with the POLAR Client -### Step 1: Check Your Requirements -- First check if the generic map client fulfills your need. The @polar/client-generic package's use is furthermore illustrated on our [documentation page](https://dataport.github.io/polar/). -- Should your project not use NPM, you may also select a client from our [releases](https://github.com/Dataport/polar/releases). On all client releases, the zipped client is downloadable and attached as an asset to the release. +### Step 1: Choose Your Installation Method + +You have two options to get started with the POLAR client: + +#### 1. Using the NPM Package + +- If your project uses NPM, you can install the generic map client via the @polar/client-generic package. Its usage is further illustrated on our [documentation page](https://dataport.github.io/polar/). + +#### 2. Using Pre-built Releases + +- If your project does not use NPM, you can download a pre-built client from our [releases](https://github.com/Dataport/polar/releases). In each release, the zipped client is available as an attached asset and can be downloaded directly. ### Step 2: Download and Installation -- Download the desired client from the releases and unzip it. -- The unzipped files contain example projects and documentation that will help you get started. + +#### For NPM Users: + +- Install the package via NPM: + +```bash +npm install @polar/client-generic +``` + +#### For Non-NPM Users: + +- Download the desired client from the [releases](https://github.com/Dataport/polar/releases) and unzip it. + The unzipped files contain example projects and documentation that will help you get started. ## 3. Developing with POLAR -### Step 1: Clone the Repository and Set Up the Environment +### Clone the Repository and Set Up the Environment + 1. Clone the POLAR repository: ```bash git clone https://github.com/Dataport/polar.git @@ -29,287 +49,214 @@ POLAR is a versatile package that allows you to develop your own map projects us ``` > This winds up our development client in a minimal test environment as a starting point. -## 4. Using Plugins in the Snowbox Client - -### 4.1 Overview of the Plugins +## 4. Integrating and Using Plugins in POLAR -The Snowbox client supports various plugins that are defined in the `package.json`. The available plugins include: -- `@polar/plugin-address-search` -- `@polar/plugin-draw` -- `@polar/plugin-fullscreen` -- `@polar/plugin-geo-location` -- `@polar/plugin-legend` -- ... and many more. +### 4.1 Overview of the Plugins and the addPlugins Mechanism -These plugins extend the functionality of the Snowbox client and provide features like address search, drawing on the map, fullscreen support, geolocation, etc. +POLAR utilizes a modular plugin system that allows you to extend the functionality of your map application. The addPlugins function is central to integrating plugins into your project. In this section, we'll explain how to use the addPlugins mechanism to add and configure plugins in your application. -### 4.2 Adding Plugins +### 4.2 Adding Plugins to Your Project -The `package.json` file of the Snowbox client already lists all available plugins as `devDependencies`. To use a plugin in your project, ensure that it is installed in your development environment. +The `package.json` file of the Snowbox client already lists all available plugins as `devDependencies`. To use a plugin in your project, ensure that it is installed in your development environment.Clients run anywhere, but their development requires further setup. Right now, clients should be developed in this repository or in a fork of it to make use of its setup. To add new plugins or activate existing ones, edit the `package.json` and add the desired plugin if it is not already present. When developing an unpublished plugin, use the path to your local plugin to ensure it is recognized by the client. -Clients run anywhere, but their development requires further setup. Right now, clients should be developed in this repository or in a fork of it to make use of its setup. - Then, reinstall the dependencies: + ```bash npm install ``` -### 4.3 Using Plugins in Code – Detailed Explanation - -The provided `addPlugins.ts` file defines the `addPlugins` function, which integrates multiple plugins into the map application. Let’s break down the code and explain how each plugin is integrated and what configuration options are available. - -#### Overview of the `addPlugins` Function - -The `addPlugins` function expects a `core` object as a parameter. This `core` object represents the main instance of the POLAR application and provides methods to add plugins. - -```typescript -export const addPlugins = (core) => { - setLayout(NineLayout); - // Adding the iconMenu plugin and other plugins - core.addPlugins([ /* List of Plugins */ ]); -} -``` - -#### Configuration of the `iconMenu` Plugin - -The `iconMenu` plugin serves as a central menu for displaying and accessing other plugins. It is configured with a list of menu entries, each embedding another plugin. - -```typescript -const iconMenu = IconMenu({ - menus: [ - { - plugin: LayerChooser({}), // Layer chooser plugin - icon: 'fa-layer-group', // Icon for the plugin in the menu - id: 'layerChooser', - }, - { - plugin: Draw({}), // Drawing plugin - icon: 'fa-pencil', // Icon for the plugin - id: 'draw', - }, - { - plugin: Zoom({ renderType: 'iconMenu' }), // Zoom plugin - id: 'zoom', - }, - { - plugin: Fullscreen({ renderType: 'iconMenu' }), // Fullscreen plugin - id: 'fullscreen', - }, - { - plugin: GeoLocation({ renderType: 'iconMenu' }), // Geolocation plugin - id: 'geoLocation', - }, - { - plugin: Attributions({ - renderType: 'iconMenu', - listenToChanges: [ - 'plugin/zoom/zoomLevel', - 'plugin/layerChooser/activeBackgroundId', - 'plugin/layerChooser/activeMaskIds', - ], - }), - icon: 'fa-regular fa-copyright', - id: 'attributions', - }, - ], - displayComponent: true, // The IconMenu will be displayed on the map - initiallyOpen: 'layerChooser', // Specifies which menu is open by default - layoutTag: NineLayoutTag.TOP_RIGHT, // Position of the iconMenu on the map -}); -``` - -#### Adding Plugins to the Map - -The `core.addPlugins` method is used to add the list of plugins to the map application. Here’s a detailed look at some of these plugins: - -```typescript -core.addPlugins([ - AddressSearch( - merge({}, defaultOptions, { - layoutTag: NineLayoutTag.TOP_LEFT, // Position of the plugin on the map - addLoading: 'plugin/loadingIndicator/addLoadingKey', - removeLoading: 'plugin/loadingIndicator/removeLoadingKey', - }) - ), - iconMenu, - Export( - merge({}, defaultOptions, { - layoutTag: NineLayoutTag.BOTTOM_LEFT, // Position of the export plugin - }) - ), - LoadingIndicator( - merge({}, defaultOptions, { - layoutTag: NineLayoutTag.MIDDLE_MIDDLE, // Position of the loading indicator - }) - ), - Legend({ - displayComponent: true, - layoutTag: NineLayoutTag.BOTTOM_RIGHT, // Position of the legend - }), - Scale( - merge({}, defaultOptions, { - layoutTag: NineLayoutTag.BOTTOM_RIGHT, // Position of the scale - }) - ), - Toast( - merge({}, defaultOptions, { - layoutTag: NineLayoutTag.BOTTOM_MIDDLE, // Position of the toast messages - }) - ), - Pins({ - appearOnClick: { show: true, atZoomLevel: 6 }, // Configuration for the pins on the map - coordinateSource: 'plugin/addressSearch/chosenAddress', - toastAction: 'plugin/toast/addToast', - }), - Gfi( - merge({}, defaultOptions, { - layoutTag: NineLayoutTag.MIDDLE_LEFT, // Position of the GFI plugin - }) - ), - ReverseGeocoder({ - url: 'https://geodienste.hamburg.de/HH_WPS', - addLoading: 'plugin/loadingIndicator/addLoadingKey', - removeLoading: 'plugin/loadingIndicator/removeLoadingKey', - coordinateSource: 'plugin/pins/transformedCoordinate', - addressTarget: 'plugin/addressSearch/selectResult', - zoomTo: 7, - }), -]); -``` - -## 5. Integrating Plugins into mapConfiguration.ts - -The `mapConfiguration.ts` file serves as the central configuration point for your map application, providing a comprehensive structure for defining how your map should look and behave. This file, in combination with `addPlugins.ts` and `polar-client.ts`, integrates and manages various plugins within the POLAR framework. +### 4.3 Configuring Plugins in addPlugins.ts -### 5.1 Basic Structure of mapConfiguration.ts +The addPlugins.ts file defines the addPlugins function, which integrates multiple plugins into the map application. Here's how you can configure and add plugins: -Initially, the `mapConfiguration.ts` file starts with foundational settings for the map, such as projection systems, language settings, and the theme configuration using Vuetify. It also sets up layers, address search, drawing options, and other components that influence how the map and its plugins function. Here's an excerpt of the core configuration: - -``` Typescript -export const mapConfiguration = { - language: 'en', - epsg: 'EPSG:25832', - locales: language, - vuetify: { - theme: { - themes: { - light: { - primary: somewhatBlue, - primaryContrast: notQuiteWhite, - secondary: eigengrau, - secondaryContrast: notQuiteWhite, - }, - }, - }, - }, - // Other map configurations like namedProjections, addressSearch, layers, etc. -}; -``` - -### 5.2 Adding Plugins Using addPlugins.ts -The `addPlugins.ts` file is used to define and manage the integration of plugins into your map instance. The addPlugins function accepts the `core` instance and sets up the layout for the map using `setLayout(NineLayout)`. Plugins such as `AddressSearch`, `GeoLocation`, `Legend`, and `Scale` are imported and added to the map with specific configurations. -Structure of `addPlugins.ts`: +Example of addPlugins.ts ```typescript -import merge from 'lodash.merge'; -import { setLayout, NineLayout, NineLayoutTag } from '@polar/core'; +import merge from 'lodash.merge' +import { setLayout, NineLayout, NineLayoutTag } from '@polar/core' // Import all the necessary plugins -import AddressSearch from '@polar/plugin-address-search'; -import Attributions from '@polar/plugin-attributions'; -import Draw from '@polar/plugin-draw'; -import Export from '@polar/plugin-export'; -import Fullscreen from '@polar/plugin-fullscreen'; -import GeoLocation from '@polar/plugin-geo-location'; -import IconMenu from '@polar/plugin-icon-menu'; -import LayerChooser from '@polar/plugin-layer-chooser'; -import Legend from '@polar/plugin-legend'; -import Scale from '@polar/plugin-scale'; -import Toast from '@polar/plugin-toast'; -import Zoom from '@polar/plugin-zoom'; - -// Default configuration for plugins +import AddressSearch from '@polar/plugin-address-search' +import Attributions from '@polar/plugin-attributions' +import Draw from '@polar/plugin-draw' +import Export from '@polar/plugin-export' +import Fullscreen from '@polar/plugin-fullscreen' +import GeoLocation from '@polar/plugin-geo-location' +import IconMenu from '@polar/plugin-icon-menu' +import LayerChooser from '@polar/plugin-layer-chooser' +import Legend from '@polar/plugin-legend' +import LoadingIndicator from '@polar/plugin-loading-indicator' +import Pins from '@polar/plugin-pins' +import ReverseGeocoder from '@polar/plugin-reverse-geocoder' +import Scale from '@polar/plugin-scale' +import Toast from '@polar/plugin-toast' +import Zoom from '@polar/plugin-zoom' + const defaultOptions = { displayComponent: true, layoutTag: NineLayoutTag.TOP_LEFT, -}; +} export const addPlugins = (core) => { - setLayout(NineLayout); + setLayout(NineLayout) // Define the iconMenu that groups several plugins const iconMenu = IconMenu({ menus: [ - { plugin: LayerChooser({}), icon: 'fa-layer-group', id: 'layerChooser' }, - { plugin: Draw({}), icon: 'fa-pencil', id: 'draw' }, - { plugin: Zoom({ renderType: 'iconMenu' }), id: 'zoom' }, - { plugin: Fullscreen({ renderType: 'iconMenu' }), id: 'fullscreen' }, - { plugin: GeoLocation({ renderType: 'iconMenu' }), id: 'geoLocation' }, { - plugin: Attributions({ renderType: 'iconMenu', listenToChanges: ['plugin/zoom/zoomLevel', 'plugin/layerChooser/activeBackgroundId'] }), + plugin: LayerChooser({}), + icon: 'fa-layer-group', // Icon for the plugin in the menu + id: 'layerChooser', + }, + { + plugin: Draw({}), + icon: 'fa-pencil', // Icon for the plugin + id: 'draw', + }, + { + plugin: Zoom({ renderType: 'iconMenu' }), + id: 'zoom', + }, + { + plugin: Fullscreen({ renderType: 'iconMenu' }), + id: 'fullscreen', + }, + { + plugin: GeoLocation({ renderType: 'iconMenu' }), + id: 'geoLocation', + }, + { + plugin: Attributions({ + renderType: 'iconMenu', + listenToChanges: [ + 'plugin/zoom/zoomLevel', + 'plugin/layerChooser/activeBackgroundId', + 'plugin/layerChooser/activeMaskIds', + ], + }), icon: 'fa-regular fa-copyright', id: 'attributions', }, ], + displayComponent: true, // The IconMenu will be displayed on the map + initiallyOpen: 'layerChooser', // Specifies which menu is open by default layoutTag: NineLayoutTag.TOP_RIGHT, - displayComponent: true, - initiallyOpen: 'layerChooser', - }); + }) // Adding all plugins to the map instance core.addPlugins([ - AddressSearch(merge({}, defaultOptions, { layoutTag: NineLayoutTag.TOP_LEFT })), + AddressSearch( + merge({}, defaultOptions, { + layoutTag: NineLayoutTag.TOP_LEFT, + addLoading: 'plugin/loadingIndicator/addLoadingKey', + removeLoading: 'plugin/loadingIndicator/removeLoadingKey', + }) + ), iconMenu, - Export(merge({}, defaultOptions, { layoutTag: NineLayoutTag.BOTTOM_LEFT })), - Legend({ displayComponent: true, layoutTag: NineLayoutTag.BOTTOM_RIGHT }), - Scale(merge({}, defaultOptions, { layoutTag: NineLayoutTag.BOTTOM_RIGHT })), - Toast(merge({}, defaultOptions, { layoutTag: NineLayoutTag.BOTTOM_MIDDLE })), - ]); -}; + Export( + merge({}, defaultOptions, { + layoutTag: NineLayoutTag.BOTTOM_LEFT, + }) + ), + LoadingIndicator( + merge({}, defaultOptions, { + layoutTag: NineLayoutTag.MIDDLE_MIDDLE, + }) + ), + Legend({ + displayComponent: true, + layoutTag: NineLayoutTag.BOTTOM_RIGHT, + }), + Scale( + merge({}, defaultOptions, { + layoutTag: NineLayoutTag.BOTTOM_RIGHT, + }) + ), + Toast( + merge({}, defaultOptions, { + layoutTag: NineLayoutTag.BOTTOM_MIDDLE, + }) + ), + Pins({ + appearOnClick: { show: true, atZoomLevel: 6 }, + coordinateSource: 'plugin/addressSearch/chosenAddress', + toastAction: 'plugin/toast/addToast', + }), + ReverseGeocoder({ + url: 'https://geodienste.hamburg.de/HH_WPS', + addLoading: 'plugin/loadingIndicator/addLoadingKey', + removeLoading: 'plugin/loadingIndicator/removeLoadingKey', + coordinateSource: 'plugin/pins/transformedCoordinate', + addressTarget: 'plugin/addressSearch/selectResult', + zoomTo: 7, + }), + ]) +} ``` -### 5.3 Integrating the Plugins into the Application via polar-client.ts -The `polar-client.ts` file ties everything together. It initializes the POLAR `core` instance, invokes the `addPlugins` function, and sets up the map with the defined configuration from `mapConfiguration.ts`. +#### Understanding the Configuration -```Typescript -import polarCore from '@polar/core'; -import { addPlugins } from './addPlugins'; -import { mapConfiguration } from './mapConfiguration'; +- Import Plugins: Begin by importing all the plugins you wish to use. +- Configure Plugins: Each plugin is configured with options like layoutTag, displayComponent, and other plugin-specific settings. +- Add Plugins: Use core.addPlugins([...]) to add the configured plugins to the map. +- Set Layout: Use setLayout(NineLayout) to define the map layout. -addPlugins(polarCore); +### 4.4 Integrating Plugins into mapConfiguration.ts and polar-client.ts + +#### mapConfiguration.ts + +The mapConfiguration.ts file serves as the central configuration point for your map application, defining settings such as projection systems, language, and theme configuration using Vuetify. + +```typescript +export const mapConfiguration = { + language: 'en', + epsg: 'EPSG:25832', + // Other configurations like locales, vuetify themes, layers, etc. +} +``` + +#### polar-client.ts + +In polar-client.ts, you initialize the POLAR core instance, invoke the addPlugins function, and set up the map with the configuration from mapConfiguration.ts. + +```typescript +import polarCore from '@polar/core' +import { addPlugins } from './addPlugins' +import { mapConfiguration } from './mapConfiguration' + +addPlugins(polarCore) const createMap = (layerConf) => { - polarCore.createMap({ - containerId: 'polarstern', - mapConfiguration: { - ...mapConfiguration, - layerConf, - }, - }).then(addStoreSubscriptions(/* subscription logic */)); -}; + polarCore + .createMap({ + containerId: 'polarstern', + mapConfiguration: { + ...mapConfiguration, + layerConf, + }, + }) + .then(addStoreSubscriptions(/* subscription logic */)) +} // Initializes the layer list and creates the map instance -polarCore.rawLayerList.initializeLayerList('https://geodienste.hamburg.de/services-internet.json', createMap); +polarCore.rawLayerList.initializeLayerList( + 'https://geodienste.hamburg.de/services-internet.json', + createMap +) ``` -### 5.4 Explanation of the Plugin Integration Process -The process is as follows: - - - `mapConfiguration.ts` defines the `core` settings and appearance of the map. - - `addPlugins.ts` handles the instantiation and configuration of individual plugins, utilizing `addPlugins(core)` to add them to the `core` instance. - - `polar-client.ts` combines these configurations to initialize and render the map, ensuring the plugins are integrated and functioning within the defined layout. +### 4.5 Best Practices for Integrating Plugins -### 5.5 Best Practices for Integrating Plugins - Centralized Configuration: Manage the primary map settings in mapConfiguration.ts for easy maintenance. - Modular Plugin Management: Use addPlugins.ts to separate plugin configurations, allowing for easier updates and modifications. - Testing: Regularly test your integrated plugins in the Snowbox environment to ensure compatibility and functionality. -## 6. Using the Layout – The `NineLayout` System +## 5. Using the Layout – The `NineLayout` System The `NineLayout` is a flexible layout system that allows plugins to be placed in a 3x3 grid (nine sections) on the map. -### 6.1 How `NineLayout.vue` Works +### 5.1 How `NineLayout.vue` Works `NineLayout.vue` is the Vue component that represents the layout of the map and places plugins according to their configured positions. @@ -337,13 +284,13 @@ The `NineLayout` is a flexible layout system that allows plugins to be placed in - The `wrapper` div is the container element for the entire layout. - Each plugin is positioned within the `plugin-vessel` component based on its `layoutTag`. -### 6.2 Summary of the Layout System +### 5.2 Summary of the Layout System The `NineLayout` system provides a structured and flexible way to display plugins in one of nine defined positions within your map application. By using the `NineLayoutTag` enum and configuring it in `NineLayout.vue`, you can precisely control the placement of your plugins and customize them to fit the needs of your map application. ## Conclusion -Congratulations! You have now completed the Getting Started guide for POLAR. You should now have a solid understanding of how to set up and work with the POLAR package, integrate and configure plugins, and customize your map application to fit your project's needs. +Congratulations! You have now completed the Getting Started guide for POLAR. You should now have a solid understanding of how to set up and work with the POLAR package library, integrate and configure plugins, and customize your map application to fit your project's needs. ### Next Steps @@ -351,4 +298,4 @@ Congratulations! You have now completed the Getting Started guide for POLAR. You - Develop Custom Plugins: Extend POLAR's capabilities. - Contribute: Help improve the POLAR project. -Enjoy Building with POLAR! 🎉 \ No newline at end of file +Enjoy Building with POLAR! 🎉 From 65a0dc71417911c0a2cca8d8f7388b6208088b5e Mon Sep 17 00:00:00 2001 From: FinnDarge Date: Thu, 24 Oct 2024 07:11:42 +0200 Subject: [PATCH 7/9] removed chapter for layout --- gettingStarted.md | 38 +------------------------------------- 1 file changed, 1 insertion(+), 37 deletions(-) diff --git a/gettingStarted.md b/gettingStarted.md index 329323d2b..19842259f 100644 --- a/gettingStarted.md +++ b/gettingStarted.md @@ -200,7 +200,7 @@ export const addPlugins = (core) => { - Import Plugins: Begin by importing all the plugins you wish to use. - Configure Plugins: Each plugin is configured with options like layoutTag, displayComponent, and other plugin-specific settings. - Add Plugins: Use core.addPlugins([...]) to add the configured plugins to the map. -- Set Layout: Use setLayout(NineLayout) to define the map layout. +- Set Layout: The `NineLayout` system provides a structured and flexible way to display plugins in 3x3 grid (nine sections) on the map within your map application. ### 4.4 Integrating Plugins into mapConfiguration.ts and polar-client.ts @@ -252,42 +252,6 @@ polarCore.rawLayerList.initializeLayerList( - Modular Plugin Management: Use addPlugins.ts to separate plugin configurations, allowing for easier updates and modifications. - Testing: Regularly test your integrated plugins in the Snowbox environment to ensure compatibility and functionality. -## 5. Using the Layout – The `NineLayout` System - -The `NineLayout` is a flexible layout system that allows plugins to be placed in a 3x3 grid (nine sections) on the map. - -### 5.1 How `NineLayout.vue` Works - -`NineLayout.vue` is the Vue component that represents the layout of the map and places plugins according to their configured positions. - -```vue - -``` - -- The `wrapper` div is the container element for the entire layout. -- Each plugin is positioned within the `plugin-vessel` component based on its `layoutTag`. - -### 5.2 Summary of the Layout System - -The `NineLayout` system provides a structured and flexible way to display plugins in one of nine defined positions within your map application. By using the `NineLayoutTag` enum and configuring it in `NineLayout.vue`, you can precisely control the placement of your plugins and customize them to fit the needs of your map application. - ## Conclusion Congratulations! You have now completed the Getting Started guide for POLAR. You should now have a solid understanding of how to set up and work with the POLAR package library, integrate and configure plugins, and customize your map application to fit your project's needs. From c38065c41f250ce841c3667d41b134d3cc17c3e8 Mon Sep 17 00:00:00 2001 From: Pascal Roehling Date: Thu, 24 Oct 2024 22:38:59 +0200 Subject: [PATCH 8/9] Remove not-selectable features from featureList --- packages/plugins/Gfi/src/store/getters.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/plugins/Gfi/src/store/getters.ts b/packages/plugins/Gfi/src/store/getters.ts index 242b2a374..6a8e23845 100644 --- a/packages/plugins/Gfi/src/store/getters.ts +++ b/packages/plugins/Gfi/src/store/getters.ts @@ -10,6 +10,7 @@ import noop from '@repositoryname/noop' import { isVisible } from '@polar/lib-invisible-style' import { Feature } from 'ol' import { Cluster as ClusterSource } from 'ol/source' +import { GeoJSON } from 'ol/format' import { GfiGetters, GfiState } from '../types' import { listableLayersFilter } from '../utils/listableLayersFilter' import getInitialState from './getInitialState' @@ -214,16 +215,18 @@ const getters: PolarGetterTree = { }, listFeatures( { visibilityChangeIndicator }, - { listableLayerSources, listMode }, + { gfiConfiguration, listableLayerSources, listMode }, __, rootGetters ): Feature[] { const { map, clientHeight, clientWidth, center, zoomLevel } = rootGetters + const writer = new GeoJSON() // trigger getter on those who indicate feature change possibility noop(clientHeight, clientWidth, center, zoomLevel) noop(visibilityChangeIndicator) return listableLayerSources .map((source) => { + const layerId = source.get('_gfiLayerId') return ( listMode === 'loaded' ? source.getFeatures() @@ -233,9 +236,15 @@ const getters: PolarGetterTree = { ) ) .filter(isVisible) + .filter((feature) => { + const { isSelectable } = gfiConfiguration.layers[layerId] + return typeof isSelectable === 'function' + ? isSelectable(JSON.parse(writer.writeFeature(feature))) + : true + }) .map((feature) => { // true = silent change (prevents cluster recomputation & rerender) - feature.set('_gfiLayerId', source.get('_gfiLayerId'), true) + feature.set('_gfiLayerId', layerId, true) return feature }) }) From 8ea8f04ec45a5edd53a8bae3468fc268164bb2ca Mon Sep 17 00:00:00 2001 From: Finn-Rasmus Darge <50116756+FinnDarge@users.noreply.github.com> Date: Fri, 25 Oct 2024 07:39:53 +0200 Subject: [PATCH 9/9] correct typos MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Pascal Röhling <73653210+dopenguin@users.noreply.github.com> --- gettingStarted.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gettingStarted.md b/gettingStarted.md index 19842259f..8a79c7220 100644 --- a/gettingStarted.md +++ b/gettingStarted.md @@ -200,7 +200,7 @@ export const addPlugins = (core) => { - Import Plugins: Begin by importing all the plugins you wish to use. - Configure Plugins: Each plugin is configured with options like layoutTag, displayComponent, and other plugin-specific settings. - Add Plugins: Use core.addPlugins([...]) to add the configured plugins to the map. -- Set Layout: The `NineLayout` system provides a structured and flexible way to display plugins in 3x3 grid (nine sections) on the map within your map application. +- Set Layout: The `NineLayout` system provides a structured and flexible way to display plugins in a 3x3 grid (nine sections) on the map within your application. ### 4.4 Integrating Plugins into mapConfiguration.ts and polar-client.ts