adb-shared 6.2.20 → 6.2.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,824 @@
1
+ import * as i1$1 from '@angular/common';
2
+ import { CommonModule } from '@angular/common';
3
+ import * as i0 from '@angular/core';
4
+ import { forwardRef, Component, InjectionToken, Inject, Injectable, Input, signal, effect, NgModule } from '@angular/core';
5
+ import * as i4 from '@angular/forms';
6
+ import { NG_VALUE_ACCESSOR, FormControl, FormGroup, FormArray, Validators, FormsModule, ReactiveFormsModule } from '@angular/forms';
7
+ import * as i1 from '@ngx-translate/core';
8
+ import { TranslateModule } from '@ngx-translate/core';
9
+ import * as Leaflet from 'leaflet';
10
+ import Leaflet__default from 'leaflet';
11
+ import '@geoman-io/leaflet-geoman-free';
12
+ import { Subscription, combineLatest, map, debounceTime } from 'rxjs';
13
+ import * as i3 from '@angular/router';
14
+ import { RouterModule } from '@angular/router';
15
+ import { finalize } from 'rxjs/operators';
16
+ import * as i3$1 from '@angular/common/http';
17
+ import * as i5 from 'adb-shared';
18
+ import { AdbDatePickerModule, AdbPipesModule, AdbFilterSectionModule, AdbPickerModule } from 'adb-shared';
19
+ import { addYears, startOfYear, endOfYear, startOfDay, endOfDay, parseISO } from 'date-fns';
20
+
21
+ class AdbMapUtils {
22
+ static INITIAL_CENTER_LAT = 62;
23
+ static INITIAL_CENTER_LNG = 17;
24
+ static OverlayMaps = {
25
+ 'Altitude': new Leaflet.TileLayer.WMS("https://maps.sgu.se/lmv/hojdmodell/v1.1?SERVICE=WMS", { layers: "terrangskuggning", format: "image/png", transparent: true, opacity: 0.3, maxZoom: 16, version: "1.3.0" }),
26
+ 'Fjällkarta': new Leaflet.TileLayer.WMS('api/maps/mountain', {
27
+ minZoom: 6,
28
+ maxZoom: 18,
29
+ maxNativeZoom: 14,
30
+ attribution: '© <a href="https://www.lantmateriet.se/">Fjällkart</a>',
31
+ wms: true
32
+ }),
33
+ 'Kommuner': new Leaflet.TileLayer.WMS('https://sosgeo.artdata.slu.se/geoserver/GeoRegion/wms?', {
34
+ layers: 'GeoRegion:Municipality',
35
+ version: '1.1.0',
36
+ transparent: true,
37
+ format: 'image/png'
38
+ }),
39
+ 'Län': new Leaflet.TileLayer.WMS('https://sosgeo.artdata.slu.se/geoserver/GeoRegion/wms?', {
40
+ layers: 'GeoRegion:County',
41
+ version: '1.1.0',
42
+ transparent: true,
43
+ format: 'image/png'
44
+ }),
45
+ 'Ortnamn': new Leaflet.TileLayer.WMS("https://maps.sgu.se/lmv/topowebb-skikt/v1.1?SERVICE=WMS", {
46
+ layers: "text",
47
+ format: "image/png",
48
+ transparent: true,
49
+ version: "1.3.0",
50
+ }),
51
+ 'Ekonomisk zon': new Leaflet.TileLayer.WMS("https://sosgeo.artdata.slu.se/geoserver/it.geosolutions/wms?", {
52
+ layers: "it.geosolutions:Ekonomiska_zonens_yttre_avgränsningslinjer_linje",
53
+ format: "image/png",
54
+ transparent: true,
55
+ version: "1.1.0"
56
+ })
57
+ };
58
+ static Providers = [
59
+ {
60
+ name: 'Open Streetmap',
61
+ id: 3,
62
+ url: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
63
+ minZoom: 0,
64
+ maxZoom: 18,
65
+ params: {
66
+ maxNativeZoom: 18,
67
+ attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
68
+ },
69
+ wms: false
70
+ },
71
+ {
72
+ name: 'Sveriges gränser',
73
+ id: 7,
74
+ url: 'https://sosgeo.artdata.slu.se/geoserver/it.geosolutions/wms',
75
+ minZoom: 6,
76
+ maxZoom: 18,
77
+ params: {
78
+ maxNativeZoom: 14,
79
+ attribution: '© <a href="https://www.lantmateriet.se/">Sveriges gränser</a>',
80
+ layers: 'it.geosolutions:Sverige_epsg5845', styles: 'municipality_name_yellow',
81
+ epsg: 5845
82
+ },
83
+ wms: true
84
+ }
85
+ ];
86
+ //NOTE: Will be move
87
+ static redListMapper2 = {
88
+ 'VU': '228',
89
+ 'EN': '229',
90
+ 'CR': '230',
91
+ 'RE': '231',
92
+ 'DD': '232',
93
+ 'NT': '233'
94
+ };
95
+ static createMap(elementId, options = {}) {
96
+ const map = Leaflet.map(elementId, {
97
+ zoomAnimation: false,
98
+ fadeAnimation: false,
99
+ markerZoomAnimation: false,
100
+ inertia: false,
101
+ scrollWheelZoom: options.scrollZoom ?? true,
102
+ zoomControl: options.zoom ?? true,
103
+ dragging: options.zoom ?? true,
104
+ minZoom: -1
105
+ });
106
+ map.setView([62, 17], 5);
107
+ Leaflet.control.scale().addTo(map);
108
+ if (options.providers !== false) {
109
+ this.addProviders(map);
110
+ }
111
+ return map;
112
+ }
113
+ static addProviders(map) {
114
+ const backgroundMaps = {};
115
+ for (const value of Object.values(AdbMapUtils.Providers)) {
116
+ backgroundMaps[value.name] = value.wms
117
+ ? Leaflet.tileLayer.wms(value.url, value.params)
118
+ : Leaflet.tileLayer(value.url, value.params);
119
+ }
120
+ if (backgroundMaps['Open Streetmap']) {
121
+ map.addLayer(backgroundMaps['Open Streetmap']);
122
+ }
123
+ Leaflet.control.layers(backgroundMaps, AdbMapUtils.OverlayMaps).addTo(map);
124
+ }
125
+ }
126
+
127
+ class PolygonDrawerInput {
128
+ subscriptions = new Subscription();
129
+ mapId = "" + Math.floor(Math.random() * Date.now());
130
+ map;
131
+ polygonDraw = false;
132
+ shapeLayer = null;
133
+ geoJson;
134
+ ngAfterViewInit() {
135
+ this.map = AdbMapUtils.createMap(this.mapId, {
136
+ zoom: true,
137
+ scrollZoom: true,
138
+ providers: true
139
+ });
140
+ this.map.whenReady(() => {
141
+ this.initDraw();
142
+ this.renderInitialShape();
143
+ });
144
+ }
145
+ ngOnInit() { }
146
+ ngOnDestroy() {
147
+ this.subscriptions.unsubscribe();
148
+ }
149
+ initDraw() {
150
+ this.map.pm.removeControls();
151
+ this.map.pm.enableDraw('Polygon', {
152
+ tooltips: false,
153
+ finishOn: 'snap',
154
+ snapDistance: 30,
155
+ snapMiddle: false
156
+ });
157
+ this.map.on("pm:create", (e) => {
158
+ if (e.shape !== "Polygon")
159
+ return;
160
+ this.polygonDraw = false;
161
+ this.map.pm.disableDraw();
162
+ this.clearShape();
163
+ this.shapeLayer = Leaflet__default.geoJSON(e.layer.toGeoJSON(), {
164
+ interactive: false
165
+ });
166
+ this.map.addLayer(this.shapeLayer);
167
+ this.map.fitBounds(this.shapeLayer.getBounds());
168
+ this.onChange(e.layer.toGeoJSON());
169
+ this.map.removeLayer(e.layer);
170
+ });
171
+ }
172
+ renderInitialShape() {
173
+ if (this.geoJson?.type === "Feature") {
174
+ this.shapeLayer = Leaflet__default.geoJSON(this.geoJson, {
175
+ interactive: false
176
+ });
177
+ this.map.addLayer(this.shapeLayer);
178
+ this.map.fitBounds(this.shapeLayer.getBounds());
179
+ }
180
+ else {
181
+ this.map.setView([AdbMapUtils.INITIAL_CENTER_LAT, AdbMapUtils.INITIAL_CENTER_LNG], 5);
182
+ }
183
+ }
184
+ clearShape() {
185
+ if (this.shapeLayer) {
186
+ this.map.removeLayer(this.shapeLayer);
187
+ this.shapeLayer = null;
188
+ }
189
+ }
190
+ onDrawPolygon() {
191
+ this.polygonDraw = true;
192
+ this.map.pm.enableDraw("Polygon", {
193
+ allowSelfIntersection: false,
194
+ snappable: false
195
+ });
196
+ }
197
+ onUndoStep() {
198
+ const draw = this.map.pm.Draw?.Polygon;
199
+ draw?._removeLastVertex();
200
+ }
201
+ onDeleteShape() {
202
+ this.clearShape();
203
+ this.map.pm.disableDraw();
204
+ this.polygonDraw = false;
205
+ this.onChange(null);
206
+ this.map.setView([62, 17], 5);
207
+ }
208
+ // ControlValueAccessor
209
+ onChange = () => { };
210
+ onTouched = () => { };
211
+ writeValue(geoJson) {
212
+ this.geoJson = geoJson;
213
+ }
214
+ registerOnChange(fn) {
215
+ this.onChange = fn;
216
+ }
217
+ registerOnTouched(fn) {
218
+ this.onTouched = fn;
219
+ }
220
+ setDisabledState(isDisabled) { }
221
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: PolygonDrawerInput, deps: [], target: i0.ɵɵFactoryTarget.Component });
222
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: PolygonDrawerInput, isStandalone: false, selector: "adb-polygon-drawer", providers: [
223
+ {
224
+ provide: NG_VALUE_ACCESSOR,
225
+ useExisting: forwardRef(() => PolygonDrawerInput),
226
+ multi: true
227
+ }
228
+ ], ngImport: i0, template: "<div class=\"mb-1 d-flex gap-1 flex-wrap align-items-start\">\r\n <button class=\"btn btn-secondary\" type=\"button\" (click)=\"onDrawPolygon()\" title=\"Polygon\">\r\n <span class=\"fas fa-draw-polygon\"></span> {{'OBSERVATION.DRAW'|translate}}\r\n </button>\r\n @if (polygonDraw) {\r\n <button class=\"btn btn-secondary\" type=\"button\" (click)=\"onDeleteShape()\" title=\"Ta bort\">\r\n <span class=\"fas fa-times\"></span>\r\n </button>\r\n <button class=\"btn btn-secondary\" type=\"button\" (click)=\"onUndoStep()\" title=\"\u00C5ngra\">\r\n <span class=\"fas fa-undo\"></span>\r\n </button>\r\n }\r\n</div>\r\n<div [id]=\"mapId\" class=\"border\" style=\"height:20rem\"></div>", dependencies: [{ kind: "pipe", type: i1.TranslatePipe, name: "translate" }] });
229
+ }
230
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: PolygonDrawerInput, decorators: [{
231
+ type: Component,
232
+ args: [{ selector: "adb-polygon-drawer", standalone: false, providers: [
233
+ {
234
+ provide: NG_VALUE_ACCESSOR,
235
+ useExisting: forwardRef(() => PolygonDrawerInput),
236
+ multi: true
237
+ }
238
+ ], template: "<div class=\"mb-1 d-flex gap-1 flex-wrap align-items-start\">\r\n <button class=\"btn btn-secondary\" type=\"button\" (click)=\"onDrawPolygon()\" title=\"Polygon\">\r\n <span class=\"fas fa-draw-polygon\"></span> {{'OBSERVATION.DRAW'|translate}}\r\n </button>\r\n @if (polygonDraw) {\r\n <button class=\"btn btn-secondary\" type=\"button\" (click)=\"onDeleteShape()\" title=\"Ta bort\">\r\n <span class=\"fas fa-times\"></span>\r\n </button>\r\n <button class=\"btn btn-secondary\" type=\"button\" (click)=\"onUndoStep()\" title=\"\u00C5ngra\">\r\n <span class=\"fas fa-undo\"></span>\r\n </button>\r\n }\r\n</div>\r\n<div [id]=\"mapId\" class=\"border\" style=\"height:20rem\"></div>" }]
239
+ }] });
240
+
241
+ var AdbMapFilterType;
242
+ (function (AdbMapFilterType) {
243
+ AdbMapFilterType["Taxon"] = "taxon";
244
+ AdbMapFilterType["Area"] = "area";
245
+ AdbMapFilterType["OwnArea"] = "ownArea";
246
+ AdbMapFilterType["Time"] = "time";
247
+ AdbMapFilterType["RedList"] = "redlist";
248
+ AdbMapFilterType["RiskList"] = "risklist";
249
+ AdbMapFilterType["TaxaLists"] = "taxonLists";
250
+ AdbMapFilterType["Datasources"] = "datasources";
251
+ })(AdbMapFilterType || (AdbMapFilterType = {}));
252
+ class VisibleFilters {
253
+ showTaxon;
254
+ showTime;
255
+ showArea;
256
+ showOwnArea;
257
+ showTaxaLists;
258
+ showDatasets;
259
+ }
260
+ const ADB_MAP_CONFIG = new InjectionToken('ADB_MAP_CONFIG');
261
+ class AdbMapConfigService {
262
+ log;
263
+ artfaktaTaxonLists;
264
+ observationFeatures;
265
+ taxaListsApi;
266
+ filters;
267
+ observationPage;
268
+ constructor(config) {
269
+ this.log = config.log ?? false;
270
+ this.artfaktaTaxonLists = config.artfaktaTaxonLists ?? 'https://artfakta.se/metadata/listor';
271
+ this.taxaListsApi = config.api + '/taxalists';
272
+ this.observationFeatures = config.api + '/observation-features';
273
+ this.observationPage = config.observationPage ?? 'https://mobil.artportalen.se/observationer';
274
+ this.filters = {
275
+ showTaxon: this.hasFilter(AdbMapFilterType.Taxon, config.filters),
276
+ showTime: this.hasFilter(AdbMapFilterType.Time, config.filters),
277
+ showArea: this.hasFilter(AdbMapFilterType.Area, config.filters),
278
+ showOwnArea: this.hasFilter(AdbMapFilterType.OwnArea, config.filters),
279
+ showTaxaLists: this.hasFilter(AdbMapFilterType.TaxaLists, config.filters),
280
+ showDatasets: this.hasFilter(AdbMapFilterType.Datasources, config.filters),
281
+ };
282
+ }
283
+ hasFilter(f, filters) {
284
+ return filters?.includes(f) ?? true;
285
+ }
286
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: AdbMapConfigService, deps: [{ token: ADB_MAP_CONFIG }], target: i0.ɵɵFactoryTarget.Injectable });
287
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: AdbMapConfigService });
288
+ }
289
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: AdbMapConfigService, decorators: [{
290
+ type: Injectable
291
+ }], ctorParameters: () => [{ type: undefined, decorators: [{
292
+ type: Inject,
293
+ args: [ADB_MAP_CONFIG]
294
+ }] }] });
295
+
296
+ class TreeviewComponent {
297
+ nodes = [];
298
+ selectedIds = [];
299
+ onChange = () => { };
300
+ onTouched = () => { };
301
+ writeValue(ids) {
302
+ this.selectedIds = ids ?? [];
303
+ this.applySelection(this.nodes);
304
+ }
305
+ registerOnChange(fn) {
306
+ this.onChange = fn;
307
+ }
308
+ registerOnTouched(fn) {
309
+ this.onTouched = fn;
310
+ }
311
+ setDisabledState(isDisabled) {
312
+ this.setReadOnly(this.nodes, isDisabled);
313
+ }
314
+ onLeafToggle(node) {
315
+ if (node._readOnly)
316
+ return;
317
+ node._selected = !node._selected;
318
+ this.selectedIds = TreeviewComponent.collectSelectedLeafIds(this.nodes);
319
+ this.onChange(this.selectedIds);
320
+ this.onTouched();
321
+ }
322
+ applySelection(nodes) {
323
+ for (const node of nodes) {
324
+ if (node.children?.length) {
325
+ this.applySelection(node.children);
326
+ }
327
+ else {
328
+ node._selected = this.selectedIds.includes(node.id);
329
+ }
330
+ }
331
+ }
332
+ setReadOnly(nodes, readOnly) {
333
+ for (const node of nodes) {
334
+ node._readOnly = readOnly;
335
+ if (node.children?.length) {
336
+ this.setReadOnly(node.children, readOnly);
337
+ }
338
+ }
339
+ }
340
+ static collectSelectedLeafIds(nodes) {
341
+ const ids = [];
342
+ for (const node of nodes) {
343
+ if (node.children?.length) {
344
+ ids.push(...this.collectSelectedLeafIds(node.children));
345
+ }
346
+ else if (node._selected) {
347
+ ids.push(node.id);
348
+ }
349
+ }
350
+ return ids;
351
+ }
352
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: TreeviewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
353
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: TreeviewComponent, isStandalone: false, selector: "app-treeview", inputs: { nodes: "nodes" }, providers: [
354
+ {
355
+ provide: NG_VALUE_ACCESSOR,
356
+ useExisting: forwardRef(() => TreeviewComponent),
357
+ multi: true
358
+ }
359
+ ], ngImport: i0, template: "<ng-container *ngTemplateOutlet=\"nodeTpl; context: { $implicit: nodes }\">\r\n</ng-container>\r\n<ng-template #nodeTpl let-nodes>\r\n @for (node of nodes; track node) {\r\n <div class=\"ms-3\">\r\n @if(!node.children || node.children.length === 0){\r\n <div class=\"d-flex align-items-center gap-2\">\r\n <input id=\"{{node.id}}\" type=\"checkbox\" class=\"form-check-input m-0\" [checked]=\"node._selected\" [disabled]=\"node._readOnly\" (change)=\"onLeafToggle(node)\" />\r\n <label for=\"{{node.id}}\">{{ node.name }}</label>\r\n </div>\r\n }\r\n @else {\r\n {{ node.name }}\r\n <div>\r\n <ng-container *ngTemplateOutlet=\"nodeTpl; context: { $implicit: node.children }\">\r\n </ng-container>\r\n </div>\r\n }\r\n </div>\r\n}\r\n</ng-template>", dependencies: [{ kind: "directive", type: i1$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }] });
360
+ }
361
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: TreeviewComponent, decorators: [{
362
+ type: Component,
363
+ args: [{ selector: 'app-treeview', standalone: false, providers: [
364
+ {
365
+ provide: NG_VALUE_ACCESSOR,
366
+ useExisting: forwardRef(() => TreeviewComponent),
367
+ multi: true
368
+ }
369
+ ], template: "<ng-container *ngTemplateOutlet=\"nodeTpl; context: { $implicit: nodes }\">\r\n</ng-container>\r\n<ng-template #nodeTpl let-nodes>\r\n @for (node of nodes; track node) {\r\n <div class=\"ms-3\">\r\n @if(!node.children || node.children.length === 0){\r\n <div class=\"d-flex align-items-center gap-2\">\r\n <input id=\"{{node.id}}\" type=\"checkbox\" class=\"form-check-input m-0\" [checked]=\"node._selected\" [disabled]=\"node._readOnly\" (change)=\"onLeafToggle(node)\" />\r\n <label for=\"{{node.id}}\">{{ node.name }}</label>\r\n </div>\r\n }\r\n @else {\r\n {{ node.name }}\r\n <div>\r\n <ng-container *ngTemplateOutlet=\"nodeTpl; context: { $implicit: node.children }\">\r\n </ng-container>\r\n </div>\r\n }\r\n </div>\r\n}\r\n</ng-template>" }]
370
+ }], propDecorators: { nodes: [{
371
+ type: Input
372
+ }] } });
373
+
374
+ class AdbObsMapComponent {
375
+ activatedRoute;
376
+ config;
377
+ router;
378
+ http;
379
+ subscriptions = new Subscription();
380
+ error;
381
+ mapLoading = signal(false, ...(ngDevMode ? [{ debugName: "mapLoading" }] : []));
382
+ siteLayer;
383
+ hasBox;
384
+ count;
385
+ mapId = '' + Math.floor(Math.random() * Date.now());
386
+ map = Leaflet.map;
387
+ //Lock start
388
+ mapReady = signal(false, ...(ngDevMode ? [{ debugName: "mapReady" }] : []));
389
+ hasQueryParams = false;
390
+ hasUrlParams = false;
391
+ queryParams;
392
+ urlParams;
393
+ //Lock end
394
+ constructor(activatedRoute, config, router, http) {
395
+ this.activatedRoute = activatedRoute;
396
+ this.config = config;
397
+ this.router = router;
398
+ this.http = http;
399
+ effect(() => {
400
+ // This will re-run whenever mapReady() changes
401
+ if (this.mapReady() && this.hasQueryParams && this.hasUrlParams) {
402
+ this.tryLoad();
403
+ }
404
+ });
405
+ }
406
+ ngAfterViewInit() {
407
+ this.map = AdbMapUtils.createMap(this.mapId, {
408
+ zoom: true,
409
+ scrollZoom: false,
410
+ providers: true
411
+ });
412
+ this.map.whenReady(() => {
413
+ this.mapReady.set(true);
414
+ });
415
+ }
416
+ // Loads data only after:
417
+ // - the Leaflet map is ready
418
+ // - Angular has emitted query params (may be empty)
419
+ // - Angular has emitted route params (may be empty)
420
+ tryLoad() {
421
+ if (!this.mapReady() || !this.hasQueryParams || !this.hasUrlParams) {
422
+ return;
423
+ }
424
+ const params = { ...this.queryParams };
425
+ this.hasBox = params['bbox'] ? true : false;
426
+ if (this.urlParams.has('taxonId')) {
427
+ params['taxonId'] = this.urlParams.get('taxonId');
428
+ }
429
+ this.loadFeature(params);
430
+ }
431
+ ngOnInit() {
432
+ this.subscriptions.add(this.activatedRoute.queryParams.subscribe(q => {
433
+ this.queryParams = q ?? {};
434
+ this.hasQueryParams = true;
435
+ this.tryLoad();
436
+ }));
437
+ this.subscriptions.add(this.activatedRoute.paramMap.subscribe(p => {
438
+ this.urlParams = p;
439
+ this.hasUrlParams = true;
440
+ this.tryLoad();
441
+ }));
442
+ }
443
+ loadFeature(qParams) {
444
+ this.mapLoading.set(true);
445
+ this.error = null;
446
+ const url = this.config.observationFeatures;
447
+ this.subscriptions.add(this.http.get(url, { observe: 'response', params: qParams }).
448
+ pipe(finalize(() => {
449
+ this.mapLoading.set(false);
450
+ })).
451
+ subscribe({
452
+ next: (response) => {
453
+ if (!response.body) {
454
+ throw new Error('No GeoJson body');
455
+ }
456
+ const geo = response.body;
457
+ geo.properties = { count: response.headers.get('X-Observations-TotalCount') };
458
+ this.buildMap(geo);
459
+ }, error: err => {
460
+ this.error = err;
461
+ }
462
+ }));
463
+ }
464
+ buildMap(geojson) {
465
+ if (this.siteLayer) {
466
+ this.map.removeLayer(this.siteLayer);
467
+ }
468
+ this.count = geojson?.properties?.count;
469
+ this.siteLayer = Leaflet.geoJson(geojson, {
470
+ style: (feature) => {
471
+ return {
472
+ className: `svg ${this.getPolygonClassName(feature.properties)}`, // Leaflet applies this class to the SVG path
473
+ weight: 1,
474
+ fillOpacity: 0.8,
475
+ };
476
+ },
477
+ pointToLayer: (feature, latlng) => {
478
+ const marker = Leaflet.marker(latlng, {
479
+ icon: Leaflet.divIcon({
480
+ className: '',
481
+ html: '<div class="adb-map-marker"></div>',
482
+ iconAnchor: [3, 3]
483
+ })
484
+ });
485
+ // if (feature.properties && feature.properties['Occurrence.OccurrenceId']) {
486
+ // marker.on('click', () => {
487
+ // window.location.href = this.config.observationPage + '/' + feature.properties['Occurrence.OccurrenceId'];
488
+ // });
489
+ // }
490
+ return marker;
491
+ }, onEachFeature: (feature, layer) => {
492
+ if (feature.geometry.type === "Polygon" ||
493
+ feature.geometry.type === "MultiPolygon") {
494
+ layer.setStyle({ interactive: false });
495
+ // if (feature.properties.ObservationsCount > 0) {
496
+ // layer.on('click', () => {
497
+ // const boundBox = JSON.stringify(this.getMapBoundsToBbox(layer.getBounds()));
498
+ // this.router.navigate([], { queryParams: { bbox: boundBox }, queryParamsHandling: 'merge' });
499
+ // });
500
+ // layer.on('mouseover', () => {
501
+ // layer.setStyle({ className: 'svg cursor-pointer' });
502
+ // });
503
+ // layer.on('mouseout', () => {
504
+ // layer.setStyle({ className: 'svg' });
505
+ // });
506
+ // }
507
+ // if (feature.properties.FeatureType) { //area
508
+ // layer.setStyle({ interactive: false });
509
+ // }
510
+ }
511
+ }
512
+ });
513
+ const circleLayer = Leaflet.featureGroup();
514
+ circleLayer.addTo(this.siteLayer);
515
+ this.map.addLayer(this.siteLayer);
516
+ if (geojson.bbox) {
517
+ const [minLon, minLat, maxLon, maxLat] = geojson.bbox;
518
+ const bounds = Leaflet.latLngBounds([[minLat, minLon], [maxLat, maxLon]]);
519
+ if (bounds.isValid()) {
520
+ // Safari sometimes needs extra time after layer adds
521
+ setTimeout(() => {
522
+ this.map.invalidateSize({ animate: false });
523
+ this.map.fitBounds(bounds, { animate: false });
524
+ }, 50); // 30–50ms works best in Safari
525
+ }
526
+ }
527
+ }
528
+ getPolygonClassName(props) {
529
+ if (props.FeatureType) {
530
+ return 'map-area';
531
+ }
532
+ if (props.ObservationsCount < 10)
533
+ return 'map-heat-low';
534
+ if (props.ObservationsCount < 500)
535
+ return 'map-heat-small';
536
+ return 'map-heat-high';
537
+ }
538
+ getMapBoundsToBbox(bounds) {
539
+ return [bounds.getNorthWest().lat, bounds.getNorthWest().lng, bounds.getSouthEast().lat, bounds.getSouthEast().lng];
540
+ }
541
+ ngOnDestroy() {
542
+ this.subscriptions.unsubscribe();
543
+ }
544
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: AdbObsMapComponent, deps: [{ token: i3.ActivatedRoute }, { token: AdbMapConfigService }, { token: i3.Router }, { token: i3$1.HttpClient }], target: i0.ɵɵFactoryTarget.Component });
545
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: AdbObsMapComponent, isStandalone: false, selector: "adb-obs-map", ngImport: i0, template: "<!-- @if(hasBox&&!mapLoading()){\r\n<div class=\"mb-1 d-flex justify-content-between flex-wrap\">\r\n <a [routerLink]=\"[]\" [queryParams]=\"{bbox:null,z:null}\" queryParamsHandling=\"merge\" class=\"d-flex gap-1 align-items-baseline\">\r\n <span class=\"fas fa-chevron-left\"></span>\r\n Tillbaka upp\r\n </a>\r\n</div>\r\n} -->\r\n@if(error){\r\n<div class=\"alert alert-danger\">\r\n {{'ERROR.SERVER_ERROR'|translate}}\r\n</div>\r\n}\r\n<div [class.loading]=\"mapLoading()\">\r\n <div [id]=\"mapId\" class=\"mb-1 map-container\"></div>\r\n</div>\r\n<div class=\"mb-1 d-flex gap-3 justify-content-between flex-wrap\">\r\n @if (!mapLoading()&&count) {\r\n <div class=\"d-flex align-items-center gap-1\">\r\n <div class=\"legend map-heat-low\"></div>\r\n &lt;10\r\n </div>\r\n <div class=\"d-flex align-items-center gap-1\">\r\n <div class=\"legend map-heat-medium\"></div>\r\n 10 - 500\r\n </div>\r\n <div class=\"d-flex align-items-center gap-1\">\r\n <div class=\"legend map-heat-high\"></div>\r\n &gt;500\r\n </div>\r\n <div class=\"ms-auto\">\r\n {{count|adbSpacing}} {{'OBSERVATION.OBSERVATIONS'|translate}}\r\n </div>\r\n }\r\n</div>", dependencies: [{ kind: "pipe", type: i1.TranslatePipe, name: "translate" }, { kind: "pipe", type: i5.NumberSpacingPipe, name: "adbSpacing" }] });
546
+ }
547
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: AdbObsMapComponent, decorators: [{
548
+ type: Component,
549
+ args: [{ selector: 'adb-obs-map', standalone: false, template: "<!-- @if(hasBox&&!mapLoading()){\r\n<div class=\"mb-1 d-flex justify-content-between flex-wrap\">\r\n <a [routerLink]=\"[]\" [queryParams]=\"{bbox:null,z:null}\" queryParamsHandling=\"merge\" class=\"d-flex gap-1 align-items-baseline\">\r\n <span class=\"fas fa-chevron-left\"></span>\r\n Tillbaka upp\r\n </a>\r\n</div>\r\n} -->\r\n@if(error){\r\n<div class=\"alert alert-danger\">\r\n {{'ERROR.SERVER_ERROR'|translate}}\r\n</div>\r\n}\r\n<div [class.loading]=\"mapLoading()\">\r\n <div [id]=\"mapId\" class=\"mb-1 map-container\"></div>\r\n</div>\r\n<div class=\"mb-1 d-flex gap-3 justify-content-between flex-wrap\">\r\n @if (!mapLoading()&&count) {\r\n <div class=\"d-flex align-items-center gap-1\">\r\n <div class=\"legend map-heat-low\"></div>\r\n &lt;10\r\n </div>\r\n <div class=\"d-flex align-items-center gap-1\">\r\n <div class=\"legend map-heat-medium\"></div>\r\n 10 - 500\r\n </div>\r\n <div class=\"d-flex align-items-center gap-1\">\r\n <div class=\"legend map-heat-high\"></div>\r\n &gt;500\r\n </div>\r\n <div class=\"ms-auto\">\r\n {{count|adbSpacing}} {{'OBSERVATION.OBSERVATIONS'|translate}}\r\n </div>\r\n }\r\n</div>" }]
550
+ }], ctorParameters: () => [{ type: i3.ActivatedRoute }, { type: AdbMapConfigService }, { type: i3.Router }, { type: i3$1.HttpClient }] });
551
+
552
+ const DateTypes = {
553
+ THIS_YEAR: '1',
554
+ LAST_YEAR: '2',
555
+ ALL_YEAR: '3',
556
+ LAST_5_YEAR: '4',
557
+ LAST_25_YEAR: '5',
558
+ CUSTOM: '6',
559
+ };
560
+
561
+ class AdbMapFilters {
562
+ cdr;
563
+ config;
564
+ http;
565
+ router;
566
+ activatedRoute;
567
+ inline = false;
568
+ static datasets = ['1', '3', '5', '6', '4', '8'];
569
+ subscriptions = new Subscription();
570
+ form;
571
+ thisYear = new Date();
572
+ lastYear = addYears(new Date(), -1);
573
+ queryParamsMap;
574
+ hasOwnArea;
575
+ geo;
576
+ area;
577
+ areasArray;
578
+ dataSetCount;
579
+ taxonListCount;
580
+ riskListCount;
581
+ redListCount;
582
+ areaCount;
583
+ periodCount;
584
+ listsError;
585
+ filters;
586
+ taxaLists;
587
+ hasTaxonInUrlParameter;
588
+ dsControls;
589
+ dateStartConfig;
590
+ dateEndConfig;
591
+ artfakta;
592
+ constructor(cdr, config, http, router, activatedRoute) {
593
+ this.cdr = cdr;
594
+ this.config = config;
595
+ this.http = http;
596
+ this.router = router;
597
+ this.activatedRoute = activatedRoute;
598
+ this.filters = config.filters;
599
+ this.artfakta = this.config.artfaktaTaxonLists;
600
+ }
601
+ ngOnInit() {
602
+ const lists$ = this.http.get(this.config.taxaListsApi, { params: { limit: -1 } });
603
+ const combined$ = combineLatest([this.activatedRoute.paramMap, this.activatedRoute.queryParamMap, lists$])
604
+ .pipe(map(results => ({ params: results[0], qParams: results[1], lists: results[2] })), debounceTime(0));
605
+ this.subscriptions.add(combined$.subscribe(result => {
606
+ this.form = this.createForm(result.params, result.qParams, result.lists);
607
+ this.cdr.markForCheck();
608
+ }));
609
+ }
610
+ removeArea(index) {
611
+ this.form.get('areas').removeAt(index);
612
+ this.form.markAsDirty();
613
+ }
614
+ addArea() {
615
+ const areas = this.form.get('areas');
616
+ areas.push(new FormControl(null));
617
+ this.form.updateValueAndValidity();
618
+ this.form.markAsDirty();
619
+ }
620
+ onSubmit() {
621
+ const data = this.form.value;
622
+ let params = {};
623
+ params.p = data.period ?? null;
624
+ if (data.period === DateTypes.THIS_YEAR) {
625
+ params.startAt = startOfYear(this.thisYear).toISOString();
626
+ params.endAt = null;
627
+ }
628
+ else if (data.period === DateTypes.LAST_YEAR) {
629
+ params.startAt = startOfYear(this.lastYear).toISOString();
630
+ params.endAt = endOfYear(this.lastYear).toISOString();
631
+ }
632
+ else if (data.period === DateTypes.ALL_YEAR) {
633
+ params.startAt = null;
634
+ params.endAt = null;
635
+ params.p = null;
636
+ }
637
+ else if (data.period === DateTypes.LAST_5_YEAR) {
638
+ params.startAt = startOfYear(addYears(new Date(), -5)).toISOString();
639
+ params.endAt = null;
640
+ }
641
+ else if (data.period === DateTypes.LAST_25_YEAR) {
642
+ params.startAt = startOfYear(addYears(new Date(), -25)).toISOString();
643
+ params.endAt = null;
644
+ }
645
+ else if (data.period === DateTypes.CUSTOM) {
646
+ params.startAt = startOfDay(data.startAt).toISOString();
647
+ params.endAt = endOfDay(data.endAt).toISOString();
648
+ }
649
+ if (this.hasOwnArea && data.geo) {
650
+ params.geo = JSON.stringify(data.geo.geometry?.coordinates[0]);
651
+ }
652
+ const cleaned = data.areas?.filter(v => v != null && v !== '');
653
+ if (cleaned.length > 0) {
654
+ params.area = cleaned;
655
+ }
656
+ if (this.activatedRoute.snapshot.paramMap.has('taxonId')) {
657
+ params.taxonId = this.activatedRoute.snapshot.paramMap.get('taxonId');
658
+ }
659
+ else {
660
+ params.taxonId = data.taxonId ?? null;
661
+ }
662
+ if (data.list?.length > 0) {
663
+ params.list = data.list;
664
+ }
665
+ if (data.ds?.length > 0) {
666
+ const selected = data.ds.filter(f => f.selected).map(m => m.id);
667
+ params.ds = selected.length ? selected : null;
668
+ }
669
+ this.router.navigate([], { queryParams: params, relativeTo: this.activatedRoute });
670
+ }
671
+ createForm(params, qMap, lists) {
672
+ this.hasTaxonInUrlParameter = params['taxonId'];
673
+ this.hasOwnArea = qMap.has('geo') && this.filters.showOwnArea;
674
+ const areas = qMap.getAll('area').filter(a => a != null && a.trim() !== '' && a.includes('-'));
675
+ const form = new FormGroup({
676
+ period: new FormControl(qMap.has('p') ? qMap.get('p') : '3'),
677
+ areas: new FormArray((Array.isArray(areas) && areas.length ? areas : [null]).map(a => new FormControl(a))),
678
+ geo: new FormControl(qMap.has('geo') ? {
679
+ type: 'Feature',
680
+ geometry: { type: 'Polygon', coordinates: [JSON.parse(qMap.get('geo'))] }
681
+ } : null)
682
+ });
683
+ this.areasArray = form.get('areas');
684
+ this.periodCount = qMap.has('p') && qMap.get('p') !== '3' ? 1 : 0;
685
+ this.areaCount = areas?.length;
686
+ if (!this.hasTaxonInUrlParameter) {
687
+ form.addControl('taxonId', new FormControl(qMap.get('taxonId') ?? null));
688
+ const selectedTaxonList = qMap.has('list') ? qMap.getAll('list').map(val => Number(val)).filter(val => !isNaN(val)) : null;
689
+ this.taxaLists = lists;
690
+ this.taxonListCount = selectedTaxonList?.length;
691
+ form.addControl('list', new FormControl(selectedTaxonList));
692
+ let datasets = qMap.has('ds') ? (Array.isArray(qMap.getAll('ds')) ? qMap.getAll('ds') : [qMap.get('ds')]) : null;
693
+ this.dataSetCount = datasets?.length;
694
+ form.addControl('ds', new FormArray(AdbMapFilters.datasets.map(id => new FormGroup({
695
+ id: new FormControl(id),
696
+ selected: new FormControl(datasets?.includes(id))
697
+ }))));
698
+ this.dsControls = form.get('ds').controls;
699
+ }
700
+ this.updateCustomDate(form, qMap.get('p'), qMap);
701
+ this.subscriptions.add(form.get('period').valueChanges.subscribe(period => {
702
+ this.updateCustomDate(form, period, qMap);
703
+ }));
704
+ return form;
705
+ }
706
+ updateCustomDate(form, period, qMap) {
707
+ if (form.contains('startAt')) {
708
+ form.removeControl('startAt');
709
+ }
710
+ if (form.contains('endAt')) {
711
+ form.removeControl('endAt');
712
+ }
713
+ if (period === DateTypes.CUSTOM) {
714
+ let start = qMap.has('startAt') ? parseISO(qMap.get('startAt')) : null;
715
+ let end = qMap.has('endAt') ? parseISO(qMap.get('endAt')) : null;
716
+ if (qMap.get('p') !== DateTypes.CUSTOM) {
717
+ start = new Date();
718
+ end = new Date();
719
+ }
720
+ this.dateStartConfig = { maxDate: end ?? new Date() };
721
+ this.dateEndConfig = { minDate: start ?? null, maxDate: new Date() };
722
+ const startControl = new FormControl(start, Validators.required);
723
+ const endControl = new FormControl(end, Validators.required);
724
+ form.addControl('startAt', startControl);
725
+ form.addControl('endAt', endControl);
726
+ this.subscriptions.add(startControl.valueChanges.subscribe(startAt => {
727
+ this.dateEndConfig = { minDate: startAt ?? null, maxDate: new Date() };
728
+ }));
729
+ this.subscriptions.add(endControl.valueChanges.subscribe(endAt => {
730
+ this.dateStartConfig = { maxDate: endAt ?? new Date() };
731
+ }));
732
+ }
733
+ }
734
+ ngOnDestroy() {
735
+ this.subscriptions.unsubscribe();
736
+ }
737
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: AdbMapFilters, deps: [{ token: i0.ChangeDetectorRef }, { token: AdbMapConfigService }, { token: i3$1.HttpClient }, { token: i3.Router }, { token: i3.ActivatedRoute }], target: i0.ɵɵFactoryTarget.Component });
738
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: AdbMapFilters, isStandalone: false, selector: "adb-map-filters", inputs: { inline: "inline" }, ngImport: i0, template: "@if(form){\r\n<form [formGroup]=\"form\" class=\"form mb-3\" (ngSubmit)=\"onSubmit()\">\r\n <div class=\"mb-3\">\r\n <adb-filter-section titleResource=\"TAXON.TAXA\" [expanded]=\"true\">\r\n @if(filters.showTaxon&&!hasTaxonInUrlParameter){\r\n <div class=\"mb-3\">\r\n <label for=\"species\">{{'TAXON.TAXON'|translate}}</label>\r\n <adb-taxon-picker formControlName=\"taxonId\" id=\"species\"></adb-taxon-picker>\r\n </div>\r\n }\r\n </adb-filter-section>\r\n @if(filters.showTime){\r\n <adb-filter-section [count]=\"periodCount\" titleResource=\"OBSERVATION.PERIOD\">\r\n <div class=\"mb-1\">\r\n <select class=\"form-select\" formControlName=\"period\" id=\"period\">\r\n <option value=\"1\">{{thisYear|date:'yyyy'}}</option>\r\n <option value=\"2\">{{lastYear|date:'yyyy'}}</option>\r\n <option value=\"3\">{{'OBSERVATION.ALL_YEAR'|translate}}</option>\r\n <option value=\"4\">{{'OBSERVATION.LAST_5_YEAR'|translate}}</option>\r\n <option value=\"5\">{{'OBSERVATION.LAST_25_YEAR'|translate}}</option>\r\n <option value=\"6\">{{'OBSERVATION.CUSTOMIZED'|translate}}</option>\r\n </select>\r\n </div>\r\n @if (form.value?.period==='6') {\r\n <div class=\"mb-2\">\r\n <label for=\"startAt\">{{'FROM' |translate}}</label>\r\n <input adbDatepicker type=\"text\" class=\"form-control datepicker\" id=\"startAt\" formControlName=\"startAt\" [settings]=\"dateStartConfig\">\r\n </div>\r\n <div class=\"mb-2\">\r\n <label for=\"endAt\">{{'TO' |translate}}</label>\r\n <input adbDatepicker type=\"text\" class=\"form-control datepicker\" id=\"endAt\" formControlName=\"endAt\" [settings]=\"dateEndConfig\">\r\n </div>\r\n }\r\n </adb-filter-section>\r\n }\r\n @if(filters.showArea){\r\n <adb-filter-section [count]=\"areaCount\" titleResource=\"GEOGRAPHY\" [lazy]=\"true\">\r\n <ng-template>\r\n @if(filters.showOwnArea){\r\n <div role=\"tablist\" aria-label=\"{{'AREA'|translate}}\" class=\"d-flex gap-2 justify-content-end\">\r\n <button class=\"btn btn-link p-0\" [class.text-dark]=\"!hasOwnArea\" (click)=\"hasOwnArea=false\" role=\"tab\" id=\"tab-select\" aria-selected=\"{{!hasOwnArea}}\" aria-controls=\"panel-select\">\r\n {{'CHOOSE'|translate}}\r\n </button>\r\n <button class=\"btn btn-link p-0\" [class.text-dark]=\"hasOwnArea\" (click)=\"hasOwnArea=true\" role=\"tab\" id=\"tab-draw\" aria-selected=\"false\" aria-controls=\"panel-draw\">\r\n {{'OWN_AREA'|translate}}\r\n </button>\r\n </div>\r\n }\r\n <div class=\"mb-2\">\r\n @if(hasOwnArea&&filters.showOwnArea){\r\n <div class=\"pt-1\" role=\"tabpanel\" id=\"area-draw\" aria-labelledby=\"tab-draw\">\r\n <div>\r\n <adb-polygon-drawer formControlName=\"geo\"></adb-polygon-drawer>\r\n </div>\r\n </div>\r\n }@else{\r\n <div role=\"tabpanel\" id=\"area-select\" aria-labelledby=\"tab-select\">\r\n <div formArrayName=\"areas\">\r\n <div class=\"d-flex align-items-end mb-1\">\r\n <label>{{'AREAS'|translate}}</label>\r\n <button class=\"ms-auto text-dark btn btn-sm btn-secondary\" attr.aria-label=\"{{'OBSERVATION.ADD_AREA'|translate}}\" title=\"{{'OBSERVATION.ADD_AREA'|translate}}\" type=\"button\" (click)=\"addArea()\">\r\n <span class=\"fas fa-plus\"></span>\r\n </button>\r\n </div>\r\n @for (ctrl of areasArray.controls; track ctrl; let i = $index; let last = $last) {\r\n <div class=\"d-flex mb-2\">\r\n <adb-area-picker id=\"area-{{i}}\" [formControlName]=\"i\"></adb-area-picker>\r\n @if(areasArray.length>1){\r\n <button class=\"btn btn-secondary ms-1\" attr.aria-label=\"{{'DELETE'|translate}}\" title=\"{{'DELETE'|translate}}\" type=\"button\" (click)=\"removeArea(i)\">\r\n <span class=\"fas fa-trash\"></span>\r\n </button>\r\n }\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n </ng-template>\r\n </adb-filter-section>\r\n }\r\n @if(form.get('taxonId')){\r\n @if(filters.showTaxaLists){\r\n <adb-filter-section [count]=\"taxonListCount\" titleResource=\"LISTS.NATURE_CONSERVATION_LISTS\">\r\n <div class=\"mb-3\">\r\n <app-treeview formControlName=\"list\" [nodes]=\"taxaLists\"></app-treeview>\r\n </div>\r\n {{'LISTS.READ_MORE'|translate}}\r\n <a [href]=\"artfakta\">{{'LISTS.LIST_CONTENT'|translate}}</a>\r\n </adb-filter-section>\r\n }\r\n @if(filters.showDatasets){\r\n <adb-filter-section [count]=\"dataSetCount\" titleResource=\"DATASET.DATASETS\">\r\n @if (form.get('ds')) {\r\n <div formArrayName=\"ds\">\r\n @for (item of dsControls; track item; let i = $index) {\r\n <div [formGroupName]=\"i\" class=\"form-check\">\r\n <input type=\"checkbox\" formControlName=\"selected\" [id]=\"'ds-' + item.value.id\" class=\"form-check-input\">\r\n <label [for]=\"'ds-' + item.value.id\" class=\"form-check-label\">{{ 'PROVIDERS.'+item.value.id |translate }}</label>\r\n </div>\r\n }\r\n </div>\r\n }\r\n </adb-filter-section>\r\n }\r\n }\r\n </div>\r\n <div class=\"d-flex justify-content-end gap-2\">\r\n @if (inline) {\r\n <a class=\"btn btn-secondary\" [routerLink]=\"[]\">\r\n {{'CANCEL'|translate}}\r\n </a>\r\n }\r\n <button type=\"submit\" class=\"btn btn-primary\" [disabled]=\"form.invalid||form.pristine\">{{'FILTER'|translate}}</button>\r\n </div>\r\n</form>\r\n}", dependencies: [{ kind: "directive", type: i4.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i4.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i4.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i4.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i4.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i4.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i4.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i4.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i3.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: i4.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i4.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: i4.FormGroupName, selector: "[formGroupName]", inputs: ["formGroupName"] }, { kind: "directive", type: i4.FormArrayName, selector: "[formArrayName]", inputs: ["formArrayName"] }, { kind: "directive", type: i5.AdbDatePickerDirective, selector: "input[adbDatepicker]", inputs: ["format", "toLeft", "settings"], outputs: ["adbBlur"] }, { kind: "component", type: i5.FilterSectionComponent, selector: "adb-filter-section", inputs: ["titleResource", "helpResource", "count", "expanded", "lazy"] }, { kind: "component", type: i5.AdbAreaPickerComponent, selector: "adb-area-picker", inputs: ["placeholder", "useObject", "prefill"] }, { kind: "component", type: i5.AdbTaxonPickerComponent, selector: "adb-taxon-picker", inputs: ["placeholder", "useObject", "prefill"] }, { kind: "component", type: PolygonDrawerInput, selector: "adb-polygon-drawer" }, { kind: "component", type: TreeviewComponent, selector: "app-treeview", inputs: ["nodes"] }, { kind: "pipe", type: i1$1.DatePipe, name: "date" }, { kind: "pipe", type: i1.TranslatePipe, name: "translate" }] });
739
+ }
740
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: AdbMapFilters, decorators: [{
741
+ type: Component,
742
+ args: [{ selector: 'adb-map-filters', standalone: false, template: "@if(form){\r\n<form [formGroup]=\"form\" class=\"form mb-3\" (ngSubmit)=\"onSubmit()\">\r\n <div class=\"mb-3\">\r\n <adb-filter-section titleResource=\"TAXON.TAXA\" [expanded]=\"true\">\r\n @if(filters.showTaxon&&!hasTaxonInUrlParameter){\r\n <div class=\"mb-3\">\r\n <label for=\"species\">{{'TAXON.TAXON'|translate}}</label>\r\n <adb-taxon-picker formControlName=\"taxonId\" id=\"species\"></adb-taxon-picker>\r\n </div>\r\n }\r\n </adb-filter-section>\r\n @if(filters.showTime){\r\n <adb-filter-section [count]=\"periodCount\" titleResource=\"OBSERVATION.PERIOD\">\r\n <div class=\"mb-1\">\r\n <select class=\"form-select\" formControlName=\"period\" id=\"period\">\r\n <option value=\"1\">{{thisYear|date:'yyyy'}}</option>\r\n <option value=\"2\">{{lastYear|date:'yyyy'}}</option>\r\n <option value=\"3\">{{'OBSERVATION.ALL_YEAR'|translate}}</option>\r\n <option value=\"4\">{{'OBSERVATION.LAST_5_YEAR'|translate}}</option>\r\n <option value=\"5\">{{'OBSERVATION.LAST_25_YEAR'|translate}}</option>\r\n <option value=\"6\">{{'OBSERVATION.CUSTOMIZED'|translate}}</option>\r\n </select>\r\n </div>\r\n @if (form.value?.period==='6') {\r\n <div class=\"mb-2\">\r\n <label for=\"startAt\">{{'FROM' |translate}}</label>\r\n <input adbDatepicker type=\"text\" class=\"form-control datepicker\" id=\"startAt\" formControlName=\"startAt\" [settings]=\"dateStartConfig\">\r\n </div>\r\n <div class=\"mb-2\">\r\n <label for=\"endAt\">{{'TO' |translate}}</label>\r\n <input adbDatepicker type=\"text\" class=\"form-control datepicker\" id=\"endAt\" formControlName=\"endAt\" [settings]=\"dateEndConfig\">\r\n </div>\r\n }\r\n </adb-filter-section>\r\n }\r\n @if(filters.showArea){\r\n <adb-filter-section [count]=\"areaCount\" titleResource=\"GEOGRAPHY\" [lazy]=\"true\">\r\n <ng-template>\r\n @if(filters.showOwnArea){\r\n <div role=\"tablist\" aria-label=\"{{'AREA'|translate}}\" class=\"d-flex gap-2 justify-content-end\">\r\n <button class=\"btn btn-link p-0\" [class.text-dark]=\"!hasOwnArea\" (click)=\"hasOwnArea=false\" role=\"tab\" id=\"tab-select\" aria-selected=\"{{!hasOwnArea}}\" aria-controls=\"panel-select\">\r\n {{'CHOOSE'|translate}}\r\n </button>\r\n <button class=\"btn btn-link p-0\" [class.text-dark]=\"hasOwnArea\" (click)=\"hasOwnArea=true\" role=\"tab\" id=\"tab-draw\" aria-selected=\"false\" aria-controls=\"panel-draw\">\r\n {{'OWN_AREA'|translate}}\r\n </button>\r\n </div>\r\n }\r\n <div class=\"mb-2\">\r\n @if(hasOwnArea&&filters.showOwnArea){\r\n <div class=\"pt-1\" role=\"tabpanel\" id=\"area-draw\" aria-labelledby=\"tab-draw\">\r\n <div>\r\n <adb-polygon-drawer formControlName=\"geo\"></adb-polygon-drawer>\r\n </div>\r\n </div>\r\n }@else{\r\n <div role=\"tabpanel\" id=\"area-select\" aria-labelledby=\"tab-select\">\r\n <div formArrayName=\"areas\">\r\n <div class=\"d-flex align-items-end mb-1\">\r\n <label>{{'AREAS'|translate}}</label>\r\n <button class=\"ms-auto text-dark btn btn-sm btn-secondary\" attr.aria-label=\"{{'OBSERVATION.ADD_AREA'|translate}}\" title=\"{{'OBSERVATION.ADD_AREA'|translate}}\" type=\"button\" (click)=\"addArea()\">\r\n <span class=\"fas fa-plus\"></span>\r\n </button>\r\n </div>\r\n @for (ctrl of areasArray.controls; track ctrl; let i = $index; let last = $last) {\r\n <div class=\"d-flex mb-2\">\r\n <adb-area-picker id=\"area-{{i}}\" [formControlName]=\"i\"></adb-area-picker>\r\n @if(areasArray.length>1){\r\n <button class=\"btn btn-secondary ms-1\" attr.aria-label=\"{{'DELETE'|translate}}\" title=\"{{'DELETE'|translate}}\" type=\"button\" (click)=\"removeArea(i)\">\r\n <span class=\"fas fa-trash\"></span>\r\n </button>\r\n }\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n </ng-template>\r\n </adb-filter-section>\r\n }\r\n @if(form.get('taxonId')){\r\n @if(filters.showTaxaLists){\r\n <adb-filter-section [count]=\"taxonListCount\" titleResource=\"LISTS.NATURE_CONSERVATION_LISTS\">\r\n <div class=\"mb-3\">\r\n <app-treeview formControlName=\"list\" [nodes]=\"taxaLists\"></app-treeview>\r\n </div>\r\n {{'LISTS.READ_MORE'|translate}}\r\n <a [href]=\"artfakta\">{{'LISTS.LIST_CONTENT'|translate}}</a>\r\n </adb-filter-section>\r\n }\r\n @if(filters.showDatasets){\r\n <adb-filter-section [count]=\"dataSetCount\" titleResource=\"DATASET.DATASETS\">\r\n @if (form.get('ds')) {\r\n <div formArrayName=\"ds\">\r\n @for (item of dsControls; track item; let i = $index) {\r\n <div [formGroupName]=\"i\" class=\"form-check\">\r\n <input type=\"checkbox\" formControlName=\"selected\" [id]=\"'ds-' + item.value.id\" class=\"form-check-input\">\r\n <label [for]=\"'ds-' + item.value.id\" class=\"form-check-label\">{{ 'PROVIDERS.'+item.value.id |translate }}</label>\r\n </div>\r\n }\r\n </div>\r\n }\r\n </adb-filter-section>\r\n }\r\n }\r\n </div>\r\n <div class=\"d-flex justify-content-end gap-2\">\r\n @if (inline) {\r\n <a class=\"btn btn-secondary\" [routerLink]=\"[]\">\r\n {{'CANCEL'|translate}}\r\n </a>\r\n }\r\n <button type=\"submit\" class=\"btn btn-primary\" [disabled]=\"form.invalid||form.pristine\">{{'FILTER'|translate}}</button>\r\n </div>\r\n</form>\r\n}" }]
743
+ }], ctorParameters: () => [{ type: i0.ChangeDetectorRef }, { type: AdbMapConfigService }, { type: i3$1.HttpClient }, { type: i3.Router }, { type: i3.ActivatedRoute }], propDecorators: { inline: [{
744
+ type: Input
745
+ }] } });
746
+
747
+ class AdbMapModule {
748
+ static forRoot(config) {
749
+ return {
750
+ ngModule: AdbMapModule,
751
+ providers: [
752
+ {
753
+ provide: ADB_MAP_CONFIG,
754
+ useValue: config
755
+ }
756
+ ]
757
+ };
758
+ }
759
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: AdbMapModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
760
+ static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.0", ngImport: i0, type: AdbMapModule, declarations: [AdbObsMapComponent,
761
+ PolygonDrawerInput,
762
+ AdbMapFilters,
763
+ TreeviewComponent], imports: [CommonModule,
764
+ FormsModule,
765
+ RouterModule,
766
+ ReactiveFormsModule,
767
+ AdbDatePickerModule, i1.TranslateModule, AdbPipesModule,
768
+ AdbFilterSectionModule,
769
+ AdbPickerModule], exports: [AdbObsMapComponent,
770
+ PolygonDrawerInput,
771
+ AdbMapFilters] });
772
+ static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: AdbMapModule, providers: [
773
+ AdbMapConfigService
774
+ ], imports: [CommonModule,
775
+ FormsModule,
776
+ RouterModule,
777
+ ReactiveFormsModule,
778
+ AdbDatePickerModule,
779
+ TranslateModule.forChild(),
780
+ AdbPipesModule,
781
+ AdbFilterSectionModule,
782
+ AdbPickerModule] });
783
+ }
784
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: AdbMapModule, decorators: [{
785
+ type: NgModule,
786
+ args: [{
787
+ imports: [
788
+ CommonModule,
789
+ FormsModule,
790
+ RouterModule,
791
+ ReactiveFormsModule,
792
+ AdbDatePickerModule,
793
+ TranslateModule.forChild(),
794
+ AdbPipesModule,
795
+ AdbFilterSectionModule,
796
+ AdbPickerModule
797
+ ],
798
+ declarations: [
799
+ AdbObsMapComponent,
800
+ PolygonDrawerInput,
801
+ AdbMapFilters,
802
+ TreeviewComponent,
803
+ // Stats,
804
+ // AdbGeojsonPolygonDirective
805
+ ],
806
+ exports: [
807
+ AdbObsMapComponent,
808
+ PolygonDrawerInput,
809
+ AdbMapFilters,
810
+ // Stats,
811
+ // AdbGeojsonPolygonDirective
812
+ ],
813
+ providers: [
814
+ AdbMapConfigService
815
+ ]
816
+ }]
817
+ }] });
818
+
819
+ /**
820
+ * Generated bundle index. Do not edit.
821
+ */
822
+
823
+ export { ADB_MAP_CONFIG, AdbMapConfigService, AdbMapFilterType, AdbMapFilters, AdbMapModule, AdbMapUtils, AdbObsMapComponent, PolygonDrawerInput, VisibleFilters };
824
+ //# sourceMappingURL=adb-shared-src-map.mjs.map