diff --git a/packages/clients/snowbox/src/exampleFeatureInformation.ts b/packages/clients/snowbox/src/exampleFeatureInformation.ts new file mode 100644 index 000000000..bae696072 --- /dev/null +++ b/packages/clients/snowbox/src/exampleFeatureInformation.ts @@ -0,0 +1,538 @@ +/* eslint-disable @typescript-eslint/naming-convention, max-lines */ + +import { FeaturesByLayerId } from '@polar/plugin-gfi/src/types' + +export const exampleFeatureInformation: FeaturesByLayerId = { + 1454: [ + { + type: 'Feature', + geometry: { + type: 'Polygon', + coordinates: [ + [ + [567357.259, 5928923.532, 0], + [567428.316, 5929001.598, 0], + [567430.133, 5929002.656, 0], + [567432.915, 5929002.111, 0], + [567437.012, 5928999.346, 0], + [567438.918, 5928996.882, 0], + [567445.48, 5928991.466, 0], + [567452.541, 5928982.393, 0], + [567454.524, 5928976.202, 0], + [567452.703, 5928972.513, 0], + [567450.196, 5928969.906, 0], + [567440.903, 5928964.726, 0], + [567440.577, 5928966.139, 0], + [567402.309, 5928931.894, 0], + [567396.03, 5928928.249, 0], + [567388.572, 5928924.876, 0], + [567383.041, 5928924.019, 0], + [567381.222, 5928920.347, 0], + [567377.75, 5928913.341, 0], + [567357.259, 5928923.532, 0], + ], + ], + }, + properties: { + vorhaben: 'M-089 - BPlan Wilhelmsburg 72', + vorhaben_zulassung_am: '17.01.1994', + vorhaben_verfahrensart: 'BPlan', + kompensationsmassnahme: 'Fließgewässer und Gräben', + massnahmenstatus: 'Maßnahme § 9 (1) Nr. 20 BauGB', + flaechensicherung: 'k.A.', + flaeche: '3069.449', + kompensationsmassnahme_detail: + 'Entwicklung naturnaher Ufergehölze; Entwicklung von Uferrandstreifen', + }, + id: 'DE.HH.UP_AUSGLEICHSFLAECHEN_443856', + }, + { + type: 'Feature', + geometry: { + type: 'Polygon', + coordinates: [ + [ + [568075.605, 5929240.552, 0], + [568083.257, 5929233.41, 0], + [568086.317, 5929240.807, 0], + [568110.037, 5929231.624, 0], + [568124.32, 5929223.462, 0], + [568139.878, 5929210.709, 0], + [568155.181, 5929193.111, 0], + [568157.732, 5929189.285, 0], + [568154.671, 5929186.734, 0], + [568161.302, 5929177.807, 0], + [568165.128, 5929179.593, 0], + [568219.198, 5929109.962, 0], + [568215.627, 5929107.157, 0], + [568223.534, 5929097.464, 0], + [568227.105, 5929099.76, 0], + [568242.918, 5929082.671, 0], + [568258.986, 5929069.408, 0], + [568273.014, 5929059.461, 0], + [568276.843, 5929057.626, 0], + [568272.353, 5929039.184, 0], + [568256.407, 5928974.91, 0], + [568231.643, 5928872.379, 0], + [568190.591, 5928890.875, 0], + [568187.183, 5928883.557, 0], + [568160.632, 5928895.529, 0], + [568128.081, 5928822.376, 0], + [568107.128, 5928834.457, 0], + [568094.719, 5928842.197, 0], + [568108.683, 5928861.728, 0], + [568088.395, 5928874.902, 0], + [568074.657, 5928855.297, 0], + [568068.832, 5928859.384, 0], + [568055.556, 5928869.77, 0], + [568050.41, 5928874.163, 0], + [568044.248, 5928879.977, 0], + [568035.744, 5928888.363, 0], + [568021.109, 5928904.589, 0], + [568044.436, 5928919.135, 0], + [568059.399, 5928933.711, 0], + [568064.897, 5928953.563, 0], + [568019.338, 5928972.388, 0], + [567989.81, 5928947.615, 0], + [567987.35, 5928945.548, 0], + [567975.597, 5928960.372, 0], + [567941.206, 5929003.66, 0], + [567929.406, 5929018.945, 0], + [567903.589, 5929054.714, 0], + [567939.638, 5929077.036, 0], + [567928.205, 5929095.265, 0], + [567890.962, 5929071.875, 0], + [567889.548, 5929073.771, 0], + [567856.828, 5929124.897, 0], + [567856.211, 5929125.803, 0], + [567867.777, 5929134.715, 0], + [567873.657, 5929142.919, 0], + [567866.223, 5929156.417, 0], + [567861.382, 5929165.213, 0], + [567846.146, 5929189.574, 0], + [567836.127, 5929205.666, 0], + [567847.888, 5929209.488, 0], + [567842.359, 5929226.669, 0], + [567835.448, 5929247.617, 0], + [567824.664, 5929280.774, 0], + [567800.425, 5929355.307, 0], + [567913.44, 5929386.822, 0], + [567940.698, 5929394.368, 0], + [567937.879, 5929389.25, 0], + [567935.583, 5929379.048, 0], + [567937.623, 5929365.275, 0], + [567950.376, 5929342.575, 0], + [567961.343, 5929326.762, 0], + [567976.135, 5929306.867, 0], + [567990.418, 5929290.033, 0], + [568006.741, 5929277.535, 0], + [568025.87, 5929267.078, 0], + [568056.221, 5929253.815, 0], + [568076.881, 5929244.888, 0], + [568075.605, 5929240.552, 0], + ], + ], + }, + properties: { + vorhaben: 'U-061 - Sanierung der Deponie Georgswerder', + vorhaben_zulassung_am: '08.12.1989', + vorhaben_verfahrensart: 'Zustimmung', + kompensationsmassnahme: 'extensiv genutztes Grünland', + massnahmenstatus: 'festgesetzt', + flaechensicherung: 'k.A.', + flaeche: '131321.2321', + hektar: '13,2819', + kompensationsmassnahme_detail: + 'Teiche / Blänken anlegen; Wiederherstellung / Aufweitung von Gräben m.flachen Uferböschungen; Wiesen- oder Weidenutzung', + }, + id: 'DE.HH.UP_AUSGLEICHSFLAECHEN_443789', + }, + { + type: 'Feature', + geometry: { + type: 'Polygon', + coordinates: [ + [ + [568276.843, 5929057.626, 0], + [568273.014, 5929059.461, 0], + [568258.986, 5929069.408, 0], + [568242.918, 5929082.671, 0], + [568227.105, 5929099.76, 0], + [568223.534, 5929097.464, 0], + [568215.627, 5929107.157, 0], + [568219.198, 5929109.962, 0], + [568165.128, 5929179.593, 0], + [568161.302, 5929177.807, 0], + [568154.671, 5929186.734, 0], + [568157.732, 5929189.285, 0], + [568155.181, 5929193.111, 0], + [568139.878, 5929210.709, 0], + [568124.32, 5929223.462, 0], + [568110.037, 5929231.624, 0], + [568086.317, 5929240.807, 0], + [568083.257, 5929233.41, 0], + [568075.605, 5929240.552, 0], + [568076.881, 5929244.888, 0], + [568056.221, 5929253.815, 0], + [568025.87, 5929267.078, 0], + [568006.741, 5929277.535, 0], + [567990.418, 5929290.033, 0], + [567976.135, 5929306.867, 0], + [567961.343, 5929326.762, 0], + [567950.376, 5929342.575, 0], + [567937.623, 5929365.275, 0], + [567935.583, 5929379.048, 0], + [567937.879, 5929389.25, 0], + [567940.698, 5929394.368, 0], + [567941.33, 5929397.698, 0], + [567941.4, 5929398.016, 0], + [567944.266, 5929410.851, 0], + [567981.598, 5929566.414, 0], + [568000.569, 5929643.233, 0], + [568008.886, 5929677.539, 0], + [568043.052, 5929789.491, 0], + [568047.573, 5929818.123, 0], + [568055.108, 5929816.616, 0], + [568064.15, 5929822.644, 0], + [568122.168, 5929815.861, 0], + [568189.229, 5929803.05, 0], + [568260.057, 5929793.251, 0], + [568348.969, 5929791.737, 0], + [568386.646, 5929867.08, 0], + [568388.906, 5929881.396, 0], + [568425.074, 5929877.625, 0], + [568435.623, 5929874.61, 0], + [568459.392, 5929830.979, 0], + [568459.783, 5929830.261, 0], + [568502.296, 5929752.157, 0], + [568576.364, 5929614.283, 0], + [568591.287, 5929585.258, 0], + [568595.816, 5929576.092, 0], + [568606.483, 5929554.492, 0], + [568621.163, 5929522.709, 0], + [568677.129, 5929390.94, 0], + [568678.394, 5929385.75, 0], + [568678.097, 5929379.983, 0], + [568674.38, 5929369.219, 0], + [568673.56, 5929364.68, 0], + [568665.078, 5929342.991, 0], + [568653.293, 5929311.953, 0], + [568643.071, 5929295.232, 0], + [568629.263, 5929268.493, 0], + [568616.518, 5929240.724, 0], + [568605.232, 5929203.73, 0], + [568599.703, 5929180.679, 0], + [568596.204, 5929159.369, 0], + [568593.822, 5929138.526, 0], + [568592.056, 5929117.056, 0], + [568592.525, 5929087.488, 0], + [568594.016, 5929066.605, 0], + [568601.075, 5929024.447, 0], + [568587.168, 5929024.298, 0], + [568590.811, 5929001.791, 0], + [568583.395, 5928997.498, 0], + [568463.706, 5929014.413, 0], + [568450.436, 5929022.219, 0], + [568353.289, 5929035.578, 0], + [568340.093, 5929036.76, 0], + [568329.38, 5929039.311, 0], + [568312.802, 5929043.647, 0], + [568300.049, 5929047.473, 0], + [568285.256, 5929053.594, 0], + [568276.843, 5929057.626, 0], + ], + ], + }, + properties: { + vorhaben: 'U-061 - Sanierung der Deponie Georgswerder', + vorhaben_zulassung_am: '08.12.1989', + vorhaben_verfahrensart: 'Zustimmung', + kompensationsmassnahme: 'Grünfläche', + massnahmenstatus: 'festgesetzt', + flaechensicherung: 'k.A.', + flaeche: '437265.6768', + hektar: '44,0000', + }, + id: 'DE.HH.UP_AUSGLEICHSFLAECHEN_445371', + }, + { + type: 'Feature', + geometry: { + type: 'Polygon', + coordinates: [ + [ + [567357.787, 5928923.949, 0], + [567315.644, 5928961.167, 0], + [567294.262, 5928980.485, 0], + [567266.077, 5928943.721, 0], + [567264.453, 5928941.474, 0], + [567250.88, 5928951.822, 0], + [567244.539, 5928958.237, 0], + [567288.796, 5929008.833, 0], + [567276.848, 5929019.384, 0], + [567297.769, 5929042.745, 0], + [567317.301, 5929064.721, 0], + [567323.939, 5929072.335, 0], + [567335.083, 5929085.128, 0], + [567339.285, 5929084.829, 0], + [567344.103, 5929081.528, 0], + [567350.428, 5929074.203, 0], + [567352.927, 5929070.973, 0], + [567356.902, 5929067.697, 0], + [567367.062, 5929060.056, 0], + [567368.663, 5929053.734, 0], + [567371.115, 5929050.485, 0], + [567375.441, 5929046.828, 0], + [567387.577, 5929031.78, 0], + [567401.087, 5929020.575, 0], + [567428.271, 5929001.666, 0], + [567429.489, 5929002.375, 0], + [567357.787, 5928923.949, 0], + ], + ], + }, + properties: { + vorhaben: 'U-061 - Sanierung der Deponie Georgswerder', + vorhaben_zulassung_am: '08.12.1989', + vorhaben_verfahrensart: 'Zustimmung', + kompensationsmassnahme: 'extensiv genutztes Grünland', + massnahmenstatus: 'festgesetzt', + flaechensicherung: 'k.A.', + flaeche: '13912.17', + hektar: '1,3909', + }, + id: 'DE.HH.UP_AUSGLEICHSFLAECHEN_445859', + }, + { + type: 'Feature', + geometry: { + type: 'Polygon', + coordinates: [ + [ + [567127.09, 5929119.895, 0], + [567127.985, 5929120.911, 0], + [567130.358, 5929123.604, 0], + [567147.174, 5929140.753, 0], + [567160.241, 5929153.479, 0], + [567176.115, 5929169.519, 0], + [567187.449, 5929179.496, 0], + [567191.052, 5929182.756, 0], + [567195.175, 5929180.848, 0], + [567209.577, 5929169.185, 0], + [567223.784, 5929158.511, 0], + [567233.903, 5929152.536, 0], + [567237.231, 5929144.525, 0], + [567254.191, 5929139.763, 0], + [567257.055, 5929139.517, 0], + [567250.885, 5929130.647, 0], + [567233.685, 5929105.925, 0], + [567217.271, 5929084.086, 0], + [567214.738, 5929080.928, 0], + [567205.369, 5929069.245, 0], + [567197.853, 5929059.619, 0], + [567156.646, 5929095.768, 0], + [567127.09, 5929119.895, 0], + ], + ], + }, + properties: { + vorhaben: 'M-089 - BPlan Wilhelmsburg 72', + vorhaben_zulassung_am: '17.01.1994', + vorhaben_verfahrensart: 'BPlan', + kompensationsmassnahme: 'Fließgewässer und Gräben', + massnahmenstatus: 'Maßnahme § 9 (1) Nr. 20 BauGB', + flaechensicherung: 'k.A.', + flaeche: '8001.0111', + kompensationsmassnahme_detail: + 'Entwicklung naturnaher Ufergehölze; Entwicklung von Uferrandstreifen', + }, + id: 'DE.HH.UP_AUSGLEICHSFLAECHEN_443869', + }, + { + type: 'Feature', + geometry: { + type: 'Polygon', + coordinates: [ + [ + [567231.266, 5929046.507, 0], + [567255.323, 5929073.702, 0], + [567283.414, 5929104.669, 0], + [567323.986, 5929072.271, 0], + [567317.352, 5929064.659, 0], + [567297.815, 5929042.682, 0], + [567276.892, 5929019.321, 0], + [567267.022, 5929008.2, 0], + [567249.716, 5929022.363, 0], + [567239.261, 5929029.874, 0], + [567225.324, 5929039.885, 0], + [567231.266, 5929046.507, 0], + ], + ], + }, + properties: { + vorhaben: 'M-089 - BPlan Wilhelmsburg 72', + vorhaben_zulassung_am: '17.01.1994', + vorhaben_verfahrensart: 'BPlan', + kompensationsmassnahme: 'Fließgewässer und Gräben', + massnahmenstatus: 'Maßnahme § 9 (1) Nr. 20 BauGB', + flaechensicherung: 'k.A.', + flaeche: '4486.4625', + kompensationsmassnahme_detail: + 'Entwicklung naturnaher Ufergehölze; Entwicklung von Uferrandstreifen', + }, + id: 'DE.HH.UP_AUSGLEICHSFLAECHEN_443868', + }, + { + type: 'Feature', + geometry: { + type: 'Polygon', + coordinates: [ + [ + [567225.324, 5929039.885, 0], + [567220.366, 5929043.447, 0], + [567236.412, 5929063.977, 0], + [567226.756, 5929071.529, 0], + [567265.335, 5929119.107, 0], + [567283.414, 5929104.669, 0], + [567255.323, 5929073.702, 0], + [567231.266, 5929046.507, 0], + [567226.907, 5929041.58, 0], + [567225.324, 5929039.885, 0], + ], + ], + }, + properties: { + vorhaben: 'M-089 - BPlan Wilhelmsburg 72', + vorhaben_zulassung_am: '17.01.1994', + vorhaben_verfahrensart: 'BPlan', + kompensationsmassnahme: 'Fließgewässer und Gräben', + massnahmenstatus: 'Maßnahme § 9 (1) Nr. 20 BauGB', + flaechensicherung: 'k.A.', + flaeche: '1493.4451', + kompensationsmassnahme_detail: + 'Entwicklung naturnaher Ufergehölze; Entwicklung von Uferrandstreifen', + }, + id: 'DE.HH.UP_AUSGLEICHSFLAECHEN_443867', + }, + { + type: 'Feature', + geometry: { + type: 'Polygon', + coordinates: [ + [ + [567214.738, 5929080.928, 0], + [567217.271, 5929084.086, 0], + [567233.685, 5929105.925, 0], + [567250.885, 5929130.647, 0], + [567265.335, 5929119.107, 0], + [567226.756, 5929071.529, 0], + [567214.738, 5929080.928, 0], + ], + ], + }, + properties: { + vorhaben: 'M-089 - BPlan Wilhelmsburg 72', + vorhaben_zulassung_am: '17.01.1994', + vorhaben_verfahrensart: 'BPlan', + kompensationsmassnahme: 'Fließgewässer und Gräben', + massnahmenstatus: 'Maßnahme § 9 (1) Nr. 20 BauGB', + flaechensicherung: 'k.A.', + flaeche: '1014.0151', + kompensationsmassnahme_detail: + 'Entwicklung naturnaher Ufergehölze; Entwicklung von Uferrandstreifen', + }, + id: 'DE.HH.UP_AUSGLEICHSFLAECHEN_443870', + }, + { + type: 'Feature', + geometry: { + type: 'Polygon', + coordinates: [ + [ + [567283.414, 5929104.669, 0], + [567293.86, 5929116.185, 0], + [567296.655, 5929119.249, 0], + [567306.145, 5929111.82, 0], + [567310.907, 5929109.094, 0], + [567319.286, 5929104.094, 0], + [567324.432, 5929093.142, 0], + [567333.796, 5929085.163, 0], + [567335.136, 5929085.067, 0], + [567323.986, 5929072.271, 0], + [567283.414, 5929104.669, 0], + ], + ], + }, + properties: { + vorhaben: 'M-089 - BPlan Wilhelmsburg 72', + vorhaben_zulassung_am: '17.01.1994', + vorhaben_verfahrensart: 'BPlan', + kompensationsmassnahme: 'Fließgewässer und Gräben', + massnahmenstatus: 'Maßnahme § 9 (1) Nr. 20 BauGB', + flaechensicherung: 'k.A.', + flaeche: '987.9114', + kompensationsmassnahme_detail: + 'Entwicklung naturnaher Ufergehölze; Entwicklung von Uferrandstreifen', + }, + id: 'DE.HH.UP_AUSGLEICHSFLAECHEN_443859', + }, + { + type: 'Feature', + geometry: { + type: 'Polygon', + coordinates: [ + [ + [567265.335, 5929119.107, 0], + [567278.897, 5929135.832, 0], + [567289.82, 5929125.988, 0], + [567296.655, 5929119.249, 0], + [567293.86, 5929116.185, 0], + [567283.414, 5929104.669, 0], + [567265.335, 5929119.107, 0], + ], + ], + }, + properties: { + vorhaben: 'M-089 - BPlan Wilhelmsburg 72', + vorhaben_zulassung_am: '17.01.1994', + vorhaben_verfahrensart: 'BPlan', + kompensationsmassnahme: 'Fließgewässer und Gräben', + massnahmenstatus: 'Maßnahme § 9 (1) Nr. 20 BauGB', + flaechensicherung: 'k.A.', + flaeche: '491.4061', + kompensationsmassnahme_detail: + 'Entwicklung naturnaher Ufergehölze; Entwicklung von Uferrandstreifen', + }, + id: 'DE.HH.UP_AUSGLEICHSFLAECHEN_443861', + }, + { + type: 'Feature', + geometry: { + type: 'Polygon', + coordinates: [ + [ + [567257.055, 5929139.517, 0], + [567264.469, 5929138.876, 0], + [567274.527, 5929139.769, 0], + [567278.897, 5929135.832, 0], + [567265.335, 5929119.107, 0], + [567250.885, 5929130.647, 0], + [567257.055, 5929139.517, 0], + ], + ], + }, + properties: { + vorhaben: 'M-089 - BPlan Wilhelmsburg 72', + vorhaben_zulassung_am: '17.01.1994', + vorhaben_verfahrensart: 'BPlan', + kompensationsmassnahme: 'Fließgewässer und Gräben', + massnahmenstatus: 'Maßnahme § 9 (1) Nr. 20 BauGB', + flaechensicherung: 'k.A.', + flaeche: '335.7387', + kompensationsmassnahme_detail: + 'Entwicklung naturnaher Ufergehölze; Entwicklung von Uferrandstreifen', + }, + id: 'DE.HH.UP_AUSGLEICHSFLAECHEN_443871', + }, + ], +} diff --git a/packages/clients/snowbox/src/index.html b/packages/clients/snowbox/src/index.html index 1e8617d69..091217213 100644 --- a/packages/clients/snowbox/src/index.html +++ b/packages/clients/snowbox/src/index.html @@ -96,6 +96,7 @@

