+
+
+
+
+
+
+
diff --git a/packages/clients/textLocator/src/plugins/Header/index.ts b/packages/clients/textLocator/src/plugins/Header/index.ts
new file mode 100644
index 000000000..142182c3d
--- /dev/null
+++ b/packages/clients/textLocator/src/plugins/Header/index.ts
@@ -0,0 +1,12 @@
+import Vue from 'vue'
+import { PluginOptions } from '@polar/lib-custom-types'
+import Header from './Header.vue'
+import language from './language'
+
+export default (options: PluginOptions) => (instance: Vue) =>
+ instance.$store.dispatch('addComponent', {
+ name: 'header',
+ plugin: Header,
+ language,
+ options,
+ })
diff --git a/packages/clients/textLocator/src/plugins/Header/language.ts b/packages/clients/textLocator/src/plugins/Header/language.ts
new file mode 100644
index 000000000..c55bb9879
--- /dev/null
+++ b/packages/clients/textLocator/src/plugins/Header/language.ts
@@ -0,0 +1,31 @@
+import { LanguageOption } from '@polar/lib-custom-types'
+
+const lang: LanguageOption[] = [
+ {
+ type: 'de',
+ resources: {
+ plugins: {
+ textLocator: {
+ header: {
+ // remove lang="en" in component if this becomes German
+ text: 'TextLocator',
+ },
+ },
+ },
+ },
+ },
+ {
+ type: 'en',
+ resources: {
+ plugins: {
+ textLocator: {
+ header: {
+ text: 'TextLocator',
+ },
+ },
+ },
+ },
+ },
+]
+
+export default lang
diff --git a/packages/clients/textLocator/src/polar-client.ts b/packages/clients/textLocator/src/polar-client.ts
new file mode 100644
index 000000000..57ebc346e
--- /dev/null
+++ b/packages/clients/textLocator/src/polar-client.ts
@@ -0,0 +1,38 @@
+import client from '@polar/core'
+import packageInfo from '../package.json'
+import { addPlugins } from './addPlugins'
+import { services as layerConf } from './services'
+import { mapConfiguration } from './mapConfig'
+
+// eslint-disable-next-line no-console
+console.log(`TextLocator map client running in version ${packageInfo.version}.`)
+const containerId = 'polarstern'
+addPlugins(client)
+
+interface TextLocatorParameters {
+ urls: {
+ textLocatorBackend: string
+ gazetteerClient: string
+ }
+}
+
+export async function initializeClient({ urls }: TextLocatorParameters) {
+ client.rawLayerList.initializeLayerList(layerConf)
+ mapConfiguration.layerConf = layerConf
+ mapConfiguration.addressSearch = {
+ searchMethods: [{ url: urls.gazetteerClient, type: 'coastalGazetteer' }],
+ minLength: 3,
+ waitMs: 500,
+ }
+
+ return await client.createMap({
+ containerId,
+ mapConfiguration: {
+ ...mapConfiguration,
+ geometrySearch: {
+ url: urls.gazetteerClient,
+ },
+ textLocatorBackendUrl: urls.textLocatorBackend,
+ },
+ })
+}
diff --git a/packages/clients/textLocator/src/services.ts b/packages/clients/textLocator/src/services.ts
new file mode 100644
index 000000000..ed219ceb4
--- /dev/null
+++ b/packages/clients/textLocator/src/services.ts
@@ -0,0 +1,104 @@
+export const openStreetMap = 'openStreetMap'
+export const openSeaMap = 'openSeaMap'
+export const mdiSeaNames = 'mdiSeaNames'
+export const wmtsTopplusOpenWeb = 'wmtsTopplusOpenWeb'
+export const wmtsTopplusOpenWebGrey = 'wmtsTopplusOpenWebGrey'
+export const wmtsTopplusOpenLight = 'wmtsTopplusOpenLight'
+export const wmtsTopplusOpenLightGrey = 'wmtsTopplusOpenLightGrey'
+export const aerial = 'aerial'
+
+export const idRegister = [
+ openSeaMap,
+ mdiSeaNames,
+ openStreetMap,
+ wmtsTopplusOpenWeb,
+ wmtsTopplusOpenWebGrey,
+ wmtsTopplusOpenLight,
+ wmtsTopplusOpenLightGrey,
+ aerial,
+]
+
+const topplusLayerNames = {
+ [wmtsTopplusOpenWeb]: 'web',
+ [wmtsTopplusOpenWebGrey]: 'web_grau',
+ [wmtsTopplusOpenLight]: 'web_light',
+ [wmtsTopplusOpenLightGrey]: 'web_light_grau',
+}
+
+export const services = [
+ ...[
+ wmtsTopplusOpenLight,
+ wmtsTopplusOpenLightGrey,
+ wmtsTopplusOpenWeb,
+ wmtsTopplusOpenWebGrey,
+ ].map((id) => ({
+ id,
+ capabilitiesUrl:
+ 'https://sgx.geodatenzentrum.de/wmts_topplus_open/1.0.0/WMTSCapabilities.xml',
+ urls: 'https://sgx.geodatenzentrum.de/wmts_topplus_open',
+ optionsFromCapabilities: true,
+ tileMatrixSet: 'EU_EPSG_25832_TOPPLUS',
+ typ: 'WMTS',
+ layers: topplusLayerNames[id],
+ legendURL: `https://sg.geodatenzentrum.de/wms_topplus_open?styles=&layer=${topplusLayerNames[id]}&service=WMS&format=image/png&sld_version=1.1.0&request=GetLegendGraphic&version=1.1.1`,
+ })),
+ {
+ id: openStreetMap,
+ urls: [
+ 'https://a.tile.openstreetmap.org/{TileMatrix}/{TileCol}/{TileRow}.png',
+ 'https://b.tile.openstreetmap.org/{TileMatrix}/{TileCol}/{TileRow}.png',
+ 'https://c.tile.openstreetmap.org/{TileMatrix}/{TileCol}/{TileRow}.png',
+ ],
+ typ: 'WMTS',
+ format: 'image/png',
+ coordinateSystem: 'EPSG:3857',
+ origin: [-20037508.3428, 20037508.3428],
+ transparent: false,
+ tileSize: '256',
+ minScale: '1',
+ maxScale: '2500000',
+ tileMatrixSet: 'google3857',
+ requestEncoding: 'REST',
+ resLength: '20',
+ },
+ {
+ id: openSeaMap,
+ urls: [
+ 'https://tiles.openseamap.org/seamark/{TileMatrix}/{TileCol}/{TileRow}.png',
+ ],
+ typ: 'WMTS',
+ format: 'image/png',
+ coordinateSystem: 'EPSG:3857',
+ origin: [-20037508.3428, 20037508.3428],
+ transparent: true,
+ tileSize: '256',
+ minScale: '1',
+ maxScale: '2500000',
+ tileMatrixSet: 'google3857',
+ requestEncoding: 'REST',
+ resLength: '20',
+ },
+ {
+ id: mdiSeaNames,
+ url: `https://mdi-de-dienste.org/geoserver_gaz/nokis/ows`,
+ typ: 'WMS',
+ layers: 'name_service',
+ legendURL: 'ignore',
+ format: 'image/png',
+ version: '1.3.0',
+ transparent: true,
+ singleTile: true,
+ STYLES: 'Seeseitig',
+ },
+ {
+ id: aerial,
+ url: 'https://sgx.geodatenzentrum.de/wms_sen2europe',
+ typ: 'WMS',
+ layers: 'rgb',
+ legendURL: 'ignore',
+ format: 'image/png',
+ version: '1.3.0',
+ transparent: true,
+ singleTile: true,
+ },
+]
diff --git a/packages/clients/textLocator/src/utils/coastalGazetteer/common.ts b/packages/clients/textLocator/src/utils/coastalGazetteer/common.ts
new file mode 100644
index 000000000..bbd23b3f9
--- /dev/null
+++ b/packages/clients/textLocator/src/utils/coastalGazetteer/common.ts
@@ -0,0 +1,11 @@
+import { WKT, GeoJSON } from 'ol/format'
+
+export const idPrefixes = {
+ country: 'EuroNat-',
+ regionRough: 'Ak',
+ regionFine: 'Landsg',
+ wattenmeer: 'SH-WATTENMEER-',
+}
+
+export const wellKnownText = new WKT()
+export const geoJson = new GeoJSON()
diff --git a/packages/clients/textLocator/src/utils/coastalGazetteer/getAllPages.ts b/packages/clients/textLocator/src/utils/coastalGazetteer/getAllPages.ts
new file mode 100644
index 000000000..6970114a9
--- /dev/null
+++ b/packages/clients/textLocator/src/utils/coastalGazetteer/getAllPages.ts
@@ -0,0 +1,105 @@
+import { PolarStore } from '@polar/lib-custom-types'
+import {
+ GeometrySearchGetters,
+ GeometrySearchState,
+} from '../../plugins/GeometrySearch/types'
+import { MakeRequestBodyParameters, ResponsePayload } from './types'
+import { makeRequestBody } from './makeRequestBody'
+
+const getEmptyResponsePayload = (): ResponsePayload => ({
+ count: '',
+ currentpage: '',
+ pages: '',
+ keyword: '',
+ querystring: '',
+ results: [],
+ time: NaN,
+})
+
+const mergeResponses = (
+ initialResponse: ResponsePayload,
+ responses: ResponsePayload[]
+): ResponsePayload => ({
+ ...initialResponse,
+ currentpage: 'merged',
+ results: [
+ initialResponse.results,
+ ...responses.map(({ results }) => results),
+ ].flat(1),
+ time: NaN, // not used, setting NaN to indicate it's not the actual time
+})
+
+const fetchParams = {
+ method: 'POST',
+ headers: {
+ // eslint-disable-next-line @typescript-eslint/naming-convention
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ },
+}
+
+const loadLengthInfoToast: [string, object] = [
+ 'plugin/toast/addToast',
+ {
+ type: 'info',
+ text: 'textLocator.info.loadingTime',
+ timeout: 10000,
+ },
+]
+
+const loadErrorInfoToast: [string, object] = [
+ 'plugin/toast/addToast',
+ {
+ type: 'error',
+ text: 'textLocator.error.searchCoastalGazetteer',
+ },
+]
+
+export async function getAllPages(
+ this: PolarStore,
+ signal: AbortSignal,
+ url: string,
+ params: Partial,
+ epsg: `EPSG:${string}`
+): Promise {
+ const response = await fetch(url, {
+ ...fetchParams,
+ body: new TextEncoder().encode(makeRequestBody(params, epsg)),
+ signal,
+ })
+
+ if (!response.ok) {
+ this.dispatch(...loadErrorInfoToast)
+ console.error('@polar/client-text-locator: Gazetteer response:', response)
+ return getEmptyResponsePayload()
+ }
+
+ const responsePayload: ResponsePayload = await response.json()
+ const pages = parseInt(responsePayload.pages, 10)
+ const initialRequestMerge = typeof params.page === 'undefined' && pages > 1
+
+ if (!initialRequestMerge) {
+ return responsePayload
+ }
+
+ if (Number(responsePayload.count) > 10) {
+ this.dispatch(...loadLengthInfoToast)
+ }
+
+ return mergeResponses(
+ responsePayload,
+ await Promise.all(
+ Array.from(Array(pages - 1)).map((_, index) =>
+ getAllPages.call(
+ this,
+ signal,
+ url,
+ {
+ ...params,
+ page: `${index + 2}`,
+ },
+ epsg
+ )
+ )
+ )
+ )
+}
diff --git a/packages/clients/textLocator/src/utils/coastalGazetteer/getPrimaryName.ts b/packages/clients/textLocator/src/utils/coastalGazetteer/getPrimaryName.ts
new file mode 100644
index 000000000..b23c60e92
--- /dev/null
+++ b/packages/clients/textLocator/src/utils/coastalGazetteer/getPrimaryName.ts
@@ -0,0 +1,14 @@
+import { ResponseName } from './types'
+
+/**
+ * Finds primary names of a response and returns them comma-separated as string.
+ * If no primary name is included, the first-best name will be chosen.
+ * If no name exists, '???' is returned as a fallback (as in Küstengazetteer).
+ */
+export const getPrimaryName = (names: ResponseName[]): string =>
+ names
+ .filter((name) => name.Typ === 'Primärer Name')
+ .map((name) => `${name.Name}`)
+ .join(', ') ||
+ names[0]?.Name ||
+ '???'
diff --git a/packages/clients/textLocator/src/utils/coastalGazetteer/makeRequestBody.ts b/packages/clients/textLocator/src/utils/coastalGazetteer/makeRequestBody.ts
new file mode 100644
index 000000000..3ab21047b
--- /dev/null
+++ b/packages/clients/textLocator/src/utils/coastalGazetteer/makeRequestBody.ts
@@ -0,0 +1,56 @@
+import union from '@turf/union'
+import flatten from '@turf/flatten'
+import { Feature, GeoJsonProperties, Geometry, Polygon } from 'geojson'
+import { wgs84ProjectionCode } from '../common'
+import { geoJson, wellKnownText } from './common'
+import { MakeRequestBodyParameters, RequestPayload } from './types'
+
+const searchRequestDefaultPayload: RequestPayload = {
+ searchType: 'like',
+ lang: '-',
+ sdate: '0001-01-01',
+ edate: new Date().toJSON().slice(0, 10),
+ type: '-',
+}
+
+/**
+ * Reduces overlapping MultiPolygon to MultiPolygon without overlapping
+ * geometries. This is required for the following step, where the MultiPolygon
+ * is converted to WKT. WKT forbids overlapping geometries in a MultiPolygon.
+ */
+const unify = (geometry: Geometry): Geometry => {
+ if (geometry.type === 'MultiPolygon') {
+ return flatten(geometry).features.reduce(
+ (accumulator, current) =>
+ // NOTE: never null, input from flatten merges as expected
+ union(accumulator, current) as Feature
+ ).geometry
+ }
+ console.warn(
+ `@polar/client-text-locator: Unexpected geometry in request body creation.`
+ )
+ return geometry
+}
+
+export const makeRequestBody = (
+ { keyword, page, geometry, ...rest }: Partial,
+ epsg: `EPSG:${string}`
+): string =>
+ Object.entries({
+ ...searchRequestDefaultPayload,
+ keyword: keyword ? `*${keyword}*` : '',
+ ...(typeof page !== 'undefined' ? { page } : {}),
+ ...(typeof geometry !== 'undefined'
+ ? {
+ geom: wellKnownText.writeGeometry(
+ geoJson.readGeometry(unify(geometry), {
+ dataProjection: epsg,
+ featureProjection: wgs84ProjectionCode,
+ })
+ ),
+ }
+ : {}),
+ ...rest,
+ })
+ .map(([key, value]) => `${key}=${value}`)
+ .join('&')
diff --git a/packages/clients/textLocator/src/utils/coastalGazetteer/responseInterpreter.ts b/packages/clients/textLocator/src/utils/coastalGazetteer/responseInterpreter.ts
new file mode 100644
index 000000000..d59a79ae8
--- /dev/null
+++ b/packages/clients/textLocator/src/utils/coastalGazetteer/responseInterpreter.ts
@@ -0,0 +1,110 @@
+import { Feature, FeatureCollection } from 'geojson'
+import levenshtein from 'js-levenshtein'
+import { MultiPolygon } from 'ol/geom'
+import { wgs84ProjectionCode } from '../common'
+import { GeometrySearchState } from '../../plugins/GeometrySearch/types'
+import {
+ ResponseGeom,
+ ResponseName,
+ ResponsePayload,
+ ResponseResult,
+} from './types'
+import { geoJson, idPrefixes, wellKnownText } from './common'
+import { getPrimaryName } from './getPrimaryName'
+
+// arbitrary sort based on input – prefer 1. startsWith 2. closer string
+const sorter =
+ (searchPhrase: string, sortKey: string) =>
+ (a: ResponseName | Feature, b: ResponseName | Feature) => {
+ const aStartsWith = a[sortKey].startsWith(searchPhrase)
+ const bStartsWith = b[sortKey].startsWith(searchPhrase)
+
+ return aStartsWith && !bStartsWith
+ ? -1
+ : !aStartsWith && bStartsWith
+ ? 1
+ : levenshtein(a[sortKey], searchPhrase) -
+ levenshtein(b[sortKey], searchPhrase)
+ }
+
+export const getEmptyFeatureCollection =
+ (): GeometrySearchState['featureCollection'] => ({
+ type: 'FeatureCollection',
+ features: [],
+ })
+
+// for now: merge all geometries, independent of their timeframe
+const geoJsonifyAllGeometries = (
+ geoms: ResponseGeom[],
+ epsg: `EPSG:${string}`
+) =>
+ geoJson.writeGeometryObject(
+ geoms.reduce(
+ (multiPolygon, currentGeom) =>
+ (
+ wellKnownText.readGeometry(currentGeom.WKT, {
+ dataProjection: wgs84ProjectionCode,
+ featureProjection: epsg,
+ }) as MultiPolygon
+ )
+ .getPolygons()
+ .reduce((accumulator, currentPolygon) => {
+ accumulator.appendPolygon(currentPolygon)
+ return accumulator
+ }, multiPolygon),
+ new MultiPolygon([])
+ )
+ )
+
+const featurify =
+ (epsg: `EPSG:${string}`, searchPhrase: string | null) =>
+ (
+ feature: ResponseResult
+ ): GeometrySearchState['featureCollection']['features'][number] | null => {
+ const title = searchPhrase
+ ? feature.names.sort(sorter(searchPhrase, 'Name'))[0]?.Name || '???'
+ : getPrimaryName(feature.names)
+ return {
+ type: 'Feature',
+ geometry: feature.geoms.length
+ ? geoJsonifyAllGeometries(feature.geoms, epsg)
+ : { type: 'Point', coordinates: [] },
+ id: feature.id,
+ properties: {
+ title,
+ names: feature.names,
+ geometries: feature.geoms,
+ },
+ // @ts-expect-error | used in POLAR for text display
+ title,
+ }
+ }
+
+export const featureCollectionify = (
+ fullResponse: ResponsePayload,
+ epsg: `EPSG:${string}`,
+ searchPhrase: string | null
+): FeatureCollection => {
+ const featureCollection = getEmptyFeatureCollection()
+ featureCollection.features.push(
+ ...fullResponse.results.reduce((accumulator, feature) => {
+ if (feature.id.includes(idPrefixes.country)) {
+ return accumulator
+ }
+ const geoJsonFeature = featurify(epsg, searchPhrase)(feature)
+ if (geoJsonFeature === null) {
+ return accumulator
+ }
+ accumulator.push(geoJsonFeature)
+ return accumulator
+ }, [] as GeometrySearchState['featureCollection']['features'])
+ )
+
+ if (searchPhrase) {
+ featureCollection.features = featureCollection.features.sort(
+ sorter(searchPhrase, 'title')
+ )
+ }
+
+ return featureCollection
+}
diff --git a/packages/clients/textLocator/src/utils/coastalGazetteer/searchGeometry.ts b/packages/clients/textLocator/src/utils/coastalGazetteer/searchGeometry.ts
new file mode 100644
index 000000000..29c687c66
--- /dev/null
+++ b/packages/clients/textLocator/src/utils/coastalGazetteer/searchGeometry.ts
@@ -0,0 +1,54 @@
+import { Feature } from 'ol'
+import { PolarStore } from '@polar/lib-custom-types'
+import { FeatureCollection } from 'geojson'
+import {
+ GeometrySearchGetters,
+ GeometrySearchState,
+} from '../../plugins/GeometrySearch/types'
+import { getAllPages } from './getAllPages'
+import { geoJson } from './common'
+import { ResponsePayload } from './types'
+import {
+ featureCollectionify,
+ getEmptyFeatureCollection,
+} from './responseInterpreter'
+
+let abortController: AbortController | null = null
+
+export async function searchGeometry(
+ this: PolarStore,
+ feature: Feature,
+ url: string,
+ epsg: `EPSG:${string}`
+): Promise | never {
+ if (abortController) {
+ abortController.abort()
+ }
+ const geometry = feature.getGeometry()
+ if (!geometry) {
+ return getEmptyFeatureCollection()
+ }
+ abortController = new AbortController()
+ const signal = abortController.signal
+ let fullResponse: ResponsePayload
+ try {
+ fullResponse = await getAllPages.call(
+ this,
+ signal,
+ url,
+ { geometry: geoJson.writeGeometryObject(geometry) },
+ epsg
+ )
+ } catch (e) {
+ if (!signal.aborted) {
+ console.error('@polar/client-text-locator:', e)
+ this.dispatch('plugin/toast/addToast', {
+ type: 'error',
+ text: 'textLocator.error.searchCoastalGazetteer',
+ })
+ }
+ return getEmptyFeatureCollection()
+ }
+ abortController = null
+ return Promise.resolve(featureCollectionify(fullResponse, epsg, null))
+}
diff --git a/packages/clients/textLocator/src/utils/coastalGazetteer/searchToponym.ts b/packages/clients/textLocator/src/utils/coastalGazetteer/searchToponym.ts
new file mode 100644
index 000000000..74d2a5ea3
--- /dev/null
+++ b/packages/clients/textLocator/src/utils/coastalGazetteer/searchToponym.ts
@@ -0,0 +1,78 @@
+import { FeatureCollection } from 'geojson'
+import { Map } from 'ol'
+import { PolarStore, SelectResultFunction } from '@polar/lib-custom-types'
+import SearchResultSymbols from '@polar/plugin-address-search/src/utils/searchResultSymbols'
+import VectorSource from 'ol/source/Vector'
+import {
+ GeometrySearchGetters,
+ GeometrySearchState,
+} from '../../plugins/GeometrySearch/types'
+import { ResponsePayload } from './types'
+import { getAllPages } from './getAllPages'
+import {
+ getEmptyFeatureCollection,
+ featureCollectionify,
+} from './responseInterpreter'
+
+interface CoastalGazetteerParameters {
+ epsg: `EPSG:${string}`
+ map: Map
+}
+
+// this method is meant to be injected into the AddressSearch plugin
+export async function searchCoastalGazetteerByToponym(
+ this: PolarStore,
+ signal: AbortSignal,
+ url: string,
+ inputValue: string,
+ queryParameters: CoastalGazetteerParameters
+): Promise | never {
+ let fullResponse: ResponsePayload
+ try {
+ fullResponse = await getAllPages.call(
+ this,
+ signal,
+ url,
+ { keyword: inputValue },
+ queryParameters.epsg
+ )
+ } catch (e) {
+ if (!signal.aborted) {
+ console.error('@polar/client-text-locator', e)
+ this.dispatch('plugin/toast/addToast', {
+ type: 'error',
+ text: 'textLocator.error.searchCoastalGazetteer',
+ })
+ }
+ return getEmptyFeatureCollection()
+ }
+ return Promise.resolve(
+ featureCollectionify(fullResponse, queryParameters.epsg, inputValue)
+ )
+}
+
+export const selectResult: SelectResultFunction = (
+ { commit, rootGetters },
+ { feature }
+) => {
+ // default behaviour (AddressSearch selects and is not involved in further behaviour)
+ commit('plugin/addressSearch/setChosenAddress', feature, { root: true })
+ commit('plugin/addressSearch/setInputValue', feature.title, { root: true })
+ commit(
+ 'plugin/addressSearch/setSearchResults',
+ SearchResultSymbols.NO_SEARCH,
+ { root: true }
+ )
+ const drawSource = rootGetters['plugin/draw/drawSource'] as VectorSource
+ drawSource.clear()
+
+ // added behaviour: push as one-element feature collection to search store
+ commit(
+ 'plugin/geometrySearch/setFeatureCollection',
+ {
+ ...getEmptyFeatureCollection(),
+ features: [feature],
+ },
+ { root: true }
+ )
+}
diff --git a/packages/clients/textLocator/src/utils/coastalGazetteer/types.ts b/packages/clients/textLocator/src/utils/coastalGazetteer/types.ts
new file mode 100644
index 000000000..2007adc32
--- /dev/null
+++ b/packages/clients/textLocator/src/utils/coastalGazetteer/types.ts
@@ -0,0 +1,61 @@
+// such names exist on the service
+/* eslint-disable @typescript-eslint/naming-convention */
+import { Geometry } from 'geojson'
+
+// // // REQUEST // // //
+
+export interface RequestPayload {
+ keyword?: string
+ searchType: 'like' | 'exact' | 'id'
+ lang: '-' | string
+ sdate: string
+ edate: string
+ type: '-' | string
+ page?: `${number}`
+ geom?: string
+}
+
+export interface MakeRequestBodyParameters extends RequestPayload {
+ geometry?: Geometry
+}
+
+// // // RESPONSE // // //
+
+export interface ResponseName {
+ Start: `${number}-${number}-${number}` // YYYY-MM-DD
+ Ende: `${number}-${number}-${number}` // YYYY-MM-DD
+ GeomID: string
+ ObjectID: string
+ Name: string
+ Quellen: object[] // not used
+ Rezent: boolean
+ Sprache: string
+ Typ: string
+}
+
+export interface ResponseGeom {
+ Start: `${number}-${number}-${number}` // YYYY-MM-DD
+ Ende: `${number}-${number}-${number}` // YYYY-MM-DD
+ GeomID: string
+ ObjectID: string
+ Quellen: object[] // not used
+ Typ: string
+ 'Typ Beschreibung': string
+ WKT: string // WKT geometry
+}
+
+export interface ResponseResult {
+ id: string
+ names: ResponseName[]
+ geoms: ResponseGeom[]
+}
+
+export interface ResponsePayload {
+ count: `${number}` | ''
+ currentpage: `${number}` | 'merged' | ''
+ pages: `${number}` | ''
+ keyword: string
+ querystring: string
+ results: ResponseResult[]
+ time: number
+}
diff --git a/packages/clients/textLocator/src/utils/common.ts b/packages/clients/textLocator/src/utils/common.ts
new file mode 100644
index 000000000..c88bfcc9f
--- /dev/null
+++ b/packages/clients/textLocator/src/utils/common.ts
@@ -0,0 +1 @@
+export const wgs84ProjectionCode = 'EPSG:4326'
diff --git a/packages/clients/textLocator/src/utils/literatureByToponym.ts b/packages/clients/textLocator/src/utils/literatureByToponym.ts
new file mode 100644
index 000000000..dac1198b6
--- /dev/null
+++ b/packages/clients/textLocator/src/utils/literatureByToponym.ts
@@ -0,0 +1,32 @@
+const urlSuffix = {
+ lookUpLocationsIndividually: '/lookup/locations_individually',
+}
+
+type LiteratureName = string
+type Toponym = string
+
+export type TitleLocationFrequency = Record<
+ LiteratureName,
+ Record
+>
+
+export async function searchLiteratureByToponym(
+ url: string,
+ names: string[]
+): Promise {
+ if (!names.length) {
+ return Promise.resolve({})
+ }
+ const response = await fetch(
+ `${url}${urlSuffix.lookUpLocationsIndividually}`,
+ {
+ method: 'POST',
+ headers: {
+ // eslint-disable-next-line @typescript-eslint/naming-convention
+ 'Content-Type': 'application/json',
+ },
+ body: `{"location_names":${JSON.stringify(names)}}`,
+ }
+ )
+ return (await response.json()).title_location_freq as TitleLocationFrequency
+}
diff --git a/packages/clients/textLocator/tsconfig.json b/packages/clients/textLocator/tsconfig.json
new file mode 100644
index 000000000..618c6c3e9
--- /dev/null
+++ b/packages/clients/textLocator/tsconfig.json
@@ -0,0 +1,3 @@
+{
+ "extends": "../../../tsconfig.json"
+}
diff --git a/packages/clients/textLocator/vite.config.js b/packages/clients/textLocator/vite.config.js
new file mode 100644
index 000000000..84e1d7ed1
--- /dev/null
+++ b/packages/clients/textLocator/vite.config.js
@@ -0,0 +1,5 @@
+import { getClientConfig } from '../../../viteConfigs'
+
+export default getClientConfig({
+ base: '',
+})
diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md
index a218e0a90..4abb72600 100644
--- a/packages/core/CHANGELOG.md
+++ b/packages/core/CHANGELOG.md
@@ -1,5 +1,11 @@
# CHANGELOG
+## unpublished
+
+- 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.
+- Fix: Adjust documentation to properly describe optionality of configuration parameters.
+
## 2.0.0-alpha.13
- Chore: Actually add external for vuetify.
diff --git a/packages/core/README.md b/packages/core/README.md
index 65051f582..9cf7e2094 100644
--- a/packages/core/README.md
+++ b/packages/core/README.md
@@ -72,11 +72,11 @@ The mapConfiguration allows controlling many client instance details.
| fieldName | type | description |
| --------------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| layerConf | LayerConf | Layer configuration as required by masterportalAPI. |
-| language | enum["de", "en"] | Initial language. Live changes not yet implemented, waiting for requirement. |
+| language | enum["de", "en"] | Initial language. |
| <...masterportalAPI.fields> | various | The object is also used to initialize the masterportalAPI. Please refer to their documentation for options. |
-| | various | Many plugins added with `addPlugin` may respect additional configuration. Please see the respective plugin documentations. Global plugin parameters are described below. |
-| locales | LanguageOption[] | All locales in POLAR's plugins can be overridden to fit your needs.|
-| vuetify | object | You may add vuetify configuration here. |
+| | various? | Many plugins added with `addPlugin` may respect additional configuration. Please see the respective plugin documentations. Global plugin parameters are described below. |
+| locales | LanguageOption[]? | All locales in POLAR's plugins can be overridden to fit your needs.|
+| vuetify | object? | You may add vuetify configuration here. |
| extendedMasterportalapiMarkers | extendedMasterportalapiMarkers? | Optional. If set, all configured visible vector layers' features can be hovered and selected by mouseover and click respectively. They are available as features in the store. Layers with `clusterDistance` will be clustered to a multi-marker that supports the same features. Please mind that this only works properly if you configure nothing but point marker vector layers styled by the masterportalAPI. |
| stylePath | string? | If no link tag with `data-polar="true"` is found in the document, this path will be used to create the link node in the client itself. It defaults to `'./style.css'`. Please mind that `data-polar="true"` is deprecated since it potentially led to flashes of misstyled content. stylePath will replace that solution in the next major release. |
| renderFaToLightDom | boolean? | POLAR requires FontAwesome in the Light/Root DOM due to an [unfixed bug in many browsers](https://bugs.chromium.org/p/chromium/issues/detail?id=336876). This value defaults to `true`. POLAR will, by default, just add the required CSS by itself. Should you have a version of Fontawesome already included, you can try to set this to `false` to check whether the versions are interoperable. |
@@ -213,7 +213,7 @@ Most plugins honor this additional field.
| fieldName | type | description |
| ---------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| displayComponent | boolean | Optional field that allows hiding UI elements from the user. The store will still be initialized, allowing you to add your own UI elements and control the plugin's functionality via the Store. This may or may not make sense, depending on the plugin. |
+| displayComponent | boolean? | Optional field that allows hiding UI elements from the user. The store will still be initialized, allowing you to add your own UI elements and control the plugin's functionality via the Store. This may or may not make sense, depending on the plugin. Defaults to `false` , meaning the default UI is hidden. |
##### mapConfiguration.vuetify
diff --git a/packages/core/src/components/MoveHandle.vue b/packages/core/src/components/MoveHandle.vue
index f01d9df6c..e2a658ee5 100644
--- a/packages/core/src/components/MoveHandle.vue
+++ b/packages/core/src/components/MoveHandle.vue
@@ -81,10 +81,7 @@ export default Vue.extend({
timeoutReference: 0,
}),
computed: {
- ...mapGetters(['hasSmallHeight', 'hasWindowSize']),
- isHorizontal() {
- return this.hasSmallHeight && this.hasWindowSize
- },
+ ...mapGetters(['deviceIsHorizontal', 'hasSmallHeight', 'hasWindowSize']),
moveEventNames(): MoveEventNames {
return this.touchDevice
? { move: 'touchmove', end: 'touchend' }
@@ -93,7 +90,7 @@ export default Vue.extend({
},
watch: {
// Fixes an issue if the orientation of a mobile device is changed while a plugin is open
- isHorizontal(newVal: boolean) {
+ deviceIsHorizontal(newVal: boolean) {
if (!newVal) {
this.updateMaxHeight()
}
diff --git a/packages/core/src/utils/createMap/updateSizeOnReady.ts b/packages/core/src/utils/createMap/updateSizeOnReady.ts
index 1d2faeaf3..94dc14aca 100644
--- a/packages/core/src/utils/createMap/updateSizeOnReady.ts
+++ b/packages/core/src/utils/createMap/updateSizeOnReady.ts
@@ -20,12 +20,14 @@ export const updateSizeOnReady = (instance: Vue) => {
console.error(
`@polar/core: The POLAR map client could not update its size. The map is probably invisible due to having 0 width or 0 height. This might be a CSS issue – please check the wrapper's size.`
)
+ instance.$store.commit('setMapHasDimensions', false)
} else {
// OL prints warnings – add this log to reduce confusion
// eslint-disable-next-line no-console
console.log(
`@polar/core: The map now has dimensions and can be rendered.`
)
+ instance.$store.commit('setMapHasDimensions', true)
clearInterval(intervalId)
}
}, 0)
diff --git a/packages/core/src/vuePlugins/vuex.ts b/packages/core/src/vuePlugins/vuex.ts
index 3b94c3124..61d9bf59a 100644
--- a/packages/core/src/vuePlugins/vuex.ts
+++ b/packages/core/src/vuePlugins/vuex.ts
@@ -74,6 +74,7 @@ const getInitialState = (): CoreState => ({
hasSmallDisplay: false,
errors: [],
language: '',
+ mapHasDimensions: false,
})
// OK for store creation
@@ -152,6 +153,8 @@ export const makeStore = () => {
window.innerWidth === state.clientWidth
)
},
+ deviceIsHorizontal: (_, getters) =>
+ getters.hasSmallHeight && getters.hasWindowSize,
},
mutations: {
...generateSimpleMutations(getInitialState()),
diff --git a/packages/lib/getFeatures/CHANGELOG.md b/packages/lib/getFeatures/CHANGELOG.md
index e302d785b..f4e787e1e 100644
--- a/packages/lib/getFeatures/CHANGELOG.md
+++ b/packages/lib/getFeatures/CHANGELOG.md
@@ -1,5 +1,9 @@
# CHANGELOG
+## unpublished
+
+- Fix: Properly extend interface `WfsParameters` from `QueryParameters` to reflect actual usage.
+
## 2.0.0-alpha.1
- Breaking: `getWfsFeatures` now throws errors if required parameters on the wfs configuration are missing instead of only printing error messages on the console.
diff --git a/packages/lib/getFeatures/package.json b/packages/lib/getFeatures/package.json
index 4aa2168ab..62eb55774 100644
--- a/packages/lib/getFeatures/package.json
+++ b/packages/lib/getFeatures/package.json
@@ -30,6 +30,9 @@
"peerDependencies": {
"ol": "^7.1.0"
},
+ "devDependencies": {
+ "@polar/lib-custom-types": "^1.0.0"
+ },
"nx": {
"includedScripts": [
"bundle"
diff --git a/packages/lib/getFeatures/types.ts b/packages/lib/getFeatures/types.ts
index e34dfbc46..24b562bd6 100644
--- a/packages/lib/getFeatures/types.ts
+++ b/packages/lib/getFeatures/types.ts
@@ -1,4 +1,5 @@
import { Feature as GeoJsonFeature } from 'geojson'
+import { QueryParameters } from '@polar/lib-custom-types'
/** Optional options related to a GetFeature request */
export interface AdditionalSearchOptions {
@@ -41,25 +42,23 @@ export interface SearchParameters {
}
/** Parameters for wfs searches */
-export interface WfsParameters {
+export interface WfsParameters extends QueryParameters {
+ /** Feature prefix from xmlns namespace without :; e.g. 'ave' */
+ featurePrefix: string
/** Name of the type of features (see ?service=wfs&request=DescribeFeatureType) */
typeName: string
+ /** Namespace of feature type */
+ xmlns: string
/** Name of the type's field to search in; mutually exclusive to patterns */
fieldName?: string
/** Input destructor patterns, e.g. \{\{streetName\}\} \{\{houseNumber\}\}; mutually exclusive to fieldName */
patterns?: string[]
/** RegExp to define pattern, e.g. \{houseNumber: '([1-9][0-9]*[a-z]?)', ...\}; only if patterns present */
patternKeys?: Record
- /** Feature prefix from xmlns namespace without :; e.g. 'ave' */
- featurePrefix: string
- /** Namespace of feature type */
- xmlns: string
- /** Limits requested features */
- maxFeatures?: number
- /** By default, if searching for "search", it is sent as "search*"; can be deactivated */
- useRightHandWildcard?: boolean
/** srsName for wfs query */
srsName?: string
+ /** By default, if searching for "search", it is sent as "search*"; can be deactivated */
+ useRightHandWildcard?: boolean
}
/**
diff --git a/packages/lib/testMountParameters/CHANGELOG.md b/packages/lib/testMountParameters/CHANGELOG.md
index 91d704261..ccfedc1a2 100644
--- a/packages/lib/testMountParameters/CHANGELOG.md
+++ b/packages/lib/testMountParameters/CHANGELOG.md
@@ -1,5 +1,9 @@
# CHANGELOG
+## unpublished
+
+- Feature: Extend mock state to match current core state type.
+
## 1.2.1
- Fix: Test now uses a mock EPSG code instead of an empty string.
diff --git a/packages/lib/testMountParameters/index.ts b/packages/lib/testMountParameters/index.ts
index aa42e8b61..f6938b551 100644
--- a/packages/lib/testMountParameters/index.ts
+++ b/packages/lib/testMountParameters/index.ts
@@ -61,6 +61,7 @@ export default (): MockParameters => {
moveHandleActionButton: 0,
plugin: {},
language: '',
+ mapHasDimensions: false,
zoomLevel: 0,
hovered: 0,
selected: 0,
diff --git a/packages/plugins/AddressSearch/CHANGELOG.md b/packages/plugins/AddressSearch/CHANGELOG.md
index 067e956a4..471b3b808 100644
--- a/packages/plugins/AddressSearch/CHANGELOG.md
+++ b/packages/plugins/AddressSearch/CHANGELOG.md
@@ -1,5 +1,10 @@
# CHANGELOG
+## unpublished
+
+- Feature: Add new optional configuration parameter `afterResultComponent` that allows to display a custom component for each search result.
+- Fix: Adjust documentation and types to properly describe optionality of configuration parameters.
+
## 2.0.0-alpha.5
Fix: Revert back to previous dependency modelling.
diff --git a/packages/plugins/AddressSearch/README.md b/packages/plugins/AddressSearch/README.md
index 3190b558e..b67b017e0 100644
--- a/packages/plugins/AddressSearch/README.md
+++ b/packages/plugins/AddressSearch/README.md
@@ -24,27 +24,30 @@ In `categoryProperties` and `groupProperties`, id strings called `groupId` and `
| fieldName | type | description |
| ------------------- | ------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| minLength | number | Minimal input length after which searches are started. |
-| waitMs | number | Debounce time in ms for search requests after last user input. |
| searchMethods | searchMethodsObject[] | Array of search method descriptions. Only searches configured here can be used. |
+| afterResultComponent | VueConstructor? | If given, this component will be rendered in the last line of every single search result. It will be forwarded its search result feature as prop `feature` of type `GeoJSON.Feature`, and the focus state of the result as prop `focus` of type `boolean`. |
+| addLoading | string? | Optional loading action name to start loading. |
+| categoryProperties | Record? | An object defining properties for a category. The searchMethod's categoryId is used as identifier. A service without categoryId does not have a fallback category. |
| customSearchMethods | Record? | An object with named search functions added to the existing set of configurable search methods. (See `addressSearch.searchMethodsObject.type`) This record's keys are added to that enum. |
| customSelectResult | Record? | An object that maps categoryIds to functions. These functions are then called as vuex store actions instead of the `selectResult` default implementation. This allows overriding selection behaviour with full store access. Use `''` as key for categoryless results. |
-| categoryProperties | Record? | An object defining properties for a category. The searchMethod's categoryId is used as identifier. A service without categoryId does not have a fallback category. |
| focusAfterSearch | boolean? | Whether the focus should switch to the first result after a successful search. Defaults to `false`. |
| groupProperties | Record? | An object defining properties for a group. The searchMethod's groupId is used as identifier. All services without groupId fall back to the key `"defaultGroup"`. |
+| minLength | number? | Minimal input length after which searches are started. Defaults to 0. |
+| removeLoading | string? | Optional loading action name to end loading. |
+| waitMs | number? | Debounce time in ms for search requests after last user input. Defaults to 0. |
#### addressSearch.searchMethodsObject
| fieldName | type | description |
| --------------- | ---------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| queryParameters | object | The object further describes details for the search request. Its contents vary by service type, see documentation below. |
-| label | string? | Display label. Can be a locale key. If grouped with other services, the group's label will be used instead. |
-| placeholder | string? | Placeholder string to display on input element. Can be a locale key. If grouped with other services, the group's placeholder will be used instead. |
-| hint | string? | Hint that is displayed below the input field if no other plugin-state-based hint is to be displayed. Can be a locale key. If grouped with other services, the group's hint will be used instead. |
| type | enum["bkg", "gazetteer", "wfs", "mpapi"] | Service type. Enum can be extended by configuration, see `addressSearch.customSearchMethods`. ⚠️ "gazetteer" is deprecated. Please use "mpapi" instead. |
| url | string | Search service URL. Should you require a service provider, please contact us for further information. |
-| groupId | string? | Default groupId is `"defaultGroup"`. All services with the same id are grouped and used together. See `addressSearch.groupProperties` for configuration options. If multiple groups exist, the UI offers a group switcher. |
| categoryId | string? | Grouped services can optionally be distinguished in the UI with categories. See `addressSearch.categoryProperties` for configuration options. |
+| groupId | string? | Default groupId is `"defaultGroup"`. All services with the same id are grouped and used together. See `addressSearch.groupProperties` for configuration options. If multiple groups exist, the UI offers a group switcher. |
+| hint | string? | Hint that is displayed below the input field if no other plugin-state-based hint is to be displayed. Can be a locale key. If grouped with other services, the group's hint will be used instead. |
+| label | string? | Display label. Can be a locale key. If grouped with other services, the group's label will be used instead. |
+| placeholder | string? | Placeholder string to display on input element. Can be a locale key. If grouped with other services, the group's placeholder will be used instead. |
+| queryParameters | object? | The object further describes details for the search request. Its contents vary by service type, see documentation below. |
#### addressSearch.customSearchMethod
@@ -87,10 +90,10 @@ With this, arbitrary click results can be supported. Please mind that undocument
| fieldName | type | description |
| ----------------- | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| label | string | Display label. Can be a locale key. |
-| placeholder | string? | Placeholder string to display on input element. Can be a locale key. |
-| hint | string? | Hint that is displayed below the input field if no other plugin-state-based hint is to be displayed. Can be a locale key. |
| resultDisplayMode | enum['mixed', 'categorized'] | Defaults to `'mixed'`. In `'mixed'`, results of all requested services are offered in a list in no specific order. In `'categorized'`, the results are listed by their searchService's categoryId. |
+| hint | string? | Hint that is displayed below the input field if no other plugin-state-based hint is to be displayed. Can be a locale key. |
| limitResults | number? | If set, only the first `n` results (per category in `categorized`) are displayed initially. All further results can be opened via UI. |
+| placeholder | string? | Placeholder string to display on input element. Can be a locale key. |
#### addressSearch.categoryProperties
@@ -104,17 +107,20 @@ These fields are interpreted by all implemented services.
| fieldName | type | description |
| ----------- | ------ | --------------------------------------- |
-| maxFeatures | number | Maximum amount of features to retrieve. |
+| maxFeatures | number? | Maximum amount of features to retrieve. Doesn't limit results if not set. |
##### addressSearch.searchMethodsObject.queryParameters (type:wfs)
| fieldName | type | description |
| ------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| typeName | string | Feature type to search for by name. |
| featurePrefix | string | XML feature prefix for WFS service. |
+| typeName | string | Feature type to search for by name. |
| xmlns | string | XML namespace to use in search. |
-| patterns | string[] | Allows specifying input patterns. In a single-field search, a pattern can be as easy as `{{theWholeThing}}`, where `theWholeThing` is also the feature field name to search in. In more complex scenarios, you may add separators and multiple fields, e.g. `{{gemarkung}} {{flur}} {{flstnrzae}}/{{flstnrnen}}` would fit many parcel search services. |
-| patternKeys | Record | Maps field names from patterns to regexes. Each field name has to have a definition. Each regex must have one capture group that is used to search. Contents next to it are ignored for the search and just used for matching. E.g. `'([0-9]+)$'` would be a value for a key that fits an arbitrary number string at the input's end. |
+| fieldName | string? | Name of the type's field to search in. Mutually exclusive to `patterns`. |
+| patterns | string[]? | Allows specifying input patterns. In a single-field search, a pattern can be as easy as `{{theWholeThing}}`, where `theWholeThing` is also the feature field name to search in. In more complex scenarios, you may add separators and multiple fields, e.g. `{{gemarkung}} {{flur}} {{flstnrzae}}/{{flstnrnen}}` would fit many parcel search services. Mutually exclusive to `fieldName`. |
+| patternKeys | Record? | Maps field names from patterns to regexes. Each field name has to have a definition. Each regex must have one capture group that is used to search. Contents next to it are ignored for the search and just used for matching. E.g. `'([0-9]+)$'` would be a value for a key that fits an arbitrary number string at the input's end. |
+| srsName | string? | Name of the projection (srs) for the query. |
+| useRightHandWildcard? | boolean? | By default, if searching for "search", it is sent as "search*". This behaviour can be deactivated by setting this parameter to `false`. |
Since inputs may overlap with multiple patterns, multiple queries are fired and executed on the WFS until the `maxFeatures` requirement is met, beginning with the pattern that 'looks like the user input the most'. The best-fitting pattern on the returned features will be used to generate a display string. When two patterns fit best, the first one is used.
@@ -124,10 +130,12 @@ Since inputs may overlap with multiple patterns, multiple queries are fired and
| fieldName | type | description |
| ------------- | -------- | ------------------------------------------------------------------------------------------------------------- |
-| memberSuffix | string | Elements to interpret are fetched from response XML as `wfs:memberSuffix`; fitting suffix must be configured. |
-| namespaces | string[] | Namespaces to add to the request. |
+| epsg | `EPSG:${string}` | EPSG code of the projection to use. |
| fieldName | string[] | Field names of service to search in. |
+| memberSuffix | string | Elements to interpret are fetched from response XML as `wfs:memberSuffix`; fitting suffix must be configured. |
+| namespaces | string \| string[] | Namespaces to add to the request. |
| storedQueryId | string | Name of the WFS-G stored query that is to be used. |
+| version | '1.1.0' \| '2.0.0'? | WFS version used. Defaults to `'2.0.0'`. |
##### addressSearch.searchMethodsObject.queryParameters (type:mpapi)
diff --git a/packages/plugins/AddressSearch/src/components/Results.vue b/packages/plugins/AddressSearch/src/components/Results.vue
index e89e3ed88..1ab83a419 100644
--- a/packages/plugins/AddressSearch/src/components/Results.vue
+++ b/packages/plugins/AddressSearch/src/components/Results.vue
@@ -52,11 +52,19 @@
@keydown.down.prevent.stop="(event) => focusNextElement(true, event)"
@keydown.up.prevent.stop="(event) => focusNextElement(false, event)"
@click="selectResult({ feature, categoryId })"
+ @focus="focusIndex = `${index}-${innerDex}`"
+ @blur="focusIndex = ''"
>
+ ({
openCategories: [] as string[],
+ focusIndex: '',
}),
computed: {
...mapGetters(['clientHeight', 'hasWindowSize']),
...mapGetters('plugin/addressSearch', [
+ 'afterResultComponent',
'featuresAvailable',
'featureListsWithCategory',
'focusAfterSearch',
diff --git a/packages/plugins/AddressSearch/src/store/getters.ts b/packages/plugins/AddressSearch/src/store/getters.ts
index 0cedc48cb..d13e7fb71 100644
--- a/packages/plugins/AddressSearch/src/store/getters.ts
+++ b/packages/plugins/AddressSearch/src/store/getters.ts
@@ -61,6 +61,9 @@ const getters: PolarGetterTree = {
...(rootGetters.configuration?.addressSearch || {}),
}
},
+ afterResultComponent(_, { addressSearchConfiguration }) {
+ return addressSearchConfiguration.afterResultComponent || null
+ },
minLength(_, { addressSearchConfiguration }) {
return addressSearchConfiguration.minLength
},
diff --git a/packages/plugins/AddressSearch/src/types.ts b/packages/plugins/AddressSearch/src/types.ts
index d7d9e1734..dac122e71 100644
--- a/packages/plugins/AddressSearch/src/types.ts
+++ b/packages/plugins/AddressSearch/src/types.ts
@@ -6,6 +6,7 @@ import {
AddressSearchGroupProperties,
AddressSearchCategoryProperties,
} from '@polar/lib-custom-types'
+import { VueConstructor } from 'vue'
import { WFSVersion } from '@polar/lib-get-features'
import { Feature, FeatureCollection } from 'geojson'
@@ -39,6 +40,8 @@ export interface BKGParameters extends QueryParameters {
accessToken: string
/** X-Api-Key header used for authentication, if given */
apiKey: string
+ /** Currently used projection of the map */
+ epsg: `EPSG:${string}`
/** Limit search to the defined filter attributes */
filter?: BKGFilter
}
@@ -48,15 +51,17 @@ export interface BKGParameters extends QueryParameters {
* Further information can be retrieved from the WFS specification
*/
export interface GazetteerParameters extends QueryParameters {
+ /** Currently used projection of the map */
+ epsg: `EPSG:${string}`
/** The fieldName to be searched within the service */
fieldName: string
- // TODO: It is assumed, for now, that a WFS-G always uses a Stored Query
- /** Id of the stored query of the service to request the features */
- storedQueryId: string
memberSuffix: MemberSuffix
/** The namespaces of the service; obsolete with WFS\@3.0.0 as GeoJSON will be the standard response */
namespaces: string | string[]
// TODO: Check whether a WFS-G can only be a WFS@2.0.0 or also a WFS@1.1.0
+ // TODO: It is assumed, for now, that a WFS-G always uses a Stored Query
+ /** Id of the stored query of the service to request the features */
+ storedQueryId: string
version?: WFSVersion
}
@@ -126,6 +131,7 @@ export interface AddressSearchGetters extends AddressSearchState {
groupSelectOptions: string[]
featureListsWithCategory: FeatureListWithCategory[]
focusAfterSearch: boolean
+ afterResultComponent: VueConstructor | null
}
export type AddressSearchAutoselect = 'first' | 'only' | 'never'
diff --git a/packages/plugins/Attributions/CHANGELOG.md b/packages/plugins/Attributions/CHANGELOG.md
index 170945934..6570e2159 100644
--- a/packages/plugins/Attributions/CHANGELOG.md
+++ b/packages/plugins/Attributions/CHANGELOG.md
@@ -4,6 +4,7 @@
- Breaking: As a result of the bundling with `rollup`, the styles of the package need to be imported via `@polar/plugin-attributions/styles.css`.
- Feature: The package is now being bundled by `rollup` before being published. This allows for a smaller package size and better compatibility with other packages.
+- Refactor: Replace redundant props with computed properties.
## 1.2.1
diff --git a/packages/plugins/Attributions/README.md b/packages/plugins/Attributions/README.md
index 9e6ee29e5..e5a028280 100644
--- a/packages/plugins/Attributions/README.md
+++ b/packages/plugins/Attributions/README.md
@@ -12,11 +12,11 @@ All parameters are optional. However, setting neither `layerAttributions` nor `s
| fieldName | type | description |
| ------------------ | ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
+| initiallyOpen | boolean? | Whether the information box is open by default. Only usable when renderType is set to 'independent', otherwise the IconMenu handles this. |
| layerAttributions | layerAttribution[]? | List of attributions that are shown when the matching layer is visible. |
-| staticAttributions | string[]? | List of static attributions that are always shown. |
-| renderType | 'iconMenu' \| 'independent'? | Whether this plugin ('independent') or the IconMenu should handle opening the information box. Defaults to 'independent'. |
| listenToChanges | string[]? | Store variable paths to listen to for changes. Will update the currently visible layers depending on the current map state on changes to these values. Please mind that, when referencing another plugin, that plugin must be in `addPlugins` before this one. |
-| initiallyOpen | boolean? | Whether the information box is open by default. Only usable when renderType is set to 'independent', otherwise the IconMenu handles this. |
+| renderType | 'iconMenu' \| 'independent'? | Whether this plugin ('independent') or the IconMenu should handle opening the information box. Defaults to 'independent'. |
+| staticAttributions | string[]? | List of static attributions that are always shown. |
| windowWidth | number? | If `renderType` is set to `independent`, sets the width of the container of the attributions. Defaults to 500. |
#### attributions.layerAttribution
diff --git a/packages/plugins/Attributions/src/components/AttributionContent.vue b/packages/plugins/Attributions/src/components/AttributionContent.vue
index e6c7b9f18..19a297fb6 100644
--- a/packages/plugins/Attributions/src/components/AttributionContent.vue
+++ b/packages/plugins/Attributions/src/components/AttributionContent.vue
@@ -1,12 +1,6 @@
-
+
{{ $t('common:plugins.attributions.title') }}
@@ -27,19 +21,18 @@ import noop from '@repositoryname/noop'
export default Vue.extend({
name: 'AttributionContent',
- props: {
- maxWidth: {
- type: [Number, String],
- default: 'inherit',
- },
- width: {
- type: [Number, String],
- default: 'inherit',
- },
- },
computed: {
- ...mapGetters(['language']),
- ...mapGetters('plugin/attributions', ['mapInfo', 'renderType']),
+ ...mapGetters([
+ 'clientWidth',
+ 'hasSmallWidth',
+ 'hasWindowSize',
+ 'language',
+ ]),
+ ...mapGetters('plugin/attributions', [
+ 'mapInfo',
+ 'renderType',
+ 'windowWidth',
+ ]),
cardText(): string {
noop(this.language)
return this.mapInfo
@@ -51,6 +44,22 @@ export default Vue.extend({
)
.join(' ')
},
+ renderIndependently() {
+ return this.renderType === 'independent'
+ },
+ color() {
+ return this.renderIndependently ? '#ffffffdd' : ''
+ },
+ maxWidth() {
+ return this.renderIndependently
+ ? this.hasWindowSize && this.hasSmallWidth
+ ? this.clientWidth * 0.85
+ : 1080
+ : 'inherit'
+ },
+ width() {
+ return this.renderIndependently ? this.windowWidth : 'inherit'
+ },
},
mounted() {
// NOTE: sources will always be defined unless someone removes the ref from the v-card-text element
diff --git a/packages/plugins/Attributions/src/components/Attributions.vue b/packages/plugins/Attributions/src/components/Attributions.vue
index eb55ae87f..010e6534a 100644
--- a/packages/plugins/Attributions/src/components/Attributions.vue
+++ b/packages/plugins/Attributions/src/components/Attributions.vue
@@ -3,12 +3,7 @@
v-if="renderType === 'independent'"
class="polar-plugin-attributions-wrapper"
>
-
+
@@ -27,17 +22,7 @@ export default Vue.extend({
AttributionContent,
},
computed: {
- ...mapGetters(['clientWidth', 'hasSmallWidth', 'hasWindowSize']),
- ...mapGetters('plugin/attributions', [
- 'renderType',
- 'windowIsOpen',
- 'windowWidth',
- ]),
- maxWidth() {
- return this.hasWindowSize && this.hasSmallWidth
- ? this.clientWidth * 0.85
- : 1080
- },
+ ...mapGetters('plugin/attributions', ['renderType', 'windowIsOpen']),
},
})
diff --git a/packages/plugins/Draw/CHANGELOG.md b/packages/plugins/Draw/CHANGELOG.md
index 8d3413fdc..a0d741c01 100644
--- a/packages/plugins/Draw/CHANGELOG.md
+++ b/packages/plugins/Draw/CHANGELOG.md
@@ -4,6 +4,7 @@
- Breaking: As a result of the bundling with `rollup`, the styles of the package need to be imported via `@polar/plugin-draw/styles.css`.
- Feature: The package is now being bundled by `rollup` before being published. This allows for a smaller package size and better compatibility with other packages.
+- Fix: Adjust documentation to properly describe optionality of configuration parameters.
## 1.1.0
diff --git a/packages/plugins/Draw/README.md b/packages/plugins/Draw/README.md
index b3f4091d8..7070b1c5e 100644
--- a/packages/plugins/Draw/README.md
+++ b/packages/plugins/Draw/README.md
@@ -33,23 +33,23 @@ The styling of the drawn features can be configured to overwrite the default ol-
| fieldName | type | description |
| ------------------- | -------- | ---------------------------------------------------------------------------- |
-| selectableDrawModes | string[] | List 'Point', 'LineString', 'Circle', 'Text' and/or 'Polygon' as desired. |
-| style | style | Please see example below for styling options. |
-| textStyle | object | Use this object with properties 'font' and 'textColor' to style text feature |
+| selectableDrawModes | string[]? | List 'Point', 'LineString', 'Circle', 'Text' and/or 'Polygon' as desired. All besides 'Text' are selectable by default. |
+| style | style? | Please see example below for styling options. Defaults to standard OpenLayers styling. |
+| textStyle | object? | Use this object with properties 'font' and 'textColor' to style text feature. |
##### draw.textStyle
| fieldName | type | description |
| --------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------- |
-| font | object \| string | Style the font of the text feature with either css font properties or use font as an object with properties 'size' and 'family' |
-| textColor | string | Define text color in hex or rgb / rgba code |
+| font | object \| string | Style the font of the text feature with either css font properties or use font as an object with properties 'size' and 'family'. |
+| textColor | string? | Define text color in hex or rgb / rgba code. |
##### draw.textStyle.font
| fieldName | type | description |
| --------- | -------- | ----------------------------------------------------------------------------------- |
-| size | number[] | Array with numbers that define the available text sizes that a user can choose from |
-| family | string | Font family |
+| family | string? | Font family. |
+| size | number[]? | Array with numbers that define the available text sizes that a user can choose from |
#### draw.style (by example)
diff --git a/packages/plugins/Export/CHANGELOG.md b/packages/plugins/Export/CHANGELOG.md
index 1d8e6efeb..f9f8892bd 100644
--- a/packages/plugins/Export/CHANGELOG.md
+++ b/packages/plugins/Export/CHANGELOG.md
@@ -5,6 +5,7 @@
- Breaking: As a result of the bundling with `rollup`, the styles of the package need to be imported via `@polar/plugin-export/styles.css`.
- Feature: The package is now being bundled by `rollup` before being published. This allows for a smaller package size and better compatibility with other packages.
- Fix: PDF export with download:false now works correctly.
+- Fix: Adjust documentation to properly describe optionality of configuration parameters.
## 1.2.0
diff --git a/packages/plugins/Export/README.md b/packages/plugins/Export/README.md
index de71ba3dc..8f8f2c888 100644
--- a/packages/plugins/Export/README.md
+++ b/packages/plugins/Export/README.md
@@ -10,10 +10,10 @@ The Export plugin offers users to download the currently visible map canvas in a
| fieldName | type | description |
| --------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| showJpg | boolean | Tools offers current map view as JPG. |
-| showPdf | boolean | Tools offers current map view as PDF. |
-| showPng | boolean | Tools offers current map view as PNG. |
-| download | boolean | Whether file is offered for download. By default, no download will happen, and the using service is supposed to register whether a "screenshot" has been taken and react accordingly. |
+| download | boolean? | Whether file is offered for download. By default, no download will happen, and the using service is supposed to register whether a "screenshot" has been taken and react accordingly. |
+| showJpg | boolean? | Tools offers current map view as JPG. Defaults to `true`. |
+| showPdf | boolean? | Tools offers current map view as PDF. Defaults to `true`. |
+| showPng | boolean? | Tools offers current map view as PNG. Defaults to `true`. |
## Store
diff --git a/packages/plugins/Filter/CHANGELOG.md b/packages/plugins/Filter/CHANGELOG.md
index 2c6346736..52f43d503 100644
--- a/packages/plugins/Filter/CHANGELOG.md
+++ b/packages/plugins/Filter/CHANGELOG.md
@@ -12,7 +12,19 @@
- Breaking: As a result of the bundling with `rollup`, the styles of the package need to be imported via `@polar/plugin-filter/styles.css`.
- Feature: The package is now being bundled by `rollup` before being published. This allows for a smaller package size and better compatibility with other packages.
+
+## 1.1.2
+
+- Fix: Features with categories that are not listed in `knownValues` are never displayed now. Previously, they were initially visible, but disappeared once any filter was touched.
+- Fix: It was possible to have features visible that were loaded after the filter was applied and that would have been filtered out. This has been resolved.
+- Fix: Adjust documentation to properly describe optionality of configuration parameters.
+- Fix: Correctly log an error if required parameter `layers` is not provided.
+
+## 1.1.1
+
- Fix: Configurations without time element could sometimes error on filtering operations.
+- Fix: Filtering by custom timeframe added an additional day into the range.
+- Fix: Filtering by a single day selected features of the next day only.
## 1.1.0
diff --git a/packages/plugins/Filter/README.md b/packages/plugins/Filter/README.md
index 787e5ffe9..d32a6c2a3 100644
--- a/packages/plugins/Filter/README.md
+++ b/packages/plugins/Filter/README.md
@@ -27,8 +27,8 @@ The following chapters contain drafts in this format. Please mind that they neit
| fieldName | type | description |
| - | - | - |
+| knownValues | (string \| number \| boolean \| null)[] | Array of known values for the feature properties. Each entry will result in a checkbox that allows filtering the appropriate features. Properties not listed will not be filterable and never be visible. The technical name will result in a localization key that can be configured on a per-client basis. |
| targetProperty | string | Target property to filter by. This is the name (that is, key) of a feature property. |
-| knownValues | (string \| number \| boolean \| null)[] | Array of known values for the feature properties. Each entry will result in a checkbox that allows filtering the appropriate features. Properties not listed will not be filterable. The technical name will result in a localization key that can be configured on a per-client basis. |
| selectAll | boolean? | If true, a checkbox is added to de/select all `knownValues` (above) at once. Defaults to `false`. |
For example, `{targetProperty: 'favouriteIceCream', knownValues: ['chocolate', 'vanilla', 'strawberry'], selectAll: true}` will add these checkboxes:
@@ -45,9 +45,9 @@ For example, `{targetProperty: 'favouriteIceCream', knownValues: ['chocolate', '
| fieldName | type | description |
| - | - | - |
| targetProperty | string | Target property to filter by. |
+| freeSelection | freeSelection? | Provide a more dynamic configurable from-to chooser for timeframes. |
| last | options[]? | Array of options to create for a `last` filter, e.g. "last 10 days". |
| next | options[]? | Array of options to create for a `next` filter, e.g. "next 10 day". |
-| freeSelection | freeSelection? | Provide a more dynamic configurable from-to chooser for timeframes. |
| pattern | string? | Pattern the target string uses for its date formatting. Defaults to `'YYYY-MM-DD'`. Only 'Y', 'M', and 'D' are interpreted. All other characters are considered filler. Example: A feature has `"AA202001-04"` as property value that is supposed to convey a date. Setting `pattern` to `"--YYYYDD-MM"` would interpret it as the 1st of April, 2020. |
Of all time restrictions, at most one can be selected at any time. The produced options are selectable by radio buttons.
@@ -57,7 +57,7 @@ Of all time restrictions, at most one can be selected at any time. The produced
| fieldName | type | description |
| - | - | - |
| amounts | number[] | Offer radio buttons for these amounts of `unit`. The rest of the current day is additionally included in the range. |
-| unit | 'days' | Implemented units. Currently, only `'days'` are supported. Defaults to `'days'`. |
+| unit | 'days'? | Implemented units. Currently, only `'days'` are supported. Defaults to `'days'`. |
For example, `{amounts: [3, 7], unit: 'days'}` as value for `last` will add these radio buttons:
@@ -73,7 +73,7 @@ In `'days'` mode, the selections will always include full days, and additionally
| fieldName | type | description |
| - | - | - |
| now | ('until' \| 'from')? | If set, only time points *until* now or *from* now are selectable, including the current time point. |
-| unit | 'days' | Implemented units. Currently, only `'days'` are supported. Defaults to `'days'`. |
+| unit | 'days'? | Implemented units. Currently, only `'days'` are supported. Defaults to `'days'`. |
For example, `{now: 'until', unit: 'days'}` will add this radio button:
diff --git a/packages/plugins/Filter/src/store/index.ts b/packages/plugins/Filter/src/store/index.ts
index aafd42cce..bda8efd75 100644
--- a/packages/plugins/Filter/src/store/index.ts
+++ b/packages/plugins/Filter/src/store/index.ts
@@ -3,6 +3,7 @@ import {
generateSimpleMutations,
} from '@repositoryname/vuex-generators'
import { FilterConfiguration, PolarModule } from '@polar/lib-custom-types'
+import ClusterSource from 'ol/source/Cluster'
import ChooseTimeFrame from '../components/ChooseTimeFrame.vue'
import {
FilterGetters,
@@ -11,7 +12,10 @@ import {
LayerId,
TargetProperty,
} from '../types'
-import { updateFeatureVisibility } from '../utils/updateFeatureVisibility'
+import {
+ getLayer,
+ updateFeatureVisibility,
+} from '../utils/updateFeatureVisibility'
import { setState } from '../utils/setState'
import { arrayOnlyContains } from '../utils/arrayOnlyContains'
import { parseTimeOption } from '../utils/parseTimeOption'
@@ -28,7 +32,17 @@ export const makeStoreModule = () => {
namespaced: true,
state: getInitialState(),
actions: {
- setupModule({ getters: { filterConfiguration }, commit }): void {
+ setupModule({
+ getters: { filterConfiguration },
+ rootGetters: { map },
+ commit,
+ dispatch,
+ }): void {
+ if (Object.entries(filterConfiguration.layers).length === 0) {
+ console.error(
+ '@polar/plugin-filter: No configuration for parameter "layers" was found. Plugin will not be usable.'
+ )
+ }
Object.entries(filterConfiguration.layers).forEach(
([layerId, { categories, time }]) => {
if (categories) {
@@ -54,6 +68,17 @@ export const makeStoreModule = () => {
},
})
}
+ // apply filter effects on layer loads
+ // @ts-expect-error | only layers with getSource allowed
+ let source = getLayer(map, layerId).getSource()
+ while (source instanceof ClusterSource) {
+ source = source.getSource()
+ }
+ source.on('featuresloadend', () =>
+ dispatch('updateFeatureVisibility', layerId)
+ )
+ // initially update visibility in case loading already took place
+ dispatch('updateFeatureVisibility', layerId)
}
)
},
diff --git a/packages/plugins/Filter/src/utils/updateFeatureVisibility.ts b/packages/plugins/Filter/src/utils/updateFeatureVisibility.ts
index cdb2c3c47..b83efdcdd 100644
--- a/packages/plugins/Filter/src/utils/updateFeatureVisibility.ts
+++ b/packages/plugins/Filter/src/utils/updateFeatureVisibility.ts
@@ -22,7 +22,7 @@ const getFreeSelectionLimits = (clickLimits: Date[]): Date[] => {
.sort()
.map((x) => new Date(x))
if (!limits[1]) {
- limits[1] = limits[0]
+ limits[1] = new Date(limits[0])
}
return limits
}
@@ -87,7 +87,7 @@ const doesFeaturePassTimeFilter = (
limits[type === 'last' ? 1 : 0] = new Date(Date.now())
}
limits[0].setHours(0, 0, 0, 0)
- limits[1].setHours(24, 0, 0, 0)
+ limits[1].setHours(23, 59, 59, 999)
return limits[0] <= featureDate && featureDate <= limits[1]
}
@@ -111,7 +111,7 @@ const doesFeaturePassFilter = (
)
}
-const getLayer = (map: Map, layerId: LayerId): BaseLayer => {
+export const getLayer = (map: Map, layerId: LayerId): BaseLayer => {
const layer = map
.getLayers()
.getArray()
@@ -150,6 +150,7 @@ export const updateFeatureVisibility = ({
.flat(1)
// only update finally to prevent overly recalculating clusters
source.clear()
+
updateFeatures.forEach((feature) => {
const targetStyle = doesFeaturePassFilter(
feature,
diff --git a/packages/plugins/Fullscreen/CHANGELOG.md b/packages/plugins/Fullscreen/CHANGELOG.md
index dc74e6780..fc267b373 100644
--- a/packages/plugins/Fullscreen/CHANGELOG.md
+++ b/packages/plugins/Fullscreen/CHANGELOG.md
@@ -4,6 +4,8 @@
- Breaking: As a result of the bundling with `rollup`, the styles of the package need to be imported via `@polar/plugin-fullscreen/styles.css`.
- Feature: The package is now being bundled by `rollup` before being published. This allows for a smaller package size and better compatibility with other packages.
+- Fix: Adjust documentation to properly describe optionality of configuration parameters.
+- Refactor: Remove redundant props regarding positioning of the tooltip.
## 1.2.1
diff --git a/packages/plugins/Fullscreen/README.md b/packages/plugins/Fullscreen/README.md
index 8bda8f897..c9ee3db42 100644
--- a/packages/plugins/Fullscreen/README.md
+++ b/packages/plugins/Fullscreen/README.md
@@ -11,7 +11,7 @@ The fullscreen plugin allows viewing the map in fullscreen mode. It relies solel
| fieldName | type | description |
|-------------------|------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| renderType | 'iconMenu' \| 'independent'? | Whether the fullscreen button is being rendered independently or as part of the IconMenu. Defaults to 'independent'. |
-| targetContainerId | string | Specifies the html element on which the fullscreen mode should be applied. If the parameter is omitted, it falls back to the configured global field `containerId`. |
+| targetContainerId | string? | Specifies the html element on which the fullscreen mode should be applied. If the parameter is omitted, it falls back to the configured global field `containerId`. |
## Store
diff --git a/packages/plugins/Fullscreen/src/components/Fullscreen.vue b/packages/plugins/Fullscreen/src/components/Fullscreen.vue
index 1624156a4..2fe95f3c6 100644
--- a/packages/plugins/Fullscreen/src/components/Fullscreen.vue
+++ b/packages/plugins/Fullscreen/src/components/Fullscreen.vue
@@ -1,10 +1,5 @@
-
+ = {
setupModule({ getters, commit, dispatch }): void {
dispatch('addMarkerLayer')
- // NOTE: limited support across browsers
- navigator.permissions.query({ name: 'geolocation' }).then((result) => {
- if (result.state === 'denied') {
- commit('setIsGeolocationDenied', true)
- }
- })
+ // NOTE: limited support across browsers; nice but optional initially
+ if (navigator.permissions?.query) {
+ navigator.permissions.query({ name: 'geolocation' }).then((result) => {
+ if (result.state === 'denied') {
+ commit('setIsGeolocationDenied', true)
+ }
+ })
+ }
if (getters.checkLocationInitially) {
dispatch('track')
}
diff --git a/packages/plugins/Gfi/CHANGELOG.md b/packages/plugins/Gfi/CHANGELOG.md
index f567480d5..b39989873 100644
--- a/packages/plugins/Gfi/CHANGELOG.md
+++ b/packages/plugins/Gfi/CHANGELOG.md
@@ -1,5 +1,12 @@
# CHANGELOG
+## unpublished
+
+- 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`.
+- Refactor: Use core getter `clientWidth` instead of local computed value.
+
## 2.0.0-alpha.6
- Chore: Add `compileTemplate` to `rollup-plugin-vue`.
diff --git a/packages/plugins/Gfi/README.md b/packages/plugins/Gfi/README.md
index 23517d78b..aa75b086b 100644
--- a/packages/plugins/Gfi/README.md
+++ b/packages/plugins/Gfi/README.md
@@ -12,54 +12,50 @@ The GFI plugin can be used to fetch and optionally display GFI (GetFeatureInfo)
| fieldName | type | description |
| -------------------- | ------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| layers | Record | Maps a string (must be a layer ID) to a behaviour configuration for that layer. |
| coordinateSources | string[] | The GFI plugin will react to these coordinate positions in the store. This allows it to react to e.g. the address search of the pins plugin. Please see example configuration for the common use-cases. Please mind that, when referencing another plugin, that plugin must be in `addPlugins` before this one. |
-| mode | enum["bboxDot", "intersects"] | Method of calculating which feature has been chosen by the user. `bbodyDot` 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` |
-| customHighlightStyle | object | 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. |
+| layers | Record | 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): Record | This method can be used to extend, filter, or otherwise modify a GFI result. |
+| afterLoadFunction | function (featuresByLayerId: Record): Record? | This method can be used to extend, filter, or otherwise modify a GFI result. |
+| 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. |
+| 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 | Vue? | Allows overriding the GfiContent.vue component for a custom design. Coding knowledge required to use this feature. |
+| 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'`. |
| 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'. |
##### gfi.gfiLayerConfiguration
| fieldName | type | description |
| -------------- | ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| window | boolean | If true, properties will be shown in the map client. |
-| geometry | boolean | If true, feature geometry will be highlighted within the map. |
-| 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. |
-| 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`. |
-| exportProperty | string | Property of the features of a service having an url usable to trigger a download of features as a document. |
+| 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. |
+| 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`. |
+
+Additionally, if using a WMS service, the following properties can be configured as well.
+
+| fieldName | type | description |
+| - | - | - |
+| filterBy | 'clickPosition'? | Some WMS services may return features close to the clicked position. By setting the this value `clickPosition`, only features that intersect the clicked position are shown to the user. Defaults to showing all features. |
+| format | 'GML' \| 'GML2' \| 'GML3' \| 'GML32' \| 'text'? | If the `infoFormat` is not set to `'application/geojson'`´, this can be configured to be the known file format of the response. If not given, the format is parsed from the response data. |
##### gfi.customHighlightStyle
| fieldName | type | description |
| --------- | ------ | ------------------------------------ |
-| stroke | object | Object for defining the stroke style |
-| fill | object | Object for defining the fill style |
-
-##### gfi.customHighlightStyle.stroke
-
-| fieldName | type | description |
-| --------- | ------ | ------------ |
-| color | string | Color value |
-| width | number | Stroke width |
-
-##### gfi.customHighlightStyle.fill
-
-| fieldName | type | description |
-| --------- | ------ | ----------- |
-| color | string | Color value |
+| fill | ol/style/Fill? | Object for defining the fill style. See [OpenLayers documentation](https://openlayers.org/en/latest/apidoc/module-ol_style_Fill-Fill.html) for full options. |
+| stroke | ol/style/Stroke? | Object for defining the stroke style. See [OpenLayers documentation](https://openlayers.org/en/latest/apidoc/module-ol_style_Stroke-Stroke.html) for full options. |
##### gfi.featureList
| fieldName | type | description |
| - | - | - |
| mode | 'visible' \| 'loaded' | Whether to show only features currently visible in the map view's bounding box or to display all loaded features. In the latter case, if you desire to display all features of a layer (seen or not), set its loading strategy to `'all'`. |
-| pageLength | number? | A number >0 that sets the limit to the feature list's length. If the length is surpassed, additional features can be reached by using the pagination that is generated in such a case. If not defined, the list can be of arbitrary length. |
-| text | (function | string)[] | Array of one to three entries that will produce title, subtitle, and an additional subtitle for the list view. If string, the text item will simply be that feature's value for the denoted property. If function, it's assumed to match the function signature `(feature: OpenLayersFeature): string`, and the returned string will be used for the text item. |
| bindWithCoreHoverSelect | boolean? | If `true`, the hover/select fields in the core's state will be listened to and interacted with. This will result in a bilateral hovering and selecting of features with the core. Defaults to `false`. |
+| pageLength | number? | A number >0 that sets the limit to the feature list's length. If the length is surpassed, additional features can be reached by using the pagination that is generated in such a case. If not defined, the list can be of arbitrary length. |
+| text | (function \| string)[]? | Array of one to three entries that will produce title, subtitle, and an additional subtitle for the list view. If string, the text item will simply be that feature's value for the denoted property. If function, it's assumed to match the function signature `(feature: OpenLayersFeature): string`, and the returned string will be used for the text item. |
#### Example configuration
diff --git a/packages/plugins/Gfi/src/components/Feature.vue b/packages/plugins/Gfi/src/components/Feature.vue
index 2b529e517..17853b880 100644
--- a/packages/plugins/Gfi/src/components/Feature.vue
+++ b/packages/plugins/Gfi/src/components/Feature.vue
@@ -17,23 +17,17 @@
-
+
diff --git a/packages/plugins/Gfi/src/components/FeatureTableBody.vue b/packages/plugins/Gfi/src/components/FeatureTableBody.vue
index 0a5503ced..dc0926d64 100644
--- a/packages/plugins/Gfi/src/components/FeatureTableBody.vue
+++ b/packages/plugins/Gfi/src/components/FeatureTableBody.vue
@@ -9,7 +9,7 @@
:alt="$t('common:plugins.gfi.property.imageAlt')"
:title="$t('common:plugins.gfi.property.linkTitle')"
:aria-label="$t('common:plugins.gfi.property.linkTitle')"
- :height="photoHeight < 200 ? 200 : photoHeight"
+ :height="photoHeight"
width="auto"
/>
@@ -32,23 +32,15 @@
diff --git a/packages/plugins/Gfi/src/store/getters.ts b/packages/plugins/Gfi/src/store/getters.ts
index 87e95026d..242b2a374 100644
--- a/packages/plugins/Gfi/src/store/getters.ts
+++ b/packages/plugins/Gfi/src/store/getters.ts
@@ -41,9 +41,41 @@ const getters: PolarGetterTree = {
? gfiConfiguration.afterLoadFunction
: null
},
+ currentProperties(
+ _,
+ { exportPropertyLayerKeys, visibleWindowFeatureIndex, windowFeatures }
+ ) {
+ const properties = {
+ ...windowFeatures[visibleWindowFeatureIndex],
+ }
+ const exportProperty =
+ exportPropertyLayerKeys[properties.polarInternalLayerKey]
+ if (exportProperty?.length > 0) {
+ delete properties[exportProperty]
+ }
+ return properties
+ },
layerKeys(_, { gfiConfiguration }) {
return Object.keys(gfiConfiguration?.layers || {})
},
+ exportProperty(
+ _,
+ {
+ currentProperties,
+ exportPropertyLayerKeys,
+ visibleWindowFeatureIndex,
+ windowFeatures,
+ }
+ ) {
+ if (currentProperties) {
+ const property =
+ exportPropertyLayerKeys[currentProperties.polarInternalLayerKey]
+ return property.length > 0
+ ? (windowFeatures[visibleWindowFeatureIndex]?.[property] as string)
+ : ''
+ }
+ return ''
+ },
exportPropertyLayerKeys(_, { gfiConfiguration }) {
return Object.entries(gfiConfiguration?.layers || {}).reduce(
(accumulator, [key, { exportProperty }]) => ({
@@ -53,6 +85,10 @@ const getters: PolarGetterTree = {
{} as Record
)
},
+ /** only show switch buttons if multiple property sets are available */
+ showSwitchButtons(_, { windowFeatures }) {
+ return windowFeatures.length > 1
+ },
windowLayerKeys(_, { gfiConfiguration }): string[] {
return Object.entries(gfiConfiguration?.layers || {}).reduce(
(accumulator, [key, { window }]) => {
diff --git a/packages/plugins/Gfi/src/types.ts b/packages/plugins/Gfi/src/types.ts
index 352e4f4ca..c12bf4058 100644
--- a/packages/plugins/Gfi/src/types.ts
+++ b/packages/plugins/Gfi/src/types.ts
@@ -41,6 +41,8 @@ export interface GfiState {
export interface GfiGetters extends GfiState {
afterLoadFunction: GfiAfterLoadFunction | null
+ currentProperties: GeoJsonProperties
+ exportProperty: string
exportPropertyLayerKeys: Record
/** subset of layerKeys, where features' geometries are to be shown on map */
geometryLayerKeys: string[]
@@ -56,6 +58,7 @@ export interface GfiGetters extends GfiState {
renderMoveHandle: boolean
renderType: RenderType
showList: boolean
+ showSwitchButtons: boolean
/** subset of layerKeys, where features' properties are to be shown in UI */
windowLayerKeys: string[]
/**
diff --git a/packages/plugins/IconMenu/CHANGELOG.md b/packages/plugins/IconMenu/CHANGELOG.md
index 8e12147d4..8151343cb 100644
--- a/packages/plugins/IconMenu/CHANGELOG.md
+++ b/packages/plugins/IconMenu/CHANGELOG.md
@@ -2,6 +2,7 @@
## unpublished
+- Feature: Remove requirement of `isHorizontal` prop for plugins as the relevant logic is implemented in `@polar/core`.
- Fix: Resolve issue with `initiallyOpen` not working as expected.
## 2.0.0-alpha.3
diff --git a/packages/plugins/IconMenu/README.md b/packages/plugins/IconMenu/README.md
index 9ad681261..ff54980b6 100644
--- a/packages/plugins/IconMenu/README.md
+++ b/packages/plugins/IconMenu/README.md
@@ -26,8 +26,7 @@ Usage during client build:
/*
* Icon for icon menu button
* If given, render a button with the icon. When clicked, open the content of the configured plugin.
- * If not given, render the plugin content as is inside the IconMenu. The component of the plugin
- * should in that case implement the prop `isHorizontal: boolean`.
+ * If not given, render the plugin content as is inside the IconMenu.
* Current examples for the usage without icon include Zoom and Fullscreen,
*/
icon: 'fa-book-atlas',
diff --git a/packages/plugins/IconMenu/src/components/IconMenu.vue b/packages/plugins/IconMenu/src/components/IconMenu.vue
index 5df4ad784..b445ac6e1 100644
--- a/packages/plugins/IconMenu/src/components/IconMenu.vue
+++ b/packages/plugins/IconMenu/src/components/IconMenu.vue
@@ -5,20 +5,14 @@
v-for="({ plugin, icon, id, hint }, index) of menus"
:key="index"
:class="
- isHorizontal ? 'icon-menu-list-item-horizontal' : 'icon-menu-list-item'
+ deviceIsHorizontal
+ ? 'icon-menu-list-item-horizontal'
+ : 'icon-menu-list-item'
"
>
-
+
-
+ | Title to be displayed for sub-layer. If false, layer name itself will be used as given in service description 'layers' field. If true, it is assumed a name exists in the layer's GetCapabilities, and that will be used. If Record, it maps the layer name to an arbitrary display name given by the configuration. The `layerName` can be any string. |
-| legend | Boolean \| Record | Legend image to be used for sub-layer. If false, no image is displayed. If true, it is assumed an image exists in the layer's GetCapabilities, and that will be used. If Record, it maps the layer name to a linked image. The `legendUrl` can be any valid reachable image URL. |
+| legend | boolean \| Record? | Legend image to be used for sub-layer. If false, no image is displayed. If true, it is assumed an image exists in the layer's GetCapabilities, and that will be used. If Record, it maps the layer name to a linked image. The `legendUrl` can be any valid reachable image URL. |
+| order | string? | Optional. If not given, field `layers` from service description will be used, and defines order of options. If layer defined in service description's `layers` and `order`, it's initially on. If only in `order`, it's initially off. If only in `layers`, it's always-on. If in neither, it's always-off. |
+| title | boolean \| Record? | Title to be displayed for sub-layer. If false, layer name itself will be used as given in service description 'layers' field. If true, it is assumed a name exists in the layer's GetCapabilities, and that will be used. If Record, it maps the layer name to an arbitrary display name given by the configuration. The `layerName` can be any string. |
diff --git a/packages/plugins/LayerChooser/src/store/index.ts b/packages/plugins/LayerChooser/src/store/index.ts
index 5a22fd3f8..60d9f0356 100644
--- a/packages/plugins/LayerChooser/src/store/index.ts
+++ b/packages/plugins/LayerChooser/src/store/index.ts
@@ -104,8 +104,9 @@ export const makeStoreModule = () => {
}
// GetCapabilities exactly needed when `true` set for an inferrable option
if (
- layer.options?.layers?.title === true ||
- layer.options?.layers?.legend === true
+ typeof layer.options?.layers === 'object' &&
+ (layer.options.layers?.title === true ||
+ layer.options.layers?.legend === true)
) {
dispatch('capabilities/loadCapabilities', layer.id, { root: true })
}
@@ -257,7 +258,7 @@ export const makeStoreModule = () => {
},
openedOptionsServiceLayers(_, { openedOptionsService }, __, rootGetters) {
const layers: LayerConfigurationOptionLayers | undefined =
- openedOptionsService.options?.layers
+ openedOptionsService?.options?.layers
if (typeof layers === 'undefined') {
return null
@@ -301,7 +302,7 @@ export const makeStoreModule = () => {
)
: layers.title === false
? technicalLayerName
- : layers.title[technicalLayerName]) || technicalLayerName,
+ : layers.title?.[technicalLayerName]) || technicalLayerName,
layerImage:
layers.legend === false
? null
diff --git a/packages/plugins/Legend/CHANGELOG.md b/packages/plugins/Legend/CHANGELOG.md
index 494a8b0e1..c79c608ab 100644
--- a/packages/plugins/Legend/CHANGELOG.md
+++ b/packages/plugins/Legend/CHANGELOG.md
@@ -5,6 +5,7 @@
- Breaking: As a result of the bundling with `rollup`, the styles of the package need to be imported via `@polar/plugin-legend/styles.css`.
- Feature: The package is now being bundled by `rollup` before being published. This allows for a smaller package size and better compatibility with other packages.
- Fix: The Legend plugin will keep working on the error that a layer without entry in the service register has been configured.
+- Fix: Remove unused prop `maxWidth`.
## 1.1.0
diff --git a/packages/plugins/Legend/src/components/Legend.vue b/packages/plugins/Legend/src/components/Legend.vue
index 1a0f1e775..bbf6ff241 100644
--- a/packages/plugins/Legend/src/components/Legend.vue
+++ b/packages/plugins/Legend/src/components/Legend.vue
@@ -27,7 +27,7 @@
>
({
isOpen: false,
}),
diff --git a/packages/plugins/LoadingIndicator/CHANGELOG.md b/packages/plugins/LoadingIndicator/CHANGELOG.md
index b59afee53..2a34a8aca 100644
--- a/packages/plugins/LoadingIndicator/CHANGELOG.md
+++ b/packages/plugins/LoadingIndicator/CHANGELOG.md
@@ -4,6 +4,7 @@
- Breaking: As a result of the bundling with `rollup`, the styles of the package need to be imported via `@polar/plugin-loading-indicator/styles.css`.
- Feature: The package is now being bundled by `rollup` before being published. This allows for a smaller package size and better compatibility with other packages.
+- Refactor: Remove redundant prop-forwarding by only using one component.
## 1.1.0
diff --git a/packages/plugins/LoadingIndicator/src/components/DefaultIndicator.vue b/packages/plugins/LoadingIndicator/src/components/DefaultIndicator.vue
deleted file mode 100644
index 94fd5a77f..000000000
--- a/packages/plugins/LoadingIndicator/src/components/DefaultIndicator.vue
+++ /dev/null
@@ -1,24 +0,0 @@
-
-
-
+
diff --git a/packages/plugins/Pins/CHANGELOG.md b/packages/plugins/Pins/CHANGELOG.md
index 71c1dce29..42b5996a3 100644
--- a/packages/plugins/Pins/CHANGELOG.md
+++ b/packages/plugins/Pins/CHANGELOG.md
@@ -1,5 +1,10 @@
# CHANGELOG
+## unpublished
+
+- Fix: Adjust documentation to properly describe optionality of configuration parameters.
+- Chore: Refactor parts of the data handling.
+
## 2.0.0-alpha.3
Fix: Revert back to previous dependency modelling.
diff --git a/packages/plugins/Pins/README.md b/packages/plugins/Pins/README.md
index d15cf0735..218493a8a 100644
--- a/packages/plugins/Pins/README.md
+++ b/packages/plugins/Pins/README.md
@@ -12,22 +12,22 @@ The usage of `displayComponent` has no influence on the creation of Pins on the
| fieldName | type | description |
| ---------------- | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| toZoomLevel | number | Zoom level to use on outside input by e.g. address search. |
-| movable | boolean? \| 'drag' \| 'click' \| 'none' | Whether a user may drag and re-click the pin (`drag` or `true`), only re-click it (`click`) or may only be placed programmatically (`none` or `false`). Defaults to 'none'. **Using a boolean for this configuration has been deprecated and will be removed in the next major release.** |
-| appearOnClick | appearOnClick | Pin restrictions. See object description below. |
-| coordinateSource | string | The pins plugin may react to changes in other plugins. This specifies the path to such store positions. The position must, when subscribed to, return a GeoJSON feature. Please mind that, when referencing another plugin, that plugin must be in `addPlugins` before this one. |
-| style | style? | Display style configuration. |
-| initial | number[]? | Configuration options for setting an initial pin. |
+| appearOnClick | appearOnClick?? | Pin restrictions. See object description below. |
| boundaryLayerId | string? | Id of a vector layer to restrict pins to. When pins are moved or created outside of the boundary, an information will be shown and the pin is reset to its previous state. The map will wait at most 10s for the layer to load; should it not happen, the geolocation feature is turned off. |
| boundaryOnError | ('strict' \| 'permissive')? | If the boundary layer check does not work due to loading or configuration errors, style `'strict'` will disable the pins feature, and style `'permissive'` will act as if no boundaryLayerId was set. Defaults to `'permissive'`. |
+| coordinateSource | string? | The pins plugin may react to changes in other plugins. This specifies the path to such store positions. The position must, when subscribed to, return a GeoJSON feature. Please mind that, when referencing another plugin, that plugin must be in `addPlugins` before this one. |
+| initial | initial? | Configuration options for setting an initial pin. |
+| movable | boolean? \| 'drag' \| 'click' \| 'none' | Whether a user may drag and re-click the pin (`drag` or `true`), only re-click it (`click`) or may only be placed programmatically (`none` or `false`). Defaults to 'none'. **Using a boolean for this configuration has been deprecated and will be removed in the next major release.** |
+| style | style? | Display style configuration. |
| toastAction | string? | If `boundaryLayerId` is set, and the pin is moved or created outside the boundary, this string will be used as action to send a toast information to the user. If no toast information is desired, leave this field undefined; for testing purposes, you can still find information in the console. |
+| toZoomLevel | number? | Zoom level to use on outside input by e.g. address search. Defaults to `0`. |
#### pins.appearOnClick
| fieldName | type | description |
|-------------|---------|------------------------------------------|
| show | boolean | Display marker. |
-| atZoomLevel | number? | Minimum zoom level for sensible marking. |
+| atZoomLevel | number? | Minimum zoom level for sensible marking. Defaults to `0`. |
#### pins.initial
diff --git a/packages/plugins/Pins/src/store/index.ts b/packages/plugins/Pins/src/store/index.ts
index 014f72e2e..98d2ad4b8 100644
--- a/packages/plugins/Pins/src/store/index.ts
+++ b/packages/plugins/Pins/src/store/index.ts
@@ -77,7 +77,6 @@ export const makeStoreModule = () => {
(await dispatch('isCoordinateInBoundaryLayer', coordinate))
) {
const payload = { coordinates: coordinate, clicked: true }
- dispatch('removeMarker')
dispatch('showMarker', payload)
commit('setCoordinatesAfterDrag', coordinate)
dispatch('updateCoordinates', coordinate)
@@ -100,7 +99,6 @@ export const makeStoreModule = () => {
clicked: false,
epsg: feature.epsg,
}
- dispatch('removeMarker')
dispatch('showMarker', payload)
}
},
@@ -116,7 +114,6 @@ export const makeStoreModule = () => {
typeof epsg === 'string'
? transform(coordinates, epsg, rootGetters.configuration.epsg)
: coordinates
- dispatch('removeMarker')
dispatch('showMarker', {
coordinates: transformedCoordinates,
clicked: true,
@@ -135,46 +132,45 @@ export const makeStoreModule = () => {
* @param payload - an object with a boolean that shows if the coordinate
* was submitted via click and the corresponding coordinates.
*/
- showMarker({ getters, rootGetters, commit, dispatch }, payload): void {
- if (getters.isActive === false) {
- const { configuration, map } = rootGetters
- if (payload.clicked === false) {
- dispatch(
- 'updateCoordinates',
- getPointCoordinate(
- payload.epsg,
- configuration.epsg,
- payload.type,
- payload.coordinates
- )
+ showMarker({ getters, rootGetters, dispatch }, payload): void {
+ // always clean up other/old markers first – single marker only atm
+ dispatch('removeMarker')
+ const { configuration, map } = rootGetters
+ if (payload.clicked === false) {
+ dispatch(
+ 'updateCoordinates',
+ getPointCoordinate(
+ payload.epsg,
+ configuration.epsg,
+ payload.type,
+ payload.coordinates
)
- map.getView().setCenter(getters.transformedCoordinate)
- map.getView().setZoom(getters.toZoomLevel)
- }
- const coordinatesForIcon =
- payload.clicked === true
- ? payload.coordinates
- : getters.transformedCoordinate
- map.removeLayer(pinsLayer)
- pinsLayer = new VectorLayer({
- source: new Vector({
- features: [
- new Feature({
- geometry: new Point(coordinatesForIcon),
- type: 'point',
- name: 'mapMarker',
- zIndex: 100,
- }),
- ],
- }),
- style: getPinStyle(configuration?.pins?.style || {}),
- })
- pinsLayer.set('polarInternalId', 'mapMarkerVectorLayer')
- map.addLayer(pinsLayer)
- pinsLayer.setZIndex(100)
- commit('setIsActive', true)
- dispatch('updateMarkerDraggability')
+ )
+ map.getView().setCenter(getters.transformedCoordinate)
+ map.getView().setZoom(getters.toZoomLevel)
}
+ const coordinatesForIcon =
+ payload.clicked === true
+ ? payload.coordinates
+ : getters.transformedCoordinate
+ map.removeLayer(pinsLayer)
+ pinsLayer = new VectorLayer({
+ source: new Vector({
+ features: [
+ new Feature({
+ geometry: new Point(coordinatesForIcon),
+ type: 'point',
+ name: 'mapMarker',
+ zIndex: 100,
+ }),
+ ],
+ }),
+ style: getPinStyle(configuration?.pins?.style || {}),
+ })
+ pinsLayer.set('polarInternalId', 'mapMarkerVectorLayer')
+ map.addLayer(pinsLayer)
+ pinsLayer.setZIndex(100)
+ dispatch('updateMarkerDraggability')
},
// Decides whether to make the mapMarker draggable and, if so, does so.
updateMarkerDraggability({
@@ -212,7 +208,6 @@ export const makeStoreModule = () => {
let coordinates = geometry?.getCoordinates()
if (!(await dispatch('isCoordinateInBoundaryLayer', coordinates))) {
coordinates = getters.transformedCoordinate
- dispatch('removeMarker')
dispatch('showMarker', {
coordinates,
clicked: true,
@@ -224,13 +219,12 @@ export const makeStoreModule = () => {
})
},
// Removes the mapMarker from the map by removing its vectorLayer
- removeMarker({ rootGetters: { map }, commit }): void {
+ removeMarker({ rootGetters: { map } }): void {
map.getLayers().forEach(function (layer) {
if (layer?.get?.('polarInternalId') === 'mapMarkerVectorLayer') {
map.removeLayer(layer)
}
})
- commit('setIsActive', false)
},
/**
* Set the value for the transformed coordinate and save it as latLon as well.
diff --git a/packages/plugins/Pins/src/store/state.ts b/packages/plugins/Pins/src/store/state.ts
index b7ceb8227..cf8c6d9d0 100644
--- a/packages/plugins/Pins/src/store/state.ts
+++ b/packages/plugins/Pins/src/store/state.ts
@@ -1,7 +1,6 @@
import { PinsState } from '../types'
export const getInitialState = (): PinsState => ({
- isActive: false,
transformedCoordinate: [],
latLon: [],
coordinatesAfterDrag: [],
diff --git a/packages/plugins/Pins/src/types.ts b/packages/plugins/Pins/src/types.ts
index f7e31c46c..2ed23394b 100644
--- a/packages/plugins/Pins/src/types.ts
+++ b/packages/plugins/Pins/src/types.ts
@@ -1,5 +1,4 @@
export interface PinsState {
- isActive: boolean
transformedCoordinate: number[]
latLon: number[]
coordinatesAfterDrag: number[]
diff --git a/packages/plugins/ReverseGeocoder/README.md b/packages/plugins/ReverseGeocoder/README.md
index b0f3564f8..1c70ce420 100644
--- a/packages/plugins/ReverseGeocoder/README.md
+++ b/packages/plugins/ReverseGeocoder/README.md
@@ -13,9 +13,9 @@ This module has been written for the HH WPS service. The return format is custom
| fieldName | type | description |
| ---------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| url | string | URL of the WPS to use for reverse geocoding. |
-| coordinateSource | string? | Points to a path in the store where a coordinate may be. If given, ReverseGeocoder will resolve on any updates. If not given, ReverseGeocoder is passive and waits for its action to be called. Please mind that, when referencing another plugin, that plugin must be in `addPlugins` before this one. |
-| addressTarget | string? | Points to a path in the store where an address can be put. If given, ReverseGeocoder will update on resolve. If not given, ReverseGeocoder's results can only be retrieved by awaiting its action. |
| addLoading | string? | Points to an action in the store; commited with a loading key as payload on starting reverse geocoding. |
+| addressTarget | string? | Points to a path in the store where an address can be put. If given, ReverseGeocoder will update on resolve. If not given, ReverseGeocoder's results can only be retrieved by awaiting its action. |
+| coordinateSource | string? | Points to a path in the store where a coordinate may be. If given, ReverseGeocoder will resolve on any updates. If not given, ReverseGeocoder is passive and waits for its action to be called. Please mind that, when referencing another plugin, that plugin must be in `addPlugins` before this one. |
| removeLoading | string? | Points to an action in the store; commited with a loading key as payload on finishing reverse geocoding. |
| zoomTo | number? | If specified, plugin zooms to given coordinate after successful reverse geocoding; number indicates maximal zoom level. |
diff --git a/packages/plugins/Toast/CHANGELOG.md b/packages/plugins/Toast/CHANGELOG.md
index e7f8d2fa8..20eca9608 100644
--- a/packages/plugins/Toast/CHANGELOG.md
+++ b/packages/plugins/Toast/CHANGELOG.md
@@ -4,6 +4,7 @@
- Breaking: As a result of the bundling with `rollup`, the styles of the package need to be imported via `@polar/plugin-toast/styles.css`.
- Feature: The package is now being bundled by `rollup` before being published. This allows for a smaller package size and better compatibility with other packages.
+- Fix: Adjust documentation to properly describe optionality of configuration parameters.
## 1.1.0
diff --git a/packages/plugins/Toast/README.md b/packages/plugins/Toast/README.md
index 11a33b593..24289392e 100644
--- a/packages/plugins/Toast/README.md
+++ b/packages/plugins/Toast/README.md
@@ -12,17 +12,17 @@ Please check the vuetify documentation to override the success, warning, info, o
| fieldName | type | description |
| --------- | ---------- | ------------------------------------- |
-| error | toastStyle | Design override for error messages. |
-| warning | toastStyle | Design override for warning messages. |
-| info | toastStyle | Design override for info messages. |
-| success | toastStyle | Design override for success messages. |
+| error | toastStyle? | Design override for error messages. |
+| info | toastStyle? | Design override for info messages. |
+| success | toastStyle? | Design override for success messages. |
+| warning | toastStyle? | Design override for warning messages. |
#### toast.toastStyle
| fieldName | type | description |
| --------- | ------- | -------------------------------------------------------------------------------------------------------------------- |
-| color | ?string | Either a color code like '#FACADE' or a color string [vuetify understands](https://vuetifyjs.com/en/styles/colors/). |
-| icon | ?string | CSS icon class. |
+| color | string? | Either a color code like '#FACADE' or a color string [vuetify understands](https://vuetifyjs.com/en/styles/colors/). |
+| icon | string? | CSS icon class. |
## Store
diff --git a/packages/plugins/Zoom/CHANGELOG.md b/packages/plugins/Zoom/CHANGELOG.md
index f4a4a0ed8..119e4ae11 100644
--- a/packages/plugins/Zoom/CHANGELOG.md
+++ b/packages/plugins/Zoom/CHANGELOG.md
@@ -4,6 +4,7 @@
- Breaking: As a result of the bundling with `rollup`, the styles of the package need to be imported via `@polar/plugin-zoom/styles.css`.
- Feature: The package is now being bundled by `rollup` before being published. This allows for a smaller package size and better compatibility with other packages.
+- Refactor: Remove redundant prop `isHorizontal` from `Zoom` component. Logic is now placed in `@polar/core`.
## 1.2.0
diff --git a/packages/plugins/Zoom/README.md b/packages/plugins/Zoom/README.md
index 26f169ff2..ec9b4df34 100644
--- a/packages/plugins/Zoom/README.md
+++ b/packages/plugins/Zoom/README.md
@@ -11,8 +11,8 @@ It can be configured as followed.
| fieldName | type | description |
|------------|------------------------------|-----------------------------------------------------------------------------------------------|
-| renderType | 'iconMenu' \| 'independent'? | Whether the zoom related buttons are being rendered independently or as part of the IconMenu. Defaults to 'independent'. |
-| showMobile | boolean? | Whether the zoom related buttons should be displayed on smaller devices; defaults to false. |
+| renderType | 'iconMenu' \| 'independent'? | Whether the zoom related buttons are being rendered independently or as part of the IconMenu. Defaults to `'independent'`. |
+| showMobile | boolean? | Whether the zoom related buttons should be displayed on smaller devices; defaults to `false`. |
## Store
diff --git a/packages/plugins/Zoom/src/components/Zoom.vue b/packages/plugins/Zoom/src/components/Zoom.vue
index 947a1745e..37477ebfd 100644
--- a/packages/plugins/Zoom/src/components/Zoom.vue
+++ b/packages/plugins/Zoom/src/components/Zoom.vue
@@ -1,16 +1,12 @@
-
+ {{ $t('common:plugins.zoom.in') }}
-
+
// optional additional search methods (client-side injections)
customSearchMethods?: Record
// optional selectResult overrides (client-side injections)
customSelectResult?: Record
- // definition of categories referred to in searchMethods
- categoryProperties?: Record
focusAfterSearch?: boolean
// definition of groups referred to in searchMethods
groupProperties?: Record
- // optional loading action name to start loading
- addLoading?: string
+ // Minimal input length before the search starts
+ minLength?: number
// optional loading action name to end loading
removeLoading?: string
+ afterResultComponent?: VueConstructor
+ // Time passed in milliseconds before another search is started
+ waitMs?: number
}
export interface Attribution {
@@ -136,11 +128,11 @@ export interface Attribution {
/** Attributions Module Configuration */
export interface AttributionsConfiguration extends PluginOptions {
- layerAttributions?: Attribution[]
- staticAttributions?: string[]
- renderType?: RenderType
initiallyOpen?: boolean
listenToChanges?: string[]
+ layerAttributions?: Attribution[]
+ renderType?: RenderType
+ staticAttributions?: string[]
windowWidth?: number
}
@@ -172,9 +164,9 @@ export interface FontStyle {
export type DrawMode = 'Circle' | 'LineString' | 'Point' | 'Polygon' | 'Text'
export interface DrawConfiguration extends Partial {
+ selectableDrawModes?: DrawMode[]
style?: DrawStyle
textStyle?: TextStyle
- selectableDrawModes?: DrawMode[]
}
export interface ExportConfiguration extends PluginOptions {
@@ -193,24 +185,24 @@ export interface ExportConfiguration extends PluginOptions {
export interface FilterConfigurationTimeOption {
amounts: number[]
- unit: 'days'
+ unit?: 'days'
}
interface FilterConfigurationTime {
targetProperty: string
- pattern?: string
- last?: FilterConfigurationTimeOption[]
- next?: FilterConfigurationTimeOption[]
freeSelection?: {
now?: 'until' | 'from'
- unit: 'days'
+ unit?: 'days'
}
+ last?: FilterConfigurationTimeOption[]
+ next?: FilterConfigurationTimeOption[]
+ pattern?: string
}
interface FilerConfigurationCategory {
- selectAll?: boolean
- targetProperty: string
knownValues: (string | number)[]
+ targetProperty: string
+ selectAll?: boolean
}
export interface FilterConfiguration extends PluginOptions {
@@ -225,18 +217,22 @@ export interface FilterConfiguration extends PluginOptions {
/** Configuration of GFI feature regarding a specific layer */
export interface GfiLayerConfiguration {
+ /**
+ * Property of the features of a service having an url usable to trigger a
+ * download of features as a document.
+ */
+ exportProperty?: string
+ // filter method to apply on response features, only relevant for WMS services
+ filterBy?: 'clickPosition'
+ // format the response is known to come in (e.g. "GML"); only relevant for WMS services
+ format?: 'GML' | 'GML2' | 'GML3' | 'GML32' | 'text'
/**
* Whether the found features' geometry, if available, is to be shown on the
* map. It is simply printed to a helper layer.
*/
geometry?: boolean
- /**
- * Whether the found features' properties are to be shown in the client's UI.
- * They are displayed as a table, one feature at a time, and if multiple
- * features are found, the user may step through all where the layer's window
- * value is true.
- */
- window?: boolean
+ // name of field to use for geometry, if not default field
+ geometryName?: string
/**
* If window is true, the properties are either
* 1. filtered by whether their key is in a string[]
@@ -250,18 +246,14 @@ export interface GfiLayerConfiguration {
* only the UI is affected by these filters/mappings.
*/
properties?: string[] | Record
+ showTooltip?: (feature: Feature, map: Map) => [string, string][]
/**
- * Property of the features of a service having an url usable to trigger a
- * download of features as a document.
+ * Whether the found features' properties are to be shown in the client's UI.
+ * They are displayed as a table, one feature at a time, and if multiple
+ * features are found, the user may step through all where the layer's window
+ * value is true.
*/
- exportProperty?: string
- // filter method to apply on response features
- filterBy?: 'clickPosition' | undefined
- // name of field to use for geometry, if not default field
- geometryName?: string
- // format the response is known to come in (e.h. "GML")
- format?: string
- showTooltip?: (feature: Feature, map: Map) => [string, string][]
+ window?: boolean
}
export type BoundaryOnError = 'strict' | 'permissive'
@@ -287,9 +279,9 @@ export interface GeoLocationConfiguration extends LayerBoundPluginOptions {
* listened to are coordinates that can be used to request information from
* the specified layers.
*/
- checkLocationInitially: boolean
+ checkLocationInitially?: boolean
/** whether to keep center on user or allow movement after first zoom to */
- keepCentered: boolean
+ keepCentered?: boolean
renderType?: RenderType
showTooltip?: boolean
/**
@@ -297,7 +289,7 @@ export interface GeoLocationConfiguration extends LayerBoundPluginOptions {
* are chosen arbitrarily. Useful if you e.g. just want one result, or to
* limit an endless stream of returns to maybe 10 or so. Infinite by default.
*/
- zoomLevel: number
+ zoomLevel?: number
}
/** Object containing information for highlighting a gfi result */
@@ -320,20 +312,25 @@ export type GfiAfterLoadFunction = (
/** GFI Module Configuration */
export interface FeatureList {
mode: 'visible' | 'loaded'
- pageLength?: number
- text: (string | ((f: Feature) => string))[]
bindWithCoreHoverSelect?: boolean
+ pageLength?: number
+ text?: (string | ((f: Feature) => string))[]
}
export interface GfiConfiguration extends PluginOptions {
- activeLayerPath?: string
- afterLoadFunction?: GfiAfterLoadFunction
/**
* Source paths through store to listen to for changes; it is assumed values
* listened to are coordinates that can be used to request information from
* the specified layers.
*/
coordinateSources: string[]
+ /**
+ * The layers to request feature information from. Both WMS and WFS layers are
+ * supported. Keys are layer IDs as specified in the services.json registry.
+ */
+ layers: Record
+ activeLayerPath?: string
+ afterLoadFunction?: GfiAfterLoadFunction
/**
* If required the stroke and fill of the highlighted feature can be configured.
* Otherwise, a default style is applied.
@@ -345,18 +342,14 @@ export interface GfiConfiguration extends PluginOptions {
* Usable to completely redesign content of GFI window.
*/
gfiContentComponent?: Vue
- renderType?: RenderType
- /**
- * The layers to request feature information from. Both WMS and WFS layers are
- * supported. Keys are layer IDs as specified in the services.json registry.
- */
- layers: Record
/**
* 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 maybe 10 or so. Infinite by default.
*/
maxFeatures?: number
+ mode?: 'bboxDot' | 'intersects'
+ renderType?: RenderType
}
export interface Menu {
@@ -393,27 +386,27 @@ interface PinStyle {
}
export interface PinsConfiguration extends LayerBoundPluginOptions {
- appearOnClick: AppearOnClick
+ appearOnClick?: AppearOnClick
/** Path in store from where coordinates can be retrieved from. */
- coordinateSource: string
+ coordinateSource?: string
initial?: InitialPin
/** If the pin should be movable; defaults to false. */
movable?: boolean | MovablePin
/** Pin styling */
style?: PinStyle
/** The zoom level to zoom to when a pin is added to the map. */
- toZoomLevel: number
+ toZoomLevel?: number
}
export interface ReverseGeocoderConfiguration {
// WPS (Web Processing Service) URL
url: string
- // points to a coordinate source; on update, coordinate is resolved
- coordinateSource?: string
- // points to an address acceptor; on resolve, address is dispatched there
- addressTarget?: string
// optional loading action name to start loading
addLoading?: string
+ // points to an address acceptor; on resolve, address is dispatched there
+ addressTarget?: string
+ // points to a coordinate source; on update, coordinate is resolved
+ coordinateSource?: string
// optional loading action name to end loading
removeLoading?: string
// optionally zooms to given coordinate after successful reverse geocoding; number indicates zoom level
@@ -467,6 +460,12 @@ export type StrategyOptions = 'all' | 'tile' | 'bbox'
export type LayerType = 'mask' | 'background'
export interface LayerConfigurationOptionLayers {
+ /**
+ * Legend image to be used for sub-layer. If false, no image is displayed.
+ * If true, it is assumed an image exists in the layer's GetCapabilities, and
+ * that will be used. If Record, it maps the layer name to a linked image.
+ */
+ legend?: boolean | Record
/**
* Comma-separated re-ordering of service layer's 'layer' specification.
* Layer's not specified in service definition, but in order, are initially
@@ -474,7 +473,7 @@ export interface LayerConfigurationOptionLayers {
* always visible. Layers specified in both are initially visible. Layers
* specified in neither are always invisible.
*/
- order: string
+ order?: string
/**
* Title to be displayed for sub-layer. If false, layer name itself will
* be used as given in service description 'layers' field. If true, it is
@@ -482,13 +481,7 @@ export interface LayerConfigurationOptionLayers {
* used. If Record, it maps the layer name to an arbitrary display name given
* by the configuration.
*/
- title: boolean | Record
- /**
- * Legend image to be used for sub-layer. If false, no image is displayed.
- * If true, it is assumed an image exists in the layer's GetCapabilities, and
- * that will be used. If Record, it maps the layer name to a linked image.
- */
- legend: boolean | Record
+ title?: boolean | Record
}
export interface LayerConfigurationOptions {
@@ -507,20 +500,16 @@ export interface LayerConfiguration {
name: string
/** Whether the layer is a background layer or a feature layer with specific information */
type: LayerType
- /** Whether the layer should be rendered; defaults to false */
- visibility?: boolean
- /** Whether the layer should be hidden from the LayerChooser selection menu */
+ /** Whether the mask-layer should be hidden from the LayerChooser selection menu */
hideInMenu?: boolean
/** The minimum zoom level the layer will be rendered in; defaults to 0 */
minZoom?: number
/** The maximum zoom level the layer will be rendered in; defaults to Number.MAX_SAFE_INTEGER */
maxZoom?: number
- /** Url to use to proxy the request with */
- proxyUrl?: string
- // TODO: Is the property 'loadingStrategy' still in use?
- loadingStrategy?: StrategyOptions
/** Enables a configuration feature for the layer in its selection. */
options?: LayerConfigurationOptions
+ /** Whether the layer should be rendered; defaults to false */
+ visibility?: boolean
}
export interface PolarMapOptions {
@@ -648,6 +637,7 @@ export interface CoreState {
hovered: number
language: string
map: number
+ mapHasDimensions: boolean
moveHandle: number
moveHandleActionButton: number
plugin: object
@@ -655,23 +645,31 @@ export interface CoreState {
zoomLevel: number
}
-export interface CoreGetters {
+export interface CoreGetters
+ extends Omit<
+ CoreState,
+ | 'components'
+ | 'hovered'
+ | 'map'
+ | 'moveHandle'
+ | 'moveHandleActionButton'
+ | 'selected'
+ > {
+ // omitted from CoreState as actual getter type diverges
components: PluginContainer[]
- configuration: MapConfig
- hasSmallHeight: boolean
- hasSmallWidth: boolean
- /** Whether the application currently has the same size as the visual viewport of the users browser */
- hasWindowSize: boolean
hovered: Feature | null
- errors: PolarError[]
map: Map
+ mapHasDimensions: boolean
moveHandle: MoveHandleProperties
moveHandleActionButton: MoveHandleActionButton
selected: Feature | null
- clientHeight: number
- clientWidth: number
- center: number[] | null
- zoomLevel: number
+
+ // regular getters
+ deviceIsHorizontal: boolean
+ hasSmallHeight: boolean
+ hasSmallWidth: boolean
+ /** Whether the application currently has the same size as the visual viewport of the users browser */
+ hasWindowSize: boolean
}
export type PolarGetter = (
diff --git a/pages/documentation.html b/pages/documentation.html
index b1f6b61f7..88fdddf84 100644
--- a/pages/documentation.html
+++ b/pages/documentation.html
@@ -133,6 +133,8 @@
Usage pattern
Startup code. This is mostly putting the aforementioned parts together and adding configuration and subscriptions.
+
+ For a minimum working example, checkout this repository ↗ which includes the example shown in the FOSSGIS 2024 presentation ↗.
import client from '@polar/client-generic'
diff --git a/scripts/buildPages.sh b/scripts/buildPages.sh
index 5edb760f0..9995ae3ce 100644
--- a/scripts/buildPages.sh
+++ b/scripts/buildPages.sh
@@ -1,4 +1,4 @@
-array=( afm generic meldemichel snowbox )
+array=( afm generic meldemichel snowbox textLocator )
for i in "${array[@]}"
do
echo "Building $i docs ..."
diff --git a/scripts/makeDocs.js b/scripts/makeDocs.js
index 5ce352461..32b9b1e68 100644
--- a/scripts/makeDocs.js
+++ b/scripts/makeDocs.js
@@ -84,6 +84,29 @@ function toHtml(filePath, children) {