🗺️ Map

Example for programmatic information binding

+

This illustrates which kind of data can be retrieved from the map client.

diff --git a/packages/clients/snowbox/src/polar-client.ts b/packages/clients/snowbox/src/polar-client.ts index 4a539ec87..b9181d266 100644 --- a/packages/clients/snowbox/src/polar-client.ts +++ b/packages/clients/snowbox/src/polar-client.ts @@ -5,6 +5,7 @@ import { changeLanguage } from 'i18next' import { enableClustering } from '../../meldemichel/src/utils/enableClustering' import { addPlugins } from './addPlugins' import { mapConfiguration, reports } from './mapConfiguration' +import { exampleFeatureInformation } from './exampleFeatureInformation' addPlugins(polarCore) @@ -80,3 +81,11 @@ document target[1].innerHTML = value === 'en' ? 'German' : 'Deutsch' }) }) + +document.getElementById('vuex-target-clicky')!.addEventListener('click', () => + // @ts-expect-error | added for e2e testing + window.mapInstance.$store.dispatch( + 'plugin/gfi/setFeatureInformation', + exampleFeatureInformation + ) +) diff --git a/packages/plugins/Gfi/CHANGELOG.md b/packages/plugins/Gfi/CHANGELOG.md index 5b91da3ad..58c1e3b33 100644 --- a/packages/plugins/Gfi/CHANGELOG.md +++ b/packages/plugins/Gfi/CHANGELOG.md @@ -1,5 +1,9 @@ # CHANGELOG +## unpublished + +- Feature: Add new action `setFeatureInformation` to be able to set feature information in the store and trigger all relevant processes so that the information displayed to the user is as if he has selected the features himself. + ## 2.0.0 - Breaking: Upgrade `@masterportal/masterportalapi` from `2.8.0` to `2.40.0` and subsequently `ol` from `^7.1.0` to `^9.2.4`. diff --git a/packages/plugins/Gfi/README.md b/packages/plugins/Gfi/README.md index 352df4ac1..fb562cc26 100644 --- a/packages/plugins/Gfi/README.md +++ b/packages/plugins/Gfi/README.md @@ -178,6 +178,56 @@ featureList: { ## Store +### Actions + +#### setFeatureInformation + +This method can be used to set the feature information in the store and trigger all relevant processes so that the information displayed to the user is as if he has selected the features himself. +Note that calling this method completely overrides the previously set feature information. + +If a layer has a `isSelectable`-function configured, the features are filtered using that function. + +```js +map.$store.dispatch('plugin/gfi/setFeatureInformation', { + "anotherLayer": [], + "yetAnotherLayer": [], + "relevantInformation": [ + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 565669.6521397199, + 5930516.358614317 + ] + }, + "properties": { + "propertyOne": "B0", + "propertyTwo": "B1" + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 565594.9377660984, + 5930524.52634174 + ] + }, + "properties": { + propertyOne: "A0", + propertyTwo: "A1" + } + } + ] +}) +``` + +The payload object expects a layer id as a key and an array of GeoJSON-Features as its value. + +The selected feature information can be reset by calling the method with an empty object. + ### State If a successful query has been sent and a response has been received, the result will be saved in the store and can be subscribed through the path `'plugin/gfi/featureInformation'`. If, however, a query for a layer fails, a `Symbol` containing the error will be saved in the store instead to indicate the error. diff --git a/packages/plugins/Gfi/src/store/actions/debouncedGfiRequest.ts b/packages/plugins/Gfi/src/store/actions/debouncedGfiRequest.ts index e0d86b37b..286e67970 100644 --- a/packages/plugins/Gfi/src/store/actions/debouncedGfiRequest.ts +++ b/packages/plugins/Gfi/src/store/actions/debouncedGfiRequest.ts @@ -10,18 +10,17 @@ import { Map, Feature } from 'ol' import { Geometry } from 'ol/geom' import VectorLayer from 'ol/layer/Vector' import compare from 'just-compare' -import { addFeature } from '../../utils/displayFeatureLayer' +import { filterFeatures } from '../../utils/filterFeatures' import { requestGfi } from '../../utils/requestGfi' import sortFeatures from '../../utils/sortFeatures' -import { GfiGetters, GfiState } from '../../types' +import { FeaturesByLayerId, GfiGetters, GfiState } from '../../types' +import { renderFeatures } from '../../utils/renderFeatures' interface GetFeatureInfoParameters { coordinateOrExtent: [number, number] | [number, number, number, number] modifierPressed?: boolean } -type FeaturesByLayerId = Record - const filterAndMapFeaturesToLayerIds = ( layerKeys: string[], gfiConfiguration: GfiConfiguration, @@ -93,17 +92,6 @@ const getPromisedFeatures = ( }) }) -const filterFeatures = ( - featuresByLayerId: FeaturesByLayerId -): Record => { - const entries = Object.entries(featuresByLayerId) - const filtered = entries.filter((keyValue) => Array.isArray(keyValue[1])) as [ - string, - GeoJsonFeature[] - ][] - return Object.fromEntries(filtered) -} - const createSelectionDiff = ( oldSelection: FeaturesByLayerId, newSelection: FeaturesByLayerId @@ -178,14 +166,11 @@ const gfiRequest = ) } commit('setFeatureInformation', featuresByLayerId) - // render feature geometries to help layer - getters.geometryLayerKeys - .filter((key) => Array.isArray(featuresByLayerId[key])) - .forEach((key) => - filterFeatures(featuresByLayerId)[key].forEach((feature) => - addFeature(feature, featureDisplayLayer) - ) - ) + renderFeatures( + featureDisplayLayer, + getters.geometryLayerKeys, + featuresByLayerId + ) } export const debouncedGfiRequest = ( diff --git a/packages/plugins/Gfi/src/store/actions/index.ts b/packages/plugins/Gfi/src/store/actions/index.ts index f4ea15fe8..24b3245f5 100644 --- a/packages/plugins/Gfi/src/store/actions/index.ts +++ b/packages/plugins/Gfi/src/store/actions/index.ts @@ -1,18 +1,20 @@ import debounce from 'lodash.debounce' +import { Feature as GeoJsonFeature } from 'geojson' import { Style, Fill, Stroke } from 'ol/style' -import Overlay from 'ol/Overlay' import { GeoJSON } from 'ol/format' import { Feature } from 'ol' -import { Feature as GeoJsonFeature, GeoJsonProperties } from 'geojson' import { PolarActionTree } from '@polar/lib-custom-types' -import getCluster from '@polar/lib-get-cluster' -import { getTooltip, Tooltip } from '@polar/lib-tooltip' -import { DragBox } from 'ol/interaction' -import { platformModifierKeyOnly } from 'ol/events/condition' import { getFeatureDisplayLayer, clear } from '../../utils/displayFeatureLayer' -import { GfiGetters, GfiState } from '../../types' -import { getOriginalFeature } from '../../utils/getOriginalFeature' +import { FeaturesByLayerId, GfiGetters, GfiState } from '../../types' +import { filterFeatures } from '../../utils/filterFeatures' +import { renderFeatures } from '../../utils/renderFeatures' import { debouncedGfiRequest } from './debouncedGfiRequest' +import { + setupCoreListener, + setupMultiSelection, + setupTooltip, + setupZoomListeners, +} from './setup' // OK for module action set creation // eslint-disable-next-line max-lines-per-function @@ -67,131 +69,10 @@ export const makeActions = () => { dispatch('setupZoomListeners') dispatch('setupMultiSelection') }, - setupCoreListener({ - getters: { gfiConfiguration }, - rootGetters, - dispatch, - }) { - if (gfiConfiguration.featureList?.bindWithCoreHoverSelect) { - this.watch( - () => rootGetters.selected, - (feature) => dispatch('setOlFeatureInformation', { feature }), - { deep: true } - ) - } - }, - setupMultiSelection({ dispatch, getters, rootGetters }) { - if (getters.gfiConfiguration.boxSelect) { - const dragBox = new DragBox({ condition: platformModifierKeyOnly }) - dragBox.on('boxend', () => - dispatch('getFeatureInfo', { - coordinateOrExtent: dragBox.getGeometry().getExtent(), - modifierPressed: true, - }) - ) - rootGetters.map.addInteraction(dragBox) - } - if (getters.gfiConfiguration.directSelect) { - rootGetters.map.on('click', ({ coordinate, originalEvent }) => - dispatch('getFeatureInfo', { - coordinateOrExtent: coordinate, - modifierPressed: - navigator.userAgent.indexOf('Mac') !== -1 - ? originalEvent.metaKey - : originalEvent.ctrlKey, - }) - ) - } - }, - setupZoomListeners({ dispatch, getters, rootGetters }) { - if (getters.gfiConfiguration.featureList) { - this.watch( - () => rootGetters.zoomLevel, - () => { - const { - featureInformation, - listableLayerSources, - visibleWindowFeatureIndex, - windowFeatures, - } = getters - - if (windowFeatures.length) { - const layerId: string = - // @ts-expect-error | if windowFeatures has features, visibleWindowFeatureIndex is in the range of possible features - windowFeatures[visibleWindowFeatureIndex].polarInternalLayerKey - const selectedFeatureProperties: GeoJsonProperties = { - // eslint-disable-next-line @typescript-eslint/naming-convention - _gfiLayerId: layerId, - ...featureInformation[layerId][visibleWindowFeatureIndex] - .properties, - } - const originalFeature = getOriginalFeature( - listableLayerSources, - selectedFeatureProperties - ) - if (originalFeature) { - dispatch('setOlFeatureInformation', { - feature: getCluster( - rootGetters.map, - originalFeature, - '_gfiLayerId' - ), - }) - } - } - } - ) - } - }, - setupTooltip({ getters: { gfiConfiguration }, rootGetters: { map } }) { - const tooltipLayerIds = Object.keys(gfiConfiguration.layers).filter( - (key) => gfiConfiguration.layers[key].showTooltip - ) - if (!tooltipLayerIds.length) { - return - } - - let element: Tooltip['element'], unregister: Tooltip['unregister'] - const overlay = new Overlay({ - positioning: 'bottom-center', - offset: [0, -5], - }) - map.addOverlay(overlay) - map.on('pointermove', ({ pixel, dragging, originalEvent }) => { - if (dragging || ['touch', 'pen'].includes(originalEvent.pointerType)) { - return - } - let hasFeatureAtPixel = false - // stops on return `true`, thus only using the uppermost feature - map.forEachFeatureAtPixel( - pixel, - (feature, layer) => { - if (!(feature instanceof Feature)) { - return false - } - hasFeatureAtPixel = true - overlay.setPosition(map.getCoordinateFromPixel(pixel)) - if (unregister) { - unregister() - } - ;({ element, unregister } = getTooltip({ - localeKeys: - // @ts-expect-error | it exists by virtue of layerFilter below - gfiConfiguration.layers[layer.get('id')].showTooltip( - feature, - map - ), - })) - overlay.setElement(element) - return true - }, - { layerFilter: (layer) => tooltipLayerIds.includes(layer.get('id')) } - ) - if (!hasFeatureAtPixel) { - overlay.setPosition(undefined) - } - }) - }, + setupCoreListener, + setupMultiSelection, + setupTooltip, + setupZoomListeners, setupFeatureVisibilityUpdates({ commit, state, getters, rootGetters }) { // debounce to prevent update spam debouncedVisibilityChangeIndicator = debounce( @@ -283,6 +164,34 @@ export const makeActions = () => { dispatch('setCoreSelection', { feature, centerOnFeature }) } }, + setFeatureInformation( + { commit, getters }, + featuresByLayerId: FeaturesByLayerId + ) { + commit('clearFeatureInformation') + commit('setVisibleWindowFeatureIndex', 0) + clear(featureDisplayLayer) + + const filteredFeatures = Object.fromEntries( + Object.entries(filterFeatures(featuresByLayerId)).map( + ([layerId, features]) => { + const { isSelectable } = getters.gfiConfiguration.layers[layerId] + return [ + layerId, + typeof isSelectable === 'function' + ? features.filter((feature) => isSelectable(feature)) + : features, + ] + } + ) + ) + commit('setFeatureInformation', filteredFeatures) + renderFeatures( + featureDisplayLayer, + getters.geometryLayerKeys, + filteredFeatures + ) + }, hover({ commit, rootGetters }, feature: Feature) { if (rootGetters.configuration.extendedMasterportalapiMarkers) { commit('setHovered', feature, { root: true }) diff --git a/packages/plugins/Gfi/src/store/actions/setup.ts b/packages/plugins/Gfi/src/store/actions/setup.ts new file mode 100644 index 000000000..8247cc59e --- /dev/null +++ b/packages/plugins/Gfi/src/store/actions/setup.ts @@ -0,0 +1,150 @@ +import { PolarActionContext, PolarStore } from '@polar/lib-custom-types' +import { GeoJsonProperties } from 'geojson' + +import getCluster from '@polar/lib-get-cluster' +import { getTooltip, Tooltip } from '@polar/lib-tooltip' +import Overlay from 'ol/Overlay' +import { Feature } from 'ol' +import { DragBox } from 'ol/interaction' +import { platformModifierKeyOnly } from 'ol/events/condition' +import { GfiGetters, GfiState } from '../../types' +import { getOriginalFeature } from '../../utils/getOriginalFeature' + +export function setupCoreListener( + this: PolarStore, + { + getters: { gfiConfiguration }, + rootGetters, + dispatch, + }: PolarActionContext +) { + if (gfiConfiguration.featureList?.bindWithCoreHoverSelect) { + this.watch( + () => rootGetters.selected, + (feature) => dispatch('setOlFeatureInformation', { feature }), + { deep: true } + ) + } +} + +export function setupMultiSelection({ + dispatch, + getters, + rootGetters, +}: PolarActionContext) { + if (getters.gfiConfiguration.boxSelect) { + const dragBox = new DragBox({ condition: platformModifierKeyOnly }) + dragBox.on('boxend', () => + dispatch('getFeatureInfo', { + coordinateOrExtent: dragBox.getGeometry().getExtent(), + modifierPressed: true, + }) + ) + rootGetters.map.addInteraction(dragBox) + } + if (getters.gfiConfiguration.directSelect) { + rootGetters.map.on('click', ({ coordinate, originalEvent }) => + dispatch('getFeatureInfo', { + coordinateOrExtent: coordinate, + modifierPressed: + navigator.userAgent.indexOf('Mac') !== -1 + ? originalEvent.metaKey + : originalEvent.ctrlKey, + }) + ) + } +} + +export function setupTooltip({ + getters: { gfiConfiguration }, + rootGetters: { map }, +}: PolarActionContext) { + const tooltipLayerIds = Object.keys(gfiConfiguration.layers).filter( + (key) => gfiConfiguration.layers[key].showTooltip + ) + if (!tooltipLayerIds.length) { + return + } + + let element: Tooltip['element'], unregister: Tooltip['unregister'] + const overlay = new Overlay({ + positioning: 'bottom-center', + offset: [0, -5], + }) + map.addOverlay(overlay) + map.on('pointermove', ({ pixel, dragging, originalEvent }) => { + if (dragging || ['touch', 'pen'].includes(originalEvent.pointerType)) { + return + } + let hasFeatureAtPixel = false + // stops on return `true`, thus only using the uppermost feature + map.forEachFeatureAtPixel( + pixel, + (feature, layer) => { + if (!(feature instanceof Feature)) { + return false + } + hasFeatureAtPixel = true + overlay.setPosition(map.getCoordinateFromPixel(pixel)) + if (unregister) { + unregister() + } + ;({ element, unregister } = getTooltip({ + localeKeys: + // @ts-expect-error | it exists by virtue of layerFilter below + gfiConfiguration.layers[layer.get('id')].showTooltip(feature, map), + })) + overlay.setElement(element) + return true + }, + { layerFilter: (layer) => tooltipLayerIds.includes(layer.get('id')) } + ) + if (!hasFeatureAtPixel) { + overlay.setPosition(undefined) + } + }) +} + +export function setupZoomListeners( + this: PolarStore, + { dispatch, getters, rootGetters }: PolarActionContext +) { + if (getters.gfiConfiguration.featureList) { + this.watch( + () => rootGetters.zoomLevel, + () => { + const { + featureInformation, + listableLayerSources, + visibleWindowFeatureIndex, + windowFeatures, + } = getters + + if (windowFeatures.length) { + const layerId: string = + // @ts-expect-error | if windowFeatures has features, visibleWindowFeatureIndex is in the range of possible features + windowFeatures[visibleWindowFeatureIndex].polarInternalLayerKey + const selectedFeatureProperties: GeoJsonProperties = { + // eslint-disable-next-line @typescript-eslint/naming-convention + _gfiLayerId: layerId, + ...featureInformation[layerId][visibleWindowFeatureIndex] + .properties, + } + const originalFeature = getOriginalFeature( + listableLayerSources, + selectedFeatureProperties + ) + if (originalFeature) { + dispatch('setOlFeatureInformation', { + feature: getCluster( + rootGetters.map, + originalFeature, + '_gfiLayerId' + ), + }) + } + } + } + ) + } +} diff --git a/packages/plugins/Gfi/src/types.ts b/packages/plugins/Gfi/src/types.ts index 8e2918ee5..6cb976d86 100644 --- a/packages/plugins/Gfi/src/types.ts +++ b/packages/plugins/Gfi/src/types.ts @@ -34,6 +34,8 @@ export interface RequestGfiWmsParameters { layer: TileLayer } +export type FeaturesByLayerId = Record + /** GFI Vuex Module State */ export interface GfiState { /** default style for stroke and fill of the highlighted feature. */ diff --git a/packages/plugins/Gfi/src/utils/filterFeatures.ts b/packages/plugins/Gfi/src/utils/filterFeatures.ts new file mode 100644 index 000000000..3e63bb1d3 --- /dev/null +++ b/packages/plugins/Gfi/src/utils/filterFeatures.ts @@ -0,0 +1,13 @@ +import { Feature as GeoJsonFeature } from 'geojson' +import { FeaturesByLayerId } from '../types' + +export function filterFeatures( + featuresByLayerId: FeaturesByLayerId +): Record { + const entries = Object.entries(featuresByLayerId) + const filtered = entries.filter((keyValue) => Array.isArray(keyValue[1])) as [ + string, + GeoJsonFeature[] + ][] + return Object.fromEntries(filtered) +} diff --git a/packages/plugins/Gfi/src/utils/renderFeatures.ts b/packages/plugins/Gfi/src/utils/renderFeatures.ts new file mode 100644 index 000000000..0f0f8ad49 --- /dev/null +++ b/packages/plugins/Gfi/src/utils/renderFeatures.ts @@ -0,0 +1,21 @@ +import VectorLayer from 'ol/layer/Vector' +import { Feature } from 'ol' +import { Geometry } from 'ol/geom' +import { FeaturesByLayerId } from '../types' +import { addFeature } from './displayFeatureLayer' +import { filterFeatures } from './filterFeatures' + +export function renderFeatures( + featureDisplayLayer: VectorLayer>, + geometryLayerKeys: string[], + features: FeaturesByLayerId +) { + const filteredFeatures = filterFeatures(features) + geometryLayerKeys + .filter((key) => Array.isArray(features[key])) + .forEach((key) => + filteredFeatures[key].forEach((feature) => + addFeature(feature, featureDisplayLayer) + ) + ) +} diff --git a/packages/plugins/Gfi/tests/filterFeatures.spec.ts b/packages/plugins/Gfi/tests/filterFeatures.spec.ts new file mode 100644 index 000000000..60cdfe5c8 --- /dev/null +++ b/packages/plugins/Gfi/tests/filterFeatures.spec.ts @@ -0,0 +1,67 @@ +import { filterFeatures } from '../src/utils/filterFeatures' + +describe('filterFeatures', () => { + beforeEach(() => { + jest.clearAllMocks() + jest.useFakeTimers() + }) + + it('should return an object with only GeoJsonFeature[] as its values if the values were already only GeoJsonFeature[]', () => { + expect( + Object.keys( + filterFeatures({ + idOne: [ + { + type: 'Feature', + geometry: { type: 'Point', coordinates: [0, 0] }, + properties: { goodFeature: true }, + }, + ], + idTwo: [ + { + type: 'Feature', + geometry: { type: 'Point', coordinates: [1, 1] }, + properties: { goodFeature: true }, + }, + { + type: 'Feature', + geometry: { type: 'Point', coordinates: [0, 1] }, + properties: { goodFeature: false }, + }, + ], + }) + ).length + ).toBe(2) + }) + it('should return an object with less keys if some values are symbols', () => { + expect( + Object.keys( + filterFeatures({ + idOne: Symbol('failing'), + idTwo: [ + { + type: 'Feature', + geometry: { type: 'Point', coordinates: [1, 1] }, + properties: { goodFeature: true }, + }, + { + type: 'Feature', + geometry: { type: 'Point', coordinates: [0, 1] }, + properties: { goodFeature: false }, + }, + ], + }) + ).length + ).toBe(1) + }) + it('should return an empty object if all values are symbols', () => { + expect( + Object.keys( + filterFeatures({ + idOne: Symbol('failing'), + idTwo: Symbol('another fail'), + }) + ).length + ).toBe(0) + }) +}) diff --git a/packages/plugins/Gfi/tests/sortFeatures.spec.ts b/packages/plugins/Gfi/tests/sortFeatures.spec.ts new file mode 100644 index 000000000..fd9d7a9e4 --- /dev/null +++ b/packages/plugins/Gfi/tests/sortFeatures.spec.ts @@ -0,0 +1,235 @@ +import { Feature as GeoJsonFeature, LineString, Point, Polygon } from 'geojson' +import sortFeatures from '../src/utils/sortFeatures' + +describe('sortFeatures', () => { + beforeEach(() => { + jest.clearAllMocks() + jest.useFakeTimers() + }) + + describe('sorting two features', () => { + it('should sort the features by area (biggest first) if both are Polygons', () => { + const featureA: GeoJsonFeature = { + type: 'Feature', + geometry: { + type: 'Polygon', + coordinates: [ + [ + [10.5, 59], + [15, 59], + [15, 56], + [10, 56], + [10.5, 59], + ], + ], + }, + properties: {}, + } + const featureB: GeoJsonFeature = { + type: 'Feature', + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-4.25, 62], + [27.5, 62], + [26.5, 45], + [-4.25, 50], + [-4.5, 62.25], + ], + ], + }, + properties: {}, + } + expect( + [featureA, featureB].sort((a, b) => sortFeatures(a, b, 'EPSG:4326')) + ).toEqual([featureB, featureA]) + }) + it('should put the Polygon feature first if the other feature is not a Polygon', () => { + const featureA: GeoJsonFeature = { + type: 'Feature', + geometry: { + type: 'LineString', + coordinates: [ + [1.5, 55], + [20, 55], + ], + }, + properties: {}, + } + const featureB: GeoJsonFeature = { + type: 'Feature', + geometry: { + type: 'Polygon', + coordinates: [ + [ + [10.5, 59], + [15, 59], + [15, 56], + [10, 56], + [10.5, 59], + ], + ], + }, + properties: {}, + } + expect( + [featureA, featureB].sort((a, b) => sortFeatures(a, b, 'EPSG:4326')) + ).toEqual([featureB, featureA]) + }) + it('should should keep the original order if both features are LineStrings', () => { + const featureA: GeoJsonFeature = { + type: 'Feature', + geometry: { + type: 'LineString', + coordinates: [ + [1.5, 55], + [20, 55], + ], + }, + properties: {}, + } + const featureB: GeoJsonFeature = { + type: 'Feature', + geometry: { + type: 'LineString', + coordinates: [ + [1.5, 55], + [20, 55], + [10, 56], + ], + }, + properties: {}, + } + expect( + [featureA, featureB].sort((a, b) => sortFeatures(a, b, 'EPSG:4326')) + ).toEqual([featureA, featureB]) + }) + it('should put the LineString feature first if the other feature is a Point Feature', () => { + const featureA: GeoJsonFeature = { + type: 'Feature', + geometry: { + type: 'Point', + coordinates: [1.5, 55], + }, + properties: {}, + } + const featureB: GeoJsonFeature = { + type: 'Feature', + geometry: { + type: 'LineString', + coordinates: [ + [1.5, 55], + [20, 55], + [10, 56], + ], + }, + properties: {}, + } + expect( + [featureA, featureB].sort((a, b) => sortFeatures(a, b, 'EPSG:4326')) + ).toEqual([featureB, featureA]) + }) + it('should should keep the original order if both features are Points', () => { + const featureA: GeoJsonFeature = { + type: 'Feature', + geometry: { + type: 'Point', + coordinates: [1.5, 55], + }, + properties: {}, + } + const featureB: GeoJsonFeature = { + type: 'Feature', + geometry: { + type: 'Point', + coordinates: [55, 1.5], + }, + properties: {}, + } + expect( + [featureA, featureB].sort((a, b) => sortFeatures(a, b, 'EPSG:4326')) + ).toEqual([featureA, featureB]) + }) + }) + describe('sorting more than two features', () => { + it('should order the features by Polygons first, then LineStrings and lastly Points', () => { + const featureA: GeoJsonFeature = { + type: 'Feature', + geometry: { + type: 'LineString', + coordinates: [ + [1.5, 55], + [20, 55], + ], + }, + properties: {}, + } + const featureB: GeoJsonFeature = { + type: 'Feature', + geometry: { + type: 'LineString', + coordinates: [ + [1.5, 55], + [20, 55], + [10, 56], + ], + }, + properties: {}, + } + const featureC: GeoJsonFeature = { + type: 'Feature', + geometry: { + type: 'Point', + coordinates: [1.5, 55], + }, + properties: {}, + } + const featureD: GeoJsonFeature = { + type: 'Feature', + geometry: { + type: 'Polygon', + coordinates: [ + [ + [10.5, 59], + [15, 59], + [15, 56], + [10, 56], + [10.5, 59], + ], + ], + }, + properties: {}, + } + const featureE: GeoJsonFeature = { + type: 'Feature', + geometry: { + type: 'Point', + coordinates: [55, 1.5], + }, + properties: {}, + } + const featureF: GeoJsonFeature = { + type: 'Feature', + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-4.25, 62], + [27.5, 62], + [26.5, 45], + [-4.25, 50], + [-4.5, 62.25], + ], + ], + }, + properties: {}, + } + expect( + [featureA, featureB, featureC, featureD, featureE, featureF].sort( + (a, b) => sortFeatures(a, b, 'EPSG:4326') + ) + ).toEqual([featureF, featureD, featureA, featureB, featureC, featureE]) + }) + }) +})